/* vi: set sw=4 ts=4: */ /* * Mini copy_file implementation for busybox * * Copyright (C) 2001 by Matt Kraai * * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. * */ #include "libbb.h" static int retry_overwrite(const char *dest, int flags) { if (!(flags & (FILEUTILS_FORCE|FILEUTILS_INTERACTIVE))) { fprintf(stderr, "'%s' exists\n", dest); return -1; } if (flags & FILEUTILS_INTERACTIVE) { fprintf(stderr, "%s: overwrite '%s'? ", applet_name, dest); if (!bb_ask_confirmation()) return 0; // not allowed to overwrite } if (unlink(dest) < 0) { bb_perror_msg("cannot remove '%s'", dest); return -1; // error } return 1; // ok (to try again) } int copy_file(const char *source, const char *dest, int flags) { struct stat source_stat; struct stat dest_stat; int status = 0; signed char dest_exists = 0; signed char ovr; #define FLAGS_DEREF (flags & FILEUTILS_DEREFERENCE) if ((FLAGS_DEREF ? stat : lstat)(source, &source_stat) < 0) { // This may be a dangling symlink. // Making [sym]links to dangling symlinks works, so... if (flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) goto make_links; bb_perror_msg("cannot stat '%s'", source); return -1; } if (lstat(dest, &dest_stat) < 0) { if (errno != ENOENT) { bb_perror_msg("cannot stat '%s'", dest); return -1; } } else { if (source_stat.st_dev == dest_stat.st_dev && source_stat.st_ino == dest_stat.st_ino ) { bb_error_msg("'%s' and '%s' are the same file", source, dest); return -1; } dest_exists = 1; } if (S_ISDIR(source_stat.st_mode)) { DIR *dp; struct dirent *d; mode_t saved_umask = 0; if (!(flags & FILEUTILS_RECUR)) { bb_error_msg("omitting directory '%s'", source); return -1; } /* Create DEST. */ if (dest_exists) { if (!S_ISDIR(dest_stat.st_mode)) { bb_error_msg("target '%s' is not a directory", dest); return -1; } } else { mode_t mode; saved_umask = umask(0); mode = source_stat.st_mode; if (!(flags & FILEUTILS_PRESERVE_STATUS)) mode = source_stat.st_mode & ~saved_umask; mode |= S_IRWXU; if (mkdir(dest, mode) < 0) { umask(saved_umask); bb_perror_msg("cannot create directory '%s'", dest); return -1; } umask(saved_umask); } /* Recursively copy files in SOURCE. */ dp = opendir(source); if (dp == NULL) { status = -1; goto preserve_status; } while ((d = readdir(dp)) != NULL) { char *new_source, *new_dest; new_source = concat_subpath_file(source, d->d_name); if (new_source == NULL) continue; new_dest = concat_path_file(dest, d->d_name); if (copy_file(new_source, new_dest, flags) < 0) status = -1; free(new_source); free(new_dest); } closedir(dp); if (!dest_exists && chmod(dest, source_stat.st_mode & ~saved_umask) < 0 ) { bb_perror_msg("cannot change permissions of '%s'", dest); status = -1; } } else if (flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) { int (*lf)(const char *oldpath, const char *newpath); make_links: // Hmm... maybe // if (DEREF && MAKE_SOFTLINK) source = realpath(source) ? // (but realpath returns NULL on dangling symlinks...) lf = (flags & FILEUTILS_MAKE_SOFTLINK) ? symlink : link; if (lf(source, dest) < 0) { ovr = retry_overwrite(dest, flags); if (ovr <= 0) return ovr; if (lf(source, dest) < 0) { bb_perror_msg("cannot create link '%s'", dest); return -1; } } return 0; } else if (S_ISREG(source_stat.st_mode) // Huh? DEREF uses stat, which never returns links IIRC... || (FLAGS_DEREF && S_ISLNK(source_stat.st_mode)) ) { int src_fd; int dst_fd; if (ENABLE_FEATURE_PRESERVE_HARDLINKS) { char *link_name; if (!FLAGS_DEREF && is_in_ino_dev_hashtable(&source_stat, &link_name) ) { if (link(link_name, dest) < 0) { ovr = retry_overwrite(dest, flags); if (ovr <= 0) return ovr; if (link(link_name, dest) < 0) { bb_perror_msg("cannot create link '%s'", dest); return -1; } } return 0; } // TODO: probably is_in_.. and add_to_... // can be combined: find_or_add_... add_to_ino_dev_hashtable(&source_stat, dest); } src_fd = open(source, O_RDONLY); if (src_fd == -1) { bb_perror_msg("cannot open '%s'", source); return -1; } // POSIX: if exists and -i, ask (w/o -i assume yes). // Then open w/o EXCL. // If open still fails and -f, try unlink, then try open again. // Result: a mess: // If dest is a softlink, we overwrite softlink's destination! // (or fail, if it points to dir/nonexistent location/etc). // This is strange, but POSIX-correct. // coreutils cp has --remove-destination to override this... dst_fd = open(dest, (flags & FILEUTILS_INTERACTIVE) ? O_WRONLY|O_CREAT|O_TRUNC|O_EXCL : O_WRONLY|O_CREAT|O_TRUNC, source_stat.st_mode); if (dst_fd == -1) { // We would not do POSIX insanity. -i asks, // then _unlinks_ the offender. Presto. // Or else we will end up having 3 open()s! ovr = retry_overwrite(dest, flags); if (ovr <= 0) { close(src_fd); return ovr; } dst_fd = open(dest, O_WRONLY|O_CREAT|O_TRUNC, source_stat.st_mode); if (dst_fd == -1) { bb_perror_msg("cannot open '%s'", dest); close(src_fd); return -1; } } if (bb_copyfd_eof(src_fd, dst_fd) == -1) status = -1; if (close(dst_fd) < 0) { bb_perror_msg("cannot close '%s'", dest); status = -1; } if (close(src_fd) < 0) { bb_perror_msg("cannot close '%s'", source); status = -1; } } else if (S_ISBLK(source_stat.st_mode) || S_ISCHR(source_stat.st_mode) || S_ISSOCK(source_stat.st_mode) || S_ISFIFO(source_stat.st_mode) || S_ISLNK(source_stat.st_mode) ) { // We are lazy here, a bit lax with races... if (dest_exists) { ovr = retry_overwrite(dest, flags); if (ovr <= 0) return ovr; } if (S_ISFIFO(source_stat.st_mode)) { if (mkfifo(dest, source_stat.st_mode) < 0) { bb_perror_msg("cannot create fifo '%s'", dest); return -1; } } else if (S_ISLNK(source_stat.st_mode)) { char *lpath; lpath = xreadlink(source); if (symlink(lpath, dest) < 0) { bb_perror_msg("cannot create symlink '%s'", dest); free(lpath); return -1; } free(lpath); if (flags & FILEUTILS_PRESERVE_STATUS) if (lchown(dest, source_stat.st_uid, source_stat.st_gid) < 0) bb_perror_msg("cannot preserve %s of '%s'", "ownership", dest); return 0; } else { if (mknod(dest, source_stat.st_mode, source_stat.st_rdev) < 0) { bb_perror_msg("cannot create '%s'", dest); return -1; } } } else { bb_error_msg("internal error: unrecognized file type"); return -1; } preserve_status: if (flags & FILEUTILS_PRESERVE_STATUS /* Cannot happen: */ /* && !(flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) */ ) { struct utimbuf times; times.actime = source_stat.st_atime; times.modtime = source_stat.st_mtime; if (utime(dest, ×) < 0) bb_perror_msg("cannot preserve %s of '%s'", "times", dest); if (chown(dest, source_stat.st_uid, source_stat.st_gid) < 0) { source_stat.st_mode &= ~(S_ISUID | S_ISGID); bb_perror_msg("cannot preserve %s of '%s'", "ownership", dest); } if (chmod(dest, source_stat.st_mode) < 0) bb_perror_msg("cannot preserve %s of '%s'", "permissions", dest); } return status; }