directory.c 8.11 KB
Newer Older
1
/*****************************************************************************
2
 * directory.c: expands a directory (directory: access_browser plug-in)
3
 *****************************************************************************
Jean-Baptiste Kempf's avatar
LGPL  
Jean-Baptiste Kempf committed
4
 * Copyright (C) 2002-2008 VLC authors and VideoLAN
5
 * $Id$
6
 *
7
 * Authors: Derk-Jan Hartman <hartman at videolan dot org>
8
 *          Rémi Denis-Courmont
9
 *          Julien 'Lta' BALLET <contact # lta.io>
10
 *
Jean-Baptiste Kempf's avatar
LGPL  
Jean-Baptiste Kempf committed
11 12 13
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
14
 * (at your option) any later version.
15
 *
16 17
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Jean-Baptiste Kempf's avatar
LGPL  
Jean-Baptiste Kempf committed
18 19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
20
 *
Jean-Baptiste Kempf's avatar
LGPL  
Jean-Baptiste Kempf committed
21 22 23
 * You should have received a copy of the GNU Lesser 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.
24 25 26 27 28
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
29

30 31 32 33
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

34
#include <vlc_common.h>
35
#include "fs.h"
Clément Stenac's avatar
Clément Stenac committed
36
#include <vlc_access.h>
37
#include <vlc_input_item.h>
38

39
#include <sys/types.h>
40
#include <sys/stat.h>
41
#include <errno.h>
42 43
#include <unistd.h>
#include <fcntl.h>
44

45
#include <vlc_fs.h>
46
#include <vlc_url.h>
47
#include <vlc_strings.h>
48
#include <vlc_charset.h>
49

50 51 52 53 54 55 56
enum
{
    ENTRY_DIR       = 0,
    ENTRY_ENOTDIR   = -1,
    ENTRY_EACCESS   = -2,
};

57 58
enum
{
59
    MODE_NONE,
60
    MODE_COLLAPSE,
61
    MODE_EXPAND,
62 63
};

64 65
typedef struct directory directory;
struct directory
66
{
67
    directory   *parent;
68 69
    DIR         *handle;
    char        *uri;
70 71
    char       **filev;
    int          filec, i;
72 73 74 75
#ifdef HAVE_OPENAT
    dev_t        device;
    ino_t        inode;
#else
76 77
    char         *path;
#endif
78
};
79

80 81
struct access_sys_t
{
82 83
    directory *current;
    char       mode;
84
};
85

86 87 88 89 90 91
/* Select non-hidden files only */
static int visible (const char *name)
{
    return name[0] != '.';
}

92 93
#ifdef HAVE_OPENAT
/* Detect directories that recurse into themselves. */
94
static bool has_inode_loop (const directory *dir, dev_t dev, ino_t inode)
95 96 97 98 99 100 101 102 103 104 105 106 107
{
    while (dir != NULL)
    {
        if ((dir->device == dev) && (dir->inode == inode))
            return true;
        dir = dir->parent;
    }
    return false;
}
#endif

/* success -> returns ENTRY_DIR and the handle parameter is set to the handle,
 * error -> return ENTRY_ENOTDIR or ENTRY_EACCESS */
108
static int directory_open (directory *p_dir, char *psz_entry, DIR **handle)
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
{
    *handle = NULL;

#ifdef HAVE_OPENAT
    int fd = vlc_openat (dirfd (p_dir->handle), psz_entry,
                         O_RDONLY | O_DIRECTORY);

    if (fd == -1)
    {
        if (errno == ENOTDIR)
            return ENTRY_ENOTDIR;
        else
            return ENTRY_EACCESS;
    }

    struct stat st;
    if (fstat (fd, &st)
        || has_inode_loop (p_dir, st.st_dev, st.st_ino)
        || (*handle = fdopendir (fd)) == NULL)
    {
        close (fd);
        return ENTRY_EACCESS;
    }
#else
    char *path;
134 135 136 137 138 139 140 141 142 143
    if (asprintf (&path, "%s/%s", p_dir->path, psz_entry) == -1)
        return ENTRY_EACCESS;

    *handle = vlc_opendir (path);

    free(path);

    if (*handle == NULL) {
        return ENTRY_ENOTDIR;
    }
144 145 146 147 148 149 150
#endif

    return ENTRY_DIR;
}

static bool directory_push (access_sys_t *p_sys, DIR *handle, char *psz_uri)
{
151
    directory *p_dir = malloc (sizeof (*p_dir));
152 153

    psz_uri = strdup (psz_uri);
154
    if (unlikely (p_dir == NULL || psz_uri == NULL))
155 156 157 158 159
        goto error;

    p_dir->parent = p_sys->current;
    p_dir->handle = handle;
    p_dir->uri = psz_uri;
160
    p_dir->filec = vlc_loaddir (handle, &p_dir->filev, visible, NULL);
161 162 163 164 165 166 167
    if (p_dir->filec < 0)
        p_dir->filev = NULL;
    p_dir->i = 0;

#ifdef HAVE_OPENAT
    struct stat st;
    if (fstat (dirfd (handle), &st))
Hannes Domani's avatar
Hannes Domani committed
168
        goto error_filev;
169 170 171 172 173
    p_dir->device = st.st_dev;
    p_dir->inode = st.st_ino;
#else
    p_dir->path = make_path (psz_uri);
    if (p_dir->path == NULL)
Hannes Domani's avatar
Hannes Domani committed
174
        goto error_filev;
175 176 177 178 179
#endif

    p_sys->current = p_dir;
    return true;

Hannes Domani's avatar
Hannes Domani committed
180 181 182 183 184
error_filev:
    for (int i = 0; i < p_dir->filec; i++)
        free (p_dir->filev[i]);
    free (p_dir->filev);

185
error:
186 187 188 189 190 191 192 193
    closedir (handle);
    free (p_dir);
    free (psz_uri);
    return false;
}

static bool directory_pop (access_sys_t *p_sys)
{
194
    directory *p_old = p_sys->current;
195 196 197 198 199 200 201

    if (p_old == NULL)
        return false;

    p_sys->current = p_old->parent;
    closedir (p_old->handle);
    free (p_old->uri);
Hannes Domani's avatar
Hannes Domani committed
202 203
    for (int i = 0; i < p_old->filec; i++)
        free (p_old->filev[i]);
204 205 206 207 208 209 210 211 212 213
    free (p_old->filev);
#ifndef HAVE_OPENAT
    free (p_old->path);
#endif
    free (p_old);

    return p_sys->current != NULL;
}


214 215 216
/*****************************************************************************
 * Open: open the directory
 *****************************************************************************/
217
int DirOpen (vlc_object_t *p_this)
218
{
219
    access_t *p_access = (access_t*)p_this;
220

221
    if (!p_access->psz_filepath)
222 223
        return VLC_EGENERIC;

224
    DIR *handle = vlc_opendir (p_access->psz_filepath);
225
    if (handle == NULL)
226
        return VLC_EGENERIC;
Gildas Bazin's avatar
 
Gildas Bazin committed
227

228 229 230 231 232 233
    return DirInit (p_access, handle);
}

int DirInit (access_t *p_access, DIR *handle)
{
    access_sys_t *p_sys = malloc (sizeof (*p_sys));
234
    if (unlikely (p_sys == NULL))
235 236 237 238
        goto error;

    char *uri;
    if (!strcmp (p_access->psz_access, "fd"))
239
    {
240
        if (asprintf (&uri, "fd://%s", p_access->psz_location) == -1)
241
            uri = NULL;
242
    }
243
    else
244
        uri = vlc_path2uri (p_access->psz_filepath, "file");
245
    if (unlikely (uri == NULL))
246 247
    {
        closedir (handle);
248
        goto error;
249
    }
250

251
    /* "Open" the base directory */
252
    p_sys->current = NULL;
253
    if (!directory_push (p_sys, handle, uri))
254 255 256 257
    {
        free (uri);
        goto error;
    }
258
    free (uri);
259

260
    p_access->p_sys = p_sys;
261 262

    p_access->pf_readdir = DirRead;
263

264
    return VLC_SUCCESS;
265 266 267 268

error:
    free (p_sys);
    return VLC_EGENERIC;
269 270 271 272 273
}

/*****************************************************************************
 * Close: close the target
 *****************************************************************************/
274
void DirClose( vlc_object_t * p_this )
275
{
276
    access_t *p_access = (access_t*)p_this;
277 278
    access_sys_t *p_sys = p_access->p_sys;

279 280
    while (directory_pop (p_sys))
        ;
281

282
    free (p_sys);
283 284
}

285 286 287
/* This function is a little bit too complex for what it seems to do, but the
 * point is to de-recursify directory recusion to avoid overruning the stack
 * in case there's a high directory depth */
Thomas Guillem's avatar
Thomas Guillem committed
288
input_item_t* DirRead (access_t *p_access)
289 290
{
    access_sys_t *p_sys = p_access->p_sys;
Thomas Guillem's avatar
Thomas Guillem committed
291
    input_item_t *p_item = NULL;
292

Thomas Guillem's avatar
Thomas Guillem committed
293
    while (!p_item && p_sys->current != NULL
294 295
           && p_sys->current->i <= p_sys->current->filec)
    {
296
        directory *p_current = p_sys->current;
297

298 299
        char *psz_entry = p_current->filev[p_current->i++];
        char *psz_full_uri, *psz_uri;
300
        DIR *handle;
301
        int i_res;
302

303 304 305 306 307 308
        /* Check if it is a directory or even readable */
        i_res = directory_open (p_current, psz_entry, &handle);

        /* Create an input item for the current entry */
        psz_uri = encode_URI_component (psz_entry);
        if (psz_uri == NULL
309 310 311 312 313
         || asprintf (&psz_full_uri, "%s/%s", p_current->uri, psz_uri) == -1)
            psz_full_uri = NULL;

        free (psz_uri);
        if (psz_full_uri == NULL)
314 315
        {
            closedir (handle);
316
            continue;
317
        }
318 319

        int i_type = i_res == ENTRY_DIR ? ITEM_TYPE_DIRECTORY : ITEM_TYPE_FILE;
Thomas Guillem's avatar
Thomas Guillem committed
320 321 322
        p_item = input_item_NewWithType (psz_full_uri, psz_entry,
                                         0, NULL, 0, 0, i_type);
        if (p_item == NULL)
323
        {
324 325 326
            free (psz_full_uri);
            closedir (handle);
            continue;
327
        }
328

329
        free (psz_full_uri);
330
    }
Thomas Guillem's avatar
Thomas Guillem committed
331
    return p_item;
332
}