1/***********************************************************************
2*                                                                      *
3*               This software is part of the ast package               *
4*          Copyright (c) 1992-2012 AT&T Intellectual Property          *
5*                      and is licensed under the                       *
6*                 Eclipse Public License, Version 1.0                  *
7*                    by AT&T Intellectual Property                     *
8*                                                                      *
9*                A copy of the License is available at                 *
10*          http://www.eclipse.org/org/documents/epl-v10.html           *
11*         (with md5 checksum b35adb5213ca9657e911e9befb180842)         *
12*                                                                      *
13*              Information and Software Systems Research               *
14*                            AT&T Research                             *
15*                           Florham Park NJ                            *
16*                                                                      *
17*                 Glenn Fowler <gsf@research.att.com>                  *
18*                  David Korn <dgk@research.att.com>                   *
19*                                                                      *
20***********************************************************************/
21#pragma prototyped
22/*
23 * Glenn Fowler
24 * AT&T Research
25 *
26 * cp/ln/mv -- copy/link/move files
27 */
28
29static const char usage_head[] =
30"[-?@(#)$Id: cp (AT&T Research) 2012-04-20 $\n]"
31USAGE_LICENSE
32;
33
34static const char usage_cp[] =
35"[+NAME?cp - copy files]"
36"[+DESCRIPTION?If the last argument names an existing directory, \bcp\b "
37    "copies each \afile\a into a file with the same name in that directory. "
38    "Otherwise, if only two files are given, \bcp\b copies the first onto "
39    "the second. It is an error if the last argument is not a directory and "
40    "more than two files are given. By default directories are not copied.]"
41
42"[a:archive?Preserve as much as possible of the structure and attributes "
43    "of the original files in the copy. Equivalent to \b--physical\b "
44    "\b--preserve\b \b--recursive\b.]"
45"[A:attributes?Preserve selected file attributes:]:[eipt]"
46    "{"
47        "[+e?Everything permissible.]"
48        "[+i?Owner uid and gid.]"
49        "[+p?Permissions.]"
50        "[+t?Access and modify times.]"
51    "}"
52"[p:preserve?Preserve file owner, group, permissions and timestamps.]"
53"[h:hierarchy|parents?Form the name of each destination file by "
54    "appending to the target directory a slash and the specified source file "
55    "name. The last argument must be an existing directory. Missing "
56    "destination directories are created.]"
57"[H:metaphysical?Follow command argument symbolic links, otherwise don't "
58    "follow.]"
59"[l:link?Make hard links to destination files instead of copies.]"
60"[U:remove-destination?Remove existing destination files before copying.]"
61"[L:logical|dereference?Follow symbolic links and copy the files they "
62    "point to.]"
63"[P|d:physical|nodereference?Don't follow symbolic links; copy symbolic "
64    "rather than the files they point to.]"
65;
66
67static const char usage_ln[] =
68"[+NAME?ln - link files]"
69"[+DESCRIPTION?If the last argument names an existing directory, \bln\b "
70    "links each \afile\a into a file with the same name in that directory. "
71    "Otherwise, if only two files are given, \bln\b links the first onto the "
72    "second. It is an error if the last argument is not a directory and more "
73    "than two files are given. By default directories are not linked.]"
74;
75
76static const char usage_mv[] =
77"[+NAME?mv - rename files]"
78"[+DESCRIPTION?If the last argument names an existing directory, \bmv\b "
79    "renames each \afile\a into a file with the same name in that directory. "
80    "Otherwise, if only two files are given, \bmv\b renames the first onto "
81    "the second. It is an error if the last argument is not a directory and "
82    "more than two files are given. If a source and destination file reside "
83    "on different filesystems then \bmv\b copies the file contents to the "
84    "destination and then deletes the source file.]"
85
86"[U:remove-destination?Remove existing destination files before moving.]"
87;
88
89static const char usage_tail[] =
90"[f:force?Replace existing destination files.]"
91"[i:interactive|prompt?Prompt whether to replace existing destination "
92    "files. An affirmative response (\by\b or \bY\b) replaces the file, a "
93    "quit response (\bq\b or \bQ\b) exits immediately, and all other "
94    "responses skip the file.]"
95"[r|R:recursive?Operate on the contents of directories recursively.]"
96"[s:symlink|symbolic-link?Make symbolic links to destination files.]"
97"[u:update?Replace a destination file only if its modification time is "
98    "older than the corresponding source file modification time.]"
99"[v:verbose?Print the name of each file before operating on it.]"
100"[F:fsync|sync?\bfsync\b(2) each file after it is copied.]"
101"[B:backup?Make backups of files that are about to be replaced. "
102    "\b--suffix\b sets the backup suffix. The backup type is determined in "
103    "this order: this option, the \bVERSION_CONTROL\b environment variable, "
104    "or the default value \bexisting\b. \atype\a may be one of:]:?[type]"
105    "{"
106        "[+numbered|t?Always make numbered backups. The numbered backup "
107            "suffix is \b.\aSNS\a, where \aS\a is the \bbackup-suffix\b and "
108            "\aN\a is the version number, starting at 1, incremented with "
109            "each version.]"
110        "[+existing|nil?Make numbered backups of files that already have "
111            "them, otherwise simple backups.]"
112        "[+simple|never?Always make simple backups.]"
113	"[+none|off?Disable backups.]"
114    "}"
115"[S:suffix?A backup file is made by renaming the file to the same name "
116    "with the backup suffix appended. The backup suffix is determined in "
117    "this order: this option, the \bSIMPLE_BACKUP_SUFFIX\b, environment "
118    "variable, or the default value \b~\b.]:[suffix]"
119"[b?\b--backup\b using the type in the \bVERSION_CONTROL\b environment "
120    "variable.]"
121"[x|X:xdev|local|mount|one-file-system?Do not descend into directories "
122    "in different filesystems than their parents.]"
123
124"\n"
125"\nsource destination\n"
126"file ... directory\n"
127"\n"
128
129"[+SEE ALSO?\bpax\b(1), \bfsync\b(2), \brename\b(2), \bunlink\b(2),"
130"	\bremove\b(3)]"
131;
132
133#include <cmd.h>
134#include <ls.h>
135#include <times.h>
136#include <fts_fix.h>
137#include <fs3d.h>
138#include <hashkey.h>
139#include <stk.h>
140#include <tmx.h>
141
142#define PATH_CHUNK	256
143
144#define CP		1
145#define LN		2
146#define MV		3
147
148#define PRESERVE_IDS	0x1		/* preserve uid gid		*/
149#define PRESERVE_PERM	0x2		/* preserve permissions		*/
150#define PRESERVE_TIME	0x4		/* preserve times		*/
151
152#define BAK_replace	0		/* no backup -- just replace	*/
153#define BAK_existing	1		/* number if already else simple*/
154#define BAK_number	2		/* append .suffix number suffix	*/
155#define BAK_simple	3		/* append suffix		*/
156
157typedef struct State_s			/* program state		*/
158{
159	Shbltin_t*	context;	/* builtin context		*/
160	int		backup;		/* BAK_* type			*/
161	int		directory;	/* destination is directory	*/
162	int		flags;		/* FTS_* flags			*/
163	int		force;		/* force approval		*/
164	int		fs3d;		/* 3d fs enabled		*/
165	int		hierarchy;	/* preserve hierarchy		*/
166	int		interactive;	/* prompt for approval		*/
167	int		missmode;	/* default missing dir mode	*/
168	int		official;	/* move to next view		*/
169	int		op;		/* {CP,LN,MV}			*/
170	int		perm;		/* permissions to preserve	*/
171	int		postsiz;	/* state.path post index	*/
172	int		presiz;		/* state.path pre index		*/
173	int		preserve;	/* preserve { ids perms times }	*/
174	int		recursive;	/* subtrees too			*/
175	int		remove;		/* remove destination before op	*/
176	int		suflen;		/* strlen(state.suffix)		*/
177	int		sync;		/* fsync() each file after copy	*/
178	int		uid;		/* caller uid			*/
179	int		update;		/* replace only if newer	*/
180	int		verbose;	/* list each file before op	*/
181	int		wflags;		/* open() for write flags	*/
182
183	int		(*link)(const char*, const char*);	/* link	*/
184	int		(*stat)(const char*, struct stat*);	/* stat	*/
185
186#define INITSTATE	pathsiz		/* (re)init state before this	*/
187	int		pathsiz;	/* state.path buffer size	*/
188
189
190	char*		path;		/* to pathname buffer		*/
191	char*		opname;		/* state.op message string	*/
192	char*		suffix;		/* backup suffix		*/
193
194	Sfio_t*		tmp;		/* tmp string stream		*/
195
196	char		text[PATH_MAX];	/* link text buffer		*/
197} State_t;
198
199static const char	dot[2] = { '.' };
200
201/*
202 * preserve support
203 */
204
205static void
206preserve(State_t* state, const char* path, struct stat* ns, struct stat* os)
207{
208	int	n;
209
210	if ((state->preserve & PRESERVE_TIME) && tmxtouch(path, tmxgetatime(os), tmxgetmtime(os), TMX_NOTIME, 0))
211		error(ERROR_SYSTEM|2, "%s: cannot reset access and modify times", path);
212	if (state->preserve & PRESERVE_IDS)
213	{
214		n = ((ns->st_uid != os->st_uid) << 1) | (ns->st_gid != os->st_gid);
215		if (n && chown(state->path, os->st_uid, os->st_gid))
216			switch (n)
217			{
218			case 01:
219				error(ERROR_SYSTEM|2, "%s: cannot reset group to %s", path, fmtgid(os->st_gid));
220				break;
221			case 02:
222				error(ERROR_SYSTEM|2, "%s: cannot reset owner to %s", path, fmtuid(os->st_uid));
223				break;
224			case 03:
225				error(ERROR_SYSTEM|2, "%s: cannot reset owner to %s and group to %s", path, fmtuid(os->st_uid), fmtgid(os->st_gid));
226				break;
227			}
228	}
229}
230
231/*
232 * visit a single file and state.op to the destination
233 */
234
235static int
236visit(State_t* state, register FTSENT* ent)
237{
238	register char*	base;
239	register int	n;
240	register int	len;
241	int		rm;
242	int		rfd;
243	int		wfd;
244	int		m;
245	int		v;
246	char*		s;
247	char*		e;
248	char*		protection;
249	Sfio_t*		ip;
250	Sfio_t*		op;
251	FTS*		fts;
252	FTSENT*		sub;
253	struct stat	st;
254
255	if (ent->fts_info == FTS_DC)
256	{
257		error(2, "%s: directory causes cycle", ent->fts_path);
258		fts_set(NiL, ent, FTS_SKIP);
259		return 0;
260	}
261	if (ent->fts_level == 0)
262	{
263		base = ent->fts_name;
264		len = ent->fts_namelen;
265		if (state->hierarchy)
266			state->presiz = -1;
267		else
268		{
269			state->presiz = ent->fts_pathlen;
270			while (*base == '.' && *(base + 1) == '/')
271				for (base += 2; *base == '/'; base++);
272			if (*base == '.' && !*(base + 1))
273				state->presiz--;
274			else if (*base)
275				state->presiz -= base - ent->fts_name;
276			base = ent->fts_name + len;
277			while (base > ent->fts_name && *(base - 1) == '/')
278				base--;
279			while (base > ent->fts_name && *(base - 1) != '/')
280				base--;
281			len -= base - ent->fts_name;
282			if (state->directory)
283				state->presiz -= len + 1;
284		}
285	}
286	else
287	{
288		base = ent->fts_path + state->presiz + 1;
289		len = ent->fts_pathlen - state->presiz - 1;
290	}
291	len++;
292	if (state->directory)
293	{
294		if ((state->postsiz + len) > state->pathsiz && !(state->path = newof(state->path, char, state->pathsiz = roundof(state->postsiz + len, PATH_CHUNK), 0)))
295			error(ERROR_SYSTEM|3, "out of space");
296		if (state->hierarchy && ent->fts_level == 0 && strchr(base, '/'))
297		{
298			s = state->path + state->postsiz;
299			memcpy(s, base, len);
300			while (e = strchr(s, '/'))
301			{
302				*e = 0;
303				if (access(state->path, F_OK))
304				{
305					st.st_mode = state->missmode;
306					if (s = strrchr(s, '/'))
307					{
308						*s = 0;
309						stat(state->path, &st);
310						*s = '/';
311					}
312					if (mkdir(state->path, st.st_mode & S_IPERM))
313					{
314						error(ERROR_SYSTEM|2, "%s: cannot create directory -- %s ignored", state->path, ent->fts_path);
315						fts_set(NiL, ent, FTS_SKIP);
316						return 0;
317					}
318				}
319				*e++ = '/';
320				s = e;
321			}
322		}
323	}
324	switch (ent->fts_info)
325	{
326	case FTS_DP:
327		if (state->preserve && state->op != LN || ent->fts_level > 0 && (ent->fts_statp->st_mode & S_IRWXU) != S_IRWXU)
328		{
329			if (len && ent->fts_level > 0)
330				memcpy(state->path + state->postsiz, base, len);
331			else
332				state->path[state->postsiz] = 0;
333			if (stat(state->path, &st))
334				error(ERROR_SYSTEM|2, "%s: cannot stat", state->path);
335			else
336			{
337				if ((ent->fts_statp->st_mode & S_IPERM) != (st.st_mode & S_IPERM) && chmod(state->path, ent->fts_statp->st_mode & S_IPERM))
338					error(ERROR_SYSTEM|2, "%s: cannot reset directory mode to %s", state->path, fmtmode(st.st_mode & S_IPERM, 0) + 1);
339				if (state->preserve & (PRESERVE_IDS|PRESERVE_TIME))
340					preserve(state, state->path, &st, ent->fts_statp);
341			}
342		}
343		return 0;
344	case FTS_DNR:
345	case FTS_DNX:
346	case FTS_D:
347		if (!state->recursive)
348		{
349			fts_set(NiL, ent, FTS_SKIP);
350			if (state->op == CP)
351				error(1, "%s: directory -- copying as plain file", ent->fts_path);
352			else if (state->link == link && !state->force)
353			{
354				error(2, "%s: cannot link directory", ent->fts_path);
355				return 0;
356			}
357		}
358		else switch (ent->fts_info)
359		{
360		case FTS_DNR:
361			error(2, "%s: cannot read directory", ent->fts_path);
362			return 0;
363		case FTS_DNX:
364			error(2, "%s: cannot search directory", ent->fts_path);
365			fts_set(NiL, ent, FTS_SKIP);
366
367			/*FALLTHROUGH*/
368		case FTS_D:
369			if (state->directory)
370				memcpy(state->path + state->postsiz, base, len);
371			if (!(*state->stat)(state->path, &st))
372			{
373				if (!S_ISDIR(st.st_mode))
374				{
375					error(2, "%s: not a directory -- %s ignored", state->path, ent->fts_path);
376					return 0;
377				}
378			}
379			else if (mkdir(state->path, (ent->fts_statp->st_mode & S_IPERM)|(ent->fts_info == FTS_D ? S_IRWXU : 0)))
380			{
381				error(ERROR_SYSTEM|2, "%s: cannot create directory -- %s ignored", state->path, ent->fts_path);
382				fts_set(NiL, ent, FTS_SKIP);
383			}
384			if (!state->directory)
385			{
386				state->directory = 1;
387				state->path[state->postsiz++] = '/';
388				state->presiz--;
389			}
390			return 0;
391		}
392		break;
393	case FTS_ERR:
394	case FTS_NS:
395	case FTS_SLNONE:
396		if (state->link != pathsetlink)
397		{
398			error(2, "%s: not found", ent->fts_path);
399			return 0;
400		}
401		break;
402#if 0
403	case FTS_SL:
404		if (state->op == CP)
405		{
406			error(2, "%s: cannot copy non-terminal symbolic link", ent->fts_path);
407			return 0;
408		}
409		break;
410#endif
411	}
412	if (state->directory)
413		memcpy(state->path + state->postsiz, base, len);
414	if ((*state->stat)(state->path, &st))
415		st.st_mode = 0;
416	else if (state->update && !S_ISDIR(st.st_mode) && (unsigned long)ent->fts_statp->st_mtime < (unsigned long)st.st_mtime)
417	{
418		fts_set(NiL, ent, FTS_SKIP);
419		return 0;
420	}
421	else if (!state->fs3d || !iview(&st))
422	{
423		/*
424		 * target is in top 3d view
425		 */
426
427		if (state->op != LN && st.st_dev == ent->fts_statp->st_dev && st.st_ino == ent->fts_statp->st_ino)
428		{
429			if (state->op == MV)
430			{
431				/*
432				 * let rename() handle it
433				 */
434
435				if (state->verbose)
436					sfputr(sfstdout, state->path, '\n');
437				goto operate;
438			}
439			if (!state->official)
440				error(2, "%s: identical to %s", state->path, ent->fts_path);
441			return 0;
442		}
443		if (S_ISDIR(st.st_mode))
444		{
445			error(2, "%s: cannot %s existing directory", state->path, state->opname);
446			return 0;
447		}
448		if (state->verbose)
449			sfputr(sfstdout, state->path, '\n');
450		rm = state->remove || ent->fts_info == FTS_SL;
451		if (!rm || !state->force)
452		{
453			if (S_ISLNK(st.st_mode) && (n = -1) || (n = open(state->path, O_RDWR|O_BINARY|O_cloexec)) >= 0)
454			{
455				if (n >= 0)
456					close(n);
457				if (state->force)
458					/* ok */;
459				else if (state->interactive)
460				{
461					if ((n = astquery(-1, "%s %s? ", state->opname, state->path)) < 0 || sh_checksig(state->context))
462						return -1;
463					if (n)
464						return 0;
465				}
466				else if (state->op == LN)
467				{
468					error(2, "%s: cannot %s existing file", state->path, state->opname);
469					return 0;
470				}
471			}
472			else if (state->force)
473				rm = 1;
474			else
475			{
476				protection =
477#ifdef ETXTBSY
478				    errno == ETXTBSY ? "``running program''" :
479#endif
480				    st.st_uid != state->uid ? "``not owner''" :
481				    fmtmode(st.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO), 0) + 1;
482				if (state->interactive)
483				{
484					if ((n = astquery(-1, "override protection %s for %s? ", protection, state->path)) < 0 || sh_checksig(state->context))
485						return -1;
486					if (n)
487						return 0;
488					rm = 1;
489				}
490				else if (!rm)
491				{
492					error(2, "%s: cannot %s %s protection", state->path, state->opname, protection);
493					return 0;
494				}
495			}
496		}
497		switch (state->backup)
498		{
499		case BAK_existing:
500		case BAK_number:
501			v = 0;
502			if (s = strrchr(state->path, '/'))
503			{
504				e = state->path;
505				*s++ = 0;
506			}
507			else
508			{
509				e = (char*)dot;
510				s = state->path;
511			}
512			n = strlen(s);
513			if (fts = fts_open((char**)e, FTS_NOCHDIR|FTS_ONEPATH|FTS_PHYSICAL|FTS_NOPOSTORDER|FTS_NOSTAT|FTS_NOSEEDOTDIR, NiL))
514			{
515				while (sub = fts_read(fts))
516				{
517					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)
518						v = m;
519					if (sub->fts_level)
520						fts_set(NiL, sub, FTS_SKIP);
521				}
522				fts_close(fts);
523			}
524			if (s != state->path)
525				*--s = '/';
526			if (v || state->backup == BAK_number)
527			{
528				sfprintf(state->tmp, "%s.%s%d%s", state->path, state->suffix, v + 1, state->suffix);
529				goto backup;
530			}
531			/*FALLTHROUGH*/
532		case BAK_simple:
533			sfprintf(state->tmp, "%s%s", state->path, state->suffix);
534		backup:
535			if (!(s = sfstruse(state->tmp)))
536				error(ERROR_SYSTEM|3, "%s: out of space", state->path);
537			if (rename(state->path, s))
538			{
539				error(ERROR_SYSTEM|2, "%s: cannot backup to %s", state->path, s);
540				return 0;
541			}
542			break;
543		default:
544			if (rm && remove(state->path))
545			{
546				error(ERROR_SYSTEM|2, "%s: cannot remove", state->path);
547				return 0;
548			}
549			break;
550		}
551	}
552 operate:
553	switch (state->op)
554	{
555	case MV:
556		for (;;)
557		{
558			if (!rename(ent->fts_path, state->path))
559				return 0;
560			if (errno == ENOENT)
561				rm = 1;
562			else if (!rm && st.st_mode && !remove(state->path))
563			{
564				rm = 1;
565				continue;
566			}
567			if (errno != EXDEV && (rm || S_ISDIR(ent->fts_statp->st_mode)))
568			{
569				error(ERROR_SYSTEM|2, "%s: cannot rename to %s", ent->fts_path, state->path);
570				return 0;
571			}
572			else
573				break;
574		}
575		/*FALLTHROUGH*/
576	case CP:
577		if (S_ISLNK(ent->fts_statp->st_mode))
578		{
579			if ((n = pathgetlink(ent->fts_path, state->text, sizeof(state->text) - 1)) < 0)
580			{
581				error(ERROR_SYSTEM|2, "%s: cannot read symbolic link text", ent->fts_path);
582				return 0;
583			}
584			state->text[n] = 0;
585			if (pathsetlink(state->text, state->path))
586			{
587				error(ERROR_SYSTEM|2, "%s: cannot copy symbolic link to %s", ent->fts_path, state->path);
588				return 0;
589			}
590		}
591		else if (state->op == CP || S_ISREG(ent->fts_statp->st_mode) || S_ISDIR(ent->fts_statp->st_mode))
592		{
593			if (ent->fts_statp->st_size > 0 && (rfd = open(ent->fts_path, O_RDONLY|O_BINARY|O_cloexec)) < 0)
594			{
595				error(ERROR_SYSTEM|2, "%s: cannot read", ent->fts_path);
596				return 0;
597			}
598			else if ((wfd = open(state->path, (st.st_mode ? (state->wflags & ~O_EXCL) : state->wflags)|O_cloexec, ent->fts_statp->st_mode & state->perm)) < 0)
599			{
600				error(ERROR_SYSTEM|2, "%s: cannot write", state->path);
601				if (ent->fts_statp->st_size > 0)
602					close(rfd);
603				return 0;
604			}
605			else if (ent->fts_statp->st_size > 0)
606			{
607				if (!(ip = sfnew(NiL, NiL, SF_UNBOUND, rfd, SF_READ)))
608				{
609					error(ERROR_SYSTEM|2, "%s: %s read stream error", ent->fts_path, state->path);
610					close(rfd);
611					close(wfd);
612					return 0;
613				}
614				if (!(op = sfnew(NiL, NiL, SF_UNBOUND, wfd, SF_WRITE)))
615				{
616					error(ERROR_SYSTEM|2, "%s: %s write stream error", ent->fts_path, state->path);
617					close(wfd);
618					sfclose(ip);
619					return 0;
620				}
621				n = 0;
622				if (sfmove(ip, op, (Sfoff_t)SF_UNBOUND, -1) < 0)
623					n |= 3;
624				if (!sfeof(ip))
625					n |= 1;
626				if (sfsync(op) || state->sync && fsync(wfd) || sfclose(op))
627					n |= 2;
628				if (sfclose(ip))
629					n |= 1;
630				if (n)
631				{
632					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"));
633					return 0;
634				}
635			}
636			else
637				close(wfd);
638		}
639		else if (S_ISBLK(ent->fts_statp->st_mode) || S_ISCHR(ent->fts_statp->st_mode) || S_ISFIFO(ent->fts_statp->st_mode))
640		{
641			if (mknod(state->path, ent->fts_statp->st_mode, idevice(ent->fts_statp)))
642			{
643				error(ERROR_SYSTEM|2, "%s: cannot copy special file to %s", ent->fts_path, state->path);
644				return 0;
645			}
646		}
647		else
648		{
649			error(2, "%s: cannot copy -- unknown file type 0%o", ent->fts_path, S_ITYPE(ent->fts_statp->st_mode));
650			return 0;
651		}
652		if (state->preserve)
653		{
654			if (ent->fts_info != FTS_SL)
655			{
656				if (stat(state->path, &st))
657					error(ERROR_SYSTEM|2, "%s: cannot stat", state->path);
658				else
659				{
660					if ((state->preserve & PRESERVE_PERM) && (ent->fts_statp->st_mode & state->perm) != (st.st_mode & state->perm) && chmod(state->path, ent->fts_statp->st_mode & state->perm))
661						error(ERROR_SYSTEM|2, "%s: cannot reset mode to %s", state->path, fmtmode(st.st_mode & state->perm, 0) + 1);
662					if (state->preserve & (PRESERVE_IDS|PRESERVE_TIME))
663						preserve(state, state->path, &st, ent->fts_statp);
664				}
665			}
666			if (state->op == MV && remove(ent->fts_path))
667				error(ERROR_SYSTEM|1, "%s: cannot remove", ent->fts_path);
668		}
669		break;
670	case LN:
671		if ((*state->link)(ent->fts_path, state->path))
672			error(ERROR_SYSTEM|2, "%s: cannot link to %s", ent->fts_path, state->path);
673		break;
674	}
675	return 0;
676}
677
678int
679b_cp(int argc, register char** argv, Shbltin_t* context)
680{
681	register char*	file;
682	register char*	s;
683	char**		v;
684	char*		backup_type;
685	FTS*		fts;
686	FTSENT*		ent;
687	const char*	usage;
688	int		path_resolve;
689	int		standard;
690	struct stat	st;
691	State_t*	state;
692	Shbltin_t*	sh;
693	Shbltin_t*	cleanup = context;
694
695	cmdinit(argc, argv, context, ERROR_CATALOG, ERROR_NOTIFY);
696	if (!(sh = CMD_CONTEXT(context)) || !(state = (State_t*)sh->ptr))
697	{
698		if (!(state = newof(0, State_t, 1, 0)))
699			error(ERROR_SYSTEM|3, "out of space");
700		if (sh)
701			sh->ptr = state;
702	}
703	else
704		memset(state, 0, offsetof(State_t, INITSTATE));
705	state->context = context;
706	state->presiz = -1;
707	backup_type = 0;
708	state->flags = FTS_NOCHDIR|FTS_NOSEEDOTDIR;
709	state->uid = geteuid();
710	state->wflags = O_WRONLY|O_CREAT|O_TRUNC|O_BINARY;
711	if (!state->tmp && !(state->tmp = sfstropen()))
712		error(ERROR_SYSTEM|3, "out of space [tmp string]");
713	sfputr(state->tmp, usage_head, -1);
714	standard = !!conformance(0, 0);
715	switch (error_info.id[0])
716	{
717	case 'c':
718	case 'C':
719		sfputr(state->tmp, usage_cp, -1);
720		state->op = CP;
721		state->stat = stat;
722		path_resolve = -1;
723		break;
724	case 'l':
725	case 'L':
726		sfputr(state->tmp, usage_ln, -1);
727		state->op = LN;
728		state->flags |= FTS_PHYSICAL;
729		state->link = link;
730		state->remove = 1;
731		state->stat = lstat;
732		path_resolve = 1;
733		break;
734	case 'm':
735	case 'M':
736		sfputr(state->tmp, usage_mv, -1);
737		state->op = MV;
738		state->flags |= FTS_PHYSICAL;
739		state->preserve = PRESERVE_IDS|PRESERVE_PERM|PRESERVE_TIME;
740		state->stat = lstat;
741		path_resolve = 1;
742		break;
743	default:
744		error(3, "not implemented");
745		break;
746	}
747	sfputr(state->tmp, usage_tail, -1);
748	if (!(usage = sfstruse(state->tmp)))
749		error(ERROR_SYSTEM|3, "%s: out of space", state->path);
750	state->opname = state->op == CP ? ERROR_translate(0, 0, 0, "overwrite") : ERROR_translate(0, 0, 0, "replace");
751	for (;;)
752	{
753		switch (optget(argv, usage))
754		{
755		case 'a':
756			state->flags |= FTS_PHYSICAL;
757			state->preserve = PRESERVE_IDS|PRESERVE_PERM|PRESERVE_TIME;
758			state->recursive = 1;
759			path_resolve = 1;
760			continue;
761		case 'A':
762			s = opt_info.arg;
763			for (;;)
764			{
765				switch (*s++)
766				{
767				case 0:
768					break;
769				case 'e':
770					state->preserve |= PRESERVE_IDS|PRESERVE_PERM|PRESERVE_TIME;
771					continue;
772				case 'i':
773					state->preserve |= PRESERVE_IDS;
774					continue;
775				case 'p':
776					state->preserve |= PRESERVE_PERM;
777					continue;
778				case 't':
779					state->preserve |= PRESERVE_TIME;
780					continue;
781				default:
782					error(1, "%s=%c: unknown attribute flag", opt_info.option, *(s - 1));
783					continue;
784				}
785				break;
786			}
787			continue;
788		case 'b':
789			state->backup = 1;
790			continue;
791		case 'f':
792			state->force = 1;
793			if (state->op != CP || !standard)
794				state->interactive = 0;
795			continue;
796		case 'h':
797			state->hierarchy = 1;
798			continue;
799		case 'i':
800			state->interactive = 1;
801			if (state->op != CP || !standard)
802				state->force = 0;
803			continue;
804		case 'l':
805			state->op = LN;
806			state->link = link;
807			state->stat = lstat;
808			continue;
809		case 'p':
810			state->preserve = PRESERVE_IDS|PRESERVE_PERM|PRESERVE_TIME;
811			continue;
812		case 'r':
813			state->recursive = 1;
814			if (path_resolve < 0)
815				path_resolve = 0;
816			continue;
817		case 's':
818			state->op = LN;
819			state->link = pathsetlink;
820			state->stat = lstat;
821			continue;
822		case 'u':
823			state->update = 1;
824			continue;
825		case 'v':
826			state->verbose = 1;
827			continue;
828		case 'x':
829			state->flags |= FTS_XDEV;
830			continue;
831		case 'B':
832			backup_type = opt_info.arg;
833			state->backup = 1;
834			continue;
835		case 'F':
836#if _lib_fsync
837			state->sync = 1;
838#else
839			error(1, "%s not implemented on this system", opt_info.name);
840#endif
841			continue;
842		case 'H':
843			state->flags |= FTS_META|FTS_PHYSICAL;
844			path_resolve = 1;
845			continue;
846		case 'L':
847			state->flags &= ~FTS_PHYSICAL;
848			path_resolve = 1;
849			continue;
850		case 'P':
851			state->flags &= ~FTS_META;
852			state->flags |= FTS_PHYSICAL;
853			path_resolve = 1;
854			continue;
855		case 'R':
856			state->recursive = 1;
857			state->flags &= ~FTS_META;
858			state->flags |= FTS_PHYSICAL;
859			path_resolve = 1;
860			continue;
861		case 'S':
862			state->suffix = opt_info.arg;
863			continue;
864		case 'U':
865			state->remove = 1;
866			continue;
867		case '?':
868			error(ERROR_USAGE|4, "%s", opt_info.arg);
869			continue;
870		case ':':
871			error(2, "%s", opt_info.arg);
872			continue;
873		}
874		break;
875	}
876	argc -= opt_info.index + 1;
877	argv += opt_info.index;
878	if (*argv && streq(*argv, "-") && !streq(*(argv - 1), "--"))
879	{
880		argc--;
881		argv++;
882	}
883	if (!(v = (char**)stkalloc(stkstd, (argc + 2) * sizeof(char*))))
884		error(ERROR_SYSTEM|3, "out of space");
885	memcpy(v, argv, (argc + 1) * sizeof(char*));
886	argv = v;
887	if (!standard)
888	{
889		state->wflags |= O_EXCL;
890		if (!argc)
891		{
892			argc++;
893			argv[1] = (char*)dot;
894		}
895	}
896	if (state->backup)
897	{
898		if (!(file = backup_type) && !(backup_type = getenv("VERSION_CONTROL")))
899			state->backup = 0;
900		else
901			switch (strkey(backup_type))
902			{
903			case HASHKEY6('e','x','i','s','t','i'):
904			case HASHKEY5('e','x','i','s','t'):
905			case HASHKEY4('e','x','i','s'):
906			case HASHKEY3('e','x','i'):
907			case HASHKEY2('e','x'):
908			case HASHKEY1('e'):
909			case HASHKEY3('n','i','l'):
910			case HASHKEY2('n','i'):
911				state->backup = BAK_existing;
912				break;
913			case HASHKEY5('n','e','v','e','r'):
914			case HASHKEY4('n','e','v','e'):
915			case HASHKEY3('n','e','v'):
916			case HASHKEY2('n','e'):
917			case HASHKEY6('s','i','m','p','l','e'):
918			case HASHKEY5('s','i','m','p','l'):
919			case HASHKEY4('s','i','m','p'):
920			case HASHKEY3('s','i','m'):
921			case HASHKEY2('s','i'):
922			case HASHKEY1('s'):
923				state->backup = BAK_simple;
924				break;
925			case HASHKEY4('n','o','n','e'):
926			case HASHKEY3('n','o','n'):
927			case HASHKEY2('n','o'):
928			case HASHKEY3('o','f','f'):
929			case HASHKEY2('o','f'):
930			case HASHKEY1('o'):
931				state->backup = 0;
932				break;
933			case HASHKEY6('n','u','m','b','e','r'):
934			case HASHKEY5('n','u','m','b','e'):
935			case HASHKEY4('n','u','m','b'):
936			case HASHKEY3('n','u','m'):
937			case HASHKEY2('n','u'):
938			case HASHKEY1('t'):
939				state->backup = BAK_number;
940				break;
941			default:
942				if (file)
943					error(2, "%s: unknown backup type", backup_type);
944				break;
945			}
946		if (!state->suffix && !(state->suffix = getenv("SIMPLE_BACKUP_SUFFIX")))
947			state->suffix = "~";
948		state->suflen = strlen(state->suffix);
949	}
950	if (argc <= 0 || error_info.errors)
951		error(ERROR_USAGE|4, "%s", optusage(NiL));
952	if (!path_resolve)
953		state->flags |= fts_flags() | FTS_META;
954	file = argv[argc];
955	argv[argc] = 0;
956	if (s = strrchr(file, '/'))
957	{
958		while (*s == '/')
959			s++;
960		if (!(!*s || *s == '.' && (!*++s || *s == '.' && !*++s)))
961			s = 0;
962	}
963	if (file != (char*)dot)
964		pathcanon(file, 0, 0);
965	if (!(state->directory = !stat(file, &st) && S_ISDIR(st.st_mode)) && argc > 1)
966		error(ERROR_USAGE|4, "%s", optusage(NiL));
967	if (s && !state->directory)
968		error(3, "%s: not a directory", file);
969	if ((state->fs3d = fs3d(FS3D_TEST)) && strmatch(file, "...|*/...|.../*"))
970		state->official = 1;
971	state->postsiz = strlen(file);
972	if (state->pathsiz < roundof(state->postsiz + 2, PATH_CHUNK) && !(state->path = newof(state->path, char, state->pathsiz = roundof(state->postsiz + 2, PATH_CHUNK), 0)))
973		error(ERROR_SYSTEM|3, "out of space");
974	memcpy(state->path, file, state->postsiz + 1);
975	if (state->directory && state->path[state->postsiz - 1] != '/')
976		state->path[state->postsiz++] = '/';
977	if (state->hierarchy)
978	{
979		if (!state->directory)
980			error(3, "%s: last argument must be a directory", file);
981		state->missmode = st.st_mode;
982	}
983	state->perm = state->uid ? S_IPERM : (S_IPERM & ~S_ISVTX);
984	if (!state->recursive)
985		state->flags |= FTS_TOP;
986	if (fts = fts_open(argv, state->flags, NiL))
987	{
988		while (!sh_checksig(context) && (ent = fts_read(fts)) && !visit(state, ent));
989		fts_close(fts);
990	}
991	else if (state->link != pathsetlink)
992		switch (state->op)
993		{
994		case CP:
995			error(ERROR_SYSTEM|2, "%s: cannot copy", argv[0]);
996			break;
997		case LN:
998			error(ERROR_SYSTEM|2, "%s: cannot link", argv[0]);
999			break;
1000		case MV:
1001			error(ERROR_SYSTEM|2, "%s: cannot move", argv[0]);
1002			break;
1003		}
1004	else if ((*state->link)(*argv, state->path))
1005		error(ERROR_SYSTEM|2, "%s: cannot link to %s", *argv, state->path);
1006	if (cleanup && !sh)
1007	{
1008		if (state->path)
1009			free(state->path);
1010		free(state);
1011	}
1012	return error_info.errors != 0;
1013}
1014