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). --- Makefile | 10 +- tests/bfs/L_capable.sh | 2 +- tests/bfs/capable.sh | 2 +- tests/bfs/color_ls.sh | 2 +- tests/bsd/L_acl.sh | 2 +- tests/bsd/acl.sh | 2 +- tests/bsd/flags.sh | 2 +- tests/bsd/rm.sh | 2 +- tests/common/L_mount.sh | 2 +- tests/common/delete.sh | 2 +- tests/common/delete_many.sh | 2 +- tests/common/inum_bind_mount.sh | 2 +- tests/common/mount.sh | 2 +- tests/gnu/fprint_duplicate.sh | 2 +- tests/gnu/ignore_readdir_race.sh | 2 +- tests/gnu/ignore_readdir_race_notdir.sh | 4 +- tests/gnu/xtype_bind_mount.sh | 2 +- tests/posix/L_xdev.sh | 2 +- tests/posix/depth_error.sh | 2 +- tests/posix/type_bind_mount.sh | 2 +- tests/posix/xdev.sh | 2 +- tests/tests.sh | 106 +++++++---------- tests/xtouch.c | 195 ++++++++++++++++++++++++++++++++ 23 files changed, 263 insertions(+), 90 deletions(-) create mode 100644 tests/xtouch.c diff --git a/Makefile b/Makefile index 66126df..0f2235f 100644 --- a/Makefile +++ b/Makefile @@ -198,7 +198,12 @@ DISTCHECK_FLAGS := -s TEST_FLAGS="--sudo --verbose=skipped" bfs: $(BIN)/bfs .PHONY: bfs -all: $(BIN)/bfs $(BIN)/tests/mksock $(BIN)/tests/trie $(BIN)/tests/xtimegm +all: \ + $(BIN)/bfs \ + $(BIN)/tests/mksock \ + $(BIN)/tests/trie \ + $(BIN)/tests/xtimegm \ + $(BIN)/tests/xtouch .PHONY: all $(BIN)/bfs: \ @@ -230,6 +235,7 @@ $(BIN)/bfs: \ $(BIN)/tests/mksock: $(OBJ)/tests/mksock.o $(BIN)/tests/trie: $(OBJ)/src/trie.o $(OBJ)/tests/trie.o $(BIN)/tests/xtimegm: $(OBJ)/src/xtime.o $(OBJ)/tests/xtimegm.o +$(BIN)/tests/xtouch: $(OBJ)/src/xtime.o $(OBJ)/tests/xtouch.o $(BIN)/%: @$(MKDIR) $(@D) @@ -257,7 +263,7 @@ $(FLAG_GOALS): $(FLAG_PREREQS) check: $(CHECKS) .PHONY: check $(CHECKS) -$(STRATEGY_CHECKS): check-%: $(BIN)/bfs $(BIN)/tests/mksock +$(STRATEGY_CHECKS): check-%: $(BIN)/bfs $(BIN)/tests/mksock $(BIN)/tests/xtouch ./tests/tests.sh --bfs="$(BIN)/bfs -S $*" $(TEST_FLAGS) check-trie check-xtimegm: check-%: $(BIN)/tests/% diff --git a/tests/bfs/L_capable.sh b/tests/bfs/L_capable.sh index f37e1f2..533ac2f 100644 --- a/tests/bfs/L_capable.sh +++ b/tests/bfs/L_capable.sh @@ -5,7 +5,7 @@ clean_scratch skip_unless invoke_bfs scratch -quit -capable -$TOUCH scratch/{normal,capable} +"$XTOUCH" scratch/{normal,capable} sudo setcap all+ep scratch/capable ln -s capable scratch/link diff --git a/tests/bfs/capable.sh b/tests/bfs/capable.sh index 6dc9bf2..256b9bc 100644 --- a/tests/bfs/capable.sh +++ b/tests/bfs/capable.sh @@ -5,7 +5,7 @@ clean_scratch skip_unless invoke_bfs scratch -quit -capable -$TOUCH scratch/{normal,capable} +"$XTOUCH" scratch/{normal,capable} sudo setcap all+ep scratch/capable ln -s capable scratch/link diff --git a/tests/bfs/color_ls.sh b/tests/bfs/color_ls.sh index 33922a8..37d088f 100644 --- a/tests/bfs/color_ls.sh +++ b/tests/bfs/color_ls.sh @@ -1,5 +1,5 @@ clean_scratch -touchp scratch/foo/bar/baz +"$XTOUCH" -p scratch/foo/bar/baz ln -s foo/bar/baz scratch/link ln -s foo/bar/nowhere scratch/broken ln -s foo/bar/nowhere/nothing scratch/nested diff --git a/tests/bsd/L_acl.sh b/tests/bsd/L_acl.sh index 4f990bb..cf573e4 100644 --- a/tests/bsd/L_acl.sh +++ b/tests/bsd/L_acl.sh @@ -2,7 +2,7 @@ clean_scratch skip_unless invoke_bfs scratch -quit -acl -$TOUCH scratch/{normal,acl} +"$XTOUCH" scratch/{normal,acl} skip_unless set_acl scratch/acl ln -s acl scratch/link diff --git a/tests/bsd/acl.sh b/tests/bsd/acl.sh index c669005..1665684 100644 --- a/tests/bsd/acl.sh +++ b/tests/bsd/acl.sh @@ -2,7 +2,7 @@ clean_scratch skip_unless invoke_bfs scratch -quit -acl -$TOUCH scratch/{normal,acl} +"$XTOUCH" scratch/{normal,acl} skip_unless set_acl scratch/acl ln -s acl scratch/link diff --git a/tests/bsd/flags.sh b/tests/bsd/flags.sh index fd25150..ffb1cc2 100644 --- a/tests/bsd/flags.sh +++ b/tests/bsd/flags.sh @@ -2,7 +2,7 @@ skip_unless invoke_bfs scratch -quit -flags offline clean_scratch -$TOUCH scratch/{foo,bar} +"$XTOUCH" scratch/{foo,bar} skip_unless chflags offline scratch/bar bfs_diff scratch -flags -offline,nohidden diff --git a/tests/bsd/rm.sh b/tests/bsd/rm.sh index e661a37..9ee2b0a 100644 --- a/tests/bsd/rm.sh +++ b/tests/bsd/rm.sh @@ -1,5 +1,5 @@ clean_scratch -touchp scratch/foo/bar/baz +"$XTOUCH" -p scratch/foo/bar/baz (cd scratch && invoke_bfs . -rm) diff --git a/tests/common/L_mount.sh b/tests/common/L_mount.sh index d9e1dec..dad7e00 100644 --- a/tests/common/L_mount.sh +++ b/tests/common/L_mount.sh @@ -5,7 +5,7 @@ clean_scratch mkdir scratch/{foo,mnt} sudo mount -t tmpfs tmpfs scratch/mnt ln -s ../mnt scratch/foo/bar -$TOUCH scratch/mnt/baz +"$XTOUCH" scratch/mnt/baz ln -s ../mnt/baz scratch/foo/qux bfs_diff -L scratch -mount diff --git a/tests/common/delete.sh b/tests/common/delete.sh index 3709897..89cf2a2 100644 --- a/tests/common/delete.sh +++ b/tests/common/delete.sh @@ -1,5 +1,5 @@ clean_scratch -touchp scratch/foo/bar/baz +"$XTOUCH" -p scratch/foo/bar/baz # Don't try to delete '.' (cd scratch && invoke_bfs . -delete) diff --git a/tests/common/delete_many.sh b/tests/common/delete_many.sh index 6f54825..6274319 100644 --- a/tests/common/delete_many.sh +++ b/tests/common/delete_many.sh @@ -2,7 +2,7 @@ clean_scratch mkdir scratch/foo -$TOUCH scratch/foo/{1..256} +"$XTOUCH" scratch/foo/{1..256} invoke_bfs scratch/foo -delete bfs_diff scratch diff --git a/tests/common/inum_bind_mount.sh b/tests/common/inum_bind_mount.sh index 8b1ac0e..e35ed4e 100644 --- a/tests/common/inum_bind_mount.sh +++ b/tests/common/inum_bind_mount.sh @@ -2,7 +2,7 @@ skip_unless test "$SUDO" skip_unless test "$UNAME" = "Linux" clean_scratch -$TOUCH scratch/{foo,bar} +"$XTOUCH" scratch/{foo,bar} sudo mount --bind scratch/{foo,bar} bfs_diff scratch -inum "$(inum scratch/bar)" diff --git a/tests/common/mount.sh b/tests/common/mount.sh index 6131ad4..2732a68 100644 --- a/tests/common/mount.sh +++ b/tests/common/mount.sh @@ -4,7 +4,7 @@ skip_if test "$UNAME" = "Darwin" clean_scratch mkdir scratch/{foo,mnt} sudo mount -t tmpfs tmpfs scratch/mnt -$TOUCH scratch/foo/bar scratch/mnt/baz +"$XTOUCH" scratch/foo/bar scratch/mnt/baz bfs_diff scratch -mount ret=$? diff --git a/tests/gnu/fprint_duplicate.sh b/tests/gnu/fprint_duplicate.sh index b4eb047..5275502 100644 --- a/tests/gnu/fprint_duplicate.sh +++ b/tests/gnu/fprint_duplicate.sh @@ -1,4 +1,4 @@ -touchp scratch/foo.out +"$XTOUCH" -p scratch/foo.out ln scratch/foo.out scratch/foo.hard ln -s foo.out scratch/foo.soft diff --git a/tests/gnu/ignore_readdir_race.sh b/tests/gnu/ignore_readdir_race.sh index 547253b..6586bcc 100644 --- a/tests/gnu/ignore_readdir_race.sh +++ b/tests/gnu/ignore_readdir_race.sh @@ -1,5 +1,5 @@ clean_scratch -$TOUCH scratch/{foo,bar} +"$XTOUCH" scratch/{foo,bar} # -links 1 forces a stat() call, which will fail for the second file invoke_bfs scratch -mindepth 1 -ignore_readdir_race -links 1 -exec "$TESTS/remove-sibling.sh" {} \; diff --git a/tests/gnu/ignore_readdir_race_notdir.sh b/tests/gnu/ignore_readdir_race_notdir.sh index 91a383b..5b8b56d 100644 --- a/tests/gnu/ignore_readdir_race_notdir.sh +++ b/tests/gnu/ignore_readdir_race_notdir.sh @@ -1,5 +1,5 @@ # Check -ignore_readdir_race handling when a directory is replaced with a file clean_scratch -touchp scratch/foo/bar +"$XTOUCH" -p scratch/foo/bar -invoke_bfs scratch -mindepth 1 -ignore_readdir_race -execdir rm -r {} \; -execdir $TOUCH {} \; +invoke_bfs scratch -mindepth 1 -ignore_readdir_race -execdir rm -r {} \; -execdir "$XTOUCH" {} \; diff --git a/tests/gnu/xtype_bind_mount.sh b/tests/gnu/xtype_bind_mount.sh index d371cd5..264b6f8 100644 --- a/tests/gnu/xtype_bind_mount.sh +++ b/tests/gnu/xtype_bind_mount.sh @@ -2,7 +2,7 @@ skip_unless test "$SUDO" skip_unless test "$UNAME" = "Linux" clean_scratch -$TOUCH scratch/{file,null} +"$XTOUCH" scratch/{file,null} sudo mount --bind /dev/null scratch/null ln -s /dev/null scratch/link diff --git a/tests/posix/L_xdev.sh b/tests/posix/L_xdev.sh index c18a39e..ddbadd8 100644 --- a/tests/posix/L_xdev.sh +++ b/tests/posix/L_xdev.sh @@ -5,7 +5,7 @@ clean_scratch mkdir scratch/{foo,mnt} sudo mount -t tmpfs tmpfs scratch/mnt ln -s ../mnt scratch/foo/bar -$TOUCH scratch/mnt/baz +"$XTOUCH" scratch/mnt/baz ln -s ../mnt/baz scratch/foo/qux bfs_diff -L scratch -xdev diff --git a/tests/posix/depth_error.sh b/tests/posix/depth_error.sh index e1267f4..f770210 100644 --- a/tests/posix/depth_error.sh +++ b/tests/posix/depth_error.sh @@ -1,5 +1,5 @@ clean_scratch -touchp scratch/foo/bar +"$XTOUCH" -p scratch/foo/bar chmod a-r scratch/foo bfs_diff scratch -depth diff --git a/tests/posix/type_bind_mount.sh b/tests/posix/type_bind_mount.sh index e05baa3..445f6ef 100644 --- a/tests/posix/type_bind_mount.sh +++ b/tests/posix/type_bind_mount.sh @@ -2,7 +2,7 @@ skip_unless test "$SUDO" skip_unless test "$UNAME" = "Linux" clean_scratch -$TOUCH scratch/{file,null} +"$XTOUCH" scratch/{file,null} sudo mount --bind /dev/null scratch/null bfs_diff scratch -type c diff --git a/tests/posix/xdev.sh b/tests/posix/xdev.sh index 7c26c3f..4591940 100644 --- a/tests/posix/xdev.sh +++ b/tests/posix/xdev.sh @@ -4,7 +4,7 @@ skip_if test "$UNAME" = "Darwin" clean_scratch mkdir scratch/{foo,mnt} sudo mount -t tmpfs tmpfs scratch/mnt -$TOUCH scratch/foo/bar scratch/mnt/baz +"$XTOUCH" scratch/foo/bar scratch/mnt/baz bfs_diff scratch -xdev ret=$? diff --git a/tests/tests.sh b/tests/tests.sh index 03ce3eb..6a32f62 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -248,6 +248,8 @@ if [ "${BUILDDIR-}" ]; then else BIN=$(_realpath "$TESTS/../bin") fi +MKSOCK="$BIN/tests/mksock" +XTOUCH="$BIN/tests/xtouch" # Try to resolve the path to $BFS before we cd, while also supporting # --bfs="./bin/bfs -S ids" @@ -311,63 +313,34 @@ else echo "Test files saved to $TMP" fi -# Install a file, creating any parent directories -function installp() { - local target="${@: -1}" - mkdir -p "${target%/*}" - install "$@" -} - -# Prefer GNU touch to work around https://apple.stackexchange.com/a/425730/397839 -if command -v gtouch &>/dev/null; then - TOUCH=gtouch -else - TOUCH=touch -fi - -# Like a mythical touch -p -function touchp() { - for arg; do - installp -m644 /dev/null "$arg" - done -} - # Creates a simple file+directory structure for tests function make_basic() { - touchp "$1/a" - touchp "$1/b" - touchp "$1/c/d" - touchp "$1/e/f" - mkdir -p "$1/g/h" - mkdir -p "$1/i" - touchp "$1/j/foo" - touchp "$1/k/foo/bar" - touchp "$1/l/foo/bar/baz" + "$XTOUCH" -p "$1"/{a,b,c/d,e/f,g/h/,i/} + "$XTOUCH" -p "$1"/{j/foo,k/foo/bar,l/foo/bar/baz} echo baz >"$1/l/foo/bar/baz" } make_basic "$TMP/basic" # Creates a file+directory structure with various permissions for tests function make_perms() { - installp -m000 /dev/null "$1/0" - installp -m444 /dev/null "$1/r" - installp -m222 /dev/null "$1/w" - installp -m644 /dev/null "$1/rw" - installp -m555 /dev/null "$1/rx" - installp -m311 /dev/null "$1/wx" - installp -m755 /dev/null "$1/rwx" + "$XTOUCH" -p -M000 "$1/0" + "$XTOUCH" -p -M444 "$1/r" + "$XTOUCH" -p -M222 "$1/w" + "$XTOUCH" -p -M644 "$1/rw" + "$XTOUCH" -p -M555 "$1/rx" + "$XTOUCH" -p -M311 "$1/wx" + "$XTOUCH" -p -M755 "$1/rwx" } make_perms "$TMP/perms" # Creates a file+directory structure with various symbolic and hard links function make_links() { - touchp "$1/file" + "$XTOUCH" -p "$1/file" ln -s file "$1/symlink" ln "$1/file" "$1/hardlink" ln -s nowhere "$1/broken" ln -s symlink/file "$1/notdir" - mkdir -p "$1/deeply/nested/dir" - touchp "$1/deeply/nested/file" + "$XTOUCH" -p "$1/deeply/nested"/{dir/,file} ln -s file "$1/deeply/nested/link" ln -s nowhere "$1/deeply/nested/broken" ln -s deeply/nested "$1/skip" @@ -376,7 +349,7 @@ make_links "$TMP/links" # Creates a file+directory structure with symbolic link loops function make_loops() { - touchp "$1/file" + "$XTOUCH" -p "$1/file" ln -s file "$1/symlink" ln -s nowhere "$1/broken" ln -s symlink/file "$1/notdir" @@ -389,29 +362,28 @@ make_loops "$TMP/loops" # Creates a file+directory structure with varying timestamps function make_times() { - mkdir -p "$1" - $TOUCH -t 199112140000 "$1/a" - $TOUCH -t 199112140001 "$1/b" - $TOUCH -t 199112140002 "$1/c" + "$XTOUCH" -p -t 199112140000 "$1/a" + "$XTOUCH" -p -t 199112140001 "$1/b" + "$XTOUCH" -p -t 199112140002 "$1/c" ln -s a "$1/l" - $TOUCH -h -t 199112140003 "$1/l" - $TOUCH -t 199112140004 "$1" + "$XTOUCH" -p -h -t 199112140003 "$1/l" + "$XTOUCH" -p -t 199112140004 "$1" } make_times "$TMP/times" # Creates a file+directory structure with various weird file/directory names function make_weirdnames() { - touchp "$1/-/a" - touchp "$1/(/b" - touchp "$1/(-/c" - touchp "$1/!/d" - touchp "$1/!-/e" - touchp "$1/,/f" - touchp "$1/)/g" - touchp "$1/.../h" - touchp "$1/\\/i" - touchp "$1/ /j" - touchp "$1/[/k" + "$XTOUCH" -p "$1/-/a" + "$XTOUCH" -p "$1/(/b" + "$XTOUCH" -p "$1/(-/c" + "$XTOUCH" -p "$1/!/d" + "$XTOUCH" -p "$1/!-/e" + "$XTOUCH" -p "$1/,/f" + "$XTOUCH" -p "$1/)/g" + "$XTOUCH" -p "$1/.../h" + "$XTOUCH" -p "$1/\\/i" + "$XTOUCH" -p "$1/ /j" + "$XTOUCH" -p "$1/[/k" } make_weirdnames "$TMP/weirdnames" @@ -439,7 +411,7 @@ function make_deep() { cd "$names" done - $TOUCH "$name" + "$XTOUCH" "$name" ) done } @@ -447,24 +419,24 @@ make_deep "$TMP/deep" # Creates a directory structure with many different types, and therefore colors function make_rainbow() { - touchp "$1/file.txt" - touchp "$1/file.dat" - touchp "$1/star".{gz,tar,tar.gz} + "$XTOUCH" -p "$1/file.txt" + "$XTOUCH" -p "$1/file.dat" + "$XTOUCH" -p "$1/star".{gz,tar,tar.gz} ln -s file.txt "$1/link.txt" - touchp "$1/mh1" + "$XTOUCH" -p "$1/mh1" ln "$1/mh1" "$1/mh2" mkfifo "$1/pipe" # TODO: block ln -s /dev/null "$1/chardev_link" ln -s nowhere "$1/broken" - "$BIN/tests/mksock" "$1/socket" - touchp "$1"/s{u,g,ug}id + "$MKSOCK" "$1/socket" + "$XTOUCH" -p "$1"/s{u,g,ug}id chmod u+s "$1"/su{,g}id chmod g+s "$1"/s{u,}gid mkdir "$1/ow" "$1"/sticky{,_ow} chmod o+w "$1"/*ow chmod +t "$1"/sticky* - touchp "$1"/exec.sh + "$XTOUCH" -p "$1"/exec.sh chmod +x "$1"/exec.sh } make_rainbow "$TMP/rainbow" @@ -631,7 +603,7 @@ function set_acl() { function make_xattrs() { clean_scratch - $TOUCH scratch/{normal,xattr,xattr_2} + "$XTOUCH" scratch/{normal,xattr,xattr_2} ln -s xattr scratch/link ln -s normal scratch/xattr_link 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