105206c06Stls /*
205206c06Stls * Copyright (c) 2006 Stefan Traby <stefan@hello-penguin.com>
305206c06Stls *
405206c06Stls * Redistribution and use in source and binary forms, with or without modifica-
505206c06Stls * tion, are permitted provided that the following conditions are met:
605206c06Stls *
705206c06Stls * 1. Redistributions of source code must retain the above copyright notice,
805206c06Stls * this list of conditions and the following disclaimer.
905206c06Stls *
1005206c06Stls * 2. Redistributions in binary form must reproduce the above copyright
1105206c06Stls * notice, this list of conditions and the following disclaimer in the
1205206c06Stls * documentation and/or other materials provided with the distribution.
1305206c06Stls *
1405206c06Stls * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
1505206c06Stls * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
1605206c06Stls * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
1705206c06Stls * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
1805206c06Stls * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
1905206c06Stls * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
2005206c06Stls * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
2105206c06Stls * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH-
2205206c06Stls * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
2305206c06Stls * OF THE POSSIBILITY OF SUCH DAMAGE.
2405206c06Stls *
2505206c06Stls * Alternatively, the contents of this file may be used under the terms of
2605206c06Stls * the GNU General Public License ("GPL") version 2 or any later version,
2705206c06Stls * in which case the provisions of the GPL are applicable instead of
2805206c06Stls * the above. If you wish to allow the use of your version of this file
2905206c06Stls * only under the terms of the GPL and not to allow others to use your
3005206c06Stls * version of this file under the BSD license, indicate your decision
3105206c06Stls * by deleting the provisions above and replace them with the notice
3205206c06Stls * and other provisions required by the GPL. If you do not delete the
3305206c06Stls * provisions above, a recipient may use your version of this file under
3405206c06Stls * either the BSD or the GPL.
3505206c06Stls */
3605206c06Stls
3705206c06Stls #include <stdio.h>
3805206c06Stls #include <string.h>
3905206c06Stls #include <stdlib.h>
4005206c06Stls #include <unistd.h>
4105206c06Stls #include <sys/types.h>
4205206c06Stls #include <sys/stat.h>
4305206c06Stls #include <fcntl.h>
4405206c06Stls #include <errno.h>
4505206c06Stls #include <limits.h>
4605206c06Stls #include "lzf.h"
4705206c06Stls
4805206c06Stls #ifdef HAVE_GETOPT_H
4905206c06Stls # include <getopt.h>
5005206c06Stls #endif
5105206c06Stls
5205206c06Stls #define BLOCKSIZE (1024 * 64 - 1)
5305206c06Stls #define MAX_BLOCKSIZE BLOCKSIZE
5405206c06Stls
5505206c06Stls static off_t nr_read, nr_written;
5605206c06Stls
5705206c06Stls static const char *imagename;
58*e6ea0ca4Stls static enum { compress, uncompress, lzfcat } mode = compress;
5905206c06Stls static int verbose = 0;
6005206c06Stls static int force = 0;
6105206c06Stls static long blocksize = BLOCKSIZE;
6205206c06Stls
6305206c06Stls #ifdef HAVE_GETOPT_LONG
6405206c06Stls
6505206c06Stls struct option longopts[] = {
6605206c06Stls {"compress", 0, 0, 'c'},
6705206c06Stls {"decompress", 0, 0, 'd'},
6805206c06Stls {"uncompress", 0, 0, 'd'},
6905206c06Stls {"force", 0, 0, 'f'},
7005206c06Stls {"help", 0, 0, 'h'},
7105206c06Stls {"verbose", 0, 0, 'v'},
7205206c06Stls {"blocksize", 1, 0, 'b'},
7305206c06Stls {0, 0, 0, 0}
7405206c06Stls };
7505206c06Stls
7605206c06Stls static const char *opt =
7705206c06Stls "-c --compress compress\n"
7805206c06Stls "-d --decompress decompress\n"
7905206c06Stls "-f --force force overwrite of output file\n"
8005206c06Stls "-h --help give this help\n" "-v --verbose verbose mode\n" "-b # --blocksize # set blocksize\n" "\n";
8105206c06Stls
8205206c06Stls #else
8305206c06Stls
8405206c06Stls static const char *opt =
8505206c06Stls "-c compress\n"
8605206c06Stls "-d decompress\n"
8705206c06Stls "-f force overwrite of output file\n"
8805206c06Stls "-h give this help\n"
8905206c06Stls "-v verbose mode\n"
9005206c06Stls "-b # set blocksize\n"
9105206c06Stls "\n";
9205206c06Stls
9305206c06Stls #endif
9405206c06Stls
9505206c06Stls static void
usage(int rc)9605206c06Stls usage (int rc)
9705206c06Stls {
9805206c06Stls fprintf (stderr, "\n"
9905206c06Stls "lzf, a very lightweight compression/decompression utility written by Stefan Traby.\n"
10005206c06Stls "uses liblzf written by Marc Lehmann <schmorp@schmorp.de> You can find more info at\n"
10105206c06Stls "http://liblzf.plan9.de/\n"
10205206c06Stls "\n"
10305206c06Stls "usage: lzf [-dufhvb] [file ...]\n"
10405206c06Stls " unlzf [file ...]\n"
105*e6ea0ca4Stls " lzfcat [file ...]\n"
10605206c06Stls "\n%s",
10705206c06Stls opt);
10805206c06Stls
10905206c06Stls exit (rc);
11005206c06Stls }
11105206c06Stls
11205206c06Stls static inline ssize_t
rread(int fd,void * buf,size_t len)11305206c06Stls rread (int fd, void *buf, size_t len)
11405206c06Stls {
11505206c06Stls ssize_t rc = 0, offset = 0;
11605206c06Stls char *p = buf;
11705206c06Stls
11805206c06Stls while (len && (rc = read (fd, &p[offset], len)) > 0)
11905206c06Stls {
12005206c06Stls offset += rc;
12105206c06Stls len -= rc;
12205206c06Stls }
12305206c06Stls
12405206c06Stls nr_read += offset;
12505206c06Stls
12605206c06Stls if (rc < 0)
12705206c06Stls return rc;
12805206c06Stls
12905206c06Stls return offset;
13005206c06Stls }
13105206c06Stls
13205206c06Stls /* returns 0 if all written else -1 */
13305206c06Stls static inline ssize_t
wwrite(int fd,void * buf,size_t len)13405206c06Stls wwrite (int fd, void *buf, size_t len)
13505206c06Stls {
13605206c06Stls ssize_t rc;
13705206c06Stls char *b = buf;
13805206c06Stls size_t l = len;
13905206c06Stls
14005206c06Stls while (l)
14105206c06Stls {
14205206c06Stls rc = write (fd, b, l);
14305206c06Stls if (rc < 0)
14405206c06Stls {
14505206c06Stls fprintf (stderr, "%s: write error: ", imagename);
14605206c06Stls perror ("");
14705206c06Stls return -1;
14805206c06Stls }
14905206c06Stls
15005206c06Stls l -= rc;
15105206c06Stls b += rc;
15205206c06Stls }
15305206c06Stls
15405206c06Stls nr_written += len;
15505206c06Stls return 0;
15605206c06Stls }
15705206c06Stls
15805206c06Stls /*
15905206c06Stls * Anatomy: an lzf file consists of any number of blocks in the following format:
16005206c06Stls *
16105206c06Stls * \x00 EOF (optional)
16205206c06Stls * "ZV\0" 2-byte-usize <uncompressed data>
16305206c06Stls * "ZV\1" 2-byte-csize 2-byte-usize <compressed data>
16405206c06Stls * "ZV\2" 4-byte-crc32-0xdebb20e3 (NYI)
16505206c06Stls */
16605206c06Stls
16705206c06Stls
16805206c06Stls #define TYPE0_HDR_SIZE 5
16905206c06Stls #define TYPE1_HDR_SIZE 7
17005206c06Stls #define MAX_HDR_SIZE 7
17105206c06Stls #define MIN_HDR_SIZE 5
17205206c06Stls
17305206c06Stls static int
compress_fd(int from,int to)17405206c06Stls compress_fd (int from, int to)
17505206c06Stls {
17605206c06Stls ssize_t us, cs, len;
17705206c06Stls u8 buf1[MAX_BLOCKSIZE + MAX_HDR_SIZE + 16];
17805206c06Stls u8 buf2[MAX_BLOCKSIZE + MAX_HDR_SIZE + 16];
17905206c06Stls u8 *header;
18005206c06Stls
18105206c06Stls nr_read = nr_written = 0;
18205206c06Stls while ((us = rread (from, &buf1[MAX_HDR_SIZE], blocksize)) > 0)
18305206c06Stls {
18405206c06Stls cs = lzf_compress (&buf1[MAX_HDR_SIZE], us, &buf2[MAX_HDR_SIZE], us > 4 ? us - 4 : us);
18505206c06Stls if (cs)
18605206c06Stls {
18705206c06Stls header = &buf2[MAX_HDR_SIZE - TYPE1_HDR_SIZE];
18805206c06Stls header[0] = 'Z';
18905206c06Stls header[1] = 'V';
19005206c06Stls header[2] = 1;
19105206c06Stls header[3] = cs >> 8;
19205206c06Stls header[4] = cs & 0xff;
19305206c06Stls header[5] = us >> 8;
19405206c06Stls header[6] = us & 0xff;
19505206c06Stls len = cs + TYPE1_HDR_SIZE;
19605206c06Stls }
19705206c06Stls else
19805206c06Stls { // write uncompressed
19905206c06Stls header = &buf1[MAX_HDR_SIZE - TYPE0_HDR_SIZE];
20005206c06Stls header[0] = 'Z';
20105206c06Stls header[1] = 'V';
20205206c06Stls header[2] = 0;
20305206c06Stls header[3] = us >> 8;
20405206c06Stls header[4] = us & 0xff;
20505206c06Stls len = us + TYPE0_HDR_SIZE;
20605206c06Stls }
20705206c06Stls
20805206c06Stls if (wwrite (to, header, len) == -1)
20905206c06Stls return -1;
21005206c06Stls }
21105206c06Stls
21205206c06Stls return 0;
21305206c06Stls }
21405206c06Stls
21505206c06Stls static int
uncompress_fd(int from,int to)21605206c06Stls uncompress_fd (int from, int to)
21705206c06Stls {
21805206c06Stls u8 header[MAX_HDR_SIZE];
21905206c06Stls u8 buf1[MAX_BLOCKSIZE + MAX_HDR_SIZE + 16];
22005206c06Stls u8 buf2[MAX_BLOCKSIZE + MAX_HDR_SIZE + 16];
22105206c06Stls u8 *p;
22205206c06Stls int l, rd;
22305206c06Stls ssize_t rc, cs, us, bytes, over = 0;
22405206c06Stls
22505206c06Stls nr_read = nr_written = 0;
22605206c06Stls while (1)
22705206c06Stls {
22805206c06Stls rc = rread (from, header + over, MAX_HDR_SIZE - over);
22905206c06Stls if (rc < 0)
23005206c06Stls {
23105206c06Stls fprintf (stderr, "%s: read error: ", imagename);
23205206c06Stls perror ("");
23305206c06Stls return -1;
23405206c06Stls }
23505206c06Stls
23605206c06Stls rc += over;
23705206c06Stls over = 0;
23805206c06Stls if (!rc || header[0] == 0)
23905206c06Stls return 0;
24005206c06Stls
24105206c06Stls if (rc < MIN_HDR_SIZE || header[0] != 'Z' || header[1] != 'V')
24205206c06Stls {
24305206c06Stls fprintf (stderr, "%s: invalid data stream - magic not found or short header\n", imagename);
24405206c06Stls return -1;
24505206c06Stls }
24605206c06Stls
24705206c06Stls switch (header[2])
24805206c06Stls {
24905206c06Stls case 0:
25005206c06Stls cs = -1;
25105206c06Stls us = (header[3] << 8) | header[4];
25205206c06Stls p = &header[TYPE0_HDR_SIZE];
25305206c06Stls break;
25405206c06Stls case 1:
25505206c06Stls if (rc < TYPE1_HDR_SIZE)
25605206c06Stls {
25705206c06Stls goto short_read;
25805206c06Stls }
25905206c06Stls cs = (header[3] << 8) | header[4];
26005206c06Stls us = (header[5] << 8) | header[6];
26105206c06Stls p = &header[TYPE1_HDR_SIZE];
26205206c06Stls break;
26305206c06Stls default:
26405206c06Stls fprintf (stderr, "%s: unknown blocktype\n", imagename);
26505206c06Stls return -1;
26605206c06Stls }
26705206c06Stls
26805206c06Stls bytes = cs == -1 ? us : cs;
26905206c06Stls l = &header[rc] - p;
27005206c06Stls
27105206c06Stls if (l > 0)
27205206c06Stls memcpy (buf1, p, l);
27305206c06Stls
27405206c06Stls if (l > bytes)
27505206c06Stls {
27605206c06Stls over = l - bytes;
27705206c06Stls memmove (header, &p[bytes], over);
27805206c06Stls }
27905206c06Stls
28005206c06Stls p = &buf1[l];
28105206c06Stls rd = bytes - l;
28205206c06Stls if (rd > 0)
28305206c06Stls if ((rc = rread (from, p, rd)) != rd)
28405206c06Stls goto short_read;
28505206c06Stls
28605206c06Stls if (cs == -1)
28705206c06Stls {
28805206c06Stls if (wwrite (to, buf1, us))
28905206c06Stls return -1;
29005206c06Stls }
29105206c06Stls else
29205206c06Stls {
29305206c06Stls if (lzf_decompress (buf1, cs, buf2, us) != us)
29405206c06Stls {
29505206c06Stls fprintf (stderr, "%s: decompress: invalid stream - data corrupted\n", imagename);
29605206c06Stls return -1;
29705206c06Stls }
29805206c06Stls
29905206c06Stls if (wwrite (to, buf2, us))
30005206c06Stls return -1;
30105206c06Stls }
30205206c06Stls }
30305206c06Stls
30405206c06Stls return 0;
30505206c06Stls
30605206c06Stls short_read:
30705206c06Stls fprintf (stderr, "%s: short data\n", imagename);
30805206c06Stls return -1;
30905206c06Stls }
31005206c06Stls
31105206c06Stls static int
open_out(const char * name)31205206c06Stls open_out (const char *name)
31305206c06Stls {
31405206c06Stls int fd;
31505206c06Stls int m = O_EXCL;
31605206c06Stls
31705206c06Stls if (force)
31805206c06Stls m = 0;
31905206c06Stls
32005206c06Stls fd = open (name, O_CREAT | O_WRONLY | O_TRUNC | m, 600);
32105206c06Stls #if defined(__MINGW32__)
32205206c06Stls _setmode(fd, _O_BINARY);
32305206c06Stls #endif
32405206c06Stls return fd;
32505206c06Stls }
32605206c06Stls
32705206c06Stls static int
compose_name(const char * fname,char * oname)32805206c06Stls compose_name (const char *fname, char *oname)
32905206c06Stls {
33005206c06Stls char *p;
33105206c06Stls
33205206c06Stls if (mode == compress)
33305206c06Stls {
33405206c06Stls if (strlen (fname) > PATH_MAX - 4)
33505206c06Stls {
33605206c06Stls fprintf (stderr, "%s: %s.lzf: name too long", imagename, fname);
33705206c06Stls return -1;
33805206c06Stls }
33905206c06Stls
34005206c06Stls strcpy (oname, fname);
34105206c06Stls strcat (oname, ".lzf");
34205206c06Stls }
34305206c06Stls else
34405206c06Stls {
34505206c06Stls if (strlen (fname) > PATH_MAX)
34605206c06Stls {
34705206c06Stls fprintf (stderr, "%s: %s: name too long\n", imagename, fname);
34805206c06Stls return -1;
34905206c06Stls }
35005206c06Stls
35105206c06Stls strcpy (oname, fname);
35205206c06Stls p = &oname[strlen (oname)] - 4;
35305206c06Stls if (p < oname || strcmp (p, ".lzf"))
35405206c06Stls {
35505206c06Stls fprintf (stderr, "%s: %s: unknown suffix\n", imagename, fname);
35605206c06Stls return -1;
35705206c06Stls }
35805206c06Stls
35905206c06Stls *p = 0;
36005206c06Stls }
36105206c06Stls
36205206c06Stls return 0;
36305206c06Stls }
36405206c06Stls
36505206c06Stls static int
run_file(const char * fname)36605206c06Stls run_file (const char *fname)
36705206c06Stls {
36805206c06Stls int fd, fd2;
36905206c06Stls int rc;
37005206c06Stls struct stat mystat;
37105206c06Stls char oname[PATH_MAX + 1];
37205206c06Stls
373*e6ea0ca4Stls if (mode != lzfcat)
37405206c06Stls if (compose_name (fname, oname))
37505206c06Stls return -1;
37605206c06Stls
37705206c06Stls #if !defined(__MINGW32__)
37805206c06Stls rc = lstat (fname, &mystat);
37905206c06Stls #else
38005206c06Stls rc = stat (fname, &mystat);
38105206c06Stls #endif
38205206c06Stls fd = open (fname, O_RDONLY);
38305206c06Stls #if defined(__MINGW32__)
38405206c06Stls _setmode(fd, _O_BINARY);
38505206c06Stls #endif
38605206c06Stls if (rc || fd == -1)
38705206c06Stls {
38805206c06Stls fprintf (stderr, "%s: %s: ", imagename, fname);
38905206c06Stls perror ("");
39005206c06Stls return -1;
39105206c06Stls }
39205206c06Stls
39305206c06Stls if (!S_ISREG (mystat.st_mode))
39405206c06Stls {
39505206c06Stls fprintf (stderr, "%s: %s: not a regular file.\n", imagename, fname);
39605206c06Stls close (fd);
39705206c06Stls return -1;
39805206c06Stls }
39905206c06Stls
400*e6ea0ca4Stls if (mode == lzfcat)
40105206c06Stls {
40205206c06Stls rc = uncompress_fd (fd, 1);
40305206c06Stls close (fd);
40405206c06Stls return rc;
40505206c06Stls }
40605206c06Stls
40705206c06Stls fd2 = open_out (oname);
40805206c06Stls if (fd2 == -1)
40905206c06Stls {
41005206c06Stls fprintf (stderr, "%s: %s: ", imagename, oname);
41105206c06Stls perror ("");
41205206c06Stls close (fd);
41305206c06Stls return -1;
41405206c06Stls }
41505206c06Stls
41605206c06Stls if (mode == compress)
41705206c06Stls {
41805206c06Stls rc = compress_fd (fd, fd2);
41905206c06Stls if (!rc && verbose)
42005206c06Stls fprintf (stderr, "%s: %5.1f%% -- replaced with %s\n",
42105206c06Stls fname, nr_read == 0 ? 0 : 100.0 - nr_written / ((double) nr_read / 100.0), oname);
42205206c06Stls }
42305206c06Stls else
42405206c06Stls {
42505206c06Stls rc = uncompress_fd (fd, fd2);
42605206c06Stls if (!rc && verbose)
42705206c06Stls fprintf (stderr, "%s: %5.1f%% -- replaced with %s\n",
42805206c06Stls fname, nr_written == 0 ? 0 : 100.0 - nr_read / ((double) nr_written / 100.0), oname);
42905206c06Stls }
43005206c06Stls
43105206c06Stls #if !defined(__MINGW32__)
43205206c06Stls fchmod (fd2, mystat.st_mode);
43305206c06Stls #else
43405206c06Stls chmod (oname, mystat.st_mode);
43505206c06Stls #endif
43605206c06Stls close (fd);
43705206c06Stls close (fd2);
43805206c06Stls
43905206c06Stls if (!rc)
44005206c06Stls unlink (fname);
44105206c06Stls
44205206c06Stls return rc;
44305206c06Stls }
44405206c06Stls
44505206c06Stls int
main(int argc,char * argv[])44605206c06Stls main (int argc, char *argv[])
44705206c06Stls {
44805206c06Stls char *p = argv[0];
44905206c06Stls int optc;
45005206c06Stls int rc = 0;
45105206c06Stls
45205206c06Stls errno = 0;
45305206c06Stls p = getenv ("LZF_BLOCKSIZE");
45405206c06Stls if (p)
45505206c06Stls {
45605206c06Stls blocksize = strtoul (p, 0, 0);
45705206c06Stls if (errno || !blocksize || blocksize > MAX_BLOCKSIZE)
45805206c06Stls blocksize = BLOCKSIZE;
45905206c06Stls }
46005206c06Stls
46105206c06Stls p = strrchr (argv[0], '/');
46205206c06Stls imagename = p ? ++p : argv[0];
46305206c06Stls
46405206c06Stls if (!strncmp (imagename, "un", 2) || !strncmp (imagename, "de", 2))
46505206c06Stls mode = uncompress;
46605206c06Stls
46705206c06Stls if (strstr (imagename, "cat"))
468*e6ea0ca4Stls mode = lzfcat;
46905206c06Stls
47005206c06Stls #ifdef HAVE_GETOPT_LONG
47105206c06Stls while ((optc = getopt_long (argc, argv, "cdfhvb:", longopts, 0)) != -1)
47205206c06Stls #else
47305206c06Stls while ((optc = getopt (argc, argv, "cdfhvb:")) != -1)
47405206c06Stls #endif
47505206c06Stls {
47605206c06Stls switch (optc)
47705206c06Stls {
47805206c06Stls case 'c':
47905206c06Stls mode = compress;
48005206c06Stls break;
48105206c06Stls case 'd':
48205206c06Stls mode = uncompress;
48305206c06Stls break;
48405206c06Stls case 'f':
48505206c06Stls force = 1;
48605206c06Stls break;
48705206c06Stls case 'h':
48805206c06Stls usage (0);
48905206c06Stls break;
49005206c06Stls case 'v':
49105206c06Stls verbose = 1;
49205206c06Stls break;
49305206c06Stls case 'b':
49405206c06Stls errno = 0;
49505206c06Stls blocksize = strtoul (optarg, 0, 0);
49605206c06Stls if (errno || !blocksize || blocksize > MAX_BLOCKSIZE)
49705206c06Stls blocksize = BLOCKSIZE;
49805206c06Stls break;
49905206c06Stls default:
50005206c06Stls usage (1);
50105206c06Stls break;
50205206c06Stls }
50305206c06Stls }
50405206c06Stls
50505206c06Stls if (optind == argc)
50605206c06Stls { // stdin stdout
50705206c06Stls if (!force)
50805206c06Stls {
509*e6ea0ca4Stls if ((mode == uncompress || mode == lzfcat) && isatty (0))
51005206c06Stls {
51105206c06Stls fprintf (stderr, "%s: compressed data not read from a terminal. Use -f to force decompression.\n", imagename);
51205206c06Stls exit (1);
51305206c06Stls }
51405206c06Stls if (mode == compress && isatty (1))
51505206c06Stls {
51605206c06Stls fprintf (stderr, "%s: compressed data not written to a terminal. Use -f to force compression.\n", imagename);
51705206c06Stls exit (1);
51805206c06Stls }
51905206c06Stls }
52005206c06Stls
52105206c06Stls if (mode == compress)
52205206c06Stls rc = compress_fd (0, 1);
52305206c06Stls else
52405206c06Stls rc = uncompress_fd (0, 1);
52505206c06Stls
52605206c06Stls exit (rc ? 1 : 0);
52705206c06Stls }
52805206c06Stls
52905206c06Stls while (optind < argc)
53005206c06Stls rc |= run_file (argv[optind++]);
53105206c06Stls
53205206c06Stls exit (rc ? 1 : 0);
53305206c06Stls }
53405206c06Stls
535