]> Sergey Matveev's repositories - bfs.git/commitdiff
tests/xtouch: Recover from ENAMETOOLONG
authorTavian Barnes <tavianator@tavianator.com>
Fri, 20 Oct 2023 15:18:15 +0000 (11:18 -0400)
committerTavian Barnes <tavianator@tavianator.com>
Fri, 20 Oct 2023 15:29:41 +0000 (11:29 -0400)
tests/xtouch.c

index a4c4d40e1280cda6acabea826fcb797b535aaa42..260a3a3761fd26dbba27efab71e121be2a23195d 100644 (file)
@@ -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[]) {