From 2d5edb37b924715b4fbee4d917ac334c773fca61 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 7 Nov 2022 15:10:50 -0500 Subject: tests/xtouch: New utility POSIX touch(1) doesn't include the -h option, and indeed OpenBSD doesn't implement it. Making our own utility also lets us add some handy extensions like -p (create parents) and -M (set permissions). --- tests/xtouch.c | 195 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 tests/xtouch.c (limited to 'tests/xtouch.c') diff --git a/tests/xtouch.c b/tests/xtouch.c new file mode 100644 index 0000000..7a248eb --- /dev/null +++ b/tests/xtouch.c @@ -0,0 +1,195 @@ +/**************************************************************************** + * bfs * + * Copyright (C) 2022 Tavian Barnes * + * * + * Permission to use, copy, modify, and/or distribute this software for any * + * purpose with or without fee is hereby granted. * + * * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * + ****************************************************************************/ + +#include "../src/xtime.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** Check if a path has a trailing slash. */ +static bool trailing_slash(const char *path) { + size_t len = strlen(path); + return len > 0 && path[len - 1] == '/'; +} + +/** Create any parent directories of the given path. */ +static int mkdirs(const char *path, mode_t mode) { + char *copy = strdup(path); + if (!copy) { + return -1; + } + + int ret = -1; + char *cur = copy + strspn(copy, "/"); + while (true) { + cur += strcspn(cur, "/"); + + char *next = cur + strspn(cur, "/"); + if (!*next) { + ret = 0; + break; + } + + *cur = '\0'; + if (mkdir(copy, mode) != 0 && errno != EEXIST) { + break; + } + *cur = '/'; + cur = next; + } + + free(copy); + return ret; +} + +/** Touch one path. */ +static int xtouch(const char *path, const struct timespec times[2], int flags, mode_t mode, bool parents, mode_t pmode) { + int ret = utimensat(AT_FDCWD, path, times, flags); + if (ret == 0 || errno != ENOENT) { + return ret; + } + + if (parents && mkdirs(path, pmode) != 0) { + return -1; + } + + if (trailing_slash(path)) { + if (mkdir(path, mode) != 0) { + return -1; + } + + return utimensat(AT_FDCWD, path, times, flags); + } else { + int fd = open(path, O_WRONLY | O_CREAT, mode); + if (fd < 0) { + return -1; + } + + if (futimens(fd, times) != 0) { + int error = errno; + close(fd); + errno = error; + return -1; + } + + return close(fd); + } +} + +int main(int argc, char *argv[]) { + int flags = 0; + bool atime = false, mtime = false; + bool parents = false; + const char *mstr = NULL; + const char *tstr = NULL; + + const char *cmd = argc > 0 ? argv[0] : "xtouch"; + int c; + while (c = getopt(argc, argv, ":M:ahmpt:"), c != -1) { + switch (c) { + case 'M': + mstr = optarg; + break; + case 'a': + atime = true; + break; + case 'h': + flags = AT_SYMLINK_NOFOLLOW; + break; + case 'm': + mtime = true; + break; + case 'p': + parents = true; + break; + case 't': + tstr = optarg; + break; + case ':': + fprintf(stderr, "%s: Missing argument to -%c\n", cmd, optopt); + return EXIT_FAILURE; + case '?': + fprintf(stderr, "%s: Unrecognized option -%c\n", cmd, optopt); + return EXIT_FAILURE; + } + } + + mode_t mask = umask(0); + mode_t fmode = 0666 & ~mask; + mode_t dmode = 0777 & ~mask; + mode_t pmode = 0777 & ~mask; + if (mstr) { + char *end; + long mode = strtol(mstr, &end, 8); + if (*mstr && !*end && mode >= 0 && mode < 01000) { + fmode = dmode = mode; + } else { + fprintf(stderr, "%s: Invalid mode '%s'\n", cmd, mstr); + return EXIT_FAILURE; + } + } + + struct timespec ts; + if (tstr) { + if (xgetdate(tstr, &ts) != 0) { + fprintf(stderr, "%s: Parsing time '%s' failed: %s\n", cmd, tstr, strerror(errno)); + return EXIT_FAILURE; + } + } else { + // Don't use UTIME_NOW, so that multiple paths all get the same timestamp + if (xgettime(&ts) != 0) { + perror("xgettime()"); + return EXIT_FAILURE; + } + } + + struct timespec times[2] = { + { .tv_nsec = UTIME_OMIT }, + { .tv_nsec = UTIME_OMIT }, + }; + if (!atime && !mtime) { + atime = true; + mtime = true; + } + if (atime) { + times[0] = ts; + } + if (mtime) { + times[1] = ts; + } + + if (optind >= argc) { + fprintf(stderr, "%s: No files to touch\n", cmd); + return EXIT_FAILURE; + } + + int ret = EXIT_SUCCESS; + for (; optind < argc; ++optind) { + const char *path = argv[optind]; + bool isdir = trailing_slash(path); + if (xtouch(path, times, flags, isdir ? dmode : fmode, parents, pmode) != 0) { + fprintf(stderr, "%s: Touching '%s' failed: %s\n", cmd, path, strerror(errno)); + ret = EXIT_FAILURE; + } + } + return ret; +} -- cgit v1.2.3