2 * Copyright (c) 2006-2011 Ed Schouten <ed@80386.nl>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * @brief Virtual filesystem.
35 #include "vfs_modules.h"
38 * @brief List of available virtual filesystem modules and their options
41 static struct vfsmodule modules
[] = {
43 { vfs_http_match
, NULL
, vfs_http_open
, 1, 1, '^' },
44 #endif /* BUILD_HTTP */
45 { vfs_m3u_match
, vfs_m3u_populate
, NULL
, 0, 1, '@' },
46 { vfs_pls_match
, vfs_pls_populate
, NULL
, 0, 1, '@' },
48 { vfs_xspf_match
, vfs_xspf_populate
, NULL
, 0, 1, '@' },
49 #endif /* BUILD_XSPF */
51 * Leave these two rules at the bottom of the list. They have
52 * the weakest matching rules.
54 { vfs_dir_match
, vfs_dir_populate
, NULL
, 0, 0, G_DIR_SEPARATOR
},
55 { vfs_file_match
, NULL
, vfs_file_open
, 0, 1, '\0' },
58 * @brief The number of virtual file system modules currently available
61 #define NUM_MODULES (sizeof modules / sizeof(struct vfsmodule))
64 * @brief Playlist writing module object. Matching is performed by
69 * @brief Write routine.
71 int (*write
)(const struct vfslist
*vl
, const char *filename
);
73 * @brief File extension.
79 * @brief List of VFS writing modules. The first item in the list is
80 * used when no matching extension was found. In that case, the
81 * extension of the first item will be appended to the filename.
83 static struct vfswriter writers
[] = {
85 { vfs_xspf_write
, ".xspf" },
86 #endif /* BUILD_XSPF */
87 { vfs_pls_write
, ".pls" },
88 { vfs_m3u_write
, ".m3u" },
91 * @brief The number of VFS writing modules currently available in the
94 #define NUM_WRITERS (sizeof writers / sizeof(struct vfswriter))
97 * @brief Concatenate a path- and filename. The resulting filename will
98 * not contain /./'s and /../'s.
101 vfs_path_concat(const char *dir
, const char *file
, int strict
)
108 #endif /* G_OS_UNIX */
110 if (!strict
&& file
[0] == '~' &&
111 (file
[1] == '\0' || file
[1] == G_DIR_SEPARATOR
)) {
112 /* Expand ~ to go to the home directory */
113 npath
= g_string_new(g_get_home_dir());
114 g_string_append(npath
, file
+ 1);
116 } else if (!strict
&& file
[0] == '~') {
117 /* Expand ~username - UNIX only */
118 uend
= strchr(file
+ 1, G_DIR_SEPARATOR
);
120 /* Temporarily split the string and resolve the username */
122 tmp
= g_strndup(file
+ 1, uend
- (file
+ 1));
126 pw
= getpwnam(file
+ 1);
131 /* Create the new pathname */
132 npath
= g_string_new(pw
->pw_dir
);
134 g_string_append(npath
, uend
);
135 #endif /* G_OS_UNIX */
136 } else if (g_path_is_absolute(file
)) {
137 /* We already have an absolute path */
138 npath
= g_string_new(file
);
139 } else if (dir
!= NULL
) {
140 /* Basedir should be absolute */
141 if (!g_path_is_absolute(dir
))
144 /* Use the predefined basedir */
145 tmp
= g_build_filename(dir
, file
, NULL
);
146 npath
= g_string_new(tmp
);
149 /* Relative filename with no base */
153 /* Remove /./ and /../ */
154 for (off
= npath
->str
;
155 (off
= strchr(off
, G_DIR_SEPARATOR
)) != NULL
;) {
156 if (off
[1] == '\0' || off
[1] == G_DIR_SEPARATOR
) {
157 /* /foo//bar -> /foo/bar */
158 g_string_erase(npath
, off
- npath
->str
, 1);
159 } else if (off
[1] == '.' &&
160 (off
[2] == '\0' || off
[2] == G_DIR_SEPARATOR
)) {
161 /* /foo/./bar -> /foo/bar */
162 g_string_erase(npath
, off
- npath
->str
, 2);
163 } else if (off
[1] == '.' && off
[2] == '.' &&
164 (off
[3] == '\0' || off
[3] == G_DIR_SEPARATOR
)) {
165 /* /foo/../bar -> /bar */
167 /* Strip one directory below */
169 tmp
= strrchr(npath
->str
, G_DIR_SEPARATOR
);
171 g_string_erase(npath
, tmp
- npath
->str
,
175 /* Woops! Too many ..'s */
176 g_string_free(npath
, TRUE
);
186 g_string_assign(npath
, G_DIR_SEPARATOR_S
);
188 return g_string_free(npath
, FALSE
);
198 struct passwd
*pw
= NULL
;
200 user
= config_getopt("vfs.lockup.user");
201 if (user
[0] != '\0') {
204 return g_strdup_printf(
205 _("Unknown user: %s\n"), user
);
208 root
= config_getopt("vfs.lockup.chroot");
209 if (root
[0] != '\0') {
210 #ifdef BUILD_RES_INIT
211 /* Already load the resolv.conf */
213 #endif /* BUILD_RES_INIT */
215 /* Try to lock ourselves in */
216 rootpath
= vfs_path_concat(NULL
, root
, 0);
217 if (rootpath
== NULL
|| chroot(rootpath
) != 0)
218 return g_strdup_printf(
219 _("Unable to chroot in %s\n"),
220 rootpath
!= NULL
? rootpath
: root
);
226 if (setgid(pw
->pw_gid
) != 0)
227 return g_strdup_printf(
228 _("Unable to change to group %d\n"),
230 if (setuid(pw
->pw_uid
) != 0)
231 return g_strdup_printf(
232 _("Unable to change to user %d\n"),
235 #endif /* G_OS_UNIX */
241 * @brief Deallocates the data structures for a VFS entity
244 vfs_dealloc(struct vfsent
*ve
)
247 g_free(ve
->filename
);
248 g_slice_free(struct vfsent
, ve
);
252 vfs_lookup(const char *filename
, const char *name
, const char *basepath
,
262 fn
= vfs_path_concat(basepath
, filename
, strict
);
263 if (fn
!= NULL
&& (vr
= vfs_cache_lookup(fn
)) != NULL
)
266 /* We only allow files and directories */
267 if (fn
== NULL
|| stat(fn
, &fs
) != 0) {
268 /* Could be a network stream */
270 /* Don't prepend the dirnames */
272 fn
= g_strdup(filename
);
273 } else if (!S_ISREG(fs
.st_mode
) && !S_ISDIR(fs
.st_mode
)) {
274 /* Device nodes and such */
279 /* Initialize our new VFS structure with minimal properties */
280 ve
= g_slice_new0(struct vfsent
);
283 vfs_list_init(&ve
->population
);
285 /* The name argument */
287 /* Set a predefined name */
288 ve
->name
= g_strdup(name
);
290 /* Pseudo file - just copy the URL */
291 ve
->name
= g_strdup(ve
->filename
);
293 /* Get the basename from the filename */
294 ve
->name
= g_path_get_basename(ve
->filename
);
297 /* Try to find a matching VFS module */
298 for (i
= 0; i
< NUM_MODULES
; i
++) {
299 /* Only allow pseudo-filenames when module supports it */
300 if (pseudo
&& !modules
[i
].pseudo
)
303 /* Try to attach the module */
304 ve
->vmod
= &modules
[i
];
305 if (ve
->vmod
->match(ve
, S_ISDIR(fs
.st_mode
)) == 0)
309 /* No matching VFS module */
314 /* Return reference object */
316 vr
= g_slice_new0(struct vfsref
);
323 vfs_dup(const struct vfsref
*vr
)
329 g_atomic_int_inc(&ve
->refcount
);
331 rvr
= g_slice_new(struct vfsref
);
339 vfs_close(struct vfsref
*vr
)
343 if (g_atomic_int_dec_and_test(&vr
->ent
->refcount
)) {
344 /* Deallocate the underlying vfsent */
345 while ((cur
= vfs_list_first(&vr
->ent
->population
)) != NULL
) {
346 vfs_list_remove(&vr
->ent
->population
, cur
);
350 vfs_dealloc(vr
->ent
);
353 /* Deallocate the reference to it */
354 g_slice_free(struct vfsref
, vr
);
358 vfs_populate(const struct vfsref
*vr
)
360 /* Some object cannot be populated */
361 if (!vfs_populatable(vr
))
364 /* Don't fetch double data */
365 if (!vfs_list_empty(vfs_population(vr
)))
368 return vr
->ent
->vmod
->populate(vr
->ent
);
372 vfs_unfold(struct vfslist
*vl
, const struct vfsref
*vr
)
376 if (vfs_playable(vr
)) {
377 /* Single item - add it to the list */
378 vfs_list_insert_tail(vl
, vfs_dup(vr
));
380 /* See if we can recurse it */
382 VFS_LIST_FOREACH(&vr
->ent
->population
, cvr
) {
383 if (cvr
->ent
->recurse
)
390 vfs_locate(struct vfslist
*vl
, const struct vfsref
*vr
,
391 const struct vfsmatch
*vm
)
396 VFS_LIST_FOREACH(&vr
->ent
->population
, cvr
) {
397 /* Add matching objects to the results */
398 if (vfs_playable(cvr
) &&
399 vfs_match_compare(vm
, vfs_filename(cvr
)))
400 vfs_list_insert_tail(vl
, vfs_dup(cvr
));
401 /* Also search through its children */
402 if (cvr
->ent
->recurse
)
403 vfs_locate(vl
, cvr
, vm
);
408 vfs_write_playlist(const struct vfslist
*vl
, const struct vfsref
*vr
,
409 const char *filename
)
411 const char *base
= NULL
;
413 struct vfsref
*rvr
= NULL
;
414 struct vfswriter
*wr
;
418 base
= vfs_filename(vr
);
419 fn
= vfs_path_concat(base
, filename
, 0);
423 /* Search for a matching extension */
424 for (i
= 0; i
< NUM_WRITERS
; i
++) {
425 if (g_str_has_suffix(fn
, writers
[i
].ext
)) {
431 /* No extension matched, use default format */
433 nfn
= g_strdup_printf("%s%s", fn
, wr
->ext
);
437 /* Write the playlist to disk */
438 if (wr
->write(vl
, fn
) == 0)
439 rvr
= vfs_lookup(fn
, NULL
, NULL
, 0);
446 vfs_delete(const char *filename
)
451 fn
= vfs_path_concat(NULL
, filename
, 0);
462 vfs_fopen(const char *filename
, const char *mode
)
467 fn
= vfs_path_concat(NULL
, filename
, 0);
471 ret
= fopen(fn
, mode
);
478 vfs_fgets(char *str
, size_t size
, FILE *fp
)
482 if (fgets(str
, size
, fp
) == NULL
)
485 eol
= strchr(str
, '\0');
486 g_assert(eol
!= NULL
);
488 /* Strip the final \n */
489 if (--eol
>= str
&& *eol
== '\n') {
491 /* Strip the final \r */
492 if (--eol
>= str
&& *eol
== '\r')
500 vfs_match_new(const char *str
)
504 vm
= g_slice_new0(struct vfsmatch
);
506 if (regcomp(&vm
->regex
, str
, REG_EXTENDED
|REG_ICASE
) != 0) {
507 g_slice_free(struct vfsmatch
, vm
);
510 vm
->string
= g_strdup(str
);
516 vfs_match_free(struct vfsmatch
*vm
)
520 g_slice_free(struct vfsmatch
, vm
);