/*********************************************************************** * * * This software is part of the ast package * * Copyright (c) 1992-2007 AT&T Knowledge Ventures * * and is licensed under the * * Common Public License, Version 1.0 * * by AT&T Knowledge Ventures * * * * A copy of the License is available at * * http://www.opensource.org/licenses/cpl1.0.txt * * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * * * * Information and Software Systems Research * * AT&T Research * * Florham Park NJ * * * * Glenn Fowler * * David Korn * * * ***********************************************************************/ #pragma prototyped /* * Glenn Fowler * AT&T Research * * cp/ln/mv -- copy/link/move files */ static const char usage_head[] = "[-?@(#)$Id: cp (AT&T Research) 2006-11-21 $\n]" USAGE_LICENSE ; static const char usage_cp[] = "[+NAME?cp - copy files]" "[+DESCRIPTION?If the last argument names an existing directory, \bcp\b" " copies each \afile\a into a file with the same name in that" " directory. Otherwise, if only two files are given, \bcp\b copies" " the first onto the second. It is an error if the last argument is" " not a directory and more than two files are given. By default" " directories are not copied.]" "[a:archive?Preserve as much as possible of the structure and attributes of" " the original files in the copy. Equivalent to \b--physical\b" " \b--preserve\b \b--recursive\b.]" "[p:preserve?Preserve file owner, group, permissions and timestamps.]" "[h:hierarchy|parents?Form the name of each destination file by appending" " to the target directory a slash and the specified source file name." " The last argument must be an existing directory. Missing destination" " directories are created.]" "[H:metaphysical?Follow command argument symbolic links, otherwise don't" " follow.]" "[l:link?Make hard links to destination files instead of copies.]" "[L:logical|dereference?Follow symbolic links and copy the files" " they point to.]" "[P|d:physical|nodereference?Don't follow symbolic links; copy symbolic" " rather than the files they point to.]" ; static const char usage_ln[] = "[+NAME?ln - link files]" "[+DESCRIPTION?If the last argument names an existing directory, \bln\b" " links each \afile\a into a file with the same name in that" " directory. Otherwise, if only two files are given, \bln\b links" " the first onto the second. It is an error if the last argument is" " not a directory and more than two files are given. By default" " directories are not linked.]" ; static const char usage_mv[] = "[+NAME?mv - rename files]" "[+DESCRIPTION?If the last argument names an existing directory, \bmv\b" " renames each \afile\a into a file with the same name in that" " directory. Otherwise, if only two files are given, \bmv\b renames" " the first onto the second. It is an error if the last argument is" " not a directory and more than two files are given. If a source and" " destination file reside on different filesystems then \bmv\b copies" " the file contents to the destination and then deletes the source" " file.]" ; static const char usage_tail[] = "[f:force?Replace existing destination files.]" "[i:interactive|prompt?Prompt whether to replace existing destination files." " An affirmative response (\by\b or \bY\b) replaces the file, a quit" " response (\bq\b or \bQ\b) exits immediately, and all other" " responses skip the file.]" "[r|R:recursive?Operate on the contents of directories recursively.]" "[s:symlink|symbolic-link?Make symbolic links to destination files.]" "[u:update?Replace a destination file only if its modification time is older" " than the corresponding source file modification time.]" "[v:verbose?Print the name of each file before operating on it.]" "[b:backup?Make backups of files that are about to be replaced. See" " \b--suffix\b and \b--version-control\b for more information.]" "[F:fsync|sync?\bfsync\b(2) each file after it is copied.]" "[S:backup-suffix|suffix?A backup file is made by renaming the file to the" " same name with the backup suffix appended. The backup suffix is" " determined in this order: this option, the \bSIMPLE_BACKUP_SUFFIX\b," " environment variable, or the default value \b~\b.]:[suffix]" "[V:backup-type|version-control?The backup type is determined in this order:" " this option, the \bVERSION_CONTROL\b environment variable, or the" " default value \bexisting\b. \atype\a may be one of:]:[type]{" " [+numbered|t?Always make numbered backups. The numbered backup" " suffix is \b.\aSNS\a, where \aS\a is the" " \bbackup-suffix\b and \aN\a is the version number," " starting at 1, incremented with each version.]" " [+existing|nil?Make numbered backups of files that already" " have them, otherwise simple backups.]" " [+simple|never?Always make simple backups.]" "}" "[x|X|l:xdev|local|mount|one-file-system?Do not descend into directories in" " different filesystems than their parents.]" "\n" "\nsource destination\n" "file ... directory\n" "\n" "[+SEE ALSO?\bpax\b(1), \bfsync\b(2), \brename\b(2), \bunlink\b(2)," " \bremove\b(3)]" ; #include #include #include #include #include #include #include #include #define PATH_CHUNK 256 #define CP 1 #define LN 2 #define MV 3 #define BAK_replace 0 /* no backup -- just replace */ #define BAK_existing 1 /* number if already else simple*/ #define BAK_number 2 /* append .suffix number suffix */ #define BAK_simple 3 /* append suffix */ typedef struct State_s /* program state */ { int backup; /* BAK_* type */ int directory; /* destination is directory */ int flags; /* FTS_* flags */ int force; /* force approval */ int fs3d; /* 3d fs enabled */ int hierarchy; /* preserve hierarchy */ int interactive; /* prompt for approval */ int missmode; /* default missing dir mode */ int official; /* move to next view */ int op; /* {CP,LN,MV} */ int pathsiz; /* state.path buffer size */ int perm; /* permissions to preserve */ int postsiz; /* state.path post index */ int presiz; /* state.path pre index */ int preserve; /* preserve { id mode time } */ int recursive; /* subtrees too */ int suflen; /* strlen(state.suffix) */ int sync; /* fsync() each file after copy */ int uid; /* caller uid */ int update; /* replace only if newer */ int verbose; /* list each file before op */ int (*link)(const char*, const char*); /* link */ int (*stat)(const char*, struct stat*); /* stat */ char* path; /* to pathname buffer */ char* opname; /* state.op message string */ char* suffix; /* backup suffix */ Sfio_t* tmp; /* tmp string stream */ char text[PATH_MAX]; /* link text buffer */ } State_t; static const char dot[2] = { '.' }; /* * preserve support */ static void preserve(State_t* state, const char* path, struct stat* ns, struct stat* os) { int n; if (tmxtouch(path, tmxgetatime(os), tmxgetmtime(os), TMX_NOTIME, 0)) error(ERROR_SYSTEM|2, "%s: cannot reset access and modify times", path); n = ((ns->st_uid != os->st_uid) << 1) | (ns->st_gid != os->st_gid); if (n && chown(state->path, os->st_uid, os->st_gid)) switch (n) { case 01: error(ERROR_SYSTEM|2, "%s: cannot reset group to %s", path, fmtgid(os->st_gid)); break; case 02: error(ERROR_SYSTEM|2, "%s: cannot reset owner to %s", path, fmtuid(os->st_uid)); break; case 03: error(ERROR_SYSTEM|2, "%s: cannot reset owner to %s and group to %s", path, fmtuid(os->st_uid), fmtgid(os->st_gid)); break; } } /* * visit a single file and state.op to the destination */ static int visit(State_t* state, register FTSENT* ent) { register char* base; register int n; register int len; int rm; int rfd; int wfd; int m; int v; char* s; char* e; char* protection; Sfio_t* ip; Sfio_t* op; FTS* fts; FTSENT* sub; struct stat st; if (cmdquit()) return -1; if (ent->fts_info == FTS_DC) { error(2, "%s: directory causes cycle", ent->fts_path); fts_set(NiL, ent, FTS_SKIP); return 0; } if (ent->fts_level == 0) { base = ent->fts_name; len = ent->fts_namelen; if (state->hierarchy) state->presiz = -1; else { state->presiz = ent->fts_pathlen; while (*base == '.' && *(base + 1) == '/') for (base += 2; *base == '/'; base++); if (*base == '.' && !*(base + 1)) state->presiz--; else if (*base) state->presiz -= base - ent->fts_name; base = ent->fts_name + len; while (base > ent->fts_name && *(base - 1) == '/') base--; while (base > ent->fts_name && *(base - 1) != '/') base--; len -= base - ent->fts_name; if (state->directory) state->presiz -= len + 1; } } else { base = ent->fts_path + state->presiz + 1; len = ent->fts_pathlen - state->presiz - 1; } len++; if (state->directory) { if ((state->postsiz + len) > state->pathsiz && !(state->path = newof(state->path, char, state->pathsiz = roundof(state->postsiz + len, PATH_CHUNK), 0))) error(3, "out of space"); if (state->hierarchy && ent->fts_level == 0 && strchr(base, '/')) { s = state->path + state->postsiz; memcpy(s, base, len); while (e = strchr(s, '/')) { *e = 0; if (access(state->path, F_OK)) { st.st_mode = state->missmode; if (s = strrchr(s, '/')) { *s = 0; stat(state->path, &st); *s = '/'; } if (mkdir(state->path, st.st_mode & S_IPERM)) { error(ERROR_SYSTEM|2, "%s: cannot create directory -- %s ignored", state->path, ent->fts_path); fts_set(NiL, ent, FTS_SKIP); return 0; } } *e++ = '/'; s = e; } } } switch (ent->fts_info) { case FTS_DP: if (state->preserve && state->op != LN || ent->fts_level > 0 && (ent->fts_statp->st_mode & S_IRWXU) != S_IRWXU) { if (len && ent->fts_level > 0) memcpy(state->path + state->postsiz, base, len); else state->path[state->postsiz] = 0; if (stat(state->path, &st)) error(ERROR_SYSTEM|2, "%s: cannot stat", state->path); else { if ((ent->fts_statp->st_mode & S_IPERM) != (st.st_mode & S_IPERM) && chmod(state->path, ent->fts_statp->st_mode & S_IPERM)) error(ERROR_SYSTEM|2, "%s: cannot reset directory mode to %s", state->path, fmtmode(st.st_mode & S_IPERM, 0) + 1); if (state->preserve) preserve(state, state->path, &st, ent->fts_statp); } } return 0; case FTS_DNR: case FTS_DNX: case FTS_D: if (!state->recursive) { fts_set(NiL, ent, FTS_SKIP); if (state->op == CP) error(1, "%s: directory -- copying as plain file", ent->fts_path); else if (state->link == link && !state->force) { error(2, "%s: cannot link directory", ent->fts_path); return 0; } } else switch (ent->fts_info) { case FTS_DNR: error(2, "%s: cannot read directory", ent->fts_path); return 0; case FTS_DNX: error(2, "%s: cannot search directory", ent->fts_path); fts_set(NiL, ent, FTS_SKIP); /*FALLTHROUGH*/ case FTS_D: if (state->directory) memcpy(state->path + state->postsiz, base, len); if (!(*state->stat)(state->path, &st)) { if (!S_ISDIR(st.st_mode)) { error(2, "%s: not a directory -- %s ignored", state->path, ent->fts_path); return 0; } } else if (mkdir(state->path, (ent->fts_statp->st_mode & S_IPERM)|(ent->fts_info == FTS_D ? S_IRWXU : 0))) { error(ERROR_SYSTEM|2, "%s: cannot create directory -- %s ignored", state->path, ent->fts_path); fts_set(NiL, ent, FTS_SKIP); } if (!state->directory) { state->directory = 1; state->path[state->postsiz++] = '/'; state->presiz--; } return 0; } break; case FTS_ERR: case FTS_NS: case FTS_SLNONE: if (state->link != pathsetlink) { error(2, "%s: not found", ent->fts_path); return 0; } break; #if 0 case FTS_SL: if (state->op == CP) { error(2, "%s: cannot copy non-terminal symbolic link", ent->fts_path); return 0; } break; #endif } if (state->directory) memcpy(state->path + state->postsiz, base, len); if ((*state->stat)(state->path, &st)) st.st_mode = 0; else if (state->update && !S_ISDIR(st.st_mode) && (unsigned long)ent->fts_statp->st_mtime < (unsigned long)st.st_mtime) { fts_set(NiL, ent, FTS_SKIP); return 0; } else if (!state->fs3d || !iview(&st)) { /* * target is in top 3d view */ if (st.st_dev == ent->fts_statp->st_dev && st.st_ino == ent->fts_statp->st_ino) { if (state->op == MV) { /* * let rename() handle it */ if (state->verbose) sfputr(sfstdout, state->path, '\n'); goto operate; } if (!state->official) error(2, "%s: identical to %s", state->path, ent->fts_path); return 0; } if (S_ISDIR(st.st_mode)) { error(2, "%s: cannot %s existing directory", state->path, state->opname); return 0; } if (state->verbose) sfputr(sfstdout, state->path, '\n'); rm = state->op == LN || ent->fts_info == FTS_SL; if (!rm || !state->force) { if ((n = open(state->path, O_RDWR|O_BINARY)) >= 0) { close(n); if (state->force) /* ok */; else if (state->interactive) { if (astquery(-1, "%s %s? ", state->opname, state->path)) return 0; } else if (state->op == LN) { error(2, "%s: cannot %s existing file", state->path, state->opname); return 0; } } else if (state->force) rm = 1; else { protection = #ifdef ETXTBSY errno == ETXTBSY ? "``running program''" : #endif st.st_uid != state->uid ? "``not owner''" : fmtmode(st.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO), 0) + 1; if (state->interactive) { if (astquery(-1, "override protection %s for %s? ", protection, state->path)) return 0; rm = 1; } else if (!rm) { error(2, "%s: cannot %s %s protection", state->path, state->opname, protection); return 0; } } } switch (state->backup) { case BAK_existing: case BAK_number: v = 0; if (s = strrchr(state->path, '/')) { e = state->path; *s++ = 0; } else { e = (char*)dot; s = state->path; } n = strlen(s); if (fts = fts_open((char**)e, FTS_NOCHDIR|FTS_ONEPATH|FTS_PHYSICAL|FTS_NOPOSTORDER|FTS_NOSTAT|FTS_NOSEEDOTDIR, NiL)) { while (sub = fts_read(fts)) { if (strneq(s, sub->fts_name, n) && sub->fts_name[n] == '.' && strneq(sub->fts_name + n + 1, state->suffix, state->suflen) && (m = strtol(sub->fts_name + n + state->suflen + 1, &e, 10)) && streq(e, state->suffix) && m > v) v = m; if (sub->fts_level) fts_set(NiL, sub, FTS_SKIP); } fts_close(fts); } if (s != state->path) *--s = '/'; if (v || state->backup == BAK_number) { sfprintf(state->tmp, "%s.%s%d%s", state->path, state->suffix, v + 1, state->suffix); goto backup; } /*FALLTHROUGH*/ case BAK_simple: sfprintf(state->tmp, "%s%s", state->path, state->suffix); backup: if (!(s = sfstruse(state->tmp))) error(ERROR_SYSTEM|3, "%s: out of space", state->path); if (rename(state->path, s)) { error(ERROR_SYSTEM|2, "%s: cannot backup to %s", state->path, s); return 0; } break; default: if (rm && remove(state->path)) { error(ERROR_SYSTEM|2, "%s: cannot remove", state->path); return 0; } break; } } operate: switch (state->op) { case MV: for (;;) { if (!rename(ent->fts_path, state->path)) return 0; if (errno == ENOENT) rm = 1; else if (!rm && st.st_mode && !remove(state->path)) { rm = 1; continue; } if (errno != EXDEV && (rm || S_ISDIR(ent->fts_statp->st_mode))) { error(ERROR_SYSTEM|2, "%s: cannot rename to %s", ent->fts_path, state->path); return 0; } else break; } /*FALLTHROUGH*/ case CP: if (S_ISLNK(ent->fts_statp->st_mode)) { if ((n = pathgetlink(ent->fts_path, state->text, sizeof(state->text) - 1)) < 0) { error(ERROR_SYSTEM|2, "%s: cannot read symbolic link text", ent->fts_path); return 0; } state->text[n] = 0; if (pathsetlink(state->text, state->path)) { error(ERROR_SYSTEM|2, "%s: cannot copy symbolic link to %s", ent->fts_path, state->path); return 0; } } else if (state->op == CP || S_ISREG(ent->fts_statp->st_mode) || S_ISDIR(ent->fts_statp->st_mode)) { if (ent->fts_statp->st_size > 0 && (rfd = open(ent->fts_path, O_RDONLY|O_BINARY)) < 0) { error(ERROR_SYSTEM|2, "%s: cannot read", ent->fts_path); return 0; } else if ((wfd = open(state->path, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, ent->fts_statp->st_mode & state->perm)) < 0) { error(ERROR_SYSTEM|2, "%s: cannot write", state->path); if (ent->fts_statp->st_size > 0) close(rfd); return 0; } else if (ent->fts_statp->st_size > 0) { if (!(ip = sfnew(NiL, NiL, SF_UNBOUND, rfd, SF_READ))) { error(ERROR_SYSTEM|2, "%s: %s read stream error", ent->fts_path, state->path); close(rfd); close(wfd); } else { n = 0; if (!(op = sfnew(NiL, NiL, SF_UNBOUND, wfd, SF_WRITE))) { error(ERROR_SYSTEM|2, "%s: %s write stream error", ent->fts_path, state->path); close(wfd); sfclose(ip); } else { if (sfmove(ip, op, (Sfoff_t)SF_UNBOUND, -1) < 0) n |= 3; if (!sfeof(ip)) n |= 1; if (sfsync(op) || state->sync && fsync(wfd) || sfclose(op)) n |= 2; if (sfclose(ip)) n |= 1; if (n) error(ERROR_SYSTEM|2, "%s: %s %s error", ent->fts_path, state->path, n == 1 ? ERROR_translate(0, 0, 0, "read") : n == 2 ? ERROR_translate(0, 0, 0, "write") : ERROR_translate(0, 0, 0, "io")); } } } else close(wfd); } else if (S_ISBLK(ent->fts_statp->st_mode) || S_ISCHR(ent->fts_statp->st_mode) || S_ISFIFO(ent->fts_statp->st_mode)) { if (mknod(state->path, ent->fts_statp->st_mode, idevice(ent->fts_statp))) { error(ERROR_SYSTEM|2, "%s: cannot copy special file to %s", ent->fts_path, state->path); return 0; } } else { error(2, "%s: cannot copy -- unknown file type 0%o", ent->fts_path, S_ITYPE(ent->fts_statp->st_mode)); return 0; } if (state->preserve) { if (ent->fts_info != FTS_SL) { if (stat(state->path, &st)) error(ERROR_SYSTEM|2, "%s: cannot stat", state->path); else { if ((ent->fts_statp->st_mode & state->perm) != (st.st_mode & state->perm) && chmod(state->path, ent->fts_statp->st_mode & state->perm)) error(ERROR_SYSTEM|2, "%s: cannot reset mode to %s", state->path, fmtmode(st.st_mode & state->perm, 0) + 1); preserve(state, state->path, &st, ent->fts_statp); } } if (state->op == MV && remove(ent->fts_path)) error(ERROR_SYSTEM|1, "%s: cannot remove", ent->fts_path); } break; case LN: if ((*state->link)(ent->fts_path, state->path)) error(ERROR_SYSTEM|2, "%s: cannot link to %s", ent->fts_path, state->path); break; } return 0; } int b_cp(int argc, register char** argv, void* context) { register char* file; register char* s; char** v; char* backup_type; FTS* fts; FTSENT* ent; const char* usage; int path_resolve; int standard; struct stat st; State_t state; cmdinit(argc, argv, context, ERROR_CATALOG, ERROR_NOTIFY); memset(&state, 0, sizeof(state)); state.presiz = -1; backup_type = 0; state.flags = FTS_NOCHDIR|FTS_NOSEEDOTDIR; state.uid = geteuid(); if (!(state.tmp = sfstropen())) error(ERROR_SYSTEM|3, "out of space [tmp string]"); sfputr(state.tmp, usage_head, -1); standard = !strcmp(astconf("CONFORMANCE", NiL, NiL), "standard"); switch (error_info.id[0]) { case 'c': case 'C': sfputr(state.tmp, usage_cp, -1); state.op = CP; state.stat = stat; path_resolve = -1; break; case 'l': case 'L': sfputr(state.tmp, usage_ln, -1); state.op = LN; state.flags |= FTS_PHYSICAL; state.link = link; state.stat = lstat; path_resolve = 1; break; case 'm': case 'M': sfputr(state.tmp, usage_mv, -1); state.op = MV; state.flags |= FTS_PHYSICAL; state.preserve = 1; state.stat = lstat; path_resolve = 1; break; default: error(3, "not implemented"); break; } sfputr(state.tmp, usage_tail, -1); if (!(usage = sfstruse(state.tmp))) error(ERROR_SYSTEM|3, "%s: out of space", state.path); state.opname = state.op == CP ? ERROR_translate(0, 0, 0, "overwrite") : ERROR_translate(0, 0, 0, "replace"); for (;;) { switch (optget(argv, usage)) { case 'a': state.flags |= FTS_PHYSICAL; state.preserve = 1; state.recursive = 1; path_resolve = 1; continue; case 'b': state.backup = 1; continue; case 'f': state.force = 1; if (state.op != CP || !standard) state.interactive = 0; continue; case 'h': state.hierarchy = 1; continue; case 'i': state.interactive = 1; if (state.op != CP || !standard) state.force = 0; continue; case 'l': state.op = LN; state.link = link; state.stat = lstat; continue; case 'p': state.preserve = 1; continue; case 'r': state.recursive = 1; if (path_resolve < 0) path_resolve = 0; continue; case 's': state.op = LN; state.link = pathsetlink; state.stat = lstat; continue; case 'u': state.update = 1; continue; case 'v': state.verbose = 1; continue; case 'x': state.flags |= FTS_XDEV; continue; case 'F': #if _lib_fsync state.sync = 1; #else error(1, "%s not implemented on this system", opt_info.name); #endif continue; case 'H': state.flags |= FTS_META|FTS_PHYSICAL; path_resolve = 1; continue; case 'L': state.flags &= ~FTS_PHYSICAL; path_resolve = 1; continue; case 'P': state.flags &= ~FTS_META; state.flags |= FTS_PHYSICAL; path_resolve = 1; continue; case 'R': state.recursive = 1; state.flags &= ~FTS_META; state.flags |= FTS_PHYSICAL; path_resolve = 1; continue; case 'S': state.suffix = opt_info.arg; continue; case 'V': backup_type = opt_info.arg; continue; case '?': error(ERROR_USAGE|4, "%s", opt_info.arg); continue; case ':': error(2, "%s", opt_info.arg); continue; } break; } argc -= opt_info.index + 1; argv += opt_info.index; if (*argv && streq(*argv, "-") && !streq(*(argv - 1), "--")) { argc--; argv++; } if (!(v = (char**)stkalloc(stkstd, (argc + 2) * sizeof(char*)))) error(3, "out of space"); memcpy(v, argv, (argc + 1) * sizeof(char*)); argv = v; if (!argc && !standard) { argc++; argv[1] = (char*)dot; } if (state.backup) { if (!(file = backup_type) && !(backup_type = getenv("VERSION_CONTROL"))) state.backup = BAK_existing; else switch (strkey(backup_type)) { case HASHKEY6('e','x','i','s','t','i'): case HASHKEY5('e','x','i','s','t'): case HASHKEY4('e','x','i','s'): case HASHKEY3('e','x','i'): case HASHKEY2('e','x'): case HASHKEY1('e'): case HASHKEY3('n','i','l'): case HASHKEY2('n','i'): state.backup = BAK_existing; break; case HASHKEY5('n','e','v','e','r'): case HASHKEY4('n','e','v','e'): case HASHKEY3('n','e','v'): case HASHKEY2('n','e'): case HASHKEY6('s','i','m','p','l','e'): case HASHKEY5('s','i','m','p','l'): case HASHKEY4('s','i','m','p'): case HASHKEY3('s','i','m'): case HASHKEY2('s','i'): case HASHKEY1('s'): state.backup = BAK_simple; break; case HASHKEY6('n','u','m','b','e','r'): case HASHKEY5('n','u','m','b','e'): case HASHKEY4('n','u','m','b'): case HASHKEY3('n','u','m'): case HASHKEY2('n','u'): case HASHKEY1('t'): state.backup = BAK_number; break; default: if (file) error(2, "%s: unknown backup type", backup_type); break; } if (!state.suffix && !(state.suffix = getenv("SIMPLE_BACKUP_SUFFIX"))) state.suffix = "~"; state.suflen = strlen(state.suffix); } if (argc <= 0 || error_info.errors) error(ERROR_USAGE|4, "%s", optusage(NiL)); if (!path_resolve) state.flags |= fts_flags(); file = argv[argc]; argv[argc] = 0; if (s = strrchr(file, '/')) { while (*s == '/') s++; if (!(!*s || *s == '.' && (!*++s || *s == '.' && !*++s))) s = 0; } if (file != (char*)dot) pathcanon(file, 0); if (!(state.directory = !stat(file, &st) && S_ISDIR(st.st_mode)) && argc > 1) error(ERROR_USAGE|4, "%s", optusage(NiL)); if (s && !state.directory) error(3, "%s: not a directory", file); if ((state.fs3d = fs3d(FS3D_TEST)) && strmatch(file, "...|*/...|.../*")) state.official = 1; state.postsiz = strlen(file); state.pathsiz = roundof(state.postsiz + 2, PATH_CHUNK); if (!(state.path = newof(0, char, state.pathsiz, 0))) error(3, "out of space"); memcpy(state.path, file, state.postsiz + 1); if (state.directory && state.path[state.postsiz - 1] != '/') state.path[state.postsiz++] = '/'; if (state.hierarchy) { if (!state.directory) error(3, "%s: last argument must be a directory", file); state.missmode = st.st_mode; } state.perm = state.uid ? S_IPERM : (S_IPERM & ~S_ISVTX); if (!state.recursive) state.flags |= FTS_TOP; if (fts = fts_open(argv, state.flags, NiL)) { while ((ent = fts_read(fts)) && !visit(&state, ent)); fts_close(fts); } else if (state.link != pathsetlink) switch (state.op) { case CP: error(ERROR_SYSTEM|2, "%s: cannot copy", argv[0]); break; case LN: error(ERROR_SYSTEM|2, "%s: cannot link", argv[0]); break; case MV: error(ERROR_SYSTEM|2, "%s: cannot move", argv[0]); break; } else if ((*state.link)(*argv, state.path)) error(ERROR_SYSTEM|2, "%s: cannot link to %s", *argv, state.path); free(state.path); return error_info.errors != 0; }