1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22 /*
23 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
25 */
26
27 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
28 /* All Rights Reserved */
29
30 /* Copyright (c) 1987, 1988 Microsoft Corporation */
31 /* All Rights Reserved */
32
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <stdio.h>
36 #include <string.h>
37 #include <strings.h>
38 #include <stdlib.h>
39 #include <ctype.h>
40 #include <libgen.h>
41 #include <fcntl.h>
42 #include <pwd.h>
43 #include <time.h>
44 #include <unistd.h>
45 #include <locale.h>
46 #include <sys/time.h>
47 #include <errno.h>
48
49 #define BADTIME "bad time specification"
50
51 static char *myname;
52
53 static int isnumber(char *);
54 static int atoi_for2(char *);
55 static void usage(const int);
56 static void touchabort(const char *);
57 static void parse_datetime(char *, timespec_t *);
58 static void parse_time(char *, timespec_t *);
59 static void parse_timespec(char *, timespec_t *);
60
61 int
main(int argc,char * argv[])62 main(int argc, char *argv[])
63 {
64 int c;
65
66 int aflag = 0;
67 int cflag = 0;
68 int rflag = 0;
69 int mflag = 0;
70 int tflag = 0;
71 int stflag = 0;
72 int status = 0;
73 int usecurrenttime = 1;
74 int timespecified;
75 int optc;
76 int fd = -1;
77 mode_t cmode =
78 (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
79 struct stat stbuf;
80 struct stat prstbuf;
81 timespec_t times[2];
82 timespec_t *tsp;
83
84 (void) setlocale(LC_ALL, "");
85 #if !defined(TEXT_DOMAIN)
86 #define TEXT_DOMAIN "SYS_TEST"
87 #endif
88 (void) textdomain(TEXT_DOMAIN);
89
90 myname = basename(argv[0]);
91 if (strcmp(myname, "settime") == 0) {
92 cflag++;
93 stflag++;
94 while ((optc = getopt(argc, argv, "f:")) != EOF) {
95 switch (optc) {
96 case 'f':
97 rflag++;
98 usecurrenttime = 0;
99 if (stat(optarg, &prstbuf) == -1) {
100 (void) fprintf(stderr, "%s: ", myname);
101 perror(optarg);
102 return (2);
103 }
104 break;
105 case '?':
106 usage(stflag);
107 break;
108 }
109 }
110 } else {
111 while ((optc = getopt(argc, argv, "acfmr:d:t:")) != EOF) {
112 switch (optc) {
113 case 'a':
114 aflag++;
115 break;
116 case 'c':
117 cflag++;
118 break;
119 case 'f': /* silently ignore for UCB compat */
120 break;
121 case 'm':
122 mflag++;
123 break;
124 case 'r': /* same as settime's -f option */
125 rflag++;
126 usecurrenttime = 0;
127 if (stat(optarg, &prstbuf) == -1) {
128 (void) fprintf(stderr, "%s: ", myname);
129 perror(optarg);
130 return (2);
131 }
132 break;
133 case 'd':
134 tflag++;
135 usecurrenttime = 0;
136 parse_datetime(optarg, &prstbuf.st_mtim);
137 prstbuf.st_atim = prstbuf.st_mtim;
138 break;
139 case 't':
140 tflag++;
141 usecurrenttime = 0;
142 parse_time(optarg, &prstbuf.st_mtim);
143 prstbuf.st_atim = prstbuf.st_mtim;
144 break;
145 case '?':
146 usage(stflag);
147 break;
148 }
149 }
150 }
151
152 argc -= optind;
153 argv += optind;
154
155 if ((argc < 1) || (rflag + tflag > 1))
156 usage(stflag);
157
158 if ((aflag == 0) && (mflag == 0)) {
159 aflag = 1;
160 mflag = 1;
161 }
162 if ((aflag && !mflag) || (mflag && !aflag))
163 usecurrenttime = 0;
164
165 /*
166 * If -r, -t or -d has been specified,
167 * use the specified time.
168 */
169 timespecified = (rflag | tflag);
170
171 if (timespecified == 0 && argc >= 2 && isnumber(*argv) &&
172 (strlen(*argv) == 8 || strlen(*argv) == 10)) {
173 /*
174 * time is specified as an operand; use it.
175 */
176 parse_timespec(*argv++, &prstbuf.st_mtim);
177 prstbuf.st_atim = prstbuf.st_mtim;
178 usecurrenttime = 0;
179 timespecified = 1;
180 argc--;
181 }
182
183 for (c = 0; c < argc; c++) {
184 if (stat(argv[c], &stbuf)) {
185 /*
186 * If stat failed for reasons other than EOVERFLOW or
187 * ENOENT, the file should not be created, since this
188 * can clobber the contents of an existing file.
189 */
190 if (errno == EOVERFLOW) {
191 /*
192 * Since we have EOVERFLOW,
193 * we know the file exists.
194 */
195 /* EMPTY */;
196 } else if (errno != ENOENT) {
197 (void) fprintf(stderr,
198 gettext("%s: cannot stat %s: %s\n"),
199 myname, argv[c], strerror(errno));
200 status++;
201 continue;
202 } else if (cflag) {
203 continue;
204 } else if ((fd = creat(argv[c], cmode)) < 0) {
205 (void) fprintf(stderr,
206 gettext("%s: cannot create %s: %s\n"),
207 myname, argv[c], strerror(errno));
208 status++;
209 continue;
210 }
211 }
212
213 if (usecurrenttime) {
214 tsp = NULL;
215 } else {
216 if (mflag == 0) {
217 /* Keep the mtime of the file */
218 times[1].tv_nsec = UTIME_OMIT;
219 } else if (timespecified) {
220 /* Set the specified time */
221 times[1] = prstbuf.st_mtim;
222 } else {
223 /* Otherwise, use the current time */
224 times[1].tv_nsec = UTIME_NOW;
225 }
226
227 if (aflag == 0) {
228 /* Keep the atime of the file */
229 times[0].tv_nsec = UTIME_OMIT;
230 } else if (timespecified) {
231 /* Set the specified time */
232 times[0] = prstbuf.st_atim;
233 } else {
234 /* Otherwise, use the current time */
235 times[0].tv_nsec = UTIME_NOW;
236 }
237
238 tsp = times;
239 }
240
241 if ((fd >= 0 && futimens(fd, tsp) != 0) ||
242 (fd < 0 && utimensat(AT_FDCWD, argv[c], tsp, 0) != 0)) {
243 (void) fprintf(stderr,
244 gettext("%s: cannot change times on %s: %s\n"),
245 myname, argv[c], strerror(errno));
246 status++;
247 }
248 if (fd >= 0) {
249 (void) close(fd);
250 fd = -1;
251 }
252 }
253 return (status);
254 }
255
256 static int
isnumber(char * s)257 isnumber(char *s)
258 {
259 int c;
260
261 while ((c = *s++) != '\0')
262 if (!isdigit(c))
263 return (0);
264 return (1);
265 }
266
267 static void
parse_datetime(char * t,timespec_t * ts)268 parse_datetime(char *t, timespec_t *ts)
269 {
270 char date[64];
271 char *year;
272 char *month;
273 char *day;
274 char *hour;
275 char *minute;
276 char *second;
277 char *fraction;
278 int utc = 0;
279 char *p;
280 time_t when;
281 int nanoseconds;
282 struct tm tm;
283
284 /*
285 * The date string has the format (defined by the touch(1) spec):
286 * YYYY-MM-DDThh:mm:SS[.frac][tz]
287 * YYYY-MM-DDThh:mm:SS[,frac][tz]
288 * T is either the literal 'T' or is a space character.
289 * tz is either empty (local time) or the literal 'Z' (UTC).
290 * All other fields are strings of digits.
291 */
292
293 /*
294 * Make a copy of the date string so it can be tokenized.
295 */
296 if (strlcpy(date, t, sizeof (date)) >= sizeof (date))
297 touchabort(BADTIME);
298
299 /* deal with the optional trailing 'Z' first */
300 p = date + strlen(date) - 1;
301 if (*p == 'Z') {
302 utc = 1;
303 *p = '\0';
304 }
305
306 /* break out the component tokens */
307 p = date;
308 year = strsep(&p, "-");
309 month = strsep(&p, "-");
310 day = strsep(&p, "T ");
311 hour = strsep(&p, ":");
312 minute = strsep(&p, ":");
313 second = strsep(&p, ".,");
314 fraction = p;
315
316 /* verify the component tokens */
317 if (year == NULL || strlen(year) < 4 || !isnumber(year) ||
318 month == NULL || strlen(month) != 2 || !isnumber(month) ||
319 day == NULL || strlen(day) != 2 || !isnumber(day) ||
320 hour == NULL || strlen(hour) != 2 || !isnumber(hour) ||
321 minute == NULL || strlen(minute) != 2 || !isnumber(minute) ||
322 second == NULL || strlen(second) != 2 || !isnumber(second) ||
323 (fraction != NULL && (*fraction == '\0' || !isnumber(fraction))))
324 touchabort(BADTIME);
325
326 (void) memset(&tm, 0, sizeof (struct tm));
327
328 tm.tm_year = atoi(year) - 1900;
329 tm.tm_mon = atoi(month) - 1;
330 tm.tm_mday = atoi(day);
331 tm.tm_hour = atoi(hour);
332 tm.tm_min = atoi(minute);
333 tm.tm_sec = atoi(second);
334 if (utc) {
335 (void) setenv("TZ", "GMT0", 1);
336 tzset();
337 }
338
339 errno = 0;
340 if ((when = mktime(&tm)) == -1 && errno != 0)
341 touchabort(BADTIME);
342 if (tm.tm_isdst)
343 when -= (timezone - altzone);
344
345 if (fraction == NULL) {
346 nanoseconds = 0;
347 } else {
348 /* truncate beyond 9 digits (nanoseconds) */
349 if (strlen(fraction) > 9)
350 fraction[9] = '\0';
351 nanoseconds = atoi(fraction);
352
353 switch (strlen(fraction)) {
354 case 1:
355 nanoseconds *= 100000000;
356 break;
357 case 2:
358 nanoseconds *= 10000000;
359 break;
360 case 3:
361 nanoseconds *= 1000000;
362 break;
363 case 4:
364 nanoseconds *= 100000;
365 break;
366 case 5:
367 nanoseconds *= 10000;
368 break;
369 case 6:
370 nanoseconds *= 1000;
371 break;
372 case 7:
373 nanoseconds *= 100;
374 break;
375 case 8:
376 nanoseconds *= 10;
377 break;
378 case 9:
379 break;
380 }
381 }
382
383 ts->tv_sec = when;
384 ts->tv_nsec = nanoseconds;
385 }
386
387 static void
parse_time(char * t,timespec_t * ts)388 parse_time(char *t, timespec_t *ts)
389 {
390 int century = 0;
391 int seconds = 0;
392 char *p;
393 time_t when;
394 struct tm tm;
395
396 /*
397 * time in the following format (defined by the touch(1) spec):
398 * [[CC]YY]MMDDhhmm[.SS]
399 */
400 if ((p = strchr(t, '.')) != NULL) {
401 if (strchr(p+1, '.') != NULL)
402 touchabort(BADTIME);
403 seconds = atoi_for2(p+1);
404 *p = '\0';
405 }
406
407 (void) memset(&tm, 0, sizeof (struct tm));
408 when = time(0);
409 tm.tm_year = localtime(&when)->tm_year;
410
411 switch (strlen(t)) {
412 case 12: /* CCYYMMDDhhmm */
413 century = atoi_for2(t);
414 t += 2;
415 /* FALLTHROUGH */
416 case 10: /* YYMMDDhhmm */
417 tm.tm_year = atoi_for2(t);
418 t += 2;
419 if (century == 0) {
420 if (tm.tm_year < 69)
421 tm.tm_year += 100;
422 } else
423 tm.tm_year += (century - 19) * 100;
424 /* FALLTHROUGH */
425 case 8: /* MMDDhhmm */
426 tm.tm_mon = atoi_for2(t) - 1;
427 t += 2;
428 tm.tm_mday = atoi_for2(t);
429 t += 2;
430 tm.tm_hour = atoi_for2(t);
431 t += 2;
432 tm.tm_min = atoi_for2(t);
433 tm.tm_sec = seconds;
434 break;
435 default:
436 touchabort(BADTIME);
437 }
438
439 if ((when = mktime(&tm)) == -1)
440 touchabort(BADTIME);
441 if (tm.tm_isdst)
442 when -= (timezone-altzone);
443
444 ts->tv_sec = when;
445 ts->tv_nsec = 0;
446 }
447
448 static void
parse_timespec(char * t,timespec_t * ts)449 parse_timespec(char *t, timespec_t *ts)
450 {
451 time_t when;
452 struct tm tm;
453
454 /*
455 * time in the following format (defined by the touch(1) spec):
456 * MMDDhhmm[yy]
457 */
458
459 (void) memset(&tm, 0, sizeof (struct tm));
460 when = time(0);
461 tm.tm_year = localtime(&when)->tm_year;
462
463 switch (strlen(t)) {
464 case 10: /* MMDDhhmmyy */
465 tm.tm_year = atoi_for2(t+8);
466 if (tm.tm_year < 69)
467 tm.tm_year += 100;
468 /* FALLTHROUGH */
469 case 8: /* MMDDhhmm */
470 tm.tm_mon = atoi_for2(t) - 1;
471 t += 2;
472 tm.tm_mday = atoi_for2(t);
473 t += 2;
474 tm.tm_hour = atoi_for2(t);
475 t += 2;
476 tm.tm_min = atoi_for2(t);
477 break;
478 default:
479 touchabort(BADTIME);
480 }
481
482 if ((when = mktime(&tm)) == -1)
483 touchabort(BADTIME);
484 if (tm.tm_isdst)
485 when -= (timezone - altzone);
486
487 ts->tv_sec = when;
488 ts->tv_nsec = 0;
489 }
490
491 static int
atoi_for2(char * p)492 atoi_for2(char *p)
493 {
494 int value;
495
496 value = (*p - '0') * 10 + *(p+1) - '0';
497 if ((value < 0) || (value > 99))
498 touchabort(BADTIME);
499 return (value);
500 }
501
502 static void
touchabort(const char * message)503 touchabort(const char *message)
504 {
505 (void) fprintf(stderr, "%s: %s\n", myname, gettext(message));
506 exit(1);
507 }
508
509 static void
usage(const int settime)510 usage(const int settime)
511 {
512 if (settime) {
513 (void) fprintf(stderr, gettext(
514 "usage: %s [-f file] [mmddhhmm[yy]] file...\n"), myname);
515 exit(2);
516 }
517 (void) fprintf(stderr, gettext(
518 "usage: %s [-acm] [-r ref_file] file...\n"
519 " %s [-acm] [-t [[CC]YY]MMDDhhmm[.SS]] file...\n"
520 " %s [-acm] [-d YYYY-MM-DDThh:mm:SS[.frac][Z]] file...\n"
521 " %s [-acm] [-d YYYY-MM-DDThh:mm:SS[,frac][Z]] file...\n"
522 " %s [-acm] [MMDDhhmm[yy]] file...\n"),
523 myname, myname, myname, myname, myname);
524 exit(2);
525 }
526