]> git.sesse.net Git - vlc/blob - modules/stream_filter/decomp.c
decomp: implement minimal controls
[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     uint64_t     offset;
61     vlc_thread_t thread;
62     pid_t        pid;
63     int          write_fd, read_fd;
64 };
65
66 static void cloexec (int fd)
67 {
68     int flags = fcntl (fd, F_GETFD);
69     fcntl (fd, F_SETFD, FD_CLOEXEC | ((flags != -1) ? flags : 0));
70 }
71
72 extern char **environ;
73
74 static const size_t bufsize = 65536;
75 static void cleanup_mmap (void *addr)
76 {
77     munmap (addr, bufsize);
78 }
79
80
81 static void *Thread (void *data)
82 {
83     stream_t *stream = data;
84     stream_sys_t *p_sys = stream->p_sys;
85     int fd = p_sys->write_fd;
86     bool error = false;
87
88     do
89     {
90         ssize_t len;
91         int canc = vlc_savecancel ();
92 #ifdef __linux__
93         unsigned char *buf = mmap (NULL, bufsize, PROT_READ|PROT_WRITE,
94                                    MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
95         vlc_cleanup_push (cleanup_mmap, buf);
96 #else
97         unsigned char buf[bufsize];
98 #endif
99
100         len = stream_Read (stream->p_source, buf, bufsize);
101         vlc_restorecancel (canc);
102
103         if (len <= 0)
104             break;
105
106         for (ssize_t i = 0, j = 0; i < len; i += j)
107         {
108             struct iovec iov[1] = { { buf + i, len - i, } };
109
110 #ifdef __linux__
111             j = vmsplice (fd, iov, 1, SPLICE_F_GIFT);
112 #else
113             j = writev (fd, iov, 1);
114 #endif
115             if (j <= 0)
116             {
117                 if (j == 0)
118                     errno = EPIPE;
119                 msg_Err (stream, "cannot write data (%m)");
120                 error = true;
121                 break;
122             }
123         }
124 #ifdef __linux__
125         vlc_cleanup_run (); /* munmap (buf, bufsize) */
126 #endif
127     }
128     while (!error);
129
130     msg_Dbg (stream, "compressed stream at EOF");
131     return NULL;
132 }
133
134
135 #define MIN_BLOCK (1 << 10)
136 #define MAX_BLOCK (1 << 20)
137 /**
138  * Reads decompressed from the decompression program
139  * @return -1 for EAGAIN, 0 for EOF, byte count otherwise.
140  */
141 static int Read (stream_t *stream, void *buf, unsigned int buflen)
142 {
143     stream_sys_t *p_sys = stream->p_sys;
144     block_t *peeked;
145     size_t bonus = 0;
146     ssize_t length;
147
148     if ((peeked = p_sys->peeked) != NULL)
149     {
150         bonus = (buflen > peeked->i_buffer) ? peeked->i_buffer : buflen;
151         memcpy (buf, peeked->p_buffer, bonus);
152         peeked->p_buffer += bonus;
153         peeked->i_buffer -= bonus;
154         if (peeked->i_buffer == 0)
155         {
156             block_Release (peeked);
157             p_sys->peeked = NULL;
158         }
159     }
160
161     length = net_Read (stream, p_sys->read_fd, NULL, buf, buflen, false);
162     if (length < 0)
163         return 0;
164     length += bonus;
165     p_sys->offset += length;
166     return length;
167 }
168
169 /**
170  *
171  */
172 static int Peek (stream_t *stream, const uint8_t **pbuf, unsigned int len)
173 {
174     stream_sys_t *p_sys = stream->p_sys;
175     block_t *peeked = p_sys->peeked;
176     size_t curlen = 0;
177     int fd = p_sys->read_fd;
178
179     if (peeked == NULL)
180         peeked = block_Alloc (len);
181     else if ((curlen = peeked->i_buffer) < len)
182         peeked = block_Realloc (peeked, 0, len);
183
184     if ((p_sys->peeked = peeked) == NULL)
185         return 0;
186
187     if (curlen < len)
188     {
189         ssize_t val = net_Read (stream, fd, NULL, peeked->p_buffer + curlen,
190                                 len - curlen, true);
191         if (val >= 0)
192         {
193             curlen += val;
194             peeked->i_buffer = curlen;
195         }
196     }
197     *pbuf = peeked->p_buffer;
198     return curlen;
199 }
200
201 /**
202  *
203  */
204 static int Control (stream_t *stream, int query, va_list args)
205 {
206     stream_sys_t *p_sys = stream->p_sys;
207
208     switch (query)
209     {
210         case STREAM_CAN_SEEK:
211         case STREAM_CAN_FASTSEEK:
212             *(va_arg (args, bool *)) = false;
213             break;
214         case STREAM_GET_POSITION:
215             *(va_arg (args, int64_t *)) = p_sys->offset;
216             break;
217         case STREAM_GET_SIZE:
218             *(va_arg (args, int64_t *)) = 0;
219             break;
220         case STREAM_GET_MTU:
221             *(va_arg (args, int *)) = 0;
222             break;
223         default:
224             return VLC_EGENERIC;
225     }
226     return VLC_SUCCESS;
227 }
228
229 /**
230  * Pipe data through an external executable.
231  * @param stream the stream filter object.
232  * @param path path to the executable.
233  */
234 static int Open (stream_t *stream, const char *path)
235 {
236     stream_sys_t *p_sys = stream->p_sys = malloc (sizeof (*p_sys));
237     if (p_sys == NULL)
238         return VLC_ENOMEM;
239
240     stream->pf_read = Read;
241     stream->pf_peek = Peek;
242     stream->pf_control = Control;
243     p_sys->peeked = NULL;
244     p_sys->offset = 0;
245     p_sys->pid = -1;
246
247     /* I am not a big fan of the pyramid style, but I cannot think of anything
248      * better here. There are too many failure cases. */
249     int ret = VLC_EGENERIC;
250     int comp[2];
251
252     if (pipe (comp) == 0)
253     {
254         cloexec (comp[1]);
255         p_sys->write_fd = comp[1];
256
257         int uncomp[2];
258         if (pipe (uncomp) == 0)
259         {
260             cloexec (uncomp[0]);
261             p_sys->read_fd = uncomp[0];
262
263             posix_spawn_file_actions_t actions;
264             if (posix_spawn_file_actions_init (&actions) == 0)
265             {
266                 char *const argv[] = { (char *)path, NULL };
267
268                 if (!posix_spawn_file_actions_adddup2 (&actions, comp[0], 0)
269                  && !posix_spawn_file_actions_addclose (&actions, comp[0])
270                  && !posix_spawn_file_actions_adddup2 (&actions, uncomp[1], 1)
271                  && !posix_spawn_file_actions_addclose (&actions, uncomp[1])
272                  && !posix_spawnp (&p_sys->pid, path, &actions, NULL, argv,
273                                    environ))
274                 {
275                     if (vlc_clone (&p_sys->thread, Thread, stream,
276                                    VLC_THREAD_PRIORITY_INPUT) == 0)
277                         ret = VLC_SUCCESS;
278                 }
279                 else
280                 {
281                     msg_Err (stream, "Cannot execute %s", path);
282                     p_sys->pid = -1;
283                 }
284                 posix_spawn_file_actions_destroy (&actions);
285             }
286             close (uncomp[1]);
287             if (ret != VLC_SUCCESS)
288                 close (uncomp[0]);
289         }
290         close (comp[0]);
291         if (ret != VLC_SUCCESS)
292         {
293             close (comp[1]);
294             if (p_sys->pid != -1)
295                 while (waitpid (p_sys->pid, &(int){ 0 }, 0) == -1);
296         }
297     }
298     return ret;
299 }
300
301
302 /**
303  * Releases allocate resources.
304  */
305 static void Close (vlc_object_t *obj)
306 {
307     stream_t *stream = (stream_t *)obj;
308     stream_sys_t *p_sys = stream->p_sys;
309     int status;
310
311     vlc_cancel (p_sys->thread);
312     close (p_sys->read_fd);
313     vlc_join (p_sys->thread, NULL);
314     close (p_sys->write_fd);
315
316     msg_Dbg (obj, "waiting for PID %u", (unsigned)p_sys->pid);
317     while (waitpid (p_sys->pid, &status, 0) == -1);
318     msg_Dbg (obj, "exit status %d", status);
319
320     if (p_sys->peeked)
321         block_Release (p_sys->peeked);
322     free (p_sys);
323 }
324
325
326 /**
327  * Detects gzip file format
328  */
329 static int OpenGzip (vlc_object_t *obj)
330 {
331     stream_t      *stream = (stream_t *)obj;
332     const uint8_t *peek;
333
334     if (stream_Peek (stream->p_source, &peek, 3) < 3)
335         return VLC_EGENERIC;
336
337     if (memcmp (peek, "\x1f\x8b\x08", 3))
338         return VLC_EGENERIC;
339
340     msg_Dbg (obj, "detected gzip compressed stream");
341     return Open (stream, "zcat");
342 }
343
344
345 /**
346  * Detects bzip2 file format
347  */
348 static int OpenBzip2 (vlc_object_t *obj)
349 {
350     stream_t      *stream = (stream_t *)obj;
351     const uint8_t *peek;
352
353     /* (Try to) parse the bzip2 header */
354     if (stream_Peek (stream->p_source, &peek, 10) < 10)
355         return VLC_EGENERIC;
356
357     if (memcmp (peek, "BZh", 3) || (peek[3] < '1') || (peek[3] > '9')
358      || memcmp (peek + 4, "\x31\x41\x59\x26\x53\x59", 6))
359         return VLC_EGENERIC;
360
361     msg_Dbg (obj, "detected bzip2 compressed stream");
362     return Open (stream, "bzcat");
363 }
364