Skip to content

Instantly share code, notes, and snippets.

@sophec
Last active February 18, 2020 23:52
Show Gist options
  • Save sophec/a5156ee84890a155e6af23a24245abca to your computer and use it in GitHub Desktop.
Save sophec/a5156ee84890a155e6af23a24245abca to your computer and use it in GitHub Desktop.
recursive file copying function in C
// note there is no check for excessive recursion so if something has already been copied it will be tried again
int cpfile(const char* src, const char* dst) {
char* b = basename(src);
if (b != NULL && (0 == strcmp(b, ".") || 0 == strcmp(b, ".."))) {
return 0;
}
struct stat srcstat, dststat;
bool dstexist = false;
int s = 0;
if (lstat(src, &srcstat) < 0) {
// couldn't stat source
return -1;
}
if (lstat(dst, &dststat) < 0) {
// couldn't stat target
if (errno != ENOENT) {
return -1;
}
} else {
if (srcstat.st_dev == dststat.st_dev && srcstat.st_ino == dststat.st_ino) {
// same file
return -1;
}
dstexist = true;
}
if (dstexist) {
return -1;
}
if (S_ISDIR(srcstat.st_mode)) {
DIR* d;
const char* tp;
struct dirent *de;
mode_t smask = umask(0);
mode_t mode = srcstat.st_mode & ~smask;
mode |= S_IRWXU;
if (mkdir(dst, mode) < 0) {
umask(smask);
return -1;
}
umask(smask);
d = opendir(src);
if (d == NULL) {
s = -1;
goto preserve;
}
while ((de = readdir(d)) != NULL) {
char *ns = malloc(PATH_MAX);
if (ns == NULL) {
s = -1;
}
char *nd = malloc(PATH_MAX);
if (nd == NULL) {
free(ns);
s = -1;
}
snprintf(ns, PATH_MAX, "%s/%s", src, de->d_name);
snprintf(nd, PATH_MAX, "%s/%s", dst, de->d_name);
if (cpfile(ns, nd) != 0) {
s = -1;
}
free(ns);
free(nd);
}
closedir(d);
chmod(dst, srcstat.st_mode & ~smask);
goto preserve;
}
if (S_ISREG(srcstat.st_mode)) {
int sfd, dfd;
mode_t nmode;
if (S_ISLNK(srcstat.st_mode)) {
goto notreg;
}
// TODO it may make more sense to use openat + an fd for the directory
sfd = open(src, O_RDONLY);
if (sfd == -1) {
return -1;
}
nmode = srcstat.st_mode;
if (!S_ISREG(srcstat.st_mode)) {
nmode = 0666;
}
dfd = open(dst, O_WRONLY|O_CREAT|O_EXCL, nmode);
if (dfd == -1) {
close(sfd);
return -1;
}
// copy the file
char cbuf[4096] = {0};
while (1) {
ssize_t r, w;
r = read(sfd, cbuf, 4096);
if (!r) {
break;
}
if (r < 0) {
s = -1;
break;
}
w = write(dfd, cbuf, r);
if (w < r) {
s = -1;
break;
}
}
if (close(dfd) < 0) {
return -1;
}
close(sfd);
if (!S_ISREG(srcstat.st_mode)) {
return s;
}
goto preserve;
}
notreg:
// source is not a regular file (it's a symlink or special)
if (S_ISLNK(srcstat.st_mode)) {
char lbuf[PATH_MAX+1] = {0};
ssize_t ls = readlink(src, lbuf, PATH_MAX);
if (ls == -1) {
return -1;
}
lbuf[ls] = '\0';
int r = symlink(lbuf, dst);
if (r != 0) {
return -1;
}
// can't preserve stuff for symlinks
return 0;
} else if (S_ISBLK(srcstat.st_mode) || S_ISCHR(srcstat.st_mode)
|| S_ISSOCK(srcstat.st_mode) || S_ISFIFO(srcstat.st_mode)) {
if (mknod(dst, srcstat.st_mode, srcstat.st_rdev) < 0) {
return -1;
}
} else {
return -1;
}
preserve:;
// preserve mode, owner, attributes, etc. here
struct timeval t[2];
t[1].tv_sec = t[0].tv_sec = srcstat.st_mtime;
t[1].tv_usec = t[0].tv_usec = 0;
// we will fail silently if any of these don't work
utimes(dst, t);
chown(dst, srcstat.st_uid, srcstat.st_gid);
chmod(dst, srcstat.st_mode);
return s;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment