Remove vfs_recurse().
[herrie-working.git] / herrie / src / vfs.c
bloba9d0863ee8ebfdd40627a111c62086a10b4cb070
1 /*
2 * Copyright (c) 2006-2007 Ed Schouten <ed@fxq.nl>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
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
24 * SUCH DAMAGE.
26 /**
27 * @file vfs.c
28 * @brief Virtual filesystem.
31 #include "stdinc.h"
33 #include "config.h"
34 #include "vfs.h"
35 #include "vfs_modules.h"
37 /**
38 * @brief List of available virtual filesystem modules and their options
39 * and functions.
41 static struct vfsmodule modules[] = {
42 #ifdef BUILD_HTTP
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, '@' },
47 #ifdef BUILD_XSPF
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' },
57 /**
58 * @brief The number of virtual file system modules currently available
59 * in the application.
61 #define NUM_MODULES (sizeof(modules) / sizeof(struct vfsmodule))
63 /**
64 * @brief Playlist writing module object. Matching is performed by
65 * extension.
67 struct vfswriter {
68 /**
69 * @brief Write routine.
71 int (*write)(const struct vfslist *vl, const char *filename);
72 /**
73 * @brief File extension.
75 char *ext;
78 /**
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[] = {
84 #ifdef BUILD_XSPF
85 { vfs_xspf_write, ".xspf" },
86 #endif /* BUILD_XSPF */
87 { vfs_pls_write, ".pls" },
88 { vfs_m3u_write, ".m3u" },
90 /**
91 * @brief The number of VFS writing modules currently available in the
92 * application.
94 #define NUM_WRITERS (sizeof(writers) / sizeof(struct vfswriter))
96 const char *
97 vfs_lockup(void)
99 #ifdef G_OS_UNIX
100 const char *root;
101 const char *user;
102 struct passwd *pw = NULL;
104 user = config_getopt("vfs.lockup.user");
105 if (user[0] != '\0') {
106 pw = getpwnam(user);
107 if (pw == NULL)
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 */
116 res_init();
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);
123 chdir("/");
126 if (pw != NULL) {
127 if (setgid(pw->pw_gid) != 0)
128 return g_strdup_printf(
129 _("Unable to change to group %d\n"),
130 (int)pw->pw_gid);
131 if (setuid(pw->pw_uid) != 0)
132 return g_strdup_printf(
133 _("Unable to change to user %d\n"),
134 (int)pw->pw_uid);
136 #endif /* G_OS_UNIX */
138 return (NULL);
142 * @brief Deallocates the data structures for a VFS entity
144 static void
145 vfs_dealloc(struct vfsent *ve)
147 g_free(ve->name);
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.
156 static char *
157 vfs_path_concat(const char *dir, const char *file, int strict)
159 GString *npath;
160 char *tmp, *off;
161 #ifdef G_OS_UNIX
162 struct passwd *pw;
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);
170 #ifdef G_OS_UNIX
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 */
176 if (off != NULL)
177 *off = '\0';
178 pw = getpwnam(file + 1);
179 if (off != NULL)
180 *off = G_DIR_SEPARATOR;
181 if (pw == NULL)
182 return (NULL);
184 /* Create the new pathname */
185 npath = g_string_new(pw->pw_dir);
186 if (off != NULL)
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))
195 return (NULL);
197 /* Use the predefined basedir */
198 tmp = g_build_filename(dir, file, NULL);
199 npath = g_string_new(tmp);
200 g_free(tmp);
201 } else {
202 /* Relative filename with no base */
203 return (NULL);
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 */
221 *off = '\0';
222 tmp = strrchr(npath->str, G_DIR_SEPARATOR);
223 if (tmp != NULL) {
224 g_string_erase(npath, tmp - npath->str,
225 (off - tmp) + 3);
226 off = tmp;
227 } else {
228 /* Woops! Too many ..'s */
229 g_string_free(npath, TRUE);
230 return (NULL);
232 } else {
233 /* Nothing useful */
234 off++;
238 if (npath->len == 0)
239 g_string_assign(npath, G_DIR_SEPARATOR_S);
241 return g_string_free(npath, FALSE);
244 struct vfsref *
245 vfs_open(const char *filename, const char *name, const char *basepath,
246 int strict)
248 char *fn;
249 struct vfsent *ve;
250 struct vfsref *vr;
251 struct stat fs;
252 unsigned int i;
253 int pseudo = 0;
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 */
260 pseudo = 1;
261 /* Don't prepend the dirnames */
262 g_free(fn);
263 fn = g_strdup(filename);
264 } else if (!S_ISREG(fs.st_mode) && !S_ISDIR(fs.st_mode)) {
265 /* Device nodes and such */
266 g_free(fn);
267 return (NULL);
270 /* Initialize our new VFS structure with minimal properties */
271 ve = g_slice_new0(struct vfsent);
272 ve->filename = fn;
273 ve->recurse = 1;
274 vfs_list_init(&ve->population);
276 /* The name argument */
277 if (name != NULL) {
278 /* Set a predefined name */
279 ve->name = g_strdup(name);
280 } else if (pseudo) {
281 /* Pseudo file - just copy the URL */
282 ve->name = g_strdup(ve->filename);
283 } else {
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)
292 continue;
294 /* Try to attach the module */
295 ve->vmod = &modules[i];
296 if (ve->vmod->vopen(ve, S_ISDIR(fs.st_mode)) == 0)
297 goto found;
300 /* No matching VFS module */
301 vfs_dealloc(ve);
302 return (NULL);
304 found:
305 /* Return reference object */
306 ve->refcount = 1;
307 vr = g_slice_new0(struct vfsref);
308 vr->ent = ve;
309 return (vr);
312 struct vfsref *
313 vfs_dup(const struct vfsref *vr)
315 struct vfsent *ve;
316 struct vfsref *rvr;
318 ve = vr->ent;
319 g_atomic_int_inc(&ve->refcount);
321 rvr = g_slice_new(struct vfsref);
322 rvr->ent = ve;
323 rvr->marked = 0;
325 return (rvr);
328 void
329 vfs_close(struct vfsref *vr)
331 struct vfsref *cur;
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);
337 vfs_close(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))
352 return (-1);
354 /* Don't fetch double data */
355 if (!vfs_list_empty(vfs_population(vr)))
356 return (0);
358 return vr->ent->vmod->vpopulate(vr->ent);
361 void
362 vfs_unfold(struct vfslist *vl, const struct vfsref *vr)
364 struct vfsref *cvr;
366 if (vfs_playable(vr)) {
367 /* Single item - add it to the list */
368 vfs_list_insert_tail(vl, vfs_dup(vr));
369 } else {
370 /* See if we can recurse it */
371 vfs_populate(vr);
372 VFS_LIST_FOREACH(&vr->ent->population, cvr) {
373 if (cvr->ent->recurse)
374 vfs_unfold(vl, cvr);
379 void
380 vfs_locate(struct vfslist *vl, const struct vfsref *vr,
381 const struct vfsmatch *vm)
383 struct vfsref *cvr;
385 vfs_populate(vr);
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);
397 struct vfsref *
398 vfs_write_playlist(const struct vfslist *vl, const struct vfsref *vr,
399 const char *filename)
401 const char *base = NULL;
402 char *fn, *nfn;
403 struct vfsref *rvr = NULL;
404 struct vfswriter *wr;
405 unsigned int i;
407 if (vr != NULL)
408 base = vfs_filename(vr);
409 fn = vfs_path_concat(base, filename, 0);
410 if (fn == NULL)
411 return (NULL);
413 /* Search for a matching extension */
414 for (i = 0; i < NUM_WRITERS; i++) {
415 if (g_str_has_suffix(fn, writers[i].ext)) {
416 wr = &writers[i];
417 goto match;
421 /* No extension matched, use default format */
422 wr = &writers[0];
423 nfn = g_strdup_printf("%s%s", fn, wr->ext);
424 g_free(fn);
425 fn = nfn;
426 match:
427 /* Write the playlist to disk */
428 if (wr->write(vl, fn) == 0)
429 rvr = vfs_open(fn, NULL, NULL, 0);
431 g_free(fn);
432 return (rvr);
436 vfs_delete(const char *filename)
438 char *fn;
439 int ret;
441 fn = vfs_path_concat(NULL, filename, 0);
442 if (fn == NULL)
443 return (-1);
445 ret = unlink(fn);
446 g_free(fn);
448 return (ret);
451 FILE *
452 vfs_fopen(const char *filename, const char *mode)
454 char *fn;
455 FILE *ret;
457 fn = vfs_path_concat(NULL, filename, 0);
458 if (fn == NULL)
459 return (NULL);
461 ret = fopen(fn, mode);
462 g_free(fn);
464 return (ret);
468 vfs_fgets(char *str, size_t size, FILE *fp)
470 char *eol;
472 if (fgets(str, size, fp) == NULL)
473 return (-1);
475 eol = strchr(str, '\0');
476 g_assert(eol != NULL);
478 /* Strip the final \n */
479 if (--eol >= str && *eol == '\n') {
480 *eol = '\0';
481 /* Strip the final \r */
482 if (--eol >= str && *eol == '\r')
483 *eol = '\0';
486 return (0);
489 struct vfsmatch *
490 vfs_match_new(const char *str)
492 struct vfsmatch *vm;
494 vm = g_slice_new0(struct vfsmatch);
496 #ifdef BUILD_REGEX
497 if (regcomp(&vm->regex, str, REG_EXTENDED|REG_ICASE) != 0) {
498 g_slice_free(struct vfsmatch, vm);
499 return (NULL);
501 #endif /* BUILD_REGEX */
502 vm->string = g_strdup(str);
504 return (vm);
507 void
508 vfs_match_free(struct vfsmatch *vm)
510 #ifdef BUILD_REGEX
511 regfree(&vm->regex);
512 #endif /* BUILD_REGEX */
513 g_free(vm->string);
514 g_slice_free(struct vfsmatch, vm);
518 vfs_match_compare(const struct vfsmatch *vm, const char *name)
520 #ifdef BUILD_REGEX
521 return (regexec(&vm->regex, name, 0, NULL, 0) == 0);
522 #else /* !BUILD_REGEX */
523 size_t len;
524 char first;
526 len = strlen(vm->string);
528 /* strcasestr()-like string comparison */
529 if ((first = tolower(vm->string[0])) == '\0')
530 return (0);
531 while (name[0] != '\0') {
532 if (tolower(name[0]) == first)
533 if (strncasecmp(name, vm->string, len) == 0)
534 return (1);
535 name++;
538 return (0);
539 #endif /* BUILD_REGEX */