summaryrefslogtreecommitdiffstats
path: root/tests/xtouch.c
diff options
context:
space:
mode:
Diffstat (limited to 'tests/xtouch.c')
-rw-r--r--tests/xtouch.c163
1 files changed, 103 insertions, 60 deletions
diff --git a/tests/xtouch.c b/tests/xtouch.c
index 4a02bf3..f33c573 100644
--- a/tests/xtouch.c
+++ b/tests/xtouch.c
@@ -1,16 +1,17 @@
// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
-#include "../src/bfstd.h"
-#include "../src/config.h"
-#include "../src/sanity.h"
-#include "../src/xtime.h"
+#include "bfstd.h"
+#include "sanity.h"
+#include "xtime.h"
+
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
+#include <sys/types.h>
#include <time.h>
#include <unistd.h>
@@ -37,83 +38,128 @@ struct args {
mode_t pmode;
};
-/** Compute flags for fstatat()/utimensat(). */
-static int at_flags(const struct args *args) {
- if (args->flags & NO_FOLLOW) {
- return AT_SYMLINK_NOFOLLOW;
- } else {
- return 0;
+/** Open (and maybe create) a single directory. */
+static int open_dir(const struct args *args, int dfd, const char *path) {
+ int ret = openat(dfd, path, O_SEARCH | O_DIRECTORY);
+
+ if (ret < 0 && errno == ENOENT && (args->flags & CREATE_PARENTS)) {
+ if (mkdirat(dfd, path, args->pmode) == 0 || errno == EEXIST) {
+ ret = openat(dfd, path, O_SEARCH | O_DIRECTORY);
+ }
}
+
+ return ret;
}
-/** Create any parent directories of the given path. */
-static int mkdirs(const char *path, mode_t mode) {
- char *copy = strdup(path);
- if (!copy) {
+/** Open (and maybe create) the parent directory of the path. */
+static int open_parent(const struct args *args, const char **path) {
+ size_t max = xbaseoff(*path);
+ if (max == 0) {
+ return AT_FDCWD;
+ }
+
+ char *dir = strndup(*path, max);
+ if (!dir) {
return -1;
}
- int ret = -1;
- char *cur = copy + strspn(copy, "/");
- while (true) {
- cur += strcspn(cur, "/");
+ // Optimistically try the whole path first
+ int dfd = open_dir(args, AT_FDCWD, dir);
+ if (dfd >= 0) {
+ goto done;
+ }
- char *next = cur + strspn(cur, "/");
- if (!*next) {
- ret = 0;
- break;
+ if (errno == ENOENT) {
+ if (!(args->flags & CREATE_PARENTS)) {
+ goto err;
}
+ } else if (!errno_is_like(ENAMETOOLONG)) {
+ goto err;
+ }
- *cur = '\0';
- if (mkdir(copy, mode) != 0 && errno != EEXIST) {
- break;
+ // Open the parents one-at-a-time
+ dfd = AT_FDCWD;
+ char *cur = dir;
+ while (*cur) {
+ char *next = cur;
+ next += strcspn(next, "/");
+ next += strspn(next, "/");
+
+ char c = *next;
+ *next = '\0';
+
+ int parent = dfd;
+ dfd = open_dir(args, parent, cur);
+ if (parent >= 0) {
+ close_quietly(parent);
}
- *cur = '/';
+ if (dfd < 0) {
+ goto err;
+ }
+
+ *next = c;
cur = next;
}
- free(copy);
- return ret;
+done:
+ *path += max;
+err:
+ free(dir);
+ return dfd;
+}
+
+/** Compute flags for fstatat()/utimensat(). */
+static int at_flags(const struct args *args) {
+ if (args->flags & NO_FOLLOW) {
+ return AT_SYMLINK_NOFOLLOW;
+ } else {
+ return 0;
+ }
}
/** Touch one path. */
static int xtouch(const struct args *args, const char *path) {
- int ret = utimensat(AT_FDCWD, path, args->times, at_flags(args));
+ int dfd = open_parent(args, &path);
+ if (dfd < 0 && dfd != (int)AT_FDCWD) {
+ return -1;
+ }
+
+ int ret = utimensat(dfd, path, args->times, at_flags(args));
if (ret == 0 || errno != ENOENT) {
- return ret;
+ goto done;
}
if (args->flags & NO_CREATE) {
- return 0;
- } else if (args->flags & CREATE_PARENTS) {
- if (mkdirs(path, args->pmode) != 0) {
- return -1;
- }
+ ret = 0;
+ goto done;
}
size_t len = strlen(path);
if (len > 0 && path[len - 1] == '/') {
- if (mkdir(path, args->dmode) != 0) {
- return -1;
+ if (mkdirat(dfd, path, args->dmode) == 0) {
+ ret = utimensat(dfd, path, args->times, at_flags(args));
}
-
- return utimensat(AT_FDCWD, path, args->times, at_flags(args));
} else {
- int fd = open(path, O_WRONLY | O_CREAT, args->fmode);
- if (fd < 0) {
- return -1;
- }
-
- if (futimens(fd, args->times) != 0) {
- close_quietly(fd);
- return -1;
+ int fd = openat(dfd, path, O_WRONLY | O_CREAT, args->fmode);
+ if (fd >= 0) {
+ if (futimens(fd, args->times) == 0) {
+ ret = xclose(fd);
+ } else {
+ close_quietly(fd);
+ }
}
+ }
- return xclose(fd);
+done:
+ if (dfd >= 0) {
+ close_quietly(dfd);
}
+ return ret;
}
int main(int argc, char *argv[]) {
+ tzset();
+
mode_t mask = umask(0);
struct args args = {
@@ -171,11 +217,8 @@ int main(int argc, char *argv[]) {
}
if (marg) {
- char *end;
- long mode = strtol(marg, &end, 8);
- // https://github.com/llvm/llvm-project/issues/64946
- sanitize_init(&end);
- if (*marg && !*end && mode >= 0 && mode < 01000) {
+ unsigned int mode;
+ if (xstrtoui(marg, NULL, 8, &mode) == 0 && mode < 01000) {
args.fmode = args.dmode = mode;
} else {
fprintf(stderr, "%s: Invalid mode '%s'\n", cmd, marg);
@@ -188,21 +231,21 @@ int main(int argc, char *argv[]) {
if (rarg) {
struct stat buf;
if (fstatat(AT_FDCWD, rarg, &buf, at_flags(&args)) != 0) {
- fprintf(stderr, "%s: '%s': %s\n", cmd, rarg, strerror(errno));
+ fprintf(stderr, "%s: '%s': %s\n", cmd, rarg, xstrerror(errno));
return EXIT_FAILURE;
}
- times[0] = buf.st_atim;
- times[1] = buf.st_mtim;
+ times[0] = ST_ATIM(buf);
+ times[1] = ST_MTIM(buf);
} else if (darg) {
if (xgetdate(darg, &times[0]) != 0) {
- fprintf(stderr, "%s: Parsing time '%s' failed: %s\n", cmd, darg, strerror(errno));
+ fprintf(stderr, "%s: Parsing time '%s' failed: %s\n", cmd, darg, xstrerror(errno));
return EXIT_FAILURE;
}
times[1] = times[0];
} else {
// Don't use UTIME_NOW, so that multiple paths all get the same timestamp
- if (xgettime(&times[0]) != 0) {
- perror("xgettime()");
+ if (clock_gettime(CLOCK_REALTIME, &times[0]) != 0) {
+ perror("clock_gettime()");
return EXIT_FAILURE;
}
times[1] = times[0];
@@ -228,7 +271,7 @@ int main(int argc, char *argv[]) {
for (; optind < argc; ++optind) {
const char *path = argv[optind];
if (xtouch(&args, path) != 0) {
- fprintf(stderr, "%s: '%s': %s\n", cmd, path, strerror(errno));
+ fprintf(stderr, "%s: '%s': %s\n", cmd, path, xstrerror(errno));
ret = EXIT_FAILURE;
}
}