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   "@(#)main.c     1.9     08/05/07 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 = 0;
 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')) {
 408                 delete_mapping(lfd, devicename, filename, B_TRUE);
 409                 return;
 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                 return;
 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                 return;
 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         char tmpfilename[MAXPATHLEN];
 492         char comp_filename[MAXPATHLEN];
 493         char algorithm[MAXALGLEN];
 494         char *dir = NULL, *file = NULL;
 495         uchar_t *uncompressed_seg = NULL;
 496         uchar_t *compressed_seg = NULL;
 497         uint32_t compressed_segsize;
 498         uint32_t len_compressed, count;
 499         uint32_t index_entries, index_sz;
 500         uint64_t *index = NULL;
 501         uint64_t offset;
 502         size_t real_segsize;
 503         struct stat64 statbuf;
 504         int compfd = -1, uncompfd = -1;
 505         int tfd = -1;
 506         ssize_t rbytes, wbytes, lastread;
 507         int i, type;
 508 
 509         /*
 510          * Disallow compressing the file if it is
 511          * already mapped
 512          */
 513         lic.li_minor = 0;
 514         (void) strlcpy(lic.li_filename, filename, sizeof (lic.li_filename));
 515         if (ioctl(lfd, LOFI_GET_MINOR, &lic) != -1)
 516                 die(gettext("%s must be unmapped before compressing"),
 517                     filename);
 518 
 519         li = &lofi_compress_table[compress_index];
 520 
 521         /*
 522          * The size of the buffer to hold compressed data must
 523          * be slightly larger than the compressed segment size.
 524          *
 525          * The compress functions use part of the buffer as
 526          * scratch space to do calculations.
 527          * Ref: http://www.zlib.net/manual.html#compress2
 528          */
 529         compressed_segsize = segsize + (segsize >> 6);
 530         compressed_seg = (uchar_t *)malloc(compressed_segsize + SEGHDR);
 531         uncompressed_seg = (uchar_t *)malloc(segsize);
 532 
 533         if (compressed_seg == NULL || uncompressed_seg == NULL)
 534                 die(gettext("No memory"));
 535 
 536         if ((uncompfd = open64(filename, O_RDONLY|O_LARGEFILE, 0)) == -1)
 537                 die(gettext("open: %s"), filename);
 538 
 539         if (fstat64(uncompfd, &statbuf) == -1) {
 540                 (void) close(uncompfd);
 541                 die(gettext("fstat: %s"), filename);
 542         }
 543 
 544         /* Zero length files don't need to be compressed */
 545         if (statbuf.st_size == 0) {
 546                 (void) close(uncompfd);
 547                 return;
 548         }
 549 
 550         /*
 551          * Create temporary files in the same directory that
 552          * will hold the intermediate data
 553          */
 554         dir = strdup(filename);
 555         dir = dirname(dir);
 556         file = strdup(filename);
 557         file = basename(file);
 558         (void) snprintf(tmpfilename, sizeof (tmpfilename),
 559             "%s/.%sXXXXXX", dir, file);
 560         (void) snprintf(comp_filename, sizeof (comp_filename),
 561             "%s/.%sXXXXXX", dir, file);
 562 
 563         if ((tfd = mkstemp64(tmpfilename)) == -1)
 564                 goto cleanup;
 565 
 566         if ((compfd = mkstemp64(comp_filename)) == -1)
 567                 goto cleanup;
 568 
 569         /*
 570          * Set the mode bits and owner of the compressed
 571          * file to be that of the original uncompressed file
 572          */
 573         (void) fchmod(compfd, statbuf.st_mode);
 574 
 575         if (fchown(compfd, statbuf.st_uid, statbuf.st_gid) == -1)
 576                 goto cleanup;
 577 
 578         /*
 579          * Calculate the number of index entries required.
 580          * index entries are stored as an array. adding
 581          * a '2' here accounts for the fact that the last
 582          * segment may not be a multiple of the segment size
 583          */
 584         index_sz = (statbuf.st_size / segsize) + 2;
 585         index = malloc(sizeof (*index) * index_sz);
 586 
 587         if (index == NULL)
 588                 goto cleanup;
 589 
 590         offset = 0;
 591         lastread = segsize;
 592         count = 0;
 593 
 594         /*
 595          * Now read from the uncompressed file in 'segsize'
 596          * sized chunks, compress what was read in and
 597          * write it out to a temporary file
 598          */
 599         for (;;) {
 600                 rbytes = read(uncompfd, uncompressed_seg, segsize);
 601 
 602                 if (rbytes <= 0)
 603                         break;
 604 
 605                 if (lastread < segsize)
 606                         goto cleanup;
 607 
 608                 /*
 609                  * Account for the first byte that
 610                  * indicates whether a segment is
 611                  * compressed or not
 612                  */
 613                 real_segsize = segsize - 1;
 614                 (void) li->l_compress(uncompressed_seg, rbytes,
 615                     compressed_seg + SEGHDR, &real_segsize, li->l_level);
 616 
 617                 /*
 618                  * If the length of the compressed data is more
 619                  * than a threshold then there isn't any benefit
 620                  * to be had from compressing this segment - leave
 621                  * it uncompressed.
 622                  *
 623                  * NB. In case an error occurs during compression (above)
 624                  * the 'real_segsize' isn't changed. The logic below
 625                  * ensures that that segment is left uncompressed.
 626                  */
 627                 len_compressed = real_segsize;
 628                 if (real_segsize > segsize - COMPRESS_THRESHOLD) {
 629                         (void) memcpy(compressed_seg + SEGHDR, uncompressed_seg,
 630                             rbytes);
 631                         type = UNCOMPRESSED;
 632                         len_compressed = rbytes;
 633                 } else {
 634                         type = COMPRESSED;
 635                 }
 636 
 637                 /*
 638                  * Set the first byte or the SEGHDR to
 639                  * indicate if it's compressed or not
 640                  */
 641                 *compressed_seg = type;
 642                 wbytes = write(tfd, compressed_seg, len_compressed + SEGHDR);
 643                 if (wbytes != (len_compressed + SEGHDR)) {
 644                         rbytes = -1;
 645                         break;
 646                 }
 647 
 648                 index[count] = BE_64(offset);
 649                 offset += wbytes;
 650                 lastread = rbytes;
 651                 count++;
 652         }
 653 
 654         (void) close(uncompfd);
 655 
 656         if (rbytes < 0)
 657                 goto cleanup;
 658         /*
 659          * The last index entry is a sentinel entry. It does not point to
 660          * an actual compressed segment but helps in computing the size of
 661          * the compressed segment. The size of each compressed segment is
 662          * computed by subtracting the current index value from the next
 663          * one (the compressed blocks are stored sequentially)
 664          */
 665         index[count++] = BE_64(offset);
 666 
 667         /*
 668          * Now write the compressed data along with the
 669          * header information to this file which will
 670          * later be renamed to the original uncompressed
 671          * file name
 672          *
 673          * The header is as follows -
 674          *
 675          * Signature (name of the compression algorithm)
 676          * Compression segment size (a multiple of 512)
 677          * Number of index entries
 678          * Size of the last block
 679          * The array containing the index entries
 680          *
 681          * the header is always stored in network byte
 682          * order
 683          */
 684         (void) bzero(algorithm, sizeof (algorithm));
 685         (void) strlcpy(algorithm, li->l_name, sizeof (algorithm));
 686         if (write(compfd, algorithm, sizeof (algorithm))
 687             != sizeof (algorithm))
 688                 goto cleanup;
 689 
 690         segsize = htonl(segsize);
 691         if (write(compfd, &segsize, sizeof (segsize)) != sizeof (segsize))
 692                 goto cleanup;
 693 
 694         index_entries = htonl(count);
 695         if (write(compfd, &index_entries, sizeof (index_entries)) !=
 696             sizeof (index_entries))
 697                 goto cleanup;
 698 
 699         lastread = htonl(lastread);
 700         if (write(compfd, &lastread, sizeof (lastread)) != sizeof (lastread))
 701                 goto cleanup;
 702 
 703         for (i = 0; i < count; i++) {
 704                 if (write(compfd, index + i, sizeof (*index)) !=
 705                     sizeof (*index))
 706                         goto cleanup;
 707         }
 708 
 709         /* Header is written, now write the compressed data */
 710         if (lseek(tfd, 0, SEEK_SET) != 0)
 711                 goto cleanup;
 712 
 713         rbytes = wbytes = 0;
 714 
 715         for (;;) {
 716                 rbytes = read(tfd, compressed_seg, compressed_segsize + SEGHDR);
 717 
 718                 if (rbytes <= 0)
 719                         break;
 720 
 721                 if (write(compfd, compressed_seg, rbytes) != rbytes)
 722                         goto cleanup;
 723         }
 724 
 725         if (fstat64(compfd, &statbuf) == -1)
 726                 goto cleanup;
 727 
 728         /*
 729          * Round up the compressed file size to be a multiple of
 730          * DEV_BSIZE. lofi(7D) likes it that way.
 731          */
 732         if ((offset = statbuf.st_size % DEV_BSIZE) > 0) {
 733 
 734                 offset = DEV_BSIZE - offset;
 735 
 736                 for (i = 0; i < offset; i++)
 737                         uncompressed_seg[i] = '\0';
 738                 if (write(compfd, uncompressed_seg, offset) != offset)
 739                         goto cleanup;
 740         }
 741         (void) close(compfd);
 742         (void) close(tfd);
 743         (void) unlink(tmpfilename);
 744 cleanup:
 745         if (rbytes < 0) {
 746                 if (tfd != -1)
 747                         (void) unlink(tmpfilename);
 748                 if (compfd != -1)
 749                         (void) unlink(comp_filename);
 750                 die(gettext("error compressing file %s"), filename);
 751         } else {
 752                 /* Rename the compressed file to the actual file */
 753                 if (rename(comp_filename, filename) == -1) {
 754                         (void) unlink(comp_filename);
 755                         die(gettext("error compressing file %s"), filename);
 756                 }
 757         }
 758         if (compressed_seg != NULL)
 759                 free(compressed_seg);
 760         if (uncompressed_seg != NULL)
 761                 free(uncompressed_seg);
 762         if (dir != NULL)
 763                 free(dir);
 764         if (file != NULL)
 765                 free(file);
 766         if (index != NULL)
 767                 free(index);
 768         if (compfd != -1)
 769                 (void) close(compfd);
 770         if (uncompfd != -1)
 771                 (void) close(uncompfd);
 772         if (tfd != -1)
 773                 (void) close(tfd);
 774 }
 775 
 776 static int
 777 lofi_compress_select(const char *algname)
 778 {
 779         int i;
 780 
 781         for (i = 0; i < LOFI_COMPRESS_FUNCTIONS; i++) {
 782                 if (strcmp(lofi_compress_table[i].l_name, algname) == 0)
 783                         return (i);
 784         }
 785         return (-1);
 786 }
 787 
 788 static void
 789 check_algorithm_validity(const char *algname, int *compress_index)
 790 {
 791         *compress_index = lofi_compress_select(algname);
 792         if (*compress_index < 0)
 793                 die(gettext("invalid algorithm name: %s\n"), algname);
 794 }
 795 
 796 static void
 797 check_file_validity(const char *filename)
 798 {
 799         struct stat64 buf;
 800         int     error;
 801         int     fd = -1;
 802 
 803         fd = open64(filename, O_RDONLY);
 804         if (fd == -1) {
 805                 die(gettext("open: %s"), filename);
 806         }
 807         error = fstat64(fd, &buf);
 808         if (error == -1) {
 809                 die(gettext("fstat: %s"), filename);
 810         } else if (!S_ISLOFIABLE(buf.st_mode)) {
 811                 die(gettext("%s is not a regular file, "
 812                     "block, or character device\n"),
 813                     filename);
 814         } else if ((buf.st_size % DEV_BSIZE) != 0) {
 815                 die(gettext("size of %s is not a multiple "
 816                     "of %d\n"),
 817                     filename, DEV_BSIZE);
 818         }
 819         (void) close(fd);
 820 
 821         if (name_to_minor(filename) != 0) {
 822                 die(gettext("cannot use " LOFI_DRIVER_NAME
 823                     " on itself\n"), NULL);
 824         }
 825 }
 826 
 827 static uint32_t
 828 convert_to_num(const char *str)
 829 {
 830         int len;
 831         uint32_t segsize, mult = 1;
 832 
 833         len = strlen(str);
 834         if (len && isalpha(str[len - 1])) {
 835                 switch (str[len - 1]) {
 836                 case 'k':
 837                 case 'K':
 838                         mult = KILOBYTE;
 839                         break;
 840                 case 'b':
 841                 case 'B':
 842                         mult = BLOCK_SIZE;
 843                         break;
 844                 case 'm':
 845                 case 'M':
 846                         mult = MEGABYTE;
 847                         break;
 848                 case 'g':
 849                 case 'G':
 850                         mult = GIGABYTE;
 851                         break;
 852                 default:
 853                         die(gettext("invalid segment size %s\n"), str);
 854                 }
 855         }
 856 
 857         segsize = atol(str);
 858         segsize *= mult;
 859 
 860         return (segsize);
 861 }
 862 
 863 int
 864 main(int argc, char *argv[])
 865 {
 866         int     lfd;
 867         int     c;
 868         const char *devicename = NULL;
 869         const char *filename = NULL;
 870         const char *algname = COMPRESS_ALGORITHM;
 871         int     openflag;
 872         int     minor;
 873         int     compress_index;
 874         uint32_t segsize = SEGSIZE;
 875         static char *lofictl = "/dev/" LOFI_CTL_NAME;
 876         boolean_t force = B_FALSE;
 877 
 878         pname = getpname(argv[0]);
 879 
 880         (void) setlocale(LC_ALL, "");
 881         (void) textdomain(TEXT_DOMAIN);
 882 
 883         while ((c = getopt(argc, argv, "a:C:d:s:U:f")) != EOF) {
 884                 switch (c) {
 885                 case 'a':
 886                         addflag = 1;
 887                         filename = optarg;
 888                         check_file_validity(filename);
 889 
 890                         if (((argc - optind) > 0) && (*argv[optind] != '-')) {
 891                                 /* optional device */
 892                                 devicename = argv[optind];
 893                                 optind++;
 894                         }
 895                         break;
 896                 case 'C':
 897                         compressflag = 1;
 898 
 899                         if (((argc - optind) > 0) &&
 900                             (*optarg == '-')) {
 901                                 check_algorithm_validity(algname,
 902                                     &compress_index);
 903                                 optind--;
 904                                 break;
 905                         } else if (((argc - optind) == 1) &&
 906                             (*argv[optind] != '-')) {
 907                                 algname = optarg;
 908                                 filename = argv[optind];
 909                                 optind++;
 910                         } else if (((argc - optind) > 1) &&
 911                             (*argv[optind] == '-')) {
 912                                 algname = optarg;
 913                                 check_algorithm_validity(algname,
 914                                     &compress_index);
 915                                 break;
 916                         } else {
 917                                 filename = optarg;
 918                         }
 919 
 920                         check_file_validity(filename);
 921                         check_algorithm_validity(algname, &compress_index);
 922                         break;
 923                 case 'd':
 924                         deleteflag = 1;
 925 
 926                         minor = name_to_minor(optarg);
 927                         if (minor != 0)
 928                                 devicename = optarg;
 929                         else
 930                                 filename = optarg;
 931                         break;
 932                 case 'f':
 933                         force = B_TRUE;
 934                         break;
 935                 case 's':
 936                         segsize = convert_to_num(optarg);
 937 
 938                         if (segsize == 0 || segsize % DEV_BSIZE)
 939                                 die(gettext("segment size %s is invalid "
 940                                     "or not a multiple of minimum block "
 941                                     "size %ld\n"), optarg, DEV_BSIZE);
 942 
 943                         filename = argv[optind];
 944                         check_file_validity(filename);
 945                         optind++;
 946                         break;
 947                 case 'U':
 948                         uncompressflag = 1;
 949                         filename = optarg;
 950                         check_file_validity(filename);
 951                         break;
 952                 case '?':
 953                 default:
 954                         errflag = 1;
 955                         break;
 956                 }
 957         }
 958         if (errflag ||
 959             (addflag && deleteflag) ||
 960             ((compressflag || uncompressflag) && (addflag || deleteflag)))
 961                 usage();
 962 
 963         switch (argc - optind) {
 964         case 0: /* no more args */
 965                 break;
 966         case 1: /* one arg without options means print the association */
 967                 if (addflag || deleteflag)
 968                         usage();
 969                 if (compressflag || uncompressflag)
 970                         usage();
 971                 minor = name_to_minor(argv[optind]);
 972                 if (minor != 0)
 973                         devicename = argv[optind];
 974                 else
 975                         filename = argv[optind];
 976                 break;
 977         default:
 978                 usage();
 979                 break;
 980         }
 981 
 982         if (filename && !valid_abspath(filename))
 983                 exit(E_ERROR);
 984 
 985         /*
 986          * Here, we know the arguments are correct, the filename is an
 987          * absolute path, it exists and is a regular file. We don't yet
 988          * know that the device name is ok or not.
 989          */
 990         /*
 991          * Now to the real work.
 992          */
 993         openflag = O_EXCL;
 994         if (addflag || deleteflag || compressflag || uncompressflag)
 995                 openflag |= O_RDWR;
 996         else
 997                 openflag |= O_RDONLY;
 998         lfd = open(lofictl, openflag);
 999         if (lfd == -1) {
1000                 if ((errno == EPERM) || (errno == EACCES)) {
1001                         die("you do not have permission to perform "
1002                             "that operation.\n");
1003                 } else {
1004                         die("%s", lofictl);
1005                 }
1006                 /*NOTREACHED*/
1007         }
1008         if (addflag)
1009                 add_mapping(lfd, devicename, filename, NULL, 0);
1010         else if (compressflag)
1011                 lofi_compress(lfd, filename, compress_index, segsize);
1012         else if (uncompressflag)
1013                 lofi_uncompress(lfd, filename);
1014         else if (deleteflag)
1015                 delete_mapping(lfd, devicename, filename, force);
1016         else if (filename || devicename)
1017                 print_one_mapping(lfd, devicename, filename);
1018         else
1019                 print_mappings(lfd);
1020 
1021         (void) close(lfd);
1022         closelib();
1023         return (E_SUCCESS);
1024 }