Skip to content

Commit dc6e429

Browse files
committed
Support moving between two different filesystems
1 parent ca2bec1 commit dc6e429

1 file changed

Lines changed: 92 additions & 18 deletions

File tree

crew-mvdir.c

Lines changed: 92 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,52 @@
3232
#include <stdio.h>
3333
#include <stdlib.h>
3434
#include <stdbool.h>
35+
#include <fcntl.h>
3536
#include <unistd.h>
3637
#include <errno.h>
3738
#include <limits.h>
3839
#include <string.h>
3940
#include <ftw.h>
41+
#include <sys/sendfile.h>
4042
#include <sys/stat.h>
4143

42-
bool verbose = false, no_clobber = false;
44+
bool same_fs = true, verbose = false, no_clobber = false;
4345
char src[PATH_MAX], dst[PATH_MAX];
4446

45-
int move_file(const char *src_path, const struct stat *sb, int flag, struct FTW *ftwbuf) {
46-
char dst_path[PATH_MAX] = {0};
47+
void copy_and_delete_file(const struct stat *src_info, const char *src_path, const char *dst_path) {
48+
// copy_and_delete_file(): copy a file and delete it after copying, used as a fallback when rename() does not work
49+
// (i.e source and destination are not in the same filesystem)
50+
int src_fd = open(src_path, O_RDONLY),
51+
dst_fd = open(dst_path, O_CREAT | O_WRONLY, src_info->st_mode);
52+
53+
if (src_fd == -1) {
54+
fprintf(stderr, "%s: open() failed: %s\n", src_path, strerror(errno));
55+
exit(errno);
56+
} else if (dst_fd == -1) {
57+
fprintf(stderr, "%s: open() failed: %s\n", dst_path, strerror(errno));
58+
exit(errno);
59+
}
60+
61+
// copy contents
62+
if (sendfile(dst_fd, src_fd, (off_t*) 0, src_info->st_size) == -1) {
63+
fprintf(stderr, "%s: sendfile() failed: %s\n", dst_path, strerror(errno));
64+
exit(errno);
65+
}
66+
67+
close(src_fd);
68+
close(dst_fd);
69+
70+
// remove source file after copying
71+
if (remove(src_path) == -1) {
72+
fprintf(stderr, "%s: failed to remove file: %s\n", src_path, strerror(errno));
73+
exit(errno);
74+
}
75+
}
76+
77+
int move_file(const char *src_path, const struct stat *src_info, int flag, struct FTW *ftwbuf) {
78+
char dst_path[PATH_MAX];
79+
4780
strcpy(dst_path, dst);
48-
strcat(dst_path, "/");
4981
strcat(dst_path, src_path + strlen(src));
5082

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

6799
// move (rename) file to dest
68-
if ( !(access(dst_path, F_OK) == 0 || rename(src_path, dst_path) == 0) ) {
69-
fprintf(stderr, "%s: rename() failed: %s\n", src_path, strerror(errno));
70-
exit(errno);
100+
if (same_fs) {
101+
// (when source and destination are in the same filesystem)
102+
// file/symlink: move file to dest, override files with same filename in dest (mode/owner remain unchanged)
103+
if (rename(src_path, dst_path) == -1) {
104+
if (errno == EXDEV) {
105+
// fallback to copying-deleting if source and destination are not in the same filesystem
106+
same_fs = false;
107+
if (verbose) fprintf(stderr, "Warning: destination is not in the same filesystem, will fallback to sendfile() instead.\n");
108+
return move_file(src_path, src_info, flag, ftwbuf);
109+
}
110+
111+
fprintf(stderr, "%s: rename() failed: %s\n", src_path, strerror(errno));
112+
exit(errno);
113+
}
114+
} else {
115+
// (when source and destination are not in the same filesystem)
116+
if (flag == FTW_F) {
117+
// file: copy source file to destination and delete it (mode will be transferred)
118+
copy_and_delete_file(src_info, src_path, dst_path);
119+
} else {
120+
// symlink: create an identical symlink and delete the source (mode will be transferred)
121+
char target[PATH_MAX];
122+
123+
ssize_t target_len = readlink(src_path, target, PATH_MAX - 1);
124+
target[target_len] = 0;
125+
126+
if (symlink(target, dst_path) == -1) {
127+
fprintf(stderr, "%s: symlink() failed: %s\n", dst_path, strerror(errno));
128+
exit(errno);
129+
}
130+
131+
// remove source after copying
132+
if (remove(src_path) == -1) {
133+
fprintf(stderr, "%s: failed to remove symlink: %s\n", src_path, strerror(errno));
134+
exit(errno);
135+
}
136+
}
71137
}
72138

73139
break;
74140
case FTW_D:
75-
// directory
76-
// create an identical directory in dest (with same permission) if the directory does not exist in dest
77-
if (access(dst_path, F_OK) != 0) {
78-
struct stat path_stat;
79-
stat(src_path, &path_stat);
80-
mode_t dir_mode = path_stat.st_mode;
141+
// directory: create an identical directory in destination if not exist (mode will be transferred)
142+
if (access(dst_path, F_OK) == -1) {
143+
mode_t dir_mode = src_info->st_mode;
81144

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

189+
struct stat src_info, dst_info;
190+
stat(src, &src_info);
191+
stat(dst, &dst_info);
192+
193+
// trailing slashes in path are required in order to make move_file() works
194+
int src_len = strlen(src),
195+
dst_len = strlen(dst);
196+
197+
if (src[src_len - 1] != '/') src[src_len] = '/';
198+
if (dst[dst_len - 1] != '/') dst[dst_len] = '/';
199+
126200
// call move_file() with files in src recursively
127-
if (nftw(src, move_file, 100, FTW_PHYS) != 0) {
128-
fprintf(stderr, "%s: %s\n", src, strerror(errno));
201+
if (nftw(src, move_file, 100, FTW_PHYS | FTW_MOUNT) == -1) {
202+
fprintf(stderr, "%s: nftw() failed: %s\n", src, strerror(errno));
129203
exit(errno);
130204
}
131205

0 commit comments

Comments
 (0)