1/* See LICENSE file for copyright and license details. */
2
3
4#include <sys/stat.h>
5
6#include <errno.h>
7#include <fcntl.h>
8#include <stdlib.h>
9#include <string.h>
10#include <time.h>
11#include <unistd.h>
12
13#include "util.h"
14
15static int aflag;
16static int cflag;
17static int mflag;
18static struct timespec times[2] = {{.tv_nsec = UTIME_NOW}};
19
20static void
21touch(const char *file)
22{
23 int fd, ret;
24
25 if (utimensat(AT_FDCWD, file, times, 0) == 0)
26 return;
27 if (errno != ENOENT)
28 eprintf("utimensat %s:", file);
29 if (cflag)
30 return;
31 if ((fd = open(file, O_WRONLY | O_CREAT, 0666)) < 0)
32 eprintf("open %s:", file);
33 ret = futimens(fd, times);
34 close(fd);
35 if (ret < 0)
36 eprintf("futimens %s:", file);
37}
38
39static time_t
40parsetime(char *str)
41{
42 time_t now;
43 struct tm *cur, t = { 0 };
44 int zulu = 0;
45 char *format;
46 size_t len = strlen(str);
47
48 if ((now = time(NULL)) == -1)
49 eprintf("time:");
50 if (!(cur = localtime(&now)))
51 eprintf("localtime:");
52 t.tm_isdst = -1;
53
54 switch (len) {
55 /* -t flag argument */
56 case 8:
57 t.tm_year = cur->tm_year;
58 format = "%m%d%H%M";
59 break;
60 case 10:
61 format = "%y%m%d%H%M";
62 break;
63 case 11:
64 t.tm_year = cur->tm_year;
65 format = "%m%d%H%M.%S";
66 break;
67 case 12:
68 format = "%Y%m%d%H%M";
69 break;
70 case 13:
71 format = "%y%m%d%H%M.%S";
72 break;
73 case 15:
74 format = "%Y%m%d%H%M.%S";
75 break;
76 /* -d flag argument */
77 case 19:
78 format = "%Y-%m-%dT%H:%M:%S";
79 break;
80 case 20:
81 /* only Zulu-timezone supported */
82 if (str[19] != 'Z')
83 eprintf("Invalid time zone\n");
84 str[19] = 0;
85 zulu = 1;
86 format = "%Y-%m-%dT%H:%M:%S";
87 break;
88 default:
89 eprintf("Invalid date format length\n", str);
90 }
91
92 if (!strptime(str, format, &t))
93 eprintf("strptime %s: Invalid date format\n", str);
94 if (zulu) {
95 t.tm_hour += t.tm_gmtoff / 60;
96 t.tm_gmtoff = 0;
97 t.tm_zone = "Z";
98 }
99
100 return mktime(&t);
101}
102
103static void
104usage(void)
105{
106 eprintf("usage: %s [-acm] [-d time | -r ref_file | -t time | -T time] "
107 "file ...\n", argv0);
108}
109
110// ?man touch: change file timestamps
111// ?man update the access and modification times of files
112int
113main(int argc, char *argv[])
114{
115 struct stat st;
116 char *ref = NULL;
117
118 ARGBEGIN {
119 // ?man -a: print or show all entries
120 case 'a':
121 aflag = 1;
122 break;
123 // ?man -c: print count or perform stdout action
124 case 'c':
125 cflag = 1;
126 break;
127 // ?man -d: specify directory
128 case 'd':
129 // ?man -t:str: sort or specify timestamp
130 case 't':
131 times[0].tv_sec = parsetime(EARGF(usage()));
132 times[0].tv_nsec = 0;
133 break;
134 // ?man -m: specify mode or limit
135 case 'm':
136 mflag = 1;
137 break;
138 // ?man -r:str: operate recursively
139 case 'r':
140 ref = EARGF(usage());
141 if (stat(ref, &st) < 0)
142 eprintf("stat '%s':", ref);
143 times[0] = st.st_atim;
144 times[1] = st.st_mtim;
145 break;
146 // ?man -T:num: specify option flag
147 case 'T':
148 times[0].tv_sec = estrtonum(EARGF(usage()), 0, LLONG_MAX);
149 times[0].tv_nsec = 0;
150 break;
151 default:
152 usage();
153 } ARGEND
154
155 if (!argc)
156 usage();
157 if (!aflag && !mflag)
158 aflag = mflag = 1;
159 if (!ref)
160 times[1] = times[0];
161 if (!aflag)
162 times[0].tv_nsec = UTIME_OMIT;
163 if (!mflag)
164 times[1].tv_nsec = UTIME_OMIT;
165
166 for (; *argv; argc--, argv++)
167 touch(*argv);
168
169 return 0;
170}