blob: e25dad468c1fba866fb452029da2e002af7e5c9d [file] [log] [blame]
/* Copyright 2008 Rob Landley <rob@landley.net>
*
* See http://opengroup.org/onlinepubs/9699919799/utilities/cp.html
*
* TODO: "R+ra+d+p+r" sHLPR
USE_CP(NEWTOY(cp, "<2"USE_CP_MORE("rdavsl")"RHLPfip", TOYFLAG_BIN))
config CP
bool "cp (broken by dirtree changes)"
default n
help
usage: cp [-fipRHLP] SOURCE... DEST
Copy files from SOURCE to DEST. If more than one SOURCE, DEST must
be a directory.
-f force copy by deleting destination file
-i interactive, prompt before overwriting existing DEST
-p preserve timestamps, ownership, and permissions
-R recurse into subdirectories (DEST must be a directory)
-H Follow symlinks listed on command line
-L Follow all symlinks
-P Do not follow symlinks [default]
config CP_MORE
bool "cp -rdavsl options"
default y
depends on CP
help
usage: cp [-rdavsl]
-r synonym for -R
-d don't dereference symlinks
-a same as -dpr
-l hard link instead of copy
-s symlink instead of copy
-v verbose
*/
#define FOR_cp
#include "toys.h"
// TODO: PLHlsd
GLOBALS(
char *destname;
int destisdir;
int keep_symlinks;
)
// Copy an individual file or directory to target.
void cp_file(char *src, char *dst, struct stat *srcst)
{
int fdout = -1;
// -i flag is specified and dst file exists.
if ((toys.optflags&FLAG_i) && !access(dst, R_OK)
&& !yesno("cp: overwrite", 1))
return;
if (toys.optflags & FLAG_v) printf("'%s' -> '%s'\n", src, dst);
// Copy directory or file to destination.
if (S_ISDIR(srcst->st_mode)) {
struct stat st2;
// Always make directory writeable to us, so we can create files in it.
//
// Yes, there's a race window between mkdir() and open() so it's
// possible that -p can be made to chown a directory other than the one
// we created. The closest we can do to closing this is make sure
// that what we open _is_ a directory rather than something else.
if ((mkdir(dst, srcst->st_mode | 0200) && errno != EEXIST)
|| 0>(fdout=open(dst, 0)) || fstat(fdout, &st2) || !S_ISDIR(st2.st_mode))
{
perror_exit("mkdir '%s'", dst);
}
} else if (TT.keep_symlinks && S_ISLNK(srcst->st_mode)) {
char *link = xreadlink(src);
// Note: -p currently has no effect on symlinks. How do you get a
// filehandle to them? O_NOFOLLOW causes the open to fail.
if (!link || symlink(link, dst)) perror_msg("link '%s'", dst);
free(link);
return;
} else if (toys.optflags & FLAG_l) {
if (link(src, dst)) perror_msg("link '%s'");
return;
} else {
int fdin, i;
fdin = xopen(src, O_RDONLY);
for (i=2 ; i; i--) {
fdout = open(dst, O_RDWR|O_CREAT|O_TRUNC, srcst->st_mode);
if (fdout>=0 || !(toys.optflags & FLAG_f)) break;
unlink(dst);
}
if (fdout<0) perror_exit("%s", dst);
xsendfile(fdin, fdout);
close(fdin);
}
// Inability to set these isn't fatal, some require root access.
// Can't do fchmod() etc here because -p works on mkdir, too.
if (toys.optflags & (FLAG_p|FLAG_a)) {
int mask = umask(0);
struct utimbuf ut;
(void) fchown(fdout,srcst->st_uid, srcst->st_gid);
ut.actime = srcst->st_atime;
ut.modtime = srcst->st_mtime;
utime(dst, &ut);
umask(mask);
}
xclose(fdout);
}
// Callback from dirtree_read() for each file/directory under a source dir.
int cp_node(struct dirtree *node)
{
char *path = dirtree_path(node, 0); // TODO: use openat() instead
char *s = path+strlen(path);
struct dirtree *n;
// Find appropriate chunk of path for destination.
n = node;
if (!TT.destisdir) n = n->parent;
for (;;n = n->parent) {
while (s!=path) if (*(--s)=='/') break;
if (!n) break;
}
if (s != path) s++;
s = xmsprintf("%s/%s", TT.destname, s);
cp_file(path, s, &(node->st));
free(s);
free(path); // redo this whole darn function.
return 0;
}
void cp_main(void)
{
char *dpath = NULL;
struct stat st, std;
int i;
// Identify destination
if (!stat(TT.destname, &std) && S_ISDIR(std.st_mode)) TT.destisdir++;
else if (toys.optc>1) error_exit("'%s' not directory", TT.destname);
// TODO: This is too early: we haven't created it yet if we need to
if (toys.optflags & (FLAG_R|FLAG_r|FLAG_a))
dpath = realpath(TT.destname = toys.optargs[--toys.optc], NULL);
// Loop through sources
for (i=0; i<toys.optc; i++) {
char *dst, *src = toys.optargs[i];
// Skip src==dest (TODO check inodes to catch "cp blah ./blah").
if (!strncmp(src, TT.destname)) continue;
// Skip nonexistent sources.
TT.keep_symlinks = toys.optflags & (FLAG_d|FLAG_a);
if (TT.keep_symlinks ? lstat(src, &st) : stat(src, &st)
|| (st.st_dev = dst.st_dev && st.st_ino == dst.dst_ino))
{
objection:
perror_msg("bad '%s'", src);
toys.exitval = 1;
continue;
}
// Copy directory or file.
if (TT.destisdir) {
char *s;
// Catch "cp -R .. ." and friends that would go on forever
if (dpath && (s = realpath(src, NULL)) {
int i = strlen(s);
i = (!strncmp(s, dst, i) && (!s[i] || s[i]=='/'));
free(s);
if (i) goto objection;
}
// Create destination filename within directory
dst = strrchr(src, '/');
if (dst) dst++;
else dst=src;
dst = xmsprintf("%s/%s", TT.destname, dst);
} else dst = TT.destname;
if (S_ISDIR(st.st_mode)) {
if (toys.optflags & (FLAG_r|FLAG_R|FLAG_a)) {
cp_file(src, dst, &st);
TT.keep_symlinks++;
dirtree_read(src, cp_node);
} else error_msg("Skipped dir '%s'", src);
} else cp_file(src, dst, &st);
if (TT.destisdir) free(dst);
}
if (CFG_TOYBOX_FREE) free(dpath);
return;
}