source: trunk/src/kmk/kmkbuiltin/install.c@ 3214

Last change on this file since 3214 was 3214, checked in by bird, 7 years ago

kmk_install: use getopt_r.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 32.0 KB
Line 
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#if 0 /*ndef 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#if 0
41#ifndef lint
42static char sccsid[] = "@(#)xinstall.c 8.1 (Berkeley) 7/21/93";
43#endif /* not lint */
44
45#include <sys/cdefs.h>
46__FBSDID("$FreeBSD: src/usr.bin/xinstall/xinstall.c,v 1.66 2005/01/25 14:34:57 ssouhlal Exp $");
47#endif
48
49#define FAKES_NO_GETOPT_H
50#include "config.h"
51#ifndef _MSC_VER
52# include <sys/param.h>
53# if !defined(__HAIKU__) && !defined(__gnu_hurd__)
54# include <sys/mount.h>
55# endif
56# include <sys/wait.h>
57# include <sys/time.h>
58#endif /* !_MSC_VER */
59#include <sys/stat.h>
60
61#include <ctype.h>
62#include "err.h"
63#include <errno.h>
64#include <fcntl.h>
65#include <grp.h>
66#include <paths.h>
67#include <pwd.h>
68#include <stdio.h>
69#include <stdlib.h>
70#include <string.h>
71#ifndef __HAIKU__
72# include <sysexits.h>
73#endif
74#ifdef __NetBSD__
75# include <util.h>
76# define strtofflags(a, b, c) string_to_flags(a, b, c)
77#endif
78#include <unistd.h>
79#if defined(__EMX__) || defined(_MSC_VER)
80# include <process.h>
81#endif
82#include "getopt_r.h"
83#ifdef __sun__
84# include "solfakes.h"
85#endif
86#ifdef _MSC_VER
87# include "mscfakes.h"
88#endif
89#ifdef __HAIKU__
90# include "haikufakes.h"
91#endif
92#include "kmkbuiltin.h"
93#include "k/kDefs.h" /* for K_OS */
94#include "dos2unix.h"
95
96
97extern void * bsd_setmode(const char *p);
98extern mode_t bsd_getmode(const void *bbox, mode_t omode);
99
100#ifndef MAXBSIZE
101# define MAXBSIZE 0x20000
102#endif
103
104#define MAX_CMP_SIZE (16 * 1024 * 1024)
105
106#define DIRECTORY 0x01 /* Tell install it's a directory. */
107#define SETFLAGS 0x02 /* Tell install to set flags. */
108#define NOCHANGEBITS (UF_IMMUTABLE | UF_APPEND | SF_IMMUTABLE | SF_APPEND)
109#define BACKUP_SUFFIX ".old"
110
111#ifndef O_BINARY
112# define O_BINARY 0
113#endif
114
115#ifndef EFTYPE
116# define EFTYPE EINVAL
117#endif
118
119#if defined(__WIN32__) || defined(__WIN64__) || defined(__OS2__)
120# define IS_SLASH(ch) ((ch) == '/' || (ch) == '\\')
121#else
122# define IS_SLASH(ch) ((ch) == '/')
123#endif
124
125/*********************************************************************************************************************************
126* Structures and Typedefs *
127*********************************************************************************************************************************/
128typedef struct INSTALLINSTANCE
129{
130 PKMKBUILTINCTX pCtx;
131
132 gid_t gid;
133 uid_t uid;
134 int dobackup, docompare, dodir, dopreserve, dostrip, nommap, safecopy, verbose, mode_given;
135 mode_t mode;
136 const char *suffix;
137 int ignore_perm_errors;
138 int hard_link_files_when_possible;
139 int dos2unix;
140} INSTALLINSTANCE;
141typedef INSTALLINSTANCE *PINSTALLINSTANCE;
142
143
144/*********************************************************************************************************************************
145* Global Variables *
146*********************************************************************************************************************************/
147static struct option long_options[] =
148{
149 { "help", no_argument, 0, 261 },
150 { "version", no_argument, 0, 262 },
151 { "ignore-perm-errors", no_argument, 0, 263 },
152 { "no-ignore-perm-errors", no_argument, 0, 264 },
153 { "hard-link-files-when-possible", no_argument, 0, 265 },
154 { "no-hard-link-files-when-possible", no_argument, 0, 266 },
155 { "dos2unix", no_argument, 0, 267 },
156 { "unix2dos", no_argument, 0, 268 },
157 { 0, 0, 0, 0 },
158};
159
160
161static int copy(PINSTALLINSTANCE, int, const char *, int *, const char *);
162static int compare(int, size_t, int, size_t);
163static int create_newfile(PINSTALLINSTANCE, const char *, int, struct stat *);
164static int create_tempfile(const char *, char *, size_t);
165static int install(PINSTALLINSTANCE, const char *, const char *, u_long, u_int);
166static int install_dir(PINSTALLINSTANCE, char *);
167static u_long numeric_id(PINSTALLINSTANCE, const char *, const char *);
168static int strip(PINSTALLINSTANCE, const char *);
169static int usage(PKMKBUILTINCTX, int);
170static char *last_slash(const char *);
171static KBOOL needs_dos2unix_conversion(const char *pszFilename);
172static KBOOL needs_unix2dos_conversion(const char *pszFilename);
173
174int
175kmk_builtin_install(int argc, char *argv[], char ** envp, PKMKBUILTINCTX pCtx)
176{
177 INSTALLINSTANCE This;
178 struct getopt_state_r gos;
179 struct stat from_sb, to_sb;
180 mode_t *set;
181 u_long fset = 0;
182 int ch, no_target;
183 u_int iflags;
184 char *flags;
185 const char *group, *owner, *to_name;
186 (void)envp;
187
188 /* Initialize global instance data. */
189 This.pCtx = pCtx;
190 This.mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
191 This.suffix = BACKUP_SUFFIX;
192 This.gid = 0;
193 This.uid = 0;
194 This.dobackup = 0;
195 This.docompare = 0;
196 This.dodir = 0;
197 This.dopreserve = 0;
198 This.dostrip = 0;
199 This.nommap = 0;
200 This.safecopy = 0;
201 This.verbose = 0;
202 This.mode_given = 0;
203 This.ignore_perm_errors = geteuid() != 0;
204 This.hard_link_files_when_possible = 0;
205 This.dos2unix = 0;
206
207 iflags = 0;
208 group = owner = NULL;
209 getopt_initialize_r(&gos, argc, argv, "B:bCcdf:g:Mm:o:pSsv", long_options, envp, pCtx);
210 while ((ch = getopt_long_r(&gos, NULL)) != -1)
211 switch(ch) {
212 case 'B':
213 This.suffix = gos.optarg;
214 /* FALLTHROUGH */
215 case 'b':
216 This.dobackup = 1;
217 break;
218 case 'C':
219 This.docompare = 1;
220 break;
221 case 'c':
222 /* For backwards compatibility. */
223 break;
224 case 'd':
225 This.dodir = 1;
226 break;
227 case 'f':
228#if defined(UF_IMMUTABLE) && K_OS != K_OS_GNU_KFBSD && K_OS != K_OS_GNU_HURD
229 flags = optarg;
230 if (strtofflags(&flags, &fset, NULL))
231 return errx(pCtx, EX_USAGE, "%s: invalid flag", flags);
232 iflags |= SETFLAGS;
233#else
234 (void)flags;
235#endif
236 break;
237 case 'g':
238 group = gos.optarg;
239 break;
240 case 'M':
241 This.nommap = 1;
242 break;
243 case 'm':
244 if (!(set = bsd_setmode(gos.optarg)))
245 return errx(pCtx, EX_USAGE, "invalid file mode: %s", gos.optarg);
246 This.mode = bsd_getmode(set, 0);
247 free(set);
248 This.mode_given = 1;
249 break;
250 case 'o':
251 owner = gos.optarg;
252 break;
253 case 'p':
254 This.docompare = This.dopreserve = 1;
255 break;
256 case 'S':
257 This.safecopy = 1;
258 break;
259 case 's':
260 This.dostrip = 1;
261 break;
262 case 'v':
263 This.verbose = 1;
264 break;
265 case 261:
266 usage(pCtx, 0);
267 return 0;
268 case 262:
269 return kbuild_version(argv[0]);
270 case 263:
271 This.ignore_perm_errors = 1;
272 break;
273 case 264:
274 This.ignore_perm_errors = 0;
275 break;
276 case 265:
277 This.hard_link_files_when_possible = 1;
278 break;
279 case 266:
280 This.hard_link_files_when_possible = 0;
281 break;
282 case 267:
283 This.dos2unix = 1;
284 break;
285 case 268:
286 This.dos2unix = -1;
287 break;
288 case '?':
289 default:
290 return usage(pCtx, 1);
291 }
292 argc -= gos.optind;
293 argv += gos.optind;
294
295 /* some options make no sense when creating directories */
296 if (This.dostrip && This.dodir) {
297 warnx(pCtx, "-d and -s may not be specified together");
298 return usage(pCtx, 1);
299 }
300
301 /* must have at least two arguments, except when creating directories */
302 if (argc == 0 || (argc == 1 && !This.dodir))
303 return usage(pCtx, 1);
304
305 /* and unix2dos doesn't combine well with a couple of other options. */
306 if (This.dos2unix != 0) {
307 if (This.docompare) {
308 warnx(pCtx, "-C/-p and --dos2unix/unix2dos may not be specified together");
309 return usage(pCtx, 1);
310 }
311 if (This.dostrip) {
312 warnx(pCtx, "-s and --dos2unix/unix2dos may not be specified together");
313 return usage(pCtx, 1);
314 }
315 }
316
317 /* need to make a temp copy so we can compare stripped version */
318 if (This.docompare && This.dostrip)
319 This.safecopy = 1;
320
321 /* get group and owner id's */
322 if (group != NULL) {
323#ifndef _MSC_VER
324 struct group *gp;
325 if ((gp = getgrnam(group)) != NULL)
326 gid = gp->gr_gid;
327 else
328#endif
329 {
330 This.gid = (gid_t)numeric_id(&This, group, "group");
331 if (This.gid == (gid_t)-1)
332 return 1;
333 }
334 } else
335 This.gid = (gid_t)-1;
336
337 if (owner != NULL) {
338#ifndef _MSC_VER
339 struct passwd *pp;
340 if ((pp = getpwnam(owner)) != NULL)
341 uid = pp->pw_uid;
342 else
343#endif
344 {
345 This.uid = (uid_t)numeric_id(&This, owner, "user");
346 if (This.uid == (uid_t)-1)
347 return 1;
348 }
349 } else
350 This.uid = (uid_t)-1;
351
352 if (This.dodir) {
353 for (; *argv != NULL; ++argv) {
354 int rc = install_dir(&This, *argv);
355 if (rc)
356 return rc;
357 }
358 return EX_OK;
359 /* NOTREACHED */
360 }
361
362 no_target = stat(to_name = argv[argc - 1], &to_sb);
363 if (!no_target && S_ISDIR(to_sb.st_mode)) {
364 for (; *argv != to_name; ++argv) {
365 int rc = install(&This, *argv, to_name, fset, iflags | DIRECTORY);
366 if (rc)
367 return rc;
368 }
369 return EX_OK;
370 }
371
372 /* can't do file1 file2 directory/file */
373 if (argc != 2) {
374 warnx(pCtx, "wrong number or types of arguments");
375 return usage(pCtx, 1);
376 }
377
378 if (!no_target) {
379 if (stat(*argv, &from_sb))
380 return err(pCtx, EX_OSERR, "%s", *argv);
381 if (!S_ISREG(to_sb.st_mode)) {
382 errno = EFTYPE;
383 return err(pCtx, EX_OSERR, "%s", to_name);
384 }
385 if (to_sb.st_dev == from_sb.st_dev &&
386 to_sb.st_dev != 0 &&
387 to_sb.st_ino == from_sb.st_ino &&
388 to_sb.st_ino != 0 &&
389 !This.hard_link_files_when_possible)
390 return errx(pCtx, EX_USAGE,
391 "%s and %s are the same file", *argv, to_name);
392 }
393 return install(&This, *argv, to_name, fset, iflags);
394}
395
396#ifdef KMK_BUILTIN_STANDALONE
397int main(int argc, char **argv, char **envp)
398{
399 KMKBUILTINCTX Ctx = { "kmk_install", NULL };
400 return kmk_builtin_install(argc, argv, envp, &Ctx);
401}
402#endif
403
404static u_long
405numeric_id(PINSTALLINSTANCE pThis, const char *name, const char *type)
406{
407 u_long val;
408 char *ep;
409
410 /*
411 * XXX
412 * We know that uid_t's and gid_t's are unsigned longs.
413 */
414 errno = 0;
415 val = strtoul(name, &ep, 10);
416 if (errno)
417 return err(pThis->pCtx, -1, "%s", name);
418 if (*ep != '\0')
419 return errx(pThis->pCtx, -1, "unknown %s %s", type, name);
420 return (val);
421}
422
423/*
424 * install --
425 * build a path name and install the file
426 */
427static int
428install(PINSTALLINSTANCE pThis, const char *from_name, const char *to_name, u_long fset, u_int flags)
429{
430 struct stat from_sb, temp_sb, to_sb;
431 struct timeval tvb[2];
432 int devnull, files_match, from_fd, serrno, target;
433 int tempcopy, temp_fd, to_fd;
434 char backup[MAXPATHLEN], *p, pathbuf[MAXPATHLEN], tempfile[MAXPATHLEN];
435 int rc = EX_OK;
436
437 files_match = 0;
438 from_fd = -1;
439 to_fd = -1;
440 temp_fd = -1;
441
442 /* If try to install NULL file to a directory, fails. */
443 if (flags & DIRECTORY
444#if defined(__EMX__) || defined(_MSC_VER)
445 || ( stricmp(from_name, _PATH_DEVNULL)
446 && stricmp(from_name, "nul")
447# ifdef __EMX__
448 && stricmp(from_name, "/dev/nul")
449# endif
450 )
451#else
452 || strcmp(from_name, _PATH_DEVNULL)
453#endif
454 ) {
455 if (stat(from_name, &from_sb))
456 return err(pThis->pCtx, EX_OSERR, "%s", from_name);
457 if (!S_ISREG(from_sb.st_mode)) {
458 errno = EFTYPE;
459 return err(pThis->pCtx, EX_OSERR, "%s", from_name);
460 }
461 /* Build the target path. */
462 if (flags & DIRECTORY) {
463 (void)snprintf(pathbuf, sizeof(pathbuf), "%s/%s",
464 to_name,
465 (p = last_slash(from_name)) ? ++p : from_name);
466 to_name = pathbuf;
467 }
468 devnull = 0;
469 } else {
470 devnull = 1;
471 }
472
473 target = stat(to_name, &to_sb) == 0;
474
475 /* Only install to regular files. */
476 if (target && !S_ISREG(to_sb.st_mode)) {
477 errno = EFTYPE;
478 warn(pThis->pCtx, "%s", to_name);
479 return EX_OK;
480 }
481
482 /* Only copy safe if the target exists. */
483 tempcopy = pThis->safecopy && target;
484
485 /* Try hard linking if wanted and possible. */
486 if (pThis->hard_link_files_when_possible)
487 {
488#ifdef KBUILD_OS_OS2
489 const char *why_not = "not supported on OS/2";
490#else
491 const char *why_not = NULL;
492 if (devnull) {
493 why_not = "/dev/null";
494 } else if (pThis->dostrip) {
495 why_not = "strip (-s)";
496 } else if (pThis->docompare) {
497 why_not = "compare (-C)";
498 } else if (pThis->dobackup) {
499 why_not = "backup (-b/-B)";
500 } else if (pThis->safecopy) {
501 why_not = "safe copy (-S)";
502 } else if (lstat(from_name, &temp_sb)) {
503 why_not = "lstat on source failed";
504 } else if (S_ISLNK(temp_sb.st_mode)) {
505 why_not = "symlink";
506 } else if (!S_ISREG(temp_sb.st_mode)) {
507 why_not = "not regular file";
508# if defined(KBUILD_OS_WINDOWS) || defined(KBUILD_OS_OS2)
509 } else if ((pThis->mode & S_IWUSR) != (from_sb.st_mode & S_IWUSR)) {
510# else
511 } else if (pThis->mode != (from_sb.st_mode & ALLPERMS)) {
512# endif
513 kmk_builtin_ctx_printf(pThis->pCtx, 0,
514 "install: warning: Not hard linking, mode differs: 0%03o, desires 0%03o\n"
515 "install: src path '%s'\n"
516 "install: dst path '%s'\n",
517 (from_sb.st_mode & ALLPERMS), pThis->mode, from_name, to_name);
518 why_not = NULL;
519 } else if (pThis->uid != (uid_t)-1 && pThis->uid != from_sb.st_uid) {
520 why_not = "uid mismatch";
521 } else if (pThis->gid != (gid_t)-1 && pThis->gid != from_sb.st_gid) {
522 why_not = "gid mismatch";
523 } else if (pThis->dos2unix > 0 && needs_dos2unix_conversion(from_name)) {
524 why_not = "dos2unix";
525 } else if (pThis->dos2unix < 0 && needs_unix2dos_conversion(from_name)) {
526 why_not = "unix2dos";
527 } else {
528 int rcLink = link(from_name, to_name);
529 if (rcLink != 0 && errno == EEXIST) {
530 unlink(to_name);
531 rcLink = link(from_name, to_name);
532 }
533 if (rcLink == 0) {
534 if (pThis->verbose)
535 kmk_builtin_ctx_printf(pThis->pCtx, 0,
536 "install: %s -> %s (hardlinked)\n", from_name, to_name);
537 goto l_done;
538 }
539 if (pThis->verbose)
540 kmk_builtin_ctx_printf(pThis->pCtx, 0, "install: hard linking '%s' to '%s' failed: %s\n",
541 to_name, from_name, strerror(errno));
542 why_not = NULL;
543 }
544#endif
545 if (pThis->verbose && why_not)
546 kmk_builtin_ctx_printf(pThis->pCtx, 0, "install: not hard linking '%s' to '%s' because: %s\n",
547 to_name, from_name, why_not);
548
549 /* Can't hard link or we failed, continue as nothing happend. */
550 }
551
552 if (!devnull && (from_fd = open(from_name, O_RDONLY | O_BINARY, 0)) < 0)
553 return err(pThis->pCtx, EX_OSERR, "%s", from_name);
554
555 /* If we don't strip, we can compare first. */
556 if (pThis->docompare && !pThis->dostrip && target) {
557 if ((to_fd = open(to_name, O_RDONLY | O_BINARY, 0)) < 0) {
558 rc = err(pThis->pCtx, EX_OSERR, "%s", to_name);
559 goto l_done;
560 }
561 if (devnull)
562 files_match = to_sb.st_size == 0;
563 else
564 files_match = !compare(from_fd, (size_t)from_sb.st_size,
565 to_fd, (size_t)to_sb.st_size);
566
567 /* Close "to" file unless we match. */
568 if (!files_match) {
569 (void)close(to_fd);
570 to_fd = -1;
571 }
572 }
573
574 if (!files_match) {
575 if (tempcopy) {
576 to_fd = create_tempfile(to_name, tempfile,
577 sizeof(tempfile));
578 if (to_fd < 0) {
579 rc = err(pThis->pCtx, EX_OSERR, "%s", tempfile);
580 goto l_done;
581 }
582 } else {
583 if ((to_fd = create_newfile(pThis, to_name, target, &to_sb)) < 0) {
584 rc = err(pThis->pCtx, EX_OSERR, "%s", to_name);
585 goto l_done;
586 }
587 if (pThis->verbose)
588 kmk_builtin_ctx_printf(pThis->pCtx, 0, "install: %s -> %s\n", from_name, to_name);
589 }
590 if (!devnull) {
591 rc = copy(pThis, from_fd, from_name, &to_fd, tempcopy ? tempfile : to_name);
592 if (rc)
593 goto l_done;
594 }
595 }
596
597 if (pThis->dostrip) {
598#if defined(__EMX__) || defined(_MSC_VER)
599 /* close before we strip. */
600 close(to_fd);
601 to_fd = -1;
602#endif
603 rc = strip(pThis, tempcopy ? tempfile : to_name);
604 if (rc)
605 goto l_done;
606
607 /*
608 * Re-open our fd on the target, in case we used a strip
609 * that does not work in-place -- like GNU binutils strip.
610 */
611#if !defined(__EMX__) && !defined(_MSC_VER)
612 close(to_fd);
613#endif
614 to_fd = open(tempcopy ? tempfile : to_name, O_RDONLY | O_BINARY, 0);
615 if (to_fd < 0) {
616 rc = err(pThis->pCtx, EX_OSERR, "stripping %s", to_name);
617 goto l_done;
618 }
619 }
620
621 /*
622 * Compare the stripped temp file with the target.
623 */
624 if (pThis->docompare && pThis->dostrip && target) {
625 temp_fd = to_fd;
626
627 /* Re-open to_fd using the real target name. */
628 if ((to_fd = open(to_name, O_RDONLY | O_BINARY, 0)) < 0) {
629 rc = err(pThis->pCtx, EX_OSERR, "%s", to_name);
630 goto l_done;
631 }
632
633 if (fstat(temp_fd, &temp_sb)) {
634 serrno = errno;
635 (void)unlink(tempfile);
636 errno = serrno;
637 rc = err(pThis->pCtx, EX_OSERR, "%s", tempfile);
638 goto l_done;
639 }
640
641 if (compare(temp_fd, (size_t)temp_sb.st_size,
642 to_fd, (size_t)to_sb.st_size) == 0) {
643 /*
644 * If target has more than one link we need to
645 * replace it in order to snap the extra links.
646 * Need to preserve target file times, though.
647 */
648#if !defined(_MSC_VER) && !defined(__EMX__)
649 if (to_sb.st_nlink != 1) {
650 tvb[0].tv_sec = to_sb.st_atime;
651 tvb[0].tv_usec = 0;
652 tvb[1].tv_sec = to_sb.st_mtime;
653 tvb[1].tv_usec = 0;
654 (void)utimes(tempfile, tvb);
655 } else
656#endif
657 {
658
659 files_match = 1;
660 (void)unlink(tempfile);
661 }
662 (void) close(temp_fd);
663 temp_fd = -1;
664 }
665 }
666
667 /*
668 * Move the new file into place if doing a safe copy
669 * and the files are different (or just not compared).
670 */
671 if (tempcopy && !files_match) {
672#ifdef UF_IMMUTABLE
673 /* Try to turn off the immutable bits. */
674 if (to_sb.st_flags & NOCHANGEBITS)
675 (void)chflags(to_name, to_sb.st_flags & ~NOCHANGEBITS);
676#endif
677 if (pThis->dobackup) {
678 if ( (size_t)snprintf(backup, MAXPATHLEN, "%s%s", to_name, pThis->suffix)
679 != strlen(to_name) + strlen(pThis->suffix)) {
680 unlink(tempfile);
681 rc = errx(pThis->pCtx, EX_OSERR, "%s: backup filename too long",
682 to_name);
683 goto l_done;
684 }
685 if (pThis->verbose)
686 kmk_builtin_ctx_printf(pThis->pCtx, 0, "install: %s -> %s\n", to_name, backup);
687 if (rename(to_name, backup) < 0) {
688 serrno = errno;
689 unlink(tempfile);
690 errno = serrno;
691 rc = err(pThis->pCtx, EX_OSERR, "rename: %s to %s", to_name,
692 backup);
693 goto l_done;
694 }
695 }
696 if (pThis->verbose)
697 kmk_builtin_ctx_printf(pThis->pCtx, 0, "install: %s -> %s\n", from_name, to_name);
698 if (rename(tempfile, to_name) < 0) {
699 serrno = errno;
700 unlink(tempfile);
701 errno = serrno;
702 rc = err(pThis->pCtx, EX_OSERR, "rename: %s to %s", tempfile, to_name);
703 goto l_done;
704 }
705
706 /* Re-open to_fd so we aren't hosed by the rename(2). */
707 (void) close(to_fd);
708 if ((to_fd = open(to_name, O_RDONLY | O_BINARY, 0)) < 0) {
709 rc = err(pThis->pCtx, EX_OSERR, "%s", to_name);
710 goto l_done;
711 }
712 }
713
714 /*
715 * Preserve the timestamp of the source file if necessary.
716 */
717 if (pThis->dopreserve && !files_match && !devnull) {
718 tvb[0].tv_sec = from_sb.st_atime;
719 tvb[0].tv_usec = 0;
720 tvb[1].tv_sec = from_sb.st_mtime;
721 tvb[1].tv_usec = 0;
722 (void)utimes(to_name, tvb);
723 }
724
725 if (fstat(to_fd, &to_sb) == -1) {
726 serrno = errno;
727 (void)unlink(to_name);
728 errno = serrno;
729 rc = err(pThis->pCtx, EX_OSERR, "%s", to_name);
730 goto l_done;
731 }
732
733 /*
734 * Set owner, group, mode for target; do the chown first,
735 * chown may lose the setuid bits.
736 */
737#ifdef UF_IMMUTABLE
738 if ((gid != (gid_t)-1 && gid != to_sb.st_gid) ||
739 (uid != (uid_t)-1 && uid != to_sb.st_uid) ||
740 (mode != (to_sb.st_mode & ALLPERMS))) {
741 /* Try to turn off the immutable bits. */
742 if (to_sb.st_flags & NOCHANGEBITS)
743 (void)fchflags(to_fd, to_sb.st_flags & ~NOCHANGEBITS);
744 }
745#endif
746
747 if ((pThis->gid != (gid_t)-1 && pThis->gid != to_sb.st_gid) ||
748 (pThis->uid != (uid_t)-1 && pThis->uid != to_sb.st_uid))
749 if (fchown(to_fd, pThis->uid, pThis->gid) == -1) {
750 if (errno == EPERM && pThis->ignore_perm_errors) {
751 warn(pThis->pCtx, "%s: ignoring chown uid=%d gid=%d failure",
752 to_name, (int)pThis->uid, (int)pThis->gid);
753 } else {
754 serrno = errno;
755 (void)unlink(to_name);
756 errno = serrno;
757 rc = err(pThis->pCtx, EX_OSERR,"%s: chown/chgrp", to_name);
758 goto l_done;
759 }
760 }
761
762 if (pThis->mode != (to_sb.st_mode & ALLPERMS))
763 if (fchmod(to_fd, pThis->mode)) {
764 serrno = errno;
765 if (serrno == EPERM && pThis->ignore_perm_errors) {
766 fchmod(to_fd, pThis->mode & (ALLPERMS & ~0007000));
767 errno = errno;
768 warn(pThis->pCtx, "%s: ignoring chmod 0%o failure", to_name, (int)(pThis->mode & ALLPERMS));
769 } else {
770 serrno = errno;
771 (void)unlink(to_name);
772 errno = serrno;
773 rc = err(pThis->pCtx, EX_OSERR, "%s: chmod", to_name);
774 goto l_done;
775 }
776 }
777
778 /*
779 * If provided a set of flags, set them, otherwise, preserve the
780 * flags, except for the dump flag.
781 * NFS does not support flags. Ignore EOPNOTSUPP flags if we're just
782 * trying to turn off UF_NODUMP. If we're trying to set real flags,
783 * then warn if the the fs doesn't support it, otherwise fail.
784 */
785#ifdef UF_IMMUTABLE
786 if ( !devnull
787 && (flags & SETFLAGS || (from_sb.st_flags & ~UF_NODUMP) != to_sb.st_flags)
788 && fchflags(to_fd, flags & SETFLAGS ? fset : from_sb.st_flags & ~UF_NODUMP)) {
789 if (flags & SETFLAGS) {
790 if (errno == EOPNOTSUPP)
791 warn(pThis->pCtx, "%s: chflags", to_name);
792 else {
793 serrno = errno;
794 (void)unlink(to_name);
795 errno = serrno;
796 rc = err(pThis->pCtx, EX_OSERR, "%s: chflags", to_name);
797 goto l_done;
798 }
799 }
800 }
801#endif
802
803l_done:
804 if (to_fd >= 0)
805 (void)close(to_fd);
806 if (temp_fd >= 0)
807 (void)close(temp_fd);
808 if (from_fd >= 0 && !devnull)
809 (void)close(from_fd);
810 return rc;
811}
812
813/*
814 * compare --
815 * compare two files; non-zero means files differ
816 */
817static int
818compare(int from_fd, size_t from_len, int to_fd, size_t to_len)
819{
820 char buf1[MAXBSIZE];
821 char buf2[MAXBSIZE];
822 int n1, n2;
823 int rv;
824
825 if (from_len != to_len)
826 return 1;
827
828 if (from_len <= MAX_CMP_SIZE) {
829 rv = 0;
830 lseek(from_fd, 0, SEEK_SET);
831 lseek(to_fd, 0, SEEK_SET);
832 while (rv == 0) {
833 n1 = read(from_fd, buf1, sizeof(buf1));
834 if (n1 == 0)
835 break; /* EOF */
836 else if (n1 > 0) {
837 n2 = read(to_fd, buf2, n1);
838 if (n2 == n1)
839 rv = memcmp(buf1, buf2, n1);
840 else
841 rv = 1; /* out of sync */
842 } else
843 rv = 1; /* read failure */
844 }
845 lseek(from_fd, 0, SEEK_SET);
846 lseek(to_fd, 0, SEEK_SET);
847 } else
848 rv = 1; /* don't bother in this case */
849
850 return rv;
851}
852
853/*
854 * create_tempfile --
855 * create a temporary file based on path and open it
856 */
857int
858create_tempfile(const char *path, char *temp, size_t tsize)
859{
860 char *p;
861
862 (void)strncpy(temp, path, tsize);
863 temp[tsize - 1] = '\0';
864 if ((p = last_slash(temp)) != NULL)
865 p++;
866 else
867 p = temp;
868 (void)strncpy(p, "INS@XXXX", &temp[tsize - 1] - p);
869 temp[tsize - 1] = '\0';
870 return (mkstemp(temp));
871}
872
873/*
874 * create_newfile --
875 * create a new file, overwriting an existing one if necessary
876 */
877int
878create_newfile(PINSTALLINSTANCE pThis, const char *path, int target, struct stat *sbp)
879{
880 char backup[MAXPATHLEN];
881 int saved_errno = 0;
882 int newfd;
883
884 if (target) {
885 /*
886 * Unlink now... avoid ETXTBSY errors later. Try to turn
887 * off the append/immutable bits -- if we fail, go ahead,
888 * it might work.
889 */
890#ifdef UF_IMMUTABLE
891 if (sbp->st_flags & NOCHANGEBITS)
892 (void)chflags(path, sbp->st_flags & ~NOCHANGEBITS);
893#endif
894
895 if (pThis->dobackup) {
896 if ( (size_t)snprintf(backup, MAXPATHLEN, "%s%s", path, pThis->suffix)
897 != strlen(path) + strlen(pThis->suffix)) {
898 errx(pThis->pCtx, EX_OSERR, "%s: backup filename too long", path);
899 errno = ENAMETOOLONG;
900 return -1;
901 }
902 (void)snprintf(backup, MAXPATHLEN, "%s%s", path, pThis->suffix);
903 if (pThis->verbose)
904 kmk_builtin_ctx_printf(pThis->pCtx, 0, "install: %s -> %s\n", path, backup);
905 if (rename(path, backup) < 0) {
906 err(pThis->pCtx, EX_OSERR, "rename: %s to %s", path, backup);
907 return -1;
908 }
909 } else
910 if (unlink(path) < 0)
911 saved_errno = errno;
912 }
913
914 newfd = open(path, O_CREAT | O_RDWR | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR);
915 if (newfd < 0 && saved_errno != 0)
916 errno = saved_errno;
917 return newfd;
918}
919
920/*
921 * Write error handler.
922 */
923static int write_error(PINSTALLINSTANCE pThis, int *ptr_to_fd, const char *to_name, int nw)
924{
925 int serrno = errno;
926 (void)close(*ptr_to_fd);
927 *ptr_to_fd = -1;
928 (void)unlink(to_name);
929 errno = nw > 0 ? EIO : serrno;
930 return err(pThis->pCtx, EX_OSERR, "%s", to_name);
931}
932
933/*
934 * Read error handler.
935 */
936static int read_error(PINSTALLINSTANCE pThis, const char *from_name, int *ptr_to_fd, const char *to_name)
937{
938 int serrno = errno;
939 (void)close(*ptr_to_fd);
940 *ptr_to_fd = -1;
941 (void)unlink(to_name);
942 errno = serrno;
943 return err(pThis->pCtx, EX_OSERR, "%s", from_name);
944}
945
946/*
947 * copy --
948 * copy from one file to another
949 */
950static int
951copy(PINSTALLINSTANCE pThis, int from_fd, const char *from_name, int *ptr_to_fd, const char *to_name)
952{
953 KBOOL fPendingCr = K_FALSE;
954 KSIZE cchDst;
955 int nr, nw;
956 char buf[MAXBSIZE];
957 int to_fd = *ptr_to_fd;
958
959 /* Rewind file descriptors. */
960 if (lseek(from_fd, (off_t)0, SEEK_SET) == (off_t)-1)
961 return err(pThis->pCtx, EX_OSERR, "lseek: %s", from_name);
962 if (lseek(to_fd, (off_t)0, SEEK_SET) == (off_t)-1)
963 return err(pThis->pCtx, EX_OSERR, "lseek: %s", to_name);
964
965 if (pThis->dos2unix == 0) {
966 /*
967 * Copy bytes, no conversion.
968 */
969 while ((nr = read(from_fd, buf, sizeof(buf))) > 0)
970 if ((nw = write(to_fd, buf, nr)) != nr)
971 return write_error(pThis, ptr_to_fd, to_name, nw);
972 } else if (pThis->dos2unix > 0) {
973 /*
974 * CRLF -> LF is a reduction, so we can work with full buffers.
975 */
976 while ((nr = read(from_fd, buf, sizeof(buf))) > 0) {
977 if ( fPendingCr
978 && buf[0] != '\n'
979 && (nw = write(to_fd, "\r", 1)) != 1)
980 return write_error(pThis, ptr_to_fd, to_name, nw);
981
982 fPendingCr = dos2unix_convert_to_unix(buf, nr, buf, &cchDst);
983
984 nw = write(to_fd, buf, cchDst);
985 if (nw != (int)cchDst)
986 return write_error(pThis, ptr_to_fd, to_name, nw);
987 }
988 } else {
989 /*
990 * LF -> CRLF is an expansion, so we work with half buffers, reading
991 * into the upper half of the buffer and expanding into the full buffer.
992 * The conversion will never expand to more than the double size.
993 *
994 * Note! We do not convert valid CRLF line endings. This gives us
995 * valid DOS text, but no round-trip conversion.
996 */
997 char * const pchSrc = &buf[sizeof(buf) / 2];
998 while ((nr = read(from_fd, pchSrc, sizeof(buf) / 2)) > 0) {
999 if ( fPendingCr
1000 && pchSrc[0] != '\n'
1001 && (nw = write(to_fd, "\r", 1))!= 1)
1002 return write_error(pThis, ptr_to_fd, to_name, nw);
1003
1004 fPendingCr = dos2unix_convert_to_dos(pchSrc, nr, buf, &cchDst);
1005
1006 nw = write(to_fd, buf, cchDst);
1007 if (nw != (int)cchDst)
1008 return write_error(pThis, ptr_to_fd, to_name, nw);
1009 }
1010 }
1011
1012 /* Check for read error. */
1013 if (nr != 0)
1014 return read_error(pThis, from_name, ptr_to_fd, to_name);
1015
1016 /* When converting, we might have a pending final CR to write. */
1017 if ( fPendingCr
1018 && (nw = write(to_fd, "\r", 1))!= 1)
1019 return write_error(pThis, ptr_to_fd, to_name, nw);
1020
1021 return EX_OK;
1022}
1023
1024/*
1025 * strip --
1026 * use strip(1) to strip the target file
1027 */
1028static int
1029strip(PINSTALLINSTANCE pThis, const char *to_name)
1030{
1031#if defined(__EMX__) || defined(_MSC_VER)
1032 const char *stripbin = getenv("STRIPBIN");
1033 if (stripbin == NULL)
1034 stripbin = "strip";
1035 (void)pThis;
1036 return spawnlp(P_WAIT, stripbin, stripbin, to_name, NULL);
1037#else
1038 const char *stripbin;
1039 int serrno, status;
1040 pid_t pid;
1041
1042 pid = fork();
1043 switch (pid) {
1044 case -1:
1045 serrno = errno;
1046 (void)unlink(to_name);
1047 errno = serrno;
1048 return err(pThis->pCtx, EX_TEMPFAIL, "fork");
1049 case 0:
1050 stripbin = getenv("STRIPBIN");
1051 if (stripbin == NULL)
1052 stripbin = "strip";
1053 execlp(stripbin, stripbin, to_name, (char *)NULL);
1054 err(pThis->pCtx, EX_OSERR, "exec(%s)", stripbin);
1055 exit(EX_OSERR);
1056 default:
1057 if (waitpid(pid, &status, 0) == -1 || status) {
1058 serrno = errno;
1059 (void)unlink(to_name);
1060 errno = serrno;
1061 return err(pThis->pCtx, EX_SOFTWARE, "waitpid");
1062 /* NOTREACHED */
1063 }
1064 }
1065 return 0;
1066#endif
1067}
1068
1069/*
1070 * install_dir --
1071 * build directory heirarchy
1072 */
1073static int
1074install_dir(PINSTALLINSTANCE pThis, char *path)
1075{
1076 char *p;
1077 struct stat sb;
1078 int ch;
1079
1080 for (p = path;; ++p)
1081 if ( !*p
1082 || ( p != path
1083 && IS_SLASH(*p)
1084#if defined(_MSC_VER) /* stat("C:") fails (VC++ v10). Just skip it since it's unnecessary. */
1085 && (p - path != 2 || p[-1] != ':')
1086#endif
1087 )) {
1088 ch = *p;
1089 *p = '\0';
1090 if (stat(path, &sb)) {
1091 if (errno != ENOENT || mkdir(path, 0755) < 0) {
1092 return err(pThis->pCtx, EX_OSERR, "mkdir %s", path);
1093 /* NOTREACHED */
1094 } else if (pThis->verbose)
1095 kmk_builtin_ctx_printf(pThis->pCtx, 0, "install: mkdir %s\n", path);
1096 } else if (!S_ISDIR(sb.st_mode))
1097 return errx(pThis->pCtx, EX_OSERR, "%s exists but is not a directory", path);
1098 if (!(*p = ch))
1099 break;
1100 }
1101
1102 if ((pThis->gid != (gid_t)-1 || pThis->uid != (uid_t)-1) && chown(path, pThis->uid, pThis->gid))
1103 warn(pThis->pCtx, "chown %u:%u %s", pThis->uid, pThis->gid, path);
1104 if (chmod(path, pThis->mode))
1105 warn(pThis->pCtx, "chmod %o %s", pThis->mode, path);
1106 return EX_OK;
1107}
1108
1109/*
1110 * usage --
1111 * print a usage message and die
1112 */
1113static int
1114usage(PKMKBUILTINCTX pCtx, int fIsErr)
1115{
1116 kmk_builtin_ctx_printf(pCtx, fIsErr,
1117"usage: %s [-bCcpSsv] [--[no-]hard-link-files-when-possible]\n"
1118" [--[no-]ignore-perm-errors] [-B suffix] [-f flags] [-g group]\n"
1119" [-m mode] [-o owner] [--dos2unix|--unix2dos] file1 file2\n"
1120" or: %s [-bCcpSsv] [--[no-]ignore-perm-errors] [-B suffix] [-f flags]\n"
1121" [-g group] [-m mode] [-o owner] file1 ... fileN directory\n"
1122" or: %s -d [-v] [-g group] [-m mode] [-o owner] directory ...\n"
1123" or: %s --help\n"
1124" or: %s --version\n",
1125 pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName,
1126 pCtx->pszProgName, pCtx->pszProgName);
1127 return EX_USAGE;
1128}
1129
1130/* figures out where the last slash or colon is. */
1131static char *
1132last_slash(const char *path)
1133{
1134#if defined(__WIN32__) || defined(__WIN64__) || defined(__OS2__)
1135 char *p = (char *)strrchr(path, '/');
1136 if (p)
1137 {
1138 char *p2 = strrchr(p, '\\');
1139 if (p2)
1140 p = p2;
1141 }
1142 else
1143 {
1144 p = (char *)strrchr(path, '\\');
1145 if (!p && isalpha(path[0]) && path[1] == ':')
1146 p = (char *)&path[1];
1147 }
1148 return p;
1149#else
1150 return strrchr(path, '/');
1151#endif
1152}
1153
1154/**
1155 * Checks if @a pszFilename actually needs dos2unix conversion.
1156 *
1157 * @returns boolean.
1158 * @param pszFilename The name of the file to check.
1159 */
1160static KBOOL needs_dos2unix_conversion(const char *pszFilename)
1161{
1162 KU32 fStyle = 0;
1163 int iErr = dos2unix_analyze_file(pszFilename, &fStyle, NULL, NULL);
1164 return iErr != 0
1165 || (fStyle & (DOS2UNIX_STYLE_MASK | DOS2UNIX_F_BINARY)) != DOS2UNIX_STYLE_UNIX;
1166}
1167
1168/**
1169 * Checks if @a pszFilename actually needs unix2dos conversion.
1170 *
1171 * @returns boolean.
1172 * @param pszFilename The name of the file to check.
1173 */
1174static KBOOL needs_unix2dos_conversion(const char *pszFilename)
1175{
1176 KU32 fStyle = 0;
1177 int iErr = dos2unix_analyze_file(pszFilename, &fStyle, NULL, NULL);
1178 return iErr != 0
1179 || (fStyle & (DOS2UNIX_STYLE_MASK | DOS2UNIX_F_BINARY)) != DOS2UNIX_STYLE_DOS;
1180}
1181
Note: See TracBrowser for help on using the repository browser.