]> git.sesse.net Git - vlc/blob - modules/stream_filter/decomp.c
4e482d9c0f5f0da1555fd0e2318ef7d3af67746c
[vlc] / modules / stream_filter / decomp.c
1 /*****************************************************************************
2  * decomp.c : Decompression module for vlc
3  *****************************************************************************
4  * Copyright © 2008 Rémi Denis-Courmont
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 2.1 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19  *****************************************************************************/
20
21 #ifdef HAVE_CONFIG_H
22 # include "config.h"
23 #endif
24
25 #include <vlc_common.h>
26 #include <vlc_plugin.h>
27 #include <vlc_stream.h>
28 #include <vlc_network.h>
29 #include <unistd.h>
30 #include <fcntl.h>
31 #include <spawn.h>
32 #include <sys/wait.h>
33 #include <sys/ioctl.h>
34 #ifdef __linux__
35 # include <sys/uio.h>
36 # include <sys/mman.h>
37 #endif
38
39 #include <assert.h>
40
41 static int  OpenGzip (vlc_object_t *);
42 static int  OpenBzip2 (vlc_object_t *);
43 static void Close (vlc_object_t *);
44
45 vlc_module_begin ()
46     set_description (N_("Decompression"))
47     set_category (CAT_INPUT)
48     set_subcategory (SUBCAT_INPUT_STREAM_FILTER)
49     set_capability ("stream_filter", 20)
50     set_callbacks (OpenBzip2, Close)
51     /* TODO: access shortnames for stream_UrlNew() */
52
53     add_submodule ()
54     set_callbacks (OpenGzip, Close)
55 vlc_module_end ()
56
57 struct stream_sys_t
58 {
59     block_t      *peeked;
60     vlc_thread_t thread;
61     pid_t        pid;
62     int          write_fd, read_fd;
63 };
64
65 static void cloexec (int fd)
66 {
67     int flags = fcntl (fd, F_GETFD);
68     fcntl (fd, F_SETFD, FD_CLOEXEC | ((flags != -1) ? flags : 0));
69 }
70
71 extern char **environ;
72
73 static const size_t bufsize = 65536;
74 static void cleanup_mmap (void *addr)
75 {
76     munmap (addr, bufsize);
77 }
78
79
80 static void *Thread (void *data)
81 {
82     stream_t *stream = data;
83     stream_sys_t *p_sys = stream->p_sys;
84     int fd = p_sys->write_fd;
85     bool error = false;
86
87     do
88     {
89         ssize_t len;
90         int canc = vlc_savecancel ();
91 #ifdef __linux__
92         unsigned char *buf = mmap (NULL, bufsize, PROT_READ|PROT_WRITE,
93                                    MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
94         vlc_cleanup_push (cleanup_mmap, buf);
95 #else
96         unsigned char buf[bufsize];
97 #endif
98
99         len = stream_Read (stream->p_source, buf, bufsize);
100         vlc_restorecancel (canc);
101
102         if (len <= 0)
103             break;
104
105         for (ssize_t i = 0, j = 0; i < len; i += j)
106         {
107             struct iovec iov[1] = { { buf + i, len - i, } };
108
109 #ifdef __linux__
110             j = vmsplice (fd, iov, 1, SPLICE_F_GIFT);
111 #else
112             j = writev (fd, iov, 1);
113 #endif
114             if (j <= 0)
115             {
116                 if (j == 0)
117                     errno = EPIPE;
118                 msg_Err (stream, "cannot write data (%m)");
119                 error = true;
120                 break;
121             }
122         }
123 #ifdef __linux__
124         vlc_cleanup_run (); /* munmap (buf, bufsize) */
125 #endif
126     }
127     while (!error);
128
129     msg_Dbg (stream, "compressed stream at EOF");
130     return NULL;
131 }
132
133
134 #define MIN_BLOCK (1 << 10)
135 #define MAX_BLOCK (1 << 20)
136 /**
137  * Reads decompressed from the decompression program
138  * @return -1 for EAGAIN, 0 for EOF, byte count otherwise.
139  */
140 static int Read (stream_t *stream, void *buf, unsigned int buflen)
141 {
142     stream_sys_t *p_sys = stream->p_sys;
143     block_t *peeked;
144     size_t bonus = 0;
145     ssize_t length;
146
147     if ((peeked = p_sys->peeked) != NULL)
148     {
149         bonus = (buflen > peeked->i_buffer) ? peeked->i_buffer : buflen;
150         memcpy (buf, peeked->p_buffer, bonus);
151         peeked->p_buffer += bonus;
152         peeked->i_buffer -= bonus;
153         if (peeked->i_buffer == 0)
154         {
155             block_Release (peeked);
156             p_sys->peeked = NULL;
157         }
158     }
159
160     length = net_Read (stream, p_sys->read_fd, NULL, buf, buflen, false);
161     return bonus + ((length >= 0) ? length : 0);
162 }
163
164 /**
165  *
166  */
167 static int Peek (stream_t *stream, const uint8_t **pbuf, unsigned int len)
168 {
169     stream_sys_t *p_sys = stream->p_sys;
170     block_t *peeked = p_sys->peeked;
171     size_t curlen = 0;
172     int fd = p_sys->read_fd;
173
174     if (peeked == NULL)
175         peeked = block_Alloc (len);
176     else if ((curlen = peeked->i_buffer) < len)
177         peeked = block_Realloc (peeked, 0, len);
178
179     if ((p_sys->peeked = peeked) == NULL)
180         return 0;
181
182     if (curlen < len)
183     {
184         ssize_t val = net_Read (stream, fd, NULL, peeked->p_buffer + curlen,
185                                 len - curlen, true);
186         if (val >= 0)
187         {
188             curlen += val;
189             peeked->i_buffer = curlen;
190         }
191     }
192     *pbuf = peeked->p_buffer;
193     return curlen;
194 }
195
196 /**
197  *
198  */
199 static int Control (stream_t *stream, int query, va_list args)
200 {
201     /*stream_sys_t *p_sys = stream->p_sys;*/
202     (void)stream;
203     (void)query; (void)args;
204     return VLC_EGENERIC;
205 }
206
207 /**
208  * Pipe data through an external executable.
209  * @param stream the stream filter object.
210  * @param path path to the executable.
211  */
212 static int Open (stream_t *stream, const char *path)
213 {
214     stream_sys_t *p_sys = stream->p_sys = malloc (sizeof (*p_sys));
215     if (p_sys == NULL)
216         return VLC_ENOMEM;
217
218     stream->pf_read = Read;
219     stream->pf_peek = Peek;
220     stream->pf_control = Control;
221     p_sys->peeked = NULL;
222     p_sys->pid = -1;
223
224     /* I am not a big fan of the pyramid style, but I cannot think of anything
225      * better here. There are too many failure cases. */
226     int ret = VLC_EGENERIC;
227     int comp[2];
228
229     if (pipe (comp) == 0)
230     {
231         cloexec (comp[1]);
232         p_sys->write_fd = comp[1];
233
234         int uncomp[2];
235         if (pipe (uncomp) == 0)
236         {
237             cloexec (uncomp[0]);
238             p_sys->read_fd = uncomp[0];
239
240             posix_spawn_file_actions_t actions;
241             if (posix_spawn_file_actions_init (&actions) == 0)
242             {
243                 char *const argv[] = { (char *)path, NULL };
244
245                 if (!posix_spawn_file_actions_adddup2 (&actions, comp[0], 0)
246                  && !posix_spawn_file_actions_addclose (&actions, comp[0])
247                  && !posix_spawn_file_actions_adddup2 (&actions, uncomp[1], 1)
248                  && !posix_spawn_file_actions_addclose (&actions, uncomp[1])
249                  && !posix_spawnp (&p_sys->pid, path, &actions, NULL, argv,
250                                    environ))
251                 {
252                     if (vlc_clone (&p_sys->thread, Thread, stream,
253                                    VLC_THREAD_PRIORITY_INPUT) == 0)
254                         ret = VLC_SUCCESS;
255                 }
256                 else
257                 {
258                     msg_Err (stream, "Cannot execute %s", path);
259                     p_sys->pid = -1;
260                 }
261                 posix_spawn_file_actions_destroy (&actions);
262             }
263             close (uncomp[1]);
264             if (ret != VLC_SUCCESS)
265                 close (uncomp[0]);
266         }
267         close (comp[0]);
268         if (ret != VLC_SUCCESS)
269         {
270             close (comp[1]);
271             if (p_sys->pid != -1)
272                 while (waitpid (p_sys->pid, &(int){ 0 }, 0) == -1);
273         }
274     }
275     return ret;
276 }
277
278
279 /**
280  * Releases allocate resources.
281  */
282 static void Close (vlc_object_t *obj)
283 {
284     stream_t *stream = (stream_t *)obj;
285     stream_sys_t *p_sys = stream->p_sys;
286     int status;
287
288     vlc_cancel (p_sys->thread);
289     close (p_sys->read_fd);
290     vlc_join (p_sys->thread, NULL);
291     close (p_sys->write_fd);
292
293     msg_Dbg (obj, "waiting for PID %u", (unsigned)p_sys->pid);
294     while (waitpid (p_sys->pid, &status, 0) == -1);
295     msg_Dbg (obj, "exit status %d", status);
296
297     if (p_sys->peeked)
298         block_Release (p_sys->peeked);
299     free (p_sys);
300 }
301
302
303 /**
304  * Detects gzip file format
305  */
306 static int OpenGzip (vlc_object_t *obj)
307 {
308     stream_t      *stream = (stream_t *)obj;
309     const uint8_t *peek;
310
311     if (stream_Peek (stream->p_source, &peek, 3) < 3)
312         return VLC_EGENERIC;
313
314     if (memcmp (peek, "\x1f\x8b\x08", 3))
315         return VLC_EGENERIC;
316
317     msg_Dbg (obj, "detected gzip compressed stream");
318     return Open (stream, "zcat");
319 }
320
321
322 /**
323  * Detects bzip2 file format
324  */
325 static int OpenBzip2 (vlc_object_t *obj)
326 {
327     stream_t      *stream = (stream_t *)obj;
328     const uint8_t *peek;
329
330     /* (Try to) parse the bzip2 header */
331     if (stream_Peek (stream->p_source, &peek, 10) < 10)
332         return VLC_EGENERIC;
333
334     if (memcmp (peek, "BZh", 3) || (peek[3] < '1') || (peek[3] > '9')
335      || memcmp (peek + 4, "\x31\x41\x59\x26\x53\x59", 6))
336         return VLC_EGENERIC;
337
338     msg_Dbg (obj, "detected bzip2 compressed stream");
339     return Open (stream, "bzcat");
340 }
341