1 /* @(#)copy.c 1.54 21/08/20 Copyright 1984, 86-90, 95-97, 99, 2000-2021 J. Schilling */
2 #include <schily/mconfig.h>
3 #ifndef lint
4 static UConst char sccsid[] =
5 "@(#)copy.c 1.54 21/08/20 Copyright 1984, 86-90, 95-97, 99, 2000-2021 J. Schilling";
6 #endif
7 /*
8 * copy files ...
9 *
10 * Copyright (c) 1984, 86-90, 95-97, 99, 2000-2021 J. Schilling
11 */
12 /*
13 * The contents of this file are subject to the terms of the
14 * Common Development and Distribution License, Version 1.0 only
15 * (the "License"). You may not use this file except in compliance
16 * with the License.
17 *
18 * See the file CDDL.Schily.txt in this distribution for details.
19 * A copy of the CDDL is also available via the Internet at
20 * http://www.opensource.org/licenses/cddl1.txt
21 *
22 * When distributing Covered Code, include this CDDL HEADER in each
23 * file and include the License file CDDL.Schily.txt from this distribution.
24 */
25
26 /*
27 * Operation modes:
28 *
29 * copy [-r] [-q] [-v] [-i] [-s] [-o] [-sparse] file1 file2
30 * copies file1 to file2
31 *
32 * copy [-r] [-q] [-v] [-i] [-s] [-o] [-sparse] file1...filen todir
33 * copies file1...filen to todir
34 *
35 * copy [-r] [-q] [-v] [-i] [-s] [-o] [-sparse] from
36 * prompts "To: "
37 * for <to filename>
38 *
39 * copy [-r] [-v] [-s] [-o] [-sparse] -i
40 * prompts "From: " and "To: "
41 * until a null name is read.
42 */
43
44 #include <schily/stdio.h>
45 #include <schily/stdlib.h>
46 #include <schily/unistd.h>
47 #include <schily/standard.h>
48 #include <schily/utypes.h>
49 #include <schily/varargs.h>
50 #include <schily/errno.h>
51 #include <schily/param.h> /* DEV_BSIZE */
52 #include <schily/stat.h>
53 #include <schily/time.h>
54 #include <schily/fcntl.h>
55 #include <schily/string.h>
56 #include <schily/maxpath.h>
57 #include <schily/libport.h>
58 #define GT_COMERR /* #define comerr gtcomerr */
59 #define GT_ERROR /* #define error gterror */
60 #include <schily/schily.h>
61 #include <schily/nlsdefs.h>
62
63 /* Probably only needed for Mark Williams C */
64 #ifndef EEXIST
65 #define EEXIST -1 /* XXX ??? */
66 #endif
67 #define is_dir(sp) S_ISDIR((sp)->st_mode)
68 #define is_link(sp) S_ISLNK((sp)->st_mode)
69 #ifndef S_IFLNK
70 #define lstat stat
71 #endif
72 #ifndef HAVE_LCHOWN
73 #define lchown chown
74 #endif
75 #define file_type(sp) ((int)((sp)->st_mode & S_IFMT))
76 #define file_size(sp) ((sp)->st_size)
77 #define disk_size(sp) ((sp)->st_size)
78 #define file_ino(sp) ((sp)->st_ino)
79 #define file_dev(sp) ((sp)->st_dev)
80 #define STATBUF struct stat
81
82 #ifdef HAVE_ST_BLOCKS
83 #undef disk_size
84 #if defined(hpux) || defined(__hpux)
85 #define disk_size(sp) ((sp)->st_blocks * (off_t)1024)
86 #else
87 #define disk_size(sp) ((sp)->st_blocks * (off_t)DEV_BSIZE)
88 #endif
89 #endif
90
91 #ifndef HAVE_VALLOC
92 #define valloc malloc
93 #endif
94 #define COPY_SIZE (8*1024*1024)
95 #define COPY_SIZE_SMALL (16*1024)
96
97 char *copybuf;
98 char *zeroblk;
99 int copybsize = COPY_SIZE;
100 int uid;
101 int verbose = FALSE;
102 int setall = FALSE;
103 int setown = FALSE;
104 int setgrp = FALSE;
105 int olddate = FALSE;
106 int setperm = FALSE;
107 int Recurse = FALSE;
108 int recurse = FALSE;
109 int is_recurse = FALSE;
110 int query = FALSE;
111 int interactive = FALSE;
112 int sparseflag = FALSE;
113 int force_hole = FALSE;
114
115 LOCAL void usage __PR((int ret));
116 #ifdef _FASCII /* Mark Williams C */
117 LOCAL void setup_env __PR((void));
118 #endif
119 #if tos
120 LOCAL BOOL is_root __PR((char *n));
121 LOCAL int mystat __PR((char *name, STATBUF *statbuf));
122 #endif
123 EXPORT int main __PR((int ac, char **av));
124 LOCAL int do_one_arg __PR((char *from));
125 LOCAL int do_interactive __PR((void));
126 LOCAL int copy __PR((char *from, char *to));
127 LOCAL int copyfile __PR((char *from, char *to, off_t fromsize,
128 off_t disksize));
129 LOCAL int copy_link __PR((char *from, char *to));
130 LOCAL int do_recurse __PR((char *from, char *to));
131 LOCAL BOOL samefile __PR((STATBUF * sp1, STATBUF * sp2));
132 LOCAL void set_access __PR((STATBUF * fromstat, char *to,
133 BOOL to_exists));
134 LOCAL void etoolong __PR((char *name));
135 LOCAL BOOL yes __PR((char *form, ...));
136 LOCAL BOOL getbase __PR((char *path, char *basenamep,
137 size_t bsize));
138 LOCAL void mygetline __PR((char *pstr, char *str, int len));
139 LOCAL int xutimes __PR((char *name, STATBUF * sp));
140 LOCAL BOOL doremove __PR((char *name));
141 #if defined(SEEK_HOLE) && defined(SEEK_DATA)
142 LOCAL BOOL sparse_file __PR((int fd, off_t fsize));
143 LOCAL int sparse_copy __PR((int fin, int fout, char *from, char *to,
144 off_t fsize));
145 LOCAL int write_end_hole __PR((int fout, char *to, off_t fsize));
146 #endif
147
148 LOCAL void
usage(ret)149 usage(ret)
150 int ret;
151 {
152 error(_("Usage:\tcopy -i [options]\n"));
153 error(_("\tcopy [options] from to\n"));
154 error(_("\tcopy [options] file1..filen target_dir\n"));
155 error(_("Options:\n"));
156 error(_("\t-q\t\tquery to confirm each copy\n"));
157 error(_("\t-i\t\tinteractive copy / prompt to confirm to overwrite files\n"));
158 error(_("\t-R\t\trecursive copy - POSIX mode\n"));
159 error(_("\t-r\t\trecursive copy\n"));
160 error(_("\t-v\t\tbe verbose\n"));
161 error(_("\t-setowner\trestore the original user\n"));
162 error(_("\t-setgrp\t\trestore the original group\n"));
163 error(_("\t-s\t\trestore the original user and group\n"));
164 error(_("\t-olddate|-o\trestore the original access times\n"));
165 error(_("\t-p\t\tpreserve file permissons ids and acces times\n"));
166 error(_("\t-sparse\t\tpreserve holes in sparse regular files\n"));
167 error(_("\t-force-hole\ttry to insert holes into all copied regular files\n"));
168 error(_("\t-help\t\tPrint this help.\n"));
169 error(_("\t-version\tPrint version information and exit.\n"));
170 exit(ret);
171 }
172
173 #ifdef _FASCII /* Mark Williams C */
174 char *_stksize = (char *)8192;
175
176 LOCAL void
setup_env()177 setup_env()
178 {
179 register char *ep;
180 extern char *getenv();
181
182 if ((ep = getenv("PATH")) == (char *)NULL || *ep == '\0')
183 putenv("PATH=.bin,,\\bin,\\lib");
184
185 if ((ep = getenv("SUFF")) == (char *)NULL || *ep == '\0')
186 putenv("SUFF=,.prg,.tos,.ttp");
187
188 if ((ep = getenv("LIBPATH")) == (char *)NULL || *ep == '\0')
189 putenv("LIBPATH=\\lib,\\bin");
190
191 if ((ep = getenv("TMPDIR")) == (char *)NULL || *ep == '\0')
192 putenv("TMPDIR=\\tmp");
193
194 if ((ep = getenv("INCDIR")) == (char *)NULL || *ep == '\0')
195 putenv("INCDIR=\\include");
196 }
197 #endif
198
199 #if tos
200
201 #define is_char(c) ((c) >= 'a' && (c) <= 'z' || (c) >= 'A' && (c) <= 'Z')
202
203 LOCAL BOOL
is_root(n)204 is_root(n)
205 register char *n;
206 {
207 return (strlen(n) == 2 && is_char(n[0]) && n[1] == ':');
208 }
209
210 LOCAL int
mystat(name,statbuf)211 mystat(name, statbuf)
212 char *name;
213 STATBUF *statbuf;
214 {
215 int ret;
216
217 if ((ret = stat(name, statbuf)) < 0) {
218 if (is_root(name)) {
219 statbuf->st_mode = S_IFDIR;
220 return (0);
221 }
222 }
223 return (ret);
224 }
225
226 #define stat mystat
227
228 #endif
229
230 LOCAL char *opts =
231 /* CSTYLED */
232 "q,i,v,R,r,s,setowner,setgrp,o,olddate,p,is_recurse,sparse,force-hole,help,h,version";
233
234 EXPORT int
main(ac,av)235 main(ac, av)
236 int ac;
237 char *av[];
238 {
239 int cac;
240 char * const * cav;
241 char *lastarg = NULL;
242 #if defined(USE_NLS)
243 char *dir;
244 #endif
245 STATBUF statbuf;
246 char toname[PATH_MAX];
247 char frombase[PATH_MAX];
248 int cnt;
249 int filecount;
250 int ret;
251 BOOL last_is_dir = FALSE;
252 BOOL help = FALSE;
253 BOOL prversion = FALSE;
254
255 save_args(ac, av);
256
257 file_raise((FILE *)NULL, FALSE);
258
259 (void) setlocale(LC_ALL, "");
260
261 #if defined(USE_NLS)
262 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
263 #define TEXT_DOMAIN "copy" /* Use this only if it weren't */
264 #endif
265 dir = searchfileinpath("share/locale", F_OK,
266 SIP_ANY_FILE|SIP_NO_PATH, NULL);
267 if (dir)
268 (void) bindtextdomain(TEXT_DOMAIN, dir);
269 else
270 #if defined(PROTOTYPES) && defined(INS_BASE)
271 (void) bindtextdomain(TEXT_DOMAIN, INS_BASE "/share/locale");
272 #else
273 (void) bindtextdomain(TEXT_DOMAIN, "/usr/share/locale");
274 #endif
275 (void) textdomain(TEXT_DOMAIN);
276 #endif
277
278 #ifdef _FASCII /* Mark Williams C */
279 stderr->_ff &= ~_FSTBUF; /* setbuf was called ??? */
280
281 setup_env();
282 #endif
283 uid = geteuid();
284 cac = --ac;
285 cav = ++av;
286
287 if (getallargs(&cac, &cav, opts,
288 &query,
289 &interactive,
290 &verbose,
291 &Recurse,
292 &recurse,
293 &setall, &setown, &setgrp,
294 &olddate, &olddate,
295 &setperm,
296 &is_recurse,
297 &sparseflag, &force_hole,
298 &help, &help, &prversion) < 0) {
299 errmsgno(EX_BAD, _("Bad flag: '%s'.\n"), cav[0]);
300 usage(EX_BAD);
301 }
302 if (help)
303 usage(0);
304 if (prversion) {
305 /* CSTYLED */
306 gtprintf("Copy release %s %s (%s-%s-%s) Copyright (C) 1984, 86-90, 95-97, 99, 2000-2021 %s\n",
307 "1.54", "2021/08/20",
308 HOST_CPU, HOST_VENDOR, HOST_OS,
309 _("Joerg Schilling"));
310 exit(0);
311 }
312
313 if (Recurse)
314 recurse = TRUE;
315 if (setperm) { setall = TRUE; olddate = TRUE; };
316 if (setall) { setown = TRUE; setgrp = TRUE; };
317
318 filecount = 0;
319 cac = ac;
320 cav = av;
321
322 while (getfiles(&cac, &cav, opts) > 0) {
323 filecount++;
324 lastarg = cav[0];
325 cac--;
326 cav++;
327 }
328 if (filecount == 0 && !interactive)
329 usage(EX_BAD);
330
331 copybuf = valloc(copybsize);
332 if (copybuf == NULL) {
333 copybsize = COPY_SIZE_SMALL;
334 copybuf = valloc(copybsize);
335 }
336 if (copybuf == NULL)
337 comerr(("Cannot allocate copy buffer.\n"));
338 fillbytes(copybuf, copybsize, '\0');
339 zeroblk = ©buf[1024];
340
341
342 if (interactive && filecount == 0)
343 exit(do_interactive());
344 if (filecount == 1)
345 exit(do_one_arg(lastarg));
346 if (filecount > 2) {
347 if (stat(lastarg, &statbuf) < 0)
348 comerr("'%s'%s%s", lastarg, _(" must be a directory, "),
349 _("but cannot get status on it\n"));
350 if (!is_dir(&statbuf)) {
351 errmsgno(EX_BAD, _("'%s' is not a directory.\n"),
352 lastarg);
353 usage(EX_BAD);
354 } else {
355 last_is_dir = TRUE;
356 }
357 } else {
358 if (stat(lastarg, &statbuf) >= 0)
359 if (is_dir(&statbuf))
360 last_is_dir = TRUE;
361 }
362 cac = ac;
363 cav = av;
364 cnt = 0;
365 ret = 0;
366 for (; getfiles(&cac, &cav, opts) > 0; cac--, cav++) {
367 char *tonmp;
368
369 if (++cnt >= filecount)
370 exit(ret);
371 if (filecount > 2 || is_recurse || (Recurse && last_is_dir)) {
372 if (!getbase(cav[0], frombase, sizeof (frombase))) {
373 etoolong(cav[0]);
374 ret = 1;
375 continue;
376 }
377 if (snprintf(toname, sizeof (toname), "%s%s%s",
378 lastarg, PATH_DELIM_STR, frombase) >=
379 sizeof (toname)) {
380 etoolong(toname);
381 ret = 1;
382 continue;
383 }
384 tonmp = toname;
385 } else {
386 tonmp = lastarg;
387 }
388 if (copy(cav[0], tonmp) < 0)
389 ret = 1;
390 }
391 exit(ret);
392 return (ret); /* Keep lint happy */
393 }
394
395 LOCAL int
do_one_arg(from)396 do_one_arg(from)
397 char *from;
398 {
399 char toname[PATH_MAX];
400
401 mygetline(_("To:"), toname, sizeof (toname));
402 if (toname[0] != '\0' && copy(from, toname) < 0)
403 return (1);
404 return (0);
405 }
406
407 LOCAL int
do_interactive()408 do_interactive()
409 {
410 char fromname[PATH_MAX];
411 char toname[PATH_MAX];
412 int ret = 0;
413
414 for (;;) {
415 mygetline(_("From:"), fromname, sizeof (fromname));
416 if (fromname[0] == '\0')
417 return (ret);
418 mygetline(_("To:"), toname, sizeof (toname));
419 if (toname[0] == '\0')
420 return (ret);
421 if (copy(fromname, toname) < 0)
422 ret = 1;
423 }
424 }
425
426 /*
427 * Copy a single file
428 */
429 LOCAL int
copy(from,to)430 copy(from, to)
431 char *from;
432 char *to;
433 {
434 char name[PATH_MAX];
435 char frombase[PATH_MAX];
436 STATBUF fromstat;
437 STATBUF tostat;
438 BOOL to_is_dir = FALSE;
439 BOOL to_exists = FALSE;
440
441 if (!getbase(from, frombase, sizeof (frombase))) {
442 etoolong(from);
443 return (-1);
444 }
445
446 if (is_recurse && (streql(frombase, ".") || streql(frombase, ".."))) {
447 return (0);
448 }
449 if (query && !interactive && !yes(_("%s to %s?"), from, to)) {
450 return (0);
451 }
452 if (lstat(from, &fromstat) < 0) {
453 /*
454 * First stat() to verify whether a file with the literal name
455 * ".*" or "*" exists.
456 */
457 if (is_recurse &&
458 (streql(frombase, ".*") || streql(frombase, "*"))) {
459 return (0);
460 }
461 errmsg(_("Cannot get status of '%s'.\n"), from);
462 return (-1);
463 }
464 if (stat(to, &tostat) >= 0) {
465 if (samefile(&fromstat, &tostat)) {
466 errmsgno(EEXIST,
467 _("Will not copy '%s' to itself ('%s').\n"),
468 from, to);
469 return (-1);
470 }
471 if (!(to_is_dir = is_dir(&tostat)) &&
472 interactive && !yes(_("overwrite %s? "), to)) {
473 return (0);
474 }
475 to_exists = TRUE;
476 }
477 if (is_dir(&fromstat)) {
478 if (!to_is_dir && mkdir(to, 0777) < 0) {
479 errmsg(_("Cannot make dir '%s'.\n"), to);
480 return (-1);
481 }
482 } else if (to_is_dir) {
483 if (snprintf(name, sizeof (name), "%s%s%s",
484 to, PATH_DELIM_STR, frombase) >= sizeof (name)) {
485 etoolong(name);
486 return (-1);
487 }
488 to = name;
489 if (stat(to, &tostat) >= 0) {
490 if (samefile(&fromstat, &tostat)) {
491 errmsgno(EEXIST,
492 _("Will not copy '%s' to itself ('%s').\n"),
493 from, to);
494 return (-1);
495 }
496 if (interactive && !yes(_("overwrite %s? "), to)) {
497 return (0);
498 }
499 to_exists = TRUE;
500 } else {
501 to_exists = FALSE;
502 }
503 }
504 if (!is_dir(&fromstat)) {
505 switch (file_type(&fromstat)) {
506 default:
507 case S_IFREG:
508 if (copyfile(from, to,
509 file_size(&fromstat), disk_size(&fromstat)) < 0) {
510 return (-1);
511 }
512 break;
513 #ifdef S_IFCHR
514 case S_IFCHR:
515 #endif
516 #ifdef S_IFBLK
517 case S_IFBLK:
518 #endif
519 #if defined(S_IFCHR) || defined(S_IFBLK)
520 #if defined(HAVE_MKNOD) && defined(HAVE_ST_RDEV)
521 if (mknod(to, fromstat.st_mode, fromstat.st_rdev) < 0) {
522 #else
523 seterrno(EINVAL);
524 if (1) {
525 #endif
526 errmsg(_("Could not make device '%s'.\n"), to);
527 return (-1);
528 }
529 break;
530 #endif
531 #ifdef S_IFIFO
532 case S_IFIFO:
533 #ifdef HAVE_MKFIFO
534 if (mkfifo(to, fromstat.st_mode) < 0) {
535 #else
536 #ifdef HAVE_MKNOD
537 #ifdef HAVE_ST_RDEV
538 if (mknod(to, fromstat.st_mode, fromstat.st_rdev) < 0) {
539
540 #else
541 if (mknod(to, fromstat.st_mode, (dev_t)0) < 0) {
542 #endif
543 #else
544 seterrno(EINVAL);
545 if (1) {
546 #endif
547 #endif
548 errmsg(_("Could not make fifo '%s'.\n"), to);
549 return (-1);
550 }
551 break;
552 #endif /* S_IFIFO */
553 #ifdef S_IFLNK
554 case S_IFLNK:
555 if (copy_link(from, to) < 0) {
556 return (-1);
557 }
558 break;
559 #endif
560 #ifdef S_IFSOCK
561 case S_IFSOCK:
562 errmsgno(EX_BAD, _("Cannot copy socket '%s'.\n"), from);
563 return (-1);
564 #endif
565 }
566 }
567 if (!is_dir(&fromstat) || !recurse)
568 set_access(&fromstat, to, to_exists);
569 if (verbose)
570 error(_("Copied '%s' to '%s'.\n"), from, to);
571 fflush(stderr);
572 if (is_dir(&fromstat) && recurse) {
573 int ret;
574
575 ret = do_recurse(from, to);
576 set_access(&fromstat, to, to_exists);
577 return (ret);
578 }
579 return (0);
580 }
581
582 LOCAL int
copyfile(from,to,fromsize,disksize)583 copyfile(from, to, fromsize, disksize)
584 char *from;
585 char *to;
586 off_t fromsize;
587 off_t disksize;
588 {
589 register int fin;
590 register int fout;
591 register int cnt;
592 register char *bp = copybuf;
593 register int size;
594 register off_t newpos = 0;
595 int err = 0;
596 int serrno;
597 STATBUF statbuf;
598 BOOL do_sparse = FALSE;
599
600 if ((fin = open(from, O_RDONLY)) < 0) {
601 errmsg(_("Cannot open '%s'.\n"), from);
602 return (-1);
603 }
604 if ((fout = creat(to, 0666)) < 0) {
605 /*
606 * If cannot stat, don't remove
607 */
608 serrno = geterrno();
609 if (lstat(to, &statbuf) >= 0 &&
610 interactive && yes(_("remove %s? "), to) &&
611 doremove(to) &&
612 (fout = creat(to, 0666)) >= 0)
613 goto docopy;
614 errmsgno(serrno, _("Cannot create '%s'.\n"), to);
615 close(fin);
616 return (-1);
617 }
618 docopy:
619
620 #if defined(SEEK_HOLE) && defined(SEEK_DATA)
621 if (sparse_file(fin, fromsize)) {
622 return (sparse_copy(fin, fout, from, to, fromsize));
623 }
624 #endif
625
626 if (sparseflag && (fromsize > disksize))
627 do_sparse = TRUE;
628 else if (force_hole)
629 do_sparse = TRUE;
630 size = do_sparse ? 512 : copybsize;
631
632 while ((cnt = read(fin, bp, size)) > 0) {
633 newpos += cnt;
634 if (do_sparse && newpos < fromsize &&
635 cmpbytes(bp, zeroblk, size) >= size) {
636 if (lseek(fout, newpos, SEEK_SET) < 0) {
637 err = geterrno();
638 errmsgno(err,
639 _("A seek error occurred on '%s'.\n"),
640 to);
641 cnt = 0;
642 break;
643 }
644 } else if (write(fout, bp, cnt) != cnt) {
645 err = geterrno();
646 errmsgno(err, _("A write error occurred on '%s'.\n"),
647 to);
648 cnt = 0;
649 break;
650 }
651 }
652 if (cnt != 0)
653 err = geterrno();
654
655 close(fin);
656 close(fout);
657 if (cnt != 0)
658 errmsgno(err, _("A read error occurred on '%s'.\n"), from);
659 if (err || cnt != 0)
660 return (-1);
661 return (0);
662 }
663
664 #ifdef S_IFLNK
665 LOCAL int
copy_link(from,to)666 copy_link(from, to)
667 char *from;
668 char *to;
669 {
670 int cnt;
671
672 if ((cnt = readlink(from, copybuf, copybsize)) < 0) {
673 errmsg(_("Could not read symbolic link '%s'.\n"), from);
674 return (-1);
675 }
676 copybuf[cnt] = '\0';
677 if (symlink(copybuf, to) < 0) {
678 errmsg(_("Could not make symbolic link '%s'.\n"), to);
679 return (-1);
680 }
681 return (0);
682 }
683 #endif
684
685 LOCAL int
do_recurse(from,to)686 do_recurse(from, to)
687 char *from;
688 char *to;
689 {
690 char cmdbuf[2*PATH_MAX + 80];
691
692 if (snprintf(cmdbuf, sizeof (cmdbuf),
693 "%s -is_recurse %s%s%s%s%s%s%s%s%s%s%s %s%s.* %s%s* %s",
694 saved_av0(),
695 query ? "-q " : "",
696 interactive ? "-i " : "",
697 verbose ? "-v " : "",
698 Recurse ? "-R " : "",
699 recurse ? "-r " : "",
700 setown ? "-setowner " : "",
701 setgrp ? "-setgrp " : "",
702 olddate ? "-olddate " : "",
703 setperm ? "-p " : "",
704 sparseflag ? "-sparse " : "",
705 force_hole ? "-force-hole " : "",
706 from, PATH_DELIM_STR, from, PATH_DELIM_STR,
707 to) >= sizeof (cmdbuf)) {
708 etoolong(cmdbuf);
709 return (-1);
710 }
711 return (system(cmdbuf));
712 }
713
714 /*
715 * Check for same file ID (filesystem and inode number)
716 */
717 LOCAL BOOL
samefile(sp1,sp2)718 samefile(sp1, sp2)
719 STATBUF *sp1;
720 STATBUF *sp2;
721 {
722 #ifdef DEBUG
723 error("file_dev(sp1): %d file_dev(sp2): %d\n",
724 file_dev(sp1), file_dev(sp2));
725 error("file_ino(sp1): %d file_ino(sp2): %d\n",
726 file_ino(sp1), file_ino(sp2));
727 #endif
728 return (file_dev(sp1) == file_dev(sp2) &&
729 file_ino(sp1) == file_ino(sp2));
730 }
731
732 LOCAL void
set_access(fromstat,to,to_exists)733 set_access(fromstat, to, to_exists)
734 STATBUF *fromstat;
735 char *to;
736 BOOL to_exists;
737 {
738 int id_ok = TRUE;
739
740 #if defined(HAVE_CHOWN) || defined(HAVE_LCHOWN)
741 if (uid == 0 && setown && lchown(to, fromstat->st_uid, -1) < 0) {
742 errmsg(_("Unable to set owner of '%s'.\n"), to);
743 id_ok = FALSE;
744 }
745 if (uid == 0 && setgrp && lchown(to, -1, fromstat->st_gid) < 0) {
746 errmsg(_("Unable to set group of '%s'.\n"), to);
747 id_ok = FALSE;
748 }
749 #endif
750 if (!is_link(fromstat)) {
751 mode_t omode = fromstat->st_mode;
752
753 if (olddate && xutimes(to, fromstat) < 0)
754 errmsg(_("Unable to set date of '%s'.\n"), to);
755
756 if (!id_ok)
757 omode &= ~(S_ISUID|S_ISGID);
758
759 if ((setperm || ! to_exists) && chmod(to, omode) < 0)
760 errmsg(_("Unable to set access modes of '%s'.\n"), to);
761 }
762 }
763
764 LOCAL void
etoolong(name)765 etoolong(name)
766 char *name;
767 {
768 errmsgno(EX_BAD, _("Path name '%s' too long.\n"), name);
769 }
770
771 /* VARARGS1 */
772 #ifdef PROTOTYPES
773 LOCAL BOOL
yes(char * form,...)774 yes(char *form, ...)
775 #else
776 LOCAL BOOL
777 yes(form, va_alist)
778 char *form;
779 va_dcl
780 #endif
781 {
782 va_list args;
783 char ansbuf[128];
784
785 #ifdef PROTOTYPES
786 va_start(args, form);
787 #else
788 va_start(args);
789 #endif
790 printf("%r", form, args);
791 va_end(args);
792 flush();
793 getline(ansbuf, sizeof (ansbuf));
794 if (streql(ansbuf, "y") || streql(ansbuf, "yes"))
795 return (TRUE);
796 else
797 return (FALSE);
798 }
799
800 LOCAL BOOL
getbase(path,basenamep,bsize)801 getbase(path, basenamep, bsize)
802 char *path;
803 register char *basenamep;
804 size_t bsize;
805 {
806 register char *p;
807
808 #ifdef tos
809 if (strlen(path) > 1 && path[1] == ':')
810 path += 2;
811 #endif
812 for (p = &path[strlen(path)-1]; p > &path[0]; p--) {
813 #ifdef tos
814 if (*p == '\\') {
815 #else
816 if (*p == '/') {
817 #endif
818 p++;
819 break;
820 }
821 }
822
823 if (strlcpy(basenamep, p, bsize) >= bsize)
824 return (FALSE);
825 return (TRUE);
826 }
827
828 LOCAL void
mygetline(pstr,str,len)829 mygetline(pstr, str, len)
830 char *pstr;
831 char *str;
832 int len;
833 {
834 for (;;) {
835 printf("%s", pstr);
836 flush();
837 getline(str, len);
838 #ifdef tos
839 if (strchr(str, '\\') && streql(getenv("SLASH"), "off")) {
840 #else
841 if (strchr(str, '/') && streql(getenv("SLASH"), "off")) {
842 #endif
843 error(_("restricted.\n"));
844 continue;
845 } else {
846 break;
847 }
848 }
849 }
850
851 LOCAL int
xutimes(name,sp)852 xutimes(name, sp)
853 char *name;
854 STATBUF *sp;
855 {
856 struct timespec tp[2];
857
858 tp[0].tv_sec = sp->st_atime;
859 tp[1].tv_sec = sp->st_mtime;
860
861 tp[0].tv_nsec = stat_ansecs(sp);
862 tp[1].tv_nsec = stat_mnsecs(sp);
863
864 return (utimens(name, tp));
865 }
866
867 LOCAL BOOL
doremove(name)868 doremove(name)
869 char *name;
870 {
871 int err;
872
873 /*
874 * Only unlink non directories or empty directories
875 * XXX need to implement the -remove_recursive flag
876 */
877 if (rmdir(name) < 0) {
878 err = geterrno();
879 if (err == EACCES)
880 goto cannot;
881
882 #if defined(__CYGWIN32__) || defined(__CYGWIN__)
883 if (err == ENOTEMPTY) {
884 /*
885 * Cygwin returns ENOTEMPTY if 'name'
886 * is not a dir.
887 * XXX Never do this on UNIX.
888 * XXX If you are root, you may unlink
889 * XXX even nonempty directories.
890 */
891 err = ENOTDIR;
892 }
893 #endif
894 if (err == ENOTDIR) {
895 if (unlink(name) < 0) {
896 err = geterrno();
897 goto cannot;
898 }
899 }
900 }
901 return (TRUE);
902 cannot:
903 errmsgno(err, _("File '%s' not removed.\n"), name);
904 return (FALSE);
905 }
906
907 #if defined(SEEK_HOLE) && defined(SEEK_DATA)
908 LOCAL BOOL
sparse_file(fd,fsize)909 sparse_file(fd, fsize)
910 int fd;
911 off_t fsize;
912 {
913 off_t pos;
914
915 /*
916 * If we have been compiled on an OS that supports SEEK_HOLE but run
917 * on an OS that does not support SEEK_HOLE, we get EINVAL.
918 * If the underlying filesystem does not support the SEEK_HOLE call,
919 * we get ENOTSUP. In all other cases, we will get either the position
920 * of the first real hole in the file or statb.st_size in case the file
921 * definitely has no holes.
922 */
923 pos = lseek(fd, (off_t)0, SEEK_HOLE); /* Check for first hole */
924 if (pos == (off_t)-1) /* SEEK_HOLE not supported */
925 return (FALSE);
926
927 if (pos != 0) /* Not at pos 0: seek back */
928 (void) lseek(fd, (off_t)0, SEEK_SET);
929
930 if (pos >= fsize) /* Definitely not sparse */
931 return (FALSE);
932 return (TRUE); /* Definitely sparse */
933 }
934
935 LOCAL int
sparse_copy(fin,fout,from,to,fsize)936 sparse_copy(fin, fout, from, to, fsize)
937 register int fin;
938 register int fout;
939 char *from;
940 char *to;
941 off_t fsize;
942 {
943 off_t off = 0;
944 off_t data;
945 off_t hole;
946 register off_t newpos = 0;
947 int err = 0;
948 register int cnt;
949 register int size;
950 register char *bp = copybuf;
951
952 for (;;) {
953 data = lseek(fin, off, SEEK_DATA);
954 if (data == (off_t)-1 || data > fsize)
955 break;
956 hole = lseek(fin, data, SEEK_HOLE);
957 if (hole == (off_t)-1 || hole > fsize)
958 break;
959
960 size = copybsize;
961 newpos = data;
962 if ((newpos + size) > hole)
963 size = hole - newpos;
964
965 if (lseek(fin, data, SEEK_SET) == (off_t)-1) {
966 err = geterrno();
967 errmsgno(err, _("A seek error occurred on '%s'.\n"),
968 from);
969 goto fail;
970 }
971 if (lseek(fout, data, SEEK_SET) == (off_t)-1) {
972 err = geterrno();
973 errmsgno(err, _("A seek error occurred on '%s'.\n"),
974 to);
975 goto fail;
976 }
977 cnt = 0;
978 while ((newpos < hole) && (cnt = read(fin, bp, size)) > 0) {
979 newpos += cnt;
980 if (write(fout, bp, cnt) != cnt) {
981 err = geterrno();
982 errmsgno(err,
983 _("A write error occurred on '%s'.\n"),
984 to);
985 cnt = 0;
986 goto fail;
987 }
988 size = copybsize;
989 if ((newpos + size) > hole)
990 size = hole - newpos;
991 }
992 if (cnt < 0) {
993 err = geterrno();
994 errmsgno(err,
995 _("A read error occurred on '%s'.\n"),
996 from);
997 goto fail;
998 }
999 if (cnt == 0) {
1000 errmsgno(EX_BAD, _("File '%s' shrunk.\n"), from);
1001 err = ENDOFFILE;
1002 goto fail;
1003 }
1004 off = hole; /* Start for next data chunk */
1005 }
1006
1007 if (newpos < fsize) {
1008 #ifdef HAVE_FTRUNCATE
1009 /*
1010 * In order to prevent Solaris from allocating space at the end
1011 * of the file we need to shrink the file. For this reason, we
1012 * first create the file a bit too large.
1013 */
1014 off = fsize;
1015
1016 #ifdef _PC_MIN_HOLE_SIZE
1017 size = fpathconf(fout, _PC_MIN_HOLE_SIZE);
1018 #else
1019 size = 0;
1020 #endif
1021 if (size <= 0)
1022 size = 8192;
1023
1024 if ((OFF_T_MAX - off) > size)
1025 (void) ftruncate(fout, off+size);
1026
1027 if (ftruncate(fout, off) < 0)
1028 err = write_end_hole(fout, to, fsize);
1029 #else
1030 err = write_end_hole(fout, to, fsize);
1031 #endif
1032 }
1033
1034 fail:
1035 close(fin);
1036 close(fout);
1037 if (err)
1038 return (-1);
1039 return (0);
1040 }
1041
1042 LOCAL int
write_end_hole(fout,to,fsize)1043 write_end_hole(fout, to, fsize)
1044 int fout;
1045 char *to;
1046 off_t fsize;
1047 {
1048 int err = 0;
1049
1050 if (lseek(fout, fsize-1, SEEK_SET) == (off_t)-1) {
1051 err = geterrno();
1052 } else if (write(fout, "", 1) != 1) {
1053 err = geterrno();
1054 }
1055 if (err)
1056 errmsgno(err, _("A seek error occurred on '%s'.\n"), to);
1057 return (err);
1058 }
1059 #endif
1060