11590Srgrimes/* 21590Srgrimes * Copyright (c) 1993 31590Srgrimes * The Regents of the University of California. All rights reserved. 41590Srgrimes * 51590Srgrimes * Redistribution and use in source and binary forms, with or without 61590Srgrimes * modification, are permitted provided that the following conditions 71590Srgrimes * are met: 81590Srgrimes * 1. Redistributions of source code must retain the above copyright 91590Srgrimes * notice, this list of conditions and the following disclaimer. 101590Srgrimes * 2. Redistributions in binary form must reproduce the above copyright 111590Srgrimes * notice, this list of conditions and the following disclaimer in the 121590Srgrimes * documentation and/or other materials provided with the distribution. 131590Srgrimes * 4. Neither the name of the University nor the names of its contributors 141590Srgrimes * may be used to endorse or promote products derived from this software 151590Srgrimes * without specific prior written permission. 161590Srgrimes * 171590Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 181590Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 191590Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 201590Srgrimes * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 211590Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 221590Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 231590Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 241590Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 251590Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 261590Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 271590Srgrimes * SUCH DAMAGE. 281590Srgrimes */ 291590Srgrimes 3087706Smarkm#include <sys/cdefs.h> 3187706Smarkm 3287706Smarkm__FBSDID("$FreeBSD$"); 3387706Smarkm 341590Srgrimes#ifndef lint 3577247Skrisstatic const char copyright[] = 361590Srgrimes"@(#) Copyright (c) 1993\n\ 371590Srgrimes The Regents of the University of California. All rights reserved.\n"; 3887706Smarkm#endif 391590Srgrimes 401590Srgrimes#ifndef lint 4177247Skrisstatic const char sccsid[] = "@(#)touch.c 8.1 (Berkeley) 6/6/93"; 4277247Skris#endif 431590Srgrimes 441590Srgrimes#include <sys/types.h> 451590Srgrimes#include <sys/stat.h> 461590Srgrimes#include <sys/time.h> 471590Srgrimes 48236852Sjilles#include <ctype.h> 491590Srgrimes#include <err.h> 501590Srgrimes#include <errno.h> 511590Srgrimes#include <fcntl.h> 52168571Sgrog#include <libgen.h> 531590Srgrimes#include <stdio.h> 541590Srgrimes#include <stdlib.h> 551590Srgrimes#include <string.h> 561590Srgrimes#include <time.h> 571590Srgrimes#include <unistd.h> 581590Srgrimes 59249805Seadlerstatic void stime_arg1(const char *, struct timeval *); 60249805Seadlerstatic void stime_arg2(const char *, int, struct timeval *); 61249805Seadlerstatic void stime_darg(const char *, struct timeval *); 62249805Seadlerstatic void stime_file(const char *, struct timeval *); 63249805Seadlerstatic int timeoffset(const char *); 64249806Seadlerstatic void usage(const char *); 651590Srgrimes 661590Srgrimesint 67102944Sdwmalonemain(int argc, char *argv[]) 681590Srgrimes{ 691590Srgrimes struct stat sb; 701590Srgrimes struct timeval tv[2]; 7183826Sobrien int (*stat_f)(const char *, struct stat *); 7283826Sobrien int (*utimes_f)(const char *, const struct timeval *); 73230979Sjh int Aflag, aflag, cflag, mflag, ch, fd, len, rval, timeset; 741590Srgrimes char *p; 75168525Sgrog char *myname; 761590Srgrimes 77168571Sgrog myname = basename(argv[0]); 78230979Sjh Aflag = aflag = cflag = mflag = timeset = 0; 7983826Sobrien stat_f = stat; 8083826Sobrien utimes_f = utimes; 81249805Seadler if (gettimeofday(&tv[0], NULL) == -1) 821590Srgrimes err(1, "gettimeofday"); 831590Srgrimes 84236852Sjilles while ((ch = getopt(argc, argv, "A:acd:fhmr:t:")) != -1) 851590Srgrimes switch(ch) { 86168525Sgrog case 'A': 87168525Sgrog Aflag = timeoffset(optarg); 88168525Sgrog break; 891590Srgrimes case 'a': 901590Srgrimes aflag = 1; 911590Srgrimes break; 921590Srgrimes case 'c': 931590Srgrimes cflag = 1; 941590Srgrimes break; 95236852Sjilles case 'd': 96236852Sjilles timeset = 1; 97236852Sjilles stime_darg(optarg, tv); 98236852Sjilles break; 991590Srgrimes case 'f': 100230979Sjh /* No-op for compatibility. */ 1011590Srgrimes break; 10283826Sobrien case 'h': 10383826Sobrien cflag = 1; 10483826Sobrien stat_f = lstat; 10583826Sobrien utimes_f = lutimes; 10683826Sobrien break; 1071590Srgrimes case 'm': 1081590Srgrimes mflag = 1; 1091590Srgrimes break; 1101590Srgrimes case 'r': 1111590Srgrimes timeset = 1; 1121590Srgrimes stime_file(optarg, tv); 1131590Srgrimes break; 1141590Srgrimes case 't': 1151590Srgrimes timeset = 1; 1161590Srgrimes stime_arg1(optarg, tv); 1171590Srgrimes break; 1181590Srgrimes default: 119168525Sgrog usage(myname); 1201590Srgrimes } 1211590Srgrimes argc -= optind; 1221590Srgrimes argv += optind; 1231590Srgrimes 124168571Sgrog if (aflag == 0 && mflag == 0) 1251590Srgrimes aflag = mflag = 1; 1261590Srgrimes 127168571Sgrog if (timeset) { 128168571Sgrog if (Aflag) { 129168571Sgrog /* 130168571Sgrog * We're setting the time to an offset from a specified 131168571Sgrog * time. God knows why, but it means that we can set 132168571Sgrog * that time once and for all here. 133168571Sgrog */ 134168571Sgrog if (aflag) 135168571Sgrog tv[0].tv_sec += Aflag; 136168571Sgrog if (mflag) 137168571Sgrog tv[1].tv_sec += Aflag; 138168571Sgrog Aflag = 0; /* done our job */ 1391590Srgrimes } 140168571Sgrog } else { 141168571Sgrog /* 142168571Sgrog * If no -r or -t flag, at least two operands, the first of 143168571Sgrog * which is an 8 or 10 digit number, use the obsolete time 144168571Sgrog * specification, otherwise use the current time. 145168571Sgrog */ 146168571Sgrog if (argc > 1) { 147168571Sgrog strtol(argv[0], &p, 10); 148168571Sgrog len = p - argv[0]; 149168571Sgrog if (*p == '\0' && (len == 8 || len == 10)) { 150168571Sgrog timeset = 1; 151168571Sgrog stime_arg2(*argv++, len == 10, tv); 152168571Sgrog } 153168571Sgrog } 154168571Sgrog /* Both times default to the same. */ 155168571Sgrog tv[1] = tv[0]; 1561590Srgrimes } 1571590Srgrimes 1581590Srgrimes if (*argv == NULL) 159168525Sgrog usage(myname); 1601590Srgrimes 161168571Sgrog if (Aflag) 162168571Sgrog cflag = 1; 163168571Sgrog 1641590Srgrimes for (rval = 0; *argv; ++argv) { 1651590Srgrimes /* See if the file exists. */ 16683826Sobrien if (stat_f(*argv, &sb) != 0) { 167198175Sjh if (errno != ENOENT) { 168198175Sjh rval = 1; 169198175Sjh warn("%s", *argv); 170198175Sjh continue; 171198175Sjh } 1721590Srgrimes if (!cflag) { 1731590Srgrimes /* Create the file. */ 1741590Srgrimes fd = open(*argv, 1751590Srgrimes O_WRONLY | O_CREAT, DEFFILEMODE); 1761590Srgrimes if (fd == -1 || fstat(fd, &sb) || close(fd)) { 1771590Srgrimes rval = 1; 1781590Srgrimes warn("%s", *argv); 1791590Srgrimes continue; 1801590Srgrimes } 1811590Srgrimes 1821590Srgrimes /* If using the current time, we're done. */ 1831590Srgrimes if (!timeset) 1841590Srgrimes continue; 1851590Srgrimes } else 1861590Srgrimes continue; 18748566Sbillf } 1881590Srgrimes 1891590Srgrimes if (!aflag) 190205793Sed TIMESPEC_TO_TIMEVAL(&tv[0], &sb.st_atim); 1911590Srgrimes if (!mflag) 192205793Sed TIMESPEC_TO_TIMEVAL(&tv[1], &sb.st_mtim); 193168571Sgrog 194168571Sgrog /* 195168571Sgrog * We're adjusting the times based on the file times, not a 196168571Sgrog * specified time (that gets handled above). 197168571Sgrog */ 198168525Sgrog if (Aflag) { 199168571Sgrog if (aflag) { 200205793Sed TIMESPEC_TO_TIMEVAL(&tv[0], &sb.st_atim); 201168571Sgrog tv[0].tv_sec += Aflag; 202168571Sgrog } 203168571Sgrog if (mflag) { 204205793Sed TIMESPEC_TO_TIMEVAL(&tv[1], &sb.st_mtim); 205168571Sgrog tv[1].tv_sec += Aflag; 206168571Sgrog } 207168525Sgrog } 2081590Srgrimes 2091590Srgrimes /* Try utimes(2). */ 21083826Sobrien if (!utimes_f(*argv, tv)) 2111590Srgrimes continue; 2121590Srgrimes 2131590Srgrimes /* If the user specified a time, nothing else we can do. */ 214198175Sjh if (timeset || Aflag) { 2151590Srgrimes rval = 1; 2161590Srgrimes warn("%s", *argv); 217155082Sache continue; 2181590Srgrimes } 2191590Srgrimes 2201590Srgrimes /* 2211590Srgrimes * System V and POSIX 1003.1 require that a NULL argument 2221590Srgrimes * set the access/modification times to the current time. 2231590Srgrimes * The permission checks are different, too, in that the 2241590Srgrimes * ability to write the file is sufficient. Take a shot. 2251590Srgrimes */ 22683826Sobrien if (!utimes_f(*argv, NULL)) 2271590Srgrimes continue; 2281590Srgrimes 229230979Sjh rval = 1; 230230979Sjh warn("%s", *argv); 2311590Srgrimes } 2321590Srgrimes exit(rval); 2331590Srgrimes} 2341590Srgrimes 2351590Srgrimes#define ATOI2(ar) ((ar)[0] - '0') * 10 + ((ar)[1] - '0'); (ar) += 2; 2361590Srgrimes 237249805Seadlerstatic void 238249805Seadlerstime_arg1(const char *arg, struct timeval *tvp) 2391590Srgrimes{ 24037259Sbde time_t now; 2411590Srgrimes struct tm *t; 2421590Srgrimes int yearset; 2431590Srgrimes char *p; 2441590Srgrimes /* Start with the current time. */ 24537259Sbde now = tvp[0].tv_sec; 24637259Sbde if ((t = localtime(&now)) == NULL) 2471590Srgrimes err(1, "localtime"); 2481590Srgrimes /* [[CC]YY]MMDDhhmm[.SS] */ 2491590Srgrimes if ((p = strchr(arg, '.')) == NULL) 2501590Srgrimes t->tm_sec = 0; /* Seconds defaults to 0. */ 2511590Srgrimes else { 2521590Srgrimes if (strlen(p + 1) != 2) 2531590Srgrimes goto terr; 2541590Srgrimes *p++ = '\0'; 2551590Srgrimes t->tm_sec = ATOI2(p); 2561590Srgrimes } 2578874Srgrimes 2581590Srgrimes yearset = 0; 2591590Srgrimes switch(strlen(arg)) { 2601590Srgrimes case 12: /* CCYYMMDDhhmm */ 2611590Srgrimes t->tm_year = ATOI2(arg); 2629446Sjoerg t->tm_year *= 100; 2631590Srgrimes yearset = 1; 264102412Scharnier /* FALLTHROUGH */ 2651590Srgrimes case 10: /* YYMMDDhhmm */ 2661590Srgrimes if (yearset) { 2671590Srgrimes yearset = ATOI2(arg); 2681590Srgrimes t->tm_year += yearset; 2691590Srgrimes } else { 2701590Srgrimes yearset = ATOI2(arg); 2711590Srgrimes if (yearset < 69) 2721590Srgrimes t->tm_year = yearset + 2000; 2731590Srgrimes else 2741590Srgrimes t->tm_year = yearset + 1900; 2751590Srgrimes } 2761590Srgrimes t->tm_year -= 1900; /* Convert to UNIX time. */ 2771590Srgrimes /* FALLTHROUGH */ 2781590Srgrimes case 8: /* MMDDhhmm */ 2791590Srgrimes t->tm_mon = ATOI2(arg); 2801590Srgrimes --t->tm_mon; /* Convert from 01-12 to 00-11 */ 2811590Srgrimes t->tm_mday = ATOI2(arg); 2821590Srgrimes t->tm_hour = ATOI2(arg); 2831590Srgrimes t->tm_min = ATOI2(arg); 2841590Srgrimes break; 2851590Srgrimes default: 2861590Srgrimes goto terr; 2871590Srgrimes } 2881590Srgrimes 2891590Srgrimes t->tm_isdst = -1; /* Figure out DST. */ 2901590Srgrimes tvp[0].tv_sec = tvp[1].tv_sec = mktime(t); 2911590Srgrimes if (tvp[0].tv_sec == -1) 292249805Seadler goto terr; 2931590Srgrimes 2941590Srgrimes tvp[0].tv_usec = tvp[1].tv_usec = 0; 295249805Seadler return; 296249805Seadler 297249805Seadlerterr: 298249805Seadler errx(1, "out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]"); 2991590Srgrimes} 3001590Srgrimes 301249805Seadlerstatic void 302249805Seadlerstime_arg2(const char *arg, int year, struct timeval *tvp) 3031590Srgrimes{ 30437259Sbde time_t now; 3051590Srgrimes struct tm *t; 3061590Srgrimes /* Start with the current time. */ 30737259Sbde now = tvp[0].tv_sec; 30837259Sbde if ((t = localtime(&now)) == NULL) 3091590Srgrimes err(1, "localtime"); 3101590Srgrimes 3111590Srgrimes t->tm_mon = ATOI2(arg); /* MMDDhhmm[yy] */ 3121590Srgrimes --t->tm_mon; /* Convert from 01-12 to 00-11 */ 3131590Srgrimes t->tm_mday = ATOI2(arg); 3141590Srgrimes t->tm_hour = ATOI2(arg); 3151590Srgrimes t->tm_min = ATOI2(arg); 31642307Sdanny if (year) { 3171590Srgrimes t->tm_year = ATOI2(arg); 31842310Sdanny if (t->tm_year < 39) /* support 2000-2038 not 1902-1969 */ 31942307Sdanny t->tm_year += 100; 32042307Sdanny } 3211590Srgrimes 3221590Srgrimes t->tm_isdst = -1; /* Figure out DST. */ 3231590Srgrimes tvp[0].tv_sec = tvp[1].tv_sec = mktime(t); 3241590Srgrimes if (tvp[0].tv_sec == -1) 3251590Srgrimes errx(1, 3261590Srgrimes "out of range or illegal time specification: MMDDhhmm[yy]"); 3271590Srgrimes 3281590Srgrimes tvp[0].tv_usec = tvp[1].tv_usec = 0; 3291590Srgrimes} 3301590Srgrimes 331249805Seadlerstatic void 332249805Seadlerstime_darg(const char *arg, struct timeval *tvp) 333236852Sjilles{ 334236852Sjilles struct tm t = { .tm_sec = 0 }; 335236852Sjilles const char *fmt, *colon; 336236852Sjilles char *p; 337236852Sjilles int val, isutc = 0; 338236852Sjilles 339236852Sjilles tvp[0].tv_usec = 0; 340236852Sjilles t.tm_isdst = -1; 341236852Sjilles colon = strchr(arg, ':'); 342236852Sjilles if (colon == NULL || strchr(colon + 1, ':') == NULL) 343236852Sjilles goto bad; 344236852Sjilles fmt = strchr(arg, 'T') != NULL ? "%Y-%m-%dT%H:%M:%S" : 345236852Sjilles "%Y-%m-%d %H:%M:%S"; 346236852Sjilles p = strptime(arg, fmt, &t); 347236852Sjilles if (p == NULL) 348236852Sjilles goto bad; 349236852Sjilles /* POSIX: must have at least one digit after dot */ 350236852Sjilles if ((*p == '.' || *p == ',') && isdigit((unsigned char)p[1])) { 351236852Sjilles p++; 352236852Sjilles val = 100000; 353236852Sjilles while (isdigit((unsigned char)*p)) { 354236852Sjilles tvp[0].tv_usec += val * (*p - '0'); 355236852Sjilles p++; 356236852Sjilles val /= 10; 357236852Sjilles } 358236852Sjilles } 359236852Sjilles if (*p == 'Z') { 360236852Sjilles isutc = 1; 361236852Sjilles p++; 362236852Sjilles } 363236852Sjilles if (*p != '\0') 364236852Sjilles goto bad; 365236852Sjilles 366236852Sjilles tvp[0].tv_sec = isutc ? timegm(&t) : mktime(&t); 367236852Sjilles 368236852Sjilles tvp[1] = tvp[0]; 369236852Sjilles return; 370236852Sjilles 371236852Sjillesbad: 372236852Sjilles errx(1, "out of range or illegal time specification: YYYY-MM-DDThh:mm:SS[.frac][tz]"); 373236852Sjilles} 374236852Sjilles 375168525Sgrog/* Calculate a time offset in seconds, given an arg of the format [-]HHMMSS. */ 376168525Sgrogint 377249805Seadlertimeoffset(const char *arg) 378168525Sgrog{ 379168525Sgrog int offset; 380168525Sgrog int isneg; 381168525Sgrog 382168525Sgrog offset = 0; 383168525Sgrog isneg = *arg == '-'; 384168525Sgrog if (isneg) 385168525Sgrog arg++; 386168525Sgrog switch (strlen(arg)) { 387168525Sgrog default: /* invalid */ 388168525Sgrog errx(1, "Invalid offset spec, must be [-][[HH]MM]SS"); 389168525Sgrog 390168525Sgrog case 6: /* HHMMSS */ 391168525Sgrog offset = ATOI2(arg); 392168525Sgrog /* FALLTHROUGH */ 393168525Sgrog case 4: /* MMSS */ 394168525Sgrog offset = offset * 60 + ATOI2(arg); 395168525Sgrog /* FALLTHROUGH */ 396168525Sgrog case 2: /* SS */ 397168525Sgrog offset = offset * 60 + ATOI2(arg); 398168525Sgrog } 399168525Sgrog if (isneg) 400168525Sgrog return (-offset); 401168525Sgrog else 402168525Sgrog return (offset); 403168525Sgrog} 404168525Sgrog 405249805Seadlerstatic void 406249805Seadlerstime_file(const char *fname, struct timeval *tvp) 4071590Srgrimes{ 4081590Srgrimes struct stat sb; 4091590Srgrimes 4101590Srgrimes if (stat(fname, &sb)) 4111590Srgrimes err(1, "%s", fname); 412205793Sed TIMESPEC_TO_TIMEVAL(tvp, &sb.st_atim); 413205793Sed TIMESPEC_TO_TIMEVAL(tvp + 1, &sb.st_mtim); 4141590Srgrimes} 4151590Srgrimes 416249805Seadlerstatic void 417249806Seadlerusage(const char *myname) 4181590Srgrimes{ 419236852Sjilles fprintf(stderr, "usage: %s [-A [-][[hh]mm]SS] [-achm] [-r file] " 420236852Sjilles "[-t [[CC]YY]MMDDhhmm[.SS]]\n" 421236852Sjilles " [-d YYYY-MM-DDThh:mm:SS[.frac][tz]] " 422236852Sjilles "file ...\n", myname); 4231590Srgrimes exit(1); 4241590Srgrimes} 425