Add support for VFS caching.
[herrie-working.git] / herrie / src / vfs.c
blob9d79854b3f14eab178a0dc6c054109245106bd90
1 /*
2 * Copyright (c) 2006-2011 Ed Schouten <ed@80386.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_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, '@' },
47 #ifdef BUILD_XSPF
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' },
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 /**
97 * @brief Concatenate a path- and filename. The resulting filename will
98 * not contain /./'s and /../'s.
100 static char *
101 vfs_path_concat(const char *dir, const char *file, int strict)
103 GString *npath;
104 char *tmp, *off;
105 #ifdef G_OS_UNIX
106 const char *uend;
107 struct passwd *pw;
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);
115 #ifdef G_OS_UNIX
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 */
121 if (uend != NULL) {
122 tmp = g_strndup(file + 1, uend - (file + 1));
123 pw = getpwnam(tmp);
124 g_free(tmp);
125 } else {
126 pw = getpwnam(file + 1);
128 if (pw == NULL)
129 return (NULL);
131 /* Create the new pathname */
132 npath = g_string_new(pw->pw_dir);
133 if (uend != NULL)
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))
142 return (NULL);
144 /* Use the predefined basedir */
145 tmp = g_build_filename(dir, file, NULL);
146 npath = g_string_new(tmp);
147 g_free(tmp);
148 } else {
149 /* Relative filename with no base */
150 return (NULL);
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 */
168 *off = '\0';
169 tmp = strrchr(npath->str, G_DIR_SEPARATOR);
170 if (tmp != NULL) {
171 g_string_erase(npath, tmp - npath->str,
172 (off - tmp) + 3);
173 off = tmp;
174 } else {
175 /* Woops! Too many ..'s */
176 g_string_free(npath, TRUE);
177 return (NULL);
179 } else {
180 /* Nothing useful */
181 off++;
185 if (npath->len == 0)
186 g_string_assign(npath, G_DIR_SEPARATOR_S);
188 return g_string_free(npath, FALSE);
191 const char *
192 vfs_lockup(void)
194 #ifdef G_OS_UNIX
195 const char *root;
196 const char *user;
197 char *rootpath;
198 struct passwd *pw = NULL;
200 user = config_getopt("vfs.lockup.user");
201 if (user[0] != '\0') {
202 pw = getpwnam(user);
203 if (pw == NULL)
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 */
212 res_init();
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);
222 chdir("/");
225 if (pw != NULL) {
226 if (setgid(pw->pw_gid) != 0)
227 return g_strdup_printf(
228 _("Unable to change to group %d\n"),
229 (int)pw->pw_gid);
230 if (setuid(pw->pw_uid) != 0)
231 return g_strdup_printf(
232 _("Unable to change to user %d\n"),
233 (int)pw->pw_uid);
235 #endif /* G_OS_UNIX */
237 return (NULL);
241 * @brief Deallocates the data structures for a VFS entity
243 static void
244 vfs_dealloc(struct vfsent *ve)
246 g_free(ve->name);
247 g_free(ve->filename);
248 g_slice_free(struct vfsent, ve);
251 struct vfsref *
252 vfs_lookup(const char *filename, const char *name, const char *basepath,
253 int strict)
255 char *fn;
256 struct vfsent *ve;
257 struct vfsref *vr;
258 struct stat fs;
259 unsigned int i;
260 int pseudo = 0;
262 fn = vfs_path_concat(basepath, filename, strict);
263 if (fn != NULL && (vr = vfs_cache_lookup(fn)) != NULL)
264 return (vr);
266 /* We only allow files and directories */
267 if (fn == NULL || stat(fn, &fs) != 0) {
268 /* Could be a network stream */
269 pseudo = 1;
270 /* Don't prepend the dirnames */
271 g_free(fn);
272 fn = g_strdup(filename);
273 } else if (!S_ISREG(fs.st_mode) && !S_ISDIR(fs.st_mode)) {
274 /* Device nodes and such */
275 g_free(fn);
276 return (NULL);
279 /* Initialize our new VFS structure with minimal properties */
280 ve = g_slice_new0(struct vfsent);
281 ve->filename = fn;
282 ve->recurse = 1;
283 vfs_list_init(&ve->population);
285 /* The name argument */
286 if (name != NULL) {
287 /* Set a predefined name */
288 ve->name = g_strdup(name);
289 } else if (pseudo) {
290 /* Pseudo file - just copy the URL */
291 ve->name = g_strdup(ve->filename);
292 } else {
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)
301 continue;
303 /* Try to attach the module */
304 ve->vmod = &modules[i];
305 if (ve->vmod->match(ve, S_ISDIR(fs.st_mode)) == 0)
306 goto found;
309 /* No matching VFS module */
310 vfs_dealloc(ve);
311 return (NULL);
313 found:
314 /* Return reference object */
315 ve->refcount = 1;
316 vr = g_slice_new0(struct vfsref);
317 vr->ent = ve;
318 vfs_cache_add(vr);
319 return (vr);
322 struct vfsref *
323 vfs_dup(const struct vfsref *vr)
325 struct vfsent *ve;
326 struct vfsref *rvr;
328 ve = vr->ent;
329 g_atomic_int_inc(&ve->refcount);
331 rvr = g_slice_new(struct vfsref);
332 rvr->ent = ve;
333 rvr->marked = 0;
335 return (rvr);
338 void
339 vfs_close(struct vfsref *vr)
341 struct vfsref *cur;
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);
347 vfs_close(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))
362 return (-1);
364 /* Don't fetch double data */
365 if (!vfs_list_empty(vfs_population(vr)))
366 return (0);
368 return vr->ent->vmod->populate(vr->ent);
371 void
372 vfs_unfold(struct vfslist *vl, const struct vfsref *vr)
374 struct vfsref *cvr;
376 if (vfs_playable(vr)) {
377 /* Single item - add it to the list */
378 vfs_list_insert_tail(vl, vfs_dup(vr));
379 } else {
380 /* See if we can recurse it */
381 vfs_populate(vr);
382 VFS_LIST_FOREACH(&vr->ent->population, cvr) {
383 if (cvr->ent->recurse)
384 vfs_unfold(vl, cvr);
389 void
390 vfs_locate(struct vfslist *vl, const struct vfsref *vr,
391 const struct vfsmatch *vm)
393 struct vfsref *cvr;
395 vfs_populate(vr);
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);
407 struct vfsref *
408 vfs_write_playlist(const struct vfslist *vl, const struct vfsref *vr,
409 const char *filename)
411 const char *base = NULL;
412 char *fn, *nfn;
413 struct vfsref *rvr = NULL;
414 struct vfswriter *wr;
415 unsigned int i;
417 if (vr != NULL)
418 base = vfs_filename(vr);
419 fn = vfs_path_concat(base, filename, 0);
420 if (fn == NULL)
421 return (NULL);
423 /* Search for a matching extension */
424 for (i = 0; i < NUM_WRITERS; i++) {
425 if (g_str_has_suffix(fn, writers[i].ext)) {
426 wr = &writers[i];
427 goto match;
431 /* No extension matched, use default format */
432 wr = &writers[0];
433 nfn = g_strdup_printf("%s%s", fn, wr->ext);
434 g_free(fn);
435 fn = nfn;
436 match:
437 /* Write the playlist to disk */
438 if (wr->write(vl, fn) == 0)
439 rvr = vfs_lookup(fn, NULL, NULL, 0);
441 g_free(fn);
442 return (rvr);
446 vfs_delete(const char *filename)
448 char *fn;
449 int ret;
451 fn = vfs_path_concat(NULL, filename, 0);
452 if (fn == NULL)
453 return (-1);
455 ret = unlink(fn);
456 g_free(fn);
458 return (ret);
461 FILE *
462 vfs_fopen(const char *filename, const char *mode)
464 char *fn;
465 FILE *ret;
467 fn = vfs_path_concat(NULL, filename, 0);
468 if (fn == NULL)
469 return (NULL);
471 ret = fopen(fn, mode);
472 g_free(fn);
474 return (ret);
478 vfs_fgets(char *str, size_t size, FILE *fp)
480 char *eol;
482 if (fgets(str, size, fp) == NULL)
483 return (-1);
485 eol = strchr(str, '\0');
486 g_assert(eol != NULL);
488 /* Strip the final \n */
489 if (--eol >= str && *eol == '\n') {
490 *eol = '\0';
491 /* Strip the final \r */
492 if (--eol >= str && *eol == '\r')
493 *eol = '\0';
496 return (0);
499 struct vfsmatch *
500 vfs_match_new(const char *str)
502 struct vfsmatch *vm;
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);
508 return (NULL);
510 vm->string = g_strdup(str);
512 return (vm);
515 void
516 vfs_match_free(struct vfsmatch *vm)
518 regfree(&vm->regex);
519 g_free(vm->string);
520 g_slice_free(struct vfsmatch, vm);