blob: b68a257b518d0c518e7f7d566bb2d7384ab4a3e5 [file] [log] [blame]
Eric Andersenaad1a882001-03-16 22:47:14 +00001/* vi: set sw=4 ts=4: */
2/*
Matt Kraai91b28552001-04-23 18:53:07 +00003 * Mini copy_file implementation for busybox
Eric Andersenaad1a882001-03-16 22:47:14 +00004 *
Matt Kraai91b28552001-04-23 18:53:07 +00005 * Copyright (C) 2001 by Matt Kraai <kraai@alumni.carnegiemellon.edu>
Denis Vlasenko49622d72007-03-10 16:58:49 +00006 * SELinux support by Yuichi Nakamura <ynakam@hitachisoft.jp>
Eric Andersenaad1a882001-03-16 22:47:14 +00007 *
Rob Landley2f309322005-11-01 21:55:14 +00008 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
Eric Andersenaad1a882001-03-16 22:47:14 +00009 *
Eric Andersenaad1a882001-03-16 22:47:14 +000010 */
11
Rob Landleyc9832742006-06-24 21:27:36 +000012#include "libbb.h"
Eric Andersenaad1a882001-03-16 22:47:14 +000013
Denis Vlasenko54d14ca2007-03-15 13:33:37 +000014// 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 softlink, we overwrite softlink's 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#define DO_POSIX_CP 0 /* 1 - POSIX behavior, 0 - safe behavior */
24
25
26static int ask_and_unlink(const char *dest, int flags)
Denis Vlasenkof24e1f42006-10-21 23:40:20 +000027{
Denis Vlasenko54d14ca2007-03-15 13:33:37 +000028 // If !DO_POSIX_CP, act as if -f is always in effect - we don't want
29 // "'file' exists" msg, we want unlink to be done (silently unless -i
30 // is also in effect).
31 // This prevents safe way from asking more questions than POSIX does.
32#if DO_POSIX_CP
Denis Vlasenkof24e1f42006-10-21 23:40:20 +000033 if (!(flags & (FILEUTILS_FORCE|FILEUTILS_INTERACTIVE))) {
34 fprintf(stderr, "'%s' exists\n", dest);
35 return -1;
36 }
Denis Vlasenko54d14ca2007-03-15 13:33:37 +000037#endif
38
39 // TODO: maybe we should do it only if ctty is present?
Denis Vlasenkof24e1f42006-10-21 23:40:20 +000040 if (flags & FILEUTILS_INTERACTIVE) {
Denis Vlasenko54d14ca2007-03-15 13:33:37 +000041 // We would not do POSIX insanity. -i asks,
42 // then _unlinks_ the offender. Presto.
43 // (No opening without O_EXCL, no unlinks only if -f)
44 // Or else we will end up having 3 open()s!
Denis Vlasenkof24e1f42006-10-21 23:40:20 +000045 fprintf(stderr, "%s: overwrite '%s'? ", applet_name, dest);
46 if (!bb_ask_confirmation())
47 return 0; // not allowed to overwrite
48 }
49 if (unlink(dest) < 0) {
50 bb_perror_msg("cannot remove '%s'", dest);
51 return -1; // error
52 }
53 return 1; // ok (to try again)
54}
55
Denis Vlasenko54d14ca2007-03-15 13:33:37 +000056/* Return:
57 * -1 error, copy not made
58 * 0 copy is made or user answered "no" in interactive mode
59 * (failures to preserve mode/owner/times are not reported in exit code)
60 */
Matt Kraai91b28552001-04-23 18:53:07 +000061int copy_file(const char *source, const char *dest, int flags)
Eric Andersenaad1a882001-03-16 22:47:14 +000062{
Matt Kraai91b28552001-04-23 18:53:07 +000063 struct stat source_stat;
64 struct stat dest_stat;
Matt Kraai91b28552001-04-23 18:53:07 +000065 int status = 0;
Denis Vlasenkof24e1f42006-10-21 23:40:20 +000066 signed char dest_exists = 0;
67 signed char ovr;
Eric Andersenaad1a882001-03-16 22:47:14 +000068
Denis Vlasenkof24e1f42006-10-21 23:40:20 +000069#define FLAGS_DEREF (flags & FILEUTILS_DEREFERENCE)
70
71 if ((FLAGS_DEREF ? stat : lstat)(source, &source_stat) < 0) {
72 // This may be a dangling symlink.
73 // Making [sym]links to dangling symlinks works, so...
74 if (flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK))
75 goto make_links;
76 bb_perror_msg("cannot stat '%s'", source);
Matt Kraai91b28552001-04-23 18:53:07 +000077 return -1;
Glenn L McGrath4949faf2001-04-11 16:23:35 +000078 }
Eric Andersenaad1a882001-03-16 22:47:14 +000079
Eric Andersen02b8dfc2002-09-16 10:23:38 +000080 if (lstat(dest, &dest_stat) < 0) {
Matt Kraai91b28552001-04-23 18:53:07 +000081 if (errno != ENOENT) {
Denis Vlasenkof24e1f42006-10-21 23:40:20 +000082 bb_perror_msg("cannot stat '%s'", dest);
Matt Kraai91b28552001-04-23 18:53:07 +000083 return -1;
Glenn L McGrath4949faf2001-04-11 16:23:35 +000084 }
Eric Andersena9a220b2002-09-17 08:42:21 +000085 } else {
Denis Vlasenkof24e1f42006-10-21 23:40:20 +000086 if (source_stat.st_dev == dest_stat.st_dev
87 && source_stat.st_ino == dest_stat.st_ino
88 ) {
89 bb_error_msg("'%s' and '%s' are the same file", source, dest);
Mike Frysinger2ed05ab2005-04-14 02:52:50 +000090 return -1;
91 }
Eric Andersena9a220b2002-09-17 08:42:21 +000092 dest_exists = 1;
93 }
Eric Andersenaad1a882001-03-16 22:47:14 +000094
Denis Vlasenko49622d72007-03-10 16:58:49 +000095#if ENABLE_SELINUX
96 if ((flags & FILEUTILS_PRESERVE_SECURITY_CONTEXT) && is_selinux_enabled() > 0) {
97 security_context_t con;
98 if (lgetfilecon(source, &con) >= 0) {
99 if (setfscreatecon(con) < 0) {
100 bb_perror_msg("cannot set setfscreatecon %s", con);
101 freecon(con);
102 return -1;
103 }
Denis Vlasenko54d14ca2007-03-15 13:33:37 +0000104 } else if (errno == ENOTSUP || errno == ENODATA) {
105 setfscreatecon_or_die(NULL);
Denis Vlasenko49622d72007-03-10 16:58:49 +0000106 } else {
Denis Vlasenko54d14ca2007-03-15 13:33:37 +0000107 bb_perror_msg("cannot lgetfilecon %s", source);
108 return -1;
Denis Vlasenko49622d72007-03-10 16:58:49 +0000109 }
110 }
111#endif
112
Matt Kraai91b28552001-04-23 18:53:07 +0000113 if (S_ISDIR(source_stat.st_mode)) {
114 DIR *dp;
115 struct dirent *d;
Matt Kraai218aa372001-04-30 17:32:43 +0000116 mode_t saved_umask = 0;
Matt Kraai91b28552001-04-23 18:53:07 +0000117
Matt Kraai01441032001-04-24 01:30:02 +0000118 if (!(flags & FILEUTILS_RECUR)) {
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000119 bb_error_msg("omitting directory '%s'", source);
Matt Kraai91b28552001-04-23 18:53:07 +0000120 return -1;
Glenn L McGrath4949faf2001-04-11 16:23:35 +0000121 }
Eric Andersenaad1a882001-03-16 22:47:14 +0000122
Matt Kraai91b28552001-04-23 18:53:07 +0000123 /* Create DEST. */
124 if (dest_exists) {
125 if (!S_ISDIR(dest_stat.st_mode)) {
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000126 bb_error_msg("target '%s' is not a directory", dest);
Matt Kraai91b28552001-04-23 18:53:07 +0000127 return -1;
Glenn L McGrath4949faf2001-04-11 16:23:35 +0000128 }
Matt Kraai91b28552001-04-23 18:53:07 +0000129 } else {
Matt Kraai218aa372001-04-30 17:32:43 +0000130 mode_t mode;
Matt Kraai91b28552001-04-23 18:53:07 +0000131 saved_umask = umask(0);
Eric Andersenaad1a882001-03-16 22:47:14 +0000132
Matt Kraai91b28552001-04-23 18:53:07 +0000133 mode = source_stat.st_mode;
Matt Kraai01441032001-04-24 01:30:02 +0000134 if (!(flags & FILEUTILS_PRESERVE_STATUS))
Matt Kraai91b28552001-04-23 18:53:07 +0000135 mode = source_stat.st_mode & ~saved_umask;
136 mode |= S_IRWXU;
137
138 if (mkdir(dest, mode) < 0) {
139 umask(saved_umask);
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000140 bb_perror_msg("cannot create directory '%s'", dest);
Matt Kraai91b28552001-04-23 18:53:07 +0000141 return -1;
Glenn L McGrath4949faf2001-04-11 16:23:35 +0000142 }
Matt Kraai91b28552001-04-23 18:53:07 +0000143
144 umask(saved_umask);
Eric Andersenaad1a882001-03-16 22:47:14 +0000145 }
Matt Kraaibf0a0102001-06-11 13:58:02 +0000146
Matt Kraai91b28552001-04-23 18:53:07 +0000147 /* Recursively copy files in SOURCE. */
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000148 dp = opendir(source);
149 if (dp == NULL) {
Matt Kraai91b28552001-04-23 18:53:07 +0000150 status = -1;
Rob Landley2f309322005-11-01 21:55:14 +0000151 goto preserve_status;
Matt Kraai91b28552001-04-23 18:53:07 +0000152 }
153
154 while ((d = readdir(dp)) != NULL) {
155 char *new_source, *new_dest;
156
Glenn L McGrath393183d2003-05-26 14:07:50 +0000157 new_source = concat_subpath_file(source, d->d_name);
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000158 if (new_source == NULL)
Matt Kraai91b28552001-04-23 18:53:07 +0000159 continue;
Matt Kraai91b28552001-04-23 18:53:07 +0000160 new_dest = concat_path_file(dest, d->d_name);
161 if (copy_file(new_source, new_dest, flags) < 0)
162 status = -1;
163 free(new_source);
164 free(new_dest);
165 }
Eric Andersena9a220b2002-09-17 08:42:21 +0000166 closedir(dp);
Matt Kraai218aa372001-04-30 17:32:43 +0000167
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000168 if (!dest_exists
169 && chmod(dest, source_stat.st_mode & ~saved_umask) < 0
170 ) {
171 bb_perror_msg("cannot change permissions of '%s'", dest);
Matt Kraai218aa372001-04-30 17:32:43 +0000172 status = -1;
173 }
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000174
175 } else if (flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) {
176 int (*lf)(const char *oldpath, const char *newpath);
177 make_links:
178 // Hmm... maybe
179 // if (DEREF && MAKE_SOFTLINK) source = realpath(source) ?
180 // (but realpath returns NULL on dangling symlinks...)
181 lf = (flags & FILEUTILS_MAKE_SOFTLINK) ? symlink : link;
182 if (lf(source, dest) < 0) {
Denis Vlasenko54d14ca2007-03-15 13:33:37 +0000183 ovr = ask_and_unlink(dest, flags);
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000184 if (ovr <= 0)
185 return ovr;
186 if (lf(source, dest) < 0) {
187 bb_perror_msg("cannot create link '%s'", dest);
188 return -1;
189 }
190 }
191 return 0;
192
193 } else if (S_ISREG(source_stat.st_mode)
Denis Vlasenko54d14ca2007-03-15 13:33:37 +0000194 /* Huh? DEREF uses stat, which never returns links! */
195 /* || (FLAGS_DEREF && S_ISLNK(source_stat.st_mode)) */
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000196 ) {
Glenn L McGrathf62ea202003-12-20 04:38:01 +0000197 int src_fd;
198 int dst_fd;
Rob Landleyc9832742006-06-24 21:27:36 +0000199 if (ENABLE_FEATURE_PRESERVE_HARDLINKS) {
Denis Vlasenko75103842007-06-20 14:49:47 +0000200 char *link_target;
Matt Kraaiace02dc2001-12-17 15:26:36 +0000201
Denis Vlasenko6ef06ee2007-03-14 22:06:01 +0000202 if (!FLAGS_DEREF) {
Denis Vlasenko75103842007-06-20 14:49:47 +0000203 link_target = is_in_ino_dev_hashtable(&source_stat);
204 if (link_target) {
205 if (link(link_target, dest) < 0) {
Denis Vlasenko54d14ca2007-03-15 13:33:37 +0000206 ovr = ask_and_unlink(dest, flags);
Denis Vlasenko6ef06ee2007-03-14 22:06:01 +0000207 if (ovr <= 0)
208 return ovr;
Denis Vlasenko75103842007-06-20 14:49:47 +0000209 if (link(link_target, dest) < 0) {
Denis Vlasenko6ef06ee2007-03-14 22:06:01 +0000210 bb_perror_msg("cannot create link '%s'", dest);
211 return -1;
212 }
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000213 }
Denis Vlasenko6ef06ee2007-03-14 22:06:01 +0000214 return 0;
Rob Landleyc9832742006-06-24 21:27:36 +0000215 }
Matt Kraaiace02dc2001-12-17 15:26:36 +0000216 }
Rob Landleyc9832742006-06-24 21:27:36 +0000217 add_to_ino_dev_hashtable(&source_stat, dest);
Matt Kraaiace02dc2001-12-17 15:26:36 +0000218 }
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000219
Denis Vlasenko50f7f442007-04-11 23:20:53 +0000220 src_fd = open_or_warn(source, O_RDONLY);
221 if (src_fd < 0) {
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000222 return -1;
Matt Kraai14b7c5d2001-12-11 16:43:48 +0000223 }
224
Denis Vlasenko54d14ca2007-03-15 13:33:37 +0000225#if DO_POSIX_CP /* POSIX way (a security problem versus symlink attacks!): */
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000226 dst_fd = open(dest, (flags & FILEUTILS_INTERACTIVE)
Denis Vlasenko54d14ca2007-03-15 13:33:37 +0000227 ? O_WRONLY|O_CREAT|O_EXCL
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000228 : O_WRONLY|O_CREAT|O_TRUNC, source_stat.st_mode);
Denis Vlasenko54d14ca2007-03-15 13:33:37 +0000229#else /* safe way: */
230 dst_fd = open(dest, O_WRONLY|O_CREAT|O_EXCL, source_stat.st_mode);
231#endif
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000232 if (dst_fd == -1) {
Denis Vlasenko54d14ca2007-03-15 13:33:37 +0000233 ovr = ask_and_unlink(dest, flags);
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000234 if (ovr <= 0) {
Glenn L McGrathf62ea202003-12-20 04:38:01 +0000235 close(src_fd);
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000236 return ovr;
237 }
Denis Vlasenko54d14ca2007-03-15 13:33:37 +0000238 /* It shouldn't exist. If it exists, do not open (symlink attack?) */
Denis Vlasenko50f7f442007-04-11 23:20:53 +0000239 dst_fd = open3_or_warn(dest, O_WRONLY|O_CREAT|O_EXCL, source_stat.st_mode);
240 if (dst_fd < 0) {
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000241 close(src_fd);
242 return -1;
Matt Kraai91b28552001-04-23 18:53:07 +0000243 }
244 }
245
Denis Vlasenko49622d72007-03-10 16:58:49 +0000246#if ENABLE_SELINUX
247 if (((flags & FILEUTILS_PRESERVE_SECURITY_CONTEXT)
248 || (flags & FILEUTILS_SET_SECURITY_CONTEXT))
Denis Vlasenko54d14ca2007-03-15 13:33:37 +0000249 && is_selinux_enabled() > 0
250 ) {
Denis Vlasenkoc86e0522007-03-20 11:30:28 +0000251 security_context_t con;
Denis Vlasenko49622d72007-03-10 16:58:49 +0000252 if (getfscreatecon(&con) == -1) {
253 bb_perror_msg("getfscreatecon");
254 return -1;
Denis Vlasenkoc86e0522007-03-20 11:30:28 +0000255 }
Denis Vlasenko49622d72007-03-10 16:58:49 +0000256 if (con) {
Denis Vlasenko51742f42007-04-12 00:32:05 +0000257 if (setfilecon(dest, con) == -1) {
Denis Vlasenko49622d72007-03-10 16:58:49 +0000258 bb_perror_msg("setfilecon:%s,%s", dest, con);
259 freecon(con);
260 return -1;
261 }
262 freecon(con);
263 }
264 }
265#endif
Glenn L McGrathf62ea202003-12-20 04:38:01 +0000266 if (bb_copyfd_eof(src_fd, dst_fd) == -1)
Matt Kraaibf0a0102001-06-11 13:58:02 +0000267 status = -1;
Glenn L McGrathf62ea202003-12-20 04:38:01 +0000268 if (close(dst_fd) < 0) {
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000269 bb_perror_msg("cannot close '%s'", dest);
Matt Kraai91b28552001-04-23 18:53:07 +0000270 status = -1;
271 }
Glenn L McGrathf62ea202003-12-20 04:38:01 +0000272 if (close(src_fd) < 0) {
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000273 bb_perror_msg("cannot close '%s'", source);
Matt Kraai91b28552001-04-23 18:53:07 +0000274 status = -1;
275 }
Eric Andersen403a73a2002-09-16 09:23:22 +0000276
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000277 } else if (S_ISBLK(source_stat.st_mode) || S_ISCHR(source_stat.st_mode)
278 || S_ISSOCK(source_stat.st_mode) || S_ISFIFO(source_stat.st_mode)
279 || S_ISLNK(source_stat.st_mode)
280 ) {
281 // We are lazy here, a bit lax with races...
Paul Fox0a92bbf2005-07-19 20:47:33 +0000282 if (dest_exists) {
Denis Vlasenko54d14ca2007-03-15 13:33:37 +0000283 ovr = ask_and_unlink(dest, flags);
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000284 if (ovr <= 0)
285 return ovr;
Paul Fox0a92bbf2005-07-19 20:47:33 +0000286 }
Rob Landley2f309322005-11-01 21:55:14 +0000287 if (S_ISFIFO(source_stat.st_mode)) {
288 if (mkfifo(dest, source_stat.st_mode) < 0) {
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000289 bb_perror_msg("cannot create fifo '%s'", dest);
Rob Landley2f309322005-11-01 21:55:14 +0000290 return -1;
291 }
292 } else if (S_ISLNK(source_stat.st_mode)) {
293 char *lpath;
294
Denis Vlasenko6ca04442007-02-11 16:19:28 +0000295 lpath = xmalloc_readlink_or_warn(source);
Denis Vlasenko54d14ca2007-03-15 13:33:37 +0000296 if (lpath && symlink(lpath, dest) < 0) {
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000297 bb_perror_msg("cannot create symlink '%s'", dest);
298 free(lpath);
Rob Landley2f309322005-11-01 21:55:14 +0000299 return -1;
300 }
301 free(lpath);
302
303 if (flags & FILEUTILS_PRESERVE_STATUS)
304 if (lchown(dest, source_stat.st_uid, source_stat.st_gid) < 0)
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000305 bb_perror_msg("cannot preserve %s of '%s'", "ownership", dest);
Rob Landley2f309322005-11-01 21:55:14 +0000306
307 return 0;
308
309 } else {
310 if (mknod(dest, source_stat.st_mode, source_stat.st_rdev) < 0) {
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000311 bb_perror_msg("cannot create '%s'", dest);
Rob Landley2f309322005-11-01 21:55:14 +0000312 return -1;
313 }
314 }
Eric Andersena9a220b2002-09-17 08:42:21 +0000315 } else {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000316 bb_error_msg("internal error: unrecognized file type");
Eric Andersena9a220b2002-09-17 08:42:21 +0000317 return -1;
Eric Andersenaad1a882001-03-16 22:47:14 +0000318 }
319
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000320 preserve_status:
Matt Kraai91b28552001-04-23 18:53:07 +0000321
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000322 if (flags & FILEUTILS_PRESERVE_STATUS
323 /* Cannot happen: */
324 /* && !(flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) */
325 ) {
Matt Kraai91b28552001-04-23 18:53:07 +0000326 struct utimbuf times;
327
328 times.actime = source_stat.st_atime;
329 times.modtime = source_stat.st_mtime;
330 if (utime(dest, &times) < 0)
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000331 bb_perror_msg("cannot preserve %s of '%s'", "times", dest);
Matt Kraai91b28552001-04-23 18:53:07 +0000332 if (chown(dest, source_stat.st_uid, source_stat.st_gid) < 0) {
333 source_stat.st_mode &= ~(S_ISUID | S_ISGID);
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000334 bb_perror_msg("cannot preserve %s of '%s'", "ownership", dest);
Matt Kraai91b28552001-04-23 18:53:07 +0000335 }
336 if (chmod(dest, source_stat.st_mode) < 0)
Denis Vlasenkof24e1f42006-10-21 23:40:20 +0000337 bb_perror_msg("cannot preserve %s of '%s'", "permissions", dest);
Eric Andersenaad1a882001-03-16 22:47:14 +0000338 }
339
Matt Kraai24abecc2001-04-30 16:37:04 +0000340 return status;
Eric Andersenaad1a882001-03-16 22:47:14 +0000341}