copy_file: handle "cp /dev/foo file" (almost) compatibly to coreutils. (almost because we do not copy mode, which is probably wasn't intended). +61 bytes.
No binary (build failed)
—
(log yok)
| @@ -71,7 +71,8 @@ int mv_main(int argc, char **argv) | |
| } | |
| DO_MOVE: | |
| - | if (dest_exists && !(flags & OPT_FILEUTILS_FORCE) |
| + | if (dest_exists |
| + | && !(flags & OPT_FILEUTILS_FORCE) |
| && ((access(dest, W_OK) < 0 && isatty(0)) | |
| || (flags & OPT_FILEUTILS_INTERACTIVE)) | |
| ) { | |
| @@ -108,6 +109,9 @@ int mv_main(int argc, char **argv) | |
| goto RET_1; | |
| } | |
| } | |
| + | /* FILEUTILS_RECUR also prevents nasties like |
| + | * "read from device and write contents to dst" |
| + | * instead of "create same device node" */ |
| copy_flag = FILEUTILS_RECUR | FILEUTILS_PRESERVE_STATUS; | |
| #if ENABLE_SELINUX | |
| copy_flag |= FILEUTILS_PRESERVE_SECURITY_CONTEXT; | |
| @@ -223,8 +223,27 @@ extern char *skip_non_whitespace(const char *); | |
| //TODO: supply a pointer to char[11] buffer (avoid statics)? | |
| extern const char *bb_mode_string(mode_t mode); | |
| extern int is_directory(const char *name, int followLinks, struct stat *statBuf); | |
| + | enum { /* DO NOT CHANGE THESE VALUES! cp.c, mv.c, install.c depend on them. */ |
| + | FILEUTILS_PRESERVE_STATUS = 1, |
| + | FILEUTILS_DEREFERENCE = 2, |
| + | FILEUTILS_RECUR = 4, |
| + | FILEUTILS_FORCE = 8, |
| + | FILEUTILS_INTERACTIVE = 0x10, |
| + | FILEUTILS_MAKE_HARDLINK = 0x20, |
| + | FILEUTILS_MAKE_SOFTLINK = 0x40, |
| + | #if ENABLE_SELINUX |
| + | FILEUTILS_PRESERVE_SECURITY_CONTEXT = 0x80, |
| + | FILEUTILS_SET_SECURITY_CONTEXT = 0x100 |
| + | #endif |
| + | }; |
| + | #define FILEUTILS_CP_OPTSTR "pdRfils" USE_SELINUX("c") |
| extern int remove_file(const char *path, int flags); | |
| + | /* NB: without FILEUTILS_RECUR in flags, it will basically "cat" |
| + | * the source, not copy (unless "source" is a directory). |
| + | * This makes "cp /dev/null file" and "install /dev/null file" (!!!) |
| + | * work coreutils-compatibly. */ |
| extern int copy_file(const char *source, const char *dest, int flags); | |
| + | |
| enum { | |
| ACTION_RECURSE = (1 << 0), | |
| ACTION_FOLLOWLINKS = (1 << 1), | |
| @@ -1170,21 +1189,6 @@ void *md5_end(void *resbuf, md5_ctx_t *ctx); | |
| uint32_t *crc32_filltable(uint32_t *tbl256, int endian); | |
| - | enum { /* DO NOT CHANGE THESE VALUES! cp.c, mv.c, install.c depend on them. */ |
| - | FILEUTILS_PRESERVE_STATUS = 1, |
| - | FILEUTILS_DEREFERENCE = 2, |
| - | FILEUTILS_RECUR = 4, |
| - | FILEUTILS_FORCE = 8, |
| - | FILEUTILS_INTERACTIVE = 0x10, |
| - | FILEUTILS_MAKE_HARDLINK = 0x20, |
| - | FILEUTILS_MAKE_SOFTLINK = 0x40, |
| - | #if ENABLE_SELINUX |
| - | FILEUTILS_PRESERVE_SECURITY_CONTEXT = 0x80, |
| - | FILEUTILS_SET_SECURITY_CONTEXT = 0x100 |
| - | #endif |
| - | }; |
| - | |
| - | #define FILEUTILS_CP_OPTSTR "pdRfils" USE_SELINUX("c") |
| extern const char *applet_name; | |
| /* "BusyBox vN.N.N (timestamp or extra_version)" */ | |
| extern const char bb_banner[]; | |
| @@ -81,6 +81,7 @@ int copy_file(const char *source, const char *dest, int flags) | |
| signed char dest_exists = 0; | |
| signed char ovr; | |
| + | /* Inverse of cp -d ("cp without -d") */ |
| #define FLAGS_DEREF (flags & FILEUTILS_DEREFERENCE) | |
| if ((FLAGS_DEREF ? stat : lstat)(source, &source_stat) < 0) { | |
| @@ -229,12 +230,22 @@ int copy_file(const char *source, const char *dest, int flags) | |
| return 0; | |
| } | |
| - | if (S_ISREG(source_stat.st_mode) |
| - | /* DEREF uses stat, which never returns S_ISLNK() == true. */ |
| + | if (/* "cp thing1 thing2" without -R: just open and read() from thing1 */ |
| + | !(flags & FILEUTILS_RECUR) |
| + | /* "cp [-opts] regular_file thing2" */ |
| + | || S_ISREG(source_stat.st_mode) |
| + | /* DEREF uses stat, which never returns S_ISLNK() == true. |
| + | * So the below is never true: */ |
| /* || (FLAGS_DEREF && S_ISLNK(source_stat.st_mode)) */ | |
| ) { | |
| int src_fd; | |
| int dst_fd; | |
| + | mode_t new_mode; |
| + | |
| + | if (!FLAGS_DEREF && S_ISLNK(source_stat.st_mode)) { |
| + | /* "cp -d symlink dst": create a link */ |
| + | goto dont_cat; |
| + | } |
| if (ENABLE_FEATURE_PRESERVE_HARDLINKS && !FLAGS_DEREF) { | |
| const char *link_target; | |
| @@ -258,18 +269,24 @@ int copy_file(const char *source, const char *dest, int flags) | |
| if (src_fd < 0) | |
| return -1; | |
| + | /* Do not try to open with weird mode fields */ |
| + | new_mode = source_stat.st_mode; |
| + | if (!S_ISREG(source_stat.st_mode)) |
| + | new_mode = 0666; |
| + | |
| /* POSIX way is a security problem versus symlink attacks, | |
| * we do it only for non-symlinks, and only for non-recursive, | |
| * non-interactive cp. NB: it is still racy | |
| * for "cp file /home/bad_user/file" case | |
| * (user can rm file and create a link to /etc/passwd) */ | |
| if (DO_POSIX_CP | |
| - | || (dest_exists && !(flags & (FILEUTILS_RECUR|FILEUTILS_INTERACTIVE)) |
| + | || (dest_exists |
| + | && !(flags & (FILEUTILS_RECUR|FILEUTILS_INTERACTIVE)) |
| && !S_ISLNK(dest_stat.st_mode)) | |
| ) { | |
| - | dst_fd = open(dest, O_WRONLY|O_CREAT|O_TRUNC, source_stat.st_mode); |
| + | dst_fd = open(dest, O_WRONLY|O_CREAT|O_TRUNC, new_mode); |
| } else /* safe way: */ | |
| - | dst_fd = open(dest, O_WRONLY|O_CREAT|O_EXCL, source_stat.st_mode); |
| + | dst_fd = open(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode); |
| if (dst_fd == -1) { | |
| ovr = ask_and_unlink(dest, flags); | |
| if (ovr <= 0) { | |
| @@ -277,7 +294,7 @@ int copy_file(const char *source, const char *dest, int flags) | |
| return ovr; | |
| } | |
| /* It shouldn't exist. If it exists, do not open (symlink attack?) */ | |
| - | dst_fd = open3_or_warn(dest, O_WRONLY|O_CREAT|O_EXCL, source_stat.st_mode); |
| + | dst_fd = open3_or_warn(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode); |
| if (dst_fd < 0) { | |
| close(src_fd); | |
| return -1; | |
| @@ -285,8 +302,7 @@ int copy_file(const char *source, const char *dest, int flags) | |
| } | |
| #if ENABLE_SELINUX | |
| - | if (((flags & FILEUTILS_PRESERVE_SECURITY_CONTEXT) |
| - | || (flags & FILEUTILS_SET_SECURITY_CONTEXT)) |
| + | if ((flags & (FILEUTILS_PRESERVE_SECURITY_CONTEXT|FILEUTILS_SET_SECURITY_CONTEXT)) |
| && is_selinux_enabled() > 0 | |
| ) { | |
| security_context_t con; | |
| @@ -313,8 +329,13 @@ int copy_file(const char *source, const char *dest, int flags) | |
| } | |
| /* ...but read size is already checked by bb_copyfd_eof */ | |
| close(src_fd); | |
| + | /* "cp /dev/something new_file" should not |
| + | * copy mode of /dev/something */ |
| + | if (!S_ISREG(source_stat.st_mode)) |
| + | return retval; |
| goto preserve_mode_ugid_time; | |
| } | |
| + | dont_cat: |
| /* Source is a symlink or a special file */ | |
| /* We are lazy here, a bit lax with races... */ | |
| @@ -2,7 +2,8 @@ echo file number one > file1 | |
| echo file number two > file2 | |
| ln -s file2 link1 | |
| mkdir dir1 | |
| - | touch --date='Sat Jan 29 21:24:08 PST 2000' dir1/file3 |
| + | # why??? |
| + | #touch --date='Sat Jan 29 21:24:08 PST 2000' dir1/file3 |
| mkdir there | |
| busybox cp -a file1 file2 link1 dir1 there | |
| test -f there/file1 | |
| @@ -0,0 +1,2 @@ | |
| + | busybox cp /dev/null foo |
| + | test -f foo |
| @@ -1,6 +1,11 @@ | |
| touch foo | |
| chmod a-r foo | |
| set +e | |
| - | busybox cp foo bar |
| + | if test `id -u` = 0; then |
| + | # run as user with nonzero uid |
| + | setuidgid 1 busybox cp foo bar |
| + | else |
| + | busybox cp foo bar |
| + | fi |
| set -e | |
| test ! -f bar | |