xref: /illumos-gate/usr/src/contrib/ast/src/lib/libcmd/cp.c (revision b30d193948be5a7794d7ae3ba0ed9c2f72c88e0f)
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 
29 static const char usage_head[] =
30 "[-?@(#)$Id: cp (AT&T Research) 2012-04-20 $\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 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 
67 static 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 
76 static 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 
89 static 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 
157 typedef 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 
199 static const char	dot[2] = { '.' };
200 
201 /*
202  * preserve support
203  */
204 
205 static void
preserve(State_t * state,const char * path,struct stat * ns,struct stat * os)206 preserve(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 
235 static int
visit(State_t * state,register FTSENT * ent)236 visit(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 
678 int
b_cp(int argc,register char ** argv,Shbltin_t * context)679 b_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