summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTavian Barnes <tavianator@tavianator.com>2022-11-07 15:10:50 -0500
committerTavian Barnes <tavianator@tavianator.com>2022-11-07 15:10:50 -0500
commit2d5edb37b924715b4fbee4d917ac334c773fca61 (patch)
treef4b73d20f42e18c99585823e33cbc1eb1261651a
parent3139cbc56a08ac76bccfe223dd2669f3f080c927 (diff)
downloadbfs-2d5edb37b924715b4fbee4d917ac334c773fca61.tar.xz
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).
-rw-r--r--Makefile10
-rw-r--r--tests/bfs/L_capable.sh2
-rw-r--r--tests/bfs/capable.sh2
-rw-r--r--tests/bfs/color_ls.sh2
-rw-r--r--tests/bsd/L_acl.sh2
-rw-r--r--tests/bsd/acl.sh2
-rw-r--r--tests/bsd/flags.sh2
-rw-r--r--tests/bsd/rm.sh2
-rw-r--r--tests/common/L_mount.sh2
-rw-r--r--tests/common/delete.sh2
-rw-r--r--tests/common/delete_many.sh2
-rw-r--r--tests/common/inum_bind_mount.sh2
-rw-r--r--tests/common/mount.sh2
-rw-r--r--tests/gnu/fprint_duplicate.sh2
-rw-r--r--tests/gnu/ignore_readdir_race.sh2
-rw-r--r--tests/gnu/ignore_readdir_race_notdir.sh4
-rw-r--r--tests/gnu/xtype_bind_mount.sh2
-rw-r--r--tests/posix/L_xdev.sh2
-rw-r--r--tests/posix/depth_error.sh2
-rw-r--r--tests/posix/type_bind_mount.sh2
-rw-r--r--tests/posix/xdev.sh2
-rwxr-xr-xtests/tests.sh106
-rw-r--r--tests/xtouch.c195
23 files changed, 263 insertions, 90 deletions
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 <tavianator@tavianator.com> *
+ * *
+ * 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 <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+
+/** 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;
+}