touch.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  1. /*++
  2. Copyright (c) 2013 Minoca Corp. All Rights Reserved
  3. Module Name:
  4. touch.c
  5. Abstract:
  6. This module implements support for the "touch" utility.
  7. Author:
  8. Evan Green 16-Aug-2013
  9. Environment:
  10. POSIX
  11. --*/
  12. //
  13. // ------------------------------------------------------------------- Includes
  14. //
  15. #include <minoca/lib/types.h>
  16. #include <assert.h>
  17. #include <errno.h>
  18. #include <fcntl.h>
  19. #include <getopt.h>
  20. #include <stdlib.h>
  21. #include <string.h>
  22. #include <sys/stat.h>
  23. #include <unistd.h>
  24. #include <utime.h>
  25. #include "swlib.h"
  26. //
  27. // ---------------------------------------------------------------- Definitions
  28. //
  29. //
  30. // Define the year after which it's assumed the century is 1900.
  31. //
  32. #define TWO_DIGIT_YEAR_CUTOFF 70
  33. #define TOUCH_VERSION_MAJOR 1
  34. #define TOUCH_VERSION_MINOR 0
  35. #define TOUCH_USAGE \
  36. "usage: touch [-acm][-r reference_file | -t time] file...\n\n" \
  37. "The touch utility shall change the modification time, access time, or \n" \
  38. "both of a file. It can also be used to create new files. If neither \n" \
  39. "-a nor -m is specified, touch behaves as if both are specified. \n" \
  40. "Options are:\n" \
  41. " -a -- Change the access time of a file.\n" \
  42. " -c, --no-create -- Do not create the file if it does not exist.\n" \
  43. " -m -- Change the modification time of the file.\n" \
  44. " -r, --reference <reference file> -- Use the corresponding time of \n" \
  45. " the given reference file instead of the current time.\n" \
  46. " -t, --time <time> -- Use the specified time instead of the current \n" \
  47. " time. The time option shall be a decimal number of the form:\n" \
  48. " [[CC]YY]MMDDhhmm[.SS]. If the century is not given but the \n" \
  49. " year is, then years >70 are in the 1900s.\n" \
  50. " --help -- Display this help text and exit.\n" \
  51. " --version -- Display the version number and exit.\n\n"
  52. #define TOUCH_OPTIONS_STRING "acmr:t:"
  53. //
  54. // Define touch options.
  55. //
  56. //
  57. // Set this flag to change the access time.
  58. //
  59. #define TOUCH_OPTION_ACCESS_TIME 0x00000001
  60. //
  61. // Set this flag to change the modification time.
  62. //
  63. #define TOUCH_OPTION_MODIFICATION_TIME 0x00000002
  64. //
  65. // Set this flag to avoid creating the file if it doesn't exist.
  66. //
  67. #define TOUCH_OPTION_NO_CREATE 0x00000004
  68. //
  69. // Define the permissions used for files created by touch.
  70. //
  71. #define TOUCH_CREATE_PERMISSIONS \
  72. (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH | S_IWOTH)
  73. //
  74. // Define the length of the touch date strings, which follows the format
  75. // [CC]YYMMDDhhmm when ignoring the [.SS] on the end.
  76. //
  77. #define TOUCH_DATE_BASE_LENGTH 10
  78. #define TOUCH_DATE_FULL_YEAR_LENGTH 12
  79. //
  80. // ------------------------------------------------------ Data Type Definitions
  81. //
  82. //
  83. // ----------------------------------------------- Internal Function Prototypes
  84. //
  85. INT
  86. TouchParseTimeString (
  87. PSTR TimeString,
  88. time_t *Time
  89. );
  90. //
  91. // -------------------------------------------------------------------- Globals
  92. //
  93. struct option TouchLongOptions[] = {
  94. {"no-create", no_argument, 0, 'c'},
  95. {"reference", required_argument, 0, 'r'},
  96. {"time", required_argument, 0, 't'},
  97. {"help", no_argument, 0, 'h'},
  98. {"version", no_argument, 0, 'V'},
  99. {NULL, 0, 0, 0}
  100. };
  101. //
  102. // ------------------------------------------------------------------ Functions
  103. //
  104. INT
  105. TouchMain (
  106. INT ArgumentCount,
  107. CHAR **Arguments
  108. )
  109. /*++
  110. Routine Description:
  111. This routine is the main entry point for the touch utility.
  112. Arguments:
  113. ArgumentCount - Supplies the number of command line arguments the program
  114. was invoked with.
  115. Arguments - Supplies a tokenized array of command line arguments.
  116. Return Value:
  117. Returns an integer exit code. 0 for success, nonzero otherwise.
  118. --*/
  119. {
  120. PSTR Argument;
  121. ULONG ArgumentIndex;
  122. PSTR FirstSource;
  123. time_t NewAccessTime;
  124. time_t NewModificationTime;
  125. struct utimbuf NewTimes;
  126. int OpenDescriptor;
  127. INT Option;
  128. ULONG Options;
  129. PSTR ReferenceFile;
  130. struct stat Stat;
  131. int Status;
  132. PSTR TimeString;
  133. int TotalStatus;
  134. FirstSource = NULL;
  135. Options = 0;
  136. ReferenceFile = NULL;
  137. Status = 0;
  138. TimeString = NULL;
  139. TotalStatus = 0;
  140. //
  141. // Process the control arguments.
  142. //
  143. while (TRUE) {
  144. Option = getopt_long(ArgumentCount,
  145. Arguments,
  146. TOUCH_OPTIONS_STRING,
  147. TouchLongOptions,
  148. NULL);
  149. if (Option == -1) {
  150. break;
  151. }
  152. if ((Option == '?') || (Option == ':')) {
  153. Status = 1;
  154. return Status;
  155. }
  156. switch (Option) {
  157. case 'a':
  158. Options |= TOUCH_OPTION_ACCESS_TIME;
  159. break;
  160. case 'c':
  161. Options |= TOUCH_OPTION_NO_CREATE;
  162. break;
  163. case 'm':
  164. Options |= TOUCH_OPTION_MODIFICATION_TIME;
  165. break;
  166. case 'r':
  167. ReferenceFile = optarg;
  168. assert(ReferenceFile != NULL);
  169. break;
  170. case 't':
  171. TimeString = optarg;
  172. assert(TimeString != NULL);
  173. break;
  174. case 'V':
  175. SwPrintVersion(TOUCH_VERSION_MAJOR, TOUCH_VERSION_MINOR);
  176. return 1;
  177. case 'h':
  178. printf(TOUCH_USAGE);
  179. return 1;
  180. default:
  181. assert(FALSE);
  182. Status = 1;
  183. return Status;
  184. }
  185. }
  186. ArgumentIndex = optind;
  187. if (ArgumentIndex > ArgumentCount) {
  188. ArgumentIndex = ArgumentCount;
  189. }
  190. if (ArgumentIndex < ArgumentCount) {
  191. FirstSource = Arguments[ArgumentIndex];
  192. }
  193. //
  194. // Fail if there's nothing to touch.
  195. //
  196. if (FirstSource == NULL) {
  197. SwPrintError(0, NULL, "Argument expected. Try --help for usage");
  198. return 1;
  199. }
  200. //
  201. // If neither access nor modification time was specified, have a can-do
  202. // attitude and do both.
  203. //
  204. if ((Options &
  205. (TOUCH_OPTION_ACCESS_TIME | TOUCH_OPTION_MODIFICATION_TIME)) == 0) {
  206. Options |= TOUCH_OPTION_ACCESS_TIME | TOUCH_OPTION_MODIFICATION_TIME;
  207. }
  208. //
  209. // If there was a reference file, use that to get the times.
  210. //
  211. if (ReferenceFile != NULL) {
  212. Status = SwStat(ReferenceFile, TRUE, &Stat);
  213. if (Status != 0) {
  214. SwPrintError(Status,
  215. ReferenceFile,
  216. "Unable to stat reference file");
  217. return Status;
  218. }
  219. NewAccessTime = Stat.st_atime;
  220. NewModificationTime = Stat.st_mtime;
  221. //
  222. // If a time was specified, parse that out.
  223. //
  224. } else if (TimeString != NULL) {
  225. Status = TouchParseTimeString(TimeString, &NewAccessTime);
  226. if (Status != 0) {
  227. SwPrintError(Status, TimeString, "Unable to parse time");
  228. return Status;
  229. }
  230. NewModificationTime = NewAccessTime;
  231. //
  232. // If nothing was specified, use the current time.
  233. //
  234. } else {
  235. NewAccessTime = time(NULL);
  236. NewModificationTime = NewAccessTime;
  237. }
  238. //
  239. // Loop through the arguments again and perform the touching.
  240. //
  241. while (ArgumentIndex < ArgumentCount) {
  242. Argument = Arguments[ArgumentIndex];
  243. ArgumentIndex += 1;
  244. OpenDescriptor = -1;
  245. //
  246. // Stat the file. If that was unsuccessful, attempt to create it.
  247. //
  248. Status = SwStat(Argument, TRUE, &Stat);
  249. if (Status != 0) {
  250. if ((Options & TOUCH_OPTION_NO_CREATE) != 0) {
  251. continue;
  252. }
  253. OpenDescriptor = creat(Argument, TOUCH_CREATE_PERMISSIONS);
  254. if (OpenDescriptor < 0) {
  255. TotalStatus = errno;
  256. SwPrintError(TotalStatus, Argument, "Cannot create");
  257. }
  258. Status = SwStat(Argument, TRUE, &Stat);
  259. if (OpenDescriptor >= 0) {
  260. close(OpenDescriptor);
  261. OpenDescriptor = -1;
  262. }
  263. if (Status != 0) {
  264. if (TotalStatus == 0) {
  265. TotalStatus = Status;
  266. }
  267. SwPrintError(Status, Argument, "Cannot stat");
  268. continue;
  269. }
  270. }
  271. //
  272. // To get here the stat must have succeeded. Set the new file times to
  273. // the current file times, and then update the desired ones.
  274. //
  275. assert(Status == 0);
  276. NewTimes.actime = Stat.st_atime;
  277. NewTimes.modtime = Stat.st_mtime;
  278. if ((Options & TOUCH_OPTION_ACCESS_TIME) != 0) {
  279. NewTimes.actime = NewAccessTime;
  280. }
  281. if ((Options & TOUCH_OPTION_MODIFICATION_TIME) != 0) {
  282. NewTimes.modtime = NewModificationTime;
  283. }
  284. Status = utime(Argument, &NewTimes);
  285. if (Status != 0) {
  286. Status = errno;
  287. if (TotalStatus == 0) {
  288. TotalStatus = Status;
  289. }
  290. SwPrintError(Status, Argument, "Failed to touch");
  291. }
  292. }
  293. if ((TotalStatus == 0) && (Status != 0)) {
  294. TotalStatus = Status;
  295. }
  296. return TotalStatus;
  297. }
  298. //
  299. // --------------------------------------------------------- Internal Functions
  300. //
  301. INT
  302. TouchParseTimeString (
  303. PSTR TimeString,
  304. time_t *Time
  305. )
  306. /*++
  307. Routine Description:
  308. This routine parses a touch command line timestamp in the form
  309. [[CC]YY]MMDDhhmm[.SS].
  310. Arguments:
  311. TimeString - Supplies a pointer to the time string to parse.
  312. Time - Supplies a pointer where the time will be returned.
  313. Return Value:
  314. 0 on success.
  315. EINVAL if the format was invalid.
  316. --*/
  317. {
  318. PSTR AfterScan;
  319. size_t BaseLength;
  320. struct tm Fields;
  321. PSTR Period;
  322. CHAR Portion[5];
  323. LONG Value;
  324. memset(&Fields, 0, sizeof(Fields));
  325. Period = strchr(TimeString, '.');
  326. if (Period != NULL) {
  327. BaseLength = (UINTN)Period - (UINTN)TimeString;
  328. } else {
  329. BaseLength = strlen(TimeString);
  330. }
  331. if ((BaseLength != TOUCH_DATE_BASE_LENGTH) &&
  332. (BaseLength != TOUCH_DATE_FULL_YEAR_LENGTH)) {
  333. return EINVAL;
  334. }
  335. //
  336. // Grab the year, either with or without the century.
  337. //
  338. if (BaseLength == TOUCH_DATE_FULL_YEAR_LENGTH) {
  339. memcpy(Portion, TimeString, 4);
  340. Portion[4] = '\0';
  341. Value = strtol(Portion, &AfterScan, 10);
  342. if ((Value < 0) || (AfterScan == Portion)) {
  343. return EINVAL;
  344. }
  345. Fields.tm_year = Value - 1900;
  346. TimeString += 4;
  347. } else {
  348. memcpy(Portion, TimeString, 2);
  349. Portion[2] = '\0';
  350. Value = strtol(Portion, &AfterScan, 10);
  351. if ((Value < 0) || (AfterScan == Portion)) {
  352. return EINVAL;
  353. }
  354. //
  355. // Below a certain value is assumed to be in the 2000s, otherwise it
  356. // must be in the 1900s. Way down the line this will have to change.
  357. //
  358. if (Value < TWO_DIGIT_YEAR_CUTOFF) {
  359. Fields.tm_year = 100 + Value;
  360. } else {
  361. Fields.tm_year = Value;
  362. }
  363. TimeString += 2;
  364. }
  365. //
  366. // Scan the month.
  367. //
  368. memcpy(Portion, TimeString, 2);
  369. Portion[2] = '\0';
  370. TimeString += 2;
  371. Value = strtol(Portion, &AfterScan, 10);
  372. if ((Value <= 0) || (AfterScan == Portion)) {
  373. return EINVAL;
  374. }
  375. Fields.tm_mon = Value - 1;
  376. //
  377. // Scan the day of the month.
  378. //
  379. memcpy(Portion, TimeString, 2);
  380. Portion[2] = '\0';
  381. TimeString += 2;
  382. Value = strtol(Portion, &AfterScan, 10);
  383. if ((Value <= 0) || (AfterScan == Portion)) {
  384. return EINVAL;
  385. }
  386. Fields.tm_mday = Value;
  387. //
  388. // Scan the hour.
  389. //
  390. memcpy(Portion, TimeString, 2);
  391. Portion[2] = '\0';
  392. TimeString += 2;
  393. Value = strtol(Portion, &AfterScan, 10);
  394. if ((Value < 0) || (AfterScan == Portion)) {
  395. return EINVAL;
  396. }
  397. Fields.tm_hour = Value;
  398. //
  399. // Scan the minute.
  400. //
  401. memcpy(Portion, TimeString, 2);
  402. Portion[2] = '\0';
  403. TimeString += 2;
  404. Value = strtol(Portion, &AfterScan, 10);
  405. if ((Value < 0) || (AfterScan == Portion)) {
  406. return EINVAL;
  407. }
  408. Fields.tm_min = Value;
  409. //
  410. // If there was a period, scan the second as well.
  411. //
  412. if (Period != NULL) {
  413. Period += 1;
  414. Value = strtol(Period, &AfterScan, 10);
  415. if ((Value < 0) || (AfterScan == Period)) {
  416. return EINVAL;
  417. }
  418. Fields.tm_sec = Value;
  419. }
  420. //
  421. // Convert the time fields to a time value.
  422. //
  423. *Time = mktime(&Fields);
  424. if (*Time == -1) {
  425. return errno;
  426. }
  427. return 0;
  428. }