Merge branch 'vendor/OPENSSL'
[dragonfly.git] / usr.bin / xinstall / xinstall.c
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. Neither the name of the University nor the names of its contributors
14  *    may be used to endorse or promote products derived from this software
15  *    without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  *
29  * @(#) Copyright (c) 1987, 1993 The Regents of the University of California.  All rights reserved.
30  * @(#)xinstall.c       8.1 (Berkeley) 7/21/93
31  * $FreeBSD: src/usr.bin/xinstall/xinstall.c,v 1.38.2.8 2002/08/07 16:29:48 ru Exp $
32  */
33
34 #include <sys/param.h>
35 #include <sys/mman.h>
36 #include <sys/mount.h>
37 #include <sys/stat.h>
38 #include <sys/wait.h>
39
40 #include <ctype.h>
41 #include <err.h>
42 #include <errno.h>
43 #include <fcntl.h>
44 #include <grp.h>
45 #include <paths.h>
46 #include <pwd.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <sysexits.h>
51 #include <unistd.h>
52 #include <utime.h>
53
54 /* Bootstrap aid - this doesn't exist in most older releases */
55 #ifndef MAP_FAILED
56 #define MAP_FAILED ((void *)-1) /* from <sys/mman.h> */
57 #endif
58 #ifndef UF_NOHISTORY
59 #define UF_NOHISTORY    0
60 #endif
61
62 #define MAX_CMP_SIZE    (16 * 1024 * 1024)
63
64 #define DIRECTORY       0x01            /* Tell install it's a directory. */
65 #define SETFLAGS        0x02            /* Tell install to set flags. */
66 #define NOCHANGEBITS    (UF_IMMUTABLE | UF_APPEND | SF_IMMUTABLE | SF_APPEND)
67 #define BACKUP_SUFFIX   ".old"
68
69 static struct passwd *pp;
70 static struct group *gp;
71 static gid_t gid;
72 static uid_t uid;
73 static int dobackup, docompare, dodir, dopreserve, dostrip, dounpriv, nommap,
74     safecopy, verbose;
75 static mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
76 static const char *suffix = BACKUP_SUFFIX;
77 static char *destdir, *fflags;
78
79 static int file_getgroup(const char *etcdir, const char *group, gid_t *gidret);
80 static int file_getowner(const char *etcdir, const char *owner, uid_t *uidret);
81
82 static int      compare(int, const char *, size_t, int, const char *, size_t);
83 static void     copy(int, const char *, int, const char *, off_t);
84 static int      create_newfile(const char *, int, struct stat *);
85 static int      create_tempfile(const char *, char *, size_t);
86 static void     install(const char *, const char *, u_long, u_long, u_int);
87 static void     install_dir(char *);
88 u_long  numeric_id(const char *, const char *);
89 static void     strip(const char *);
90 static int      trymmap(int);
91 static void     usage(void);
92
93 int
94 main(int argc, char *argv[])
95 {
96         struct stat from_sb, to_sb;
97         mode_t *set;
98         u_long fset;
99         u_long fclr;
100         int ch, no_target;
101         int trysys;
102         u_int iflags;
103         const char *group, *owner, *to_name;
104         const char *etcdir;
105
106         iflags = 0;
107         trysys = 0;
108         group = NULL;
109         owner = NULL;
110         etcdir = NULL;
111
112         while ((ch = getopt(argc, argv, "L:B:bCcD:df:g:lMm:o:pSsUv")) != -1)
113                 switch((char)ch) {
114                 case 'B':
115                         suffix = optarg;
116                         /* FALLTHROUGH */
117                 case 'b':
118                         dobackup = 1;
119                         break;
120                 case 'C':
121                         docompare = 1;
122                         break;
123                 case 'c':
124                         /* For backwards compatibility. */
125                         break;
126                 case 'D':
127                         destdir = optarg;
128                         break;
129                 case 'd':
130                         dodir = 1;
131                         break;
132                 case 'f':
133                         fflags = optarg;
134                         break;
135                 case 'g':
136                         group = optarg;
137                         break;
138                 case 'L':
139                         etcdir = optarg;
140                         break;
141                 case 'l':
142                         trysys = 1;
143                         break;
144                 case 'M':
145                         nommap = 1;
146                         break;
147                 case 'm':
148                         if (!(set = setmode(optarg)))
149                                 errx(EX_USAGE, "invalid file mode: %s",
150                                      optarg);
151                         mode = getmode(set, 0);
152                         free(set);
153                         break;
154                 case 'o':
155                         owner = optarg;
156                         break;
157                 case 'p':
158                         docompare = dopreserve = 1;
159                         break;
160                 case 'S':
161                         safecopy = 1;
162                         break;
163                 case 's':
164                         dostrip = 1;
165                         break;
166                 case 'U':
167                         dounpriv = 1;
168                         break;
169                 case 'v':
170                         verbose = 1;
171                         break;
172                 case '?':
173                 default:
174                         usage();
175                 }
176         argc -= optind;
177         argv += optind;
178
179         /* some options make no sense when creating directories */
180         if (dostrip && dodir) {
181                 warnx("-d and -s may not be specified together");
182                 usage();
183         }
184
185         if (getenv("DONTSTRIP") != NULL) {
186                 warnx("DONTSTRIP set - will not strip installed binaries");
187                 dostrip = 0;
188         }
189
190         /* must have at least two arguments, except when creating directories */
191         if (argc < 2 && !dodir)
192                 usage();
193
194         /* need to make a temp copy so we can compare stripped version */
195         if (docompare && dostrip)
196                 safecopy = 1;
197
198         /* no etcdir specified, always try the system */
199         if (etcdir == NULL)
200                 trysys = 1;
201         uid = (uid_t)-1;
202         gid = (gid_t)-1;
203
204         /* get group and owner id's */
205         if (group != NULL && !dounpriv) {
206                 if (etcdir && file_getgroup(etcdir, group, &gid)) {
207                         ;
208                 } else if (trysys && (gp = getgrnam(group)) != NULL) {
209                         gid = gp->gr_gid;
210                 } else {
211                         gid = (gid_t)numeric_id(group, "group");
212                 }
213         }
214
215         if (owner != NULL && !dounpriv) {
216                 if (etcdir && file_getowner(etcdir, owner, &uid)) {
217                         ;
218                 } else if (trysys && (pp = getpwnam(owner)) != NULL) {
219                         uid = pp->pw_uid;
220                 } else {
221                         uid = (uid_t)numeric_id(owner, "user");
222                 }
223         }
224
225         if (fflags != NULL && !dounpriv) {
226                 if (strtofflags(&fflags, &fset, &fclr))
227                         errx(EX_USAGE, "%s: invalid flag", fflags);
228                 iflags |= SETFLAGS;
229         }
230
231         if (dodir) {
232                 for (; *argv != NULL; ++argv)
233                         install_dir(*argv);
234                 exit(EX_OK);
235                 /* NOTREACHED */
236         }
237
238         to_name = argv[argc - 1];
239         no_target = stat(to_name, &to_sb);
240         if (!no_target && S_ISDIR(to_sb.st_mode)) {
241                 for (; *argv != to_name; ++argv)
242                         install(*argv, to_name, fset, fclr, iflags | DIRECTORY);
243                 exit(EX_OK);
244                 /* NOTREACHED */
245         }
246
247         /* can't do file1 file2 directory/file */
248         if (argc != 2) {
249                 if (no_target)
250                         warnx("target directory `%s' does not exist",
251                             argv[argc - 1]);
252                 else
253                         warnx("target `%s' is not a directory",
254                             argv[argc - 1]);
255                 usage();
256         }
257
258         if (!no_target) {
259                 if (stat(*argv, &from_sb))
260                         err(EX_OSERR, "%s", *argv);
261                 if (!S_ISREG(to_sb.st_mode)) {
262                         errno = EFTYPE;
263                         err(EX_OSERR, "%s", to_name);
264                 }
265                 if (to_sb.st_dev == from_sb.st_dev &&
266                     to_sb.st_ino == from_sb.st_ino)
267                         errx(EX_USAGE, 
268                             "%s and %s are the same file", *argv, to_name);
269         }
270         install(*argv, to_name, fset, fclr, iflags);
271         exit(EX_OK);
272         /* NOTREACHED */
273 }
274
275 u_long
276 numeric_id(const char *name, const char *type)
277 {
278         u_long val;
279         char *ep;
280
281         /*
282          * XXX
283          * We know that uid_t's and gid_t's are unsigned longs.
284          */
285         errno = 0;
286         val = strtoul(name, &ep, 10);
287         if (errno)
288                 err(EX_NOUSER, "%s", name);
289         if (*ep != '\0')
290                 errx(EX_NOUSER, "unknown %s %s", type, name);
291         return (val);
292 }
293
294 static
295 int
296 file_getgroup(const char *etcdir, const char *group, gid_t *gidret)
297 {
298         FILE *fp;
299         size_t len;
300         size_t grlen;
301         char *path;
302         char *ptr;
303         char *scan;
304
305         grlen = strlen(group);
306
307         if (asprintf(&path, "%s/group", etcdir) < 0)
308                 errx(EX_OSERR, "asprintf()");
309         if ((fp = fopen(path, "r")) != NULL) {
310                 while ((ptr = fgetln(fp, &len)) != NULL && len) {
311                         ptr[len - 1] = 0;
312                         if ((scan = strchr(ptr, ':')) == NULL)
313                                 continue;
314                         if ((size_t)(scan - ptr) != grlen)
315                                 continue;
316                         if (strncmp(ptr, group, grlen) != 0)
317                                 continue;
318                         if ((scan = strchr(scan + 1, ':')) == NULL)
319                                 continue;
320                         *gidret = strtoul(scan + 1, NULL, 10);
321                         break;
322                 }
323                 fclose(fp);
324         }
325         free(path);
326         return((*gidret == (gid_t)-1) ? 0 : 1);
327 }
328
329 static
330 int
331 file_getowner(const char *etcdir, const char *owner, uid_t *uidret)
332 {
333         FILE *fp;
334         size_t len;
335         size_t owner_len;
336         char *path;
337         char *ptr;
338         char *scan;
339
340         owner_len = strlen(owner);
341
342         if (asprintf(&path, "%s/master.passwd", etcdir) < 0)
343                 errx(EX_OSERR, "asprintf()");
344         if ((fp = fopen(path, "r")) != NULL) {
345                 while ((ptr = fgetln(fp, &len)) != NULL && len) {
346                         ptr[len - 1] = 0;
347                         if ((scan = strchr(ptr, ':')) == NULL)
348                                 continue;
349                         if ((size_t)(scan - ptr) != owner_len)
350                                 continue;
351                         if (strncmp(ptr, owner, owner_len) != 0)
352                                 continue;
353                         if ((scan = strchr(scan + 1, ':')) == NULL)
354                                 continue;
355                         *uidret = strtoul(scan + 1, NULL, 10);
356                         break;
357                 }
358                 fclose(fp);
359         }
360         free(path);
361         return((*uidret == (uid_t)-1) ? 0 : 1);
362 }
363
364 /*
365  * install --
366  *      build a path name and install the file
367  */
368 static void
369 install(const char *from_name, const char *to_name, u_long fset, u_long fclr,
370         u_int flags)
371 {
372         struct stat from_sb, temp_sb, to_sb;
373         struct utimbuf utb;
374         int devnull, files_match, from_fd, serrno, target;
375         int tempcopy, temp_fd, to_fd;
376         u_long nfset;
377         char backup[MAXPATHLEN], *p, pathbuf[MAXPATHLEN], tempfile[MAXPATHLEN];
378
379         files_match = 0;
380         from_fd = -1;
381
382         /* If try to install NULL file to a directory, fails. */
383         if (flags & DIRECTORY || strcmp(from_name, _PATH_DEVNULL)) {
384                 if (stat(from_name, &from_sb))
385                         err(EX_OSERR, "%s", from_name);
386                 if (!S_ISREG(from_sb.st_mode)) {
387                         errno = EFTYPE;
388                         err(EX_OSERR, "%s", from_name);
389                 }
390                 /* Build the target path. */
391                 if (flags & DIRECTORY) {
392                         (void)snprintf(pathbuf, sizeof(pathbuf), "%s/%s",
393                             to_name,
394                             (p = strrchr(from_name, '/')) ? ++p : from_name);
395                         to_name = pathbuf;
396                 }
397                 devnull = 0;
398         } else {
399                 devnull = 1;
400         }
401
402         target = stat(to_name, &to_sb) == 0;
403
404         /* Only install to regular files. */
405         if (target && !S_ISREG(to_sb.st_mode)) {
406                 errno = EFTYPE;
407                 warn("%s", to_name);
408                 return;
409         }
410
411         /* Only copy safe if the target exists. */
412         tempcopy = safecopy && target;
413
414         if (!devnull && (from_fd = open(from_name, O_RDONLY, 0)) < 0)
415                 err(EX_OSERR, "%s", from_name);
416
417         /* If we don't strip, we can compare first. */
418         if (docompare && !dostrip && target) {
419                 if ((to_fd = open(to_name, O_RDONLY, 0)) < 0)
420                         err(EX_OSERR, "%s", to_name);
421                 if (devnull)
422                         files_match = to_sb.st_size == 0;
423                 else
424                         files_match = !(compare(from_fd, from_name,
425                             (size_t)from_sb.st_size, to_fd,
426                             to_name, (size_t)to_sb.st_size));
427
428                 /* Close "to" file unless we match. */
429                 if (!files_match)
430                         (void)close(to_fd);
431         }
432
433         if (!files_match) {
434                 if (tempcopy) {
435                         to_fd = create_tempfile(to_name, tempfile,
436                             sizeof(tempfile));
437                         if (to_fd < 0)
438                                 err(EX_OSERR, "%s", tempfile);
439                 } else {
440                         if ((to_fd = create_newfile(to_name, target,
441                             &to_sb)) < 0)
442                                 err(EX_OSERR, "%s", to_name);
443                         if (verbose)
444                                 (void)printf("install: %s -> %s\n",
445                                     from_name, to_name);
446                 }
447                 if (!devnull)
448                         copy(from_fd, from_name, to_fd,
449                              tempcopy ? tempfile : to_name, from_sb.st_size);
450         }
451
452         if (dostrip) {
453                 strip(tempcopy ? tempfile : to_name);
454
455                 /*
456                  * Re-open our fd on the target, in case we used a strip
457                  * that does not work in-place -- like GNU binutils strip.
458                  */
459                 close(to_fd);
460                 to_fd = open(tempcopy ? tempfile : to_name, O_RDONLY, 0);
461                 if (to_fd < 0)
462                         err(EX_OSERR, "stripping %s", to_name);
463         }
464
465         /*
466          * Compare the stripped temp file with the target.
467          */
468         if (docompare && dostrip && target) {
469                 temp_fd = to_fd;
470
471                 /* Re-open to_fd using the real target name. */
472                 if ((to_fd = open(to_name, O_RDONLY, 0)) < 0)
473                         err(EX_OSERR, "%s", to_name);
474
475                 if (fstat(temp_fd, &temp_sb)) {
476                         serrno = errno;
477                         (void)unlink(tempfile);
478                         errno = serrno;
479                         err(EX_OSERR, "%s", tempfile);
480                 }
481
482                 if (compare(temp_fd, tempfile, (size_t)temp_sb.st_size, to_fd,
483                             to_name, (size_t)to_sb.st_size) == 0) {
484                         /*
485                          * If target has more than one link we need to
486                          * replace it in order to snap the extra links.
487                          * Need to preserve target file times, though.
488                          */
489                         if (to_sb.st_nlink != 1) {
490                                 utb.actime = to_sb.st_atime;
491                                 utb.modtime = to_sb.st_mtime;
492                                 utime(tempfile, &utb);
493                         } else {
494                                 files_match = 1;
495                                 (void)unlink(tempfile);
496                         }
497                         (void) close(temp_fd);
498                 }
499         }
500
501         /*
502          * Move the new file into place if doing a safe copy
503          * and the files are different (or just not compared).
504          */
505         if (tempcopy && !files_match) {
506                 /* Try to turn off the immutable bits. */
507                 if (to_sb.st_flags & NOCHANGEBITS)
508                         (void)chflags(to_name, to_sb.st_flags & ~NOCHANGEBITS);
509                 if (dobackup) {
510                         if ((size_t)snprintf(backup, MAXPATHLEN, "%s%s", to_name,
511                             suffix) != strlen(to_name) + strlen(suffix)) {
512                                 unlink(tempfile);
513                                 errx(EX_OSERR, "%s: backup filename too long",
514                                     to_name);
515                         }
516                         if (verbose)
517                                 (void)printf("install: %s -> %s\n", to_name, backup);
518                         if (rename(to_name, backup) < 0) {
519                                 serrno = errno;
520                                 unlink(tempfile);
521                                 errno = serrno;
522                                 err(EX_OSERR, "rename: %s to %s", to_name,
523                                      backup);
524                         }
525                 }
526                 if (verbose)
527                         (void)printf("install: %s -> %s\n", from_name, to_name);
528                 if (rename(tempfile, to_name) < 0) {
529                         serrno = errno;
530                         unlink(tempfile);
531                         errno = serrno;
532                         err(EX_OSERR, "rename: %s to %s",
533                             tempfile, to_name);
534                 }
535
536                 /* Re-open to_fd so we aren't hosed by the rename(2). */
537                 (void) close(to_fd);
538                 if ((to_fd = open(to_name, O_RDONLY, 0)) < 0)
539                         err(EX_OSERR, "%s", to_name);
540         }
541
542         /*
543          * Preserve the timestamp of the source file if necessary.
544          */
545         if (dopreserve && !files_match && !devnull) {
546                 utb.actime = from_sb.st_atime;
547                 utb.modtime = from_sb.st_mtime;
548                 utime(to_name, &utb);
549         }
550
551         if (fstat(to_fd, &to_sb) == -1) {
552                 serrno = errno;
553                 (void)unlink(to_name);
554                 errno = serrno;
555                 err(EX_OSERR, "%s", to_name);
556         }
557
558         /*
559          * Set owner, group, mode for target; do the chown first,
560          * chown may lose the setuid bits.
561          */
562         if (!dounpriv && ((gid != (gid_t)-1 && gid != to_sb.st_gid) ||
563             (uid != (uid_t)-1 && uid != to_sb.st_uid) ||
564             (mode != to_sb.st_mode))) {
565                 /* Try to turn off the immutable bits. */
566                 if (to_sb.st_flags & NOCHANGEBITS)
567                         (void)fchflags(to_fd, to_sb.st_flags & ~NOCHANGEBITS);
568         }
569
570         if (!dounpriv && (
571             (gid != (gid_t)-1 && gid != to_sb.st_gid) ||
572             (uid != (uid_t)-1 && uid != to_sb.st_uid)))
573                 if (fchown(to_fd, uid, gid) == -1) {
574                         serrno = errno;
575                         (void)unlink(to_name);
576                         errno = serrno;
577                         err(EX_OSERR,"%s: chown/chgrp", to_name);
578                 }
579
580         if (mode != to_sb.st_mode) {
581                 if (fchmod(to_fd,
582                      dounpriv ? mode & (S_IRWXU|S_IRWXG|S_IRWXO) : mode)) {
583                         serrno = errno;
584                         (void)unlink(to_name);
585                         errno = serrno;
586                         err(EX_OSERR, "%s: chmod", to_name);
587                 }
588         }
589
590         /*
591          * If provided a set of flags, set them, otherwise, preserve the
592          * flags, except for the dump and history flags.  The dump flag
593          * is left clear on the target while the history flag from when
594          * the target was created (which is inherited from the target's
595          * parent directory) is retained.
596          */
597         if (flags & SETFLAGS) {
598                 nfset = (to_sb.st_flags | fset) & ~fclr;
599         } else {
600                 nfset = (from_sb.st_flags & ~(UF_NODUMP | UF_NOHISTORY)) |
601                         (to_sb.st_flags & UF_NOHISTORY);
602         }
603
604         /*
605          * NFS does not support flags.  Ignore EOPNOTSUPP flags if we're just
606          * trying to turn off UF_NODUMP.  If we're trying to set real flags,
607          * then warn if the fs doesn't support it, otherwise fail.
608          */
609         if (!dounpriv && !devnull && fchflags(to_fd, nfset)) {
610                 if (flags & SETFLAGS) {
611                         if (errno == EOPNOTSUPP)
612                                 warn("%s: chflags", to_name);
613                         else {
614                                 serrno = errno;
615                                 (void)unlink(to_name);
616                                 errno = serrno;
617                                 err(EX_OSERR, "%s: chflags", to_name);
618                         }
619                 }
620         }
621
622         (void)close(to_fd);
623         if (!devnull)
624                 (void)close(from_fd);
625 }
626
627 /*
628  * compare --
629  *      compare two files; non-zero means files differ
630  */
631 static int
632 compare(int from_fd, const char *from_name __unused, size_t from_len,
633         int to_fd, const char *to_name __unused, size_t to_len)
634 {
635         char *p, *q;
636         int rv;
637         int done_compare;
638
639         rv = 0;
640         if (from_len != to_len)
641                 return 1;
642
643         if (from_len <= MAX_CMP_SIZE) {
644                 done_compare = 0;
645                 if (trymmap(from_fd) && trymmap(to_fd)) {
646                         p = mmap(NULL, from_len, PROT_READ, MAP_SHARED, from_fd, (off_t)0);
647                         if (p == (char *)MAP_FAILED)
648                                 goto out;
649                         q = mmap(NULL, from_len, PROT_READ, MAP_SHARED, to_fd, (off_t)0);
650                         if (q == (char *)MAP_FAILED) {
651                                 munmap(p, from_len);
652                                 goto out;
653                         }
654
655                         rv = memcmp(p, q, from_len);
656                         munmap(p, from_len);
657                         munmap(q, from_len);
658                         done_compare = 1;
659                 }
660         out:
661                 if (!done_compare) {
662                         char buf1[MAXBSIZE];
663                         char buf2[MAXBSIZE];
664                         int n1, n2;
665
666                         rv = 0;
667                         lseek(from_fd, 0, SEEK_SET);
668                         lseek(to_fd, 0, SEEK_SET);
669                         while (rv == 0) {
670                                 n1 = read(from_fd, buf1, sizeof(buf1));
671                                 if (n1 == 0)
672                                         break;          /* EOF */
673                                 else if (n1 > 0) {
674                                         n2 = read(to_fd, buf2, n1);
675                                         if (n2 == n1)
676                                                 rv = memcmp(buf1, buf2, n1);
677                                         else
678                                                 rv = 1; /* out of sync */
679                                 } else
680                                         rv = 1;         /* read failure */
681                         }
682                         lseek(from_fd, 0, SEEK_SET);
683                         lseek(to_fd, 0, SEEK_SET);
684                 }
685         } else
686                 rv = 1; /* don't bother in this case */
687
688         return rv;
689 }
690
691 /*
692  * create_tempfile --
693  *      create a temporary file based on path and open it
694  */
695 static int
696 create_tempfile(const char *path, char *temp, size_t tsize)
697 {
698         char *p;
699
700         (void)strncpy(temp, path, tsize);
701         temp[tsize - 1] = '\0';
702         if ((p = strrchr(temp, '/')) != NULL)
703                 p++;
704         else
705                 p = temp;
706         (void)strncpy(p, "INS@XXXX", &temp[tsize - 1] - p);
707         temp[tsize - 1] = '\0';
708         return (mkstemp(temp));
709 }
710
711 /*
712  * create_newfile --
713  *      create a new file, overwriting an existing one if necessary
714  */
715 static int
716 create_newfile(const char *path, int target, struct stat *sbp)
717 {
718         char backup[MAXPATHLEN];
719
720         if (target) {
721                 /*
722                  * Unlink now... avoid ETXTBSY errors later.  Try to turn
723                  * off the append/immutable bits -- if we fail, go ahead,
724                  * it might work.
725                  */
726                 if (sbp->st_flags & NOCHANGEBITS)
727                         (void)chflags(path, sbp->st_flags & ~NOCHANGEBITS);
728
729                 if (dobackup) {
730                         if ((size_t)snprintf(backup, MAXPATHLEN, "%s%s",
731                             path, suffix) != strlen(path) + strlen(suffix))
732                                 errx(EX_OSERR, "%s: backup filename too long",
733                                     path);
734                         (void)snprintf(backup, MAXPATHLEN, "%s%s",
735                             path, suffix);
736                         if (verbose)
737                                 (void)printf("install: %s -> %s\n",
738                                     path, backup);
739                         if (rename(path, backup) < 0)
740                                 err(EX_OSERR, "rename: %s to %s", path, backup);
741                 } else
742                         unlink(path);
743         }
744
745         return (open(path, O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR));
746 }
747
748 /*
749  * copy --
750  *      copy from one file to another
751  */
752 static void
753 copy(int from_fd, const char *from_name, int to_fd,
754      const char *to_name, off_t size)
755 {
756         int nr, nw;
757         int serrno;
758         char *p;
759         char buf[MAXBSIZE];
760         int done_copy;
761
762         /* Rewind file descriptors. */
763         if (lseek(from_fd, (off_t)0, SEEK_SET) == (off_t)-1)
764                 err(EX_OSERR, "lseek: %s", from_name);
765         if (lseek(to_fd, (off_t)0, SEEK_SET) == (off_t)-1)
766                 err(EX_OSERR, "lseek: %s", to_name);
767
768         /*
769          * Mmap and write if less than 8M (the limit is so we don't totally
770          * trash memory on big files.  This is really a minor hack, but it
771          * wins some CPU back.
772          */
773         done_copy = 0;
774         if (size <= 8 * 1048576 && trymmap(from_fd) &&
775             (p = mmap(NULL, (size_t)size, PROT_READ, MAP_SHARED,
776                     from_fd, (off_t)0)) != (char *)MAP_FAILED) {
777                 if ((nw = write(to_fd, p, size)) != size) {
778                         serrno = errno;
779                         (void)unlink(to_name);
780                         errno = nw > 0 ? EIO : serrno;
781                         err(EX_OSERR, "%s", to_name);
782                 }
783                 done_copy = 1;
784         }
785         if (!done_copy) {
786                 while ((nr = read(from_fd, buf, sizeof(buf))) > 0) {
787                         if ((nw = write(to_fd, buf, nr)) != nr) {
788                                 serrno = errno;
789                                 (void)unlink(to_name);
790                                 errno = nw > 0 ? EIO : serrno;
791                                 err(EX_OSERR, "%s", to_name);
792                         }
793                 }
794                 if (nr != 0) {
795                         serrno = errno;
796                         (void)unlink(to_name);
797                         errno = serrno;
798                         err(EX_OSERR, "%s", from_name);
799                 }
800         }
801 }
802
803 /*
804  * strip --
805  *      use strip(1) to strip the target file
806  */
807 static void
808 strip(const char *to_name)
809 {
810         const char *stripbin;
811         int serrno, status;
812
813         switch (fork()) {
814         case -1:
815                 serrno = errno;
816                 (void)unlink(to_name);
817                 errno = serrno;
818                 err(EX_TEMPFAIL, "fork");
819         case 0:
820                 stripbin = getenv("STRIPBIN");
821                 if (stripbin == NULL)
822                         stripbin = "strip";
823                 execlp(stripbin, stripbin, to_name, NULL);
824                 err(EX_OSERR, "exec(%s)", stripbin);
825         default:
826                 if (wait(&status) == -1 || status) {
827                         serrno = errno;
828                         (void)unlink(to_name);
829                         errc(EX_SOFTWARE, serrno, "wait");
830                         /* NOTREACHED */
831                 }
832         }
833 }
834
835 /*
836  * When doing a concurrent make -j N multiple install's can race the mkdir.
837  */
838 static
839 int
840 mkdir_race(const char *path, int nmode)
841 {
842         int res;
843         struct stat sb;
844
845         res = mkdir(path, nmode);
846         if (res < 0 && errno == EEXIST) {
847                 if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode))
848                         return(0);
849                 res = mkdir(path, nmode);
850         }
851         return (res);
852 }
853
854 /*
855  * install_dir --
856  *      build directory hierarchy
857  */
858 static void
859 install_dir(char *path)
860 {
861         char *p;
862         struct stat sb;
863         int ch;
864
865         for (p = path;; ++p)
866                 if (!*p || (p != path && *p  == '/')) {
867                         ch = *p;
868                         *p = '\0';
869                         if (stat(path, &sb)) {
870                                 if (errno != ENOENT ||
871                                     mkdir_race(path, 0755) < 0) {
872                                         err(EX_OSERR, "mkdir %s", path);
873                                         /* NOTREACHED */
874                                 } else if (verbose)
875                                         (void)printf("install: mkdir %s\n",
876                                                      path);
877                         } else if (!S_ISDIR(sb.st_mode))
878                                 errx(EX_OSERR, "%s exists but is not a directory", path);
879                         if (!(*p = ch))
880                                 break;
881                 }
882
883         if (!dounpriv) {
884                 if ((gid != (gid_t)-1 || uid != (uid_t)-1) &&
885                     chown(path, uid, gid))
886                         warn("chown %u:%u %s", uid, gid, path);
887                 /* XXXBED: should we do the chmod in the dounpriv case? */
888                 if (chmod(path, mode))
889                         warn("chmod %o %s", mode, path);
890         }
891 }
892
893 /*
894  * usage --
895  *      print a usage message and die
896  */
897 static void
898 usage(void)
899 {
900         fprintf(stderr,
901 "usage: install [-bCcpSsUv] [-B suffix] [-D dest] [-f flags] [-g group]\n"
902 "               [-m mode] [-o owner] file1 file2\n"
903 "       install [-bCcpSsUv] [-B suffix] [-D dest] [-f flags] [-g group]\n"
904 "               [-m mode] [-o owner] file1 ... fileN directory\n"
905 "       install -d [-lUv] [-D dest] [-g group] [-m mode] [-o owner]\n"
906 "               directory ...\n");
907         exit(EX_USAGE);
908         /* NOTREACHED */
909 }
910
911 /*
912  * trymmap --
913  *      return true (1) if mmap should be tried, false (0) if not.
914  */
915 static int
916 trymmap(int fd)
917 {
918 /*
919  * The ifdef is for bootstrapping - f_fstypename doesn't exist in
920  * pre-Lite2-merge systems.
921  */
922 #ifdef MFSNAMELEN
923         struct statfs stfs;
924
925         if (nommap || fstatfs(fd, &stfs) != 0)
926                 return (0);
927         if (strcmp(stfs.f_fstypename, "ufs") == 0 ||
928             strcmp(stfs.f_fstypename, "cd9660") == 0)
929                 return (1);
930 #endif
931         return (0);
932 }