summaryrefslogtreecommitdiff
path: root/usr/src/lib/libcmd/common/cp.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/lib/libcmd/common/cp.c')
-rw-r--r--usr/src/lib/libcmd/common/cp.c927
1 files changed, 927 insertions, 0 deletions
diff --git a/usr/src/lib/libcmd/common/cp.c b/usr/src/lib/libcmd/common/cp.c
new file mode 100644
index 0000000000..3aa6b75273
--- /dev/null
+++ b/usr/src/lib/libcmd/common/cp.c
@@ -0,0 +1,927 @@
+/***********************************************************************
+* *
+* 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 <gsf@research.att.com> *
+* David Korn <dgk@research.att.com> *
+* *
+***********************************************************************/
+#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 <cmd.h>
+#include <ls.h>
+#include <times.h>
+#include <fts.h>
+#include <fs3d.h>
+#include <hashkey.h>
+#include <stk.h>
+#include <tmx.h>
+
+#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;
+}