Changeset View
Changeset View
Standalone View
Standalone View
bin/cp/cp.c
Show First 20 Lines • Show All 58 Lines • ▼ Show 20 Lines | |||||
* 'cp file1 file2 ... fileN dir' where the hierarchy is traversed and the | * 'cp file1 file2 ... fileN dir' where the hierarchy is traversed and the | ||||
* path (relative to the root of the traversal) is appended to dir (stored | * path (relative to the root of the traversal) is appended to dir (stored | ||||
* in "to") to form the final target path. | * in "to") to form the final target path. | ||||
*/ | */ | ||||
#include <sys/types.h> | #include <sys/types.h> | ||||
#include <sys/stat.h> | #include <sys/stat.h> | ||||
#include <assert.h> | |||||
#include <err.h> | #include <err.h> | ||||
#include <errno.h> | #include <errno.h> | ||||
#include <fts.h> | #include <fts.h> | ||||
#include <limits.h> | #include <limits.h> | ||||
#include <signal.h> | #include <signal.h> | ||||
#include <stdio.h> | #include <stdio.h> | ||||
#include <stdlib.h> | #include <stdlib.h> | ||||
#include <string.h> | #include <string.h> | ||||
Show All 11 Lines | |||||
PATH_T to = { to.p_path, emptystring, "" }; | PATH_T to = { to.p_path, emptystring, "" }; | ||||
int fflag, iflag, lflag, nflag, pflag, sflag, vflag; | int fflag, iflag, lflag, nflag, pflag, sflag, vflag; | ||||
static int Rflag, rflag; | static int Rflag, rflag; | ||||
volatile sig_atomic_t info; | volatile sig_atomic_t info; | ||||
enum op { FILE_TO_FILE, FILE_TO_DIR, DIR_TO_DNE }; | enum op { FILE_TO_FILE, FILE_TO_DIR, DIR_TO_DNE }; | ||||
static int copy(char *[], enum op, int); | static int copy(char *[], enum op, int, struct stat *); | ||||
static void siginfo(int __unused); | static void siginfo(int __unused); | ||||
int | int | ||||
main(int argc, char *argv[]) | main(int argc, char *argv[]) | ||||
{ | { | ||||
struct stat to_stat, tmp_stat; | struct stat to_stat, tmp_stat; | ||||
enum op type; | enum op type; | ||||
int Hflag, Lflag, ch, fts_options, r, have_trailing_slash; | int Hflag, Lflag, ch, fts_options, r, have_trailing_slash; | ||||
▲ Show 20 Lines • Show All 151 Lines • ▼ Show 20 Lines | if (have_trailing_slash && type == FILE_TO_FILE) { | ||||
errx(1, "%s is not a directory", to.p_path); | errx(1, "%s is not a directory", to.p_path); | ||||
} | } | ||||
} else | } else | ||||
/* | /* | ||||
* Case (2). Target is a directory. | * Case (2). Target is a directory. | ||||
*/ | */ | ||||
type = FILE_TO_DIR; | type = FILE_TO_DIR; | ||||
exit (copy(argv, type, fts_options)); | /* | ||||
* For DIR_TO_DNE, we could provide copy() with the to_stat we've | |||||
* already allocated on the stack here that isn't being used for | |||||
* anything. Not doing so, though, simplifies later logic a little bit | |||||
* as we need to skip checking root_stat on the first iteration and | |||||
* ensure that we set it with the first mkdir(). | |||||
*/ | |||||
exit (copy(argv, type, fts_options, (type == DIR_TO_DNE ? NULL : | |||||
&to_stat))); | |||||
} | } | ||||
static int | static int | ||||
copy(char *argv[], enum op type, int fts_options) | copy(char *argv[], enum op type, int fts_options, struct stat *root_stat) | ||||
{ | { | ||||
struct stat to_stat; | struct stat created_root_stat, to_stat; | ||||
FTS *ftsp; | FTS *ftsp; | ||||
FTSENT *curr; | FTSENT *curr; | ||||
int base = 0, dne, badcp, rval; | int base = 0, dne, badcp, rval; | ||||
size_t nlen; | size_t nlen; | ||||
char *p, *target_mid; | char *p, *recurse_path, *target_mid; | ||||
mode_t mask, mode; | mode_t mask, mode; | ||||
/* | /* | ||||
* Keep an inverted copy of the umask, for use in correcting | * Keep an inverted copy of the umask, for use in correcting | ||||
* permissions on created directories when not using -p. | * permissions on created directories when not using -p. | ||||
*/ | */ | ||||
mask = ~umask(0777); | mask = ~umask(0777); | ||||
umask(~mask); | umask(~mask); | ||||
recurse_path = NULL; | |||||
if ((ftsp = fts_open(argv, fts_options, NULL)) == NULL) | if ((ftsp = fts_open(argv, fts_options, NULL)) == NULL) | ||||
err(1, "fts_open"); | err(1, "fts_open"); | ||||
for (badcp = rval = 0; errno = 0, (curr = fts_read(ftsp)) != NULL; | for (badcp = rval = 0; errno = 0, (curr = fts_read(ftsp)) != NULL; | ||||
badcp = 0) { | badcp = 0) { | ||||
switch (curr->fts_info) { | switch (curr->fts_info) { | ||||
case FTS_NS: | case FTS_NS: | ||||
case FTS_DNR: | case FTS_DNR: | ||||
case FTS_ERR: | case FTS_ERR: | ||||
warnx("%s: %s", | warnx("%s: %s", | ||||
curr->fts_path, strerror(curr->fts_errno)); | curr->fts_path, strerror(curr->fts_errno)); | ||||
badcp = rval = 1; | badcp = rval = 1; | ||||
continue; | continue; | ||||
case FTS_DC: /* Warn, continue. */ | case FTS_DC: /* Warn, continue. */ | ||||
warnx("%s: directory causes a cycle", curr->fts_path); | warnx("%s: directory causes a cycle", curr->fts_path); | ||||
badcp = rval = 1; | badcp = rval = 1; | ||||
continue; | continue; | ||||
default: | default: | ||||
; | ; | ||||
} | } | ||||
if (curr->fts_info == FTS_D && type != FILE_TO_FILE && | |||||
root_stat != NULL && | |||||
root_stat->st_dev == curr->fts_statp->st_dev && | |||||
root_stat->st_ino == curr->fts_statp->st_ino) { | |||||
assert(recurse_path == NULL); | |||||
if (curr->fts_level > FTS_ROOTLEVEL) { | |||||
/* | /* | ||||
* If the recursion isn't at the immediate | |||||
* level, we can just not traverse into this | |||||
* directory. | |||||
*/ | |||||
fts_set(ftsp, curr, FTS_SKIP); | |||||
continue; | |||||
} else { | |||||
const char *slash; | |||||
/* | |||||
* Grab the last path component and double it, | |||||
* to make life easier later and ensure that | |||||
* we work even with fts_level == 0 is a couple | |||||
* of components deep in fts_path. No path | |||||
* separators are fine and expected in the | |||||
* common case, though. | |||||
*/ | |||||
slash = strrchr(curr->fts_path, '/'); | |||||
if (slash != NULL) | |||||
slash++; | |||||
else | |||||
slash = curr->fts_path; | |||||
if (asprintf(&recurse_path, "%s/%s", | |||||
curr->fts_path, slash) == -1) | |||||
err(1, "asprintf"); | |||||
} | |||||
} | |||||
if (recurse_path != NULL && | |||||
strcmp(curr->fts_path, recurse_path) == 0) { | |||||
fts_set(ftsp, curr, FTS_SKIP); | |||||
continue; | |||||
} | |||||
/* | |||||
* If we are in case (2) or (3) above, we need to append the | * If we are in case (2) or (3) above, we need to append the | ||||
* source name to the target name. | * source name to the target name. | ||||
*/ | */ | ||||
if (type != FILE_TO_FILE) { | if (type != FILE_TO_FILE) { | ||||
/* | /* | ||||
* Need to remember the roots of traversals to create | * Need to remember the roots of traversals to create | ||||
* correct pathnames. If there's a directory being | * correct pathnames. If there's a directory being | ||||
* copied to a non-existent directory, e.g. | * copied to a non-existent directory, e.g. | ||||
▲ Show 20 Lines • Show All 131 Lines • ▼ Show 20 Lines | case S_IFDIR: | ||||
* able to write the directory (if from directory is | * able to write the directory (if from directory is | ||||
* 555) and not causing a permissions race. If the | * 555) and not causing a permissions race. If the | ||||
* umask blocks owner writes, we fail. | * umask blocks owner writes, we fail. | ||||
*/ | */ | ||||
if (dne) { | if (dne) { | ||||
if (mkdir(to.p_path, | if (mkdir(to.p_path, | ||||
curr->fts_statp->st_mode | S_IRWXU) < 0) | curr->fts_statp->st_mode | S_IRWXU) < 0) | ||||
err(1, "%s", to.p_path); | err(1, "%s", to.p_path); | ||||
/* | |||||
* First DNE with a NULL root_stat is the root | |||||
* path, so set root_stat. We can't really | |||||
* tell in all cases if the target path is | |||||
* within the src path, so we just stat() the | |||||
* first directory we created and use that. | |||||
*/ | |||||
if (root_stat == NULL && | |||||
stat(to.p_path, &created_root_stat) == -1) { | |||||
err(1, "stat"); | |||||
} else if (root_stat == NULL) { | |||||
root_stat = &created_root_stat; | |||||
} | |||||
} else if (!S_ISDIR(to_stat.st_mode)) { | } else if (!S_ISDIR(to_stat.st_mode)) { | ||||
errno = ENOTDIR; | errno = ENOTDIR; | ||||
err(1, "%s", to.p_path); | err(1, "%s", to.p_path); | ||||
} | } | ||||
/* | /* | ||||
* Arrange to correct directory attributes later | * Arrange to correct directory attributes later | ||||
* (in the post-order phase) if this is a new | * (in the post-order phase) if this is a new | ||||
* directory, or if the -p flag is in effect. | * directory, or if the -p flag is in effect. | ||||
Show All 29 Lines | default: | ||||
break; | break; | ||||
} | } | ||||
if (vflag && !badcp) | if (vflag && !badcp) | ||||
(void)printf("%s -> %s\n", curr->fts_path, to.p_path); | (void)printf("%s -> %s\n", curr->fts_path, to.p_path); | ||||
} | } | ||||
if (errno) | if (errno) | ||||
err(1, "fts_read"); | err(1, "fts_read"); | ||||
fts_close(ftsp); | fts_close(ftsp); | ||||
free(recurse_path); | |||||
return (rval); | return (rval); | ||||
} | } | ||||
static void | static void | ||||
siginfo(int sig __unused) | siginfo(int sig __unused) | ||||
{ | { | ||||
info = 1; | info = 1; | ||||
} | } |