Home | History | Annotate | Download | only in liblzf
      1 /*
      2  * Copyright (c) 2006      Stefan Traby <stefan (at) hello-penguin.com>
      3  *
      4  * Redistribution and use in source and binary forms, with or without modifica-
      5  * tion, are permitted provided that the following conditions are met:
      6  *
      7  *   1.  Redistributions of source code must retain the above copyright notice,
      8  *       this list of conditions and the following disclaimer.
      9  *
     10  *   2.  Redistributions in binary form must reproduce the above copyright
     11  *       notice, this list of conditions and the following disclaimer in the
     12  *       documentation and/or other materials provided with the distribution.
     13  *
     14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
     15  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
     16  * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO
     17  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
     18  * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
     20  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
     21  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH-
     22  * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
     23  * OF THE POSSIBILITY OF SUCH DAMAGE.
     24  *
     25  * Alternatively, the contents of this file may be used under the terms of
     26  * the GNU General Public License ("GPL") version 2 or any later version,
     27  * in which case the provisions of the GPL are applicable instead of
     28  * the above. If you wish to allow the use of your version of this file
     29  * only under the terms of the GPL and not to allow others to use your
     30  * version of this file under the BSD license, indicate your decision
     31  * by deleting the provisions above and replace them with the notice
     32  * and other provisions required by the GPL. If you do not delete the
     33  * provisions above, a recipient may use your version of this file under
     34  * either the BSD or the GPL.
     35  */
     36 
     37 #include "config.h"
     38 #include <stdio.h>
     39 #include <string.h>
     40 #include <stdlib.h>
     41 #include <unistd.h>
     42 #include <sys/types.h>
     43 #include <sys/stat.h>
     44 #include <fcntl.h>
     45 #include <errno.h>
     46 #include <limits.h>
     47 #include "lzf.h"
     48 
     49 #ifdef HAVE_GETOPT_H
     50 # include <getopt.h>
     51 #endif
     52 
     53 #define BLOCKSIZE (1024 * 64 - 1)
     54 #define MAX_BLOCKSIZE BLOCKSIZE
     55 
     56 typedef unsigned char u8;
     57 
     58 static off_t nr_read, nr_written;
     59 
     60 static const char *imagename;
     61 static enum { compress, uncompress, lzcat } mode = compress;
     62 static int verbose = 0;
     63 static int force = 0;
     64 static long blocksize = BLOCKSIZE;
     65 
     66 #ifdef HAVE_GETOPT_LONG
     67 
     68   struct option longopts[] = {
     69     {"compress", 0, 0, 'c'},
     70     {"decompress", 0, 0, 'd'},
     71     {"uncompress", 0, 0, 'd'},
     72     {"force", 0, 0, 'f'},
     73     {"help", 0, 0, 'h'},
     74     {"verbose", 0, 0, 'v'},
     75     {"blocksize", 1, 0, 'b'},
     76     {0, 0, 0, 0}
     77   };
     78 
     79   static const char *opt =
     80     "-c --compress    compress\n"
     81     "-d --decompress  decompress\n"
     82     "-f --force       force overwrite of output file\n"
     83     "-h --help        give this help\n" "-v --verbose     verbose mode\n" "-b # --blocksize # set blocksize\n" "\n";
     84 
     85 #else
     86 
     87   static const char *opt =
     88     "-c   compress\n"
     89     "-d   decompress\n"
     90     "-f   force overwrite of output file\n"
     91     "-h   give this help\n"
     92     "-v   verbose mode\n"
     93     "-b # set blocksize\n"
     94     "\n";
     95 
     96 #endif
     97 
     98 static void
     99 usage (int rc)
    100 {
    101   fprintf (stderr, "\n"
    102            "lzf, a very lightweight compression/decompression utility written by Stefan Traby.\n"
    103            "uses liblzf written by Marc Lehmann <schmorp (at) schmorp.de> You can find more info at\n"
    104            "http://liblzf.plan9.de/\n"
    105            "\n"
    106            "usage: lzf [-dufhvb] [file ...]\n"
    107            "       unlzf [file ...]\n"
    108            "       lzcat [file ...]\n"
    109            "\n%s",
    110            opt);
    111 
    112   exit (rc);
    113 }
    114 
    115 static inline ssize_t
    116 rread (int fd, void *buf, size_t len)
    117 {
    118   ssize_t rc = 0, offset = 0;
    119   char *p = buf;
    120 
    121   while (len && (rc = read (fd, &p[offset], len)) > 0)
    122     {
    123       offset += rc;
    124       len -= rc;
    125     }
    126 
    127   nr_read += offset;
    128 
    129   if (rc < 0)
    130     return rc;
    131 
    132   return offset;
    133 }
    134 
    135 /* returns 0 if all written else -1 */
    136 static inline ssize_t
    137 wwrite (int fd, void *buf, size_t len)
    138 {
    139   ssize_t rc;
    140   char *b = buf;
    141   size_t l = len;
    142 
    143   while (l)
    144     {
    145       rc = write (fd, b, l);
    146       if (rc < 0)
    147         {
    148           fprintf (stderr, "%s: write error: ", imagename);
    149           perror ("");
    150           return -1;
    151         }
    152 
    153       l -= rc;
    154       b += rc;
    155     }
    156 
    157   nr_written += len;
    158   return 0;
    159 }
    160 
    161 /*
    162  * Anatomy: an lzf file consists of any number of blocks in the following format:
    163  *
    164  * \x00   EOF (optional)
    165  * "ZV\0" 2-byte-usize <uncompressed data>
    166  * "ZV\1" 2-byte-csize 2-byte-usize <compressed data>
    167  * "ZV\2" 4-byte-crc32-0xdebb20e3 (NYI)
    168  */
    169 
    170 
    171 #define TYPE0_HDR_SIZE 5
    172 #define TYPE1_HDR_SIZE 7
    173 #define MAX_HDR_SIZE 7
    174 #define MIN_HDR_SIZE 5
    175 
    176 static int
    177 compress_fd (int from, int to)
    178 {
    179   ssize_t us, cs, len;
    180   u8 buf1[MAX_BLOCKSIZE + MAX_HDR_SIZE + 16];
    181   u8 buf2[MAX_BLOCKSIZE + MAX_HDR_SIZE + 16];
    182   u8 *header;
    183 
    184   nr_read = nr_written = 0;
    185   while ((us = rread (from, &buf1[MAX_HDR_SIZE], blocksize)) > 0)
    186     {
    187       cs = lzf_compress (&buf1[MAX_HDR_SIZE], us, &buf2[MAX_HDR_SIZE], us > 4 ? us - 4 : us);
    188       if (cs)
    189         {
    190           header = &buf2[MAX_HDR_SIZE - TYPE1_HDR_SIZE];
    191           header[0] = 'Z';
    192           header[1] = 'V';
    193           header[2] = 1;
    194           header[3] = cs >> 8;
    195           header[4] = cs & 0xff;
    196           header[5] = us >> 8;
    197           header[6] = us & 0xff;
    198           len = cs + TYPE1_HDR_SIZE;
    199         }
    200       else
    201         {                       // write uncompressed
    202           header = &buf1[MAX_HDR_SIZE - TYPE0_HDR_SIZE];
    203           header[0] = 'Z';
    204           header[1] = 'V';
    205           header[2] = 0;
    206           header[3] = us >> 8;
    207           header[4] = us & 0xff;
    208           len = us + TYPE0_HDR_SIZE;
    209         }
    210 
    211       if (wwrite (to, header, len) == -1)
    212         return -1;
    213     }
    214 
    215   return 0;
    216 }
    217 
    218 static int
    219 uncompress_fd (int from, int to)
    220 {
    221   u8 header[MAX_HDR_SIZE];
    222   u8 buf1[MAX_BLOCKSIZE + MAX_HDR_SIZE + 16];
    223   u8 buf2[MAX_BLOCKSIZE + MAX_HDR_SIZE + 16];
    224   u8 *p;
    225   int l, rd;
    226   ssize_t rc, cs, us, bytes, over = 0;
    227 
    228   nr_read = nr_written = 0;
    229   while (1)
    230     {
    231       rc = rread (from, header + over, MAX_HDR_SIZE - over);
    232       if (rc < 0)
    233         {
    234           fprintf (stderr, "%s: read error: ", imagename);
    235           perror ("");
    236           return -1;
    237         }
    238 
    239       rc += over;
    240       over = 0;
    241       if (!rc || header[0] == 0)
    242         return 0;
    243 
    244       if (rc < MIN_HDR_SIZE || header[0] != 'Z' || header[1] != 'V')
    245         {
    246           fprintf (stderr, "%s: invalid data stream - magic not found or short header\n", imagename);
    247           return -1;
    248         }
    249 
    250       switch (header[2])
    251         {
    252           case 0:
    253             cs = -1;
    254             us = (header[3] << 8) | header[4];
    255             p = &header[TYPE0_HDR_SIZE];
    256             break;
    257           case 1:
    258             if (rc < TYPE1_HDR_SIZE)
    259               {
    260                 goto short_read;
    261               }
    262             cs = (header[3] << 8) | header[4];
    263             us = (header[5] << 8) | header[6];
    264             p = &header[TYPE1_HDR_SIZE];
    265             break;
    266           default:
    267             fprintf (stderr, "%s: unknown blocktype\n", imagename);
    268             return -1;
    269         }
    270 
    271       bytes = cs == -1 ? us : cs;
    272       l = &header[rc] - p;
    273 
    274       if (l > 0)
    275         memcpy (buf1, p, l);
    276 
    277       if (l > bytes)
    278         {
    279           over = l - bytes;
    280           memmove (header, &p[bytes], over);
    281         }
    282 
    283       p = &buf1[l];
    284       rd = bytes - l;
    285       if (rd > 0)
    286         if ((rc = rread (from, p, rd)) != rd)
    287           goto short_read;
    288 
    289       if (cs == -1)
    290         {
    291           if (wwrite (to, buf1, us))
    292             return -1;
    293         }
    294       else
    295         {
    296           if (lzf_decompress (buf1, cs, buf2, us) != us)
    297             {
    298               fprintf (stderr, "%s: decompress: invalid stream - data corrupted\n", imagename);
    299               return -1;
    300             }
    301 
    302           if (wwrite (to, buf2, us))
    303             return -1;
    304         }
    305     }
    306 
    307   return 0;
    308 
    309 short_read:
    310   fprintf (stderr, "%s: short data\n", imagename);
    311   return -1;
    312 }
    313 
    314 static int
    315 open_out (const char *name)
    316 {
    317   int fd;
    318   int m = O_EXCL;
    319 
    320   if (force)
    321     m = 0;
    322 
    323   fd = open (name, O_CREAT | O_WRONLY | O_TRUNC | m, 600);
    324 #if defined(__MINGW32__)
    325   _setmode(fd, _O_BINARY);
    326 #endif
    327   return fd;
    328 }
    329 
    330 static int
    331 compose_name (const char *fname, char *oname)
    332 {
    333   char *p;
    334 
    335   if (mode == compress)
    336     {
    337       if (strlen (fname) > PATH_MAX - 4)
    338         {
    339           fprintf (stderr, "%s: %s.lzf: name too long", imagename, fname);
    340           return -1;
    341         }
    342 
    343       strcpy (oname, fname);
    344       strcat (oname, ".lzf");
    345     }
    346   else
    347     {
    348       if (strlen (fname) > PATH_MAX)
    349         {
    350           fprintf (stderr, "%s: %s: name too long\n", imagename, fname);
    351           return -1;
    352         }
    353 
    354       strcpy (oname, fname);
    355       p = &oname[strlen (oname)] - 4;
    356       if (p < oname || strcmp (p, ".lzf"))
    357         {
    358           fprintf (stderr, "%s: %s: unknown suffix\n", imagename, fname);
    359           return -1;
    360         }
    361 
    362       *p = 0;
    363     }
    364 
    365   return 0;
    366 }
    367 
    368 static int
    369 run_file (const char *fname)
    370 {
    371   int fd, fd2;
    372   int rc;
    373   struct stat mystat;
    374   char oname[PATH_MAX + 1];
    375 
    376   if (mode != lzcat)
    377     if (compose_name (fname, oname))
    378       return -1;
    379 
    380 #if !defined(__MINGW32__)
    381   rc = lstat (fname, &mystat);
    382 #else
    383   rc = stat (fname, &mystat);
    384 #endif
    385   fd = open (fname, O_RDONLY);
    386 #if defined(__MINGW32__)
    387   _setmode(fd, _O_BINARY);
    388 #endif
    389   if (rc || fd == -1)
    390     {
    391       fprintf (stderr, "%s: %s: ", imagename, fname);
    392       perror ("");
    393       return -1;
    394     }
    395 
    396   if (!S_ISREG (mystat.st_mode))
    397     {
    398       fprintf (stderr, "%s: %s: not a regular file.\n", imagename, fname);
    399       close (fd);
    400       return -1;
    401     }
    402 
    403   if (mode == lzcat)
    404     {
    405       rc = uncompress_fd (fd, 1);
    406       close (fd);
    407       return rc;
    408     }
    409 
    410   fd2 = open_out (oname);
    411   if (fd2 == -1)
    412     {
    413       fprintf (stderr, "%s: %s: ", imagename, oname);
    414       perror ("");
    415       close (fd);
    416       return -1;
    417     }
    418 
    419   if (mode == compress)
    420     {
    421       rc = compress_fd (fd, fd2);
    422       if (!rc && verbose)
    423         fprintf (stderr, "%s:  %5.1f%% -- replaced with %s\n",
    424                  fname, nr_read == 0 ? 0 : 100.0 - nr_written / ((double) nr_read / 100.0), oname);
    425     }
    426   else
    427     {
    428       rc = uncompress_fd (fd, fd2);
    429       if (!rc && verbose)
    430         fprintf (stderr, "%s:  %5.1f%% -- replaced with %s\n",
    431                  fname, nr_written == 0 ? 0 : 100.0 - nr_read / ((double) nr_written / 100.0), oname);
    432     }
    433 
    434 #if !defined(__MINGW32__)
    435   fchmod (fd2, mystat.st_mode);
    436 #else
    437   chmod (oname, mystat.st_mode);
    438 #endif
    439   close (fd);
    440   close (fd2);
    441 
    442   if (!rc)
    443     unlink (fname);
    444 
    445   return rc;
    446 }
    447 
    448 int
    449 main (int argc, char *argv[])
    450 {
    451   char *p = argv[0];
    452   int optc;
    453   int rc = 0;
    454 
    455   errno = 0;
    456   p = getenv ("LZF_BLOCKSIZE");
    457   if (p)
    458     {
    459       blocksize = strtoul (p, 0, 0);
    460       if (errno || !blocksize || blocksize > MAX_BLOCKSIZE)
    461         blocksize = BLOCKSIZE;
    462     }
    463 
    464   p = strrchr (argv[0], '/');
    465   imagename = p ? ++p : argv[0];
    466 
    467   if (!strncmp (imagename, "un", 2) || !strncmp (imagename, "de", 2))
    468     mode = uncompress;
    469 
    470   if (strstr (imagename, "cat"))
    471     mode = lzcat;
    472 
    473 #ifdef HAVE_GETOPT_LONG
    474   while ((optc = getopt_long (argc, argv, "cdfhvb:", longopts, 0)) != -1)
    475 #else
    476   while ((optc = getopt (argc, argv, "cdfhvb:")) != -1)
    477 #endif
    478     {
    479       switch (optc)
    480         {
    481           case 'c':
    482             mode = compress;
    483             break;
    484           case 'd':
    485             mode = uncompress;
    486             break;
    487           case 'f':
    488             force = 1;
    489             break;
    490           case 'h':
    491             usage (0);
    492             break;
    493           case 'v':
    494             verbose = 1;
    495             break;
    496           case 'b':
    497             errno = 0;
    498             blocksize = strtoul (optarg, 0, 0);
    499             if (errno || !blocksize || blocksize > MAX_BLOCKSIZE)
    500               blocksize = BLOCKSIZE;
    501             break;
    502           default:
    503             usage (1);
    504             break;
    505         }
    506     }
    507 
    508   if (optind == argc)
    509     {                           // stdin stdout
    510       if (!force)
    511         {
    512           if ((mode == uncompress || mode == lzcat) && isatty (0))
    513             {
    514               fprintf (stderr, "%s: compressed data not read from a terminal. Use -f to force decompression.\n", imagename);
    515               exit (1);
    516             }
    517           if (mode == compress && isatty (1))
    518             {
    519               fprintf (stderr, "%s: compressed data not written to a terminal. Use -f to force compression.\n", imagename);
    520               exit (1);
    521             }
    522         }
    523 
    524       if (mode == compress)
    525         rc = compress_fd (0, 1);
    526       else
    527         rc = uncompress_fd (0, 1);
    528 
    529       exit (rc ? 1 : 0);
    530     }
    531 
    532   while (optind < argc)
    533     rc |= run_file (argv[optind++]);
    534 
    535   exit (rc ? 1 : 0);
    536 }
    537 
    538