xref: /illumos-gate/usr/src/cmd/lofiadm/main.c (revision 7ddc9b1afd18f260b9fb78ec7732facd91769131)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 /*
27  * lofiadm - administer lofi(7d). Very simple, add and remove file<->device
28  * associations, and display status. All the ioctls are private between
29  * lofi and lofiadm, and so are very simple - device information is
30  * communicated via a minor number.
31  */
32 
33 #pragma ident	"%Z%%M%	%I%	%E% SMI"
34 
35 #include <sys/types.h>
36 #include <sys/param.h>
37 #include <sys/lofi.h>
38 #include <sys/stat.h>
39 #include <netinet/in.h>
40 #include <stdio.h>
41 #include <fcntl.h>
42 #include <locale.h>
43 #include <string.h>
44 #include <strings.h>
45 #include <errno.h>
46 #include <stdlib.h>
47 #include <unistd.h>
48 #include <stropts.h>
49 #include <libdevinfo.h>
50 #include <libgen.h>
51 #include <ctype.h>
52 #include <dlfcn.h>
53 #include "utils.h"
54 
55 static const char USAGE[] =
56 	"Usage: %s -a file [ device ]\n"
57 	"       %s -d file | device \n"
58 	"       %s -C [algorithm] [-s segment_size] file \n"
59 	"       %s -U file \n"
60 	"       %s [ device | file ]\n";
61 
62 static const char *pname;
63 static int	addflag = 0;
64 static int	deleteflag = 0;
65 static int	errflag = 0;
66 static int	compressflag = 0;
67 static int 	uncompressflag = 0;
68 
69 static int gzip_compress(void *src, size_t srclen, void *dst,
70 	size_t *destlen, int level);
71 
72 lofi_compress_info_t lofi_compress_table[LOFI_COMPRESS_FUNCTIONS] = {
73 	{NULL,  gzip_compress,  6,	"gzip"}, /* default */
74 	{NULL,	gzip_compress,	6,	"gzip-6"},
75 	{NULL,	gzip_compress,	9, 	"gzip-9"}
76 };
77 
78 #define	FORMAT 			"%-20s     %-30s	%s\n"
79 #define	NONE			"-"
80 #define	COMPRESS		"Compressed"
81 #define	COMPRESS_ALGORITHM	"gzip"
82 #define	COMPRESS_THRESHOLD	2048
83 #define	SEGSIZE			131072
84 #define	BLOCK_SIZE		512
85 #define	KILOBYTE		1024
86 #define	MEGABYTE		(KILOBYTE * KILOBYTE)
87 #define	GIGABYTE		(KILOBYTE * MEGABYTE)
88 #define	LIBZ			"libz.so"
89 
90 static int (*compress2p)(void *, ulong_t *, void *, size_t, int) = NULL;
91 
92 static int gzip_compress(void *src, size_t srclen, void *dst,
93 	size_t *dstlen, int level)
94 {
95 	void *libz_hdl = NULL;
96 
97 	/*
98 	 * The first time we are called, attempt to dlopen()
99 	 * libz.so and get a pointer to the compress2() function
100 	 */
101 	if (compress2p == NULL) {
102 		if ((libz_hdl = openlib(LIBZ)) == NULL)
103 			die(gettext("could not find %s. "
104 			    "gzip compression unavailable\n"), LIBZ);
105 
106 		if ((compress2p =
107 		    (int (*)(void *, ulong_t *, void *, size_t, int))
108 		    dlsym(libz_hdl, "compress2")) == NULL) {
109 			closelib();
110 			die(gettext("could not find the correct %s. "
111 			    "gzip compression unavailable\n"), LIBZ);
112 		}
113 	}
114 
115 	if ((*compress2p)(dst, (ulong_t *)dstlen, src, srclen, level) != 0)
116 		return (-1);
117 	return (0);
118 }
119 
120 /*
121  * Print the list of all the mappings. Including a header.
122  */
123 static void
124 print_mappings(int fd)
125 {
126 	struct lofi_ioctl li;
127 	int	minor;
128 	int	maxminor;
129 	char	path[MAXPATHLEN];
130 	char	options[MAXPATHLEN];
131 
132 	li.li_minor = 0;
133 	if (ioctl(fd, LOFI_GET_MAXMINOR, &li) == -1) {
134 		perror("ioctl");
135 		exit(E_ERROR);
136 	}
137 
138 	maxminor = li.li_minor;
139 
140 	(void) printf(FORMAT, "Block Device", "File", "Options");
141 	for (minor = 1; minor <= maxminor; minor++) {
142 		li.li_minor = minor;
143 		if (ioctl(fd, LOFI_GET_FILENAME, &li) == -1) {
144 			if (errno == ENXIO)
145 				continue;
146 			perror("ioctl");
147 			break;
148 		}
149 		(void) snprintf(path, sizeof (path), "/dev/%s/%d",
150 		    LOFI_BLOCK_NAME, minor);
151 		if (li.li_algorithm[0] == '\0')
152 			(void) snprintf(options, sizeof (options), "%s", NONE);
153 		else
154 			(void) snprintf(options, sizeof (options),
155 			    COMPRESS "(%s)", li.li_algorithm);
156 
157 		(void) printf(FORMAT, path, li.li_filename, options);
158 	}
159 }
160 
161 static void
162 usage(void)
163 {
164 	(void) fprintf(stderr, gettext(USAGE), pname, pname,
165 	    pname, pname, pname);
166 	exit(E_USAGE);
167 }
168 
169 /*
170  * Translate a lofi device name to a minor number. We might be asked
171  * to do this when there is no association (such as when the user specifies
172  * a particular device), so we can only look at the string.
173  */
174 static int
175 name_to_minor(const char *devicename)
176 {
177 	int	minor;
178 
179 	if (sscanf(devicename, "/dev/" LOFI_BLOCK_NAME "/%d", &minor) == 1) {
180 		return (minor);
181 	}
182 	if (sscanf(devicename, "/dev/" LOFI_CHAR_NAME "/%d", &minor) == 1) {
183 		return (minor);
184 	}
185 	return (0);
186 }
187 
188 /*
189  * This might be the first time we've used this minor number. If so,
190  * it might also be that the /dev links are in the process of being created
191  * by devfsadmd (or that they'll be created "soon"). We cannot return
192  * until they're there or the invoker of lofiadm might try to use them
193  * and not find them. This can happen if a shell script is running on
194  * an MP.
195  */
196 static int sleeptime = 2;	/* number of seconds to sleep between stat's */
197 static int maxsleep = 120;	/* maximum number of seconds to sleep */
198 
199 static void
200 wait_until_dev_complete(int minor)
201 {
202 	struct stat64 buf;
203 	int	cursleep;
204 	char	blkpath[MAXPATHLEN];
205 	char	charpath[MAXPATHLEN];
206 	di_devlink_handle_t hdl;
207 
208 
209 	(void) snprintf(blkpath, sizeof (blkpath), "/dev/%s/%d",
210 	    LOFI_BLOCK_NAME, minor);
211 	(void) snprintf(charpath, sizeof (charpath), "/dev/%s/%d",
212 	    LOFI_CHAR_NAME, minor);
213 
214 	/* Check if links already present */
215 	if (stat64(blkpath, &buf) == 0 && stat64(charpath, &buf) == 0)
216 		return;
217 
218 	/* First use di_devlink_init() */
219 	if (hdl = di_devlink_init("lofi", DI_MAKE_LINK)) {
220 		(void) di_devlink_fini(&hdl);
221 		goto out;
222 	}
223 
224 	/*
225 	 * Under normal conditions, di_devlink_init(DI_MAKE_LINK) above will
226 	 * only fail if the caller is non-root. In that case, wait for
227 	 * link creation via sysevents.
228 	 */
229 	cursleep = 0;
230 	while (cursleep < maxsleep) {
231 		if ((stat64(blkpath, &buf) == -1) ||
232 		    (stat64(charpath, &buf) == -1)) {
233 			(void) sleep(sleeptime);
234 			cursleep += sleeptime;
235 			continue;
236 		}
237 		return;
238 	}
239 
240 	/* one last try */
241 
242 out:
243 	if (stat64(blkpath, &buf) == -1) {
244 		die(gettext("%s was not created"), blkpath);
245 	}
246 	if (stat64(charpath, &buf) == -1) {
247 		die(gettext("%s was not created"), charpath);
248 	}
249 }
250 
251 /*
252  * Add a device association. If devicename is NULL, let the driver
253  * pick a device.
254  */
255 static void
256 add_mapping(int lfd, const char *devicename, const char *filename,
257     int *minor_created, int suppress)
258 {
259 	struct lofi_ioctl li;
260 	int	minor;
261 
262 	if (devicename == NULL) {
263 		/* pick one */
264 		li.li_minor = 0;
265 		(void) strlcpy(li.li_filename, filename,
266 		    sizeof (li.li_filename));
267 		minor = ioctl(lfd, LOFI_MAP_FILE, &li);
268 		if (minor == -1) {
269 			die(gettext("could not map file %s"), filename);
270 		}
271 		wait_until_dev_complete(minor);
272 		/* print one picked */
273 		if (!suppress)
274 			(void) printf("/dev/%s/%d\n", LOFI_BLOCK_NAME, minor);
275 
276 		/* fill in the minor if needed */
277 		if (minor_created != NULL) {
278 			*minor_created = minor;
279 		}
280 		return;
281 	}
282 	/* use device we were given */
283 	minor = name_to_minor(devicename);
284 	if (minor == 0) {
285 		die(gettext("malformed device name %s\n"), devicename);
286 	}
287 	(void) strlcpy(li.li_filename, filename, sizeof (li.li_filename));
288 	li.li_minor = minor;
289 	if (ioctl(lfd, LOFI_MAP_FILE_MINOR, &li) == -1) {
290 		die(gettext("could not map file %s to %s"), filename,
291 		    devicename);
292 	}
293 	wait_until_dev_complete(minor);
294 }
295 
296 /*
297  * Remove an association. Delete by device name if non-NULL, or by
298  * filename otherwise.
299  */
300 static void
301 delete_mapping(int lfd, const char *devicename, const char *filename,
302     boolean_t force)
303 {
304 	struct lofi_ioctl li;
305 
306 	li.li_force = force;
307 	li.li_cleanup = B_FALSE;
308 
309 	if (devicename == NULL) {
310 		/* delete by filename */
311 		(void) strlcpy(li.li_filename, filename,
312 		    sizeof (li.li_filename));
313 		li.li_minor = 0;
314 		if (ioctl(lfd, LOFI_UNMAP_FILE, &li) == -1) {
315 			die(gettext("could not unmap file %s"), filename);
316 		}
317 		return;
318 	}
319 	/* delete by device */
320 
321 	li.li_minor = name_to_minor(devicename);
322 	if (li.li_minor == 0) {
323 		die(gettext("malformed device name %s\n"), devicename);
324 	}
325 	if (ioctl(lfd, LOFI_UNMAP_FILE_MINOR, &li) == -1) {
326 		die(gettext("could not unmap device %s"), devicename);
327 	}
328 }
329 
330 static void
331 print_one_mapping(int lfd, const char *devicename, const char *filename)
332 {
333 	struct lofi_ioctl li;
334 
335 	if (devicename == NULL) {
336 		/* given filename, print devicename */
337 		li.li_minor = 0;
338 		(void) strlcpy(li.li_filename, filename,
339 		    sizeof (li.li_filename));
340 		if (ioctl(lfd, LOFI_GET_MINOR, &li) == -1) {
341 			die(gettext("could not find device for %s"), filename);
342 		}
343 		(void) printf("/dev/%s/%d\n", LOFI_BLOCK_NAME, li.li_minor);
344 		return;
345 	}
346 
347 	/* given devicename, print filename */
348 	li.li_minor = name_to_minor(devicename);
349 	if (li.li_minor == 0) {
350 		die(gettext("malformed device name %s\n"), devicename);
351 	}
352 	if (ioctl(lfd, LOFI_GET_FILENAME, &li) == -1) {
353 		die(gettext("could not find filename for %s"), devicename);
354 	}
355 	(void) printf("%s\n", li.li_filename);
356 }
357 
358 /*
359  * Uncompress a file.
360  *
361  * First map the file in to establish a device
362  * association, then read from it. On-the-fly
363  * decompression will automatically uncompress
364  * the file if it's compressed
365  *
366  * If the file is mapped and a device association
367  * has been established, disallow uncompressing
368  * the file until it is unmapped.
369  */
370 static void
371 lofi_uncompress(int lfd, const char *filename)
372 {
373 	struct lofi_ioctl li;
374 	char buf[MAXBSIZE];
375 	char devicename[32];
376 	char tmpfilename[MAXPATHLEN];
377 	char *dir = NULL;
378 	char *file = NULL;
379 	int minor = 0;
380 	struct stat64 statbuf;
381 	int compfd = -1;
382 	int uncompfd = -1;
383 	ssize_t rbytes;
384 
385 	/*
386 	 * Disallow uncompressing the file if it is
387 	 * already mapped.
388 	 */
389 	li.li_minor = 0;
390 	(void) strlcpy(li.li_filename, filename, sizeof (li.li_filename));
391 	if (ioctl(lfd, LOFI_GET_MINOR, &li) != -1)
392 		die(gettext("%s must be unmapped before uncompressing"),
393 		    filename);
394 
395 	/* Zero length files don't need to be uncompressed */
396 	if (stat64(filename, &statbuf) == -1)
397 		die(gettext("stat: %s"), filename);
398 	if (statbuf.st_size == 0)
399 		return;
400 
401 	add_mapping(lfd, NULL, filename, &minor, 1);
402 	(void) snprintf(devicename, sizeof (devicename), "/dev/%s/%d",
403 	    LOFI_BLOCK_NAME, minor);
404 
405 	/* If the file isn't compressed, we just return */
406 	if ((ioctl(lfd, LOFI_CHECK_COMPRESSED, &li) == -1) ||
407 	    (li.li_algorithm[0] == '\0')) {
408 		delete_mapping(lfd, devicename, filename, B_TRUE);
409 		die("%s is not compressed\n", filename);
410 	}
411 
412 	if ((compfd = open64(devicename, O_RDONLY | O_NONBLOCK)) == -1) {
413 		delete_mapping(lfd, devicename, filename, B_TRUE);
414 		die(gettext("open: %s"), filename);
415 	}
416 	/* Create a temp file in the same directory */
417 	dir = strdup(filename);
418 	dir = dirname(dir);
419 	file = strdup(filename);
420 	file = basename(file);
421 	(void) snprintf(tmpfilename, sizeof (tmpfilename),
422 	    "%s/.%sXXXXXX", dir, file);
423 
424 	if ((uncompfd = mkstemp64(tmpfilename)) == -1) {
425 		(void) close(compfd);
426 		delete_mapping(lfd, devicename, filename, B_TRUE);
427 		free(dir);
428 		free(file);
429 		die("%s could not be uncompressed\n", filename);
430 	}
431 
432 	/*
433 	 * Set the mode bits and the owner of this temporary
434 	 * file to be that of the original uncompressed file
435 	 */
436 	(void) fchmod(uncompfd, statbuf.st_mode);
437 
438 	if (fchown(uncompfd, statbuf.st_uid, statbuf.st_gid) == -1) {
439 		(void) close(compfd);
440 		(void) close(uncompfd);
441 		delete_mapping(lfd, devicename, filename, B_TRUE);
442 		free(dir);
443 		free(file);
444 		die("%s could not be uncompressed\n", filename);
445 	}
446 
447 	/* Now read from the device in MAXBSIZE-sized chunks */
448 	for (;;) {
449 		rbytes = read(compfd, buf, sizeof (buf));
450 
451 		if (rbytes <= 0)
452 			break;
453 
454 		if (write(uncompfd, buf, rbytes) != rbytes) {
455 			rbytes = -1;
456 			break;
457 		}
458 	}
459 
460 	(void) close(compfd);
461 	(void) close(uncompfd);
462 	free(dir);
463 	free(file);
464 
465 	/* Delete the mapping */
466 	delete_mapping(lfd, devicename, filename, B_TRUE);
467 
468 	/*
469 	 * If an error occured while reading or writing, rbytes will
470 	 * be negative
471 	 */
472 	if (rbytes < 0) {
473 		(void) unlink(tmpfilename);
474 		die(gettext("could not read from %s"), filename);
475 	}
476 
477 	/* Rename the temp file to the actual file */
478 	if (rename(tmpfilename, filename) == -1)
479 		(void) unlink(tmpfilename);
480 }
481 
482 /*
483  * Compress a file
484  */
485 static void
486 lofi_compress(int *lfd, const char *filename, int compress_index,
487     uint32_t segsize)
488 {
489 	struct lofi_ioctl lic;
490 	lofi_compress_info_t *li;
491 	struct flock lock;
492 	char tmpfilename[MAXPATHLEN];
493 	char comp_filename[MAXPATHLEN];
494 	char algorithm[MAXALGLEN];
495 	char *dir = NULL, *file = NULL;
496 	uchar_t *uncompressed_seg = NULL;
497 	uchar_t *compressed_seg = NULL;
498 	uint32_t compressed_segsize;
499 	uint32_t len_compressed, count;
500 	uint32_t index_entries, index_sz;
501 	uint64_t *index = NULL;
502 	uint64_t offset;
503 	size_t real_segsize;
504 	struct stat64 statbuf;
505 	int compfd = -1, uncompfd = -1;
506 	int tfd = -1;
507 	ssize_t rbytes, wbytes, lastread;
508 	int i, type;
509 
510 	/*
511 	 * Disallow compressing the file if it is
512 	 * already mapped
513 	 */
514 	lic.li_minor = 0;
515 	(void) strlcpy(lic.li_filename, filename, sizeof (lic.li_filename));
516 	if (ioctl(*lfd, LOFI_GET_MINOR, &lic) != -1)
517 		die(gettext("%s must be unmapped before compressing"),
518 		    filename);
519 
520 	/*
521 	 * Close the control device so other operations
522 	 * can use it
523 	 */
524 	(void) close(*lfd);
525 	*lfd = -1;
526 
527 	li = &lofi_compress_table[compress_index];
528 
529 	/*
530 	 * The size of the buffer to hold compressed data must
531 	 * be slightly larger than the compressed segment size.
532 	 *
533 	 * The compress functions use part of the buffer as
534 	 * scratch space to do calculations.
535 	 * Ref: http://www.zlib.net/manual.html#compress2
536 	 */
537 	compressed_segsize = segsize + (segsize >> 6);
538 	compressed_seg = (uchar_t *)malloc(compressed_segsize + SEGHDR);
539 	uncompressed_seg = (uchar_t *)malloc(segsize);
540 
541 	if (compressed_seg == NULL || uncompressed_seg == NULL)
542 		die(gettext("No memory"));
543 
544 	if ((uncompfd = open64(filename, O_RDWR|O_LARGEFILE, 0)) == -1)
545 		die(gettext("open: %s"), filename);
546 
547 	lock.l_type = F_WRLCK;
548 	lock.l_whence = SEEK_SET;
549 	lock.l_start = 0;
550 	lock.l_len = 0;
551 
552 	/*
553 	 * Use an advisory lock to ensure that only a
554 	 * single lofiadm process compresses a given
555 	 * file at any given time
556 	 *
557 	 * A close on the file descriptor automatically
558 	 * closes all lock state on the file
559 	 */
560 	if (fcntl(uncompfd, F_SETLKW, &lock) == -1)
561 		die(gettext("fcntl: %s"), filename);
562 
563 	if (fstat64(uncompfd, &statbuf) == -1) {
564 		(void) close(uncompfd);
565 		die(gettext("fstat: %s"), filename);
566 	}
567 
568 	/* Zero length files don't need to be compressed */
569 	if (statbuf.st_size == 0) {
570 		(void) close(uncompfd);
571 		return;
572 	}
573 
574 	/*
575 	 * Create temporary files in the same directory that
576 	 * will hold the intermediate data
577 	 */
578 	dir = strdup(filename);
579 	dir = dirname(dir);
580 	file = strdup(filename);
581 	file = basename(file);
582 	(void) snprintf(tmpfilename, sizeof (tmpfilename),
583 	    "%s/.%sXXXXXX", dir, file);
584 	(void) snprintf(comp_filename, sizeof (comp_filename),
585 	    "%s/.%sXXXXXX", dir, file);
586 
587 	if ((tfd = mkstemp64(tmpfilename)) == -1)
588 		goto cleanup;
589 
590 	if ((compfd = mkstemp64(comp_filename)) == -1)
591 		goto cleanup;
592 
593 	/*
594 	 * Set the mode bits and owner of the compressed
595 	 * file to be that of the original uncompressed file
596 	 */
597 	(void) fchmod(compfd, statbuf.st_mode);
598 
599 	if (fchown(compfd, statbuf.st_uid, statbuf.st_gid) == -1)
600 		goto cleanup;
601 
602 	/*
603 	 * Calculate the number of index entries required.
604 	 * index entries are stored as an array. adding
605 	 * a '2' here accounts for the fact that the last
606 	 * segment may not be a multiple of the segment size
607 	 */
608 	index_sz = (statbuf.st_size / segsize) + 2;
609 	index = malloc(sizeof (*index) * index_sz);
610 
611 	if (index == NULL)
612 		goto cleanup;
613 
614 	offset = 0;
615 	lastread = segsize;
616 	count = 0;
617 
618 	/*
619 	 * Now read from the uncompressed file in 'segsize'
620 	 * sized chunks, compress what was read in and
621 	 * write it out to a temporary file
622 	 */
623 	for (;;) {
624 		rbytes = read(uncompfd, uncompressed_seg, segsize);
625 
626 		if (rbytes <= 0)
627 			break;
628 
629 		if (lastread < segsize)
630 			goto cleanup;
631 
632 		/*
633 		 * Account for the first byte that
634 		 * indicates whether a segment is
635 		 * compressed or not
636 		 */
637 		real_segsize = segsize - 1;
638 		(void) li->l_compress(uncompressed_seg, rbytes,
639 		    compressed_seg + SEGHDR, &real_segsize, li->l_level);
640 
641 		/*
642 		 * If the length of the compressed data is more
643 		 * than a threshold then there isn't any benefit
644 		 * to be had from compressing this segment - leave
645 		 * it uncompressed.
646 		 *
647 		 * NB. In case an error occurs during compression (above)
648 		 * the 'real_segsize' isn't changed. The logic below
649 		 * ensures that that segment is left uncompressed.
650 		 */
651 		len_compressed = real_segsize;
652 		if (real_segsize > segsize - COMPRESS_THRESHOLD) {
653 			(void) memcpy(compressed_seg + SEGHDR, uncompressed_seg,
654 			    rbytes);
655 			type = UNCOMPRESSED;
656 			len_compressed = rbytes;
657 		} else {
658 			type = COMPRESSED;
659 		}
660 
661 		/*
662 		 * Set the first byte or the SEGHDR to
663 		 * indicate if it's compressed or not
664 		 */
665 		*compressed_seg = type;
666 		wbytes = write(tfd, compressed_seg, len_compressed + SEGHDR);
667 		if (wbytes != (len_compressed + SEGHDR)) {
668 			rbytes = -1;
669 			break;
670 		}
671 
672 		index[count] = BE_64(offset);
673 		offset += wbytes;
674 		lastread = rbytes;
675 		count++;
676 	}
677 
678 	(void) close(uncompfd);
679 
680 	if (rbytes < 0)
681 		goto cleanup;
682 	/*
683 	 * The last index entry is a sentinel entry. It does not point to
684 	 * an actual compressed segment but helps in computing the size of
685 	 * the compressed segment. The size of each compressed segment is
686 	 * computed by subtracting the current index value from the next
687 	 * one (the compressed blocks are stored sequentially)
688 	 */
689 	index[count++] = BE_64(offset);
690 
691 	/*
692 	 * Now write the compressed data along with the
693 	 * header information to this file which will
694 	 * later be renamed to the original uncompressed
695 	 * file name
696 	 *
697 	 * The header is as follows -
698 	 *
699 	 * Signature (name of the compression algorithm)
700 	 * Compression segment size (a multiple of 512)
701 	 * Number of index entries
702 	 * Size of the last block
703 	 * The array containing the index entries
704 	 *
705 	 * the header is always stored in network byte
706 	 * order
707 	 */
708 	(void) bzero(algorithm, sizeof (algorithm));
709 	(void) strlcpy(algorithm, li->l_name, sizeof (algorithm));
710 	if (write(compfd, algorithm, sizeof (algorithm))
711 	    != sizeof (algorithm))
712 		goto cleanup;
713 
714 	segsize = htonl(segsize);
715 	if (write(compfd, &segsize, sizeof (segsize)) != sizeof (segsize))
716 		goto cleanup;
717 
718 	index_entries = htonl(count);
719 	if (write(compfd, &index_entries, sizeof (index_entries)) !=
720 	    sizeof (index_entries))
721 		goto cleanup;
722 
723 	lastread = htonl(lastread);
724 	if (write(compfd, &lastread, sizeof (lastread)) != sizeof (lastread))
725 		goto cleanup;
726 
727 	for (i = 0; i < count; i++) {
728 		if (write(compfd, index + i, sizeof (*index)) !=
729 		    sizeof (*index))
730 			goto cleanup;
731 	}
732 
733 	/* Header is written, now write the compressed data */
734 	if (lseek(tfd, 0, SEEK_SET) != 0)
735 		goto cleanup;
736 
737 	rbytes = wbytes = 0;
738 
739 	for (;;) {
740 		rbytes = read(tfd, compressed_seg, compressed_segsize + SEGHDR);
741 
742 		if (rbytes <= 0)
743 			break;
744 
745 		if (write(compfd, compressed_seg, rbytes) != rbytes)
746 			goto cleanup;
747 	}
748 
749 	if (fstat64(compfd, &statbuf) == -1)
750 		goto cleanup;
751 
752 	/*
753 	 * Round up the compressed file size to be a multiple of
754 	 * DEV_BSIZE. lofi(7D) likes it that way.
755 	 */
756 	if ((offset = statbuf.st_size % DEV_BSIZE) > 0) {
757 
758 		offset = DEV_BSIZE - offset;
759 
760 		for (i = 0; i < offset; i++)
761 			uncompressed_seg[i] = '\0';
762 		if (write(compfd, uncompressed_seg, offset) != offset)
763 			goto cleanup;
764 	}
765 	(void) close(compfd);
766 	(void) close(tfd);
767 	(void) unlink(tmpfilename);
768 cleanup:
769 	if (rbytes < 0) {
770 		if (tfd != -1)
771 			(void) unlink(tmpfilename);
772 		if (compfd != -1)
773 			(void) unlink(comp_filename);
774 		die(gettext("error compressing file %s"), filename);
775 	} else {
776 		/* Rename the compressed file to the actual file */
777 		if (rename(comp_filename, filename) == -1) {
778 			(void) unlink(comp_filename);
779 			die(gettext("error compressing file %s"), filename);
780 		}
781 	}
782 	if (compressed_seg != NULL)
783 		free(compressed_seg);
784 	if (uncompressed_seg != NULL)
785 		free(uncompressed_seg);
786 	if (dir != NULL)
787 		free(dir);
788 	if (file != NULL)
789 		free(file);
790 	if (index != NULL)
791 		free(index);
792 	if (compfd != -1)
793 		(void) close(compfd);
794 	if (uncompfd != -1)
795 		(void) close(uncompfd);
796 	if (tfd != -1)
797 		(void) close(tfd);
798 }
799 
800 static int
801 lofi_compress_select(const char *algname)
802 {
803 	int i;
804 
805 	for (i = 0; i < LOFI_COMPRESS_FUNCTIONS; i++) {
806 		if (strcmp(lofi_compress_table[i].l_name, algname) == 0)
807 			return (i);
808 	}
809 	return (-1);
810 }
811 
812 static void
813 check_algorithm_validity(const char *algname, int *compress_index)
814 {
815 	*compress_index = lofi_compress_select(algname);
816 	if (*compress_index < 0)
817 		die(gettext("invalid algorithm name: %s\n"), algname);
818 }
819 
820 static void
821 check_file_validity(const char *filename)
822 {
823 	struct stat64 buf;
824 	int 	error;
825 	int	fd = -1;
826 
827 	fd = open64(filename, O_RDONLY);
828 	if (fd == -1) {
829 		die(gettext("open: %s"), filename);
830 	}
831 	error = fstat64(fd, &buf);
832 	if (error == -1) {
833 		die(gettext("fstat: %s"), filename);
834 	} else if (!S_ISLOFIABLE(buf.st_mode)) {
835 		die(gettext("%s is not a regular file, "
836 		    "block, or character device\n"),
837 		    filename);
838 	} else if ((buf.st_size % DEV_BSIZE) != 0) {
839 		die(gettext("size of %s is not a multiple "
840 		    "of %d\n"),
841 		    filename, DEV_BSIZE);
842 	}
843 	(void) close(fd);
844 
845 	if (name_to_minor(filename) != 0) {
846 		die(gettext("cannot use " LOFI_DRIVER_NAME
847 		    " on itself\n"), NULL);
848 	}
849 }
850 
851 static uint32_t
852 convert_to_num(const char *str)
853 {
854 	int len;
855 	uint32_t segsize, mult = 1;
856 
857 	len = strlen(str);
858 	if (len && isalpha(str[len - 1])) {
859 		switch (str[len - 1]) {
860 		case 'k':
861 		case 'K':
862 			mult = KILOBYTE;
863 			break;
864 		case 'b':
865 		case 'B':
866 			mult = BLOCK_SIZE;
867 			break;
868 		case 'm':
869 		case 'M':
870 			mult = MEGABYTE;
871 			break;
872 		case 'g':
873 		case 'G':
874 			mult = GIGABYTE;
875 			break;
876 		default:
877 			die(gettext("invalid segment size %s\n"), str);
878 		}
879 	}
880 
881 	segsize = atol(str);
882 	segsize *= mult;
883 
884 	return (segsize);
885 }
886 
887 int
888 main(int argc, char *argv[])
889 {
890 	int	lfd;
891 	int	c;
892 	const char *devicename = NULL;
893 	const char *filename = NULL;
894 	const char *algname = COMPRESS_ALGORITHM;
895 	int	openflag;
896 	int	minor;
897 	int 	compress_index;
898 	uint32_t segsize = SEGSIZE;
899 	static char *lofictl = "/dev/" LOFI_CTL_NAME;
900 	boolean_t force = B_FALSE;
901 
902 	pname = getpname(argv[0]);
903 
904 	(void) setlocale(LC_ALL, "");
905 	(void) textdomain(TEXT_DOMAIN);
906 
907 	while ((c = getopt(argc, argv, "a:C:d:s:U:f")) != EOF) {
908 		switch (c) {
909 		case 'a':
910 			addflag = 1;
911 			filename = optarg;
912 			check_file_validity(filename);
913 
914 			if (((argc - optind) > 0) && (*argv[optind] != '-')) {
915 				/* optional device */
916 				devicename = argv[optind];
917 				optind++;
918 			}
919 			break;
920 		case 'C':
921 			compressflag = 1;
922 
923 			if (((argc - optind) > 0) &&
924 			    (*optarg == '-')) {
925 				check_algorithm_validity(algname,
926 				    &compress_index);
927 				optind--;
928 				break;
929 			} else if (((argc - optind) == 1) &&
930 			    (*argv[optind] != '-')) {
931 				algname = optarg;
932 				filename = argv[optind];
933 				optind++;
934 			} else if (((argc - optind) > 1) &&
935 			    (*argv[optind] == '-')) {
936 				algname = optarg;
937 				check_algorithm_validity(algname,
938 				    &compress_index);
939 				break;
940 			} else {
941 				filename = optarg;
942 			}
943 
944 			check_file_validity(filename);
945 			check_algorithm_validity(algname, &compress_index);
946 			break;
947 		case 'd':
948 			deleteflag = 1;
949 
950 			minor = name_to_minor(optarg);
951 			if (minor != 0)
952 				devicename = optarg;
953 			else
954 				filename = optarg;
955 			break;
956 		case 'f':
957 			force = B_TRUE;
958 			break;
959 		case 's':
960 			segsize = convert_to_num(optarg);
961 
962 			if (segsize == 0 || segsize % DEV_BSIZE)
963 				die(gettext("segment size %s is invalid "
964 				    "or not a multiple of minimum block "
965 				    "size %ld\n"), optarg, DEV_BSIZE);
966 
967 			filename = argv[optind];
968 			check_file_validity(filename);
969 			optind++;
970 			break;
971 		case 'U':
972 			uncompressflag = 1;
973 			filename = optarg;
974 			check_file_validity(filename);
975 			break;
976 		case '?':
977 		default:
978 			errflag = 1;
979 			break;
980 		}
981 	}
982 	if (errflag ||
983 	    (addflag && deleteflag) ||
984 	    ((compressflag || uncompressflag) && (addflag || deleteflag)))
985 		usage();
986 
987 	switch (argc - optind) {
988 	case 0: /* no more args */
989 		break;
990 	case 1: /* one arg without options means print the association */
991 		if (addflag || deleteflag)
992 			usage();
993 		if (compressflag || uncompressflag)
994 			usage();
995 		minor = name_to_minor(argv[optind]);
996 		if (minor != 0)
997 			devicename = argv[optind];
998 		else
999 			filename = argv[optind];
1000 		break;
1001 	default:
1002 		usage();
1003 		break;
1004 	}
1005 
1006 	if (filename && !valid_abspath(filename))
1007 		exit(E_ERROR);
1008 
1009 	/*
1010 	 * Here, we know the arguments are correct, the filename is an
1011 	 * absolute path, it exists and is a regular file. We don't yet
1012 	 * know that the device name is ok or not.
1013 	 */
1014 	/*
1015 	 * Now to the real work.
1016 	 */
1017 	openflag = O_EXCL;
1018 	if (addflag || deleteflag || compressflag || uncompressflag)
1019 		openflag |= O_RDWR;
1020 	else
1021 		openflag |= O_RDONLY;
1022 	lfd = open(lofictl, openflag);
1023 	if (lfd == -1) {
1024 		if ((errno == EPERM) || (errno == EACCES)) {
1025 			die("you do not have permission to perform "
1026 			    "that operation.\n");
1027 		} else {
1028 			die("%s", lofictl);
1029 		}
1030 		/*NOTREACHED*/
1031 	}
1032 	if (addflag)
1033 		add_mapping(lfd, devicename, filename, NULL, 0);
1034 	else if (compressflag)
1035 		lofi_compress(&lfd, filename, compress_index, segsize);
1036 	else if (uncompressflag)
1037 		lofi_uncompress(lfd, filename);
1038 	else if (deleteflag)
1039 		delete_mapping(lfd, devicename, filename, force);
1040 	else if (filename || devicename)
1041 		print_one_mapping(lfd, devicename, filename);
1042 	else
1043 		print_mappings(lfd);
1044 
1045 	if (lfd != -1)
1046 		(void) close(lfd);
1047 	closelib();
1048 	return (E_SUCCESS);
1049 }
1050