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