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