coreutils

General Software Utilities
git clone http://git.omkov.net/coreutils
Log | Tree | Refs | README | LICENCE | Download

coreutils/src/touch.c (145 lines, 4.4 KiB) -rw-r--r-- blame download

0123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
// touch.c, version 1.0.2
// OMKOV coreutils implementation of POSIX touch
// Copyright (C) 2020, Jakob Wakeling
// MIT Licence

/*
	TODO Implement [.frac] support for -d option.
*/

#define _XOPEN_SOURCE 700

#include "util/error.h"
#include "util/optget.h"

#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>

#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>

#define VERSION "1.0.1"

static struct lop lops[] = {
	{ "help",    ARG_NUL, 256 },
	{ "version", ARG_NUL, 257 },
	{ NULL, 0, 0 }
};

static bool aflag, cflag, mflag;
static bool rflag, notnow;

static struct timespec times[2] = {{ .tv_nsec = UTIME_NOW }};

static inline void dparse(const char *str);
static inline void tparse(const char *str);
static inline void rparse(const char *str);

static inline int touch(const char *file);

static void hlp(void);
static void ver(void);

int main(int ac, char *av[]) { A0 = av[0];
	struct opt opt = OPTGET_INIT; opt.str = "acd:mr:t:"; opt.lops = lops; int o;
	while ((o = optget(&opt, av, 1)) != -1) switch (o) {
	case 'a': { aflag = true; break; }
	case 'c': { cflag = true; break; }
	case 'd': { if (notnow) { goto invalid; } dparse(opt.arg); break; }
	case 'm': { mflag = true; break; }
	case 'r': { if (notnow) { goto invalid; } rparse(opt.arg); break; }
	case 't': { if (notnow) { goto invalid; } tparse(opt.arg); break; }
	case 256: { hlp(); return 0; }
	case 257: { ver(); return 0; }
	default: { return 1; }
	invalid: { error(1, "invalid option combination"); }
	}
	
	if (opt.ind == ac) { error(1, "missing operand"); }
	
	if (!rflag) { times[1] = times[0]; }
	if (!aflag && !mflag) { aflag = mflag = true; }
	else if (!aflag) { times[0].tv_nsec = UTIME_OMIT; }
	else if (!mflag) { times[1].tv_nsec = UTIME_OMIT; }
	
	bool warned = false;
	
	for (char **p = &av[opt.ind]; *p; ++p) if (touch(*p)) {
		warn("%s: %s", *p, serr()); warned = true;
	}
	
	return warned;
}

static inline void dparse(const char *str) {
	struct tm t = { 0 }; t.tm_isdst = -1;
	
	register size_t len = strlen(str);
	if (len == 19 && strptime(str, "%Y-%m-%dT%H:%M:%S", &t));
	else if (len == 20 && strptime(str, "%Y-%m-%dT%H:%M:%SZ", &t)) {
		tzset(); t.tm_sec -= timezone;
	} else { error(1, "%s: invalid time format", str); }
	
	struct timespec ts; ts.tv_sec = mktime(&t); ts.tv_nsec = 0;
	times[0] = ts; notnow = true; return;
}

static inline void tparse(const char *str) {
	time_t now; struct tm *cur, t = { 0 };
	if ((now = time(NULL)) == -1 || !(cur = localtime(&now))) {
		error(1, "%s: %s", str, serr());
	} t.tm_isdst = -1;
	
	register size_t len = strlen(str);
	if (len == 8 && strptime(str, "%m%d%H%M", &t));
	else if (len == 10 && strptime(str, "%y%m%d%H%M", &t));
	else if (len == 11 && strptime(str, "%m%d%H%M.%S", &t));
	else if (len == 12 && strptime(str, "%Y%m%d%H%M", &t));
	else if (len == 13 && strptime(str, "%y%m%d%H%M.%S", &t));
	else if (len == 15 && strptime(str, "%Y%m%d%H%M.%S", &t));
	else { error(1, "%s: invalid time format", str); }
	
	struct timespec ts; ts.tv_sec = mktime(&t); ts.tv_nsec = 0;
	times[0] = ts; notnow = true; return;
}

static inline void rparse(const char *file) {
	struct stat fs; if (stat(file, &fs)) {
		error(1, "%s: %s", file, serr());
	} times[0] = fs.st_atim; times[1] = fs.st_mtim;
	rflag = true; notnow = true; return;
}

static inline int touch(const char *file) {
	if (utimensat(AT_FDCWD, file, times, 0)) {
		if (errno != ENOENT) { return 1; } if (cflag) { return 0; }
		unsigned m = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
		register int fd; if ((fd = creat(file, m)) == -1) { return 1; }
		if (futimens(fd, times)) { close(fd); return 1; } close(fd);
	} return 0;
}

static void hlp(void) {
	puts("touch - change file access and modify times\n");
	puts("usage: touch [-acm] [-d time|-r file|-t time] file...\n");
	puts("options:");
	puts("  -a         Change the access time only");
	puts("  -c         Do not create new files");
	puts("  -d time    Use YYYY-MM-DDThh:mm:SS[.frac][tz]");
	puts("  -m         Change the modification time only");
	puts("  -r file    Use a specified files times");
	puts("  -t time    Use [[CC]YY]MMDDhhmm[.SS]");
	puts("  --help     Display help information");
	puts("  --version  Display version information");
}

static void ver(void) {
	puts("OMKOV coreutils touch, version " VERSION);
	puts("Copyright (C) 2020, Jakob Wakeling");
	puts("MIT Licence (https://opensource.org/licenses/MIT)");
}