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