]> git.sesse.net Git - vlc/blob - modules/access/directory.c
5c471a299ee75aef5f234541163411de61837ece
[vlc] / modules / access / directory.c
1 /*****************************************************************************
2  * directory.c: expands a directory (directory: access_browser plug-in)
3  *****************************************************************************
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  *
11  * This program is free software; you can redistribute it and/or modify it
12  * under the terms of the GNU Lesser General Public License as published by
13  * the Free Software Foundation; either version 2.1 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * along with this program; if not, write to the Free Software Foundation,
23  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24  *****************************************************************************/
25
26 /*****************************************************************************
27  * Preamble
28  *****************************************************************************/
29
30 #ifdef HAVE_CONFIG_H
31 # include "config.h"
32 #endif
33
34 #include <vlc_common.h>
35 #include "fs.h"
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 #include <unistd.h>
43 #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 enum
51 {
52     ENTRY_DIR       = 0,
53     ENTRY_ENOTDIR   = -1,
54     ENTRY_EACCESS   = -2,
55 };
56
57 enum
58 {
59     MODE_NONE,
60     MODE_COLLAPSE,
61     MODE_EXPAND,
62 };
63
64 typedef struct directory directory;
65 struct directory
66 {
67     directory   *parent;
68     DIR         *handle;
69     char        *uri;
70     char       **filev;
71     int          filec, i;
72 #ifdef HAVE_OPENAT
73     dev_t        device;
74     ino_t        inode;
75 #else
76     char         *path;
77 #endif
78 };
79
80 struct access_sys_t
81 {
82     directory *current;
83     char      *ignored_exts;
84     char       mode;
85     int        (*compar) (const char **a, const char **b);
86 };
87
88 /* Select non-hidden files only */
89 static int visible (const char *name)
90 {
91     return name[0] != '.';
92 }
93
94 static int collate (const char **a, const char **b)
95 {
96 #ifdef HAVE_STRCOLL
97     return strcoll (*a, *b);
98 #else
99     return strcmp  (*a, *b);
100 #endif
101 }
102
103 static int version (const char **a, const char **b)
104 {
105     return strverscmp (*a, *b);
106 }
107
108 /**
109  * Does the provided URI/path/stuff has one of the extension provided ?
110  *
111  * \param psz_exts A comma separated list of extension without dot, or only
112  * one ext (ex: "avi,mkv,webm")
113  * \param psz_uri The uri/path to check (ex: "file:///home/foo/bar.avi"). If
114  * providing an URI, it must not contain a query string.
115  *
116  * \return true if the uri/path has one of the provided extension
117  * false otherwise.
118  */
119 static bool has_ext (const char *psz_exts, const char *psz_uri)
120 {
121     if (psz_exts == NULL)
122         return false;
123
124     const char *ext = strrchr (psz_uri, '.');
125     if (ext == NULL)
126         return false;
127
128     size_t extlen = strlen (++ext);
129
130     for (const char *type = psz_exts, *end; type[0]; type = end + 1)
131     {
132         end = strchr (type, ',');
133         if (end == NULL)
134             end = type + strlen (type);
135
136         if (type + extlen == end && !strncasecmp (ext, type, extlen))
137             return true;
138
139         if (*end == '\0')
140             break;
141     }
142
143     return false;
144 }
145
146
147 #ifdef HAVE_OPENAT
148 /* Detect directories that recurse into themselves. */
149 static bool has_inode_loop (const directory *dir, dev_t dev, ino_t inode)
150 {
151     while (dir != NULL)
152     {
153         if ((dir->device == dev) && (dir->inode == inode))
154             return true;
155         dir = dir->parent;
156     }
157     return false;
158 }
159 #endif
160
161 /* success -> returns ENTRY_DIR and the handle parameter is set to the handle,
162  * error -> return ENTRY_ENOTDIR or ENTRY_EACCESS */
163 static int directory_open (directory *p_dir, char *psz_entry, DIR **handle)
164 {
165     *handle = NULL;
166
167 #ifdef HAVE_OPENAT
168     int fd = vlc_openat (dirfd (p_dir->handle), psz_entry,
169                          O_RDONLY | O_DIRECTORY);
170
171     if (fd == -1)
172     {
173         if (errno == ENOTDIR)
174             return ENTRY_ENOTDIR;
175         else
176             return ENTRY_EACCESS;
177     }
178
179     struct stat st;
180     if (fstat (fd, &st)
181         || has_inode_loop (p_dir, st.st_dev, st.st_ino)
182         || (*handle = fdopendir (fd)) == NULL)
183     {
184         close (fd);
185         return ENTRY_EACCESS;
186     }
187 #else
188     char *path;
189     if (asprintf (&path, "%s/%s", p_dir->path, psz_entry) == -1)
190         return ENTRY_EACCESS;
191
192     *handle = vlc_opendir (path);
193
194     free(path);
195
196     if (*handle == NULL) {
197         return ENTRY_ENOTDIR;
198     }
199 #endif
200
201     return ENTRY_DIR;
202 }
203
204 static bool directory_push (access_sys_t *p_sys, DIR *handle, char *psz_uri)
205 {
206     directory *p_dir = malloc (sizeof (*p_dir));
207
208     psz_uri = strdup (psz_uri);
209     if (unlikely (p_dir == NULL || psz_uri == NULL))
210         goto error;
211
212     p_dir->parent = p_sys->current;
213     p_dir->handle = handle;
214     p_dir->uri = psz_uri;
215     p_dir->filec = vlc_loaddir (handle, &p_dir->filev, visible, p_sys->compar);
216     if (p_dir->filec < 0)
217         p_dir->filev = NULL;
218     p_dir->i = 0;
219
220 #ifdef HAVE_OPENAT
221     struct stat st;
222     if (fstat (dirfd (handle), &st))
223         goto error_filev;
224     p_dir->device = st.st_dev;
225     p_dir->inode = st.st_ino;
226 #else
227     p_dir->path = make_path (psz_uri);
228     if (p_dir->path == NULL)
229         goto error_filev;
230 #endif
231
232     p_sys->current = p_dir;
233     return true;
234
235 error_filev:
236     for (int i = 0; i < p_dir->filec; i++)
237         free (p_dir->filev[i]);
238     free (p_dir->filev);
239
240 error:
241     closedir (handle);
242     free (p_dir);
243     free (psz_uri);
244     return false;
245 }
246
247 static bool directory_pop (access_sys_t *p_sys)
248 {
249     directory *p_old = p_sys->current;
250
251     if (p_old == NULL)
252         return false;
253
254     p_sys->current = p_old->parent;
255     closedir (p_old->handle);
256     free (p_old->uri);
257     for (int i = 0; i < p_old->filec; i++)
258         free (p_old->filev[i]);
259     free (p_old->filev);
260 #ifndef HAVE_OPENAT
261     free (p_old->path);
262 #endif
263     free (p_old);
264
265     return p_sys->current != NULL;
266 }
267
268
269 /*****************************************************************************
270  * Open: open the directory
271  *****************************************************************************/
272 int DirOpen (vlc_object_t *p_this)
273 {
274     access_t *p_access = (access_t*)p_this;
275
276     if (!p_access->psz_filepath)
277         return VLC_EGENERIC;
278
279     DIR *handle = vlc_opendir (p_access->psz_filepath);
280     if (handle == NULL)
281         return VLC_EGENERIC;
282
283     return DirInit (p_access, handle);
284 }
285
286 int DirInit (access_t *p_access, DIR *handle)
287 {
288     access_sys_t *p_sys = malloc (sizeof (*p_sys));
289     if (unlikely (p_sys == NULL))
290         goto error;
291
292     char *psz_sort = var_InheritString (p_access, "directory-sort");
293     if (!psz_sort)
294         p_sys->compar = collate;
295     else if (!strcasecmp ( psz_sort, "version"))
296         p_sys->compar = version;
297     else if (!strcasecmp (psz_sort, "none"))
298         p_sys->compar = NULL;
299     else
300         p_sys->compar = collate;
301     free(psz_sort);
302
303     char *uri;
304     if (!strcmp (p_access->psz_access, "fd"))
305     {
306         if (asprintf (&uri, "fd://%s", p_access->psz_location) == -1)
307             uri = NULL;
308     }
309     else
310         uri = vlc_path2uri (p_access->psz_filepath, "file");
311     if (unlikely (uri == NULL))
312     {
313         closedir (handle);
314         goto error;
315     }
316
317     /* "Open" the base directory */
318     p_sys->current = NULL;
319     if (!directory_push (p_sys, handle, uri))
320     {
321         free (uri);
322         goto error;
323     }
324     free (uri);
325
326     p_access->p_sys = p_sys;
327     p_sys->ignored_exts = var_InheritString (p_access, "ignore-filetypes");
328
329
330     /* Handle mode */
331     char *psz_rec = var_InheritString (p_access, "recursive");
332     if (psz_rec == NULL || !strcasecmp (psz_rec, "none"))
333         p_sys->mode = MODE_NONE;
334     else if (!strcasecmp (psz_rec, "collapse"))
335         p_sys->mode = MODE_COLLAPSE;
336     else
337         p_sys->mode = MODE_EXPAND;
338     free (psz_rec);
339
340     p_access->pf_readdir = DirRead;
341     p_access->pf_control = DirControl;
342
343     return VLC_SUCCESS;
344
345 error:
346     free (p_sys);
347     return VLC_EGENERIC;
348 }
349
350 /*****************************************************************************
351  * Close: close the target
352  *****************************************************************************/
353 void DirClose( vlc_object_t * p_this )
354 {
355     access_t *p_access = (access_t*)p_this;
356     access_sys_t *p_sys = p_access->p_sys;
357
358     while (directory_pop (p_sys))
359         ;
360
361     free (p_sys->ignored_exts);
362     free (p_sys);
363 }
364
365 /* This function is a little bit too complex for what it seems to do, but the
366  * point is to de-recursify directory recusion to avoid overruning the stack
367  * in case there's a high directory depth */
368 int DirRead (access_t *p_access, input_item_node_t *p_current_node)
369 {
370     access_sys_t *p_sys = p_access->p_sys;
371
372     while (p_sys->current != NULL
373            && p_sys->current->i <= p_sys->current->filec)
374     {
375         directory *p_current = p_sys->current;
376
377         /* End of the current folder, let's pop directory and node */
378         if (p_current->i == p_current->filec)
379         {
380             directory_pop (p_sys);
381             p_current_node = p_current_node->p_parent;
382             continue;
383         }
384
385         char *psz_entry = p_current->filev[p_current->i++];
386         char *psz_full_uri, *psz_uri;
387         DIR *handle;
388         input_item_t *p_new = NULL;
389         int i_res;
390
391         /* Check if it is a directory or even readable */
392         i_res = directory_open (p_current, psz_entry, &handle);
393
394         if (i_res == ENTRY_EACCESS
395             || (i_res == ENTRY_DIR && p_sys->mode == MODE_NONE)
396             || (i_res == ENTRY_ENOTDIR && has_ext (p_sys->ignored_exts, psz_entry)))
397             continue;
398
399
400         /* Create an input item for the current entry */
401         psz_uri = encode_URI_component (psz_entry);
402         if (psz_uri == NULL
403          || asprintf (&psz_full_uri, "%s/%s", p_current->uri, psz_uri) == -1)
404             psz_full_uri = NULL;
405
406         free (psz_uri);
407         if (psz_full_uri == NULL)
408         {
409             closedir (handle);
410             continue;
411         }
412
413         int i_type = i_res == ENTRY_DIR ? ITEM_TYPE_DIRECTORY : ITEM_TYPE_FILE;
414         p_new = input_item_NewWithType (psz_full_uri, psz_entry,
415                                         0, NULL, 0, 0, i_type);
416         if (p_new == NULL)
417         {
418             free (psz_full_uri);
419             closedir (handle);
420             continue;
421         }
422
423         input_item_CopyOptions (p_current_node->p_item, p_new);
424         input_item_node_t *p_new_node = input_item_node_AppendItem (p_current_node, p_new);
425
426         /* Handle directory flags and recursion if in EXPAND mode  */
427         if (i_res == ENTRY_DIR)
428         {
429             if (p_sys->mode == MODE_EXPAND
430                 && directory_push (p_sys, handle, psz_full_uri))
431             {
432                 p_current_node = p_new_node;
433             }
434         }
435
436         free (psz_full_uri);
437         input_item_Release (p_new);
438     }
439
440     return VLC_SUCCESS;
441 }
442
443 /*****************************************************************************
444  * Control:
445  *****************************************************************************/
446 int DirControl (access_t *p_access, int i_query, va_list args)
447 {
448     VLC_UNUSED (p_access);
449
450     switch (i_query)
451     {
452         case ACCESS_CAN_SEEK:
453         case ACCESS_CAN_FASTSEEK:
454             *va_arg (args, bool*) = false;
455             break;
456
457         case ACCESS_CAN_PAUSE:
458         case ACCESS_CAN_CONTROL_PACE:
459             *va_arg (args, bool*) = true;
460             break;
461
462         case ACCESS_GET_PTS_DELAY:
463             *va_arg (args, int64_t *) = DEFAULT_PTS_DELAY * 1000;
464             break;
465
466         default:
467             return VLC_EGENERIC;
468      }
469      return VLC_SUCCESS;
470  }