514a0aca362b9cb6b357ce367bb9beb8fd588c5c
[vlc.git] / modules / stream_filter / decomp.c
1 /*****************************************************************************
2  * decomp.c : Decompression module for vlc
3  *****************************************************************************
4  * Copyright © 2008-2009 Rémi Denis-Courmont
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * 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 Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * 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 <vlc_fs.h>
30 #include <assert.h>
31 #include <unistd.h>
32 #include <errno.h>
33 #ifndef _POSIX_SPAWN
34 # define _POSIX_SPAWN (-1)
35 #endif
36 #include <fcntl.h>
37 #if (_POSIX_SPAWN >= 0)
38 # include <spawn.h>
39 #endif
40 #include <sys/wait.h>
41 #include <sys/ioctl.h>
42 #if defined (__linux__) && defined (HAVE_VMSPLICE)
43 # include <sys/uio.h>
44 # include <sys/mman.h>
45 #else
46 # undef HAVE_VMSPLICE
47 #endif
48 #include <vlc_interrupt.h>
49
50 #include <signal.h>
51
52 static int  OpenGzip (vlc_object_t *);
53 static int  OpenBzip2 (vlc_object_t *);
54 static int  OpenXZ (vlc_object_t *);
55 static void Close (vlc_object_t *);
56
57 vlc_module_begin ()
58     set_category (CAT_INPUT)
59     set_subcategory (SUBCAT_INPUT_STREAM_FILTER)
60     set_capability ("stream_filter", 20)
61
62     set_description (N_("LZMA decompression"))
63     set_callbacks (OpenXZ, Close)
64
65     add_submodule ()
66     set_description (N_("Burrows-Wheeler decompression"))
67     set_callbacks (OpenBzip2, Close)
68     /* TODO: access shortnames for vlc_stream_NewURL() */
69
70     add_submodule ()
71     set_description (N_("gzip decompression"))
72     set_callbacks (OpenGzip, Close)
73 vlc_module_end ()
74
75 struct stream_sys_t
76 {
77     /* Thread data */
78     int          write_fd;
79
80     /* Shared data */
81     vlc_cond_t   wait;
82     vlc_mutex_t  lock;
83     bool         paused;
84
85     /* Caller data */
86     vlc_thread_t thread;
87     pid_t        pid;
88
89     int          read_fd;
90     bool         can_pace;
91     bool         can_pause;
92     int64_t      pts_delay;
93 };
94
95 extern char **environ;
96
97 static const size_t bufsize = 65536;
98 #ifdef HAVE_VMSPLICE
99 static void cleanup_mmap (void *addr)
100 {
101     munmap (addr, bufsize);
102 }
103 #endif
104
105 static void *Thread (void *data)
106 {
107     stream_t *stream = data;
108     stream_sys_t *p_sys = stream->p_sys;
109 #ifdef HAVE_VMSPLICE
110     const ssize_t page_mask = sysconf (_SC_PAGE_SIZE) - 1;
111 #endif
112     int fd = p_sys->write_fd;
113     bool error = false;
114     sigset_t set;
115
116     sigemptyset(&set);
117     sigaddset(&set, SIGPIPE);
118     pthread_sigmask(SIG_BLOCK, &set, NULL);
119
120     do
121     {
122         ssize_t len;
123         int canc = vlc_savecancel ();
124 #ifdef HAVE_VMSPLICE
125         unsigned char *buf = mmap (NULL, bufsize, PROT_READ|PROT_WRITE,
126                                    MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
127         if (unlikely(buf == MAP_FAILED))
128             break;
129         vlc_cleanup_push (cleanup_mmap, buf);
130 #else
131         unsigned char *buf = malloc (bufsize);
132         if (unlikely(buf == NULL))
133             break;
134         vlc_cleanup_push (free, buf);
135 #endif
136
137         vlc_mutex_lock (&p_sys->lock);
138         while (p_sys->paused) /* practically always false, but... */
139             vlc_cond_wait (&p_sys->wait, &p_sys->lock);
140         len = vlc_stream_Read (stream->s, buf, bufsize);
141         vlc_mutex_unlock (&p_sys->lock);
142
143         vlc_restorecancel (canc);
144         error = len <= 0;
145
146         for (ssize_t i = 0, j; i < len; i += j)
147         {
148 #ifdef HAVE_VMSPLICE
149             if ((len - i) <= page_mask) /* incomplete last page */
150                 j = write (fd, buf + i, len - i);
151             else
152             {
153                 struct iovec iov = {
154                     .iov_base = buf + i,
155                     .iov_len = (len - i) & ~page_mask };
156
157                 j = vmsplice (fd, &iov, 1, SPLICE_F_GIFT);
158             }
159             if (j == -1 && errno == ENOSYS) /* vmsplice() not supported */
160 #endif
161             j = write (fd, buf + i, len - i);
162             if (j <= 0)
163             {
164                 if (j == 0)
165                     errno = EPIPE;
166                 msg_Err (stream, "cannot write data: %s",
167                          vlc_strerror_c(errno));
168                 error = true;
169                 break;
170             }
171         }
172         vlc_cleanup_pop ();
173 #ifdef HAVE_VMSPLICE
174         munmap (buf, bufsize);
175 #else
176         free (buf);
177 #endif
178     }
179     while (!error);
180
181     msg_Dbg (stream, "compressed stream at EOF");
182     /* Let child process know about EOF */
183     p_sys->write_fd = -1;
184     vlc_close (fd);
185     return NULL;
186 }
187
188
189 #define MIN_BLOCK (1 << 10)
190 #define MAX_BLOCK (1 << 20)
191 /**
192  * Reads decompressed from the decompression program
193  * @return -1 for EAGAIN, 0 for EOF, byte count otherwise.
194  */
195 static ssize_t Read (stream_t *stream, void *buf, size_t buflen)
196 {
197     stream_sys_t *sys = stream->p_sys;
198     ssize_t val = vlc_read_i11e (sys->read_fd, buf, buflen);
199     return (val >= 0) ? val : 0;
200 }
201
202 /**
203  *
204  */
205 static int Control (stream_t *stream, int query, va_list args)
206 {
207     stream_sys_t *p_sys = stream->p_sys;
208
209     switch (query)
210     {
211         case STREAM_CAN_SEEK:
212         case STREAM_CAN_FASTSEEK:
213             *(va_arg (args, bool *)) = false;
214             break;
215         case STREAM_CAN_PAUSE:
216              *(va_arg (args, bool *)) = p_sys->can_pause;
217             break;
218         case STREAM_CAN_CONTROL_PACE:
219             *(va_arg (args, bool *)) = p_sys->can_pace;
220             break;
221         case STREAM_GET_SIZE:
222             *(va_arg (args, uint64_t *)) = 0;
223             break;
224         case STREAM_GET_PTS_DELAY:
225             *va_arg (args, int64_t *) = p_sys->pts_delay;
226             break;
227         case STREAM_SET_PAUSE_STATE:
228         {
229             bool paused = va_arg (args, unsigned);
230
231             vlc_mutex_lock (&p_sys->lock);
232             vlc_stream_Control(stream->s, STREAM_SET_PAUSE_STATE, paused);
233             p_sys->paused = paused;
234             vlc_cond_signal (&p_sys->wait);
235             vlc_mutex_unlock (&p_sys->lock);
236             break;
237         }
238         default:
239             return VLC_EGENERIC;
240     }
241     return VLC_SUCCESS;
242 }
243
244 /**
245  * Pipe data through an external executable.
246  * @param stream the stream filter object.
247  * @param path path to the executable.
248  */
249 static int Open (stream_t *stream, const char *path)
250 {
251     stream_sys_t *p_sys = stream->p_sys = malloc (sizeof (*p_sys));
252     if (p_sys == NULL)
253         return VLC_ENOMEM;
254
255     vlc_cond_init (&p_sys->wait);
256     vlc_mutex_init (&p_sys->lock);
257     p_sys->paused = false;
258     p_sys->pid = -1;
259     vlc_stream_Control(stream->s, STREAM_CAN_PAUSE, &p_sys->can_pause);
260     vlc_stream_Control(stream->s, STREAM_CAN_CONTROL_PACE, &p_sys->can_pace);
261     vlc_stream_Control(stream->s, STREAM_GET_PTS_DELAY, &p_sys->pts_delay);
262
263     /* I am not a big fan of the pyramid style, but I cannot think of anything
264      * better here. There are too many failure cases. */
265     int ret = VLC_EGENERIC;
266     int comp[2];
267
268     /* We use two pipes rather than one stream socket pair, so that we can
269      * use vmsplice() on Linux. */
270     if (vlc_pipe (comp) == 0)
271     {
272         p_sys->write_fd = comp[1];
273
274         int uncomp[2];
275         if (vlc_pipe (uncomp) == 0)
276         {
277             p_sys->read_fd = uncomp[0];
278
279 #if (_POSIX_SPAWN >= 0)
280             posix_spawn_file_actions_t actions;
281             if (posix_spawn_file_actions_init (&actions) == 0)
282             {
283                 char *const argv[] = { (char *)path, NULL };
284
285                 if (!posix_spawn_file_actions_adddup2 (&actions, comp[0], 0)
286                  && !posix_spawn_file_actions_adddup2 (&actions, uncomp[1], 1)
287                  && !posix_spawnp (&p_sys->pid, path, &actions, NULL, argv,
288                                    environ))
289                 {
290                     if (vlc_clone (&p_sys->thread, Thread, stream,
291                                    VLC_THREAD_PRIORITY_INPUT) == 0)
292                         ret = VLC_SUCCESS;
293                 }
294                 else
295                 {
296                     msg_Err (stream, "cannot execute %s", path);
297                     p_sys->pid = -1;
298                 }
299                 posix_spawn_file_actions_destroy (&actions);
300             }
301 #else /* _POSIX_SPAWN */
302             switch (p_sys->pid = fork ())
303             {
304                 case -1:
305                     msg_Err (stream, "cannot fork: %s", vlc_strerror_c(errno));
306                     break;
307                 case 0:
308                     dup2 (comp[0], 0);
309                     dup2 (uncomp[1], 1);
310                     execlp (path, path, (const char *)NULL);
311                     exit (1); /* if we get, execlp() failed! */
312                 default:
313                     if (vlc_clone (&p_sys->thread, Thread, stream,
314                                    VLC_THREAD_PRIORITY_INPUT) == 0)
315                         ret = VLC_SUCCESS;
316             }
317 #endif /* _POSIX_SPAWN < 0 */
318             vlc_close (uncomp[1]);
319             if (ret != VLC_SUCCESS)
320                 vlc_close (uncomp[0]);
321         }
322         vlc_close (comp[0]);
323         if (ret != VLC_SUCCESS)
324             vlc_close (comp[1]);
325     }
326
327     if (ret != VLC_SUCCESS)
328     {
329         if (p_sys->pid != -1)
330             while (waitpid (p_sys->pid, &(int){ 0 }, 0) == -1);
331         vlc_mutex_destroy (&p_sys->lock);
332         vlc_cond_destroy (&p_sys->wait);
333         free (p_sys);
334         return ret;
335     }
336
337     stream->pf_read = Read;
338     stream->pf_seek = NULL;
339     stream->pf_control = Control;
340     return VLC_SUCCESS;
341 }
342
343
344 /**
345  * Releases allocate resources.
346  */
347 static void Close (vlc_object_t *obj)
348 {
349     stream_t *stream = (stream_t *)obj;
350     stream_sys_t *p_sys = stream->p_sys;
351     int status;
352
353     vlc_cancel (p_sys->thread);
354     vlc_close (p_sys->read_fd);
355     vlc_join (p_sys->thread, NULL);
356     if (p_sys->write_fd != -1)
357         /* Killed before EOF? */
358         vlc_close (p_sys->write_fd);
359
360     msg_Dbg (obj, "waiting for PID %u", (unsigned)p_sys->pid);
361     while (waitpid (p_sys->pid, &status, 0) == -1);
362     msg_Dbg (obj, "exit status %d", status);
363
364     vlc_mutex_destroy (&p_sys->lock);
365     vlc_cond_destroy (&p_sys->wait);
366     free (p_sys);
367 }
368
369
370 /**
371  * Detects gzip file format
372  */
373 static int OpenGzip (vlc_object_t *obj)
374 {
375     stream_t      *stream = (stream_t *)obj;
376     const uint8_t *peek;
377
378     if (vlc_stream_Peek (stream->s, &peek, 3) < 3)
379         return VLC_EGENERIC;
380
381     if (memcmp (peek, "\x1f\x8b\x08", 3))
382         return VLC_EGENERIC;
383
384     msg_Dbg (obj, "detected gzip compressed stream");
385     return Open (stream, "zcat");
386 }
387
388
389 /**
390  * Detects bzip2 file format
391  */
392 static int OpenBzip2 (vlc_object_t *obj)
393 {
394     stream_t      *stream = (stream_t *)obj;
395     const uint8_t *peek;
396
397     /* (Try to) parse the bzip2 header */
398     if (vlc_stream_Peek (stream->s, &peek, 10) < 10)
399         return VLC_EGENERIC;
400
401     if (memcmp (peek, "BZh", 3) || (peek[3] < '1') || (peek[3] > '9')
402      || memcmp (peek + 4, "\x31\x41\x59\x26\x53\x59", 6))
403         return VLC_EGENERIC;
404
405     msg_Dbg (obj, "detected bzip2 compressed stream");
406     return Open (stream, "bzcat");
407 }
408
409 /**
410  * Detects xz file format
411  */
412 static int OpenXZ (vlc_object_t *obj)
413 {
414     stream_t      *stream = (stream_t *)obj;
415     const uint8_t *peek;
416
417     /* (Try to) parse the xz stream header */
418     if (vlc_stream_Peek (stream->s, &peek, 8) < 8)
419         return VLC_EGENERIC;
420
421     if (memcmp (peek, "\xfd\x37\x7a\x58\x5a", 6))
422         return VLC_EGENERIC;
423
424     msg_Dbg (obj, "detected xz compressed stream");
425     return Open (stream, "xzcat");
426 }