summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--tests/xtouch.c129
1 files changed, 84 insertions, 45 deletions
diff --git a/tests/xtouch.c b/tests/xtouch.c
index a4c4d40..260a3a3 100644
--- a/tests/xtouch.c
+++ b/tests/xtouch.c
@@ -37,89 +37,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) {
- int ret = -1;
- char *dir = xdirname(path);
+/** 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) {
- goto err;
+ return -1;
}
- if (strcmp(dir, ".") == 0) {
+ // Optimistically try the whole path first
+ int dfd = open_dir(args, AT_FDCWD, dir);
+ if (dfd >= 0) {
goto done;
}
- // Optimistically try the immediate parent first
- if (mkdir(dir, mode) == 0 || errno == EEXIST) {
- goto done;
+ switch (errno) {
+ case ENAMETOOLONG:
+ break;
+ case ENOENT:
+ if (args->flags & CREATE_PARENTS) {
+ break;
+ } else {
+ goto err;
+ }
+ default:
+ goto err;
}
- // Create the parents one-at-a-time
- char *cur = dir + strspn(dir, "/");
+ // Open the parents one-at-a-time
+ dfd = AT_FDCWD;
+ char *cur = dir;
while (*cur) {
- cur += strcspn(cur, "/");
- char *next = cur + strspn(cur, "/");
+ char *next = cur;
+ next += strcspn(next, "/");
+ next += strspn(next, "/");
+
+ char c = *next;
+ *next = '\0';
- char c = *cur;
- *cur = '\0';
- if (mkdir(dir, mode) != 0 && errno != EEXIST) {
+ int parent = dfd;
+ dfd = open_dir(args, parent, cur);
+ if (parent >= 0) {
+ close_quietly(parent);
+ }
+ if (dfd < 0) {
goto err;
}
- *cur = c;
+
+ *next = c;
cur = next;
}
done:
- ret = 0;
+ *path += max;
err:
free(dir);
- return ret;
+ 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 != 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[]) {