Bug Summary

File:src/usr.bin/xinstall/xinstall.c
Warning:line 544, column 17
Call to function 'vfork' is insecure as it can lead to denial of service situations in the parent process. Replace calls to vfork with calls to the safer 'posix_spawn' function

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple amd64-unknown-openbsd7.4 -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name xinstall.c -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 1 -pic-is-pie -mframe-pointer=all -relaxed-aliasing -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -target-feature +retpoline-indirect-calls -target-feature +retpoline-indirect-branches -tune-cpu generic -debugger-tuning=gdb -fcoverage-compilation-dir=/usr/src/usr.bin/xinstall/obj -resource-dir /usr/local/llvm16/lib/clang/16 -internal-isystem /usr/local/llvm16/lib/clang/16/include -internal-externc-isystem /usr/include -O2 -fdebug-compilation-dir=/usr/src/usr.bin/xinstall/obj -ferror-limit 19 -fwrapv -D_RET_PROTECTOR -ret-protector -fcf-protection=branch -fno-jump-tables -fgnuc-version=4.2.1 -vectorize-loops -vectorize-slp -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-valloc -fno-builtin-free -fno-builtin-strdup -fno-builtin-strndup -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /home/ben/Projects/scan/2024-01-11-140451-98009-1 -x c /usr/src/usr.bin/xinstall/xinstall.c
1/* $OpenBSD: xinstall.c,v 1.77 2022/12/04 23:50:50 cheloha Exp $ */
2/* $NetBSD: xinstall.c,v 1.9 1995/12/20 10:25:17 jonathan Exp $ */
3
4/*
5 * Copyright (c) 1987, 1993
6 * The Regents of the University of California. All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. Neither the name of the University nor the names of its contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32
33#include <sys/types.h>
34#include <sys/wait.h>
35#include <sys/mman.h>
36#include <sys/stat.h>
37
38#include <ctype.h>
39#include <err.h>
40#include <errno(*__errno()).h>
41#include <fcntl.h>
42#include <grp.h>
43#include <paths.h>
44#include <pwd.h>
45#include <stdio.h>
46#include <stdlib.h>
47#include <string.h>
48#include <unistd.h>
49#include <limits.h>
50#include <libgen.h>
51
52#include "pathnames.h"
53
54#define _MAXBSIZE(64 * 1024) (64 * 1024)
55
56#define MINIMUM(a, b)(((a) < (b)) ? (a) : (b)) (((a) < (b)) ? (a) : (b))
57
58#define DIRECTORY0x01 0x01 /* Tell install it's a directory. */
59#define SETFLAGS0x02 0x02 /* Tell install to set flags. */
60#define USEFSYNC0x04 0x04 /* Tell install to use fsync(2). */
61#define NOCHANGEBITS(0x00000002 | 0x00000004 | 0x00020000 | 0x00040000) (UF_IMMUTABLE0x00000002 | UF_APPEND0x00000004 | SF_IMMUTABLE0x00020000 | SF_APPEND0x00040000)
62#define BACKUP_SUFFIX".old" ".old"
63
64int dobackup, docompare, dodest, dodir, dopreserve, dostrip;
65int mode = S_IRWXU0000700|S_IRGRP0000040|S_IXGRP0000010|S_IROTH0000004|S_IXOTH0000001;
66char pathbuf[PATH_MAX1024], tempfile[PATH_MAX1024];
67char *suffix = BACKUP_SUFFIX".old";
68uid_t uid = (uid_t)-1;
69gid_t gid = (gid_t)-1;
70
71void copy(int, char *, int, char *, off_t, int);
72int compare(int, const char *, off_t, int, const char *, off_t);
73void install(char *, char *, u_long, u_int);
74void install_dir(char *, int);
75void strip(char *);
76void usage(void);
77int create_tempfile(char *, char *, size_t);
78int file_write(int, char *, size_t, int *, int *, int);
79void file_flush(int, int);
80
81int
82main(int argc, char *argv[])
83{
84 struct stat from_sb, to_sb;
85 void *set;
86 u_int32_t fset;
87 u_int iflags;
88 int ch, no_target;
89 char *flags, *to_name, *group = NULL((void *)0), *owner = NULL((void *)0);
90 const char *errstr;
91
92 iflags = 0;
93 while ((ch = getopt(argc, argv, "B:bCcDdFf:g:m:o:pSs")) != -1)
94 switch(ch) {
95 case 'C':
96 docompare = 1;
97 break;
98 case 'B':
99 suffix = optarg;
100 /* fall through; -B implies -b */
101 case 'b':
102 dobackup = 1;
103 break;
104 case 'c':
105 /* For backwards compatibility. */
106 break;
107 case 'F':
108 iflags |= USEFSYNC0x04;
109 break;
110 case 'f':
111 flags = optarg;
112 if (strtofflags(&flags, &fset, NULL((void *)0)))
113 errx(1, "%s: invalid flag", flags);
114 iflags |= SETFLAGS0x02;
115 break;
116 case 'g':
117 group = optarg;
118 break;
119 case 'm':
120 if (!(set = setmode(optarg)))
121 errx(1, "%s: invalid file mode", optarg);
122 mode = getmode(set, 0);
123 free(set);
124 break;
125 case 'o':
126 owner = optarg;
127 break;
128 case 'p':
129 docompare = dopreserve = 1;
130 break;
131 case 'S':
132 /* For backwards compatibility. */
133 break;
134 case 's':
135 dostrip = 1;
136 break;
137 case 'D':
138 dodest = 1;
139 break;
140 case 'd':
141 dodir = 1;
142 break;
143 default:
144 usage();
145 }
146 argc -= optind;
147 argv += optind;
148
149 /* some options make no sense when creating directories */
150 if ((docompare || dostrip) && dodir)
151 usage();
152
153 /* must have at least two arguments, except when creating directories */
154 if (argc == 0 || (argc == 1 && !dodir))
155 usage();
156
157 /* get group and owner id's */
158 if (group != NULL((void *)0) && gid_from_group(group, &gid) == -1) {
159 gid = strtonum(group, 0, GID_MAX0xffffffffU, &errstr);
160 if (errstr != NULL((void *)0))
161 errx(1, "unknown group %s", group);
162 }
163 if (owner != NULL((void *)0) && uid_from_user(owner, &uid) == -1) {
164 uid = strtonum(owner, 0, UID_MAX0xffffffffU, &errstr);
165 if (errstr != NULL((void *)0))
166 errx(1, "unknown user %s", owner);
167 }
168
169 if (dodir) {
170 for (; *argv != NULL((void *)0); ++argv)
171 install_dir(*argv, mode);
172 exit(0);
173 /* NOTREACHED */
174 }
175
176 if (dodest) {
177 char *dest = dirname(argv[argc - 1]);
178 if (dest == NULL((void *)0))
179 errx(1, "cannot determine dirname");
180 /*
181 * When -D is passed, do not chmod the directory with the mode set for
182 * the target file. If more restrictive permissions are required then
183 * '-d -m' ought to be used instead.
184 */
185 install_dir(dest, 0755);
186 }
187
188 no_target = stat(to_name = argv[argc - 1], &to_sb);
189 if (!no_target && S_ISDIR(to_sb.st_mode)((to_sb.st_mode & 0170000) == 0040000)) {
190 for (; *argv != to_name; ++argv)
191 install(*argv, to_name, fset, iflags | DIRECTORY0x01);
192 exit(0);
193 /* NOTREACHED */
194 }
195
196 /* can't do file1 file2 directory/file */
197 if (argc != 2)
198 errx(1, "Target: %s", argv[argc-1]);
199
200 if (!no_target) {
201 if (stat(*argv, &from_sb))
202 err(1, "%s", *argv);
203 if (!S_ISREG(to_sb.st_mode)((to_sb.st_mode & 0170000) == 0100000))
204 errc(1, EFTYPE79, "%s", to_name);
205 if (to_sb.st_dev == from_sb.st_dev &&
206 to_sb.st_ino == from_sb.st_ino)
207 errx(1, "%s and %s are the same file", *argv, to_name);
208 }
209 install(*argv, to_name, fset, iflags);
210 exit(0);
211 /* NOTREACHED */
212}
213
214/*
215 * install --
216 * build a path name and install the file
217 */
218void
219install(char *from_name, char *to_name, u_long fset, u_int flags)
220{
221 struct stat from_sb, to_sb;
222 struct timespec ts[2];
223 int devnull, from_fd, to_fd, serrno, files_match = 0;
224 char *p;
225 char *target_name = tempfile;
226
227 (void)memset((void *)&from_sb, 0, sizeof(from_sb));
228 (void)memset((void *)&to_sb, 0, sizeof(to_sb));
229
230 /* If try to install NULL file to a directory, fails. */
231 if (flags & DIRECTORY0x01 || strcmp(from_name, _PATH_DEVNULL"/dev/null")) {
232 if (stat(from_name, &from_sb))
233 err(1, "%s", from_name);
234 if (!S_ISREG(from_sb.st_mode)((from_sb.st_mode & 0170000) == 0100000))
235 errc(1, EFTYPE79, "%s", from_name);
236 /* Build the target path. */
237 if (flags & DIRECTORY0x01) {
238 (void)snprintf(pathbuf, sizeof(pathbuf), "%s/%s",
239 to_name,
240 (p = strrchr(from_name, '/')) ? ++p : from_name);
241 to_name = pathbuf;
242 }
243 devnull = 0;
244 } else {
245 devnull = 1;
246 }
247
248 if (stat(to_name, &to_sb) == 0) {
249 /* Only compare against regular files. */
250 if (docompare && !S_ISREG(to_sb.st_mode)((to_sb.st_mode & 0170000) == 0100000)) {
251 docompare = 0;
252 warnc(EFTYPE79, "%s", to_name);
253 }
254 } else if (docompare) {
255 /* File does not exist so silently ignore compare flag. */
256 docompare = 0;
257 }
258
259 if (!devnull) {
260 if ((from_fd = open(from_name, O_RDONLY0x0000)) == -1)
261 err(1, "%s", from_name);
262 }
263
264 to_fd = create_tempfile(to_name, tempfile, sizeof(tempfile));
265 if (to_fd < 0)
266 err(1, "%s", tempfile);
267
268 if (!devnull)
269 copy(from_fd, from_name, to_fd, tempfile, from_sb.st_size,
270 ((off_t)from_sb.st_blocks * S_BLKSIZE512 < from_sb.st_size));
271
272 if (dostrip) {
273 strip(tempfile);
274
275 /*
276 * Re-open our fd on the target, in case we used a strip
277 * that does not work in-place -- like gnu binutils strip.
278 */
279 close(to_fd);
280 if ((to_fd = open(tempfile, O_RDONLY0x0000)) == -1)
281 err(1, "stripping %s", to_name);
282 }
283
284 /*
285 * Compare the (possibly stripped) temp file to the target.
286 */
287 if (docompare) {
288 int temp_fd = to_fd;
289 struct stat temp_sb;
290
291 /* Re-open to_fd using the real target name. */
292 if ((to_fd = open(to_name, O_RDONLY0x0000)) == -1)
293 err(1, "%s", to_name);
294
295 if (fstat(temp_fd, &temp_sb)) {
296 serrno = errno(*__errno());
297 (void)unlink(tempfile);
298 errc(1, serrno, "%s", tempfile);
299 }
300
301 if (compare(temp_fd, tempfile, temp_sb.st_size, to_fd,
302 to_name, to_sb.st_size) == 0) {
303 /*
304 * If target has more than one link we need to
305 * replace it in order to snap the extra links.
306 * Need to preserve target file times, though.
307 */
308 if (to_sb.st_nlink != 1) {
309 ts[0] = to_sb.st_atim;
310 ts[1] = to_sb.st_mtim;
311 futimens(temp_fd, ts);
312 } else {
313 files_match = 1;
314 (void)unlink(tempfile);
315 target_name = to_name;
316 (void)close(temp_fd);
317 }
318 }
319 if (!files_match) {
320 (void)close(to_fd);
321 to_fd = temp_fd;
322 }
323 }
324
325 /*
326 * Preserve the timestamp of the source file if necessary.
327 */
328 if (dopreserve && !files_match) {
329 ts[0] = from_sb.st_atim;
330 ts[1] = from_sb.st_mtim;
331 futimens(to_fd, ts);
332 }
333
334 /*
335 * Set owner, group, mode for target; do the chown first,
336 * chown may lose the setuid bits.
337 */
338 if ((gid != (gid_t)-1 || uid != (uid_t)-1) &&
339 fchown(to_fd, uid, gid)) {
340 serrno = errno(*__errno());
341 if (target_name == tempfile)
342 (void)unlink(target_name);
343 errx(1, "%s: chown/chgrp: %s", target_name, strerror(serrno));
344 }
345 if (fchmod(to_fd, mode)) {
346 serrno = errno(*__errno());
347 if (target_name == tempfile)
348 (void)unlink(target_name);
349 errx(1, "%s: chmod: %s", target_name, strerror(serrno));
350 }
351
352 /*
353 * If provided a set of flags, set them, otherwise, preserve the
354 * flags, except for the dump flag.
355 */
356 if (fchflags(to_fd,
357 flags & SETFLAGS0x02 ? fset : from_sb.st_flags & ~UF_NODUMP0x00000001)) {
358 if (errno(*__errno()) != EOPNOTSUPP45 || (from_sb.st_flags & ~UF_NODUMP0x00000001) != 0)
359 warnx("%s: chflags: %s", target_name, strerror(errno(*__errno())));
360 }
361
362 if (flags & USEFSYNC0x04)
363 fsync(to_fd);
364 (void)close(to_fd);
365 if (!devnull)
366 (void)close(from_fd);
367
368 /*
369 * Move the new file into place if the files are different
370 * or were not compared.
371 */
372 if (!files_match) {
373 /* Try to turn off the immutable bits. */
374 if (to_sb.st_flags & (NOCHANGEBITS(0x00000002 | 0x00000004 | 0x00020000 | 0x00040000)))
375 (void)chflags(to_name, to_sb.st_flags & ~(NOCHANGEBITS(0x00000002 | 0x00000004 | 0x00020000 | 0x00040000)));
376 if (dobackup) {
377 char backup[PATH_MAX1024];
378 (void)snprintf(backup, PATH_MAX1024, "%s%s", to_name,
379 suffix);
380 /* It is ok for the target file not to exist. */
381 if (rename(to_name, backup) == -1 && errno(*__errno()) != ENOENT2) {
382 serrno = errno(*__errno());
383 unlink(tempfile);
384 errx(1, "rename: %s to %s: %s", to_name,
385 backup, strerror(serrno));
386 }
387 }
388 if (rename(tempfile, to_name) == -1 ) {
389 serrno = errno(*__errno());
390 unlink(tempfile);
391 errx(1, "rename: %s to %s: %s", tempfile,
392 to_name, strerror(serrno));
393 }
394 }
395}
396
397/*
398 * copy --
399 * copy from one file to another
400 */
401void
402copy(int from_fd, char *from_name, int to_fd, char *to_name, off_t size,
403 int sparse)
404{
405 ssize_t nr, nw;
406 int serrno;
407 char *p, buf[_MAXBSIZE(64 * 1024)];
408
409 if (size == 0)
410 return;
411
412 /* Rewind file descriptors. */
413 if (lseek(from_fd, (off_t)0, SEEK_SET0) == (off_t)-1)
414 err(1, "lseek: %s", from_name);
415 if (lseek(to_fd, (off_t)0, SEEK_SET0) == (off_t)-1)
416 err(1, "lseek: %s", to_name);
417
418 /*
419 * Mmap and write if less than 8M (the limit is so we don't totally
420 * trash memory on big files. This is really a minor hack, but it
421 * wins some CPU back. Sparse files need special treatment.
422 */
423 if (!sparse && size <= 8 * 1048576) {
424 size_t siz;
425
426 if ((p = mmap(NULL((void *)0), (size_t)size, PROT_READ0x01, MAP_PRIVATE0x0002,
427 from_fd, (off_t)0)) == MAP_FAILED((void *)-1)) {
428 serrno = errno(*__errno());
429 (void)unlink(to_name);
430 errc(1, serrno, "%s", from_name);
431 }
432 madvise(p, size, MADV_SEQUENTIAL2);
433 siz = (size_t)size;
434 if ((nw = write(to_fd, p, siz)) != siz) {
435 serrno = errno(*__errno());
436 (void)unlink(to_name);
437 errx(1, "%s: %s",
438 to_name, strerror(nw > 0 ? EIO5 : serrno));
439 }
440 (void) munmap(p, (size_t)size);
441 } else {
442 int sz, rem, isem = 1;
443 struct stat sb;
444
445 /*
446 * Pass the blocksize of the file being written to the write
447 * routine. if the size is zero, use the default S_BLKSIZE.
448 */
449 if (fstat(to_fd, &sb) != 0 || sb.st_blksize == 0)
450 sz = S_BLKSIZE512;
451 else
452 sz = sb.st_blksize;
453 rem = sz;
454
455 while ((nr = read(from_fd, buf, sizeof(buf))) > 0) {
456 if (sparse)
457 nw = file_write(to_fd, buf, nr, &rem, &isem, sz);
458 else
459 nw = write(to_fd, buf, nr);
460 if (nw != nr) {
461 serrno = errno(*__errno());
462 (void)unlink(to_name);
463 errx(1, "%s: %s",
464 to_name, strerror(nw > 0 ? EIO5 : serrno));
465 }
466 }
467 if (sparse)
468 file_flush(to_fd, isem);
469 if (nr != 0) {
470 serrno = errno(*__errno());
471 (void)unlink(to_name);
472 errc(1, serrno, "%s", from_name);
473 }
474 }
475}
476
477/*
478 * compare --
479 * compare two files; non-zero means files differ
480 */
481int
482compare(int from_fd, const char *from_name, off_t from_len, int to_fd,
483 const char *to_name, off_t to_len)
484{
485 caddr_t p1, p2;
486 size_t length;
487 off_t from_off, to_off, remainder;
488 int dfound;
489
490 if (from_len == 0 && from_len == to_len)
491 return (0);
492
493 if (from_len != to_len)
494 return (1);
495
496 /*
497 * Compare the two files being careful not to mmap
498 * more than 8M at a time.
499 */
500 from_off = to_off = (off_t)0;
501 remainder = from_len;
502 do {
503 length = MINIMUM(remainder, 8 * 1048576)(((remainder) < (8 * 1048576)) ? (remainder) : (8 * 1048576
))
;
504 remainder -= length;
505
506 if ((p1 = mmap(NULL((void *)0), length, PROT_READ0x01, MAP_PRIVATE0x0002,
507 from_fd, from_off)) == MAP_FAILED((void *)-1))
508 err(1, "%s", from_name);
509 if ((p2 = mmap(NULL((void *)0), length, PROT_READ0x01, MAP_PRIVATE0x0002,
510 to_fd, to_off)) == MAP_FAILED((void *)-1))
511 err(1, "%s", to_name);
512 if (length) {
513 madvise(p1, length, MADV_SEQUENTIAL2);
514 madvise(p2, length, MADV_SEQUENTIAL2);
515 }
516
517 dfound = memcmp(p1, p2, length);
518
519 (void) munmap(p1, length);
520 (void) munmap(p2, length);
521
522 from_off += length;
523 to_off += length;
524
525 } while (!dfound && remainder > 0);
526
527 return(dfound);
528}
529
530/*
531 * strip --
532 * use strip(1) to strip the target file
533 */
534void
535strip(char *to_name)
536{
537 int serrno, status;
538 char * volatile path_strip;
539 pid_t pid;
540
541 if (issetugid() || (path_strip = getenv("STRIP")) == NULL((void *)0))
542 path_strip = _PATH_STRIP"/usr/bin/strip";
543
544 switch ((pid = vfork())) {
Call to function 'vfork' is insecure as it can lead to denial of service situations in the parent process. Replace calls to vfork with calls to the safer 'posix_spawn' function
545 case -1:
546 serrno = errno(*__errno());
547 (void)unlink(to_name);
548 errc(1, serrno, "forks");
549 case 0:
550 execl(path_strip, "strip", "--", to_name, (char *)NULL((void *)0));
551 warn("%s", path_strip);
552 _exit(1);
553 default:
554 while (waitpid(pid, &status, 0) == -1) {
555 if (errno(*__errno()) != EINTR4)
556 break;
557 }
558 if (!WIFEXITED(status)(((status) & 0177) == 0))
559 (void)unlink(to_name);
560 }
561}
562
563/*
564 * install_dir --
565 * build directory hierarchy
566 */
567void
568install_dir(char *path, int mode)
569{
570 char *p;
571 struct stat sb;
572 int ch;
573
574 for (p = path;; ++p)
575 if (!*p || (p != path && *p == '/')) {
576 ch = *p;
577 *p = '\0';
578 if (mkdir(path, 0777)) {
579 int mkdir_errno = errno(*__errno());
580 if (stat(path, &sb)) {
581 /* Not there; use mkdir()s errno */
582 errc(1, mkdir_errno, "%s",
583 path);
584 /* NOTREACHED */
585 }
586 if (!S_ISDIR(sb.st_mode)((sb.st_mode & 0170000) == 0040000)) {
587 /* Is there, but isn't a directory */
588 errc(1, ENOTDIR20, "%s", path);
589 /* NOTREACHED */
590 }
591 }
592 if (!(*p = ch))
593 break;
594 }
595
596 if (((gid != (gid_t)-1 || uid != (uid_t)-1) && chown(path, uid, gid)) ||
597 chmod(path, mode)) {
598 warn("%s", path);
599 }
600}
601
602/*
603 * usage --
604 * print a usage message and die
605 */
606void
607usage(void)
608{
609 (void)fprintf(stderr(&__sF[2]), "\
610usage: install [-bCcDdFpSs] [-B suffix] [-f flags] [-g group] [-m mode] [-o owner]\n source ... target ...\n");
611 exit(1);
612 /* NOTREACHED */
613}
614
615/*
616 * create_tempfile --
617 * create a temporary file based on path and open it
618 */
619int
620create_tempfile(char *path, char *temp, size_t tsize)
621{
622 char *p;
623
624 strlcpy(temp, path, tsize);
625 if ((p = strrchr(temp, '/')) != NULL((void *)0))
626 p++;
627 else
628 p = temp;
629 *p = '\0';
630 strlcat(p, "INS@XXXXXXXXXX", tsize);
631
632 return(mkstemp(temp));
633}
634
635/*
636 * file_write()
637 * Write/copy a file (during copy or archive extract). This routine knows
638 * how to copy files with lseek holes in it. (Which are read as file
639 * blocks containing all 0's but do not have any file blocks associated
640 * with the data). Typical examples of these are files created by dbm
641 * variants (.pag files). While the file size of these files are huge, the
642 * actual storage is quite small (the files are sparse). The problem is
643 * the holes read as all zeros so are probably stored on the archive that
644 * way (there is no way to determine if the file block is really a hole,
645 * we only know that a file block of all zero's can be a hole).
646 * At this writing, no major archive format knows how to archive files
647 * with holes. However, on extraction (or during copy, -rw) we have to
648 * deal with these files. Without detecting the holes, the files can
649 * consume a lot of file space if just written to disk. This replacement
650 * for write when passed the basic allocation size of a file system block,
651 * uses lseek whenever it detects the input data is all 0 within that
652 * file block. In more detail, the strategy is as follows:
653 * While the input is all zero keep doing an lseek. Keep track of when we
654 * pass over file block boundaries. Only write when we hit a non zero
655 * input. once we have written a file block, we continue to write it to
656 * the end (we stop looking at the input). When we reach the start of the
657 * next file block, start checking for zero blocks again. Working on file
658 * block boundaries significantly reduces the overhead when copying files
659 * that are NOT very sparse. This overhead (when compared to a write) is
660 * almost below the measurement resolution on many systems. Without it,
661 * files with holes cannot be safely copied. It does has a side effect as
662 * it can put holes into files that did not have them before, but that is
663 * not a problem since the file contents are unchanged (in fact it saves
664 * file space). (Except on paging files for diskless clients. But since we
665 * cannot determine one of those file from here, we ignore them). If this
666 * ever ends up on a system where CTG files are supported and the holes
667 * are not desired, just do a conditional test in those routines that
668 * call file_write() and have it call write() instead. BEFORE CLOSING THE
669 * FILE, make sure to call file_flush() when the last write finishes with
670 * an empty block. A lot of file systems will not create an lseek hole at
671 * the end. In this case we drop a single 0 at the end to force the
672 * trailing 0's in the file.
673 * ---Parameters---
674 * rem: how many bytes left in this file system block
675 * isempt: have we written to the file block yet (is it empty)
676 * sz: basic file block allocation size
677 * cnt: number of bytes on this write
678 * str: buffer to write
679 * Return:
680 * number of bytes written, -1 on write (or lseek) error.
681 */
682
683int
684file_write(int fd, char *str, size_t cnt, int *rem, int *isempt, int sz)
685{
686 char *pt;
687 char *end;
688 size_t wcnt;
689 char *st = str;
690
691 /*
692 * while we have data to process
693 */
694 while (cnt) {
695 if (!*rem) {
696 /*
697 * We are now at the start of file system block again
698 * (or what we think one is...). start looking for
699 * empty blocks again
700 */
701 *isempt = 1;
702 *rem = sz;
703 }
704
705 /*
706 * only examine up to the end of the current file block or
707 * remaining characters to write, whatever is smaller
708 */
709 wcnt = MINIMUM(cnt, *rem)(((cnt) < (*rem)) ? (cnt) : (*rem));
710 cnt -= wcnt;
711 *rem -= wcnt;
712 if (*isempt) {
713 /*
714 * have not written to this block yet, so we keep
715 * looking for zero's
716 */
717 pt = st;
718 end = st + wcnt;
719
720 /*
721 * look for a zero filled buffer
722 */
723 while ((pt < end) && (*pt == '\0'))
724 ++pt;
725
726 if (pt == end) {
727 /*
728 * skip, buf is empty so far
729 */
730 if (lseek(fd, (off_t)wcnt, SEEK_CUR1) == -1) {
731 warn("lseek");
732 return(-1);
733 }
734 st = pt;
735 continue;
736 }
737 /*
738 * drat, the buf is not zero filled
739 */
740 *isempt = 0;
741 }
742
743 /*
744 * have non-zero data in this file system block, have to write
745 */
746 if (write(fd, st, wcnt) != wcnt) {
747 warn("write");
748 return(-1);
749 }
750 st += wcnt;
751 }
752 return(st - str);
753}
754
755/*
756 * file_flush()
757 * when the last file block in a file is zero, many file systems will not
758 * let us create a hole at the end. To get the last block with zeros, we
759 * write the last BYTE with a zero (back up one byte and write a zero).
760 */
761void
762file_flush(int fd, int isempt)
763{
764 static char blnk[] = "\0";
765
766 /*
767 * silly test, but make sure we are only called when the last block is
768 * filled with all zeros.
769 */
770 if (!isempt)
771 return;
772
773 /*
774 * move back one byte and write a zero
775 */
776 if (lseek(fd, (off_t)-1, SEEK_CUR1) == -1) {
777 warn("Failed seek on file");
778 return;
779 }
780
781 if (write(fd, blnk, 1) == -1)
782 warn("Failed write to file");
783 return;
784}