source: branches/3.2/mindi-busybox/libbb/copy_file.c @ 3232

Last change on this file since 3232 was 3232, checked in by bruno, 6 years ago
  • Update mindi-busybox to 1.21.1
File size: 11.3 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    return 1; /* ok (to try again) */
68}
69
70/* Return:
71 * -1 error, copy not made
72 *  0 copy is made or user answered "no" in interactive mode
73 *    (failures to preserve mode/owner/times are not reported in exit code)
74 */
75int FAST_FUNC copy_file(const char *source, const char *dest, int flags)
76{
77    /* This is a recursive function, try to minimize stack usage */
78    /* NB: each struct stat is ~100 bytes */
79    struct stat source_stat;
80    struct stat dest_stat;
81    smallint retval = 0;
82    smallint dest_exists = 0;
83    smallint ovr;
84
85/* Inverse of cp -d ("cp without -d") */
86#define FLAGS_DEREF (flags & (FILEUTILS_DEREFERENCE + FILEUTILS_DEREFERENCE_L0))
87
88    if ((FLAGS_DEREF ? stat : lstat)(source, &source_stat) < 0) {
89        /* This may be a dangling symlink.
90         * Making [sym]links to dangling symlinks works, so... */
91        if (flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK))
92            goto make_links;
93        bb_perror_msg("can't stat '%s'", source);
94        return -1;
95    }
96
97    if (lstat(dest, &dest_stat) < 0) {
98        if (errno != ENOENT) {
99            bb_perror_msg("can't stat '%s'", dest);
100            return -1;
101        }
102    } else {
103        if (source_stat.st_dev == dest_stat.st_dev
104         && source_stat.st_ino == dest_stat.st_ino
105        ) {
106            bb_error_msg("'%s' and '%s' are the same file", source, dest);
107            return -1;
108        }
109        dest_exists = 1;
110    }
111
112#if ENABLE_SELINUX
113    if ((flags & FILEUTILS_PRESERVE_SECURITY_CONTEXT) && is_selinux_enabled() > 0) {
114        security_context_t con;
115        if (lgetfilecon(source, &con) >= 0) {
116            if (setfscreatecon(con) < 0) {
117                bb_perror_msg("can't set setfscreatecon %s", con);
118                freecon(con);
119                return -1;
120            }
121        } else if (errno == ENOTSUP || errno == ENODATA) {
122            setfscreatecon_or_die(NULL);
123        } else {
124            bb_perror_msg("can't lgetfilecon %s", source);
125            return -1;
126        }
127    }
128#endif
129
130    if (S_ISDIR(source_stat.st_mode)) {
131        DIR *dp;
132        const char *tp;
133        struct dirent *d;
134        mode_t saved_umask = 0;
135
136        if (!(flags & FILEUTILS_RECUR)) {
137            bb_error_msg("omitting directory '%s'", source);
138            return -1;
139        }
140
141        /* Did we ever create source ourself before? */
142        tp = is_in_ino_dev_hashtable(&source_stat);
143        if (tp) {
144            /* We did! it's a recursion! man the lifeboats... */
145            bb_error_msg("recursion detected, omitting directory '%s'",
146                    source);
147            return -1;
148        }
149
150        if (dest_exists) {
151            if (!S_ISDIR(dest_stat.st_mode)) {
152                bb_error_msg("target '%s' is not a directory", dest);
153                return -1;
154            }
155            /* race here: user can substitute a symlink between
156             * this check and actual creation of files inside dest */
157        } else {
158            /* Create DEST */
159            mode_t mode;
160            saved_umask = umask(0);
161
162            mode = source_stat.st_mode;
163            if (!(flags & FILEUTILS_PRESERVE_STATUS))
164                mode = source_stat.st_mode & ~saved_umask;
165            /* Allow owner to access new dir (at least for now) */
166            mode |= S_IRWXU;
167            if (mkdir(dest, mode) < 0) {
168                umask(saved_umask);
169                bb_perror_msg("can't create directory '%s'", dest);
170                return -1;
171            }
172            umask(saved_umask);
173            /* need stat info for add_to_ino_dev_hashtable */
174            if (lstat(dest, &dest_stat) < 0) {
175                bb_perror_msg("can't stat '%s'", dest);
176                return -1;
177            }
178        }
179        /* remember (dev,inode) of each created dir.
180         * NULL: name is not remembered */
181        add_to_ino_dev_hashtable(&dest_stat, NULL);
182
183        /* Recursively copy files in SOURCE */
184        dp = opendir(source);
185        if (dp == NULL) {
186            retval = -1;
187            goto preserve_mode_ugid_time;
188        }
189
190        while ((d = readdir(dp)) != NULL) {
191            char *new_source, *new_dest;
192
193            new_source = concat_subpath_file(source, d->d_name);
194            if (new_source == NULL)
195                continue;
196            new_dest = concat_path_file(dest, d->d_name);
197            if (copy_file(new_source, new_dest, flags & ~FILEUTILS_DEREFERENCE_L0) < 0)
198                retval = -1;
199            free(new_source);
200            free(new_dest);
201        }
202        closedir(dp);
203
204        if (!dest_exists
205         && chmod(dest, source_stat.st_mode & ~saved_umask) < 0
206        ) {
207            bb_perror_msg("can't preserve %s of '%s'", "permissions", dest);
208            /* retval = -1; - WRONG! copy *WAS* made */
209        }
210        goto preserve_mode_ugid_time;
211    }
212
213    if (flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) {
214        int (*lf)(const char *oldpath, const char *newpath);
215 make_links:
216        /* Hmm... maybe
217         * if (DEREF && MAKE_SOFTLINK) source = realpath(source) ?
218         * (but realpath returns NULL on dangling symlinks...) */
219        lf = (flags & FILEUTILS_MAKE_SOFTLINK) ? symlink : link;
220        if (lf(source, dest) < 0) {
221            ovr = ask_and_unlink(dest, flags);
222            if (ovr <= 0)
223                return ovr;
224            if (lf(source, dest) < 0) {
225                bb_perror_msg("can't create link '%s'", dest);
226                return -1;
227            }
228        }
229        /* _Not_ jumping to preserve_mode_ugid_time:
230         * (sym)links don't have those */
231        return 0;
232    }
233
234    if (/* "cp thing1 thing2" without -R: just open and read() from thing1 */
235        !(flags & FILEUTILS_RECUR)
236        /* "cp [-opts] regular_file thing2" */
237     || S_ISREG(source_stat.st_mode)
238     /* DEREF uses stat, which never returns S_ISLNK() == true.
239      * So the below is never true: */
240     /* || (FLAGS_DEREF && S_ISLNK(source_stat.st_mode)) */
241    ) {
242        int src_fd;
243        int dst_fd;
244        mode_t new_mode;
245
246        if (!FLAGS_DEREF && S_ISLNK(source_stat.st_mode)) {
247            /* "cp -d symlink dst": create a link */
248            goto dont_cat;
249        }
250
251        if (ENABLE_FEATURE_PRESERVE_HARDLINKS && !FLAGS_DEREF) {
252            const char *link_target;
253            link_target = is_in_ino_dev_hashtable(&source_stat);
254            if (link_target) {
255                if (link(link_target, dest) < 0) {
256                    ovr = ask_and_unlink(dest, flags);
257                    if (ovr <= 0)
258                        return ovr;
259                    if (link(link_target, dest) < 0) {
260                        bb_perror_msg("can't create link '%s'", dest);
261                        return -1;
262                    }
263                }
264                return 0;
265            }
266            add_to_ino_dev_hashtable(&source_stat, dest);
267        }
268
269        src_fd = open_or_warn(source, O_RDONLY);
270        if (src_fd < 0)
271            return -1;
272
273        /* Do not try to open with weird mode fields */
274        new_mode = source_stat.st_mode;
275        if (!S_ISREG(source_stat.st_mode))
276            new_mode = 0666;
277
278        // POSIX way is a security problem versus (sym)link attacks
279        if (!ENABLE_FEATURE_NON_POSIX_CP) {
280            dst_fd = open(dest, O_WRONLY|O_CREAT|O_TRUNC, new_mode);
281        } else { /* safe way: */
282            dst_fd = open(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode);
283        }
284        if (dst_fd == -1) {
285            ovr = ask_and_unlink(dest, flags);
286            if (ovr <= 0) {
287                close(src_fd);
288                return ovr;
289            }
290            /* It shouldn't exist. If it exists, do not open (symlink attack?) */
291            dst_fd = open3_or_warn(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode);
292            if (dst_fd < 0) {
293                close(src_fd);
294                return -1;
295            }
296        }
297
298#if ENABLE_SELINUX
299        if ((flags & (FILEUTILS_PRESERVE_SECURITY_CONTEXT|FILEUTILS_SET_SECURITY_CONTEXT))
300         && is_selinux_enabled() > 0
301        ) {
302            security_context_t con;
303            if (getfscreatecon(&con) == -1) {
304                bb_perror_msg("getfscreatecon");
305                return -1;
306            }
307            if (con) {
308                if (setfilecon(dest, con) == -1) {
309                    bb_perror_msg("setfilecon:%s,%s", dest, con);
310                    freecon(con);
311                    return -1;
312                }
313                freecon(con);
314            }
315        }
316#endif
317        if (bb_copyfd_eof(src_fd, dst_fd) == -1)
318            retval = -1;
319        /* Careful with writing... */
320        if (close(dst_fd) < 0) {
321            bb_perror_msg("error writing to '%s'", dest);
322            retval = -1;
323        }
324        /* ...but read size is already checked by bb_copyfd_eof */
325        close(src_fd);
326        /* "cp /dev/something new_file" should not
327         * copy mode of /dev/something */
328        if (!S_ISREG(source_stat.st_mode))
329            return retval;
330        goto preserve_mode_ugid_time;
331    }
332 dont_cat:
333
334    /* Source is a symlink or a special file */
335    /* We are lazy here, a bit lax with races... */
336    if (dest_exists) {
337        errno = EEXIST;
338        ovr = ask_and_unlink(dest, flags);
339        if (ovr <= 0)
340            return ovr;
341    }
342    if (S_ISLNK(source_stat.st_mode)) {
343        char *lpath = xmalloc_readlink_or_warn(source);
344        if (lpath) {
345            int r = symlink(lpath, dest);
346            free(lpath);
347            if (r < 0) {
348                bb_perror_msg("can't create symlink '%s'", dest);
349                return -1;
350            }
351            if (flags & FILEUTILS_PRESERVE_STATUS)
352                if (lchown(dest, source_stat.st_uid, source_stat.st_gid) < 0)
353                    bb_perror_msg("can't preserve %s of '%s'", "ownership", dest);
354        }
355        /* _Not_ jumping to preserve_mode_ugid_time:
356         * symlinks don't have those */
357        return 0;
358    }
359    if (S_ISBLK(source_stat.st_mode) || S_ISCHR(source_stat.st_mode)
360     || S_ISSOCK(source_stat.st_mode) || S_ISFIFO(source_stat.st_mode)
361    ) {
362        if (mknod(dest, source_stat.st_mode, source_stat.st_rdev) < 0) {
363            bb_perror_msg("can't create '%s'", dest);
364            return -1;
365        }
366    } else {
367        bb_error_msg("unrecognized file '%s' with mode %x", source, source_stat.st_mode);
368        return -1;
369    }
370
371 preserve_mode_ugid_time:
372
373    if (flags & FILEUTILS_PRESERVE_STATUS
374    /* Cannot happen: */
375    /* && !(flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) */
376    ) {
377        struct timeval times[2];
378
379        times[1].tv_sec = times[0].tv_sec = source_stat.st_mtime;
380        times[1].tv_usec = times[0].tv_usec = 0;
381        /* BTW, utimes sets usec-precision time - just FYI */
382        if (utimes(dest, times) < 0)
383            bb_perror_msg("can't preserve %s of '%s'", "times", dest);
384        if (chown(dest, source_stat.st_uid, source_stat.st_gid) < 0) {
385            source_stat.st_mode &= ~(S_ISUID | S_ISGID);
386            bb_perror_msg("can't preserve %s of '%s'", "ownership", dest);
387        }
388        if (chmod(dest, source_stat.st_mode) < 0)
389            bb_perror_msg("can't preserve %s of '%s'", "permissions", dest);
390    }
391
392    return retval;
393}
Note: See TracBrowser for help on using the repository browser.