logo

utils

~/.local/bin tools and git-hooks git clone https://hacktivis.me/git/utils.git

touch.c (2722B)


  1. // Collection of Unix tools, comparable to coreutils
  2. // SPDX-FileCopyrightText: 2023 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
  3. // SPDX-License-Identifier: MPL-2.0
  4. #define _POSIX_C_SOURCE 200809L // O_NOFOLLOW
  5. #include "../lib/iso_parse.h" /* iso_parse */
  6. #include <errno.h> /* errno */
  7. #include <fcntl.h> /* open */
  8. #include <stdbool.h> /* bool */
  9. #include <stdio.h> /* perror */
  10. #include <sys/stat.h> /* futimens, stat, utimensat */
  11. #include <unistd.h> /* getopt, opt*, close */
  12. int
  13. main(int argc, char *argv[])
  14. {
  15. bool ch_atime = false, ch_mtime = false;
  16. char *ref_file = NULL;
  17. struct timespec times[2] = {
  18. {.tv_sec = 0, .tv_nsec = UTIME_OMIT}, // access
  19. {.tv_sec = 0, .tv_nsec = UTIME_OMIT} // modification
  20. };
  21. struct timespec target = {0, UTIME_NOW};
  22. int open_flags = O_WRONLY | O_CREAT | O_NOCTTY;
  23. int utimensat_flags = 0;
  24. int c = 0;
  25. while((c = getopt(argc, argv, ":achmr:t:d:")) != -1)
  26. {
  27. switch(c)
  28. {
  29. case 'a':
  30. ch_atime = true;
  31. break;
  32. case 'c':
  33. open_flags ^= O_CREAT;
  34. break;
  35. case 'h':
  36. open_flags |= O_NOFOLLOW;
  37. utimensat_flags |= AT_SYMLINK_NOFOLLOW;
  38. break;
  39. case 'm':
  40. ch_mtime = true;
  41. break;
  42. case 'r':
  43. ref_file = optarg;
  44. break;
  45. case 't': // [[CC]YY]MMDDhhmm[.SS]
  46. // Too legacy of a format, too annoying to parse
  47. fprintf(stderr, "touch: Option -d not supported, use -t\n");
  48. return 1;
  49. break;
  50. case 'd':
  51. target = iso_parse(optarg);
  52. break;
  53. case ':':
  54. fprintf(stderr, "touch: Error: Missing operand for option: '-%c'\n", optopt);
  55. return 1;
  56. case '?':
  57. fprintf(stderr, "touch: Error: Unrecognised option: '-%c'\n", optopt);
  58. return 1;
  59. }
  60. }
  61. argc -= optind;
  62. argv += optind;
  63. // When neither -a nor -m are specified, change both
  64. if(!ch_atime && !ch_mtime)
  65. {
  66. ch_atime = true;
  67. ch_mtime = true;
  68. }
  69. if(ref_file == NULL)
  70. {
  71. if(ch_atime) times[0] = target;
  72. if(ch_mtime) times[1] = target;
  73. }
  74. else
  75. {
  76. struct stat ref;
  77. if(stat(ref_file, &ref) != 0)
  78. {
  79. perror("touch: stat");
  80. return 1;
  81. }
  82. if(ch_atime)
  83. {
  84. times[0] = ref.st_atim;
  85. }
  86. if(ch_mtime)
  87. {
  88. times[1] = ref.st_mtim;
  89. }
  90. }
  91. for(int i = 0; i < argc; i++)
  92. {
  93. int fd = open(argv[i], open_flags, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
  94. if(fd == -1)
  95. {
  96. if(errno == EISDIR)
  97. {
  98. if(utimensat(AT_FDCWD, argv[i], times, utimensat_flags) != 0)
  99. {
  100. perror("touch: utimensat");
  101. return 1;
  102. }
  103. return 0;
  104. }
  105. if(errno != ENOENT) perror("touch: open");
  106. return 1;
  107. }
  108. if(futimens(fd, times) != 0)
  109. {
  110. perror("touch: futimens");
  111. return 1;
  112. }
  113. if(close(fd) != 0)
  114. {
  115. perror("touch: close");
  116. return 1;
  117. }
  118. }
  119. return 0;
  120. }