From: maximilian attems Date: Mon, 4 Jul 2011 17:51:52 +0200 Subject: [PATCH] [klibc] Fix minimal mv to work across fs Forwarded: not-needed This is the use case in initramfs to keep data from /run to /root/run before calling switch_root(). copy_file() doesn't yet catch EINTR, but that seems less of an issue in initramfs. copy_file() and much logic out of copy() is from Ulrich Dangel. Thank you for the collaboration on this blocker for http://bugs.debian.org/627808 While we are it require move to have mv at least 2 arguments passed. Signed-off-by: Ulrich Dangel Signed-off-by: maximilian attems [bwh: This is no longer needed by initramfs-tools starting with version 0.121, so can be dropped after the stretch release] --- usr/utils/mv.c | 234 +++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 files changed, 216 insertions(+), 18 deletions(-) Index: klibc-2.0~rc2/usr/utils/mv.c =================================================================== --- klibc-2.0~rc2.orig/usr/utils/mv.c 2012-02-11 18:50:21.000000000 +0000 +++ klibc-2.0~rc2/usr/utils/mv.c 2012-02-11 19:04:54.000000000 +0000 @@ -1,3 +1,6 @@ +#include +#include +#include #include #include #include @@ -7,10 +10,204 @@ #include +#define BUFFER_SIZE 32768 + +/* copy_file - copy a file, used on different fs mv */ +static int copy_file(const char *src, const char *dest, mode_t mode) +{ + char buf[BUFFER_SIZE]; + int sfd, dfd; + ssize_t len; + + sfd = open(src, O_RDONLY); + if (sfd < 0) + return -1; + + dfd = open(dest, O_WRONLY | O_CREAT, mode); + if (dfd < 0) { + close(sfd); + return -1; + } + + while ((len = read(sfd, buf, sizeof(buf))) > 0) { + len = write(dfd, buf, len); + if (len < 0) { + close(sfd); + close(dfd); + return -1; + } + } + + close(sfd); + close(dfd); + return 0; +} + +/* copy - recursively copy directories */ +static int copy(char *src, const char *dest) +{ + int len; + struct stat sb, sf; + char target[PATH_MAX]; + char *p; + + p = strrchr(src, '/'); + if (p) { + p++; + /* trailing slashes case */ + if (strlen(p) == 0) { + len = strlen(src) - 1; + p = src; + while (0 < len && p[len] == '/') + p[len--] = '\0'; + p = strrchr(p, '/'); + p++; + } + } else { + p = src; + } + + memset(&sb, 0, sizeof(struct stat)); + /* might not exist yet */ + if (stat(dest, &sb) == 0) { + if (S_ISDIR(sb.st_mode)) { + len = snprintf(target, PATH_MAX, "%s/%s", dest, p); + if (len >= PATH_MAX) + return -1; + } else { + len = snprintf(target, PATH_MAX, "%s/%s", dest, src); + if (len >= PATH_MAX) + return -1; + } + } else { + len = snprintf(target, PATH_MAX, "%s", dest); + if (len >= PATH_MAX) + return -1; + } + + if (rename(src, target) == 0) { + return 0; + } else { + if (errno != EXDEV) + return -1; + } + + + /* cross fs copy */ + memset(&sf, 0, sizeof(struct stat)); + if (stat(src, &sf) < 0) + return -1; + if (!S_ISDIR(sf.st_mode)) { + len = copy_file(src, target, sf.st_mode); + if (len == 0) + return 0; + else + return -1; + } + + DIR *dir; + struct dirent *d; + char path[PATH_MAX]; + + if (mkdir(target, sf.st_mode) < 0) + return -1; + + dir = opendir(src); + if (!dir) { + /* EACCES means we can't read it. + * Might be empty and removable. */ + if (errno != EACCES) + return -1; + } + while ((d = readdir(dir))) { + + /* Skip . and .. */ + if (d->d_name[0] == '.' && (d->d_name[1] == '\0' + || (d->d_name[1] == '.' && d->d_name[2] == '\0'))) + continue; + + /* skip to long path */ + if (strlen(src) + 1 + strlen(d->d_name) >= PATH_MAX - 1) + continue; + + snprintf(path, sizeof path, "%s/%s", src, d->d_name); + if (len >= sizeof path) + return -1; + + memset(&sf, 0, sizeof(struct stat)); + if (stat(path, &sf) < 0) { + closedir(dir); + return -1; + } + + /* recursively copy files and directories */ + if (copy(path, target) < 0) + return -1; + } + closedir(dir); + return 0; +} + +/* nuke - rm a file or directory recursively */ +static int nuke(const char *src) +{ + struct stat sb; + DIR *dir; + struct dirent *d; + char path[PATH_MAX]; + int len; + + memset(&sb, 0, sizeof(struct stat)); + /* gone, no work */ + if (stat(src, &sb) < 0) + return 0; + + if (!S_ISDIR(sb.st_mode)) { + if (unlink(src) == 0) + return 0; + else + return 1; + } + + dir = opendir(src); + if (!dir) { + /* EACCES means we can't read it. + * Might be empty and removable. */ + if (errno != EACCES) + return -1; + } + while ((d = readdir(dir))) { + + /* Skip . and .. */ + if (d->d_name[0] == '.' && (d->d_name[1] == '\0' + || (d->d_name[1] == '.' && d->d_name[2] == '\0'))) + continue; + + /* skip to long path */ + if (strlen(src) + 1 + strlen(d->d_name) >= PATH_MAX - 1) + continue; + + len = snprintf(path, sizeof path, "%s/%s", src, d->d_name); + if (len >= sizeof path) + return -1; + + memset(&sb, 0, sizeof(struct stat)); + if (stat(path, &sb) < 0) { + closedir(dir); + return -1; + } + + if (nuke(path) < 0) + return -1; + } + closedir(dir); + rmdir(src); + return 0; +} + int main(int argc, char *argv[]) { int c, f; - char *p; struct stat sb; f = 0; @@ -32,11 +229,13 @@ } while (1); - if (optind == argc) { + /* not enough arguments */ + if (argc - optind < 2) { fprintf(stderr, "Usage: %s [-f] source dest\n", argv[0]); return 1; } + /* check on many archs if destination is a directory to mv in */ memset(&sb, 0, sizeof(struct stat)); if (stat(argv[argc - 1], &sb) < 0 && argc - optind > 2) { if (!(S_ISDIR(sb.st_mode))) { @@ -47,23 +246,22 @@ } } - for (c = optind; c < argc - 1; c++) { - char target[PATH_MAX]; - - p = strrchr(argv[c], '/'); - p++; - - if (S_ISDIR(sb.st_mode)) - snprintf(target, PATH_MAX, "%s/%s", argv[argc - 1], p); - else - snprintf(target, PATH_MAX, "%s", argv[argc - 1]); - - if (f) - unlink(target); - - if (rename(argv[c], target) == -1) - perror(target); - } + /* remove destination */ + if (f) + nuke(argv[argc - 1]); + + /* the mv action */ + for (c = optind; c < argc - 1; c++) + if (copy(argv[c], argv[argc - 1]) < 0) { + perror("Could not copy file"); + return -1; + } + /* Only rm after sucessfull rename */ + for (c = optind; c < argc - 1; c++) + if (nuke(argv[c]) < 0) { + perror("Could not rm file"); + return -1; + } return 0; }