1 /*****************************************************************************
2 * mmap.c: memory-mapped file input
3 *****************************************************************************
4 * Copyright © 2007-2008 Rémi Denis-Courmont
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
20 *****************************************************************************/
26 #include <vlc_common.h>
27 #include <vlc_plugin.h>
28 #include <vlc_access.h>
29 #include <vlc_input.h>
31 #include <vlc_dialog.h>
35 #include <sys/types.h>
41 #define FILE_MMAP_TEXT N_("Use file memory mapping")
42 #define FILE_MMAP_LONGTEXT N_( \
43 "Try to use memory mapping to read files and block devices." )
46 /*# define MMAP_DEBUG 1*/
49 static int Open (vlc_object_t
*);
50 static void Close (vlc_object_t
*);
53 set_shortname (N_("MMap"))
54 set_description (N_("Memory-mapped file input"))
55 set_category (CAT_INPUT
)
56 set_subcategory (SUBCAT_INPUT_ACCESS
)
57 set_capability ("access", 52)
59 set_callbacks (Open
, Close
)
61 add_bool ("file-mmap", true, NULL
,
62 FILE_MMAP_TEXT
, FILE_MMAP_LONGTEXT
, true)
64 add_bool ("file-mmap", false, NULL
,
65 FILE_MMAP_TEXT
, FILE_MMAP_LONGTEXT
, true)
69 static block_t
*Block (access_t
*);
70 static int Seek (access_t
*, uint64_t);
71 static int Control (access_t
*, int, va_list);
80 #define MMAP_SIZE (1 << 20)
82 static int Open (vlc_object_t
*p_this
)
84 access_t
*p_access
= (access_t
*)p_this
;
86 const char *path
= p_access
->psz_filepath
;
89 assert ((INT64_C(1) << 63) == ((off_t
)(INT64_C(1) << 63)));
91 if (!var_InheritBool (p_this
, "file-mmap"))
92 return VLC_EGENERIC
; /* disabled */
94 STANDARD_BLOCK_ACCESS_INIT
;
96 msg_Dbg (p_access
, "opening file %s", path
);
97 fd
= vlc_open (path
, O_RDONLY
| O_NOCTTY
);
101 msg_Warn (p_access
, "cannot open %s: %m", path
);
105 /* mmap() is only safe for regular and block special files.
106 * For other types, it may be some idiosyncrasic interface (e.g. packet
107 * sockets are a good example), if it works at all. */
112 msg_Err (p_access
, "cannot stat %s: %m", path
);
116 if (!S_ISREG (st
.st_mode
) && !S_ISBLK (st
.st_mode
))
118 msg_Dbg (p_access
, "skipping non-regular file %s", path
);
122 #if defined(HAVE_FCNTL_H)
123 /* We'd rather use any available memory for reading ahead
124 * than for caching what we've already mmap'ed */
125 # if defined(F_RDAHEAD)
126 fcntl (fd
, F_RDAHEAD
, 1);
128 # if defined(F_NOCACHE)
129 fcntl (fd
, F_NOCACHE
, 1);
133 /* Autodetect mmap() support */
136 void *addr
= mmap (NULL
, 1, PROT_READ
|PROT_WRITE
, MAP_PRIVATE
, fd
, 0);
137 if (addr
!= MAP_FAILED
)
143 p_sys
->page_size
= sysconf (_SC_PAGE_SIZE
);
144 p_sys
->mtu
= MMAP_SIZE
;
145 if (p_sys
->mtu
< p_sys
->page_size
)
146 p_sys
->mtu
= p_sys
->page_size
;
149 p_access
->info
.i_size
= st
.st_size
;
150 #ifdef HAVE_POSIX_FADVISE
151 posix_fadvise (fd
, 0, 0, POSIX_FADV_SEQUENTIAL
);
164 static void Close (vlc_object_t
* p_this
)
166 access_t
*p_access
= (access_t
*)p_this
;
167 access_sys_t
*p_sys
= p_access
->p_sys
;
169 close (p_sys
->fd
); /* don't care about error when only reading */
173 static block_t
*Block (access_t
*p_access
)
175 access_sys_t
*p_sys
= p_access
->p_sys
;
177 /* Check if file size changed... */
180 if ((fstat (p_sys
->fd
, &st
) == 0)
181 && (st
.st_size
!= p_access
->info
.i_size
))
183 p_access
->info
.i_size
= st
.st_size
;
184 p_access
->info
.i_update
|= INPUT_UPDATE_SIZE
;
187 if (p_access
->info
.i_pos
>= p_access
->info
.i_size
)
189 /* We are at end of file */
190 p_access
->info
.b_eof
= true;
191 msg_Dbg (p_access
, "at end of memory mapped file");
196 uint64_t dbgpos
= lseek (p_sys
->fd
, 0, SEEK_CUR
);
197 if (dbgpos
!= p_access
->info
.i_pos
)
198 msg_Err (p_access
, "position: 0x%016"PRIx64
" instead of 0x%016"PRIx64
,
199 p_access
->info
.i_pos
, dbgpos
);
202 const uintptr_t page_mask
= p_sys
->page_size
- 1;
203 /* Start the mapping on a page boundary: */
204 off_t outer_offset
= p_access
->info
.i_pos
& ~(off_t
)page_mask
;
205 /* Skip useless bytes at the beginning of the first page: */
206 size_t inner_offset
= p_access
->info
.i_pos
& page_mask
;
207 /* Map no more bytes than remain: */
208 size_t length
= p_sys
->mtu
;
209 if (outer_offset
+ length
> p_access
->info
.i_size
)
210 length
= p_access
->info
.i_size
- outer_offset
;
212 assert (outer_offset
<= p_access
->info
.i_pos
); /* and */
213 assert (p_access
->info
.i_pos
< p_access
->info
.i_size
); /* imply */
214 assert (outer_offset
< p_access
->info
.i_size
); /* imply */
217 /* NOTE: We use PROT_WRITE and MAP_PRIVATE so that the block can be
218 * modified down the chain, without messing up with the underlying
219 * original file. This does NOT need open write permission. */
220 void *addr
= mmap (NULL
, length
, PROT_READ
|PROT_WRITE
, MAP_PRIVATE
224 , p_sys
->fd
, outer_offset
);
225 if (addr
== MAP_FAILED
)
227 msg_Err (p_access
, "memory mapping failed (%m)");
228 dialog_Fatal (p_access
, _("File reading failed"), "%s",
229 _("VLC could not read the file."));
232 #ifdef HAVE_POSIX_MADVISE
233 posix_madvise (addr
, length
, POSIX_MADV_SEQUENTIAL
);
236 block_t
*block
= block_mmap_Alloc (addr
, length
);
240 block
->p_buffer
+= inner_offset
;
241 block
->i_buffer
-= inner_offset
;
244 msg_Dbg (p_access
, "mapped 0x%zx bytes at %p from offset 0x%"PRIx64
,
245 length
, addr
, (uint64_t)outer_offset
);
247 /* Compare normal I/O with memory mapping */
248 char *buf
= malloc (block
->i_buffer
);
249 ssize_t i_read
= read (p_sys
->fd
, buf
, block
->i_buffer
);
251 if (i_read
!= (ssize_t
)block
->i_buffer
)
252 msg_Err (p_access
, "read %zd instead of %zu bytes", i_read
,
254 if (memcmp (buf
, block
->p_buffer
, block
->i_buffer
))
255 msg_Err (p_access
, "inconsistent data buffer");
259 p_access
->info
.i_pos
= outer_offset
+ length
;
263 p_access
->info
.b_eof
= true;
268 static int Seek (access_t
*p_access
, uint64_t i_pos
)
271 lseek (p_access
->p_sys
->fd
, i_pos
, SEEK_SET
);
274 p_access
->info
.i_pos
= i_pos
;
275 p_access
->info
.b_eof
= false;
280 static int Control (access_t
*p_access
, int query
, va_list args
)
284 case ACCESS_CAN_SEEK
:
285 case ACCESS_CAN_FASTSEEK
:
286 case ACCESS_CAN_PAUSE
:
287 case ACCESS_CAN_CONTROL_PACE
:
288 *va_arg(args
, bool *) = true;
291 case ACCESS_GET_PTS_DELAY
:
293 int64_t delay_ms
= var_CreateGetInteger (p_access
, "file-caching");
294 *va_arg(args
, int64_t *) = delay_ms
* INT64_C (1000);
298 case ACCESS_GET_TITLE_INFO
:
299 case ACCESS_GET_META
:
302 case ACCESS_SET_PAUSE_STATE
:
305 case ACCESS_SET_TITLE
:
306 case ACCESS_SET_SEEKPOINT
:
307 case ACCESS_SET_PRIVATE_ID_STATE
:
308 case ACCESS_SET_PRIVATE_ID_CA
:
309 case ACCESS_GET_PRIVATE_ID_STATE
:
310 case ACCESS_GET_CONTENT_TYPE
:
314 msg_Warn (p_access
, "unimplemented query %d in control", query
);