1 /*****************************************************************************
2  * block.c: Data blocks management functions
3  *****************************************************************************
4  * Copyright (C) 2003-2004 VLC authors and VideoLAN
5  * Copyright (C) 2007-2009 Rémi Denis-Courmont
6  *
7  * Authors: Laurent Aimar <fenrir@videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software Foundation,
21  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23 
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
27 
28 #include <sys/stat.h>
29 #include <assert.h>
30 #include <errno.h>
31 #include <unistd.h>
32 #include <fcntl.h>
33 
34 #include <vlc_common.h>
35 #include <vlc_block.h>
36 #include <vlc_fs.h>
37 
38 #ifndef NDEBUG
BlockNoRelease(block_t * b)39 static void BlockNoRelease( block_t *b )
40 {
41     fprintf( stderr, "block %p has no release callback! This is a bug!\n",
42              (void *) b );
43     abort();
44 }
45 
block_Check(block_t * block)46 static void block_Check (block_t *block)
47 {
48     while (block != NULL)
49     {
50         unsigned char *start = block->p_start;
51         unsigned char *end = block->p_start + block->i_size;
52         unsigned char *bufstart = block->p_buffer;
53         unsigned char *bufend = block->p_buffer + block->i_buffer;
54 
55         assert (block->pf_release != BlockNoRelease);
56         assert (start <= end);
57         assert (bufstart <= bufend);
58         assert (bufstart >= start);
59         assert (bufend <= end);
60 
61         block = block->p_next;
62     }
63 }
64 
block_Invalidate(block_t * block)65 static void block_Invalidate (block_t *block)
66 {
67     block->p_next = NULL;
68     block_Check (block);
69     block->pf_release = BlockNoRelease;
70 }
71 #else
72 # define block_Check(b) ((void)(b))
73 # define block_Invalidate(b) ((void)(b))
74 #endif
75 
block_Init(block_t * restrict b,void * buf,size_t size)76 void block_Init( block_t *restrict b, void *buf, size_t size )
77 {
78     /* Fill all fields to their default */
79     b->p_next = NULL;
80     b->p_buffer = buf;
81     b->i_buffer = size;
82     b->p_start = buf;
83     b->i_size = size;
84     b->i_flags = 0;
85     b->i_nb_samples = 0;
86     b->i_pts =
87     b->i_dts = VLC_TS_INVALID;
88     b->i_length = 0;
89 #ifndef NDEBUG
90     b->pf_release = BlockNoRelease;
91 #endif
92 }
93 
block_generic_Release(block_t * block)94 static void block_generic_Release (block_t *block)
95 {
96     /* That is always true for blocks allocated with block_Alloc(). */
97     assert (block->p_start == (unsigned char *)(block + 1));
98     block_Invalidate (block);
99     free (block);
100 }
101 
BlockMetaCopy(block_t * restrict out,const block_t * in)102 static void BlockMetaCopy( block_t *restrict out, const block_t *in )
103 {
104     out->p_next    = in->p_next;
105     out->i_nb_samples = in->i_nb_samples;
106     out->i_dts     = in->i_dts;
107     out->i_pts     = in->i_pts;
108     out->i_flags   = in->i_flags;
109     out->i_length  = in->i_length;
110 }
111 
112 /** Initial memory alignment of data block.
113  * @note This must be a multiple of sizeof(void*) and a power of two.
114  * libavcodec AVX optimizations require at least 32-bytes. */
115 #define BLOCK_ALIGN        32
116 
117 /** Initial reserved header and footer size. */
118 #define BLOCK_PADDING      32
119 
block_Alloc(size_t size)120 block_t *block_Alloc (size_t size)
121 {
122     if (unlikely(size >> 27))
123     {
124         errno = ENOBUFS;
125         return NULL;
126     }
127 
128     /* 2 * BLOCK_PADDING: pre + post padding */
129     const size_t alloc = sizeof (block_t) + BLOCK_ALIGN + (2 * BLOCK_PADDING)
130                        + size;
131     if (unlikely(alloc <= size))
132         return NULL;
133 
134     block_t *b = malloc (alloc);
135     if (unlikely(b == NULL))
136         return NULL;
137 
138     block_Init (b, b + 1, alloc - sizeof (*b));
139     static_assert ((BLOCK_PADDING % BLOCK_ALIGN) == 0,
140                    "BLOCK_PADDING must be a multiple of BLOCK_ALIGN");
141     b->p_buffer += BLOCK_PADDING + BLOCK_ALIGN - 1;
142     b->p_buffer = (void *)(((uintptr_t)b->p_buffer) & ~(BLOCK_ALIGN - 1));
143     b->i_buffer = size;
144     b->pf_release = block_generic_Release;
145     return b;
146 }
147 
block_TryRealloc(block_t * p_block,ssize_t i_prebody,size_t i_body)148 block_t *block_TryRealloc (block_t *p_block, ssize_t i_prebody, size_t i_body)
149 {
150     block_Check( p_block );
151 
152     /* Corner case: empty block requested */
153     if( i_prebody <= 0 && i_body <= (size_t)(-i_prebody) )
154         i_prebody = i_body = 0;
155 
156     assert( p_block->p_start <= p_block->p_buffer );
157     assert( p_block->p_start + p_block->i_size
158                                     >= p_block->p_buffer + p_block->i_buffer );
159 
160     /* First, shrink payload */
161 
162     /* Pull payload start */
163     if( i_prebody < 0 )
164     {
165         if( p_block->i_buffer >= (size_t)-i_prebody )
166         {
167             p_block->p_buffer -= i_prebody;
168             p_block->i_buffer += i_prebody;
169         }
170         else /* Discard current payload entirely */
171             p_block->i_buffer = 0;
172         i_body += i_prebody;
173         i_prebody = 0;
174     }
175 
176     /* Trim payload end */
177     if( p_block->i_buffer > i_body )
178         p_block->i_buffer = i_body;
179 
180     size_t requested = i_prebody + i_body;
181 
182     if( p_block->i_buffer == 0 )
183     {   /* Corner case: nothing to preserve */
184         if( requested <= p_block->i_size )
185         {   /* Enough room: recycle buffer */
186             size_t extra = p_block->i_size - requested;
187 
188             p_block->p_buffer = p_block->p_start + (extra / 2);
189             p_block->i_buffer = requested;
190             return p_block;
191         }
192 
193         /* Not enough room: allocate a new buffer */
194         block_t *p_rea = block_Alloc( requested );
195         if( p_rea == NULL )
196             return NULL;
197 
198         BlockMetaCopy( p_rea, p_block );
199         block_Release( p_block );
200         return p_rea;
201     }
202 
203     uint8_t *p_start = p_block->p_start;
204     uint8_t *p_end = p_start + p_block->i_size;
205 
206     /* Second, reallocate the buffer if we lack space. */
207     assert( i_prebody >= 0 );
208     if( (size_t)(p_block->p_buffer - p_start) < (size_t)i_prebody
209      || (size_t)(p_end - p_block->p_buffer) < i_body )
210     {
211         block_t *p_rea = block_Alloc( requested );
212         if( p_rea == NULL )
213             return NULL;
214 
215         memcpy( p_rea->p_buffer + i_prebody, p_block->p_buffer,
216                 p_block->i_buffer );
217         BlockMetaCopy( p_rea, p_block );
218         block_Release( p_block );
219         return p_rea;
220     }
221 
222     /* Third, expand payload */
223 
224     /* Push payload start */
225     if( i_prebody > 0 )
226     {
227         p_block->p_buffer -= i_prebody;
228         p_block->i_buffer += i_prebody;
229         i_body += i_prebody;
230         i_prebody = 0;
231     }
232 
233     /* Expand payload to requested size */
234     p_block->i_buffer = i_body;
235 
236     return p_block;
237 }
238 
block_Realloc(block_t * block,ssize_t prebody,size_t body)239 block_t *block_Realloc (block_t *block, ssize_t prebody, size_t body)
240 {
241     block_t *rea = block_TryRealloc (block, prebody, body);
242     if (rea == NULL)
243         block_Release(block);
244     return rea;
245 }
246 
block_heap_Release(block_t * block)247 static void block_heap_Release (block_t *block)
248 {
249     block_Invalidate (block);
250     free (block->p_start);
251     free (block);
252 }
253 
block_heap_Alloc(void * addr,size_t length)254 block_t *block_heap_Alloc (void *addr, size_t length)
255 {
256     block_t *block = malloc (sizeof (*block));
257     if (block == NULL)
258     {
259         free (addr);
260         return NULL;
261     }
262 
263     block_Init (block, addr, length);
264     block->pf_release = block_heap_Release;
265     return block;
266 }
267 
268 #ifdef HAVE_MMAP
269 # include <sys/mman.h>
270 
block_mmap_Release(block_t * block)271 static void block_mmap_Release (block_t *block)
272 {
273     block_Invalidate (block);
274     munmap (block->p_start, block->i_size);
275     free (block);
276 }
277 
block_mmap_Alloc(void * addr,size_t length)278 block_t *block_mmap_Alloc (void *addr, size_t length)
279 {
280     if (addr == MAP_FAILED)
281         return NULL;
282 
283     long page_mask = sysconf(_SC_PAGESIZE) - 1;
284     size_t left = ((uintptr_t)addr) & page_mask;
285     size_t right = (-length) & page_mask;
286 
287     block_t *block = malloc (sizeof (*block));
288     if (block == NULL)
289     {
290         munmap (addr, length);
291         return NULL;
292     }
293 
294     block_Init (block, ((char *)addr) - left, left + length + right);
295     block->p_buffer = addr;
296     block->i_buffer = length;
297     block->pf_release = block_mmap_Release;
298     return block;
299 }
300 #else
block_mmap_Alloc(void * addr,size_t length)301 block_t *block_mmap_Alloc (void *addr, size_t length)
302 {
303     (void)addr; (void)length; return NULL;
304 }
305 #endif
306 
307 #ifdef HAVE_SYS_SHM_H
308 # include <sys/shm.h>
309 
310 typedef struct block_shm_t
311 {
312     block_t     self;
313     void       *base_addr;
314 } block_shm_t;
315 
block_shm_Release(block_t * block)316 static void block_shm_Release (block_t *block)
317 {
318     block_shm_t *p_sys = (block_shm_t *)block;
319 
320     shmdt (p_sys->base_addr);
321     free (p_sys);
322 }
323 
block_shm_Alloc(void * addr,size_t length)324 block_t *block_shm_Alloc (void *addr, size_t length)
325 {
326     block_shm_t *block = malloc (sizeof (*block));
327     if (unlikely(block == NULL))
328     {
329         shmdt (addr);
330         return NULL;
331     }
332 
333     block_Init (&block->self, (uint8_t *)addr, length);
334     block->self.pf_release = block_shm_Release;
335     block->base_addr = addr;
336     return &block->self;
337 }
338 #else
block_shm_Alloc(void * addr,size_t length)339 block_t *block_shm_Alloc (void *addr, size_t length)
340 {
341     (void) addr; (void) length;
342     abort ();
343 }
344 #endif
345 
346 
347 #ifdef _WIN32
348 # include <io.h>
349 
350 static
pread(int fd,void * buf,size_t count,off_t offset)351 ssize_t pread (int fd, void *buf, size_t count, off_t offset)
352 {
353     HANDLE handle = (HANDLE)(intptr_t)_get_osfhandle (fd);
354     if (handle == INVALID_HANDLE_VALUE)
355         return -1;
356 
357     OVERLAPPED olap = {.Offset = offset, .OffsetHigh = (offset >> 32)};
358     DWORD written;
359     /* This braindead API will override the file pointer even if we specify
360      * an explicit read offset... So do not expect this to mix well with
361      * regular read() calls. */
362     if (ReadFile (handle, buf, count, &written, &olap))
363         return written;
364     return -1;
365 }
366 #endif
367 
block_File(int fd,bool write)368 block_t *block_File(int fd, bool write)
369 {
370     size_t length;
371     struct stat st;
372 
373     /* First, get the file size */
374     if (fstat (fd, &st))
375         return NULL;
376 
377     /* st_size is meaningful for regular files, shared memory and typed memory.
378      * It's also meaning for symlinks, but that's not possible with fstat().
379      * In other cases, it's undefined, and we should really not go further. */
380 #ifndef S_TYPEISSHM
381 # define S_TYPEISSHM( buf ) (0)
382 #endif
383     if (S_ISDIR (st.st_mode))
384     {
385         errno = EISDIR;
386         return NULL;
387     }
388     if (!S_ISREG (st.st_mode) && !S_TYPEISSHM (&st))
389     {
390         errno = ESPIPE;
391         return NULL;
392     }
393 
394     /* Prevent an integer overflow in mmap() and malloc() */
395     if ((uintmax_t)st.st_size >= SIZE_MAX)
396     {
397         errno = ENOMEM;
398         return NULL;
399     }
400     length = (size_t)st.st_size;
401 
402 #ifdef HAVE_MMAP
403     if (length > 0)
404     {
405         int prot = PROT_READ | (write ? PROT_WRITE : 0);
406         int flags = write ? MAP_PRIVATE : MAP_SHARED;
407         void *addr = mmap(NULL, length, prot, flags, fd, 0);
408 
409         if (addr != MAP_FAILED)
410             return block_mmap_Alloc (addr, length);
411     }
412 #endif
413 
414     /* If mmap() is not implemented by the OS _or_ the filesystem... */
415     block_t *block = block_Alloc (length);
416     if (block == NULL)
417         return NULL;
418     block_cleanup_push (block);
419 
420     for (size_t i = 0; i < length;)
421     {
422         ssize_t len = pread (fd, block->p_buffer + i, length - i, i);
423         if (len == -1)
424         {
425             block_Release (block);
426             block = NULL;
427             break;
428         }
429         i += len;
430     }
431     vlc_cleanup_pop ();
432     return block;
433 }
434 
block_FilePath(const char * path,bool write)435 block_t *block_FilePath(const char *path, bool write)
436 {
437     /* NOTE: Writeable shared mappings are not supported here. So there are no
438      * needs to open the file for writing (even if the mapping is writable). */
439     int fd = vlc_open (path, O_RDONLY);
440     if (fd == -1)
441         return NULL;
442 
443     block_t *block = block_File(fd, write);
444     vlc_close (fd);
445     return block;
446 }
447