source: MondoRescue/branches/3.3/mindi-busybox/libbb/copy_file.c@ 3865

Last change on this file since 3865 was 3621, checked in by Bruno Cornec, 10 years ago

New 3?3 banch for incorporation of latest busybox 1.25. Changing minor version to handle potential incompatibilities.

File size: 11.8 KB
Line 
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 */
26static 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_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 */
80int 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 // POSIX way is a security problem versus (sym)link attacks
300 if (!ENABLE_FEATURE_NON_POSIX_CP) {
301 dst_fd = open(dest, O_WRONLY|O_CREAT|O_TRUNC, new_mode);
302 } else { /* safe way: */
303 dst_fd = open(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode);
304 }
305 if (dst_fd == -1) {
306 ovr = ask_and_unlink(dest, flags);
307 if (ovr <= 0) {
308 close(src_fd);
309 return ovr;
310 }
311 /* It shouldn't exist. If it exists, do not open (symlink attack?) */
312 dst_fd = open3_or_warn(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode);
313 if (dst_fd < 0) {
314 close(src_fd);
315 return -1;
316 }
317 }
318
319#if ENABLE_SELINUX
320 if ((flags & (FILEUTILS_PRESERVE_SECURITY_CONTEXT|FILEUTILS_SET_SECURITY_CONTEXT))
321 && is_selinux_enabled() > 0
322 ) {
323 security_context_t con;
324 if (getfscreatecon(&con) == -1) {
325 bb_perror_msg("getfscreatecon");
326 return -1;
327 }
328 if (con) {
329 if (setfilecon(dest, con) == -1) {
330 bb_perror_msg("setfilecon:%s,%s", dest, con);
331 freecon(con);
332 return -1;
333 }
334 freecon(con);
335 }
336 }
337#endif
338 if (bb_copyfd_eof(src_fd, dst_fd) == -1)
339 retval = -1;
340 /* Careful with writing... */
341 if (close(dst_fd) < 0) {
342 bb_perror_msg("error writing to '%s'", dest);
343 retval = -1;
344 }
345 /* ...but read size is already checked by bb_copyfd_eof */
346 close(src_fd);
347 /* "cp /dev/something new_file" should not
348 * copy mode of /dev/something */
349 if (!S_ISREG(source_stat.st_mode))
350 return retval;
351 goto preserve_mode_ugid_time;
352 }
353 dont_cat:
354
355 /* Source is a symlink or a special file */
356 /* We are lazy here, a bit lax with races... */
357 if (dest_exists) {
358 errno = EEXIST;
359 ovr = ask_and_unlink(dest, flags);
360 if (ovr <= 0)
361 return ovr;
362 }
363 if (S_ISLNK(source_stat.st_mode)) {
364 char *lpath = xmalloc_readlink_or_warn(source);
365 if (lpath) {
366 int r = symlink(lpath, dest);
367 free(lpath);
368 if (r < 0) {
369 bb_perror_msg("can't create symlink '%s'", dest);
370 return -1;
371 }
372 if (flags & FILEUTILS_PRESERVE_STATUS)
373 if (lchown(dest, source_stat.st_uid, source_stat.st_gid) < 0)
374 bb_perror_msg("can't preserve %s of '%s'", "ownership", dest);
375 }
376 /* _Not_ jumping to preserve_mode_ugid_time:
377 * symlinks don't have those */
378 return 0;
379 }
380 if (S_ISBLK(source_stat.st_mode) || S_ISCHR(source_stat.st_mode)
381 || S_ISSOCK(source_stat.st_mode) || S_ISFIFO(source_stat.st_mode)
382 ) {
383 if (mknod(dest, source_stat.st_mode, source_stat.st_rdev) < 0) {
384 bb_perror_msg("can't create '%s'", dest);
385 return -1;
386 }
387 } else {
388 bb_error_msg("unrecognized file '%s' with mode %x", source, source_stat.st_mode);
389 return -1;
390 }
391
392 preserve_mode_ugid_time:
393
394 if (flags & FILEUTILS_PRESERVE_STATUS
395 /* Cannot happen: */
396 /* && !(flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) */
397 ) {
398 struct timeval times[2];
399
400 times[1].tv_sec = times[0].tv_sec = source_stat.st_mtime;
401 times[1].tv_usec = times[0].tv_usec = 0;
402 /* BTW, utimes sets usec-precision time - just FYI */
403 if (utimes(dest, times) < 0)
404 bb_perror_msg("can't preserve %s of '%s'", "times", dest);
405 if (chown(dest, source_stat.st_uid, source_stat.st_gid) < 0) {
406 source_stat.st_mode &= ~(S_ISUID | S_ISGID);
407 bb_perror_msg("can't preserve %s of '%s'", "ownership", dest);
408 }
409 if (chmod(dest, source_stat.st_mode) < 0)
410 bb_perror_msg("can't preserve %s of '%s'", "permissions", dest);
411 }
412
413 if (flags & FILEUTILS_VERBOSE) {
414 printf("'%s' -> '%s'\n", source, dest);
415 }
416
417 return retval;
418}
Note: See TracBrowser for help on using the repository browser.