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