xinstall.c revision 11356
1/*
2 * Copyright (c) 1987, 1993
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 *    must display the following acknowledgement:
15 *	This product includes software developed by the University of
16 *	California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#ifndef lint
35static const char copyright[] =
36"@(#) Copyright (c) 1987, 1993\n\
37	The Regents of the University of California.  All rights reserved.\n";
38#endif /* not lint */
39
40#ifndef lint
41/*static char sccsid[] = "From: @(#)xinstall.c	8.1 (Berkeley) 7/21/93";*/
42static const char rcsid[] =
43	"$Id$";
44#endif /* not lint */
45
46/*-
47 * Todo:
48 * o for -C, compare original files except in -s case.
49 * o for -C, don't change anything if nothing needs be changed.  In
50 *   particular, don't toggle the immutable flags just to allow null
51 *   attribute changes and don't clear the dump flag.  (I think inode
52 *   ctimes are not updated for null attribute changes, but this is a
53 *   bug.)
54 * o independent of -C, if a copy must be made, then copy to a tmpfile,
55 *   set all attributes except the immutable flags, then rename, then
56 *   set the immutable flags.  It's annoying that the immutable flags
57 *   defeat the atomicicity of rename - it seems that there must be
58 * o a window where the target is not immutable.
59 */
60
61#include <sys/param.h>
62#include <sys/wait.h>
63#include <sys/mman.h>
64#include <sys/stat.h>
65
66#include <ctype.h>
67#include <err.h>
68#include <errno.h>
69#include <fcntl.h>
70#include <grp.h>
71#include <paths.h>
72#include <pwd.h>
73#include <stdio.h>
74#include <stdlib.h>
75#include <string.h>
76#include <sysexits.h>
77#include <unistd.h>
78#include <utime.h>
79
80#include "pathnames.h"
81
82struct passwd *pp;
83struct group *gp;
84int debug, docompare, docopy, dopreserve, dostrip;
85int mode = S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH;
86char *group, *owner, pathbuf[MAXPATHLEN];
87char pathbuf2[MAXPATHLEN];
88
89#define	DIRECTORY	0x01		/* Tell install it's a directory. */
90#define	SETFLAGS	0x02		/* Tell install to set flags. */
91#define	NOCHANGEBITS	(UF_IMMUTABLE | UF_APPEND | SF_IMMUTABLE | SF_APPEND)
92
93void	copy __P((int, char *, int, char *, off_t));
94int	compare __P((int, const char *, int, const char *,
95		     const struct stat *, const struct stat *));
96void	install __P((char *, char *, u_long, u_int));
97u_long	string_to_flags __P((char **, u_long *, u_long *));
98void	strip __P((char *));
99void	usage __P((void));
100
101int
102main(argc, argv)
103	int argc;
104	char *argv[];
105{
106	struct stat from_sb, to_sb;
107	mode_t *set;
108	u_long fset;
109	u_int iflags;
110	int ch, no_target;
111	char *flags, *to_name;
112
113	iflags = 0;
114	while ((ch = getopt(argc, argv, "Ccdf:g:m:o:ps")) != EOF)
115		switch((char)ch) {
116		case 'C':
117			docompare = docopy = 1;
118			break;
119		case 'c':
120			docopy = 1;
121			break;
122		case 'd':
123			debug++;
124			break;
125		case 'f':
126			flags = optarg;
127			if (string_to_flags(&flags, &fset, NULL))
128				errx(EX_USAGE, "%s: invalid flag", flags);
129			iflags |= SETFLAGS;
130			break;
131		case 'g':
132			group = optarg;
133			break;
134		case 'm':
135			if (!(set = setmode(optarg)))
136				errx(EX_USAGE, "invalid file mode: %s",
137				     optarg);
138			mode = getmode(set, 0);
139			break;
140		case 'o':
141			owner = optarg;
142			break;
143		case 'p':
144			docompare = docopy = dopreserve = 1;
145			break;
146		case 's':
147			dostrip = 1;
148			break;
149		case '?':
150		default:
151			usage();
152		}
153	argc -= optind;
154	argv += optind;
155	if (argc < 2)
156		usage();
157
158	/* get group and owner id's */
159	if (group && !(gp = getgrnam(group)))
160		errx(EX_NOUSER, "unknown group %s", group);
161	if (owner && !(pp = getpwnam(owner)))
162		errx(EX_NOUSER, "unknown user %s", owner);
163
164	no_target = stat(to_name = argv[argc - 1], &to_sb);
165	if (!no_target && (to_sb.st_mode & S_IFMT) == S_IFDIR) {
166		for (; *argv != to_name; ++argv)
167			install(*argv, to_name, fset, iflags | DIRECTORY);
168		exit(0);
169	}
170
171	/* can't do file1 file2 directory/file */
172	if (argc != 2)
173		usage();
174
175	if (!no_target) {
176		if (stat(*argv, &from_sb))
177			err(EX_OSERR, "%s", *argv);
178		if (!S_ISREG(to_sb.st_mode)) {
179			errno = EFTYPE;
180			err(EX_OSERR, "%s", to_name);
181		}
182		if (to_sb.st_dev == from_sb.st_dev &&
183		    to_sb.st_ino == from_sb.st_ino)
184			errx(EX_USAGE,
185			    "%s and %s are the same file", *argv, to_name);
186/*
187 * XXX - It's not at all clear why this code was here, since it completely
188 * duplicates code install().  The version in install() handles the -C flag
189 * correctly, so we'll just disable this for now.
190 */
191#if 0
192		/*
193		 * Unlink now... avoid ETXTBSY errors later.  Try and turn
194		 * off the append/immutable bits -- if we fail, go ahead,
195		 * it might work.
196		 */
197		if (to_sb.st_flags & NOCHANGEBITS)
198			(void)chflags(to_name,
199			    to_sb.st_flags & ~(NOCHANGEBITS));
200		(void)unlink(to_name);
201#endif
202	}
203	install(*argv, to_name, fset, iflags);
204	exit(0);
205}
206
207/*
208 * install --
209 *	build a path name and install the file
210 */
211void
212install(from_name, to_name, fset, flags)
213	char *from_name, *to_name;
214	u_long fset;
215	u_int flags;
216{
217	struct stat from_sb, to_sb;
218	int devnull, from_fd, to_fd, serrno;
219	char *p, *old_to_name = 0;
220
221	if (debug >= 2 && !docompare)
222		fprintf(stderr, "install: invoked without -C for %s to %s\n",
223			from_name, to_name);
224
225	/* If try to install NULL file to a directory, fails. */
226	if (flags & DIRECTORY || strcmp(from_name, _PATH_DEVNULL)) {
227		if (stat(from_name, &from_sb))
228			err(EX_OSERR, "%s", from_name);
229		if (!S_ISREG(from_sb.st_mode)) {
230			errno = EFTYPE;
231			err(EX_OSERR, "%s", from_name);
232		}
233		/* Build the target path. */
234		if (flags & DIRECTORY) {
235			(void)snprintf(pathbuf, sizeof(pathbuf), "%s/%s",
236			    to_name,
237			    (p = strrchr(from_name, '/')) ? ++p : from_name);
238			to_name = pathbuf;
239		}
240		devnull = 0;
241	} else {
242		from_sb.st_flags = 0;	/* XXX */
243		devnull = 1;
244	}
245
246	if (docompare) {
247		old_to_name = to_name;
248		/*
249		 * Make a new temporary file in the same file system
250		 * (actually, in in the same directory) as the target so
251		 * that the temporary file can be renamed to the target.
252		 */
253		snprintf(pathbuf2, sizeof pathbuf2, "%s", to_name);
254		p = strrchr(pathbuf2, '/');
255		p = (p == NULL ? pathbuf2 : p + 1);
256		snprintf(p, &pathbuf2[sizeof pathbuf2] - p, "INS@XXXX");
257		to_fd = mkstemp(pathbuf2);
258		if (to_fd < 0)
259			/* XXX should fall back to not comparing. */
260			err(EX_OSERR, "mkstemp: %s for %s", pathbuf2, to_name);
261		to_name = pathbuf2;
262	} else {
263		/*
264		 * Unlink now... avoid errors later.  Try to turn off the
265		 * append/immutable bits -- if we fail, go ahead, it might
266		 * work.
267		 */
268		if (stat(to_name, &to_sb) == 0 && to_sb.st_flags & NOCHANGEBITS)
269			(void)chflags(to_name, to_sb.st_flags & ~NOCHANGEBITS);
270		unlink(to_name);
271
272		/* Create target. */
273		to_fd = open(to_name,
274			     O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR);
275		if (to_fd < 0)
276			err(EX_OSERR, "%s", to_name);
277	}
278
279	if (!devnull) {
280		if ((from_fd = open(from_name, O_RDONLY, 0)) < 0) {
281			serrno = errno;
282			(void)unlink(to_name);
283			errno = serrno;
284			err(EX_OSERR, "%s", from_name);
285		}
286		copy(from_fd, from_name, to_fd, to_name, from_sb.st_size);
287		(void)close(from_fd);
288	}
289
290	if (dostrip)
291		strip(to_name);
292
293	/*
294	 * Unfortunately, because we strip the installed file and not the
295	 * original one, it is impossible to do the comparison without
296	 * first laboriously copying things over and then comparing.
297	 * It may be possible to better optimize the !dostrip case, however.
298	 * For further study.
299	 */
300	if (docompare) {
301		struct stat old_sb, new_sb, timestamp_sb;
302		int old_fd;
303		struct utimbuf utb;
304
305		old_fd = open(old_to_name, O_RDONLY, 0);
306		if (old_fd < 0 && errno == ENOENT)
307			goto different;
308		if (old_fd < 0)
309			err(EX_OSERR, "%s", old_to_name);
310		fstat(old_fd, &old_sb);
311		if (old_sb.st_flags & NOCHANGEBITS)
312			(void)fchflags(old_fd, old_sb.st_flags & ~NOCHANGEBITS);
313		fstat(to_fd, &new_sb);
314		if (compare(old_fd, old_to_name, to_fd, to_name, &old_sb,
315			    &new_sb)) {
316different:
317			if (debug != 0)
318				fprintf(stderr,
319					"install: renaming for %s: %s to %s\n",
320					from_name, to_name, old_to_name);
321			if (dopreserve && stat(from_name, &timestamp_sb) == 0) {
322				utb.actime = from_sb.st_atime;
323				utb.modtime = from_sb.st_mtime;
324				(void)utime(to_name, &utb);
325			}
326moveit:
327			if (rename(to_name, old_to_name) < 0) {
328				serrno = errno;
329				unlink(to_name);
330				unlink(old_to_name);
331				errno = serrno;
332				err(EX_OSERR, "rename: %s to %s", to_name,
333				    old_to_name);
334			}
335			close(old_fd);
336		} else {
337			if (old_sb.st_nlink != 1) {
338				/*
339				 * Replace the target, although it hasn't
340				 * changed, to snap the extra links.  But
341				 * preserve the target file times.
342				 */
343				if (fstat(old_fd, &timestamp_sb) == 0) {
344					utb.actime = timestamp_sb.st_atime;
345					utb.modtime = timestamp_sb.st_mtime;
346					(void)utime(to_name, &utb);
347				}
348				goto moveit;
349			}
350			if (unlink(to_name) < 0)
351				err(EX_OSERR, "unlink: %s", to_name);
352			close(to_fd);
353			to_fd = old_fd;
354		}
355		to_name = old_to_name;
356	}
357
358	/*
359	 * Set owner, group, mode for target; do the chown first,
360	 * chown may lose the setuid bits.
361	 */
362	if ((group || owner) &&
363	    fchown(to_fd, owner ? pp->pw_uid : -1, group ? gp->gr_gid : -1)) {
364		serrno = errno;
365		(void)unlink(to_name);
366		errno = serrno;
367		err(EX_OSERR,"%s: chown/chgrp", to_name);
368	}
369	if (fchmod(to_fd, mode)) {
370		serrno = errno;
371		(void)unlink(to_name);
372		errno = serrno;
373		err(EX_OSERR, "%s: chmod", to_name);
374	}
375
376	/*
377	 * If provided a set of flags, set them, otherwise, preserve the
378	 * flags, except for the dump flag.
379	 */
380	if (fchflags(to_fd,
381	    flags & SETFLAGS ? fset : from_sb.st_flags & ~UF_NODUMP)) {
382		serrno = errno;
383		(void)unlink(to_name);
384		errno = serrno;
385		err(EX_OSERR, "%s: chflags", to_name);
386	}
387
388	(void)close(to_fd);
389	if (!docopy && !devnull && unlink(from_name))
390		err(EX_OSERR, "%s", from_name);
391}
392
393/*
394 * compare --
395 *	compare two files; non-zero means files differ
396 */
397int
398compare(int from_fd, const char *from_name, int to_fd, const char *to_name,
399	const struct stat *from_sb, const struct stat *to_sb)
400{
401	char *p, *q;
402	int rv;
403	size_t tsize;
404
405	if (from_sb->st_size != to_sb->st_size)
406		return 1;
407
408	tsize = (size_t)from_sb->st_size;
409
410	if (tsize <= 8 * 1024 * 1024) {
411		p = mmap(NULL, tsize, PROT_READ, 0, from_fd, (off_t)0);
412		if ((long)p == -1)
413			err(EX_OSERR, "mmap %s", from_name);
414		q = mmap(NULL, tsize, PROT_READ, 0, to_fd, (off_t)0);
415		if ((long)q == -1)
416			err(EX_OSERR, "mmap %s", to_name);
417
418		rv = memcmp(p, q, tsize);
419		munmap(p, tsize);
420		munmap(q, tsize);
421	} else {
422		rv = 1;		/* don't bother in this case */
423	}
424	return rv;
425}
426
427/*
428 * copy --
429 *	copy from one file to another
430 */
431void
432copy(from_fd, from_name, to_fd, to_name, size)
433	register int from_fd, to_fd;
434	char *from_name, *to_name;
435	off_t size;
436{
437	register int nr, nw;
438	int serrno;
439	char *p, buf[MAXBSIZE];
440
441	/*
442	 * Mmap and write if less than 8M (the limit is so we don't totally
443	 * trash memory on big files.  This is really a minor hack, but it
444	 * wins some CPU back.
445	 */
446	if (size <= 8 * 1048576) {
447		if ((p = mmap(NULL, (size_t)size, PROT_READ,
448		    0, from_fd, (off_t)0)) == (char *)-1)
449			err(EX_OSERR, "mmap %s", from_name);
450		if (write(to_fd, p, size) != size)
451			err(EX_OSERR, "%s", to_name);
452	} else {
453		while ((nr = read(from_fd, buf, sizeof(buf))) > 0)
454			if ((nw = write(to_fd, buf, nr)) != nr) {
455				serrno = errno;
456				(void)unlink(to_name);
457				errno = nw > 0 ? EIO : serrno;
458				err(EX_OSERR, "%s", to_name);
459			}
460		if (nr != 0) {
461			serrno = errno;
462			(void)unlink(to_name);
463			errno = serrno;
464			err(EX_OSERR, "%s", from_name);
465		}
466	}
467}
468
469/*
470 * strip --
471 *	use strip(1) to strip the target file
472 */
473void
474strip(to_name)
475	char *to_name;
476{
477	int serrno, status;
478
479	switch (vfork()) {
480	case -1:
481		serrno = errno;
482		(void)unlink(to_name);
483		errno = serrno;
484		err(EX_TEMPFAIL, "fork");
485	case 0:
486		execl(_PATH_STRIP, "strip", to_name, NULL);
487		err(EX_OSERR, "exec(" _PATH_STRIP ")");
488	default:
489		if (wait(&status) == -1 || status)
490			(void)unlink(to_name);
491	}
492}
493
494/*
495 * usage --
496 *	print a usage message and die
497 */
498void
499usage()
500{
501	(void)fprintf(stderr,
502"usage: install [-Ccdps] [-f flags] [-g group] [-m mode] [-o owner] file1 file2;\n\tor file1 ... fileN directory\n");
503	exit(1);
504}
505