xref: /openbsd/usr.bin/xinstall/xinstall.c (revision 50f4c734)
1 /*	$OpenBSD: xinstall.c,v 1.68 2019/02/08 12:53:44 schwarze 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/param.h>	/* MAXBSIZE */
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.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>
52 
53 #include "pathnames.h"
54 
55 #define MINIMUM(a, b)	(((a) < (b)) ? (a) : (b))
56 
57 #define	DIRECTORY	0x01		/* Tell install it's a directory. */
58 #define	SETFLAGS	0x02		/* Tell install to set flags. */
59 #define	USEFSYNC	0x04		/* Tell install to use fsync(2). */
60 #define NOCHANGEBITS	(UF_IMMUTABLE | UF_APPEND | SF_IMMUTABLE | SF_APPEND)
61 #define BACKUP_SUFFIX	".old"
62 
63 int dobackup, docompare, dodest, dodir, dopreserve, dostrip;
64 int mode = S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH;
65 char pathbuf[PATH_MAX], tempfile[PATH_MAX];
66 char *suffix = BACKUP_SUFFIX;
67 uid_t uid = (uid_t)-1;
68 gid_t gid = (gid_t)-1;
69 
70 void	copy(int, char *, int, char *, off_t, int);
71 int	compare(int, const char *, off_t, int, const char *, off_t);
72 void	install(char *, char *, u_long, u_int);
73 void	install_dir(char *, int);
74 void	strip(char *);
75 void	usage(void);
76 int	create_tempfile(char *, char *, size_t);
77 int	file_write(int, char *, size_t, int *, int *, int);
78 void	file_flush(int, int);
79 
80 int
81 main(int argc, char *argv[])
82 {
83 	struct stat from_sb, to_sb;
84 	void *set;
85 	u_int32_t fset;
86 	u_int iflags;
87 	int ch, no_target;
88 	char *flags, *to_name, *group = NULL, *owner = NULL;
89 	const char *errstr;
90 
91 	iflags = 0;
92 	while ((ch = getopt(argc, argv, "B:bCcDdFf:g:m:o:pSs")) != -1)
93 		switch(ch) {
94 		case 'C':
95 			docompare = 1;
96 			break;
97 		case 'B':
98 			suffix = optarg;
99 			/* fall through; -B implies -b */
100 		case 'b':
101 			dobackup = 1;
102 			break;
103 		case 'c':
104 			/* For backwards compatibility. */
105 			break;
106 		case 'F':
107 			iflags |= USEFSYNC;
108 			break;
109 		case 'f':
110 			flags = optarg;
111 			if (strtofflags(&flags, &fset, NULL))
112 				errx(1, "%s: invalid flag", flags);
113 			iflags |= SETFLAGS;
114 			break;
115 		case 'g':
116 			group = optarg;
117 			break;
118 		case 'm':
119 			if (!(set = setmode(optarg)))
120 				errx(1, "%s: invalid file mode", optarg);
121 			mode = getmode(set, 0);
122 			free(set);
123 			break;
124 		case 'o':
125 			owner = optarg;
126 			break;
127 		case 'p':
128 			docompare = dopreserve = 1;
129 			break;
130 		case 'S':
131 			/* For backwards compatibility. */
132 			break;
133 		case 's':
134 			dostrip = 1;
135 			break;
136 		case 'D':
137 			dodest = 1;
138 			break;
139 		case 'd':
140 			dodir = 1;
141 			break;
142 		case '?':
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 < 2 && !dodir)
155 		usage();
156 
157 	/* get group and owner id's */
158 	if (group != NULL && gid_from_group(group, &gid) == -1) {
159 		gid = strtonum(group, 0, GID_MAX, &errstr);
160 		if (errstr != NULL)
161 			errx(1, "unknown group %s", group);
162 	}
163 	if (owner != NULL && uid_from_user(owner, &uid) == -1) {
164 		uid = strtonum(owner, 0, UID_MAX, &errstr);
165 		if (errstr != NULL)
166 			errx(1, "unknown user %s", owner);
167 	}
168 
169 	if (dodir) {
170 		for (; *argv != NULL; ++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)
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)) {
190 		for (; *argv != to_name; ++argv)
191 			install(*argv, to_name, fset, iflags | DIRECTORY);
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))
204 			errc(1, EFTYPE, "%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  */
218 void
219 install(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 
226 	(void)memset((void *)&from_sb, 0, sizeof(from_sb));
227 	(void)memset((void *)&to_sb, 0, sizeof(to_sb));
228 
229 	/* If try to install NULL file to a directory, fails. */
230 	if (flags & DIRECTORY || strcmp(from_name, _PATH_DEVNULL)) {
231 		if (stat(from_name, &from_sb))
232 			err(1, "%s", from_name);
233 		if (!S_ISREG(from_sb.st_mode))
234 			errc(1, EFTYPE, "%s", from_name);
235 		/* Build the target path. */
236 		if (flags & DIRECTORY) {
237 			(void)snprintf(pathbuf, sizeof(pathbuf), "%s/%s",
238 			    to_name,
239 			    (p = strrchr(from_name, '/')) ? ++p : from_name);
240 			to_name = pathbuf;
241 		}
242 		devnull = 0;
243 	} else {
244 		devnull = 1;
245 	}
246 
247 	if (stat(to_name, &to_sb) == 0) {
248 		/* Only compare against regular files. */
249 		if (docompare && !S_ISREG(to_sb.st_mode)) {
250 			docompare = 0;
251 			warnc(EFTYPE, "%s", to_name);
252 		}
253 	} else if (docompare) {
254 		/* File does not exist so silently ignore compare flag. */
255 		docompare = 0;
256 	}
257 
258 	if (!devnull) {
259 		if ((from_fd = open(from_name, O_RDONLY, 0)) < 0)
260 			err(1, "%s", from_name);
261 	}
262 
263 	to_fd = create_tempfile(to_name, tempfile, sizeof(tempfile));
264 	if (to_fd < 0)
265 		err(1, "%s", tempfile);
266 
267 	if (!devnull)
268 		copy(from_fd, from_name, to_fd, tempfile, from_sb.st_size,
269 		    ((off_t)from_sb.st_blocks * S_BLKSIZE < from_sb.st_size));
270 
271 	if (dostrip) {
272 		strip(tempfile);
273 
274 		/*
275 		 * Re-open our fd on the target, in case we used a strip
276 		 *  that does not work in-place -- like gnu binutils strip.
277 		 */
278 		close(to_fd);
279 		if ((to_fd = open(tempfile, O_RDONLY, 0)) < 0)
280 			err(1, "stripping %s", to_name);
281 	}
282 
283 	/*
284 	 * Compare the (possibly stripped) temp file to the target.
285 	 */
286 	if (docompare) {
287 		int temp_fd = to_fd;
288 		struct stat temp_sb;
289 
290 		/* Re-open to_fd using the real target name. */
291 		if ((to_fd = open(to_name, O_RDONLY, 0)) < 0)
292 			err(1, "%s", to_name);
293 
294 		if (fstat(temp_fd, &temp_sb)) {
295 			serrno = errno;
296 			(void)unlink(tempfile);
297 			errc(1, serrno, "%s", tempfile);
298 		}
299 
300 		if (compare(temp_fd, tempfile, temp_sb.st_size, to_fd,
301 			    to_name, to_sb.st_size) == 0) {
302 			/*
303 			 * If target has more than one link we need to
304 			 * replace it in order to snap the extra links.
305 			 * Need to preserve target file times, though.
306 			 */
307 			if (to_sb.st_nlink != 1) {
308 				ts[0] = to_sb.st_atim;
309 				ts[1] = to_sb.st_mtim;
310 				futimens(temp_fd, ts);
311 			} else {
312 				files_match = 1;
313 				(void)unlink(tempfile);
314 			}
315 		}
316 		(void)close(to_fd);
317 		to_fd = temp_fd;
318 	}
319 
320 	/*
321 	 * Preserve the timestamp of the source file if necessary.
322 	 */
323 	if (dopreserve && !files_match) {
324 		ts[0] = from_sb.st_atim;
325 		ts[1] = from_sb.st_mtim;
326 		futimens(to_fd, ts);
327 	}
328 
329 	/*
330 	 * Set owner, group, mode for target; do the chown first,
331 	 * chown may lose the setuid bits.
332 	 */
333 	if ((gid != (gid_t)-1 || uid != (uid_t)-1) &&
334 	    fchown(to_fd, uid, gid)) {
335 		serrno = errno;
336 		(void)unlink(tempfile);
337 		errx(1, "%s: chown/chgrp: %s", tempfile, strerror(serrno));
338 	}
339 	if (fchmod(to_fd, mode)) {
340 		serrno = errno;
341 		(void)unlink(tempfile);
342 		errx(1, "%s: chmod: %s", tempfile, strerror(serrno));
343 	}
344 
345 	/*
346 	 * If provided a set of flags, set them, otherwise, preserve the
347 	 * flags, except for the dump flag.
348 	 */
349 	if (fchflags(to_fd,
350 	    flags & SETFLAGS ? fset : from_sb.st_flags & ~UF_NODUMP)) {
351 		if (errno != EOPNOTSUPP || (from_sb.st_flags & ~UF_NODUMP) != 0)
352 			warnx("%s: chflags: %s", tempfile, strerror(errno));
353 	}
354 
355 	if (flags & USEFSYNC)
356 		fsync(to_fd);
357 	(void)close(to_fd);
358 	if (!devnull)
359 		(void)close(from_fd);
360 
361 	/*
362 	 * Move the new file into place if the files are different
363 	 * or were not compared.
364 	 */
365 	if (!files_match) {
366 		/* Try to turn off the immutable bits. */
367 		if (to_sb.st_flags & (NOCHANGEBITS))
368 			(void)chflags(to_name, to_sb.st_flags & ~(NOCHANGEBITS));
369 		if (dobackup) {
370 			char backup[PATH_MAX];
371 			(void)snprintf(backup, PATH_MAX, "%s%s", to_name,
372 			    suffix);
373 			/* It is ok for the target file not to exist. */
374 			if (rename(to_name, backup) < 0 && errno != ENOENT) {
375 				serrno = errno;
376 				unlink(tempfile);
377 				errx(1, "rename: %s to %s: %s", to_name,
378 				     backup, strerror(serrno));
379 			}
380 		}
381 		if (rename(tempfile, to_name) < 0 ) {
382 			serrno = errno;
383 			unlink(tempfile);
384 			errx(1, "rename: %s to %s: %s", tempfile,
385 			     to_name, strerror(serrno));
386 		}
387 	}
388 }
389 
390 /*
391  * copy --
392  *	copy from one file to another
393  */
394 void
395 copy(int from_fd, char *from_name, int to_fd, char *to_name, off_t size,
396     int sparse)
397 {
398 	ssize_t nr, nw;
399 	int serrno;
400 	char *p, buf[MAXBSIZE];
401 
402 	if (size == 0)
403 		return;
404 
405 	/* Rewind file descriptors. */
406 	if (lseek(from_fd, (off_t)0, SEEK_SET) == (off_t)-1)
407 		err(1, "lseek: %s", from_name);
408 	if (lseek(to_fd, (off_t)0, SEEK_SET) == (off_t)-1)
409 		err(1, "lseek: %s", to_name);
410 
411 	/*
412 	 * Mmap and write if less than 8M (the limit is so we don't totally
413 	 * trash memory on big files.  This is really a minor hack, but it
414 	 * wins some CPU back.  Sparse files need special treatment.
415 	 */
416 	if (!sparse && size <= 8 * 1048576) {
417 		size_t siz;
418 
419 		if ((p = mmap(NULL, (size_t)size, PROT_READ, MAP_PRIVATE,
420 		    from_fd, (off_t)0)) == MAP_FAILED) {
421 			serrno = errno;
422 			(void)unlink(to_name);
423 			errc(1, serrno, "%s", from_name);
424 		}
425 		madvise(p, size, MADV_SEQUENTIAL);
426 		siz = (size_t)size;
427 		if ((nw = write(to_fd, p, siz)) != siz) {
428 			serrno = errno;
429 			(void)unlink(to_name);
430 			errx(1, "%s: %s",
431 			    to_name, strerror(nw > 0 ? EIO : serrno));
432 		}
433 		(void) munmap(p, (size_t)size);
434 	} else {
435 		int sz, rem, isem = 1;
436 		struct stat sb;
437 
438 		/*
439 		 * Pass the blocksize of the file being written to the write
440 		 * routine.  if the size is zero, use the default S_BLKSIZE.
441 		 */
442 		if (fstat(to_fd, &sb) != 0 || sb.st_blksize == 0)
443 			sz = S_BLKSIZE;
444 		else
445 			sz = sb.st_blksize;
446 		rem = sz;
447 
448 		while ((nr = read(from_fd, buf, sizeof(buf))) > 0) {
449 			if (sparse)
450 				nw = file_write(to_fd, buf, nr, &rem, &isem, sz);
451 			else
452 				nw = write(to_fd, buf, nr);
453 			if (nw != nr) {
454 				serrno = errno;
455 				(void)unlink(to_name);
456 				errx(1, "%s: %s",
457 				    to_name, strerror(nw > 0 ? EIO : serrno));
458 			}
459 		}
460 		if (sparse)
461 			file_flush(to_fd, isem);
462 		if (nr != 0) {
463 			serrno = errno;
464 			(void)unlink(to_name);
465 			errc(1, serrno, "%s", from_name);
466 		}
467 	}
468 }
469 
470 /*
471  * compare --
472  *	compare two files; non-zero means files differ
473  */
474 int
475 compare(int from_fd, const char *from_name, off_t from_len, int to_fd,
476     const char *to_name, off_t to_len)
477 {
478 	caddr_t p1, p2;
479 	size_t length;
480 	off_t from_off, to_off, remainder;
481 	int dfound;
482 
483 	if (from_len == 0 && from_len == to_len)
484 		return (0);
485 
486 	if (from_len != to_len)
487 		return (1);
488 
489 	/*
490 	 * Compare the two files being careful not to mmap
491 	 * more than 8M at a time.
492 	 */
493 	from_off = to_off = (off_t)0;
494 	remainder = from_len;
495 	do {
496 		length = MINIMUM(remainder, 8 * 1048576);
497 		remainder -= length;
498 
499 		if ((p1 = mmap(NULL, length, PROT_READ, MAP_PRIVATE,
500 		    from_fd, from_off)) == MAP_FAILED)
501 			err(1, "%s", from_name);
502 		if ((p2 = mmap(NULL, length, PROT_READ, MAP_PRIVATE,
503 		    to_fd, to_off)) == MAP_FAILED)
504 			err(1, "%s", to_name);
505 		if (length) {
506 			madvise(p1, length, MADV_SEQUENTIAL);
507 			madvise(p2, length, MADV_SEQUENTIAL);
508 		}
509 
510 		dfound = memcmp(p1, p2, length);
511 
512 		(void) munmap(p1, length);
513 		(void) munmap(p2, length);
514 
515 		from_off += length;
516 		to_off += length;
517 
518 	} while (!dfound && remainder > 0);
519 
520 	return(dfound);
521 }
522 
523 /*
524  * strip --
525  *	use strip(1) to strip the target file
526  */
527 void
528 strip(char *to_name)
529 {
530 	int serrno, status;
531 	char * volatile path_strip;
532 	pid_t pid;
533 
534 	if (issetugid() || (path_strip = getenv("STRIP")) == NULL)
535 		path_strip = _PATH_STRIP;
536 
537 	switch ((pid = vfork())) {
538 	case -1:
539 		serrno = errno;
540 		(void)unlink(to_name);
541 		errc(1, serrno, "forks");
542 	case 0:
543 		execl(path_strip, "strip", "--", to_name, (char *)NULL);
544 		warn("%s", path_strip);
545 		_exit(1);
546 	default:
547 		while (waitpid(pid, &status, 0) == -1) {
548 			if (errno != EINTR)
549 				break;
550 		}
551 		if (!WIFEXITED(status))
552 			(void)unlink(to_name);
553 	}
554 }
555 
556 /*
557  * install_dir --
558  *	build directory hierarchy
559  */
560 void
561 install_dir(char *path, int mode)
562 {
563 	char *p;
564 	struct stat sb;
565 	int ch;
566 
567 	for (p = path;; ++p)
568 		if (!*p || (p != path && *p  == '/')) {
569 			ch = *p;
570 			*p = '\0';
571 			if (mkdir(path, 0777)) {
572 				int mkdir_errno = errno;
573 				if (stat(path, &sb)) {
574 					/* Not there; use mkdir()s errno */
575 					errc(1, mkdir_errno, "%s",
576 					    path);
577 					/* NOTREACHED */
578 				}
579 				if (!S_ISDIR(sb.st_mode)) {
580 					/* Is there, but isn't a directory */
581 					errc(1, ENOTDIR, "%s", path);
582 					/* NOTREACHED */
583 				}
584 			}
585 			if (!(*p = ch))
586 				break;
587  		}
588 
589 	if (((gid != (gid_t)-1 || uid != (uid_t)-1) && chown(path, uid, gid)) ||
590 	    chmod(path, mode)) {
591 		warn("%s", path);
592 	}
593 }
594 
595 /*
596  * usage --
597  *	print a usage message and die
598  */
599 void
600 usage(void)
601 {
602 	(void)fprintf(stderr, "\
603 usage: install [-bCcDdFpSs] [-B suffix] [-f flags] [-g group] [-m mode] [-o owner]\n	       source ... target ...\n");
604 	exit(1);
605 	/* NOTREACHED */
606 }
607 
608 /*
609  * create_tempfile --
610  *	create a temporary file based on path and open it
611  */
612 int
613 create_tempfile(char *path, char *temp, size_t tsize)
614 {
615 	char *p;
616 
617 	strlcpy(temp, path, tsize);
618 	if ((p = strrchr(temp, '/')) != NULL)
619 		p++;
620 	else
621 		p = temp;
622 	*p = '\0';
623 	strlcat(p, "INS@XXXXXXXXXX", tsize);
624 
625 	return(mkstemp(temp));
626 }
627 
628 /*
629  * file_write()
630  *	Write/copy a file (during copy or archive extract). This routine knows
631  *	how to copy files with lseek holes in it. (Which are read as file
632  *	blocks containing all 0's but do not have any file blocks associated
633  *	with the data). Typical examples of these are files created by dbm
634  *	variants (.pag files). While the file size of these files are huge, the
635  *	actual storage is quite small (the files are sparse). The problem is
636  *	the holes read as all zeros so are probably stored on the archive that
637  *	way (there is no way to determine if the file block is really a hole,
638  *	we only know that a file block of all zero's can be a hole).
639  *	At this writing, no major archive format knows how to archive files
640  *	with holes. However, on extraction (or during copy, -rw) we have to
641  *	deal with these files. Without detecting the holes, the files can
642  *	consume a lot of file space if just written to disk. This replacement
643  *	for write when passed the basic allocation size of a file system block,
644  *	uses lseek whenever it detects the input data is all 0 within that
645  *	file block. In more detail, the strategy is as follows:
646  *	While the input is all zero keep doing an lseek. Keep track of when we
647  *	pass over file block boundaries. Only write when we hit a non zero
648  *	input. once we have written a file block, we continue to write it to
649  *	the end (we stop looking at the input). When we reach the start of the
650  *	next file block, start checking for zero blocks again. Working on file
651  *	block boundaries significantly reduces the overhead when copying files
652  *	that are NOT very sparse. This overhead (when compared to a write) is
653  *	almost below the measurement resolution on many systems. Without it,
654  *	files with holes cannot be safely copied. It does has a side effect as
655  *	it can put holes into files that did not have them before, but that is
656  *	not a problem since the file contents are unchanged (in fact it saves
657  *	file space). (Except on paging files for diskless clients. But since we
658  *	cannot determine one of those file from here, we ignore them). If this
659  *	ever ends up on a system where CTG files are supported and the holes
660  *	are not desired, just do a conditional test in those routines that
661  *	call file_write() and have it call write() instead. BEFORE CLOSING THE
662  *	FILE, make sure to call file_flush() when the last write finishes with
663  *	an empty block. A lot of file systems will not create an lseek hole at
664  *	the end. In this case we drop a single 0 at the end to force the
665  *	trailing 0's in the file.
666  *	---Parameters---
667  *	rem: how many bytes left in this file system block
668  *	isempt: have we written to the file block yet (is it empty)
669  *	sz: basic file block allocation size
670  *	cnt: number of bytes on this write
671  *	str: buffer to write
672  * Return:
673  *	number of bytes written, -1 on write (or lseek) error.
674  */
675 
676 int
677 file_write(int fd, char *str, size_t cnt, int *rem, int *isempt, int sz)
678 {
679 	char *pt;
680 	char *end;
681 	size_t wcnt;
682 	char *st = str;
683 
684 	/*
685 	 * while we have data to process
686 	 */
687 	while (cnt) {
688 		if (!*rem) {
689 			/*
690 			 * We are now at the start of file system block again
691 			 * (or what we think one is...). start looking for
692 			 * empty blocks again
693 			 */
694 			*isempt = 1;
695 			*rem = sz;
696 		}
697 
698 		/*
699 		 * only examine up to the end of the current file block or
700 		 * remaining characters to write, whatever is smaller
701 		 */
702 		wcnt = MINIMUM(cnt, *rem);
703 		cnt -= wcnt;
704 		*rem -= wcnt;
705 		if (*isempt) {
706 			/*
707 			 * have not written to this block yet, so we keep
708 			 * looking for zero's
709 			 */
710 			pt = st;
711 			end = st + wcnt;
712 
713 			/*
714 			 * look for a zero filled buffer
715 			 */
716 			while ((pt < end) && (*pt == '\0'))
717 				++pt;
718 
719 			if (pt == end) {
720 				/*
721 				 * skip, buf is empty so far
722 				 */
723 				if (lseek(fd, (off_t)wcnt, SEEK_CUR) < 0) {
724 					warn("lseek");
725 					return(-1);
726 				}
727 				st = pt;
728 				continue;
729 			}
730 			/*
731 			 * drat, the buf is not zero filled
732 			 */
733 			*isempt = 0;
734 		}
735 
736 		/*
737 		 * have non-zero data in this file system block, have to write
738 		 */
739 		if (write(fd, st, wcnt) != wcnt) {
740 			warn("write");
741 			return(-1);
742 		}
743 		st += wcnt;
744 	}
745 	return(st - str);
746 }
747 
748 /*
749  * file_flush()
750  *	when the last file block in a file is zero, many file systems will not
751  *	let us create a hole at the end. To get the last block with zeros, we
752  *	write the last BYTE with a zero (back up one byte and write a zero).
753  */
754 void
755 file_flush(int fd, int isempt)
756 {
757 	static char blnk[] = "\0";
758 
759 	/*
760 	 * silly test, but make sure we are only called when the last block is
761 	 * filled with all zeros.
762 	 */
763 	if (!isempt)
764 		return;
765 
766 	/*
767 	 * move back one byte and write a zero
768 	 */
769 	if (lseek(fd, (off_t)-1, SEEK_CUR) < 0) {
770 		warn("Failed seek on file");
771 		return;
772 	}
773 
774 	if (write(fd, blnk, 1) < 0)
775 		warn("Failed write to file");
776 	return;
777 }
778