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 tvp[0].tv_sec = tvp[1].tv_sec = mktime(t); 267 if (tvp[0].tv_sec == -1) 268 goto terr; 269 270 tvp[0].tv_nsec = tvp[1].tv_nsec = 0; 271 return; 272 273 terr: 274 errx(1, "out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]"); 275 } 276 277 static void 278 stime_arg2(const char *arg, int year, struct timespec *tvp) 279 { 280 time_t now; 281 struct tm *t; 282 283 now = time(NULL); 284 if ((t = localtime(&now)) == NULL) 285 err(1, "localtime"); 286 287 t->tm_mon = ATOI2(arg); /* MMDDhhmm[yy] */ 288 --t->tm_mon; /* Convert from 01-12 to 00-11 */ 289 t->tm_mday = ATOI2(arg); 290 t->tm_hour = ATOI2(arg); 291 t->tm_min = ATOI2(arg); 292 if (year) { 293 t->tm_year = ATOI2(arg); 294 if (t->tm_year < 39) /* support 2000-2038 not 1902-1969 */ 295 t->tm_year += 100; 296 } 297 298 t->tm_isdst = -1; /* Figure out DST. */ 299 tvp[0].tv_sec = tvp[1].tv_sec = mktime(t); 300 if (tvp[0].tv_sec == -1) 301 errx(1, 302 "out of range or illegal time specification: MMDDhhmm[yy]"); 303 304 tvp[0].tv_nsec = tvp[1].tv_nsec = 0; 305 } 306 307 static void 308 stime_darg(const char *arg, struct timespec *tvp) 309 { 310 struct tm t = { .tm_sec = 0 }; 311 const char *fmt, *colon; 312 char *p; 313 int val, isutc = 0; 314 315 tvp[0].tv_nsec = 0; 316 t.tm_isdst = -1; 317 colon = strchr(arg, ':'); 318 if (colon == NULL || strchr(colon + 1, ':') == NULL) 319 goto bad; 320 fmt = strchr(arg, 'T') != NULL ? "%Y-%m-%dT%H:%M:%S" : 321 "%Y-%m-%d %H:%M:%S"; 322 p = strptime(arg, fmt, &t); 323 if (p == NULL) 324 goto bad; 325 /* POSIX: must have at least one digit after dot */ 326 if ((*p == '.' || *p == ',') && isdigit((unsigned char)p[1])) { 327 p++; 328 val = 100000000; 329 while (isdigit((unsigned char)*p)) { 330 tvp[0].tv_nsec += val * (*p - '0'); 331 p++; 332 val /= 10; 333 } 334 } 335 if (*p == 'Z') { 336 isutc = 1; 337 p++; 338 } 339 if (*p != '\0') 340 goto bad; 341 342 tvp[0].tv_sec = isutc ? timegm(&t) : mktime(&t); 343 344 tvp[1] = tvp[0]; 345 return; 346 347 bad: 348 errx(1, "out of range or illegal time specification: YYYY-MM-DDThh:mm:SS[.frac][tz]"); 349 } 350 351 /* Calculate a time offset in seconds, given an arg of the format [-]HHMMSS. */ 352 int 353 timeoffset(const char *arg) 354 { 355 int offset; 356 int isneg; 357 358 offset = 0; 359 isneg = *arg == '-'; 360 if (isneg) 361 arg++; 362 switch (strlen(arg)) { 363 default: /* invalid */ 364 errx(1, "Invalid offset spec, must be [-][[HH]MM]SS"); 365 366 case 6: /* HHMMSS */ 367 offset = ATOI2(arg); 368 /* FALLTHROUGH */ 369 case 4: /* MMSS */ 370 offset = offset * 60 + ATOI2(arg); 371 /* FALLTHROUGH */ 372 case 2: /* SS */ 373 offset = offset * 60 + ATOI2(arg); 374 } 375 if (isneg) 376 return (-offset); 377 else 378 return (offset); 379 } 380 381 static void 382 stime_file(const char *fname, struct timespec *tsp) 383 { 384 struct stat sb; 385 386 if (stat(fname, &sb)) 387 err(1, "%s", fname); 388 tsp[0] = sb.st_atim; 389 tsp[1] = sb.st_mtim; 390 } 391 392 static void 393 usage(const char *myname) 394 { 395 fprintf(stderr, "usage: %s [-A [-][[hh]mm]SS] [-achm] [-r file] " 396 "[-t [[CC]YY]MMDDhhmm[.SS]]\n" 397 " [-d YYYY-MM-DDThh:mm:SS[.frac][tz]] " 398 "file ...\n", myname); 399 exit(1); 400 } 401