Start 1.33.0 development cycle
[oweals/busybox.git] / libbb / copy_file.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Mini copy_file implementation for busybox
4  *
5  * Copyright (C) 2001 by Matt Kraai <kraai@alumni.carnegiemellon.edu>
6  * SELinux support by Yuichi Nakamura <ynakam@hitachisoft.jp>
7  *
8  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
9  */
10 #include "libbb.h"
11
12 // FEATURE_NON_POSIX_CP:
13 //
14 // POSIX: if exists and -i, ask (w/o -i assume yes).
15 // Then open w/o EXCL (yes, not unlink!).
16 // If open still fails and -f, try unlink, then try open again.
17 // Result: a mess:
18 // If dest is a (sym)link, we overwrite link destination!
19 // (or fail, if it points to dir/nonexistent location/etc).
20 // This is strange, but POSIX-correct.
21 // coreutils cp has --remove-destination to override this...
22
23 /* Called if open of destination, link creation etc fails.
24  * errno must be set to relevant value ("why we cannot create dest?")
25  * to give reasonable error message */
26 static int ask_and_unlink(const char *dest, int flags)
27 {
28         int e = errno;
29
30 #if !ENABLE_FEATURE_NON_POSIX_CP
31         if (!(flags & (FILEUTILS_FORCE|FILEUTILS_INTERACTIVE))) {
32                 /* Either it exists, or the *path* doesnt exist */
33                 bb_perror_msg("can't create '%s'", dest);
34                 return -1;
35         }
36 #endif
37         // else: act as if -f is always in effect.
38         // We don't want "can't create" msg, we want unlink to be done
39         // (silently unless -i). Why? POSIX cp usually succeeds with
40         // O_TRUNC open of existing file, and user is left ignorantly happy.
41         // With above block unconditionally enabled, non-POSIX cp
42         // will complain a lot more than POSIX one.
43
44         /* TODO: maybe we should do it only if ctty is present? */
45         if (flags & FILEUTILS_INTERACTIVE) {
46                 // We would not do POSIX insanity. -i asks,
47                 // then _unlinks_ the offender. Presto.
48                 // (No "opening without O_EXCL", no "unlink only if -f")
49                 // Or else we will end up having 3 open()s!
50                 fprintf(stderr, "%s: overwrite '%s'? ", applet_name, dest);
51                 if (!bb_ask_y_confirmation())
52                         return 0; /* not allowed to overwrite */
53         }
54         if (unlink(dest) < 0) {
55 #if ENABLE_FEATURE_VERBOSE_CP_MESSAGE
56                 if (e == errno && e == ENOENT) {
57                         /* e == ENOTDIR is similar: path has non-dir component,
58                          * but in this case we don't even reach copy_file() */
59                         bb_error_msg("can't create '%s': Path does not exist", dest);
60                         return -1; /* error */
61                 }
62 #endif
63                 errno = e; /* do not use errno from unlink */
64                 bb_perror_msg("can't create '%s'", dest);
65                 return -1; /* error */
66         }
67 #if ENABLE_FEATURE_CP_LONG_OPTIONS
68         if (flags & FILEUTILS_RMDEST)
69                 if (flags & FILEUTILS_VERBOSE)
70                         printf("removed '%s'\n", dest);
71 #endif
72         return 1; /* ok (to try again) */
73 }
74
75 /* Return:
76  * -1 error, copy not made
77  *  0 copy is made or user answered "no" in interactive mode
78  *    (failures to preserve mode/owner/times are not reported in exit code)
79  */
80 int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
81 {
82         /* This is a recursive function, try to minimize stack usage */
83         /* NB: each struct stat is ~100 bytes */
84         struct stat source_stat;
85         struct stat dest_stat;
86         smallint retval = 0;
87         smallint dest_exists = 0;
88         smallint ovr;
89
90 /* Inverse of cp -d ("cp without -d") */
91 #define FLAGS_DEREF (flags & (FILEUTILS_DEREFERENCE + FILEUTILS_DEREFERENCE_L0))
92
93         if ((FLAGS_DEREF ? stat : lstat)(source, &source_stat) < 0) {
94                 /* This may be a dangling symlink.
95                  * Making [sym]links to dangling symlinks works, so... */
96                 if (flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK))
97                         goto make_links;
98                 bb_perror_msg("can't stat '%s'", source);
99                 return -1;
100         }
101
102         if (lstat(dest, &dest_stat) < 0) {
103                 if (errno != ENOENT) {
104                         bb_perror_msg("can't stat '%s'", dest);
105                         return -1;
106                 }
107         } else {
108                 if (source_stat.st_dev == dest_stat.st_dev
109                  && source_stat.st_ino == dest_stat.st_ino
110                 ) {
111                         bb_error_msg("'%s' and '%s' are the same file", source, dest);
112                         return -1;
113                 }
114                 dest_exists = 1;
115         }
116
117 #if ENABLE_SELINUX
118         if ((flags & FILEUTILS_PRESERVE_SECURITY_CONTEXT) && is_selinux_enabled() > 0) {
119                 security_context_t con;
120                 if (lgetfilecon(source, &con) >= 0) {
121                         if (setfscreatecon(con) < 0) {
122                                 bb_perror_msg("can't set setfscreatecon %s", con);
123                                 freecon(con);
124                                 return -1;
125                         }
126                 } else if (errno == ENOTSUP || errno == ENODATA) {
127                         setfscreatecon_or_die(NULL);
128                 } else {
129                         bb_perror_msg("can't lgetfilecon %s", source);
130                         return -1;
131                 }
132         }
133 #endif
134
135         if (S_ISDIR(source_stat.st_mode)) {
136                 DIR *dp;
137                 const char *tp;
138                 struct dirent *d;
139                 mode_t saved_umask = 0;
140
141                 if (!(flags & FILEUTILS_RECUR)) {
142                         bb_error_msg("omitting directory '%s'", source);
143                         return -1;
144                 }
145
146                 /* Did we ever create source ourself before? */
147                 tp = is_in_ino_dev_hashtable(&source_stat);
148                 if (tp) {
149                         /* We did! it's a recursion! man the lifeboats... */
150                         bb_error_msg("recursion detected, omitting directory '%s'",
151                                         source);
152                         return -1;
153                 }
154
155                 if (dest_exists) {
156                         if (!S_ISDIR(dest_stat.st_mode)) {
157                                 bb_error_msg("target '%s' is not a directory", dest);
158                                 return -1;
159                         }
160                         /* race here: user can substitute a symlink between
161                          * this check and actual creation of files inside dest */
162                 } else {
163                         /* Create DEST */
164                         mode_t mode;
165                         saved_umask = umask(0);
166
167                         mode = source_stat.st_mode;
168                         if (!(flags & FILEUTILS_PRESERVE_STATUS))
169                                 mode = source_stat.st_mode & ~saved_umask;
170                         /* Allow owner to access new dir (at least for now) */
171                         mode |= S_IRWXU;
172                         if (mkdir(dest, mode) < 0) {
173                                 umask(saved_umask);
174                                 bb_perror_msg("can't create directory '%s'", dest);
175                                 return -1;
176                         }
177                         umask(saved_umask);
178                         /* need stat info for add_to_ino_dev_hashtable */
179                         if (lstat(dest, &dest_stat) < 0) {
180                                 bb_perror_msg("can't stat '%s'", dest);
181                                 return -1;
182                         }
183                 }
184                 /* remember (dev,inode) of each created dir.
185                  * NULL: name is not remembered */
186                 add_to_ino_dev_hashtable(&dest_stat, NULL);
187
188                 /* Recursively copy files in SOURCE */
189                 dp = opendir(source);
190                 if (dp == NULL) {
191                         retval = -1;
192                         goto preserve_mode_ugid_time;
193                 }
194
195                 while ((d = readdir(dp)) != NULL) {
196                         char *new_source, *new_dest;
197
198                         new_source = concat_subpath_file(source, d->d_name);
199                         if (new_source == NULL)
200                                 continue;
201                         new_dest = concat_path_file(dest, d->d_name);
202                         if (copy_file(new_source, new_dest, flags & ~FILEUTILS_DEREFERENCE_L0) < 0)
203                                 retval = -1;
204                         free(new_source);
205                         free(new_dest);
206                 }
207                 closedir(dp);
208
209                 if (!dest_exists
210                  && chmod(dest, source_stat.st_mode & ~saved_umask) < 0
211                 ) {
212                         bb_perror_msg("can't preserve %s of '%s'", "permissions", dest);
213                         /* retval = -1; - WRONG! copy *WAS* made */
214                 }
215                 goto preserve_mode_ugid_time;
216         }
217
218         if (dest_exists) {
219                 if (flags & FILEUTILS_UPDATE) {
220                         if (source_stat.st_mtime <= dest_stat.st_mtime) {
221                                 return 0; /* source file must be newer */
222                         }
223                 }
224 #if ENABLE_FEATURE_CP_LONG_OPTIONS
225                 if (flags & FILEUTILS_RMDEST) {
226                         ovr = ask_and_unlink(dest, flags);
227                         if (ovr <= 0)
228                                 return ovr;
229                         dest_exists = 0;
230                 }
231 #endif
232         }
233
234         if (flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) {
235                 int (*lf)(const char *oldpath, const char *newpath);
236  make_links:
237                 /* Hmm... maybe
238                  * if (DEREF && MAKE_SOFTLINK) source = realpath(source) ?
239                  * (but realpath returns NULL on dangling symlinks...) */
240                 lf = (flags & FILEUTILS_MAKE_SOFTLINK) ? symlink : link;
241                 if (lf(source, dest) < 0) {
242                         ovr = ask_and_unlink(dest, flags);
243                         if (ovr <= 0)
244                                 return ovr;
245                         if (lf(source, dest) < 0) {
246                                 bb_perror_msg("can't create link '%s'", dest);
247                                 return -1;
248                         }
249                 }
250                 /* _Not_ jumping to preserve_mode_ugid_time:
251                  * (sym)links don't have those */
252                 return 0;
253         }
254
255         if (/* "cp thing1 thing2" without -R: just open and read() from thing1 */
256             !(flags & FILEUTILS_RECUR)
257             /* "cp [-opts] regular_file thing2" */
258          || S_ISREG(source_stat.st_mode)
259          /* DEREF uses stat, which never returns S_ISLNK() == true.
260           * So the below is never true: */
261          /* || (FLAGS_DEREF && S_ISLNK(source_stat.st_mode)) */
262         ) {
263                 int src_fd;
264                 int dst_fd;
265                 mode_t new_mode;
266
267                 if (!FLAGS_DEREF && S_ISLNK(source_stat.st_mode)) {
268                         /* "cp -d symlink dst": create a link */
269                         goto dont_cat;
270                 }
271
272                 if (ENABLE_FEATURE_PRESERVE_HARDLINKS && !FLAGS_DEREF) {
273                         const char *link_target;
274                         link_target = is_in_ino_dev_hashtable(&source_stat);
275                         if (link_target) {
276                                 if (link(link_target, dest) < 0) {
277                                         ovr = ask_and_unlink(dest, flags);
278                                         if (ovr <= 0)
279                                                 return ovr;
280                                         if (link(link_target, dest) < 0) {
281                                                 bb_perror_msg("can't create link '%s'", dest);
282                                                 return -1;
283                                         }
284                                 }
285                                 return 0;
286                         }
287                         add_to_ino_dev_hashtable(&source_stat, dest);
288                 }
289
290                 src_fd = open_or_warn(source, O_RDONLY);
291                 if (src_fd < 0)
292                         return -1;
293
294                 /* Do not try to open with weird mode fields */
295                 new_mode = source_stat.st_mode;
296                 if (!S_ISREG(source_stat.st_mode))
297                         new_mode = 0666;
298
299                 if (ENABLE_FEATURE_NON_POSIX_CP || (flags & FILEUTILS_INTERACTIVE)) {
300                         /*
301                          * O_CREAT|O_EXCL: require that file did not exist before creation
302                          */
303                         dst_fd = open(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode);
304                 } else { /* POSIX, and not "cp -i" */
305                         /*
306                          * O_CREAT|O_TRUNC: create, or truncate (security problem versus (sym)link attacks)
307                          */
308                         dst_fd = open(dest, O_WRONLY|O_CREAT|O_TRUNC, new_mode);
309                 }
310                 if (dst_fd == -1) {
311                         ovr = ask_and_unlink(dest, flags);
312                         if (ovr <= 0) {
313                                 close(src_fd);
314                                 return ovr;
315                         }
316                         /* It shouldn't exist. If it exists, do not open (symlink attack?) */
317                         dst_fd = open3_or_warn(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode);
318                         if (dst_fd < 0) {
319                                 close(src_fd);
320                                 return -1;
321                         }
322                 }
323
324 #if ENABLE_SELINUX
325                 if ((flags & (FILEUTILS_PRESERVE_SECURITY_CONTEXT|FILEUTILS_SET_SECURITY_CONTEXT))
326                  && is_selinux_enabled() > 0
327                 ) {
328                         security_context_t con;
329                         if (getfscreatecon(&con) == -1) {
330                                 bb_simple_perror_msg("getfscreatecon");
331                                 return -1;
332                         }
333                         if (con) {
334                                 if (setfilecon(dest, con) == -1) {
335                                         bb_perror_msg("setfilecon:%s,%s", dest, con);
336                                         freecon(con);
337                                         return -1;
338                                 }
339                                 freecon(con);
340                         }
341                 }
342 #endif
343 #if ENABLE_FEATURE_CP_REFLINK
344 # undef BTRFS_IOCTL_MAGIC
345 # define BTRFS_IOCTL_MAGIC 0x94
346 # undef BTRFS_IOC_CLONE
347 # define BTRFS_IOC_CLONE _IOW (BTRFS_IOCTL_MAGIC, 9, int)
348                 if (flags & FILEUTILS_REFLINK) {
349                         retval = ioctl(dst_fd, BTRFS_IOC_CLONE, src_fd);
350                         if (retval == 0)
351                                 goto do_close;
352                         /* reflink did not work */
353                         if (flags & FILEUTILS_REFLINK_ALWAYS) {
354                                 bb_perror_msg("failed to clone '%s' from '%s'", dest, source);
355                                 goto do_close;
356                         }
357                         /* fall through to standard copy */
358                         retval = 0;
359                 }
360 #endif
361                 if (bb_copyfd_eof(src_fd, dst_fd) == -1)
362                         retval = -1;
363  IF_FEATURE_CP_REFLINK(do_close:)
364                 /* Careful with writing... */
365                 if (close(dst_fd) < 0) {
366                         bb_perror_msg("error writing to '%s'", dest);
367                         retval = -1;
368                 }
369                 /* ...but read size is already checked by bb_copyfd_eof */
370                 close(src_fd);
371                 /* "cp /dev/something new_file" should not
372                  * copy mode of /dev/something */
373                 if (!S_ISREG(source_stat.st_mode))
374                         return retval;
375                 goto preserve_mode_ugid_time;
376         }
377  dont_cat:
378
379         /* Source is a symlink or a special file */
380         /* We are lazy here, a bit lax with races... */
381         if (dest_exists) {
382                 errno = EEXIST;
383                 ovr = ask_and_unlink(dest, flags);
384                 if (ovr <= 0)
385                         return ovr;
386         }
387         if (S_ISLNK(source_stat.st_mode)) {
388                 char *lpath = xmalloc_readlink_or_warn(source);
389                 if (lpath) {
390                         int r = symlink(lpath, dest);
391                         if (r < 0) {
392                                 /* shared message */
393                                 bb_perror_msg("can't create %slink '%s' to '%s'",
394                                         "sym", dest, lpath
395                                 );
396                                 free(lpath);
397                                 return -1;
398                         }
399                         free(lpath);
400                         if (flags & FILEUTILS_PRESERVE_STATUS)
401                                 if (lchown(dest, source_stat.st_uid, source_stat.st_gid) < 0)
402                                         bb_perror_msg("can't preserve %s of '%s'", "ownership", dest);
403                 }
404                 /* _Not_ jumping to preserve_mode_ugid_time:
405                  * symlinks don't have those */
406                 goto verb_and_exit;
407         }
408         if (S_ISBLK(source_stat.st_mode) || S_ISCHR(source_stat.st_mode)
409          || S_ISSOCK(source_stat.st_mode) || S_ISFIFO(source_stat.st_mode)
410         ) {
411                 if (mknod(dest, source_stat.st_mode, source_stat.st_rdev) < 0) {
412                         bb_perror_msg("can't create '%s'", dest);
413                         return -1;
414                 }
415         } else {
416                 bb_error_msg("unrecognized file '%s' with mode %x", source, source_stat.st_mode);
417                 return -1;
418         }
419
420  preserve_mode_ugid_time:
421
422         if (flags & FILEUTILS_PRESERVE_STATUS
423         /* Cannot happen: */
424         /* && !(flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) */
425         ) {
426                 struct timeval times[2];
427
428                 times[1].tv_sec = times[0].tv_sec = source_stat.st_mtime;
429                 times[1].tv_usec = times[0].tv_usec = 0;
430                 /* BTW, utimes sets usec-precision time - just FYI */
431                 if (utimes(dest, times) < 0)
432                         bb_perror_msg("can't preserve %s of '%s'", "times", dest);
433                 if (chown(dest, source_stat.st_uid, source_stat.st_gid) < 0) {
434                         source_stat.st_mode &= ~(S_ISUID | S_ISGID);
435                         bb_perror_msg("can't preserve %s of '%s'", "ownership", dest);
436                 }
437                 if (chmod(dest, source_stat.st_mode) < 0)
438                         bb_perror_msg("can't preserve %s of '%s'", "permissions", dest);
439         }
440
441  verb_and_exit:
442         if (flags & FILEUTILS_VERBOSE) {
443                 printf("'%s' -> '%s'\n", source, dest);
444         }
445
446         return retval;
447 }