Skip to content

Commit

Permalink
Support moving between two different filesystems
Browse files Browse the repository at this point in the history
  • Loading branch information
supechicken committed Mar 11, 2023
1 parent ca2bec1 commit dc6e429
Showing 1 changed file with 92 additions and 18 deletions.
110 changes: 92 additions & 18 deletions crew-mvdir.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,52 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#include <ftw.h>
#include <sys/sendfile.h>
#include <sys/stat.h>

bool verbose = false, no_clobber = false;
bool same_fs = true, verbose = false, no_clobber = false;
char src[PATH_MAX], dst[PATH_MAX];

int move_file(const char *src_path, const struct stat *sb, int flag, struct FTW *ftwbuf) {
char dst_path[PATH_MAX] = {0};
void copy_and_delete_file(const struct stat *src_info, const char *src_path, const char *dst_path) {
// copy_and_delete_file(): copy a file and delete it after copying, used as a fallback when rename() does not work
// (i.e source and destination are not in the same filesystem)
int src_fd = open(src_path, O_RDONLY),
dst_fd = open(dst_path, O_CREAT | O_WRONLY, src_info->st_mode);

if (src_fd == -1) {
fprintf(stderr, "%s: open() failed: %s\n", src_path, strerror(errno));
exit(errno);
} else if (dst_fd == -1) {
fprintf(stderr, "%s: open() failed: %s\n", dst_path, strerror(errno));
exit(errno);
}

// copy contents
if (sendfile(dst_fd, src_fd, (off_t*) 0, src_info->st_size) == -1) {
fprintf(stderr, "%s: sendfile() failed: %s\n", dst_path, strerror(errno));
exit(errno);
}

close(src_fd);
close(dst_fd);

// remove source file after copying
if (remove(src_path) == -1) {
fprintf(stderr, "%s: failed to remove file: %s\n", src_path, strerror(errno));
exit(errno);
}
}

int move_file(const char *src_path, const struct stat *src_info, int flag, struct FTW *ftwbuf) {
char dst_path[PATH_MAX];

strcpy(dst_path, dst);
strcat(dst_path, "/");
strcat(dst_path, src_path + strlen(src));

if (strcmp(src_path, ".") == 0) return 0;
Expand All @@ -54,33 +86,64 @@ int move_file(const char *src_path, const struct stat *sb, int flag, struct FTW
switch (flag) {
case FTW_F:
case FTW_SL:
// file/symlink: Move file to dest, override files with same filename in dest (mode/owner remain unchanged)
case FTW_SLN:
if (access(dst_path, F_OK) == 0) {
// do not touch existing files if -n specified
if ( !(no_clobber || remove(dst_path) == 0) ) {
if (!(no_clobber || remove(dst_path) == 0)) {
// remove file with same name in dest (if exist)
fprintf(stderr, "%s: failed to remove file: %s\n", dst_path, strerror(errno));
exit(errno);
}
}

// move (rename) file to dest
if ( !(access(dst_path, F_OK) == 0 || rename(src_path, dst_path) == 0) ) {
fprintf(stderr, "%s: rename() failed: %s\n", src_path, strerror(errno));
exit(errno);
if (same_fs) {
// (when source and destination are in the same filesystem)
// file/symlink: move file to dest, override files with same filename in dest (mode/owner remain unchanged)
if (rename(src_path, dst_path) == -1) {
if (errno == EXDEV) {
// fallback to copying-deleting if source and destination are not in the same filesystem
same_fs = false;
if (verbose) fprintf(stderr, "Warning: destination is not in the same filesystem, will fallback to sendfile() instead.\n");
return move_file(src_path, src_info, flag, ftwbuf);
}

fprintf(stderr, "%s: rename() failed: %s\n", src_path, strerror(errno));
exit(errno);
}
} else {
// (when source and destination are not in the same filesystem)
if (flag == FTW_F) {
// file: copy source file to destination and delete it (mode will be transferred)
copy_and_delete_file(src_info, src_path, dst_path);
} else {
// symlink: create an identical symlink and delete the source (mode will be transferred)
char target[PATH_MAX];

ssize_t target_len = readlink(src_path, target, PATH_MAX - 1);
target[target_len] = 0;

if (symlink(target, dst_path) == -1) {
fprintf(stderr, "%s: symlink() failed: %s\n", dst_path, strerror(errno));
exit(errno);
}

// remove source after copying
if (remove(src_path) == -1) {
fprintf(stderr, "%s: failed to remove symlink: %s\n", src_path, strerror(errno));
exit(errno);
}
}
}

break;
case FTW_D:
// directory
// create an identical directory in dest (with same permission) if the directory does not exist in dest
if (access(dst_path, F_OK) != 0) {
struct stat path_stat;
stat(src_path, &path_stat);
mode_t dir_mode = path_stat.st_mode;
// directory: create an identical directory in destination if not exist (mode will be transferred)
if (access(dst_path, F_OK) == -1) {
mode_t dir_mode = src_info->st_mode;

if (verbose) fprintf(stderr, "Creating directory %s\n", dst_path);
if (mkdir(dst_path, dir_mode) != 0) {
if (mkdir(dst_path, dir_mode) == -1) {
fprintf(stderr, "%s: mkdir() failed: %s\n", src_path, strerror(errno));
exit(errno);
}
Expand Down Expand Up @@ -123,9 +186,20 @@ int main(int argc, char** argv) {
strcpy(src, argv[optind]);
strcpy(dst, argv[optind + 1]);

struct stat src_info, dst_info;
stat(src, &src_info);
stat(dst, &dst_info);

// trailing slashes in path are required in order to make move_file() works
int src_len = strlen(src),
dst_len = strlen(dst);

if (src[src_len - 1] != '/') src[src_len] = '/';
if (dst[dst_len - 1] != '/') dst[dst_len] = '/';

// call move_file() with files in src recursively
if (nftw(src, move_file, 100, FTW_PHYS) != 0) {
fprintf(stderr, "%s: %s\n", src, strerror(errno));
if (nftw(src, move_file, 100, FTW_PHYS | FTW_MOUNT) == -1) {
fprintf(stderr, "%s: nftw() failed: %s\n", src, strerror(errno));
exit(errno);
}

Expand Down

0 comments on commit dc6e429

Please sign in to comment.