Skip to content

Instantly share code, notes, and snippets.

@iamahuman
Last active June 25, 2019 18:21
Show Gist options
  • Save iamahuman/506dac6d760fdc9ea69c7b46b6b9dc27 to your computer and use it in GitHub Desktop.
Save iamahuman/506dac6d760fdc9ea69c7b46b6b9dc27 to your computer and use it in GitHub Desktop.
(*nix) Merge two dirs w/o copying
#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)
{
if (errno != ENOENT) break;
res = 0;
}
}
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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment