ecdb5cb99d96a258dc185729d9f8ba3cf4a90de1
[oweals/opkg-lede.git] / libbb / gzip.c
1 /*
2  *  Copyright (C) 2016 Jo-Philipp Wich <jo@mein.io>
3  *  Copyright (C) 2016 Felix Fietkau <nbd@nbd.name>
4  *
5  *  Zlib decrompression utility routines.
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU Library General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20  */
21
22 #include <string.h>
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <unistd.h>
26 #include <poll.h>
27 #include <stdlib.h>
28 #include <sys/stat.h>
29 #include <sys/wait.h>
30
31 #include "gzip.h"
32
33 static void
34 to_devnull(int fd)
35 {
36         int devnull = open("/dev/null", fd ? O_WRONLY : O_RDONLY);
37
38         if (devnull >= 0)
39                 dup2(devnull, fd);
40
41         if (devnull > STDERR_FILENO)
42                 close(devnull);
43 }
44
45 void *
46 gzip_thread(void *ptr)
47 {
48         struct gzip_handle *zh = ptr;
49         char buf[4096];
50         int len, ret;
51
52         while (1) {
53                 if (zh->file)
54                         len = fread(buf, 1, sizeof(buf), zh->file);
55                 else if (zh->gzip)
56                         len = gzip_read(zh->gzip, buf, sizeof(buf));
57
58                 if (len <= 0)
59                         break;
60
61                 do {
62                         ret = write(zh->wfd, buf, len);
63                 } while (ret == -1 && errno == EINTR);
64         }
65
66         close(zh->wfd);
67         zh->wfd = -1;
68 }
69
70 int
71 gzip_exec(struct gzip_handle *zh, const char *filename)
72 {
73         int rpipe[2] = { -1, -1 }, wpipe[2] = { -1, -1 };
74         struct sigaction pipe_sa = { .sa_handler = SIG_IGN };
75
76         zh->rfd = -1;
77         zh->wfd = -1;
78
79         if (sigaction(SIGPIPE, &pipe_sa, &zh->pipe_sa) < 0)
80                 return -1;
81
82         if (pipe(rpipe) < 0)
83                 return -1;
84
85         if (!filename && pipe(wpipe) < 0) {
86                 close(rpipe[0]);
87                 close(rpipe[1]);
88                 return -1;
89         }
90
91         zh->pid = vfork();
92
93         switch (zh->pid) {
94                 case -1:
95                         return -1;
96
97                 case 0:
98                         to_devnull(STDERR_FILENO);
99
100                         if (filename) {
101                                 to_devnull(STDIN_FILENO);
102                         }
103                         else {
104                                 dup2(wpipe[0], STDIN_FILENO);
105                                 close(wpipe[0]);
106                                 close(wpipe[1]);
107                         }
108
109                         dup2(rpipe[1], STDOUT_FILENO);
110                         close(rpipe[0]);
111                         close(rpipe[1]);
112
113                         execlp("gzip", "gzip", "-d",  "-c", filename, NULL);
114                         exit(-1);
115
116                 default:
117                         zh->rfd = rpipe[0];
118                         zh->wfd = wpipe[1];
119
120                         fcntl(zh->rfd, F_SETFD, fcntl(zh->rfd, F_GETFD) | FD_CLOEXEC);
121                         close(rpipe[1]);
122
123                         if (zh->wfd >= 0) {
124                                 fcntl(zh->wfd, F_SETFD, fcntl(zh->wfd, F_GETFD) | FD_CLOEXEC);
125                                 close(wpipe[0]);
126                                 pthread_create(&zh->thread, NULL, gzip_thread, zh);
127                         }
128         }
129
130         return 0;
131 }
132
133 ssize_t
134 gzip_read(struct gzip_handle *zh, char *buf, ssize_t len)
135 {
136         ssize_t ret;
137
138         do {
139                 ret = read(zh->rfd, buf, len);
140         } while (ret == -1 && errno != EINTR);
141
142         return ret;
143 }
144
145 ssize_t
146 gzip_copy(struct gzip_handle *zh, FILE *out, ssize_t len)
147 {
148         char buf[4096];
149         ssize_t rlen, total = 0;
150
151         while (len > 0) {
152                 rlen = gzip_read(zh, buf,
153                                     (len > sizeof(buf)) ? sizeof(buf) : len);
154
155                 if (rlen <= 0)
156                         break;
157
158                 if (out != NULL) {
159                         if (fwrite(buf, 1, rlen, out) != rlen)
160                                 break;
161                 }
162
163                 len -= rlen;
164                 total += rlen;
165         }
166
167         return total;
168 }
169
170 FILE *
171 gzip_fdopen(struct gzip_handle *zh, const char *filename)
172 {
173         memset(zh, 0, sizeof(*zh));
174
175         if (!filename || gzip_exec(zh, filename) < 0)
176                 return NULL;
177
178         fcntl(zh->rfd, F_SETFL, fcntl(zh->rfd, F_GETFL) & ~O_NONBLOCK);
179
180         return fdopen(zh->rfd, "r");
181 }
182
183 int
184 gzip_close(struct gzip_handle *zh)
185 {
186         int code = -1;
187
188         if (zh->rfd >= 0)
189                 close(zh->rfd);
190
191         if (zh->wfd >= 0)
192                 close(zh->wfd);
193
194         if (zh->pid > 0) {
195                 kill(zh->pid, SIGKILL);
196                 waitpid(zh->pid, &code, 0);
197         }
198
199         if (zh->file)
200                 fclose(zh->file);
201
202         if (zh->thread)
203                 pthread_join(zh->thread, NULL);
204
205         sigaction(SIGPIPE, &zh->pipe_sa, NULL);
206
207         return WIFEXITED(code) ? WEXITSTATUS(code) : -1;
208 }