1 /*- 2 * SPDX-License-Identifier: BSD-3-Clause 3 * 4 * Copyright (c) 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of the University nor the names of its contributors 16 * may be used to endorse or promote products derived from this software 17 * without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 #include <sys/types.h> 33 #include <sys/stat.h> 34 #include <sys/time.h> 35 36 #include <ctype.h> 37 #include <err.h> 38 #include <errno.h> 39 #include <fcntl.h> 40 #include <libgen.h> 41 #include <stdio.h> 42 #include <stdlib.h> 43 #include <string.h> 44 #include <time.h> 45 #include <unistd.h> 46 47 static void stime_arg1(const char *, struct timespec *); 48 static void stime_arg2(const char *, int, struct timespec *); 49 static void stime_darg(const char *, struct timespec *); 50 static void stime_file(const char *, struct timespec *); 51 static int timeoffset(const char *); 52 static void usage(const char *); 53 54 int 55 main(int argc, char *argv[]) 56 { 57 struct stat sb; 58 struct timespec ts[2]; 59 int atflag; 60 int Aflag, aflag, cflag, mflag, ch, fd, len, rval, timeset; 61 char *p; 62 char *myname; 63 64 myname = basename(argv[0]); 65 Aflag = aflag = cflag = mflag = timeset = 0; 66 atflag = 0; 67 ts[0].tv_sec = ts[1].tv_sec = 0; 68 ts[0].tv_nsec = ts[1].tv_nsec = UTIME_NOW; 69 70 while ((ch = getopt(argc, argv, "A:acd:fhmr:t:")) != -1) 71 switch(ch) { 72 case 'A': 73 Aflag = timeoffset(optarg); 74 break; 75 case 'a': 76 aflag = 1; 77 break; 78 case 'c': 79 cflag = 1; 80 break; 81 case 'd': 82 timeset = 1; 83 stime_darg(optarg, ts); 84 break; 85 case 'f': 86 /* No-op for compatibility. */ 87 break; 88 case 'h': 89 cflag = 1; 90 atflag = AT_SYMLINK_NOFOLLOW; 91 break; 92 case 'm': 93 mflag = 1; 94 break; 95 case 'r': 96 timeset = 1; 97 stime_file(optarg, ts); 98 break; 99 case 't': 100 timeset = 1; 101 stime_arg1(optarg, ts); 102 break; 103 default: 104 usage(myname); 105 } 106 argc -= optind; 107 argv += optind; 108 109 if (aflag == 0 && mflag == 0) 110 aflag = mflag = 1; 111 112 if (timeset) { 113 if (Aflag) { 114 /* 115 * We're setting the time to an offset from a specified 116 * time. God knows why, but it means that we can set 117 * that time once and for all here. 118 */ 119 if (aflag) 120 ts[0].tv_sec += Aflag; 121 if (mflag) 122 ts[1].tv_sec += Aflag; 123 Aflag = 0; /* done our job */ 124 } 125 } else { 126 /* 127 * If no -r or -t flag, at least two operands, the first of 128 * which is an 8 or 10 digit number, use the obsolete time 129 * specification, otherwise use the current time. 130 */ 131 if (argc > 1) { 132 strtol(argv[0], &p, 10); 133 len = p - argv[0]; 134 if (*p == '\0' && (len == 8 || len == 10)) { 135 timeset = 1; 136 stime_arg2(*argv++, len == 10, ts); 137 } 138 } 139 /* Both times default to the same. */ 140 ts[1] = ts[0]; 141 } 142 143 if (!aflag) 144 ts[0].tv_nsec = UTIME_OMIT; 145 if (!mflag) 146 ts[1].tv_nsec = UTIME_OMIT; 147 148 if (*argv == NULL) 149 usage(myname); 150 151 if (Aflag) 152 cflag = 1; 153 154 for (rval = 0; *argv; ++argv) { 155 /* See if the file exists. */ 156 if (fstatat(AT_FDCWD, *argv, &sb, atflag) != 0) { 157 if (errno != ENOENT) { 158 rval = 1; 159 warn("%s", *argv); 160 continue; 161 } 162 if (!cflag) { 163 /* Create the file. */ 164 fd = open(*argv, 165 O_WRONLY | O_CREAT, DEFFILEMODE); 166 if (fd < 0 || fstat(fd, &sb) < 0) { 167 rval = 1; 168 warn("%s", *argv); 169 if (fd >= 0) 170 (void)close(fd); 171 continue; 172 } 173 (void)close(fd); 174 175 /* If using the current time, we're done. */ 176 if (!timeset) 177 continue; 178 } else 179 continue; 180 } 181 182 /* 183 * We're adjusting the times based on the file times, not a 184 * specified time (that gets handled above). 185 */ 186 if (Aflag) { 187 if (aflag) { 188 ts[0] = sb.st_atim; 189 ts[0].tv_sec += Aflag; 190 } 191 if (mflag) { 192 ts[1] = sb.st_mtim; 193 ts[1].tv_sec += Aflag; 194 } 195 } 196 197 if (!utimensat(AT_FDCWD, *argv, ts, atflag)) 198 continue; 199 200 rval = 1; 201 warn("%s", *argv); 202 } 203 exit(rval); 204 } 205 206 #define ATOI2(ar) ((ar)[0] - '0') * 10 + ((ar)[1] - '0'); (ar) += 2; 207 208 static void 209 stime_arg1(const char *arg, struct timespec *tvp) 210 { 211 time_t now; 212 struct tm *t; 213 int yearset; 214 char *p; 215 216 now = time(NULL); 217 if ((t = localtime(&now)) == NULL) 218 err(1, "localtime"); 219 /* [[CC]YY]MMDDhhmm[.SS] */ 220 if ((p = strchr(arg, '.')) == NULL) 221 t->tm_sec = 0; /* Seconds defaults to 0. */ 222 else { 223 if (strlen(p + 1) != 2) 224 goto terr; 225 *p++ = '\0'; 226 t->tm_sec = ATOI2(p); 227 } 228 229 yearset = 0; 230 switch (strlen(arg)) { 231 case 12: /* CCYYMMDDhhmm */ 232 t->tm_year = ATOI2(arg); 233 t->tm_year *= 100; 234 yearset = 1; 235 /* FALLTHROUGH */ 236 case 10: /* YYMMDDhhmm */ 237 if (yearset) { 238 yearset = ATOI2(arg); 239 t->tm_year += yearset; 240 } else { 241 yearset = ATOI2(arg); 242 if (yearset < 69) 243 t->tm_year = yearset + 2000; 244 else 245 t->tm_year = yearset + 1900; 246 } 247 t->tm_year -= 1900; /* Convert to UNIX time. */ 248 /* FALLTHROUGH */ 249 case 8: /* MMDDhhmm */ 250 t->tm_mon = ATOI2(arg); 251 --t->tm_mon; /* Convert from 01-12 to 00-11 */ 252 t->tm_mday = ATOI2(arg); 253 t->tm_hour = ATOI2(arg); 254 t->tm_min = ATOI2(arg); 255 break; 256 default: 257 goto terr; 258 } 259 260 t->tm_isdst = -1; /* Figure out DST. */ 261 t->tm_yday = -1; 262 tvp[0].tv_sec = tvp[1].tv_sec = mktime(t); 263 if (t->tm_yday == -1) 264 goto terr; 265 266 tvp[0].tv_nsec = tvp[1].tv_nsec = 0; 267 return; 268 269 terr: 270 errx(1, "out of range or illegal time specification: " 271 "[[CC]YY]MMDDhhmm[.SS]"); 272 } 273 274 static void 275 stime_arg2(const char *arg, int year, struct timespec *tvp) 276 { 277 time_t now; 278 struct tm *t; 279 280 now = time(NULL); 281 if ((t = localtime(&now)) == NULL) 282 err(1, "localtime"); 283 284 t->tm_mon = ATOI2(arg); /* MMDDhhmm[yy] */ 285 --t->tm_mon; /* Convert from 01-12 to 00-11 */ 286 t->tm_mday = ATOI2(arg); 287 t->tm_hour = ATOI2(arg); 288 t->tm_min = ATOI2(arg); 289 if (year) { 290 t->tm_year = ATOI2(arg); 291 if (t->tm_year < 39) /* support 2000-2038 not 1902-1969 */ 292 t->tm_year += 100; 293 } 294 295 t->tm_isdst = -1; /* Figure out DST. */ 296 t->tm_yday = -1; 297 tvp[0].tv_sec = tvp[1].tv_sec = mktime(t); 298 if (t->tm_yday == -1) 299 errx(1, "out of range or illegal time specification: " 300 "MMDDhhmm[yy]"); 301 302 tvp[0].tv_nsec = tvp[1].tv_nsec = 0; 303 } 304 305 static void 306 stime_darg(const char *arg, struct timespec *tvp) 307 { 308 struct tm t = { .tm_sec = 0 }; 309 const char *fmt, *colon; 310 char *p; 311 int val, isutc = 0; 312 313 tvp[0].tv_nsec = 0; 314 t.tm_isdst = -1; 315 colon = strchr(arg, ':'); 316 if (colon == NULL || strchr(colon + 1, ':') == NULL) 317 goto bad; 318 fmt = strchr(arg, 'T') != NULL ? "%Y-%m-%dT%H:%M:%S" : 319 "%Y-%m-%d %H:%M:%S"; 320 p = strptime(arg, fmt, &t); 321 if (p == NULL) 322 goto bad; 323 /* POSIX: must have at least one digit after dot */ 324 if ((*p == '.' || *p == ',') && isdigit((unsigned char)p[1])) { 325 p++; 326 val = 100000000; 327 while (isdigit((unsigned char)*p)) { 328 tvp[0].tv_nsec += val * (*p - '0'); 329 p++; 330 val /= 10; 331 } 332 } 333 if (*p == 'Z') { 334 isutc = 1; 335 p++; 336 } 337 if (*p != '\0') 338 goto bad; 339 340 t.tm_yday = -1; 341 tvp[0].tv_sec = isutc ? timegm(&t) : mktime(&t); 342 if (t.tm_yday == -1) 343 goto bad; 344 345 tvp[1] = tvp[0]; 346 return; 347 348 bad: 349 errx(1, "out of range or illegal time specification: " 350 "YYYY-MM-DDThh:mm:SS[.frac][tz]"); 351 } 352 353 /* Calculate a time offset in seconds, given an arg of the format [-]HHMMSS. */ 354 int 355 timeoffset(const char *arg) 356 { 357 int offset; 358 int isneg; 359 360 offset = 0; 361 isneg = *arg == '-'; 362 if (isneg) 363 arg++; 364 switch (strlen(arg)) { 365 default: /* invalid */ 366 errx(1, "Invalid offset spec, must be [-][[HH]MM]SS"); 367 368 case 6: /* HHMMSS */ 369 offset = ATOI2(arg); 370 /* FALLTHROUGH */ 371 case 4: /* MMSS */ 372 offset = offset * 60 + ATOI2(arg); 373 /* FALLTHROUGH */ 374 case 2: /* SS */ 375 offset = offset * 60 + ATOI2(arg); 376 } 377 if (isneg) 378 return (-offset); 379 else 380 return (offset); 381 } 382 383 static void 384 stime_file(const char *fname, struct timespec *tsp) 385 { 386 struct stat sb; 387 388 if (stat(fname, &sb)) 389 err(1, "%s", fname); 390 tsp[0] = sb.st_atim; 391 tsp[1] = sb.st_mtim; 392 } 393 394 static void 395 usage(const char *myname) 396 { 397 fprintf(stderr, "usage: %s [-A [-][[hh]mm]SS] [-achm] [-r file] " 398 "[-t [[CC]YY]MMDDhhmm[.SS]]\n" 399 " [-d YYYY-MM-DDThh:mm:SS[.frac][tz]] " 400 "file ...\n", myname); 401 exit(1); 402 } 403