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 == -1) { 167 rval = 1; 168 warn("%s", *argv); 169 continue; 170 } 171 if (fstat(fd, &sb) < 0) { 172 warn("%s", *argv); 173 rval = 1; 174 } 175 if (close(fd) < 0) { 176 warn("%s", *argv); 177 rval = 1; 178 } 179 180 /* If using the current time, we're done. */ 181 if (!timeset) 182 continue; 183 } else 184 continue; 185 } 186 187 /* 188 * We're adjusting the times based on the file times, not a 189 * specified time (that gets handled above). 190 */ 191 if (Aflag) { 192 if (aflag) { 193 ts[0] = sb.st_atim; 194 ts[0].tv_sec += Aflag; 195 } 196 if (mflag) { 197 ts[1] = sb.st_mtim; 198 ts[1].tv_sec += Aflag; 199 } 200 } 201 202 if (!utimensat(AT_FDCWD, *argv, ts, atflag)) 203 continue; 204 205 rval = 1; 206 warn("%s", *argv); 207 } 208 exit(rval); 209 } 210 211 #define ATOI2(ar) ((ar)[0] - '0') * 10 + ((ar)[1] - '0'); (ar) += 2; 212 213 static void 214 stime_arg1(const char *arg, struct timespec *tvp) 215 { 216 time_t now; 217 struct tm *t; 218 int yearset; 219 char *p; 220 221 now = time(NULL); 222 if ((t = localtime(&now)) == NULL) 223 err(1, "localtime"); 224 /* [[CC]YY]MMDDhhmm[.SS] */ 225 if ((p = strchr(arg, '.')) == NULL) 226 t->tm_sec = 0; /* Seconds defaults to 0. */ 227 else { 228 if (strlen(p + 1) != 2) 229 goto terr; 230 *p++ = '\0'; 231 t->tm_sec = ATOI2(p); 232 } 233 234 yearset = 0; 235 switch (strlen(arg)) { 236 case 12: /* CCYYMMDDhhmm */ 237 t->tm_year = ATOI2(arg); 238 t->tm_year *= 100; 239 yearset = 1; 240 /* FALLTHROUGH */ 241 case 10: /* YYMMDDhhmm */ 242 if (yearset) { 243 yearset = ATOI2(arg); 244 t->tm_year += yearset; 245 } else { 246 yearset = ATOI2(arg); 247 if (yearset < 69) 248 t->tm_year = yearset + 2000; 249 else 250 t->tm_year = yearset + 1900; 251 } 252 t->tm_year -= 1900; /* Convert to UNIX time. */ 253 /* FALLTHROUGH */ 254 case 8: /* MMDDhhmm */ 255 t->tm_mon = ATOI2(arg); 256 --t->tm_mon; /* Convert from 01-12 to 00-11 */ 257 t->tm_mday = ATOI2(arg); 258 t->tm_hour = ATOI2(arg); 259 t->tm_min = ATOI2(arg); 260 break; 261 default: 262 goto terr; 263 } 264 265 t->tm_isdst = -1; /* Figure out DST. */ 266 t->tm_yday = -1; 267 tvp[0].tv_sec = tvp[1].tv_sec = mktime(t); 268 if (t->tm_yday == -1) 269 goto terr; 270 271 tvp[0].tv_nsec = tvp[1].tv_nsec = 0; 272 return; 273 274 terr: 275 errx(1, "out of range or illegal time specification: " 276 "[[CC]YY]MMDDhhmm[.SS]"); 277 } 278 279 static void 280 stime_arg2(const char *arg, int year, struct timespec *tvp) 281 { 282 time_t now; 283 struct tm *t; 284 285 now = time(NULL); 286 if ((t = localtime(&now)) == NULL) 287 err(1, "localtime"); 288 289 t->tm_mon = ATOI2(arg); /* MMDDhhmm[yy] */ 290 --t->tm_mon; /* Convert from 01-12 to 00-11 */ 291 t->tm_mday = ATOI2(arg); 292 t->tm_hour = ATOI2(arg); 293 t->tm_min = ATOI2(arg); 294 if (year) { 295 t->tm_year = ATOI2(arg); 296 if (t->tm_year < 39) /* support 2000-2038 not 1902-1969 */ 297 t->tm_year += 100; 298 } 299 300 t->tm_isdst = -1; /* Figure out DST. */ 301 t->tm_yday = -1; 302 tvp[0].tv_sec = tvp[1].tv_sec = mktime(t); 303 if (t->tm_yday == -1) 304 errx(1, "out of range or illegal time specification: " 305 "MMDDhhmm[yy]"); 306 307 tvp[0].tv_nsec = tvp[1].tv_nsec = 0; 308 } 309 310 static void 311 stime_darg(const char *arg, struct timespec *tvp) 312 { 313 struct tm t = { .tm_sec = 0 }; 314 const char *fmt, *colon; 315 char *p; 316 int val, isutc = 0; 317 318 tvp[0].tv_nsec = 0; 319 t.tm_isdst = -1; 320 colon = strchr(arg, ':'); 321 if (colon == NULL || strchr(colon + 1, ':') == NULL) 322 goto bad; 323 fmt = strchr(arg, 'T') != NULL ? "%Y-%m-%dT%H:%M:%S" : 324 "%Y-%m-%d %H:%M:%S"; 325 p = strptime(arg, fmt, &t); 326 if (p == NULL) 327 goto bad; 328 /* POSIX: must have at least one digit after dot */ 329 if ((*p == '.' || *p == ',') && isdigit((unsigned char)p[1])) { 330 p++; 331 val = 100000000; 332 while (isdigit((unsigned char)*p)) { 333 tvp[0].tv_nsec += val * (*p - '0'); 334 p++; 335 val /= 10; 336 } 337 } 338 if (*p == 'Z') { 339 isutc = 1; 340 p++; 341 } 342 if (*p != '\0') 343 goto bad; 344 345 t.tm_yday = -1; 346 tvp[0].tv_sec = isutc ? timegm(&t) : mktime(&t); 347 if (t.tm_yday == -1) 348 goto bad; 349 350 tvp[1] = tvp[0]; 351 return; 352 353 bad: 354 errx(1, "out of range or illegal time specification: " 355 "YYYY-MM-DDThh:mm:SS[.frac][tz]"); 356 } 357 358 /* Calculate a time offset in seconds, given an arg of the format [-]HHMMSS. */ 359 int 360 timeoffset(const char *arg) 361 { 362 int offset; 363 int isneg; 364 365 offset = 0; 366 isneg = *arg == '-'; 367 if (isneg) 368 arg++; 369 switch (strlen(arg)) { 370 default: /* invalid */ 371 errx(1, "Invalid offset spec, must be [-][[HH]MM]SS"); 372 373 case 6: /* HHMMSS */ 374 offset = ATOI2(arg); 375 /* FALLTHROUGH */ 376 case 4: /* MMSS */ 377 offset = offset * 60 + ATOI2(arg); 378 /* FALLTHROUGH */ 379 case 2: /* SS */ 380 offset = offset * 60 + ATOI2(arg); 381 } 382 if (isneg) 383 return (-offset); 384 else 385 return (offset); 386 } 387 388 static void 389 stime_file(const char *fname, struct timespec *tsp) 390 { 391 struct stat sb; 392 393 if (stat(fname, &sb)) 394 err(1, "%s", fname); 395 tsp[0] = sb.st_atim; 396 tsp[1] = sb.st_mtim; 397 } 398 399 static void 400 usage(const char *myname) 401 { 402 fprintf(stderr, "usage: %s [-A [-][[hh]mm]SS] [-achm] [-r file] " 403 "[-t [[CC]YY]MMDDhhmm[.SS]]\n" 404 " [-d YYYY-MM-DDThh:mm:SS[.frac][tz]] " 405 "file ...\n", myname); 406 exit(1); 407 } 408