OpenSolaris_b135/cmd/touch/touch.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/

/*	Copyright (c) 1987, 1988 Microsoft Corporation	*/
/*	  All Rights Reserved	*/

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <ctype.h>
#include <libgen.h>
#include <fcntl.h>
#include <pwd.h>
#include <time.h>
#include <unistd.h>
#include <locale.h>
#include <sys/time.h>
#include <errno.h>

#define	BADTIME	"bad time specification"

static char	*myname;

static int isnumber(char *);
static int atoi_for2(char *);
static void usage(const int);
static void touchabort(const char *);
static void parse_datetime(char *, timespec_t *);
static void parse_time(char *, timespec_t *);
static void parse_timespec(char *, timespec_t *);

int
main(int argc, char *argv[])
{
	int c;

	int		aflag	= 0;
	int		cflag	= 0;
	int		rflag	= 0;
	int		mflag	= 0;
	int		tflag	= 0;
	int		stflag	= 0;
	int		status	= 0;
	int		usecurrenttime = 1;
	int		timespecified;
	int		optc;
	int		fd = -1;
	mode_t		cmode =
	    (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
	struct stat	stbuf;
	struct stat	prstbuf;
	timespec_t	times[2];
	timespec_t	*tsp;

	(void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)
#define	TEXT_DOMAIN "SYS_TEST"
#endif
	(void) textdomain(TEXT_DOMAIN);

	myname = basename(argv[0]);
	if (strcmp(myname, "settime") == 0) {
		cflag++;
		stflag++;
		while ((optc = getopt(argc, argv, "f:")) != EOF) {
			switch (optc) {
			case 'f':
				rflag++;
				usecurrenttime = 0;
				if (stat(optarg, &prstbuf) == -1) {
					(void) fprintf(stderr, "%s: ", myname);
					perror(optarg);
					return (2);
				}
				break;
			case '?':
				usage(stflag);
				break;
			}
		}
	} else {
		while ((optc = getopt(argc, argv, "acfmr:d:t:")) != EOF) {
			switch (optc) {
			case 'a':
				aflag++;
				break;
			case 'c':
				cflag++;
				break;
			case 'f':	/* silently ignore for UCB compat */
				break;
			case 'm':
				mflag++;
				break;
			case 'r':	/* same as settime's -f option */
				rflag++;
				usecurrenttime = 0;
				if (stat(optarg, &prstbuf) == -1) {
					(void) fprintf(stderr, "%s: ", myname);
					perror(optarg);
					return (2);
				}
				break;
			case 'd':
				tflag++;
				usecurrenttime = 0;
				parse_datetime(optarg, &prstbuf.st_mtim);
				prstbuf.st_atim = prstbuf.st_mtim;
				break;
			case 't':
				tflag++;
				usecurrenttime = 0;
				parse_time(optarg, &prstbuf.st_mtim);
				prstbuf.st_atim = prstbuf.st_mtim;
				break;
			case '?':
				usage(stflag);
				break;
			}
		}
	}

	argc -= optind;
	argv += optind;

	if ((argc < 1) || (rflag + tflag > 1))
		usage(stflag);

	if ((aflag == 0) && (mflag == 0)) {
		aflag = 1;
		mflag = 1;
	}
	if ((aflag && !mflag) || (mflag && !aflag))
		usecurrenttime = 0;

	/*
	 * If -r, -t or -d has been specified,
	 * use the specified time.
	 */
	timespecified = (rflag | tflag);

	if (timespecified == 0 && argc >= 2 && isnumber(*argv) &&
	    (strlen(*argv) == 8 || strlen(*argv) == 10)) {
		/*
		 * time is specified as an operand; use it.
		 */
		parse_timespec(*argv++, &prstbuf.st_mtim);
		prstbuf.st_atim = prstbuf.st_mtim;
		usecurrenttime = 0;
		timespecified = 1;
		argc--;
	}

	for (c = 0; c < argc; c++) {
		if (stat(argv[c], &stbuf)) {
			/*
			 * If stat failed for reasons other than EOVERFLOW or
			 * ENOENT, the file should not be created, since this
			 * can clobber the contents of an existing file.
			 */
			if (errno == EOVERFLOW) {
				/*
				 * Since we have EOVERFLOW,
				 * we know the file exists.
				 */
				/* EMPTY */;
			} else if (errno != ENOENT) {
				(void) fprintf(stderr,
				    gettext("%s: cannot stat %s: %s\n"),
				    myname, argv[c], strerror(errno));
				status++;
				continue;
			} else if (cflag) {
				continue;
			} else if ((fd = creat(argv[c], cmode)) < 0) {
				(void) fprintf(stderr,
				    gettext("%s: cannot create %s: %s\n"),
				    myname, argv[c], strerror(errno));
				status++;
				continue;
			}
		}

		if (usecurrenttime) {
			tsp = NULL;
		} else {
			if (mflag == 0) {
				/* Keep the mtime of the file */
				times[1].tv_nsec = UTIME_OMIT;
			} else if (timespecified) {
				/* Set the specified time */
				times[1] = prstbuf.st_mtim;
			} else {
				/* Otherwise, use the current time */
				times[1].tv_nsec = UTIME_NOW;
			}

			if (aflag == 0) {
				/* Keep the atime of the file */
				times[0].tv_nsec = UTIME_OMIT;
			} else if (timespecified) {
				/* Set the specified time */
				times[0] = prstbuf.st_atim;
			} else {
				/* Otherwise, use the current time */
				times[0].tv_nsec = UTIME_NOW;
			}

			tsp = times;
		}

		if ((fd >= 0 && futimens(fd, tsp) != 0) ||
		    (fd < 0 && utimensat(AT_FDCWD, argv[c], tsp, 0) != 0)) {
			(void) fprintf(stderr,
			    gettext("%s: cannot change times on %s: %s\n"),
			    myname, argv[c], strerror(errno));
			status++;
		}
		if (fd >= 0) {
			(void) close(fd);
			fd = -1;
		}
	}
	return (status);
}

static int
isnumber(char *s)
{
	int c;

	while ((c = *s++) != '\0')
		if (!isdigit(c))
			return (0);
	return (1);
}

static void
parse_datetime(char *t, timespec_t *ts)
{
	char		date[64];
	char		*year;
	char		*month;
	char		*day;
	char		*hour;
	char		*minute;
	char		*second;
	char		*fraction;
	int		utc = 0;
	char		*p;
	time_t		when;
	int		nanoseconds;
	struct tm	tm;

	/*
	 * The date string has the format (defined by the touch(1) spec):
	 *	YYYY-MM-DDThh:mm:SS[.frac][tz]
	 *	YYYY-MM-DDThh:mm:SS[,frac][tz]
	 * T is either the literal 'T' or is a space character.
	 * tz is either empty (local time) or the literal 'Z' (UTC).
	 * All other fields are strings of digits.
	 */

	/*
	 * Make a copy of the date string so it can be tokenized.
	 */
	if (strlcpy(date, t, sizeof (date)) >= sizeof (date))
		touchabort(BADTIME);

	/* deal with the optional trailing 'Z' first */
	p = date + strlen(date) - 1;
	if (*p == 'Z') {
		utc = 1;
		*p = '\0';
	}

	/* break out the component tokens */
	p = date;
	year = strsep(&p, "-");
	month = strsep(&p, "-");
	day = strsep(&p, "T ");
	hour = strsep(&p, ":");
	minute = strsep(&p, ":");
	second = strsep(&p, ".,");
	fraction = p;

	/* verify the component tokens */
	if (year == NULL || strlen(year) < 4 || !isnumber(year) ||
	    month == NULL || strlen(month) != 2 || !isnumber(month) ||
	    day == NULL || strlen(day) != 2 || !isnumber(day) ||
	    hour == NULL || strlen(hour) != 2 || !isnumber(hour) ||
	    minute == NULL || strlen(minute) != 2 || !isnumber(minute) ||
	    second == NULL || strlen(second) != 2 || !isnumber(second) ||
	    (fraction != NULL && (*fraction == '\0' || !isnumber(fraction))))
		touchabort(BADTIME);

	(void) memset(&tm, 0, sizeof (struct tm));

	tm.tm_year = atoi(year) - 1900;
	tm.tm_mon = atoi(month) - 1;
	tm.tm_mday = atoi(day);
	tm.tm_hour = atoi(hour);
	tm.tm_min = atoi(minute);
	tm.tm_sec = atoi(second);
	if (utc) {
		(void) setenv("TZ", "GMT0", 1);
		tzset();
	}

	errno = 0;
	if ((when = mktime(&tm)) == -1 && errno != 0)
		touchabort(BADTIME);
	if (tm.tm_isdst)
		when -= (timezone - altzone);

	if (fraction == NULL) {
		nanoseconds = 0;
	} else {
		/* truncate beyond 9 digits (nanoseconds) */
		if (strlen(fraction) > 9)
			fraction[9] = '\0';
		nanoseconds = atoi(fraction);

		switch (strlen(fraction)) {
		case 1:
			nanoseconds *= 100000000;
			break;
		case 2:
			nanoseconds *= 10000000;
			break;
		case 3:
			nanoseconds *= 1000000;
			break;
		case 4:
			nanoseconds *= 100000;
			break;
		case 5:
			nanoseconds *= 10000;
			break;
		case 6:
			nanoseconds *= 1000;
			break;
		case 7:
			nanoseconds *= 100;
			break;
		case 8:
			nanoseconds *= 10;
			break;
		case 9:
			break;
		}
	}

	ts->tv_sec = when;
	ts->tv_nsec = nanoseconds;
}

static void
parse_time(char *t, timespec_t *ts)
{
	int		century = 0;
	int		seconds = 0;
	char		*p;
	time_t		when;
	struct tm	tm;

	/*
	 * time in the following format (defined by the touch(1) spec):
	 *	[[CC]YY]MMDDhhmm[.SS]
	 */
	if ((p = strchr(t, '.')) != NULL) {
		if (strchr(p+1, '.') != NULL)
			touchabort(BADTIME);
		seconds = atoi_for2(p+1);
		*p = '\0';
	}

	(void) memset(&tm, 0, sizeof (struct tm));
	when = time(0);
	tm.tm_year = localtime(&when)->tm_year;

	switch (strlen(t)) {
		case 12:	/* CCYYMMDDhhmm */
			century = atoi_for2(t);
			t += 2;
			/* FALLTHROUGH */
		case 10:	/* YYMMDDhhmm */
			tm.tm_year = atoi_for2(t);
			t += 2;
			if (century == 0) {
				if (tm.tm_year < 69)
					tm.tm_year += 100;
			} else
				tm.tm_year += (century - 19) * 100;
			/* FALLTHROUGH */
		case 8:		/* MMDDhhmm */
			tm.tm_mon = atoi_for2(t) - 1;
			t += 2;
			tm.tm_mday = atoi_for2(t);
			t += 2;
			tm.tm_hour = atoi_for2(t);
			t += 2;
			tm.tm_min = atoi_for2(t);
			tm.tm_sec = seconds;
			break;
		default:
			touchabort(BADTIME);
	}

	if ((when = mktime(&tm)) == -1)
		touchabort(BADTIME);
	if (tm.tm_isdst)
		when -= (timezone-altzone);

	ts->tv_sec = when;
	ts->tv_nsec = 0;
}

static void
parse_timespec(char *t, timespec_t *ts)
{
	time_t		when;
	struct tm	tm;

	/*
	 * time in the following format (defined by the touch(1) spec):
	 *	MMDDhhmm[yy]
	 */

	(void) memset(&tm, 0, sizeof (struct tm));
	when = time(0);
	tm.tm_year = localtime(&when)->tm_year;

	switch (strlen(t)) {
		case 10:	/* MMDDhhmmyy */
			tm.tm_year = atoi_for2(t+8);
			if (tm.tm_year < 69)
				tm.tm_year += 100;
			/* FALLTHROUGH */
		case 8:		/* MMDDhhmm */
			tm.tm_mon = atoi_for2(t) - 1;
			t += 2;
			tm.tm_mday = atoi_for2(t);
			t += 2;
			tm.tm_hour = atoi_for2(t);
			t += 2;
			tm.tm_min = atoi_for2(t);
			break;
		default:
			touchabort(BADTIME);
	}

	if ((when = mktime(&tm)) == -1)
		touchabort(BADTIME);
	if (tm.tm_isdst)
		when -= (timezone - altzone);

	ts->tv_sec = when;
	ts->tv_nsec = 0;
}

static int
atoi_for2(char *p)
{
	int value;

	value = (*p - '0') * 10 + *(p+1) - '0';
	if ((value < 0) || (value > 99))
		touchabort(BADTIME);
	return (value);
}

static void
touchabort(const char *message)
{
	(void) fprintf(stderr, "%s: %s\n", myname, gettext(message));
	exit(1);
}

static void
usage(const int settime)
{
	if (settime) {
		(void) fprintf(stderr, gettext(
		    "usage: %s [-f file] [mmddhhmm[yy]] file...\n"), myname);
		exit(2);
	}
	(void) fprintf(stderr, gettext(
	    "usage: %s [-acm] [-r ref_file] file...\n"
	    "       %s [-acm] [-t [[CC]YY]MMDDhhmm[.SS]] file...\n"
	    "       %s [-acm] [-d YYYY-MM-DDThh:mm:SS[.frac][Z]] file...\n"
	    "       %s [-acm] [-d YYYY-MM-DDThh:mm:SS[,frac][Z]] file...\n"
	    "       %s [-acm] [MMDDhhmm[yy]] file...\n"),
	    myname, myname, myname, myname, myname);
	exit(2);
}