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	= &copybuf[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