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