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