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