/* Return: * -1 error, copy not made * 0 copy is made or user answered "no" in interactive mode * (failures to preserve mode/owner/times are not reported in exit code) */ int FAST_FUNC copy_file(const char *source, const char *dest, int flags) { /* This is a recursive function, try to minimize stack usage */ /* NB: each struct stat is ~100 bytes */ struct stat source_stat; struct stat dest_stat; smallint retval = 0; smallint dest_exists = 0; smallint ovr; /* Inverse of cp -d ("cp without -d") */ #define FLAGS_DEREF (flags & (FILEUTILS_DEREFERENCE + FILEUTILS_DEREFERENCE_L0)) 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("can't stat '%s'", source); return -1; } if (lstat(dest, &dest_stat) < 0) { if (errno != ENOENT) { bb_perror_msg("can't 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 ENABLE_SELINUX if ((flags & FILEUTILS_PRESERVE_SECURITY_CONTEXT) && is_selinux_enabled() > 0) { security_context_t con; if (lgetfilecon(source, &con) >= 0) { if (setfscreatecon(con) < 0) { bb_perror_msg("can't set setfscreatecon %s", con); freecon(con); return -1; } } else if (errno == ENOTSUP || errno == ENODATA) { setfscreatecon_or_die(NULL); } else { bb_perror_msg("can't lgetfilecon %s", source); return -1; } } #endif if (S_ISDIR(source_stat.st_mode)) { DIR *dp; const char *tp; struct dirent *d; mode_t saved_umask = 0; if (!(flags & FILEUTILS_RECUR)) { bb_error_msg("omitting directory '%s'", source); return -1; } /* Did we ever create source ourself before? */ tp = is_in_ino_dev_hashtable(&source_stat); if (tp) { /* We did! it's a recursion! man the lifeboats... */ bb_error_msg("recursion detected, omitting directory '%s'", source); return -1; } if (dest_exists) { if (!S_ISDIR(dest_stat.st_mode)) { bb_error_msg("target '%s' is not a directory", dest); return -1; } /* race here: user can substitute a symlink between * this check and actual creation of files inside dest */ } else { /* Create DEST */ mode_t mode; saved_umask = umask(0); mode = source_stat.st_mode; if (!(flags & FILEUTILS_PRESERVE_STATUS)) mode = source_stat.st_mode & ~saved_umask; /* Allow owner to access new dir (at least for now) */ mode |= S_IRWXU; if (mkdir(dest, mode) < 0) { umask(saved_umask); bb_perror_msg("can't create directory '%s'", dest); return -1; } umask(saved_umask); /* need stat info for add_to_ino_dev_hashtable */ if (lstat(dest, &dest_stat) < 0) { bb_perror_msg("can't stat '%s'", dest); return -1; } } /* remember (dev,inode) of each created dir. * NULL: name is not remembered */ add_to_ino_dev_hashtable(&dest_stat, NULL); /* Recursively copy files in SOURCE */ dp = opendir(source); if (dp == NULL) { retval = -1; goto preserve_mode_ugid_time; } 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 & ~FILEUTILS_DEREFERENCE_L0) < 0) retval = -1; free(new_source); free(new_dest); } closedir(dp); if (!dest_exists && chmod(dest, source_stat.st_mode & ~saved_umask) < 0 ) { bb_perror_msg("can't preserve %s of '%s'", "permissions", dest); /* retval = -1; - WRONG! copy *WAS* made */ } goto preserve_mode_ugid_time; } 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 = ask_and_unlink(dest, flags); if (ovr <= 0) return ovr; if (lf(source, dest) < 0) { bb_perror_msg("can't create link '%s'", dest); return -1; } } /* _Not_ jumping to preserve_mode_ugid_time: * (sym)links don't have those */ return 0; } 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; link_target = is_in_ino_dev_hashtable(&source_stat); if (link_target) { if (link(link_target, dest) < 0) { ovr = ask_and_unlink(dest, flags); if (ovr <= 0) return ovr; if (link(link_target, dest) < 0) { bb_perror_msg("can't create link '%s'", dest); return -1; } } return 0; } add_to_ino_dev_hashtable(&source_stat, dest); } src_fd = open_or_warn(source, O_RDONLY); 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 (sym)link attacks if (!ENABLE_FEATURE_NON_POSIX_CP) { 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, new_mode); } if (dst_fd == -1) { ovr = ask_and_unlink(dest, flags); if (ovr <= 0) { close(src_fd); 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, new_mode); if (dst_fd < 0) { close(src_fd); return -1; } } #if ENABLE_SELINUX if ((flags & (FILEUTILS_PRESERVE_SECURITY_CONTEXT|FILEUTILS_SET_SECURITY_CONTEXT)) && is_selinux_enabled() > 0 ) { security_context_t con; if (getfscreatecon(&con) == -1) { bb_perror_msg("getfscreatecon"); return -1; } if (con) { if (setfilecon(dest, con) == -1) { bb_perror_msg("setfilecon:%s,%s", dest, con); freecon(con); return -1; } freecon(con); } } #endif if (bb_copyfd_eof(src_fd, dst_fd) == -1) retval = -1; /* Careful with writing... */ if (close(dst_fd) < 0) { bb_perror_msg("error writing to '%s'", dest); retval = -1; } /* ...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... */ if (dest_exists) { errno = EEXIST; ovr = ask_and_unlink(dest, flags); if (ovr <= 0) return ovr; } if (S_ISLNK(source_stat.st_mode)) { char *lpath = xmalloc_readlink_or_warn(source); if (lpath) { int r = symlink(lpath, dest); free(lpath); if (r < 0) { bb_perror_msg("can't create symlink '%s'", dest); return -1; } if (flags & FILEUTILS_PRESERVE_STATUS) if (lchown(dest, source_stat.st_uid, source_stat.st_gid) < 0) bb_perror_msg("can't preserve %s of '%s'", "ownership", dest); } /* _Not_ jumping to preserve_mode_ugid_time: * symlinks don't have those */ return 0; } 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) ) { if (mknod(dest, source_stat.st_mode, source_stat.st_rdev) < 0) { bb_perror_msg("can't create '%s'", dest); return -1; } } else { bb_error_msg("unrecognized file '%s' with mode %x", source, source_stat.st_mode); return -1; } preserve_mode_ugid_time: if (flags & FILEUTILS_PRESERVE_STATUS /* Cannot happen: */ /* && !(flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) */ ) { struct timeval times[2]; times[1].tv_sec = times[0].tv_sec = source_stat.st_mtime; times[1].tv_usec = times[0].tv_usec = 0; /* BTW, utimes sets usec-precision time - just FYI */ if (utimes(dest, times) < 0) bb_perror_msg("can't 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("can't preserve %s of '%s'", "ownership", dest); } #if ENABLE_XATTR /* Preserve extended attributes. We must copy it after chown() * because it resets capabilities. */ if (copy_file_attr(source, dest) == -1) bb_perror_msg("can't preserve %s of '%s'", "extended attributes", dest); #endif if (chmod(dest, source_stat.st_mode) < 0) bb_perror_msg("can't preserve %s of '%s'", "permissions", dest); } return retval; }
static int writeTarHeader(struct TarBallInfo *tbInfo, const char *header_name, const char *fileName, struct stat *statbuf) { struct TarHeader header; if (sizeof(header) != 512) BUG_tar_header_size(); memset(&header, 0, sizeof(struct TarHeader)); strncpy(header.name, header_name, sizeof(header.name)); /* POSIX says to mask mode with 07777. */ PUT_OCTAL(header.mode, statbuf->st_mode & 07777); PUT_OCTAL(header.uid, statbuf->st_uid); PUT_OCTAL(header.gid, statbuf->st_gid); memset(header.size, '0', sizeof(header.size)-1); /* Regular file size is handled later */ PUT_OCTAL(header.mtime, statbuf->st_mtime); /* Enter the user and group names */ safe_strncpy(header.uname, get_cached_username(statbuf->st_uid), sizeof(header.uname)); safe_strncpy(header.gname, get_cached_groupname(statbuf->st_gid), sizeof(header.gname)); if (tbInfo->hlInfo) { /* This is a hard link */ header.typeflag = LNKTYPE; strncpy(header.linkname, tbInfo->hlInfo->name, sizeof(header.linkname)); #if ENABLE_FEATURE_TAR_GNU_EXTENSIONS /* Write out long linkname if needed */ if (header.linkname[sizeof(header.linkname)-1]) writeLongname(tbInfo->tarFd, GNULONGLINK, tbInfo->hlInfo->name, 0); #endif } else if (S_ISLNK(statbuf->st_mode)) { char *lpath = xmalloc_readlink_or_warn(fileName); if (!lpath) return FALSE; header.typeflag = SYMTYPE; strncpy(header.linkname, lpath, sizeof(header.linkname)); #if ENABLE_FEATURE_TAR_GNU_EXTENSIONS /* Write out long linkname if needed */ if (header.linkname[sizeof(header.linkname)-1]) writeLongname(tbInfo->tarFd, GNULONGLINK, lpath, 0); #else /* If it is larger than 100 bytes, bail out */ if (header.linkname[sizeof(header.linkname)-1]) { free(lpath); bb_error_msg("names longer than "NAME_SIZE_STR" chars not supported"); return FALSE; } #endif free(lpath); } else if (S_ISDIR(statbuf->st_mode)) { header.typeflag = DIRTYPE; /* Append '/' only if there is a space for it */ if (!header.name[sizeof(header.name)-1]) header.name[strlen(header.name)] = '/'; } else if (S_ISCHR(statbuf->st_mode)) { header.typeflag = CHRTYPE; PUT_OCTAL(header.devmajor, major(statbuf->st_rdev)); PUT_OCTAL(header.devminor, minor(statbuf->st_rdev)); } else if (S_ISBLK(statbuf->st_mode)) { header.typeflag = BLKTYPE; PUT_OCTAL(header.devmajor, major(statbuf->st_rdev)); PUT_OCTAL(header.devminor, minor(statbuf->st_rdev)); } else if (S_ISFIFO(statbuf->st_mode)) { header.typeflag = FIFOTYPE; } else if (S_ISREG(statbuf->st_mode)) { if (sizeof(statbuf->st_size) > 4 && statbuf->st_size > (off_t)0777777777777LL ) { bb_error_msg_and_die("cannot store file '%s' " "of size %"OFF_FMT"d, aborting", fileName, statbuf->st_size); } header.typeflag = REGTYPE; PUT_OCTAL(header.size, statbuf->st_size); } else { bb_error_msg("%s: unknown file type", fileName); return FALSE; } #if ENABLE_FEATURE_TAR_GNU_EXTENSIONS /* Write out long name if needed */ /* (we, like GNU tar, output long linkname *before* long name) */ if (header.name[sizeof(header.name)-1]) writeLongname(tbInfo->tarFd, GNULONGNAME, header_name, S_ISDIR(statbuf->st_mode)); #endif /* Now write the header out to disk */ chksum_and_xwrite(tbInfo->tarFd, &header); /* Now do the verbose thing (or not) */ if (tbInfo->verboseFlag) { FILE *vbFd = stdout; /* If archive goes to stdout, verbose goes to stderr */ if (tbInfo->tarFd == STDOUT_FILENO) vbFd = stderr; /* GNU "tar cvvf" prints "extended" listing a-la "ls -l" */ /* We don't have such excesses here: for us "v" == "vv" */ /* '/' is probably a GNUism */ fprintf(vbFd, "%s%s\n", header_name, S_ISDIR(statbuf->st_mode) ? "/" : ""); } return TRUE; }
/* Write out a tar header for the specified file/directory/whatever */ static int writeTarHeader(struct TarBallInfo *tbInfo, const char *header_name, const char *fileName, struct stat *statbuf) { struct tar_header_t header; memset(&header, 0, sizeof(header)); strncpy(header.name, header_name, sizeof(header.name)); /* POSIX says to mask mode with 07777. */ PUT_OCTAL(header.mode, statbuf->st_mode & 07777); PUT_OCTAL(header.uid, statbuf->st_uid); PUT_OCTAL(header.gid, statbuf->st_gid); memset(header.size, '0', sizeof(header.size)-1); /* Regular file size is handled later */ /* users report that files with negative st_mtime cause trouble, so: */ PUT_OCTAL(header.mtime, statbuf->st_mtime >= 0 ? statbuf->st_mtime : 0); /* Enter the user and group names */ safe_strncpy(header.uname, get_cached_username(statbuf->st_uid), sizeof(header.uname)); safe_strncpy(header.gname, get_cached_groupname(statbuf->st_gid), sizeof(header.gname)); if (tbInfo->hlInfo) { /* This is a hard link */ header.typeflag = LNKTYPE; strncpy(header.linkname, tbInfo->hlInfo->name, sizeof(header.linkname)); #if ENABLE_FEATURE_TAR_GNU_EXTENSIONS /* Write out long linkname if needed */ if (header.linkname[sizeof(header.linkname)-1]) writeLongname(tbInfo->tarFd, GNULONGLINK, tbInfo->hlInfo->name, 0); #endif } else if (S_ISLNK(statbuf->st_mode)) { char *lpath = xmalloc_readlink_or_warn(fileName); if (!lpath) return FALSE; header.typeflag = SYMTYPE; strncpy(header.linkname, lpath, sizeof(header.linkname)); #if ENABLE_FEATURE_TAR_GNU_EXTENSIONS /* Write out long linkname if needed */ if (header.linkname[sizeof(header.linkname)-1]) writeLongname(tbInfo->tarFd, GNULONGLINK, lpath, 0); #else /* If it is larger than 100 bytes, bail out */ if (header.linkname[sizeof(header.linkname)-1]) { free(lpath); bb_error_msg("names longer than "NAME_SIZE_STR" chars not supported"); return FALSE; } #endif free(lpath); } else if (S_ISDIR(statbuf->st_mode)) { header.typeflag = DIRTYPE; /* Append '/' only if there is a space for it */ if (!header.name[sizeof(header.name)-1]) header.name[strlen(header.name)] = '/'; } else if (S_ISCHR(statbuf->st_mode)) { header.typeflag = CHRTYPE; PUT_OCTAL(header.devmajor, major(statbuf->st_rdev)); PUT_OCTAL(header.devminor, minor(statbuf->st_rdev)); } else if (S_ISBLK(statbuf->st_mode)) { header.typeflag = BLKTYPE; PUT_OCTAL(header.devmajor, major(statbuf->st_rdev)); PUT_OCTAL(header.devminor, minor(statbuf->st_rdev)); } else if (S_ISFIFO(statbuf->st_mode)) { header.typeflag = FIFOTYPE; } else if (S_ISREG(statbuf->st_mode)) { /* header.size field is 12 bytes long */ /* Does octal-encoded size fit? */ uoff_t filesize = statbuf->st_size; if (sizeof(filesize) <= 4 || filesize <= (uoff_t)0777777777777LL ) { PUT_OCTAL(header.size, filesize); } /* Does base256-encoded size fit? * It always does unless off_t is wider than 64 bits. */ else if (ENABLE_FEATURE_TAR_GNU_EXTENSIONS #if ULLONG_MAX > 0xffffffffffffffffLL /* 2^64-1 */ && (filesize <= 0x3fffffffffffffffffffffffLL) #endif ) { /* GNU tar uses "base-256 encoding" for very large numbers. * Encoding is binary, with highest bit always set as a marker * and sign in next-highest bit: * 80 00 .. 00 - zero * bf ff .. ff - largest positive number * ff ff .. ff - minus 1 * c0 00 .. 00 - smallest negative number */ char *p8 = header.size + sizeof(header.size); do { *--p8 = (uint8_t)filesize; filesize >>= 8; } while (p8 != header.size); *p8 |= 0x80; } else {
/* Return value will become exit code. * It's ok to exit instead of return. */ static NOINLINE int cpio_o(void) { static const char trailer[] ALIGN1 = "TRAILER!!!"; struct name_s { struct name_s *next; char name[1]; }; struct inodes_s { struct inodes_s *next; struct name_s *names; struct stat st; }; struct inodes_s *links = NULL; off_t bytes = 0; /* output bytes count */ while (1) { const char *name; char *line; struct stat st; line = (option_mask32 & CPIO_OPT_NUL_TERMINATED) ? bb_get_chunk_from_file(stdin, NULL) : xmalloc_fgetline(stdin); if (line) { /* Strip leading "./[./]..." from the filename */ name = line; while (name[0] == '.' && name[1] == '/') { while (*++name == '/') continue; } if (!*name) { /* line is empty */ free(line); continue; } if ((option_mask32 & CPIO_OPT_DEREF) ? stat(name, &st) : lstat(name, &st) ) { abort_cpio_o: bb_simple_perror_msg_and_die(name); } if (!(S_ISLNK(st.st_mode) || S_ISREG(st.st_mode))) st.st_size = 0; /* paranoia */ /* Store hardlinks for later processing, dont output them */ if (!S_ISDIR(st.st_mode) && st.st_nlink > 1) { struct name_s *n; struct inodes_s *l; /* Do we have this hardlink remembered? */ l = links; while (1) { if (l == NULL) { /* Not found: add new item to "links" list */ l = xzalloc(sizeof(*l)); l->st = st; l->next = links; links = l; break; } if (l->st.st_ino == st.st_ino) { /* found */ break; } l = l->next; } /* Add new name to "l->names" list */ n = xmalloc(sizeof(*n) + strlen(name)); strcpy(n->name, name); n->next = l->names; l->names = n; free(line); continue; } } else { /* line == NULL: EOF */ next_link: if (links) { /* Output hardlink's data */ st = links->st; name = links->names->name; links->names = links->names->next; /* GNU cpio is reported to emit file data * only for the last instance. Mimic that. */ if (links->names == NULL) links = links->next; else st.st_size = 0; /* NB: we leak links->names and/or links, * this is intended (we exit soon anyway) */ } else { /* If no (more) hardlinks to output, * output "trailer" entry */ name = trailer; /* st.st_size == 0 is a must, but for uniformity * in the output, we zero out everything */ memset(&st, 0, sizeof(st)); /* st.st_nlink = 1; - GNU cpio does this */ } } bytes += printf("070701" "%08X%08X%08X%08X%08X%08X%08X" "%08X%08X%08X%08X" /* GNU cpio uses uppercase hex */ /* strlen+1: */ "%08X" /* chksum: */ "00000000" /* (only for "070702" files) */ /* name,NUL: */ "%s%c", (unsigned)(uint32_t) st.st_ino, (unsigned)(uint32_t) st.st_mode, (unsigned)(uint32_t) st.st_uid, (unsigned)(uint32_t) st.st_gid, (unsigned)(uint32_t) st.st_nlink, (unsigned)(uint32_t) st.st_mtime, (unsigned)(uint32_t) st.st_size, (unsigned)(uint32_t) major(st.st_dev), (unsigned)(uint32_t) minor(st.st_dev), (unsigned)(uint32_t) major(st.st_rdev), (unsigned)(uint32_t) minor(st.st_rdev), (unsigned)(strlen(name) + 1), name, '\0'); bytes = cpio_pad4(bytes); if (st.st_size) { if (S_ISLNK(st.st_mode)) { char *lpath = xmalloc_readlink_or_warn(name); if (!lpath) goto abort_cpio_o; bytes += printf("%s", lpath); free(lpath); } else { /* S_ISREG */ int fd = xopen(name, O_RDONLY); fflush_all(); /* We must abort if file got shorter too! */ bb_copyfd_exact_size(fd, STDOUT_FILENO, st.st_size); bytes += st.st_size; close(fd); } bytes = cpio_pad4(bytes); } if (!line) { if (name != trailer) goto next_link; /* TODO: GNU cpio pads trailer to 512 bytes, do we want that? */ return EXIT_SUCCESS; } free(line); } /* end of "while (1)" */ }