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 
to_devnull(int fd)33 static void to_devnull(int fd)
34 {
35 	int devnull = open("/dev/null", fd ? O_WRONLY : O_RDONLY);
36 
37 	if (devnull >= 0)
38 		dup2(devnull, fd);
39 
40 	if (devnull > STDERR_FILENO)
41 		close(devnull);
42 }
43 
gzip_thread(void * ptr)44 static void *gzip_thread(void *ptr)
45 {
46 	struct gzip_handle *zh = ptr;
47 	char buf[4096];
48 	int len = 0, ret;
49 
50 	while (1) {
51 		if (zh->file)
52 			len = fread(buf, 1, sizeof(buf), zh->file);
53 		else if (zh->gzip)
54 			len = gzip_read(zh->gzip, buf, sizeof(buf));
55 
56 		if (len <= 0)
57 			break;
58 
59 		do {
60 			ret = write(zh->wfd, buf, len);
61 		} while (ret == -1 && errno == EINTR);
62 	}
63 
64 	close(zh->wfd);
65 	zh->wfd = -1;
66 
67 	return NULL;
68 }
69 
gzip_exec(struct gzip_handle * zh,const char * filename)70 int gzip_exec(struct gzip_handle *zh, const char *filename)
71 {
72 	int rpipe[2] = { -1, -1 }, wpipe[2] = {
73 	-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 		} else {
103 			dup2(wpipe[0], STDIN_FILENO);
104 			close(wpipe[0]);
105 			close(wpipe[1]);
106 		}
107 
108 		dup2(rpipe[1], STDOUT_FILENO);
109 		close(rpipe[0]);
110 		close(rpipe[1]);
111 
112 		execlp("gzip", "gzip", "-d", "-c", filename, NULL);
113 		exit(-1);
114 
115 	default:
116 		zh->rfd = rpipe[0];
117 		zh->wfd = wpipe[1];
118 
119 		fcntl(zh->rfd, F_SETFD, fcntl(zh->rfd, F_GETFD) | FD_CLOEXEC);
120 		close(rpipe[1]);
121 
122 		if (zh->wfd >= 0) {
123 			fcntl(zh->wfd, F_SETFD,
124 			      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 
gzip_read(struct gzip_handle * zh,void * buf,ssize_t len)133 ssize_t gzip_read(struct gzip_handle * zh, void *buf, ssize_t len)
134 {
135 	ssize_t ret;
136 
137 	do {
138 		ret = read(zh->rfd, buf, len);
139 	} while (ret == -1 && errno != EINTR);
140 
141 	return ret;
142 }
143 
gzip_copy(struct gzip_handle * zh,FILE * out,ssize_t len)144 ssize_t gzip_copy(struct gzip_handle * zh, FILE * out, ssize_t len)
145 {
146 	char buf[4096];
147 	ssize_t rlen, total = 0;
148 
149 	while (len > 0) {
150 		rlen = gzip_read(zh, buf,
151 				 (len > sizeof(buf)) ? sizeof(buf) : len);
152 
153 		if (rlen <= 0)
154 			break;
155 
156 		if (out != NULL) {
157 			if (fwrite(buf, 1, rlen, out) != rlen)
158 				break;
159 		}
160 
161 		len -= rlen;
162 		total += rlen;
163 	}
164 
165 	return total;
166 }
167 
gzip_fdopen(struct gzip_handle * zh,const char * filename)168 FILE *gzip_fdopen(struct gzip_handle * zh, const char *filename)
169 {
170 	memset(zh, 0, sizeof(*zh));
171 
172 	if (!filename || gzip_exec(zh, filename) < 0)
173 		return NULL;
174 
175 	fcntl(zh->rfd, F_SETFL, fcntl(zh->rfd, F_GETFL) & ~O_NONBLOCK);
176 
177 	return fdopen(zh->rfd, "r");
178 }
179 
gzip_close(struct gzip_handle * zh)180 int gzip_close(struct gzip_handle *zh)
181 {
182 	int code = -1;
183 
184 	if (zh->rfd >= 0)
185 		close(zh->rfd);
186 
187 	if (zh->wfd >= 0)
188 		close(zh->wfd);
189 
190 	if (zh->pid > 0) {
191 		kill(zh->pid, SIGKILL);
192 		waitpid(zh->pid, &code, 0);
193 	}
194 
195 	if (zh->file)
196 		fclose(zh->file);
197 
198 	if (zh->thread)
199 		pthread_join(zh->thread, NULL);
200 
201 	sigaction(SIGPIPE, &zh->pipe_sa, NULL);
202 
203 	return WIFEXITED(code) ? WEXITSTATUS(code) : -1;
204 }
205