mmap.c 8.71 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
/*****************************************************************************
 * mmap.c: memory-mapped file input
 *****************************************************************************
 * Copyright © 2007-2008 Rémi Denis-Courmont
 * $Id$
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

26
#include <vlc_common.h>
27
#include <vlc_plugin.h>
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
#include <vlc_access.h>
#include <vlc_input.h>
#include <vlc_charset.h>
#include <vlc_interface.h>

#include <assert.h>

#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>

#define FILE_MMAP_TEXT N_("Use file memory mapping")
#define FILE_MMAP_LONGTEXT N_( \
    "Try to use memory mapping to read files and block devices." )

46 47 48 49
#ifndef NDEBUG
/*# define MMAP_DEBUG 1*/
#endif

50 51 52 53
static int Open (vlc_object_t *);
static void Close (vlc_object_t *);

vlc_module_begin();
54 55
    set_shortname (N_("MMap"));
    set_description (N_("Memory-mapped file input"));
56 57
    set_category (CAT_INPUT);
    set_subcategory (SUBCAT_INPUT_ACCESS);
58
    set_capability ("access", 52);
59 60 61
    add_shortcut ("file");
    set_callbacks (Open, Close);

62 63
    add_bool ("file-mmap", true, NULL,
              FILE_MMAP_TEXT, FILE_MMAP_LONGTEXT, true);
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
vlc_module_end();

static block_t *Block (access_t *);
static int Seek (access_t *, int64_t);
static int Control (access_t *, int, va_list);

struct access_sys_t
{
    size_t page_size;
    size_t mtu;
    int    fd;
};

#define MMAP_SIZE (1 << 20)

static int Open (vlc_object_t *p_this)
{
    access_t *p_access = (access_t *)p_this;
    access_sys_t *p_sys;
    const char *path = p_access->psz_path;
    int fd;

    if (!var_CreateGetBool (p_this, "file-mmap"))
        return VLC_EGENERIC; /* disabled */

    STANDARD_BLOCK_ACCESS_INIT;

    if (!strcmp (p_access->psz_path, "-"))
        fd = dup (0);
    else
    {
        msg_Dbg (p_access, "opening file %s", path);
        fd = utf8_open (path, O_RDONLY | O_NOCTTY, 0666);
    }

    if (fd == -1)
    {
        msg_Warn (p_access, "cannot open %s: %m", path);
        goto error;
    }

    /* mmap() is only safe for regular and block special files.
     * For other types, it may be some idiosyncrasic interface (e.g. packet
     * sockets are a good example), if it works at all. */
    struct stat st;

    if (fstat (fd, &st))
    {
        msg_Err (p_access, "cannot stat %s: %m", path);
        goto error;
    }

    if (!S_ISREG (st.st_mode) && !S_ISBLK (st.st_mode))
    {
        msg_Dbg (p_access, "skipping non regular file %s", path);
        goto error;
    }

    /* Autodetect mmap() support */
    if (st.st_size > 0)
    {
        void *addr = mmap (NULL, 1, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
        if (addr != MAP_FAILED)
            munmap (addr, 1);
        else
            goto error;
    }

    p_sys->page_size = sysconf (_SC_PAGE_SIZE);
    p_sys->mtu = MMAP_SIZE;
    if (p_sys->mtu < p_sys->page_size)
        p_sys->mtu = p_sys->page_size;
    p_sys->fd = fd;

    p_access->info.i_size = st.st_size;
139
#ifdef HAVE_POSIX_FADVISE    
140
    posix_fadvise (fd, 0, 0, POSIX_FADV_SEQUENTIAL);
141
#endif 
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180

    return VLC_SUCCESS;

error:
    if (fd != -1)
        close (fd);
    free (p_sys);
    return VLC_EGENERIC;
}


static void Close (vlc_object_t * p_this)
{
    access_t *p_access = (access_t *)p_this;
    access_sys_t *p_sys = p_access->p_sys;

    close (p_sys->fd); /* don't care about error when only reading */
    free (p_sys);
}

static block_t *Block (access_t *p_access)
{
    access_sys_t *p_sys = p_access->p_sys;

    if ((uint64_t)p_access->info.i_pos >= (uint64_t)p_access->info.i_size)
    {
        /* End of file - check if file size changed... */
        struct stat st;

        if ((fstat (p_sys->fd, &st) == 0)
         && (st.st_size != p_access->info.i_size))
        {
            p_access->info.i_size = st.st_size;
            p_access->info.i_update |= INPUT_UPDATE_SIZE;
        }

        /* Really at end of file then */
        if ((uint64_t)p_access->info.i_pos >= (uint64_t)p_access->info.i_size)
        {
181
            p_access->info.b_eof = true;
182 183 184 185 186
            msg_Dbg (p_access, "at end of memory mapped file");
            return NULL;
        }
    }

187
#ifdef MMAP_DEBUG
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
188 189 190 191 192 193
    int64_t dbgpos = lseek (p_sys->fd, 0, SEEK_CUR);
    if (dbgpos != p_access->info.i_pos)
        msg_Err (p_access, "position: 0x%08llx instead of 0x%08llx",
                 p_access->info.i_pos, dbgpos);
#endif

194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
    const uintptr_t page_mask = p_sys->page_size - 1;
    /* Start the mapping on a page boundary: */
    off_t  outer_offset = p_access->info.i_pos & ~page_mask;
    /* Skip useless bytes at the beginning of the first page: */
    size_t inner_offset = p_access->info.i_pos & page_mask;
    /* Map no more bytes than remain: */
    size_t length = p_sys->mtu;
    if (outer_offset + length > p_access->info.i_size)
        length = p_access->info.i_size - outer_offset;

    assert (outer_offset <= p_access->info.i_pos);          /* and */
    assert (p_access->info.i_pos < p_access->info.i_size); /* imply */
    assert (outer_offset < p_access->info.i_size);         /* imply */
    assert (length > 0);

    /* NOTE: We use PROT_WRITE and MAP_PRIVATE so that the block can be
     * modified down the chain, without messing up with the underlying
     * original file. This does NOT need open write permission. */
    void *addr = mmap (NULL, length, PROT_READ|PROT_WRITE, MAP_PRIVATE,
                       p_sys->fd, outer_offset);
    if (addr == MAP_FAILED)
    {
        msg_Err (p_access, "memory mapping failed (%m)");
217
        intf_UserFatal (p_access, false, _("File reading failed"),
218 219 220 221
                        _("VLC could not read the file."));
        msleep (INPUT_ERROR_SLEEP);
        return NULL;
    }
222
#ifdef HAVE_POSIX_MADVISE    
223
    posix_madvise (addr, length, POSIX_MADV_SEQUENTIAL);
224
#endif
225 226 227 228 229 230 231 232

    block_t *block = block_mmap_Alloc (addr, length);
    if (block == NULL)
        return NULL;

    block->p_buffer += inner_offset;
    block->i_buffer -= inner_offset;

233
#ifdef MMAP_DEBUG
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
    msg_Dbg (p_access, "mapped 0x%lx bytes at %p from offset 0x%lx",
             (unsigned long)length, addr, (unsigned long)outer_offset);

    /* Compare normal I/O with memory mapping */
    char *buf = malloc (block->i_buffer);
    ssize_t i_read = read (p_sys->fd, buf, block->i_buffer);

    if (i_read != (ssize_t)block->i_buffer)
        msg_Err (p_access, "read %u instead of %u bytes", (unsigned)i_read,
                 (unsigned)block->i_buffer);
    if (memcmp (buf, block->p_buffer, block->i_buffer))
        msg_Err (p_access, "inconsistent data buffer");
    free (buf);
#endif

    p_access->info.i_pos = outer_offset + length;
    return block;
}


static int Seek (access_t *p_access, int64_t i_pos)
{
256
#ifdef MMAP_DEBUG
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
257 258 259
    lseek (p_access->p_sys->fd, i_pos, SEEK_SET);
#endif

260
    p_access->info.i_pos = i_pos;
261
    p_access->info.b_eof = false;
262 263 264 265 266 267 268 269 270 271 272 273 274 275
    return VLC_SUCCESS;
}


static int Control (access_t *p_access, int query, va_list args)
{
    access_sys_t *p_sys = p_access->p_sys;

    switch (query)
    {
        case ACCESS_CAN_SEEK:
        case ACCESS_CAN_FASTSEEK:
        case ACCESS_CAN_PAUSE:
        case ACCESS_CAN_CONTROL_PACE:
276
            *((bool *)va_arg (args, bool *)) = true;
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
            return VLC_SUCCESS;

        case ACCESS_GET_MTU:
            *((int *)va_arg (args, int *)) = p_sys->mtu;
            return VLC_SUCCESS;

        case ACCESS_GET_PTS_DELAY:
            *((int64_t *)va_arg (args, int64_t *)) = DEFAULT_PTS_DELAY;
            return VLC_SUCCESS;

        case ACCESS_GET_TITLE_INFO:
        case ACCESS_GET_META:
            break;

        case ACCESS_SET_PAUSE_STATE:
            return VLC_SUCCESS;

        case ACCESS_SET_TITLE:
        case ACCESS_SET_SEEKPOINT:
        case ACCESS_SET_PRIVATE_ID_STATE:
        case ACCESS_SET_PRIVATE_ID_CA:
        case ACCESS_GET_PRIVATE_ID_STATE:
        case ACCESS_GET_CONTENT_TYPE:
            break;

        default:
            msg_Warn (p_access, "unimplemented query %d in control", query);
    }

    return VLC_EGENERIC;
}