Skip to content

Instantly share code, notes, and snippets.

@iamahuman
Last active June 25, 2019 18:21

Revisions

  1. iamahuman revised this gist Jun 25, 2019. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion mvdir.c
    Original file line number Diff line number Diff line change
    @@ -70,7 +70,8 @@ static int movex(int olddirfd, const char *oldpath, int newdirfd, const char *ne
    res = movex(oldfd, ent->d_name, newfd, ent->d_name);
    if (res < 0)
    {
    break;
    if (errno != ENOENT) break;
    res = 0;
    }
    }
    err = errno;
  2. iamahuman created this gist Jun 25, 2019.
    104 changes: 104 additions & 0 deletions mvdir.c
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,104 @@
    #define _GNU_SOURCE
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    #include <sys/types.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <dirent.h>

    #ifdef O_PATH
    #define LEAST_OPEN_PRIV O_PATH /* Linux >= 2.6.39 */
    #elif defined(O_SEARCH) && !defined(O_EXEC)
    #define LEAST_OPEN_PRIV O_SEARCH /* POSIX.1-2008 */
    #else
    #define LEAST_OPEN_PRIV O_EXEC /* some FreeBSD filesystem drivers allow VEXEC for directory search */
    #endif

    static int movex(int olddirfd, const char *oldpath, int newdirfd, const char *newpath)
    {
    int res, err, err2, oldfd, newfd;

    while ((res = renameat(olddirfd, oldpath, newdirfd, newpath)) < 0 &&
    ((err = errno) == ENOTEMPTY || err == EEXIST))
    {
    DIR *dir;
    struct dirent *ent;

    newfd = openat(newdirfd, newpath, LEAST_OPEN_PRIV | O_CLOEXEC | O_NOFOLLOW | O_DIRECTORY);
    if (newfd < 0)
    {
    err2 = errno;
    if (err2 == ENOENT || err2 == ENOTDIR) continue;
    errno = err2;
    break;
    }

    oldfd = openat(olddirfd, oldpath, O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_DIRECTORY);
    if (oldfd < 0)
    {
    err2 = errno;
    close(newfd);
    if (err2 == ENOTDIR) continue;
    errno = err2;
    break;
    }

    dir = fdopendir(oldfd);
    if (dir == NULL)
    {
    err2 = errno;
    close(oldfd);
    close(newfd);
    errno = err2;
    break;
    }

    res = 0;
    while ((ent = readdir(dir)) != NULL)
    {
    const char *n = ent->d_name;
    if (n[0] == '.')
    {
    char ch1 = n[1];
    if (ch1 == '\0' || (ch1 == '.' && n[2] == '\0'))
    {
    continue;
    }
    }
    res = movex(oldfd, ent->d_name, newfd, ent->d_name);
    if (res < 0)
    {
    break;
    }
    }
    err = errno;
    closedir(dir);
    close(newfd);

    if (res >= 0 && unlinkat(olddirfd, oldpath, AT_REMOVEDIR) < 0)
    {
    if (errno == ENOENT || errno == ENOTEMPTY || errno == ENOTDIR) continue;
    fprintf(stderr, "<%d>/%s: unlinkat(%d, AT_REMOVEDIR): %s\n", olddirfd, oldpath, oldfd, strerror(errno));
    }
    errno = err;
    break;
    }

    if (res != 0)
    {
    fprintf(stderr, "move <%d>/%s to <%d>/%s failed: %s\n", olddirfd, oldpath, newdirfd, newpath, strerror(errno));
    }
    return res;
    }

    int main(int argc, char **argv)
    {
    if (argc != 3)
    {
    fprintf(stderr, "usage: %s <old> <new>\n", argv[0]);
    return EXIT_FAILURE;
    }
    return movex(AT_FDCWD, argv[1], AT_FDCWD, argv[2]) < 0 ? EXIT_FAILURE : 0;
    }