2 * Copyright (c) 2006-2007 Ed Schouten <ed@fxq.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_open
, NULL
, vfs_http_handle
, 1, 1, '^' },
44 #endif /* BUILD_HTTP */
45 { vfs_m3u_open
, vfs_m3u_populate
, NULL
, 0, 1, '@' },
46 { vfs_pls_open
, vfs_pls_populate
, NULL
, 0, 1, '@' },
48 { vfs_xspf_open
, 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_open
, vfs_dir_populate
, NULL
, 0, 0, G_DIR_SEPARATOR
},
55 { vfs_file_open
, NULL
, vfs_file_handle
, 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))
102 struct passwd
*pw
= NULL
;
104 user
= config_getopt("vfs.lockup.user");
105 if (user
[0] != '\0') {
108 return g_strdup_printf(
109 _("Unknown user: %s\n"), user
);
112 root
= config_getopt("vfs.lockup.chroot");
113 if (root
[0] != '\0') {
114 #ifdef BUILD_RES_INIT
115 /* Already load the resolv.conf */
117 #endif /* BUILD_RES_INIT */
118 /* Try to lock ourselves in */
119 if (chroot(root
) != 0)
120 return g_strdup_printf(
121 _("Unable to chroot in %s\n"), root
);
127 if (setgid(pw
->pw_gid
) != 0)
128 return g_strdup_printf(
129 _("Unable to change to group %d\n"),
131 if (setuid(pw
->pw_uid
) != 0)
132 return g_strdup_printf(
133 _("Unable to change to user %d\n"),
136 #endif /* G_OS_UNIX */
142 * @brief Deallocates the data structures for a VFS entity
145 vfs_dealloc(struct vfsent
*ve
)
148 g_free(ve
->filename
);
149 g_slice_free(struct vfsent
, ve
);
153 * @brief Concatenate a path- and filename. The resulting filename will
154 * not contain /./'s and /../'s.
157 vfs_path_concat(const char *dir
, const char *file
, int strict
)
163 #endif /* G_OS_UNIX */
165 if (!strict
&& file
[0] == '~' &&
166 (file
[1] == '\0' || file
[1] == G_DIR_SEPARATOR
)) {
167 /* Expand ~ to go to the home directory */
168 npath
= g_string_new(g_get_home_dir());
169 g_string_append(npath
, file
+ 1);
171 } else if (!strict
&& file
[0] == '~') {
172 /* Expand ~username - UNIX only */
173 off
= strchr(file
, G_DIR_SEPARATOR
);
175 /* Temporarily split the string and resolve the username */
178 pw
= getpwnam(file
+ 1);
180 *off
= G_DIR_SEPARATOR
;
184 /* Create the new pathname */
185 npath
= g_string_new(pw
->pw_dir
);
187 g_string_append(npath
, off
);
188 #endif /* G_OS_UNIX */
189 } else if (g_path_is_absolute(file
)) {
190 /* We already have an absolute path */
191 npath
= g_string_new(file
);
192 } else if (dir
!= NULL
) {
193 /* Basedir should be absolute */
194 if (!g_path_is_absolute(dir
))
197 /* Use the predefined basedir */
198 tmp
= g_build_filename(dir
, file
, NULL
);
199 npath
= g_string_new(tmp
);
202 /* Relative filename with no base */
206 /* Remove /./ and /../ */
207 for (off
= npath
->str
;
208 (off
= strchr(off
, G_DIR_SEPARATOR
)) != NULL
;) {
209 if (off
[1] == '\0' || off
[1] == G_DIR_SEPARATOR
) {
210 /* /foo//bar -> /foo/bar */
211 g_string_erase(npath
, off
- npath
->str
, 1);
212 } else if (off
[1] == '.' &&
213 (off
[2] == '\0' || off
[2] == G_DIR_SEPARATOR
)) {
214 /* /foo/./bar -> /foo/bar */
215 g_string_erase(npath
, off
- npath
->str
, 2);
216 } else if (off
[1] == '.' && off
[2] == '.' &&
217 (off
[3] == '\0' || off
[3] == G_DIR_SEPARATOR
)) {
218 /* /foo/../bar -> /bar */
220 /* Strip one directory below */
222 tmp
= strrchr(npath
->str
, G_DIR_SEPARATOR
);
224 g_string_erase(npath
, tmp
- npath
->str
,
228 /* Woops! Too many ..'s */
229 g_string_free(npath
, TRUE
);
239 g_string_assign(npath
, G_DIR_SEPARATOR_S
);
241 return g_string_free(npath
, FALSE
);
245 vfs_open(const char *filename
, const char *name
, const char *basepath
,
255 fn
= vfs_path_concat(basepath
, filename
, strict
);
257 /* We only allow files and directories */
258 if (fn
== NULL
|| stat(fn
, &fs
) != 0) {
259 /* Could be a network stream */
261 /* Don't prepend the dirnames */
263 fn
= g_strdup(filename
);
264 } else if (!S_ISREG(fs
.st_mode
) && !S_ISDIR(fs
.st_mode
)) {
265 /* Device nodes and such */
270 /* Initialize our new VFS structure with minimal properties */
271 ve
= g_slice_new0(struct vfsent
);
274 vfs_list_init(&ve
->population
);
276 /* The name argument */
278 /* Set a predefined name */
279 ve
->name
= g_strdup(name
);
281 /* Pseudo file - just copy the URL */
282 ve
->name
= g_strdup(ve
->filename
);
284 /* Get the basename from the filename */
285 ve
->name
= g_path_get_basename(ve
->filename
);
288 /* Try to find a matching VFS module */
289 for (i
= 0; i
< NUM_MODULES
; i
++) {
290 /* Only allow pseudo-filenames when module supports it */
291 if (pseudo
&& !modules
[i
].pseudo
)
294 /* Try to attach the module */
295 ve
->vmod
= &modules
[i
];
296 if (ve
->vmod
->vopen(ve
, S_ISDIR(fs
.st_mode
)) == 0)
300 /* No matching VFS module */
305 /* Return reference object */
307 vr
= g_slice_new0(struct vfsref
);
313 vfs_dup(const struct vfsref
*vr
)
319 g_atomic_int_inc(&ve
->refcount
);
321 rvr
= g_slice_new(struct vfsref
);
329 vfs_close(struct vfsref
*vr
)
333 if (g_atomic_int_dec_and_test(&vr
->ent
->refcount
)) {
334 /* Deallocate the underlying vfsent */
335 while ((cur
= vfs_list_first(&vr
->ent
->population
)) != NULL
) {
336 vfs_list_remove(&vr
->ent
->population
, cur
);
340 vfs_dealloc(vr
->ent
);
343 /* Deallocate the reference to it */
344 g_slice_free(struct vfsref
, vr
);
348 vfs_populate(const struct vfsref
*vr
)
350 /* Some object cannot be populated */
351 if (!vfs_populatable(vr
))
354 /* Don't fetch double data */
355 if (!vfs_list_empty(vfs_population(vr
)))
358 return vr
->ent
->vmod
->vpopulate(vr
->ent
);
362 vfs_unfold(struct vfslist
*vl
, const struct vfsref
*vr
)
366 if (vfs_playable(vr
)) {
367 /* Single item - add it to the list */
368 vfs_list_insert_tail(vl
, vfs_dup(vr
));
370 /* See if we can recurse it */
372 VFS_LIST_FOREACH(&vr
->ent
->population
, cvr
) {
373 if (cvr
->ent
->recurse
)
380 vfs_locate(struct vfslist
*vl
, const struct vfsref
*vr
,
381 const struct vfsmatch
*vm
)
386 VFS_LIST_FOREACH(&vr
->ent
->population
, cvr
) {
387 /* Add matching objects to the results */
388 if (vfs_playable(cvr
) &&
389 vfs_match_compare(vm
, vfs_filename(cvr
)))
390 vfs_list_insert_tail(vl
, vfs_dup(cvr
));
391 /* Also search through its children */
392 if (cvr
->ent
->recurse
)
393 vfs_locate(vl
, cvr
, vm
);
398 vfs_write_playlist(const struct vfslist
*vl
, const struct vfsref
*vr
,
399 const char *filename
)
401 const char *base
= NULL
;
403 struct vfsref
*rvr
= NULL
;
404 struct vfswriter
*wr
;
408 base
= vfs_filename(vr
);
409 fn
= vfs_path_concat(base
, filename
, 0);
413 /* Search for a matching extension */
414 for (i
= 0; i
< NUM_WRITERS
; i
++) {
415 if (g_str_has_suffix(fn
, writers
[i
].ext
)) {
421 /* No extension matched, use default format */
423 nfn
= g_strdup_printf("%s%s", fn
, wr
->ext
);
427 /* Write the playlist to disk */
428 if (wr
->write(vl
, fn
) == 0)
429 rvr
= vfs_open(fn
, NULL
, NULL
, 0);
436 vfs_delete(const char *filename
)
441 fn
= vfs_path_concat(NULL
, filename
, 0);
452 vfs_fopen(const char *filename
, const char *mode
)
457 fn
= vfs_path_concat(NULL
, filename
, 0);
461 ret
= fopen(fn
, mode
);
468 vfs_fgets(char *str
, size_t size
, FILE *fp
)
472 if (fgets(str
, size
, fp
) == NULL
)
475 eol
= strchr(str
, '\0');
476 g_assert(eol
!= NULL
);
478 /* Strip the final \n */
479 if (--eol
>= str
&& *eol
== '\n') {
481 /* Strip the final \r */
482 if (--eol
>= str
&& *eol
== '\r')
490 vfs_match_new(const char *str
)
494 vm
= g_slice_new0(struct vfsmatch
);
497 if (regcomp(&vm
->regex
, str
, REG_EXTENDED
|REG_ICASE
) != 0) {
498 g_slice_free(struct vfsmatch
, vm
);
501 #endif /* BUILD_REGEX */
502 vm
->string
= g_strdup(str
);
508 vfs_match_free(struct vfsmatch
*vm
)
512 #endif /* BUILD_REGEX */
514 g_slice_free(struct vfsmatch
, vm
);
518 vfs_match_compare(const struct vfsmatch
*vm
, const char *name
)
521 return (regexec(&vm
->regex
, name
, 0, NULL
, 0) == 0);
522 #else /* !BUILD_REGEX */
526 len
= strlen(vm
->string
);
528 /* strcasestr()-like string comparison */
529 if ((first
= tolower(vm
->string
[0])) == '\0')
531 while (name
[0] != '\0') {
532 if (tolower(name
[0]) == first
)
533 if (strncasecmp(name
, vm
->string
, len
) == 0)
539 #endif /* BUILD_REGEX */