aboutsummaryrefslogtreecommitdiff
path: root/toys/lsb/gzip.c
blob: df5bf3af3548b929446291404b6d0b3c0b577615 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
/* gzip.c - gzip/gunzip/zcat
 *
 * Copyright 2017 The Android Open Source Project
 *
 * See http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/gzip.html
 * GZIP RFC: http://www.ietf.org/rfc/rfc1952.txt
 *
 * todo: qtv --rsyncable

// gzip.net version allows all options for all commands.
USE_GZIP(NEWTOY(gzip,     "cdfk123456789[-123456789]", TOYFLAG_USR|TOYFLAG_BIN))
USE_GUNZIP(NEWTOY(gunzip, "cdfk123456789[-123456789]", TOYFLAG_USR|TOYFLAG_BIN))
USE_ZCAT(NEWTOY(zcat,     "cdfk123456789[-123456789]", TOYFLAG_USR|TOYFLAG_BIN))

config GZIP
  bool "gzip"
  default n
  help
    usage: gzip [-19cdfk] [FILE...]

    Compress files. With no files, compresses stdin to stdout.
    On success, the input files are removed and replaced by new
    files with the .gz suffix.

    -c	Output to stdout
    -d	Decompress (act as gunzip)
    -f	Force: allow overwrite of output file
    -k	Keep input files (default is to remove)
    -#	Compression level 1-9 (1:fastest, 6:default, 9:best)

config GUNZIP
  bool "gunzip"
  default y
  help
    usage: gunzip [-cfk] [FILE...]

    Decompress files. With no files, decompresses stdin to stdout.
    On success, the input files are removed and replaced by new
    files without the .gz suffix.

    -c	Output to stdout (act as zcat)
    -f	Force: allow read from tty
    -k	Keep input files (default is to remove)

config ZCAT
  bool "zcat"
  default y
  help
    usage: zcat [FILE...]

    Decompress files to stdout. Like `gzip -dc`.

    -f	Force: allow read from tty
*/

#define FORCE_FLAGS
#define FOR_gzip
#include "toys.h"

GLOBALS(
  int level;
)

// Use assembly optimized zlib code?
#if CFG_TOYBOX_LIBZ
#include <zlib.h>

// Read from in_fd, write to out_fd, decompress if dd else compress
static int do_deflate(int in_fd, int out_fd, int dd, int level)
{
  int len, err = 0;
  char *b = "r";
  gzFile gz;

  if (!dd) {
    sprintf(b = toybuf, "w%d", level);
    if (out_fd == 1) out_fd = xdup(out_fd);
  }
  if (!(gz = gzdopen(dd ? in_fd : out_fd, b))) perror_exit("gzdopen");
  if (dd) {
    while ((len = gzread(gz, toybuf, sizeof(toybuf))) > 0)
      if (len != writeall(out_fd, toybuf, len)) break;
  } else {
    while ((len = read(in_fd, toybuf, sizeof(toybuf))) > 0)
      if (len != gzwrite(gz, toybuf, len))  break;
  }

  err = !!len;
  if (len>0 || err == Z_ERRNO) perror_msg(dd ? "write" : "read");
  if (len<0)
    error_msg("%s%s: %s", "gz", dd ? "read" : "write", gzerror(gz, &len));

  if (gzclose(gz) != Z_OK) perror_msg("gzclose"), err++;

  return err;
}

// Use toybox's builtin lib/deflate.c
#else

// Read from in_fd, write to out_fd, decompress if dd else compress
static int do_deflate(int in_fd, int out_fd, int dd, int level)
{
  int x;

  if (dd) WOULD_EXIT(x, gunzip_fd(in_fd, out_fd));
  else WOULD_EXIT(x, gzip_fd(in_fd, out_fd));

  return x;
}

#endif

static void do_gzip(int ifd, char *in)
{
  struct stat sb;
  char *out = 0;
  int ofd = 0;

  // Are we writing to stdout?
  if (!ifd || FLAG(c)) ofd = 1;
  if (isatty(ifd)) {
    if (!FLAG(f)) return error_msg("%s:need -f to read TTY"+3*!!ifd, in);
    else ofd = 1;
  }

  // Are we reading file.gz to write to file?
  if (!ofd) {
    if (fstat(ifd, &sb)) return perror_msg("%s", in);

    // Add or remove .gz suffix as necessary
    if (!FLAG(d)) out = xmprintf("%s%s", in, ".gz");
    else if ((out = strend(in, ".gz"))>in) out = xstrndup(in, out-in);
    else return error_msg("no .gz: %s", in);

    ofd = xcreate(out, O_CREAT|O_WRONLY|WARN_ONLY|(O_EXCL*!FLAG(f)),sb.st_mode);
    if (ofd == -1) return;
  }

  if (do_deflate(ifd, ofd, FLAG(d), TT.level)) in = out;

  if (out) {
    struct timespec times[] = {sb.st_atim, sb.st_mtim};

    if (utimensat(AT_FDCWD, out, times, 0)) perror_exit("utimensat");
    close(ofd);
    if (!FLAG(k) && in && unlink(in)) perror_msg("unlink %s", in);
    free(out);
  }
}

void gzip_main(void)
{
  // This depends on 1-9 being at the end of the option list
  for (TT.level = 0; TT.level<9; TT.level++)
    if ((toys.optflags>>TT.level)&1) break;
  if (!(TT.level = 9-TT.level)) TT.level = 6;

  loopfiles(toys.optargs, do_gzip);
}

void gunzip_main(void)
{
  toys.optflags |= FLAG_d;
  gzip_main();
}

void zcat_main(void)
{
  toys.optflags |= (FLAG_c|FLAG_d);
  gzip_main();
}