access_output: allow pf_seek to be NULL, simplify
[vlc.git] / modules / access_output / file.c
1 /*****************************************************************************
2  * file.c
3  *****************************************************************************
4  * Copyright (C) 2001, 2002 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Laurent Aimar <fenrir@via.ecp.fr>
8  *          Eric Petit <titer@videolan.org>
9  *
10  * This program is free software; you can redistribute it and/or modify it
11  * under the terms of the GNU Lesser General Public License as published by
12  * the Free Software Foundation; either version 2.1 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public License
21  * along with this program; if not, write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28
29 #ifdef HAVE_CONFIG_H
30 # include "config.h"
31 #endif
32
33 #include <assert.h>
34 #include <signal.h>
35 #include <sys/types.h>
36 #include <fcntl.h>
37 #include <errno.h>
38 #include <sys/stat.h>
39 #include <unistd.h>
40 #ifdef __OS2__
41 #   include <io.h>      /* setmode() */
42 #endif
43
44 #include <vlc_common.h>
45 #include <vlc_plugin.h>
46 #include <vlc_sout.h>
47 #include <vlc_block.h>
48 #include <vlc_fs.h>
49 #include <vlc_network.h>
50 #include <vlc_strings.h>
51 #include <vlc_dialog.h>
52
53 #ifndef O_LARGEFILE
54 #   define O_LARGEFILE 0
55 #endif
56 #ifndef _POSIX_REALTIME_SIGNALS
57 # define _POSIX_REALTIME_SIGNALS (-1)
58 #endif
59
60 #define SOUT_CFG_PREFIX "sout-file-"
61
62 /*****************************************************************************
63  * Read: standard read on a file descriptor.
64  *****************************************************************************/
65 static ssize_t Read( sout_access_out_t *p_access, block_t *p_buffer )
66 {
67     ssize_t val;
68
69     do
70         val = read( (intptr_t)p_access->p_sys, p_buffer->p_buffer,
71                     p_buffer->i_buffer );
72     while (val == -1 && errno == EINTR);
73     return val;
74 }
75
76 /*****************************************************************************
77  * Write: standard write on a file descriptor.
78  *****************************************************************************/
79 static ssize_t Write( sout_access_out_t *p_access, block_t *p_buffer )
80 {
81     size_t i_write = 0;
82
83     while( p_buffer )
84     {
85         ssize_t val = write ((intptr_t)p_access->p_sys,
86                              p_buffer->p_buffer, p_buffer->i_buffer);
87         if (val <= 0)
88         {
89             if (errno == EINTR)
90                 continue;
91             block_ChainRelease (p_buffer);
92             msg_Err( p_access, "cannot write: %s", vlc_strerror_c(errno) );
93             return -1;
94         }
95
96         if ((size_t)val >= p_buffer->i_buffer)
97         {
98             block_t *p_next = p_buffer->p_next;
99             block_Release (p_buffer);
100             p_buffer = p_next;
101         }
102         else
103         {
104             p_buffer->p_buffer += val;
105             p_buffer->i_buffer -= val;
106         }
107         i_write += val;
108     }
109     return i_write;
110 }
111
112 static ssize_t WritePipe(sout_access_out_t *access, block_t *block)
113 {
114     int fd = (intptr_t)access->p_sys;
115     ssize_t total = 0;
116
117     while (block != NULL)
118     {
119         if (block->i_buffer == 0)
120         {
121             block_t *next = block->p_next;
122             block_Release(block);
123             block = next;
124             continue;
125         }
126
127         /* TODO: vectorized I/O with writev() */
128         ssize_t val = vlc_write(fd, block->p_buffer, block->i_buffer);
129         if (val < 0)
130         {
131             if (errno == EINTR)
132                 continue;
133
134             block_ChainRelease(block);
135             msg_Err(access, "cannot write: %s", vlc_strerror_c(errno));
136             total = -1;
137             break;
138         }
139
140         total += val;
141
142         assert((size_t)val <= block->i_buffer);
143         block->p_buffer += val;
144         block->i_buffer -= val;
145     }
146
147     return total;
148 }
149
150 #ifdef S_ISSOCK
151 static ssize_t Send(sout_access_out_t *access, block_t *block)
152 {
153     int fd = (intptr_t)access->p_sys;
154     size_t total = 0;
155
156     while (block != NULL)
157     {
158         if (block->i_buffer == 0)
159         {
160             block_t *next = block->p_next;
161             block_Release(block);
162             block = next;
163             continue;
164         }
165
166         /* TODO: vectorized I/O with sendmsg() */
167         ssize_t val = send(fd, block->p_buffer, block->i_buffer, MSG_NOSIGNAL);
168         if (val <= 0)
169         {   /* FIXME: errno is meaningless if val is zero */
170             if (errno == EINTR)
171                 continue;
172             block_ChainRelease(block);
173             msg_Err(access, "cannot write: %s", vlc_strerror_c(errno));
174             return -1;
175         }
176
177         total += val;
178
179         assert((size_t)val <= block->i_buffer);
180         block->p_buffer += val;
181         block->i_buffer -= val;
182     }
183     return total;
184 }
185 #endif
186
187 /*****************************************************************************
188  * Seek: seek to a specific location in a file
189  *****************************************************************************/
190 static int Seek( sout_access_out_t *p_access, off_t i_pos )
191 {
192     return lseek( (intptr_t)p_access->p_sys, i_pos, SEEK_SET );
193 }
194
195 static int Control( sout_access_out_t *p_access, int i_query, va_list args )
196 {
197     switch( i_query )
198     {
199         case ACCESS_OUT_CONTROLS_PACE:
200         {
201             bool *pb = va_arg( args, bool * );
202             *pb = strcmp( p_access->psz_access, "stream" );
203             break;
204         }
205
206         case ACCESS_OUT_CAN_SEEK:
207         {
208             bool *pb = va_arg( args, bool * );
209             *pb = p_access->pf_seek != NULL;
210             break;
211         }
212
213         default:
214             return VLC_EGENERIC;
215     }
216     return VLC_SUCCESS;
217 }
218
219 static const char *const ppsz_sout_options[] = {
220     "append",
221     "format",
222     "overwrite",
223 #ifdef O_SYNC
224     "sync",
225 #endif
226     NULL
227 };
228
229 /*****************************************************************************
230  * Open: open the file
231  *****************************************************************************/
232 static int Open( vlc_object_t *p_this )
233 {
234     sout_access_out_t   *p_access = (sout_access_out_t*)p_this;
235     int                 fd;
236
237     config_ChainParse( p_access, SOUT_CFG_PREFIX, ppsz_sout_options, p_access->p_cfg );
238
239     if( !p_access->psz_path )
240     {
241         msg_Err( p_access, "no file name specified" );
242         return VLC_EGENERIC;
243     }
244
245     bool overwrite = var_GetBool (p_access, SOUT_CFG_PREFIX"overwrite");
246     bool append = var_GetBool( p_access, SOUT_CFG_PREFIX "append" );
247
248     if (!strcmp (p_access->psz_access, "fd"))
249     {
250         char *end;
251
252         fd = strtol (p_access->psz_path, &end, 0);
253         if (!*p_access->psz_path || *end)
254         {
255             msg_Err (p_access, "invalid file descriptor: %s",
256                      p_access->psz_path);
257             return VLC_EGENERIC;
258         }
259         fd = vlc_dup (fd);
260         if (fd == -1)
261         {
262             msg_Err (p_access, "cannot use file descriptor: %s",
263                      vlc_strerror_c(errno));
264             return VLC_EGENERIC;
265         }
266     }
267     else
268     if( !strcmp( p_access->psz_path, "-" ) )
269     {
270 #if defined( _WIN32 ) || defined( __OS2__ )
271         setmode (STDOUT_FILENO, O_BINARY);
272 #endif
273         fd = vlc_dup (STDOUT_FILENO);
274         if (fd == -1)
275         {
276             msg_Err (p_access, "cannot use standard output: %s",
277                      vlc_strerror_c(errno));
278             return VLC_EGENERIC;
279         }
280         msg_Dbg( p_access, "using stdout" );
281     }
282     else
283     {
284         const char *path = p_access->psz_path;
285         char *buf = NULL;
286
287         if (var_InheritBool (p_access, SOUT_CFG_PREFIX"format"))
288         {
289             buf = vlc_strftime (path);
290             path = buf;
291         }
292
293         int flags = O_RDWR | O_CREAT | O_LARGEFILE;
294         if (!overwrite)
295             flags |= O_EXCL;
296         if (!append)
297             flags |= O_TRUNC;
298 #ifdef O_SYNC
299         if (var_GetBool (p_access, SOUT_CFG_PREFIX"sync"))
300             flags |= O_SYNC;
301 #endif
302         do
303         {
304             fd = vlc_open (path, flags, 0666);
305             if (fd != -1)
306                 break;
307             if (fd == -1)
308                 msg_Err (p_access, "cannot create %s: %s", path,
309                          vlc_strerror_c(errno));
310             if (overwrite || errno != EEXIST)
311                 break;
312             flags &= ~O_EXCL;
313         }
314         while (vlc_dialog_wait_question (p_access, VLC_DIALOG_QUESTION_NORMAL,
315                                          _("Keep existing file"),
316                                          _("Overwrite"), NULL, path,
317                                          _("The output file already exists. "
318                                          "If recording continues, the file will be "
319                                          "overridden and its content will be lost.")) == 1);
320         free (buf);
321         if (fd == -1)
322             return VLC_EGENERIC;
323     }
324
325     struct stat st;
326
327     if (fstat (fd, &st))
328     {
329         msg_Err (p_access, "write error: %s", vlc_strerror_c(errno));
330         vlc_close (fd);
331         return VLC_EGENERIC;
332     }
333
334     p_access->pf_read  = Read;
335
336     if (S_ISREG(st.st_mode) || S_ISBLK(st.st_mode))
337     {
338         p_access->pf_write = Write;
339         p_access->pf_seek  = Seek;
340     }
341 #ifdef S_ISSOCK
342     else if (S_ISSOCK(st.st_mode))
343     {
344         p_access->pf_write = Send;
345         p_access->pf_seek = NULL;
346     }
347 #endif
348     else
349     {
350         p_access->pf_write = WritePipe;
351         p_access->pf_seek = NULL;
352     }
353     p_access->pf_control = Control;
354     p_access->p_sys    = (void *)(intptr_t)fd;
355
356     msg_Dbg( p_access, "file access output opened (%s)", p_access->psz_path );
357     if (append)
358         lseek (fd, 0, SEEK_END);
359
360     return VLC_SUCCESS;
361 }
362
363 /*****************************************************************************
364  * Close: close the target
365  *****************************************************************************/
366 static void Close( vlc_object_t * p_this )
367 {
368     sout_access_out_t *p_access = (sout_access_out_t*)p_this;
369
370     vlc_close( (intptr_t)p_access->p_sys );
371
372     msg_Dbg( p_access, "file access output closed" );
373 }
374
375 #define OVERWRITE_TEXT N_("Overwrite existing file")
376 #define OVERWRITE_LONGTEXT N_( \
377     "If the file already exists, it will be overwritten.")
378 #define APPEND_TEXT N_("Append to file")
379 #define APPEND_LONGTEXT N_( "Append to file if it exists instead " \
380                             "of replacing it.")
381 #define FORMAT_TEXT N_("Format time and date")
382 #define FORMAT_LONGTEXT N_("Perform ISO C time and date formatting " \
383     "on the file path")
384 #define SYNC_TEXT N_("Synchronous writing")
385 #define SYNC_LONGTEXT N_( "Open the file with synchronous writing.")
386
387 vlc_module_begin ()
388     set_description( N_("File stream output") )
389     set_shortname( N_("File" ))
390     set_capability( "sout access", 50 )
391     set_category( CAT_SOUT )
392     set_subcategory( SUBCAT_SOUT_ACO )
393     add_shortcut( "file", "stream", "fd" )
394     add_bool( SOUT_CFG_PREFIX "overwrite", true, OVERWRITE_TEXT,
395               OVERWRITE_LONGTEXT, true )
396     add_bool( SOUT_CFG_PREFIX "append", false, APPEND_TEXT,APPEND_LONGTEXT,
397               true )
398     add_bool( SOUT_CFG_PREFIX "format", false, FORMAT_TEXT, FORMAT_LONGTEXT,
399               true )
400 #ifdef O_SYNC
401     add_bool( SOUT_CFG_PREFIX "sync", false, SYNC_TEXT,SYNC_LONGTEXT,
402               false )
403 #endif
404     set_callbacks( Open, Close )
405 vlc_module_end ()