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