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;
4345char 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