Merge branch 'merges'
[unleashed.git] / bin / xinstall / xinstall.c
blobde305b5229f7ea66714df3f584331f7d9f27c52f
1 /* $OpenBSD: xinstall.c,v 1.65 2016/05/13 17:51:15 jmc Exp $ */
2 /* $NetBSD: xinstall.c,v 1.9 1995/12/20 10:25:17 jonathan Exp $ */
4 /*
5 * Copyright (c) 1987, 1993
6 * The Regents of the University of California. All rights reserved.
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.
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.
33 #include <sys/param.h> /* MAXBSIZE */
34 #include <sys/wait.h>
35 #include <sys/mman.h>
36 #include <sys/stat.h>
38 #include <ctype.h>
39 #include <err.h>
40 #include <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 <utime.h>
51 #include <libgen.h>
53 #include "pathnames.h"
54 #include "compat.h"
56 #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b))
58 #define DIRECTORY 0x01 /* Tell install it's a directory. */
59 #define SETFLAGS 0x02 /* Tell install to set flags. */
60 #define USEFSYNC 0x04 /* Tell install to use fsync(2). */
61 #define NOCHANGEBITS (UF_IMMUTABLE | UF_APPEND | SF_IMMUTABLE | SF_APPEND)
62 #define BACKUP_SUFFIX ".old"
64 struct passwd *pp;
65 struct group *gp;
66 int dobackup, docompare, dodest, dodir, dopreserve, dostrip, safecopy;
67 int mode = S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH;
68 char pathbuf[PATH_MAX], tempfile[PATH_MAX];
69 char *suffix = BACKUP_SUFFIX;
70 uid_t uid;
71 gid_t gid;
73 void copy(int, char *, int, char *, off_t, int);
74 int compare(int, const char *, off_t, int, const char *, off_t);
75 void install(char *, char *, u_long, u_int);
76 void install_dir(char *, int);
77 void strip(char *);
78 void usage(void);
79 int create_newfile(char *, struct stat *);
80 int create_tempfile(char *, char *, size_t);
81 int file_write(int, char *, size_t, int *, int *, int);
82 void file_flush(int, int);
84 int
85 main(int argc, char *argv[])
87 struct stat from_sb, to_sb;
88 void *set;
89 u_int32_t fset;
90 u_int iflags;
91 int ch, no_target;
92 char *flags, *to_name, *group = NULL, *owner = NULL;
94 iflags = 0;
95 while ((ch = getopt(argc, argv, "B:bCcDdFf:g:m:o:pSs")) != -1)
96 switch(ch) {
97 case 'C':
98 docompare = 1;
99 break;
100 case 'B':
101 suffix = optarg;
102 /* fall through; -B implies -b */
103 case 'b':
104 dobackup = 1;
105 break;
106 case 'c':
107 /* For backwards compatibility. */
108 break;
109 case 'F':
110 iflags |= USEFSYNC;
111 break;
112 case 'g':
113 group = optarg;
114 break;
115 case 'm':
116 mode = strtol(optarg, NULL, 8);
117 break;
118 case 'o':
119 owner = optarg;
120 break;
121 case 'p':
122 docompare = dopreserve = 1;
123 break;
124 case 'S':
125 safecopy = 1;
126 break;
127 case 's':
128 dostrip = 1;
129 break;
130 case 'D':
131 dodest = 1;
132 break;
133 case 'd':
134 dodir = 1;
135 break;
136 case '?':
137 default:
138 usage();
140 argc -= optind;
141 argv += optind;
143 /* some options make no sense when creating directories */
144 if ((safecopy || docompare || dostrip) && dodir)
145 usage();
147 /* must have at least two arguments, except when creating directories */
148 if (argc < 2 && !dodir)
149 usage();
151 /* need to make a temp copy so we can compare stripped version */
152 if (docompare && dostrip)
153 safecopy = 1;
155 /* get group and owner id's */
156 if (group && !(gp = getgrnam(group)) && !isdigit((unsigned char)*group))
157 errx(1, "unknown group %s", group);
158 gid = (group) ? ((gp) ? gp->gr_gid : (gid_t)strtoul(group, NULL, 10)) : (gid_t)-1;
159 if (owner && !(pp = getpwnam(owner)) && !isdigit((unsigned char)*owner))
160 errx(1, "unknown user %s", owner);
161 uid = (owner) ? ((pp) ? pp->pw_uid : (uid_t)strtoul(owner, NULL, 10)) : (uid_t)-1;
163 if (dodir) {
164 for (; *argv != NULL; ++argv)
165 install_dir(*argv, mode);
166 exit(0);
167 /* NOTREACHED */
170 if (dodest) {
171 char *dest = dirname(argv[argc - 1]);
172 if (dest == NULL)
173 errx(1, "cannot determine dirname");
175 * When -D is passed, do not chmod the directory with the mode set for
176 * the target file. If more restrictive permissions are required then
177 * '-d -m' ought to be used instead.
179 install_dir(dest, 0755);
182 no_target = stat(to_name = argv[argc - 1], &to_sb);
183 if (!no_target && S_ISDIR(to_sb.st_mode)) {
184 for (; *argv != to_name; ++argv)
185 install(*argv, to_name, fset, iflags | DIRECTORY);
186 exit(0);
187 /* NOTREACHED */
190 /* can't do file1 file2 directory/file */
191 if (argc != 2)
192 errx(1, "Target: %s", argv[argc-1]);
194 if (!no_target) {
195 if (stat(*argv, &from_sb))
196 err(1, "%s", *argv);
197 if (!S_ISREG(to_sb.st_mode))
198 errc(1, EFTYPE, "%s", to_name);
199 if (to_sb.st_dev == from_sb.st_dev &&
200 to_sb.st_ino == from_sb.st_ino)
201 errx(1, "%s and %s are the same file", *argv, to_name);
203 install(*argv, to_name, fset, iflags);
204 exit(0);
205 /* NOTREACHED */
209 * install --
210 * build a path name and install the file
212 void
213 install(char *from_name, char *to_name, u_long fset, u_int flags)
215 struct stat from_sb, to_sb;
216 struct timespec ts[2];
217 int devnull, from_fd, to_fd, serrno, files_match = 0;
218 char *p;
220 (void)memset(&from_sb, 0, sizeof(from_sb));
221 (void)memset(&to_sb, 0, sizeof(to_sb));
223 /* If try to install NULL file to a directory, fails. */
224 if (flags & DIRECTORY || strcmp(from_name, _PATH_DEVNULL)) {
225 if (stat(from_name, &from_sb))
226 err(1, "%s", from_name);
227 if (!S_ISREG(from_sb.st_mode))
228 errc(1, EFTYPE, "%s", from_name);
229 /* Build the target path. */
230 if (flags & DIRECTORY) {
231 (void)snprintf(pathbuf, sizeof(pathbuf), "%s/%s",
232 to_name,
233 (p = strrchr(from_name, '/')) ? ++p : from_name);
234 to_name = pathbuf;
236 devnull = 0;
237 } else {
238 devnull = 1;
241 if (stat(to_name, &to_sb) == 0) {
242 /* Only compare against regular files. */
243 if (docompare && !S_ISREG(to_sb.st_mode)) {
244 docompare = 0;
245 warnc(EFTYPE, "%s", to_name);
247 } else if (docompare) {
248 /* File does not exist so silently ignore compare flag. */
249 docompare = 0;
252 if (!devnull) {
253 if ((from_fd = open(from_name, O_RDONLY, 0)) < 0)
254 err(1, "%s", from_name);
257 if (safecopy) {
258 to_fd = create_tempfile(to_name, tempfile, sizeof(tempfile));
259 if (to_fd < 0)
260 err(1, "%s", tempfile);
261 } else if (docompare && !dostrip) {
262 if ((to_fd = open(to_name, O_RDONLY, 0)) < 0)
263 err(1, "%s", to_name);
264 } else {
265 if ((to_fd = create_newfile(to_name, &to_sb)) < 0)
266 err(1, "%s", to_name);
269 if (!devnull) {
270 if (docompare && !safecopy) {
271 files_match = !(compare(from_fd, from_name,
272 from_sb.st_size, to_fd,
273 to_name, to_sb.st_size));
275 /* Truncate "to" file for copy unless we match */
276 if (!files_match) {
277 (void)close(to_fd);
278 if ((to_fd = create_newfile(to_name, &to_sb)) < 0)
279 err(1, "%s", to_name);
282 if (!files_match)
283 copy(from_fd, from_name, to_fd,
284 safecopy ? tempfile : to_name, from_sb.st_size,
285 ((off_t)from_sb.st_blocks * S_BLKSIZE < from_sb.st_size));
288 if (dostrip) {
289 strip(safecopy ? tempfile : to_name);
292 * Re-open our fd on the target, in case we used a strip
293 * that does not work in-place -- like gnu binutils strip.
295 close(to_fd);
296 if ((to_fd = open(safecopy ? tempfile : to_name, O_RDONLY,
297 0)) < 0)
298 err(1, "stripping %s", to_name);
302 * Compare the (possibly stripped) temp file to the target.
304 if (safecopy && docompare) {
305 int temp_fd = to_fd;
306 struct stat temp_sb;
308 /* Re-open to_fd using the real target name. */
309 if ((to_fd = open(to_name, O_RDONLY, 0)) < 0)
310 err(1, "%s", to_name);
312 if (fstat(temp_fd, &temp_sb)) {
313 serrno = errno;
314 (void)unlink(tempfile);
315 errc(1, serrno, "%s", tempfile);
318 if (compare(temp_fd, tempfile, temp_sb.st_size, to_fd,
319 to_name, to_sb.st_size) == 0) {
321 * If target has more than one link we need to
322 * replace it in order to snap the extra links.
323 * Need to preserve target file times, though.
325 if (to_sb.st_nlink != 1) {
326 ts[0] = to_sb.st_atim;
327 ts[1] = to_sb.st_mtim;
328 futimens(temp_fd, ts);
329 } else {
330 files_match = 1;
331 (void)unlink(tempfile);
334 (void)close(to_fd);
335 to_fd = temp_fd;
339 * Preserve the timestamp of the source file if necessary.
341 if (dopreserve && !files_match) {
342 ts[0] = from_sb.st_atim;
343 ts[1] = from_sb.st_mtim;
344 futimens(to_fd, ts);
348 * Set owner, group, mode for target; do the chown first,
349 * chown may lose the setuid bits.
351 if ((gid != (gid_t)-1 || uid != (uid_t)-1) &&
352 fchown(to_fd, uid, gid)) {
353 serrno = errno;
354 (void)unlink(safecopy ? tempfile : to_name);
355 errx(1, "%s: chown/chgrp: %s",
356 safecopy ? tempfile : to_name, strerror(serrno));
358 if (fchmod(to_fd, mode)) {
359 serrno = errno;
360 (void)unlink(safecopy ? tempfile : to_name);
361 errx(1, "%s: chmod: %s", safecopy ? tempfile : to_name,
362 strerror(serrno));
365 if (flags & USEFSYNC)
366 fsync(to_fd);
367 (void)close(to_fd);
368 if (!devnull)
369 (void)close(from_fd);
372 * Move the new file into place if doing a safe copy
373 * and the files are different (or just not compared).
375 if (safecopy && !files_match) {
376 if (dobackup) {
377 char backup[PATH_MAX];
378 (void)snprintf(backup, PATH_MAX, "%s%s", to_name,
379 suffix);
380 /* It is ok for the target file not to exist. */
381 if (rename(to_name, backup) < 0 && errno != ENOENT) {
382 serrno = errno;
383 unlink(tempfile);
384 errx(1, "rename: %s to %s: %s", to_name,
385 backup, strerror(serrno));
388 if (rename(tempfile, to_name) < 0 ) {
389 serrno = errno;
390 unlink(tempfile);
391 errx(1, "rename: %s to %s: %s", tempfile,
392 to_name, strerror(serrno));
398 * copy --
399 * copy from one file to another
401 void
402 copy(int from_fd, char *from_name, int to_fd, char *to_name, off_t size,
403 int sparse)
405 ssize_t nr, nw;
406 int serrno;
407 char *p, buf[MAXBSIZE];
409 if (size == 0)
410 return;
412 /* Rewind file descriptors. */
413 if (lseek(from_fd, (off_t)0, SEEK_SET) == (off_t)-1)
414 err(1, "lseek: %s", from_name);
415 if (lseek(to_fd, (off_t)0, SEEK_SET) == (off_t)-1)
416 err(1, "lseek: %s", to_name);
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.
423 if (!sparse && size <= 8 * 1048576) {
424 size_t siz;
426 if ((p = mmap(NULL, (size_t)size, PROT_READ, MAP_PRIVATE,
427 from_fd, (off_t)0)) == MAP_FAILED) {
428 serrno = errno;
429 (void)unlink(to_name);
430 errc(1, serrno, "%s", from_name);
432 madvise(p, size, MADV_SEQUENTIAL);
433 siz = (size_t)size;
434 if ((nw = write(to_fd, p, siz)) != siz) {
435 serrno = errno;
436 (void)unlink(to_name);
437 errx(1, "%s: %s",
438 to_name, strerror(nw > 0 ? EIO : serrno));
440 (void) munmap(p, (size_t)size);
441 } else {
442 int sz, rem, isem = 1;
443 struct stat sb;
446 * Pass the blocksize of the file being written to the write
447 * routine. if the size is zero, use the default S_BLKSIZE.
449 if (fstat(to_fd, &sb) != 0 || sb.st_blksize == 0)
450 sz = S_BLKSIZE;
451 else
452 sz = sb.st_blksize;
453 rem = sz;
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;
462 (void)unlink(to_name);
463 errx(1, "%s: %s",
464 to_name, strerror(nw > 0 ? EIO : serrno));
467 if (sparse)
468 file_flush(to_fd, isem);
469 if (nr != 0) {
470 serrno = errno;
471 (void)unlink(to_name);
472 errc(1, serrno, "%s", from_name);
478 * compare --
479 * compare two files; non-zero means files differ
482 compare(int from_fd, const char *from_name, off_t from_len, int to_fd,
483 const char *to_name, off_t to_len)
485 caddr_t p1, p2;
486 size_t length;
487 off_t from_off, to_off, remainder;
488 int dfound;
490 if (from_len == 0 && from_len == to_len)
491 return (0);
493 if (from_len != to_len)
494 return (1);
497 * Compare the two files being careful not to mmap
498 * more than 8M at a time.
500 from_off = to_off = (off_t)0;
501 remainder = from_len;
502 do {
503 length = MINIMUM(remainder, 8 * 1048576);
504 remainder -= length;
506 if ((p1 = mmap(NULL, length, PROT_READ, MAP_PRIVATE,
507 from_fd, from_off)) == MAP_FAILED)
508 err(1, "%s", from_name);
509 if ((p2 = mmap(NULL, length, PROT_READ, MAP_PRIVATE,
510 to_fd, to_off)) == MAP_FAILED)
511 err(1, "%s", to_name);
512 if (length) {
513 madvise(p1, length, MADV_SEQUENTIAL);
514 madvise(p2, length, MADV_SEQUENTIAL);
517 dfound = memcmp(p1, p2, length);
519 (void) munmap(p1, length);
520 (void) munmap(p2, length);
522 from_off += length;
523 to_off += length;
525 } while (!dfound && remainder > 0);
527 return(dfound);
531 * strip --
532 * use strip(1) to strip the target file
534 void
535 strip(char *to_name)
537 int serrno, status;
538 char * volatile path_strip;
540 if (issetugid() || (path_strip = getenv("STRIP")) == NULL)
541 path_strip = _PATH_STRIP;
543 switch (vfork()) {
544 case -1:
545 serrno = errno;
546 (void)unlink(to_name);
547 errc(1, serrno, "forks");
548 case 0:
549 execl(path_strip, "strip", "--", to_name, (char *)NULL);
550 warn("%s", path_strip);
551 _exit(1);
552 default:
553 if (wait(&status) == -1 || !WIFEXITED(status))
554 (void)unlink(to_name);
559 * install_dir --
560 * build directory hierarchy
562 void
563 install_dir(char *path, int mode)
565 char *p;
566 struct stat sb;
567 int ch;
569 for (p = path;; ++p)
570 if (!*p || (p != path && *p == '/')) {
571 ch = *p;
572 *p = '\0';
573 if (mkdir(path, 0777)) {
574 int mkdir_errno = errno;
575 if (stat(path, &sb)) {
576 /* Not there; use mkdir()s errno */
577 errc(1, mkdir_errno, "%s",
578 path);
579 /* NOTREACHED */
581 if (!S_ISDIR(sb.st_mode)) {
582 /* Is there, but isn't a directory */
583 errc(1, ENOTDIR, "%s", path);
584 /* NOTREACHED */
587 if (!(*p = ch))
588 break;
591 if (((gid != (gid_t)-1 || uid != (uid_t)-1) && chown(path, uid, gid)) ||
592 chmod(path, mode)) {
593 warn("%s", path);
598 * usage --
599 * print a usage message and die
601 void
602 usage(void)
604 (void)fprintf(stderr, "\
605 usage: install [-bCcDdFpSs] [-B suffix] [-f flags] [-g group] [-m mode] [-o owner]\n source ... target ...\n");
606 exit(1);
607 /* NOTREACHED */
611 * create_tempfile --
612 * create a temporary file based on path and open it
615 create_tempfile(char *path, char *temp, size_t tsize)
617 char *p;
619 strlcpy(temp, path, tsize);
620 if ((p = strrchr(temp, '/')) != NULL)
621 p++;
622 else
623 p = temp;
624 *p = '\0';
625 strlcat(p, "INS@XXXXXXXXXX", tsize);
627 return(mkstemp(temp));
631 * create_newfile --
632 * create a new file, overwriting an existing one if necessary
635 create_newfile(char *path, struct stat *sbp)
637 char backup[PATH_MAX];
639 if (dobackup) {
640 (void)snprintf(backup, PATH_MAX, "%s%s", path, suffix);
641 /* It is ok for the target file not to exist. */
642 if (rename(path, backup) < 0 && errno != ENOENT)
643 err(1, "rename: %s to %s (errno %d)", path, backup, errno);
644 } else {
645 if (unlink(path) < 0 && errno != ENOENT)
646 err(1, "%s", path);
649 return(open(path, O_CREAT | O_RDWR | O_EXCL, S_IRUSR | S_IWUSR));
653 * file_write()
654 * Write/copy a file (during copy or archive extract). This routine knows
655 * how to copy files with lseek holes in it. (Which are read as file
656 * blocks containing all 0's but do not have any file blocks associated
657 * with the data). Typical examples of these are files created by dbm
658 * variants (.pag files). While the file size of these files are huge, the
659 * actual storage is quite small (the files are sparse). The problem is
660 * the holes read as all zeros so are probably stored on the archive that
661 * way (there is no way to determine if the file block is really a hole,
662 * we only know that a file block of all zero's can be a hole).
663 * At this writing, no major archive format knows how to archive files
664 * with holes. However, on extraction (or during copy, -rw) we have to
665 * deal with these files. Without detecting the holes, the files can
666 * consume a lot of file space if just written to disk. This replacement
667 * for write when passed the basic allocation size of a file system block,
668 * uses lseek whenever it detects the input data is all 0 within that
669 * file block. In more detail, the strategy is as follows:
670 * While the input is all zero keep doing an lseek. Keep track of when we
671 * pass over file block boundaries. Only write when we hit a non zero
672 * input. once we have written a file block, we continue to write it to
673 * the end (we stop looking at the input). When we reach the start of the
674 * next file block, start checking for zero blocks again. Working on file
675 * block boundaries significantly reduces the overhead when copying files
676 * that are NOT very sparse. This overhead (when compared to a write) is
677 * almost below the measurement resolution on many systems. Without it,
678 * files with holes cannot be safely copied. It does has a side effect as
679 * it can put holes into files that did not have them before, but that is
680 * not a problem since the file contents are unchanged (in fact it saves
681 * file space). (Except on paging files for diskless clients. But since we
682 * cannot determine one of those file from here, we ignore them). If this
683 * ever ends up on a system where CTG files are supported and the holes
684 * are not desired, just do a conditional test in those routines that
685 * call file_write() and have it call write() instead. BEFORE CLOSING THE
686 * FILE, make sure to call file_flush() when the last write finishes with
687 * an empty block. A lot of file systems will not create an lseek hole at
688 * the end. In this case we drop a single 0 at the end to force the
689 * trailing 0's in the file.
690 * ---Parameters---
691 * rem: how many bytes left in this file system block
692 * isempt: have we written to the file block yet (is it empty)
693 * sz: basic file block allocation size
694 * cnt: number of bytes on this write
695 * str: buffer to write
696 * Return:
697 * number of bytes written, -1 on write (or lseek) error.
701 file_write(int fd, char *str, size_t cnt, int *rem, int *isempt, int sz)
703 char *pt;
704 char *end;
705 size_t wcnt;
706 char *st = str;
709 * while we have data to process
711 while (cnt) {
712 if (!*rem) {
714 * We are now at the start of file system block again
715 * (or what we think one is...). start looking for
716 * empty blocks again
718 *isempt = 1;
719 *rem = sz;
723 * only examine up to the end of the current file block or
724 * remaining characters to write, whatever is smaller
726 wcnt = MINIMUM(cnt, *rem);
727 cnt -= wcnt;
728 *rem -= wcnt;
729 if (*isempt) {
731 * have not written to this block yet, so we keep
732 * looking for zero's
734 pt = st;
735 end = st + wcnt;
738 * look for a zero filled buffer
740 while ((pt < end) && (*pt == '\0'))
741 ++pt;
743 if (pt == end) {
745 * skip, buf is empty so far
747 if (lseek(fd, (off_t)wcnt, SEEK_CUR) < 0) {
748 warn("lseek");
749 return(-1);
751 st = pt;
752 continue;
755 * drat, the buf is not zero filled
757 *isempt = 0;
761 * have non-zero data in this file system block, have to write
763 if (write(fd, st, wcnt) != wcnt) {
764 warn("write");
765 return(-1);
767 st += wcnt;
769 return(st - str);
773 * file_flush()
774 * when the last file block in a file is zero, many file systems will not
775 * let us create a hole at the end. To get the last block with zeros, we
776 * write the last BYTE with a zero (back up one byte and write a zero).
778 void
779 file_flush(int fd, int isempt)
781 static char blnk[] = "\0";
784 * silly test, but make sure we are only called when the last block is
785 * filled with all zeros.
787 if (!isempt)
788 return;
791 * move back one byte and write a zero
793 if (lseek(fd, (off_t)-1, SEEK_CUR) < 0) {
794 warn("Failed seek on file");
795 return;
798 if (write(fd, blnk, 1) < 0)
799 warn("Failed write to file");
800 return;