xref: /titanic_52/usr/src/cmd/mv/mv.c (revision 73427c57f824c3ec3b396181b163f37d50c5b3b1)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
27 /*	  All Rights Reserved  	*/
28 
29 /*
30  * University Copyright- Copyright (c) 1982, 1986, 1988
31  * The Regents of the University of California
32  * All Rights Reserved
33  *
34  * University Acknowledgment- Portions of this document are derived from
35  * software developed by the University of California, Berkeley, and its
36  * contributors.
37  */
38 
39 #pragma ident	"%Z%%M%	%I%	%E% SMI"
40 
41 /*
42  * Combined mv/cp/ln command:
43  *	mv file1 file2
44  *	mv dir1 dir2
45  *	mv file1 ... filen dir1
46  */
47 #include <stdio.h>
48 #include <sys/types.h>
49 #include <sys/stat.h>
50 #include <sys/avl.h>
51 #include <sys/mman.h>
52 #include <fcntl.h>
53 #include <sys/time.h>
54 #include <signal.h>
55 #include <errno.h>
56 #include <dirent.h>
57 #include <stdlib.h>
58 #include <locale.h>
59 #include <langinfo.h>
60 #include <stdarg.h>
61 #include <string.h>
62 #include <unistd.h>
63 #include <limits.h>
64 #include <sys/acl.h>
65 #include <libcmdutils.h>
66 #include <aclutils.h>
67 
68 #define	FTYPE(A)	(A.st_mode)
69 #define	FMODE(A)	(A.st_mode)
70 #define	UID(A)		(A.st_uid)
71 #define	GID(A)		(A.st_gid)
72 #define	IDENTICAL(A, B)	(A.st_dev == B.st_dev && A.st_ino == B.st_ino)
73 #define	ISBLK(A)	((A.st_mode & S_IFMT) == S_IFBLK)
74 #define	ISCHR(A)	((A.st_mode & S_IFMT) == S_IFCHR)
75 #define	ISDIR(A)	((A.st_mode & S_IFMT) == S_IFDIR)
76 #define	ISDOOR(A)	((A.st_mode & S_IFMT) == S_IFDOOR)
77 #define	ISFIFO(A)	((A.st_mode & S_IFMT) == S_IFIFO)
78 #define	ISLNK(A)	((A.st_mode & S_IFMT) == S_IFLNK)
79 #define	ISREG(A)	(((A).st_mode & S_IFMT) == S_IFREG)
80 #define	ISDEV(A)	((A.st_mode & S_IFMT) == S_IFCHR || \
81 			(A.st_mode & S_IFMT) == S_IFBLK || \
82 			(A.st_mode & S_IFMT) == S_IFIFO)
83 
84 #define	BLKSIZE	4096
85 #define	PATHSIZE 1024
86 #define	DOT	"."
87 #define	DOTDOT	".."
88 #define	DELIM	'/'
89 #define	EQ(x, y)	(strcmp(x, y) == 0)
90 #define	FALSE	0
91 #define	MODEBITS (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)
92 #define	TRUE 1
93 #define	MAXMAPSIZE	(1024*1024*8)	/* map at most 8MB */
94 #define	SMALLFILESIZE	(32*1024)	/* don't use mmap on little files */
95 
96 static char		*dname(char *);
97 static int		getresp(void);
98 static int		lnkfil(char *, char *);
99 static int		cpymve(char *, char *);
100 static int		chkfiles(char *, char **);
101 static int		rcopy(char *, char *);
102 static int		chk_different(char *, char *);
103 static int		chg_time(char *, struct stat);
104 static int		chg_mode(char *, uid_t, gid_t, mode_t);
105 static int		copydir(char *, char *);
106 static int		copyspecial(char *);
107 static int		getrealpath(char *, char *);
108 static void		usage(void);
109 static void		Perror(char *);
110 static void		Perror2(char *, char *);
111 static int		writefile(int, int, char *, char *,
112 			    struct stat *, struct stat *);
113 static int		use_stdin(void);
114 static int		copyattributes(char *, char *);
115 static void		timestruc_to_timeval(timestruc_t *, struct timeval *);
116 static tree_node_t	*create_tnode(dev_t, ino_t);
117 
118 extern	int 		errno;
119 extern  char 		*optarg;
120 extern	int 		optind, opterr;
121 static struct stat 	s1, s2;
122 static int 		cpy = FALSE;
123 static int 		mve = FALSE;
124 static int 		lnk = FALSE;
125 static char		*cmd;
126 static int		silent = 0;
127 static int		fflg = 0;
128 static int		iflg = 0;
129 static int		pflg = 0;
130 static int		Rflg = 0;	/* recursive copy */
131 static int		rflg = 0;	/* recursive copy */
132 static int		sflg = 0;
133 static int		Hflg = 0;	/* follow cmd line arg symlink to dir */
134 static int		Lflg = 0;	/* follow symlinks */
135 static int		Pflg = 0;	/* do not follow symlinks */
136 static int		atflg = 0;
137 static int		attrsilent = 0;
138 static int		targetexists = 0;
139 static char		yeschr[SCHAR_MAX + 2];
140 static char		nochr[SCHAR_MAX + 2];
141 static int		cmdarg;		/* command line argument */
142 static avl_tree_t	*stree = NULL;	/* source file inode search tree */
143 static acl_t		*s1acl;
144 
145 int
146 main(int argc, char *argv[])
147 {
148 	int c, i, r, errflg = 0;
149 	char target[PATH_MAX];
150 	int (*move)(char *, char *);
151 
152 	/*
153 	 * Determine command invoked (mv, cp, or ln)
154 	 */
155 
156 	if (cmd = strrchr(argv[0], '/'))
157 		++cmd;
158 	else
159 		cmd = argv[0];
160 
161 	/*
162 	 * Set flags based on command.
163 	 */
164 
165 	(void) setlocale(LC_ALL, "");
166 #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
167 #define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it weren't */
168 #endif
169 	(void) textdomain(TEXT_DOMAIN);
170 
171 	(void) strncpy(yeschr, nl_langinfo(YESSTR), SCHAR_MAX + 2);
172 	(void) strncpy(nochr, nl_langinfo(NOSTR), SCHAR_MAX + 2);
173 
174 	if (EQ(cmd, "mv"))
175 		mve = TRUE;
176 	else if (EQ(cmd, "ln"))
177 		lnk = TRUE;
178 	else if (EQ(cmd, "cp"))
179 		cpy = TRUE;
180 	else {
181 		(void) fprintf(stderr,
182 		    gettext("Invalid command name (%s); expecting "
183 		    "mv, cp, or ln.\n"), cmd);
184 		exit(1);
185 	}
186 
187 	/*
188 	 * Check for options:
189 	 * 	cp  -r|-R [-H|-L|-P] [-fip@] file1 [file2 ...] target
190 	 * 	cp [-fiprR@] file1 [file2 ...] target
191 	 *	ln [-f] [-n] [-s] file1 [file2 ...] target
192 	 *	ln [-f] [-n] [-s] file1 [file2 ...]
193 	 *	mv [-f|i] file1 [file2 ...] target
194 	 *	mv [-f|i] dir1 target
195 	 */
196 
197 	if (cpy) {
198 		while ((c = getopt(argc, argv, "fHiLpPrR@")) != EOF)
199 			switch (c) {
200 			case 'f':
201 				fflg++;
202 				break;
203 			case 'i':
204 				iflg++;
205 				break;
206 			case 'p':
207 				pflg++;
208 #ifdef XPG4
209 				attrsilent = 1;
210 				atflg = 0;
211 #else
212 				if (atflg == 0)
213 					attrsilent = 1;
214 #endif
215 				break;
216 			case 'H':
217 				/*
218 				 * If more than one of -H, -L, or -P are
219 				 * specified, only the last option specified
220 				 * determines the behavior.
221 				 */
222 				Lflg = Pflg = 0;
223 				Hflg++;
224 				break;
225 			case 'L':
226 				Hflg = Pflg = 0;
227 				Lflg++;
228 				break;
229 			case 'P':
230 				Lflg = Hflg = 0;
231 				Pflg++;
232 				break;
233 			case 'R':
234 				/*
235 				 * The default behavior of cp -R|-r
236 				 * when specified without -H|-L|-P
237 				 * is -L.
238 				 */
239 				Rflg++;
240 				/*FALLTHROUGH*/
241 			case 'r':
242 				rflg++;
243 				break;
244 			case '@':
245 				atflg++;
246 				attrsilent = 0;
247 #ifdef XPG4
248 				pflg = 0;
249 #endif
250 				break;
251 			default:
252 				errflg++;
253 			}
254 
255 		/* -R or -r must be specified with -H, -L, or -P */
256 		if ((Hflg || Lflg || Pflg) && !(Rflg || rflg)) {
257 			errflg++;
258 		}
259 
260 	} else if (mve) {
261 		while ((c = getopt(argc, argv, "fis")) != EOF)
262 			switch (c) {
263 			case 'f':
264 				silent++;
265 #ifdef XPG4
266 				iflg = 0;
267 #endif
268 				break;
269 			case 'i':
270 				iflg++;
271 #ifdef XPG4
272 				silent = 0;
273 #endif
274 				break;
275 			default:
276 				errflg++;
277 			}
278 	} else { /* ln */
279 		while ((c = getopt(argc, argv, "fns")) != EOF)
280 			switch (c) {
281 			case 'f':
282 				silent++;
283 				break;
284 			case 'n':
285 				/* silently ignored; this is the default */
286 				break;
287 			case 's':
288 				sflg++;
289 				break;
290 			default:
291 				errflg++;
292 			}
293 	}
294 
295 	/*
296 	 * For BSD compatibility allow - to delimit the end of
297 	 * options for mv.
298 	 */
299 	if (mve && optind < argc && (strcmp(argv[optind], "-") == 0))
300 		optind++;
301 
302 	/*
303 	 * Check for sufficient arguments
304 	 * or a usage error.
305 	 */
306 
307 	argc -= optind;
308 	argv  = &argv[optind];
309 
310 	if ((argc < 2 && lnk != TRUE) || (argc < 1 && lnk == TRUE)) {
311 		(void) fprintf(stderr,
312 		    gettext("%s: Insufficient arguments (%d)\n"),
313 		    cmd, argc);
314 		usage();
315 	}
316 
317 	if (errflg != 0)
318 		usage();
319 
320 	/*
321 	 * If there is more than a source and target,
322 	 * the last argument (the target) must be a directory
323 	 * which really exists.
324 	 */
325 
326 	if (argc > 2) {
327 		if (stat(argv[argc-1], &s2) < 0) {
328 			(void) fprintf(stderr,
329 			    gettext("%s: %s not found\n"),
330 			    cmd, argv[argc-1]);
331 			exit(2);
332 		}
333 
334 		if (!ISDIR(s2)) {
335 			(void) fprintf(stderr,
336 			    gettext("%s: Target %s must be a directory\n"),
337 			    cmd, argv[argc-1]);
338 			usage();
339 		}
340 	}
341 
342 	if (strlen(argv[argc-1]) >= PATH_MAX) {
343 		(void) fprintf(stderr,
344 		    gettext("%s: Target %s file name length exceeds PATH_MAX"
345 		    " %d\n"), cmd, argv[argc-1], PATH_MAX);
346 		exit(78);
347 	}
348 
349 	if (argc == 1) {
350 		if (!lnk)
351 			usage();
352 		(void) strcpy(target, ".");
353 	} else {
354 		(void) strcpy(target, argv[--argc]);
355 	}
356 
357 	/*
358 	 * Perform a multiple argument mv|cp|ln by
359 	 * multiple invocations of cpymve() or lnkfil().
360 	 */
361 	if (lnk)
362 		move = lnkfil;
363 	else
364 		move = cpymve;
365 
366 	r = 0;
367 	for (i = 0; i < argc; i++) {
368 		stree = NULL;
369 		cmdarg = 1;
370 		r += move(argv[i], target);
371 	}
372 
373 	/*
374 	 * Show errors by nonzero exit code.
375 	 */
376 
377 	return (r?2:0);
378 }
379 
380 static int
381 lnkfil(char *source, char *target)
382 {
383 	char	*buf = NULL;
384 
385 	if (sflg) {
386 
387 		/*
388 		 * If target is a directory make complete
389 		 * name of the new symbolic link within that
390 		 * directory.
391 		 */
392 
393 		if ((stat(target, &s2) >= 0) && ISDIR(s2)) {
394 			size_t len;
395 
396 			len = strlen(target) + strlen(dname(source)) + 4;
397 			if ((buf = (char *)malloc(len)) == NULL) {
398 				(void) fprintf(stderr,
399 				    gettext("%s: Insufficient memory "
400 				    "to %s %s\n"), cmd, cmd, source);
401 				exit(3);
402 			}
403 			(void) snprintf(buf, len, "%s/%s",
404 			    target, dname(source));
405 			target = buf;
406 		}
407 
408 		/*
409 		 * Check to see if the file exists already
410 		 */
411 
412 		if ((stat(target, &s2) == 0)) {
413 			/*
414 			 * Check if the silent flag is set ie. the -f option
415 			 * is used.  If so, use unlink to remove the current
416 			 * target to replace with the new target, specified
417 			 * on the command line.  Proceed with symlink.
418 			 */
419 			if (silent) {
420 				if (unlink(target) < 0) {
421 					(void) fprintf(stderr,
422 					    gettext("%s: cannot unlink %s: "),
423 					    cmd, target);
424 					perror("");
425 					return (1);
426 				}
427 			}
428 		}
429 
430 
431 		/*
432 		 * Create a symbolic link to the source.
433 		 */
434 
435 		if (symlink(source, target) < 0) {
436 			(void) fprintf(stderr,
437 			    gettext("%s: cannot create %s: "),
438 			    cmd, target);
439 			perror("");
440 			if (buf != NULL)
441 				free(buf);
442 			return (1);
443 		}
444 		if (buf != NULL)
445 			free(buf);
446 		return (0);
447 	}
448 
449 	switch (chkfiles(source, &target)) {
450 		case 1: return (1);
451 		case 2: return (0);
452 			/* default - fall through */
453 	}
454 
455 	/*
456 	 * Make sure source file is not a directory,
457 	 * we can't link directories...
458 	 */
459 
460 	if (ISDIR(s1)) {
461 		(void) fprintf(stderr,
462 		    gettext("%s: %s is a directory\n"), cmd, source);
463 		return (1);
464 	}
465 
466 	/*
467 	 * hard link, call link() and return.
468 	 */
469 
470 	if (link(source, target) < 0) {
471 		if (errno == EXDEV)
472 			(void) fprintf(stderr,
473 			    gettext("%s: %s is on a different file system\n"),
474 			    cmd, target);
475 		else {
476 			(void) fprintf(stderr,
477 			    gettext("%s: cannot create link %s: "),
478 			    cmd, target);
479 			perror("");
480 		}
481 		if (buf != NULL)
482 			free(buf);
483 		return (1);
484 	} else {
485 		if (buf != NULL)
486 			free(buf);
487 		return (0);
488 	}
489 }
490 
491 static int
492 cpymve(char *source, char *target)
493 {
494 	int	n;
495 	int fi, fo;
496 	int ret = 0;
497 	int attret = 0;
498 	int errno_save;
499 
500 	switch (chkfiles(source, &target)) {
501 		case 1: return (1);
502 		case 2: return (0);
503 			/* default - fall through */
504 	}
505 
506 	/*
507 	 * If it's a recursive copy and source
508 	 * is a directory, then call rcopy (from copydir).
509 	 */
510 	if (cpy) {
511 		if (ISDIR(s1)) {
512 			int		rc;
513 			avl_index_t	where = 0;
514 			tree_node_t	*tnode;
515 			tree_node_t	*tptr;
516 			dev_t		save_dev = s1.st_dev;
517 			ino_t		save_ino = s1.st_ino;
518 
519 			/*
520 			 * We will be recursing into the directory so
521 			 * save the inode information to a search tree
522 			 * to avoid getting into an endless loop.
523 			 */
524 			if ((rc = add_tnode(&stree, save_dev, save_ino)) != 1) {
525 				if (rc == 0) {
526 					/*
527 					 * We've already visited this directory.
528 					 * Don't remove the search tree entry
529 					 * to make sure we don't get into an
530 					 * endless loop if revisited from a
531 					 * different part of the hierarchy.
532 					 */
533 					(void) fprintf(stderr, gettext(
534 					    "%s: cycle detected: %s\n"),
535 					    cmd, source);
536 				} else {
537 					Perror(source);
538 				}
539 				return (1);
540 			}
541 
542 			cmdarg = 0;
543 			rc = copydir(source, target);
544 
545 			/*
546 			 * Create a tnode to get an index to the matching
547 			 * node (same dev and inode) in the search tree,
548 			 * then use the index to remove the matching node
549 			 * so it we do not wrongly detect a cycle when
550 			 * revisiting this directory from another part of
551 			 * the hierarchy.
552 			 */
553 			if ((tnode = create_tnode(save_dev,
554 			    save_ino)) == NULL) {
555 				Perror(source);
556 				return (1);
557 			}
558 			if ((tptr = avl_find(stree, tnode, &where)) != NULL) {
559 				avl_remove(stree, tptr);
560 			}
561 			free(tptr);
562 			free(tnode);
563 			return (rc);
564 
565 		} else if (ISDEV(s1) && Rflg) {
566 			return (copyspecial(target));
567 		} else {
568 			goto copy;
569 		}
570 	}
571 
572 	if (mve) {
573 		if (rename(source, target) >= 0)
574 			return (0);
575 		if (errno != EXDEV) {
576 			if (errno == ENOTDIR && ISDIR(s1)) {
577 				(void) fprintf(stderr,
578 				    gettext("%s: %s is a directory\n"),
579 				    cmd, source);
580 				return (1);
581 			}
582 			(void) fprintf(stderr,
583 			    gettext("%s: cannot rename %s to %s: "),
584 			    cmd, source, target);
585 			perror("");
586 			return (1);
587 		}
588 
589 		/*
590 		 * cannot move a non-directory (source) onto an existing
591 		 * directory (target)
592 		 *
593 		 */
594 		if (targetexists && ISDIR(s2) && (!ISDIR(s1))) {
595 			(void) fprintf(stderr,
596 			    gettext("%s: cannot mv a non directory %s "
597 			    "over existing directory"
598 			    " %s \n"), cmd, source, target);
599 			return (1);
600 		}
601 		if (ISDIR(s1)) {
602 #ifdef XPG4
603 			if (targetexists && ISDIR(s2)) {
604 				/* existing target dir must be empty */
605 				if (rmdir(target) < 0) {
606 					errno_save = errno;
607 					(void) fprintf(stderr,
608 					    gettext("%s: cannot rmdir %s: "),
609 					    cmd, target);
610 					errno = errno_save;
611 					perror("");
612 					return (1);
613 				}
614 			}
615 #endif
616 			if ((n =  copydir(source, target)) == 0)
617 				(void) rmdir(source);
618 			return (n);
619 		}
620 
621 		/* doors can't be moved across filesystems */
622 		if (ISDOOR(s1)) {
623 			(void) fprintf(stderr,
624 			    gettext("%s: %s: can't move door "
625 			    "across file systems\n"), cmd, source);
626 			return (1);
627 		}
628 		/*
629 		 * File can't be renamed, try to recreate the symbolic
630 		 * link or special device, or copy the file wholesale
631 		 * between file systems.
632 		 */
633 		if (ISLNK(s1)) {
634 			register int	m;
635 			register mode_t md;
636 			char symln[PATH_MAX + 1];
637 
638 			if (targetexists && unlink(target) < 0) {
639 				(void) fprintf(stderr,
640 				    gettext("%s: cannot unlink %s: "),
641 				    cmd, target);
642 				perror("");
643 				return (1);
644 			}
645 
646 			if ((m = readlink(source, symln,
647 			    sizeof (symln) - 1)) < 0) {
648 				Perror(source);
649 				return (1);
650 			}
651 			symln[m] = '\0';
652 
653 			md = umask(~(s1.st_mode & MODEBITS));
654 			if (symlink(symln, target) < 0) {
655 				Perror(target);
656 				return (1);
657 			}
658 			(void) umask(md);
659 			m = lchown(target, UID(s1), GID(s1));
660 #ifdef XPG4
661 			if (m < 0) {
662 				(void) fprintf(stderr, gettext("%s: cannot"
663 				    " change owner and group of"
664 				    " %s: "), cmd, target);
665 				perror("");
666 			}
667 #endif
668 			goto cleanup;
669 		}
670 		if (ISDEV(s1)) {
671 
672 			if (targetexists && unlink(target) < 0) {
673 				(void) fprintf(stderr,
674 				    gettext("%s: cannot unlink %s: "),
675 				    cmd, target);
676 				perror("");
677 				return (1);
678 			}
679 
680 			if (mknod(target, s1.st_mode, s1.st_rdev) < 0) {
681 				Perror(target);
682 				return (1);
683 			}
684 
685 			(void) chg_mode(target, UID(s1), GID(s1), FMODE(s1));
686 			(void) chg_time(target, s1);
687 			goto cleanup;
688 		}
689 
690 		if (ISREG(s1)) {
691 			if (ISDIR(s2)) {
692 				if (targetexists && rmdir(target) < 0) {
693 					(void) fprintf(stderr,
694 					    gettext("%s: cannot rmdir %s: "),
695 					    cmd, target);
696 					perror("");
697 					return (1);
698 				}
699 			} else {
700 				if (targetexists && unlink(target) < 0) {
701 					(void) fprintf(stderr,
702 					    gettext("%s: cannot unlink %s: "),
703 					    cmd, target);
704 					perror("");
705 					return (1);
706 				}
707 			}
708 
709 
710 copy:
711 			/*
712 			 * If the source file is a symlink, and either
713 			 * -P or -H flag (only if -H is specified and the
714 			 * source file is not a command line argument)
715 			 * were specified, then action is taken on the symlink
716 			 * itself, not the file referenced by the symlink.
717 			 * Note: this is executed for 'cp' only.
718 			 */
719 			if (cpy && (Pflg || (Hflg && !cmdarg)) && (ISLNK(s1))) {
720 				int	m;
721 				mode_t	md;
722 				char symln[PATH_MAX + 1];
723 
724 				m = readlink(source, symln, sizeof (symln) - 1);
725 
726 				if (m < 0) {
727 					Perror(source);
728 					return (1);
729 				}
730 				symln[m] = '\0';
731 
732 				/*
733 				 * Copy the sym link to the target.
734 				 * Note: If the target exists, write a
735 				 * diagnostic message, do nothing more
736 				 * with the source file, and return to
737 				 * process any remaining files.
738 				 */
739 				md = umask(~(s1.st_mode & MODEBITS));
740 				if (symlink(symln, target) < 0) {
741 					Perror(target);
742 					return (1);
743 				}
744 				(void) umask(md);
745 				m = lchown(target, UID(s1), GID(s1));
746 
747 				if (m < 0) {
748 					(void) fprintf(stderr, gettext(
749 					    "cp: cannot change owner and "
750 					    "group of %s:"), target);
751 					perror("");
752 				}
753 			} else {
754 				/*
755 				 * Copy the file.  If it happens to be a
756 				 * symlink, copy the file referenced
757 				 * by the symlink.
758 				 */
759 				fi = open(source, O_RDONLY);
760 				if (fi < 0) {
761 					(void) fprintf(stderr,
762 					    gettext("%s: cannot open %s: "),
763 					    cmd, source);
764 					perror("");
765 					return (1);
766 				}
767 
768 				fo = creat(target, s1.st_mode & MODEBITS);
769 				if (fo < 0) {
770 					/*
771 					 * If -f and creat() failed, unlink
772 					 * and try again.
773 					 */
774 					if (fflg) {
775 						(void) unlink(target);
776 						fo = creat(target,
777 						    s1.st_mode & MODEBITS);
778 					}
779 				}
780 				if (fo < 0) {
781 					(void) fprintf(stderr,
782 					    gettext("%s: cannot create %s: "),
783 					    cmd, target);
784 					perror("");
785 					(void) close(fi);
786 					return (1);
787 				} else {
788 					/* stat the new file, its used below */
789 					(void) stat(target, &s2);
790 				}
791 
792 				/*
793 				 * Set target's permissions to the source
794 				 * before any copying so that any partially
795 				 * copied file will have the source's
796 				 * permissions (at most) or umask permissions
797 				 * whichever is the most restrictive.
798 				 *
799 				 * ACL for regular files
800 				 */
801 
802 				if (pflg || mve) {
803 					(void) chmod(target, FMODE(s1));
804 					if (s1acl != NULL) {
805 						if ((acl_set(target,
806 						    s1acl)) < 0) {
807 							if (pflg || mve) {
808 								(void) fprintf(
809 								    stderr,
810 					"%s: failed to set acl entries on %s\n",
811 								    cmd,
812 								    target);
813 							}
814 							acl_free(s1acl);
815 							s1acl = NULL;
816 							/*
817 							 * else: silent and
818 							 * continue
819 							 */
820 						}
821 					}
822 				}
823 
824 				if (fstat(fi, &s1) < 0) {
825 					(void) fprintf(stderr,
826 					    gettext("%s: cannot access %s\n"),
827 					    cmd, source);
828 					return (1);
829 				}
830 				if (IDENTICAL(s1, s2)) {
831 					(void) fprintf(stderr,
832 					    gettext(
833 					    "%s: %s and %s are identical\n"),
834 					    cmd, source, target);
835 					return (1);
836 				}
837 
838 				if (writefile(fi, fo, source, target,
839 				    &s1, &s2) != 0) {
840 					return (1);
841 				}
842 
843 				(void) close(fi);
844 				if (close(fo) < 0) {
845 					Perror2(target, "write");
846 					return (1);
847 				}
848 			}
849 
850 			if (pflg || atflg || mve) {
851 				attret = copyattributes(source, target);
852 				if (attret != 0 && !attrsilent) {
853 					(void) fprintf(stderr, gettext(
854 						"%s: Failed to preserve"
855 						" extended attributes of file"
856 						" %s\n"), cmd, source);
857 				}
858 
859 				if (mve && attret != 0) {
860 					(void) unlink(target);
861 					return (1);
862 				}
863 
864 				if (attrsilent)
865 					attret = 0;
866 			}
867 
868 			/*
869 			 * XPG4: the write system call will clear setgid
870 			 * and setuid bits, so set them again.
871 			 */
872 			if (pflg || mve) {
873 				if ((ret = chg_mode(target, UID(s1), GID(s1),
874 				    FMODE(s1))) > 0)
875 					return (1);
876 				/*
877 				 * Reapply ACL, since chmod may have
878 				 * altered ACL
879 				 */
880 				if (s1acl != NULL) {
881 					if ((acl_set(target, s1acl)) < 0) {
882 						if (pflg || mve) {
883 							(void) fprintf(
884 							    stderr,
885 					"%s: failed to set acl entries on %s\n",
886 							    cmd,
887 							    target);
888 						}
889 						/*
890 						 * else: silent and
891 						 * continue
892 						 */
893 					}
894 				}
895 				if ((ret = chg_time(target, s1)) > 0)
896 					return (1);
897 			}
898 			if (cpy) {
899 				if (attret != 0)
900 					return (1);
901 				return (0);
902 			}
903 			goto cleanup;
904 		}
905 		(void) fprintf(stderr,
906 		    gettext("%s: %s: unknown file type 0x%x\n"), cmd,
907 			source, (s1.st_mode & S_IFMT));
908 		return (1);
909 
910 cleanup:
911 		if (unlink(source) < 0) {
912 			(void) unlink(target);
913 			(void) fprintf(stderr,
914 			    gettext("%s: cannot unlink %s: "),
915 			    cmd, source);
916 			perror("");
917 			return (1);
918 		}
919 		if (attret != 0)
920 			return (attret);
921 		return (ret);
922 	}
923 	/*NOTREACHED*/
924 	return (ret);
925 }
926 
927 static int
928 writefile(int fi, int fo, char *source, char *target,
929 		struct stat *s1p, struct stat *s2p)
930 {
931 	int mapsize, munmapsize;
932 	caddr_t cp;
933 	off_t filesize = s1p->st_size;
934 	off_t offset;
935 	int nbytes;
936 	int remains;
937 	int n;
938 
939 	if (ISREG(*s1p) && s1p->st_size > SMALLFILESIZE) {
940 		/*
941 		 * Determine size of initial mapping.  This will determine the
942 		 * size of the address space chunk we work with.  This initial
943 		 * mapping size will be used to perform munmap() in the future.
944 		 */
945 		mapsize = MAXMAPSIZE;
946 		if (s1p->st_size < mapsize) mapsize = s1p->st_size;
947 		munmapsize = mapsize;
948 
949 		/*
950 		 * Mmap time!
951 		 */
952 		if ((cp = mmap((caddr_t)NULL, mapsize, PROT_READ,
953 		    MAP_SHARED, fi, (off_t)0)) == MAP_FAILED)
954 			mapsize = 0;   /* can't mmap today */
955 	} else
956 		mapsize = 0;
957 
958 	if (mapsize != 0) {
959 		offset = 0;
960 
961 		for (;;) {
962 			nbytes = write(fo, cp, mapsize);
963 			/*
964 			 * if we write less than the mmaped size it's due to a
965 			 * media error on the input file or out of space on
966 			 * the output file.  So, try again, and look for errno.
967 			 */
968 			if ((nbytes >= 0) && (nbytes != (int)mapsize)) {
969 				remains = mapsize - nbytes;
970 				while (remains > 0) {
971 					nbytes = write(fo,
972 					    cp + mapsize - remains, remains);
973 					if (nbytes < 0) {
974 						if (errno == ENOSPC)
975 							Perror(target);
976 						else
977 							Perror(source);
978 						(void) close(fi);
979 						(void) close(fo);
980 						(void) munmap(cp, munmapsize);
981 						if (ISREG(*s2p))
982 							(void) unlink(target);
983 						return (1);
984 					}
985 					remains -= nbytes;
986 					if (remains == 0)
987 						nbytes = mapsize;
988 				}
989 			}
990 			/*
991 			 * although the write manual page doesn't specify this
992 			 * as a possible errno, it is set when the nfs read
993 			 * via the mmap'ed file is accessed, so report the
994 			 * problem as a source access problem, not a target file
995 			 * problem
996 			 */
997 			if (nbytes < 0) {
998 				if (errno == EACCES)
999 					Perror(source);
1000 				else
1001 					Perror(target);
1002 				(void) close(fi);
1003 				(void) close(fo);
1004 				(void) munmap(cp, munmapsize);
1005 				if (ISREG(*s2p))
1006 					(void) unlink(target);
1007 				return (1);
1008 			}
1009 			filesize -= nbytes;
1010 			if (filesize == 0)
1011 				break;
1012 			offset += nbytes;
1013 			if (filesize < mapsize)
1014 				mapsize = filesize;
1015 			if (mmap(cp, mapsize, PROT_READ, MAP_SHARED | MAP_FIXED,
1016 			    fi, offset) == MAP_FAILED) {
1017 				Perror(source);
1018 				(void) close(fi);
1019 				(void) close(fo);
1020 				(void) munmap(cp, munmapsize);
1021 				if (ISREG(*s2p))
1022 					(void) unlink(target);
1023 				return (1);
1024 			}
1025 		}
1026 		(void) munmap(cp, munmapsize);
1027 	} else {
1028 		char buf[SMALLFILESIZE];
1029 		for (;;) {
1030 			n = read(fi, buf, sizeof (buf));
1031 			if (n == 0) {
1032 				return (0);
1033 			} else if (n < 0) {
1034 				Perror2(source, "read");
1035 				(void) close(fi);
1036 				(void) close(fo);
1037 				if (ISREG(*s2p))
1038 					(void) unlink(target);
1039 				return (1);
1040 			} else if (write(fo, buf, n) != n) {
1041 				Perror2(target, "write");
1042 				(void) close(fi);
1043 				(void) close(fo);
1044 				if (ISREG(*s2p))
1045 					(void) unlink(target);
1046 				return (1);
1047 			}
1048 		}
1049 	}
1050 	return (0);
1051 }
1052 
1053 /*
1054  * create_tnode()
1055  *
1056  * Create a node for use with the search tree which contains the
1057  * inode information (device id and inode number).
1058  *
1059  * Input
1060  *	dev	- device id
1061  *	ino	- inode number
1062  *
1063  * Output
1064  *	tnode	- NULL on error, otherwise returns a tnode structure
1065  *		  which contains the input device id and inode number.
1066  */
1067 static tree_node_t *
1068 create_tnode(dev_t dev, ino_t ino)
1069 {
1070 	tree_node_t	*tnode;
1071 
1072 	if ((tnode = (tree_node_t *)malloc(sizeof (tree_node_t))) != NULL) {
1073 		tnode->node_dev = dev;
1074 		tnode->node_ino = ino;
1075 	}
1076 
1077 	return (tnode);
1078 }
1079 
1080 static int
1081 chkfiles(char *source, char **to)
1082 {
1083 	char	*buf = (char *)NULL;
1084 	int	(*statf)() = (cpy &&
1085 		    !(Pflg || (Hflg && !cmdarg))) ? stat : lstat;
1086 	char    *target = *to;
1087 	int	error;
1088 
1089 	/*
1090 	 * Make sure source file exists.
1091 	 */
1092 	if ((*statf)(source, &s1) < 0) {
1093 		/*
1094 		 * Keep the old error message except when someone tries to
1095 		 * mv/cp/ln a symbolic link that has a trailing slash and
1096 		 * points to a file.
1097 		 */
1098 		if (errno == ENOTDIR)
1099 			(void) fprintf(stderr, "%s: %s: %s\n", cmd, source,
1100 			    strerror(errno));
1101 		else
1102 			(void) fprintf(stderr,
1103 			    gettext("%s: cannot access %s\n"), cmd, source);
1104 		return (1);
1105 	}
1106 
1107 	/*
1108 	 * Get ACL info: don't bother with ln or mv'ing symlinks
1109 	 */
1110 	if ((!lnk) && !(mve && ISLNK(s1))) {
1111 		if (s1acl != NULL) {
1112 			acl_free(s1acl);
1113 			s1acl = NULL;
1114 		}
1115 		if ((error = acl_get(source, ACL_NO_TRIVIAL, &s1acl)) != 0) {
1116 			(void) fprintf(stderr,
1117 			    "%s: failed to get acl entries: %s\n", source,
1118 			    acl_strerror(error));
1119 			return (1);
1120 		}
1121 		/* else: just permission bits */
1122 	}
1123 
1124 	/*
1125 	 * If stat fails, then the target doesn't exist,
1126 	 * we will create a new target with default file type of regular.
1127 	 */
1128 
1129 	FTYPE(s2) = S_IFREG;
1130 	targetexists = 0;
1131 	if ((*statf)(target, &s2) >= 0) {
1132 		if (ISLNK(s2))
1133 			(void) stat(target, &s2);
1134 		/*
1135 		 * If target is a directory,
1136 		 * make complete name of new file
1137 		 * within that directory.
1138 		 */
1139 		if (ISDIR(s2)) {
1140 			size_t len;
1141 
1142 			len = strlen(target) + strlen(dname(source)) + 4;
1143 			if ((buf = (char *)malloc(len)) == NULL) {
1144 				(void) fprintf(stderr,
1145 				    gettext("%s: Insufficient memory to "
1146 					"%s %s\n "), cmd, cmd, source);
1147 				exit(3);
1148 			}
1149 			(void) snprintf(buf, len, "%s/%s",
1150 			    target, dname(source));
1151 			*to = target = buf;
1152 		}
1153 
1154 		if ((*statf)(target, &s2) >= 0) {
1155 			int overwrite	= FALSE;
1156 			int override	= FALSE;
1157 
1158 			targetexists++;
1159 			if (cpy || mve) {
1160 				/*
1161 				 * For cp and mv, it is an error if the
1162 				 * source and target are the same file.
1163 				 * Check for the same inode and file
1164 				 * system, but don't check for the same
1165 				 * absolute pathname because it is an
1166 				 * error when the source and target are
1167 				 * hard links to the same file.
1168 				 */
1169 				if (IDENTICAL(s1, s2)) {
1170 					(void) fprintf(stderr,
1171 					    gettext(
1172 					    "%s: %s and %s are identical\n"),
1173 					    cmd, source, target);
1174 					if (buf != NULL)
1175 						free(buf);
1176 					return (1);
1177 				}
1178 			}
1179 			if (lnk) {
1180 				/*
1181 				 * For ln, it is an error if the source and
1182 				 * target are identical files (same inode,
1183 				 * same file system, and filenames resolve
1184 				 * to same absolute pathname).
1185 				 */
1186 				if (!chk_different(source, target)) {
1187 					if (buf != NULL)
1188 						free(buf);
1189 					return (1);
1190 				}
1191 			}
1192 			if (lnk && !silent) {
1193 				(void) fprintf(stderr,
1194 				    gettext("%s: %s: File exists\n"),
1195 				    cmd, target);
1196 				if (buf != NULL)
1197 					free(buf);
1198 				return (1);
1199 			}
1200 
1201 			/*
1202 			 * overwrite:
1203 			 * If the user does not have access to
1204 			 * the target, ask ----if it is not
1205 			 * silent and user invoked command
1206 			 * interactively.
1207 			 *
1208 			 * override:
1209 			 * If not silent, and stdin is a terminal, and
1210 			 * there's no write access, and the file isn't a
1211 			 * symbolic link, ask for permission.
1212 			 *
1213 			 * XPG4: both overwrite and override:
1214 			 * ask only one question.
1215 			 *
1216 			 * TRANSLATION_NOTE - The following messages will
1217 			 * contain the first character of the strings for
1218 			 * "yes" and "no" defined in the file
1219 			 * "nl_langinfo.po".  After substitution, the
1220 			 * message will appear as follows:
1221 			 *	<cmd>: overwrite <filename> (y/n)?
1222 			 * where <cmd> is the name of the command
1223 			 * (cp, mv) and <filename> is the destination file
1224 			 */
1225 
1226 
1227 			overwrite = iflg && !silent && use_stdin();
1228 			override = !cpy && (access(target, 2) < 0) &&
1229 			    !silent && use_stdin() && !ISLNK(s2);
1230 
1231 			if (overwrite && override) {
1232 				(void) fprintf(stderr,
1233 				    gettext("%s: overwrite %s and override "
1234 				    "protection %o (%s/%s)? "), cmd, target,
1235 				    FMODE(s2) & MODEBITS, yeschr, nochr);
1236 				if (getresp()) {
1237 					if (buf != NULL)
1238 						free(buf);
1239 					return (2);
1240 				}
1241 			} else if (overwrite && ISREG(s2)) {
1242 				(void) fprintf(stderr,
1243 				    gettext("%s: overwrite %s (%s/%s)? "),
1244 				    cmd, target, yeschr, nochr);
1245 				if (getresp()) {
1246 					if (buf != NULL)
1247 						free(buf);
1248 					return (2);
1249 				}
1250 			} else if (override) {
1251 				(void) fprintf(stderr,
1252 				    gettext("%s: %s: override protection "
1253 				    /*CSTYLED*/
1254 				    "%o (%s/%s)? "),
1255 				    /*CSTYLED*/
1256 				    cmd, target, FMODE(s2) & MODEBITS,
1257 				    yeschr, nochr);
1258 				if (getresp()) {
1259 					if (buf != NULL)
1260 						free(buf);
1261 					return (2);
1262 				}
1263 			}
1264 
1265 			if (lnk && unlink(target) < 0) {
1266 				(void) fprintf(stderr,
1267 				    gettext("%s: cannot unlink %s: "),
1268 				    cmd, target);
1269 				perror("");
1270 				return (1);
1271 			}
1272 		}
1273 	}
1274 	return (0);
1275 }
1276 
1277 /*
1278  * check whether source and target are different
1279  * return 1 when they are different
1280  * return 0 when they are identical, or when unable to resolve a pathname
1281  */
1282 static int
1283 chk_different(char *source, char *target)
1284 {
1285 	char	rtarget[PATH_MAX], rsource[PATH_MAX];
1286 
1287 	if (IDENTICAL(s1, s2)) {
1288 		/*
1289 		 * IDENTICAL will be true for hard links, therefore
1290 		 * check whether the filenames are different
1291 		 */
1292 		if ((getrealpath(source, rsource) == 0) ||
1293 		    (getrealpath(target, rtarget) == 0)) {
1294 			return (0);
1295 		}
1296 		if (strncmp(rsource, rtarget, PATH_MAX) == 0) {
1297 			(void) fprintf(stderr, gettext(
1298 			    "%s: %s and %s are identical\n"),
1299 			    cmd, source, target);
1300 			return (0);
1301 		}
1302 	}
1303 	return (1);
1304 }
1305 
1306 /*
1307  * get real path (resolved absolute pathname)
1308  * return 1 on success, 0 on failure
1309  */
1310 static int
1311 getrealpath(char *path, char *rpath)
1312 {
1313 	if (realpath(path, rpath) == NULL) {
1314 		int	errno_save = errno;
1315 		(void) fprintf(stderr, gettext(
1316 		    "%s: can't resolve path %s: "), cmd, path);
1317 		errno = errno_save;
1318 		perror("");
1319 		return (0);
1320 	}
1321 	return (1);
1322 }
1323 
1324 static int
1325 rcopy(char *from, char *to)
1326 {
1327 	DIR *fold = opendir(from);
1328 	struct dirent *dp;
1329 	struct stat statb, s1save;
1330 	int errs = 0;
1331 	char fromname[PATH_MAX];
1332 
1333 	if (fold == 0 || ((pflg || mve) && fstat(fold->dd_fd, &statb) < 0)) {
1334 		Perror(from);
1335 		return (1);
1336 	}
1337 	if (pflg || mve) {
1338 		/*
1339 		 * Save s1 (stat information for source dir) so that
1340 		 * mod and access times can be reserved during "cp -p"
1341 		 * or mv, since s1 gets overwritten.
1342 		 */
1343 		s1save = s1;
1344 	}
1345 	for (;;) {
1346 		dp = readdir(fold);
1347 		if (dp == 0) {
1348 			(void) closedir(fold);
1349 			if (pflg || mve)
1350 				return (chg_time(to, s1save) + errs);
1351 			return (errs);
1352 		}
1353 		if (dp->d_ino == 0)
1354 			continue;
1355 		if ((strcmp(dp->d_name, ".") == 0) ||
1356 		    (strcmp(dp->d_name, "..") == 0))
1357 			continue;
1358 		if (strlen(from)+1+strlen(dp->d_name) >=
1359 		    sizeof (fromname) - 1) {
1360 			(void) fprintf(stderr,
1361 			    gettext("%s : %s/%s: Name too long\n"),
1362 			    cmd, from, dp->d_name);
1363 			errs++;
1364 			continue;
1365 		}
1366 		(void) snprintf(fromname, sizeof (fromname),
1367 		    "%s/%s", from, dp->d_name);
1368 		errs += cpymve(fromname, to);
1369 	}
1370 }
1371 
1372 static char *
1373 dname(char *name)
1374 {
1375 	register char *p;
1376 
1377 	/*
1378 	 * Return just the file name given the complete path.
1379 	 * Like basename(1).
1380 	 */
1381 
1382 	p = name;
1383 
1384 	/*
1385 	 * While there are characters left,
1386 	 * set name to start after last
1387 	 * delimiter.
1388 	 */
1389 
1390 	while (*p)
1391 		if (*p++ == DELIM && *p)
1392 			name = p;
1393 	return (name);
1394 }
1395 
1396 static int
1397 getresp(void)
1398 {
1399 	register int	c, i;
1400 	char	ans_buf[SCHAR_MAX + 1];
1401 
1402 	/*
1403 	 * Get response from user. Based on
1404 	 * first character, make decision.
1405 	 * Discard rest of line.
1406 	 */
1407 	for (i = 0; ; i++) {
1408 		c = getchar();
1409 		if (c == '\n' || c == 0 || c == EOF) {
1410 			ans_buf[i] = 0;
1411 			break;
1412 		}
1413 		if (i < SCHAR_MAX)
1414 			ans_buf[i] = c;
1415 	}
1416 	if (i >= SCHAR_MAX) {
1417 		i = SCHAR_MAX;
1418 		ans_buf[SCHAR_MAX] = 0;
1419 	}
1420 	if ((i == 0) | (strncmp(yeschr, ans_buf, i)))
1421 		return (1);
1422 	return (0);
1423 }
1424 
1425 static void
1426 usage(void)
1427 {
1428 	/*
1429 	 * Display usage message.
1430 	 */
1431 
1432 	if (mve) {
1433 		(void) fprintf(stderr, gettext(
1434 		    "Usage: mv [-f] [-i] f1 f2\n"
1435 		    "       mv [-f] [-i] f1 ... fn d1\n"
1436 		    "       mv [-f] [-i] d1 d2\n"));
1437 	} else if (lnk) {
1438 		(void) fprintf(stderr, gettext(
1439 #ifdef XPG4
1440 		    "Usage: ln [-f] [-s] f1 [f2]\n"
1441 		    "       ln [-f] [-s] f1 ... fn d1\n"
1442 		    "       ln [-f] -s d1 d2\n"));
1443 #else
1444 		    "Usage: ln [-f] [-n] [-s] f1 [f2]\n"
1445 		    "       ln [-f] [-n] [-s] f1 ... fn d1\n"
1446 		    "       ln [-f] [-n] -s d1 d2\n"));
1447 #endif
1448 	} else if (cpy) {
1449 		(void) fprintf(stderr, gettext(
1450 		    "Usage: cp [-f] [-i] [-p] [-@] f1 f2\n"
1451 		    "       cp [-f] [-i] [-p] [-@] f1 ... fn d1\n"
1452 		    "       cp -r|-R [-H|-L|-P] [-f] [-i] [-p] [-@] "
1453 		    "d1 ... dn-1 dn\n"));
1454 	}
1455 	exit(2);
1456 }
1457 
1458 /*
1459  * chg_time()
1460  *
1461  * Try to preserve modification and access time.
1462  * If 1) pflg is not set, or 2) pflg is set and this is the Solaris version,
1463  * don't report a utime() failure.
1464  * If this is the XPG4 version and utime fails, if 1) pflg is set (cp -p)
1465  * or 2) we are doing a mv, print a diagnostic message; arrange for a non-zero
1466  * exit status only if pflg is set.
1467  * utimes(2) is being used to achieve granularity in
1468  * microseconds while setting file times.
1469  */
1470 static int
1471 chg_time(char *to, struct stat ss)
1472 {
1473 	struct timeval times[2];
1474 	int rc;
1475 
1476 	timestruc_to_timeval(&ss.st_atim, times);
1477 	timestruc_to_timeval(&ss.st_mtim, times + 1);
1478 
1479 	rc = utimes(to, times);
1480 #ifdef XPG4
1481 	if ((pflg || mve) && rc != 0) {
1482 		(void) fprintf(stderr,
1483 		    gettext("%s: cannot set times for %s: "), cmd, to);
1484 		perror("");
1485 		if (pflg)
1486 			return (1);
1487 	}
1488 #endif
1489 
1490 	return (0);
1491 
1492 }
1493 
1494 /*
1495  * chg_mode()
1496  *
1497  * This function is called upon "cp -p" or mv across filesystems.
1498  *
1499  * Try to preserve the owner and group id.  If chown() fails,
1500  * only print a diagnostic message if doing a mv in the XPG4 version;
1501  * try to clear S_ISUID and S_ISGID bits in the target.  If unable to clear
1502  * S_ISUID and S_ISGID bits, print a diagnostic message and arrange for a
1503  * non-zero exit status because this is a security violation.
1504  * Try to preserve permissions.
1505  * If this is the XPG4 version and chmod() fails, print a diagnostic message
1506  * and arrange for a non-zero exit status.
1507  * If this is the Solaris version and chmod() fails, do not print a
1508  * diagnostic message or exit with a non-zero value.
1509  */
1510 static int
1511 chg_mode(char *target, uid_t uid, gid_t gid, mode_t mode)
1512 {
1513 	int clearflg = 0; /* controls message printed upon chown() error */
1514 
1515 	if (chown(target, uid, gid) != 0) {
1516 #ifdef XPG4
1517 		if (mve) {
1518 			(void) fprintf(stderr, gettext("%s: cannot change"
1519 			    " owner and group of %s: "), cmd, target);
1520 			perror("");
1521 		}
1522 #endif
1523 		if (mode & (S_ISUID | S_ISGID)) {
1524 			/* try to clear S_ISUID and S_ISGID */
1525 			mode &= ~S_ISUID & ~S_ISGID;
1526 			++clearflg;
1527 		}
1528 	}
1529 	if (chmod(target, mode) != 0) {
1530 		if (clearflg) {
1531 			(void) fprintf(stderr, gettext(
1532 			    "%s: cannot clear S_ISUID and S_ISGID bits in"
1533 			    " %s: "), cmd, target);
1534 			perror("");
1535 			/* cp -p should get non-zero exit; mv should not */
1536 			if (pflg)
1537 				return (1);
1538 		}
1539 #ifdef XPG4
1540 		else {
1541 			(void) fprintf(stderr, gettext(
1542 			"%s: cannot set permissions for %s: "), cmd, target);
1543 			perror("");
1544 			/* cp -p should get non-zero exit; mv should not */
1545 			if (pflg)
1546 				return (1);
1547 		}
1548 #endif
1549 	}
1550 	return (0);
1551 
1552 }
1553 
1554 static void
1555 Perror(char *s)
1556 {
1557 	char buf[PATH_MAX + 10];
1558 
1559 	(void) snprintf(buf, sizeof (buf), "%s: %s", cmd, s);
1560 	perror(buf);
1561 }
1562 
1563 static void
1564 Perror2(char *s1, char *s2)
1565 {
1566 	char buf[PATH_MAX + 20];
1567 
1568 	(void) snprintf(buf, sizeof (buf), "%s: %s: %s",
1569 	    cmd, gettext(s1), gettext(s2));
1570 	perror(buf);
1571 }
1572 
1573 /*
1574  * used for cp -R and for mv across file systems
1575  */
1576 static int
1577 copydir(char *source, char *target)
1578 {
1579 	int ret, attret = 0;
1580 	int pret = 0;		/* need separate flag if -p is specified */
1581 	mode_t	fixmode = (mode_t)0;	/* cleanup mode after copy */
1582 	struct stat s1save;
1583 	acl_t  *s1acl_save;
1584 
1585 	s1acl_save = NULL;
1586 
1587 	if (cpy && !rflg) {
1588 		(void) fprintf(stderr,
1589 		    gettext("%s: %s: is a directory\n"), cmd, source);
1590 		return (1);
1591 	}
1592 
1593 	if (stat(target, &s2) < 0) {
1594 		if (mkdir(target, (s1.st_mode & MODEBITS)) < 0) {
1595 			(void) fprintf(stderr, "%s: ", cmd);
1596 			perror(target);
1597 			return (1);
1598 		}
1599 		if (stat(target, &s2) == 0) {
1600 			fixmode = s2.st_mode;
1601 		} else {
1602 			fixmode = s1.st_mode;
1603 		}
1604 		(void) chmod(target, ((fixmode & MODEBITS) | S_IRWXU));
1605 	} else if (!(ISDIR(s2))) {
1606 		(void) fprintf(stderr,
1607 		    gettext("%s: %s: not a directory.\n"), cmd, target);
1608 		return (1);
1609 	}
1610 	if (pflg || mve) {
1611 		/*
1612 		 * Save s1 (stat information for source dir) and acl info,
1613 		 * if any, so that ownership, modes, times, and acl's can
1614 		 * be reserved during "cp -p" or mv.
1615 		 * s1 gets overwritten when doing the recursive copy.
1616 		 */
1617 		s1save = s1;
1618 		if (s1acl != NULL) {
1619 			s1acl_save = acl_dup(s1acl);
1620 			if (s1acl_save == NULL) {
1621 				(void) fprintf(stderr, gettext("%s: "
1622 				    "Insufficient memory to save acl"
1623 				    " entry\n"), cmd);
1624 				if (pflg)
1625 					return (1);
1626 
1627 			}
1628 #ifdef XPG4
1629 			else {
1630 				(void) fprintf(stderr, gettext("%s: "
1631 				    "Insufficient memory to save acl"
1632 				    " entry\n"), cmd);
1633 				if (pflg)
1634 					return (1);
1635 			}
1636 #endif
1637 		}
1638 	}
1639 
1640 	ret = rcopy(source, target);
1641 
1642 	/*
1643 	 * Once we created a directory, go ahead and set
1644 	 * its attributes, e.g. acls and time. The info
1645 	 * may get overwritten if we continue traversing
1646 	 * down the tree.
1647 	 *
1648 	 * ACL for directory
1649 	 */
1650 	if (pflg || mve) {
1651 		if ((pret = chg_mode(target, UID(s1save), GID(s1save),
1652 		    FMODE(s1save))) == 0)
1653 			pret = chg_time(target, s1save);
1654 		ret += pret;
1655 		if (s1acl_save != NULL) {
1656 			if (acl_set(target, s1acl_save) < 0) {
1657 #ifdef XPG4
1658 				if (pflg || mve) {
1659 #else
1660 				if (pflg) {
1661 #endif
1662 					(void) fprintf(stderr, gettext(
1663 					    "%s: failed to set acl entries "
1664 					    "on %s\n"), cmd, target);
1665 					if (pflg) {
1666 						acl_free(s1acl_save);
1667 						s1acl_save = NULL;
1668 						ret++;
1669 					}
1670 				}
1671 				/* else: silent and continue */
1672 			}
1673 			acl_free(s1acl_save);
1674 			s1acl_save = NULL;
1675 		}
1676 	} else if (fixmode != (mode_t)0)
1677 		(void) chmod(target, fixmode & MODEBITS);
1678 
1679 	if (pflg || atflg || mve) {
1680 		attret = copyattributes(source, target);
1681 		if (!attrsilent && attret != 0) {
1682 			(void) fprintf(stderr, gettext("%s: Failed to preserve"
1683 			    " extended attributes of directory"
1684 			    " %s\n"), cmd, source);
1685 		} else {
1686 			/*
1687 			 * Otherwise ignore failure.
1688 			 */
1689 			attret = 0;
1690 		}
1691 	}
1692 	if (attret != 0)
1693 		return (attret);
1694 	return (ret);
1695 }
1696 
1697 static int
1698 copyspecial(char *target)
1699 {
1700 	int ret = 0;
1701 
1702 	if (mknod(target, s1.st_mode, s1.st_rdev) != 0) {
1703 		(void) fprintf(stderr, gettext(
1704 		    "cp: cannot create special file %s: "), target);
1705 		perror("");
1706 		return (1);
1707 	}
1708 
1709 	if (pflg) {
1710 		if ((ret = chg_mode(target, UID(s1), GID(s1), FMODE(s1))) == 0)
1711 			ret = chg_time(target, s1);
1712 	}
1713 
1714 	return (ret);
1715 }
1716 
1717 static int
1718 use_stdin(void)
1719 {
1720 #ifdef XPG4
1721 	return (1);
1722 #else
1723 	return (isatty(fileno(stdin)));
1724 #endif
1725 }
1726 
1727 static int
1728 copyattributes(char *source, char *target)
1729 {
1730 	int sourcedirfd, targetdirfd;
1731 	int srcfd, targfd;
1732 	int tmpfd;
1733 	DIR *srcdirp;
1734 	int srcattrfd, targattrfd;
1735 	struct dirent *dp;
1736 	char *attrstr;
1737 	char *srcbuf, *targbuf;
1738 	size_t src_size, targ_size;
1739 	int error = 0;
1740 	int aclerror;
1741 	mode_t mode;
1742 	int clearflg = 0;
1743 	acl_t *xacl = NULL;
1744 	acl_t *attrdiracl = NULL;
1745 	struct stat attrdir, s3, s4;
1746 	struct timeval times[2];
1747 	mode_t	targmode;
1748 
1749 	srcdirp = NULL;
1750 	srcfd = targfd = tmpfd = -1;
1751 	sourcedirfd = targetdirfd = srcattrfd = targattrfd = -1;
1752 	srcbuf = targbuf = NULL;
1753 
1754 	if (pathconf(source, _PC_XATTR_EXISTS) != 1)
1755 		return (0);
1756 
1757 	if (pathconf(target, _PC_XATTR_ENABLED) != 1) {
1758 		if (!attrsilent) {
1759 			(void) fprintf(stderr,
1760 			    gettext(
1761 			    "%s: cannot preserve extended attributes, "
1762 			    "operation not supported on file"
1763 			    " %s\n"), cmd, target);
1764 		}
1765 		return (1);
1766 	}
1767 
1768 
1769 	if ((srcfd = open(source, O_RDONLY)) == -1) {
1770 		if (pflg && attrsilent) {
1771 			error = 0;
1772 			goto out;
1773 		}
1774 		if (!attrsilent) {
1775 			(void) fprintf(stderr,
1776 			    gettext("%s: cannot open file"
1777 			    " %s: "), cmd, source);
1778 			perror("");
1779 		}
1780 		++error;
1781 		goto out;
1782 	}
1783 	if ((targfd = open(target, O_RDONLY)) == -1) {
1784 
1785 		if (pflg && attrsilent) {
1786 			error = 0;
1787 			goto out;
1788 		}
1789 		if (!attrsilent) {
1790 			(void) fprintf(stderr,
1791 			    gettext("%s: cannot open file"
1792 			    " %s: "), cmd, source);
1793 			perror("");
1794 		}
1795 		++error;
1796 		goto out;
1797 	}
1798 
1799 	if ((sourcedirfd = openat(srcfd, ".", O_RDONLY|O_XATTR)) == -1) {
1800 		if (pflg && attrsilent) {
1801 			error = 0;
1802 			goto out;
1803 		}
1804 		if (!attrsilent) {
1805 			(void) fprintf(stderr,
1806 			    gettext("%s: cannot open attribute"
1807 			    " directory for %s: "), cmd, source);
1808 			perror("");
1809 			++error;
1810 		}
1811 		goto out;
1812 	}
1813 
1814 	if (fstat(sourcedirfd, &attrdir) == -1) {
1815 		if (pflg && attrsilent) {
1816 			error = 0;
1817 			goto out;
1818 		}
1819 
1820 		if (!attrsilent) {
1821 			(void) fprintf(stderr,
1822 				gettext("%s: could not retrieve stat"
1823 					" information for attribute directory"
1824 					"of file %s: "), cmd, source);
1825 			perror("");
1826 			++error;
1827 		}
1828 		goto out;
1829 	}
1830 	if ((targetdirfd = openat(targfd, ".", O_RDONLY|O_XATTR)) == -1) {
1831 		/*
1832 		 * We couldn't create the attribute directory
1833 		 *
1834 		 * Lets see if we can add write support to the mode
1835 		 * and create the directory and then put the mode back
1836 		 * to way it should be.
1837 		 */
1838 
1839 		targmode = FMODE(s1) | S_IWUSR;
1840 		if (fchmod(targfd, targmode) == 0) {
1841 			targetdirfd = openat(targfd, ".", O_RDONLY|O_XATTR);
1842 			/*
1843 			 * Put mode back to what it was
1844 			 */
1845 			targmode = FMODE(s1) & MODEBITS;
1846 			if (fchmod(targfd, targmode) == -1) {
1847 				if (pflg && attrsilent) {
1848 					error = 0;
1849 					goto out;
1850 				}
1851 				if (!attrsilent) {
1852 					(void) fprintf(stderr,
1853 					    gettext("%s: failed to set"
1854 					    " mode correctly on file"
1855 					    " %s: "), cmd, target);
1856 					perror("");
1857 					++error;
1858 					goto out;
1859 				}
1860 			}
1861 		} else {
1862 			if (pflg && attrsilent) {
1863 				error = 0;
1864 				goto out;
1865 			}
1866 			if (!attrsilent) {
1867 				(void) fprintf(stderr,
1868 				    gettext("%s: cannot open attribute"
1869 				    " directory for %s: "), cmd, target);
1870 				perror("");
1871 				++error;
1872 			}
1873 			goto out;
1874 		}
1875 	}
1876 
1877 	if (targetdirfd == -1) {
1878 		if (pflg && attrsilent) {
1879 			error = 0;
1880 			goto out;
1881 		}
1882 		if (!attrsilent) {
1883 			(void) fprintf(stderr,
1884 			    gettext("%s: cannot open attribute directory"
1885 			    " for %s: "), cmd, target);
1886 			perror("");
1887 			++error;
1888 		}
1889 		goto out;
1890 	}
1891 
1892 	/*
1893 	 * Set mode of attribute directory same as on source,
1894 	 * if pflg set or this is a move.
1895 	 */
1896 
1897 	if (pflg || mve) {
1898 		if (fchmod(targetdirfd, attrdir.st_mode) == -1) {
1899 			if (!attrsilent) {
1900 				(void) fprintf(stderr,
1901 					gettext("%s: failed to set file mode"
1902 					" correctly on attribute directory of"
1903 					" file %s: "), cmd, target);
1904 				perror("");
1905 				++error;
1906 			}
1907 		}
1908 
1909 		if (fchown(targetdirfd, attrdir.st_uid, attrdir.st_gid) == -1) {
1910 			if (!attrsilent) {
1911 				(void) fprintf(stderr,
1912 				    gettext("%s: failed to set file"
1913 				    " ownership correctly on attribute"
1914 				    " directory of file %s: "), cmd, target);
1915 				perror("");
1916 				++error;
1917 			}
1918 		}
1919 		/*
1920 		 * Now that we are the owner we can update st_ctime by calling
1921 		 * futimesat.
1922 		 */
1923 		times[0].tv_sec = attrdir.st_atime;
1924 		times[0].tv_usec = 0;
1925 		times[1].tv_sec = attrdir.st_mtime;
1926 		times[1].tv_usec = 0;
1927 		if (futimesat(targetdirfd, ".", times) < 0) {
1928 			if (!attrsilent) {
1929 				(void) fprintf(stderr,
1930 					gettext("%s: cannot set attribute times"
1931 					" for %s: "), cmd, target);
1932 				perror("");
1933 				++error;
1934 			}
1935 		}
1936 
1937 		/*
1938 		 * Now set owner and group of attribute directory, implies
1939 		 * changing the ACL of the hidden attribute directory first.
1940 		 */
1941 		if ((aclerror = facl_get(sourcedirfd,
1942 		    ACL_NO_TRIVIAL, &attrdiracl)) != 0) {
1943 			if (!attrsilent) {
1944 				(void) fprintf(stderr, gettext(
1945 				    "%s: failed to get acl entries of"
1946 				    " attribute directory for"
1947 				    " %s : %s\n"), cmd,
1948 				    source, acl_strerror(aclerror));
1949 				++error;
1950 			}
1951 		}
1952 
1953 		if (attrdiracl) {
1954 			if (facl_set(targetdirfd, attrdiracl) != 0) {
1955 				if (!attrsilent) {
1956 					(void) fprintf(stderr, gettext(
1957 					"%s: failed to set acl entries"
1958 					" on attribute directory "
1959 					"for %s\n"), cmd, target);
1960 					++error;
1961 				}
1962 				acl_free(attrdiracl);
1963 				attrdiracl = NULL;
1964 			}
1965 		}
1966 	}
1967 
1968 	/*
1969 	 * dup sourcedirfd for use by fdopendir().
1970 	 * fdopendir will take ownership of given fd and will close
1971 	 * it when closedir() is called.
1972 	 */
1973 
1974 	if ((tmpfd = dup(sourcedirfd)) == -1) {
1975 		if (pflg && attrsilent) {
1976 			error = 0;
1977 			goto out;
1978 		}
1979 		if (!attrsilent) {
1980 			(void) fprintf(stderr,
1981 			    gettext(
1982 			    "%s: unable to dup attribute directory"
1983 			    " file descriptor for %s: "), cmd, source);
1984 			perror("");
1985 			++error;
1986 		}
1987 		goto out;
1988 	}
1989 	if ((srcdirp = fdopendir(tmpfd)) == NULL) {
1990 		if (pflg && attrsilent) {
1991 			error = 0;
1992 			goto out;
1993 		}
1994 		if (!attrsilent) {
1995 			(void) fprintf(stderr,
1996 			    gettext("%s: failed to open attribute"
1997 			    " directory for %s: "), cmd, source);
1998 			perror("");
1999 			++error;
2000 		}
2001 		goto out;
2002 	}
2003 
2004 	while (dp = readdir(srcdirp)) {
2005 		if ((dp->d_name[0] == '.' && dp->d_name[1] == '\0') ||
2006 			(dp->d_name[0] == '.' && dp->d_name[1] == '.' &&
2007 			dp->d_name[2] == '\0'))
2008 			continue;
2009 
2010 		if ((srcattrfd = openat(sourcedirfd, dp->d_name,
2011 		    O_RDONLY)) == -1) {
2012 			if (!attrsilent) {
2013 				(void) fprintf(stderr,
2014 				    gettext("%s: cannot open attribute %s on"
2015 				    " file %s: "), cmd, dp->d_name, source);
2016 				perror("");
2017 				++error;
2018 				goto next;
2019 			}
2020 		}
2021 
2022 		if (fstat(srcattrfd, &s3) < 0) {
2023 			if (!attrsilent) {
2024 				(void) fprintf(stderr,
2025 				    gettext("%s: could not stat attribute"
2026 				    " %s on file"
2027 				    " %s: "), cmd, dp->d_name, source);
2028 				perror("");
2029 				++error;
2030 			}
2031 			goto next;
2032 		}
2033 
2034 		if (pflg || mve) {
2035 			if ((aclerror = facl_get(srcattrfd,
2036 			    ACL_NO_TRIVIAL, &xacl)) != 0) {
2037 				if (!attrsilent) {
2038 					(void) fprintf(stderr, gettext(
2039 					    "%s: failed to get acl entries of"
2040 					    " attribute %s for"
2041 					    " %s: %s"), cmd, dp->d_name,
2042 					    source, acl_strerror(aclerror));
2043 					++error;
2044 				}
2045 			}
2046 		}
2047 
2048 		(void) unlinkat(targetdirfd, dp->d_name, 0);
2049 		if ((targattrfd = openat(targetdirfd, dp->d_name,
2050 		    O_RDWR|O_CREAT|O_TRUNC, s3.st_mode & MODEBITS)) == -1) {
2051 			if (!attrsilent) {
2052 				(void) fprintf(stderr,
2053 				    gettext("%s: could not create attribute"
2054 				    " %s on file"
2055 				    " %s: "), cmd, dp->d_name, target);
2056 				perror("");
2057 				++error;
2058 			}
2059 			goto next;
2060 		}
2061 
2062 		/*
2063 		 * preserve ACL
2064 		 */
2065 		if ((pflg || mve) && xacl != NULL) {
2066 			if ((facl_set(targattrfd, xacl)) < 0) {
2067 				if (!attrsilent) {
2068 					(void) fprintf(stderr, gettext(
2069 					    "%s: failed to set acl entries on"
2070 					    " attribute %s for"
2071 					    "%s\n"), cmd, dp->d_name, target);
2072 					++error;
2073 				}
2074 				acl_free(xacl);
2075 				xacl = NULL;
2076 			}
2077 		}
2078 
2079 		if (fstat(targattrfd, &s4) < 0) {
2080 			if (!attrsilent) {
2081 				(void) fprintf(stderr,
2082 				    gettext("%s: could not stat attribute"
2083 				    " %s on file"
2084 				    " %s: "), cmd, dp->d_name, source);
2085 				perror("");
2086 				++error;
2087 			}
2088 			goto next;
2089 		}
2090 
2091 /*
2092  * setup path string to be passed to writefile
2093  *
2094  * We need to include attribute in the string so that
2095  * a useful error message can be printed in the case of a failure.
2096  */
2097 		attrstr = gettext(" attribute ");
2098 		src_size = strlen(source) +
2099 		    strlen(dp->d_name) + strlen(attrstr) + 1;
2100 		srcbuf = malloc(src_size);
2101 
2102 		if (srcbuf == NULL) {
2103 			if (!attrsilent) {
2104 			(void) fprintf(stderr,
2105 				gettext("%s: could not allocate memory"
2106 					" for path buffer: "), cmd);
2107 				perror("");
2108 				++error;
2109 			}
2110 			goto next;
2111 		}
2112 		targ_size = strlen(target) +
2113 		    strlen(dp->d_name) + strlen(attrstr) + 1;
2114 		targbuf = malloc(targ_size);
2115 		if (targbuf == NULL) {
2116 			if (!attrsilent) {
2117 				(void) fprintf(stderr,
2118 					gettext("%s: could not allocate memory"
2119 						" for path buffer: "), cmd);
2120 				perror("");
2121 				++error;
2122 			}
2123 			goto next;
2124 		}
2125 
2126 		(void) snprintf(srcbuf, src_size, "%s%s%s",
2127 		    source, attrstr, dp->d_name);
2128 		(void) snprintf(targbuf, targ_size, "%s%s%s",
2129 		    target, attrstr, dp->d_name);
2130 
2131 		if (writefile(srcattrfd, targattrfd,
2132 		    srcbuf, targbuf, &s3, &s4) != 0) {
2133 			if (!attrsilent) {
2134 				++error;
2135 			}
2136 			goto next;
2137 		}
2138 
2139 		if (pflg || mve) {
2140 			mode = FMODE(s3);
2141 
2142 			if (fchown(targattrfd, UID(s3), GID(s3)) != 0) {
2143 				if (!attrsilent) {
2144 					(void) fprintf(stderr,
2145 					    gettext("%s: cannot change"
2146 					    " owner and group of"
2147 					    " attribute %s for" " file"
2148 					    " %s: "), cmd, dp->d_name, target);
2149 					perror("");
2150 					++error;
2151 				}
2152 				if (mode & (S_ISUID | S_ISGID)) {
2153 					/* try to clear S_ISUID and S_ISGID */
2154 					mode &= ~S_ISUID & ~S_ISGID;
2155 					++clearflg;
2156 				}
2157 			}
2158 			/* tv_usec were cleared above */
2159 			times[0].tv_sec = s3.st_atime;
2160 			times[1].tv_sec = s3.st_mtime;
2161 			if (futimesat(targetdirfd, dp->d_name, times) < 0) {
2162 				if (!attrsilent) {
2163 					(void) fprintf(stderr,
2164 					    gettext("%s: cannot set attribute"
2165 					    " times for %s: "), cmd, target);
2166 					perror("");
2167 					++error;
2168 				}
2169 			}
2170 			if (fchmod(targattrfd, mode) != 0) {
2171 				if (clearflg) {
2172 					(void) fprintf(stderr, gettext(
2173 					    "%s: cannot clear S_ISUID and "
2174 					    "S_ISGID bits in attribute %s"
2175 					    " for file"
2176 					    " %s: "), cmd, dp->d_name, target);
2177 				} else {
2178 					if (!attrsilent) {
2179 						(void) fprintf(stderr,
2180 							gettext(
2181 				"%s: cannot set permissions of attribute"
2182 				" %s for %s: "), cmd, dp->d_name, target);
2183 						perror("");
2184 						++error;
2185 					}
2186 				}
2187 			}
2188 			if (xacl && ((facl_set(targattrfd, xacl)) < 0)) {
2189 				if (!attrsilent) {
2190 					(void) fprintf(stderr, gettext(
2191 					    "%s: failed to set acl entries on"
2192 					    " attribute %s for"
2193 					    "%s\n"), cmd, dp->d_name, target);
2194 					++error;
2195 				}
2196 				acl_free(xacl);
2197 				xacl = NULL;
2198 			}
2199 		}
2200 next:
2201 		if (xacl != NULL) {
2202 			acl_free(xacl);
2203 			xacl = NULL;
2204 		}
2205 		if (srcbuf != NULL)
2206 			free(srcbuf);
2207 		if (targbuf != NULL)
2208 			free(targbuf);
2209 		if (srcattrfd != -1)
2210 			(void) close(srcattrfd);
2211 		if (targattrfd != -1)
2212 			(void) close(targattrfd);
2213 		srcattrfd = targattrfd = -1;
2214 		srcbuf = targbuf = NULL;
2215 	}
2216 out:
2217 	if (xacl != NULL) {
2218 		acl_free(xacl);
2219 		xacl = NULL;
2220 	}
2221 	if (attrdiracl != NULL) {
2222 		acl_free(attrdiracl);
2223 		attrdiracl = NULL;
2224 	}
2225 	if (srcbuf)
2226 		free(srcbuf);
2227 	if (targbuf)
2228 		free(targbuf);
2229 	if (sourcedirfd != -1)
2230 		(void) close(sourcedirfd);
2231 	if (targetdirfd != -1)
2232 		(void) close(targetdirfd);
2233 	if (srcdirp != NULL)
2234 		(void) closedir(srcdirp);
2235 	if (srcfd != -1)
2236 		(void) close(srcfd);
2237 	if (targfd != -1)
2238 		(void) close(targfd);
2239 	return (error == 0 ? 0 : 1);
2240 }
2241 
2242 /*
2243  * nanoseconds are rounded off to microseconds by flooring.
2244  */
2245 static void
2246 timestruc_to_timeval(timestruc_t *ts, struct timeval *tv)
2247 {
2248 	tv->tv_sec = ts->tv_sec;
2249 	tv->tv_usec = ts->tv_nsec / 1000;
2250 }
2251