Prepend current working directory to paths so gstfs still works in background
[gstfs.git] / gstfs.c
blob3451d8c2cce4762591e67faa9fbea2b1108183bf
1 /*
2 * gstfs - a gstreamer filesystem
3 */
4 #include <sys/types.h>
5 #include <dirent.h>
6 #include <pthread.h>
7 #include <stdio.h>
8 #include <string.h>
9 #include <stdlib.h>
10 #include <stddef.h>
11 #include <unistd.h>
12 #include <fuse.h>
13 #include <errno.h>
14 #include <glib.h>
15 #include <gst/gst.h>
16 #include "xcode.h"
18 #define min(a,b) ((a)<(b)?(a):(b))
19 #define max(a,b) ((a)>(b)?(a):(b))
21 #define GSTFS_OPT_KEY(templ, elem, key) \
22 { templ, offsetof(struct gstfs_mount_info, elem), key }
24 /* per-mount options and data structures */
25 struct gstfs_mount_info
27 pthread_mutex_t cache_mutex; /* protects file_cache, cache_lru accesses */
28 GHashTable *file_cache; /* cache of transcoded audio */
29 GQueue *cache_lru; /* queue of items in LRU order */
30 int max_cache_entries; /* max # of entries in the cache */
31 char *src_mnt; /* directory we are mirroring */
32 char *src_ext; /* extension of files we transcode */
33 char *dst_ext; /* extension of target files */
34 char *pipeline; /* gstreamer pipeline */
37 /* This stuff is stored into file_cache by filename */
38 struct gstfs_file_info
40 char *filename; /* hash key */
41 char *src_filename; /* filename in other mount */
42 pthread_mutex_t mutex; /* protects this file info */
43 size_t len; /* size of file */
44 size_t alloc_len; /* allocated size of buf */
45 char *buf; /* completely converted file */
46 GList *list_node; /* pointer for cache_lru */
48 static char *get_source_path(const char *filename);
50 static struct gstfs_mount_info mount_info;
52 void usage(const char *prog)
54 printf("Usage: %s -o [options] mount_point\n\n"
55 "where options can be:\n"
56 " src=[source directory] (required)\n"
57 " src_ext=[mp3|ogg|...] (required)\n"
58 " dst_ext=[mp3|ogg|...] (required)\n"
59 " pipeline=[gst pipeline] (required)\n"
60 " ncache=[0-9]* (optional)\n",
61 prog);
65 * Create a new gstfs_file_info object using the specified destination file.
67 struct gstfs_file_info *get_file_info(const char *filename)
69 struct gstfs_file_info *fi;
71 fi = calloc(1, sizeof(struct gstfs_file_info));
72 fi->filename = g_strdup(filename);
73 fi->src_filename = get_source_path(filename);
74 pthread_mutex_init(&fi->mutex, NULL);
75 return fi;
79 * Release a previously allocated gstfs_file_info object.
81 void put_file_info(struct gstfs_file_info *fi)
83 g_free(fi->filename);
84 g_free(fi->src_filename);
85 free(fi);
89 * Given a filename with extension "search", return a possibly reallocated
90 * string with "replace" on the end.
92 char *replace_ext(char *filename, char *search, char *replace)
94 char *ext = strrchr(filename, '.');
95 if (ext && strcmp(ext+1, search) == 0)
97 *(ext+1) = 0;
98 filename = g_strconcat(filename, replace, NULL);
100 return filename;
104 * Return true if filename has extension dst_ext.
106 int is_target_type(const char *filename)
108 char *ext = strrchr(filename, '.');
109 return (ext && strcmp(ext+1, mount_info.dst_ext) == 0);
113 * Remove items from the file cache until below the maximum.
114 * This is relatively quick since we can find elements by looking at the
115 * head of the lru list and then do a single hash lookup to remove from
116 * the hash table.
118 * Called with cache_mutex held.
120 static void expire_cache()
122 struct gstfs_file_info *fi;
124 while (g_queue_get_length(mount_info.cache_lru) >
125 mount_info.max_cache_entries)
127 fi = (struct gstfs_file_info *) g_queue_pop_head(mount_info.cache_lru);
128 g_hash_table_remove(mount_info.file_cache, fi);
129 put_file_info(fi);
134 * If the path represents a file in the mirror filesystem, then
135 * look for it in the cache. If not, create a new file info.
137 * If it isn't a mirror file, return NULL.
139 static struct gstfs_file_info *gstfs_lookup(const char *path)
141 struct gstfs_file_info *ret;
143 if (!is_target_type(path))
144 return NULL;
146 pthread_mutex_lock(&mount_info.cache_mutex);
147 ret = g_hash_table_lookup(mount_info.file_cache, path);
148 if (!ret)
150 ret = get_file_info(path);
151 if (!ret)
152 goto out;
154 g_hash_table_replace(mount_info.file_cache, ret->filename, ret);
157 // move to end of LRU
158 if (ret->list_node)
159 g_queue_unlink(mount_info.cache_lru, ret->list_node);
161 g_queue_push_tail(mount_info.cache_lru, ret);
162 ret->list_node = mount_info.cache_lru->tail;
164 expire_cache();
166 out:
167 pthread_mutex_unlock(&mount_info.cache_mutex);
168 return ret;
172 * Given a filename from the fuse mount, return the corresponding filename
173 * in the mirror.
175 static char *get_source_path(const char *filename)
177 char *source;
179 source = g_strdup_printf("%s%s", mount_info.src_mnt, filename);
180 source = replace_ext(source, mount_info.dst_ext, mount_info.src_ext);
181 return source;
184 static char *canonize(const char *cwd, const char *filename)
186 if (filename[0] == '/')
187 return g_strdup(filename);
188 else
189 return g_strdup_printf("%s/%s", cwd, filename);
192 int gstfs_statfs(const char *path, struct statvfs *buf)
194 char *source_path;
196 source_path = get_source_path(path);
197 if (statvfs(source_path, buf))
198 return -errno;
200 g_free(source_path);
201 return 0;
204 int gstfs_getattr(const char *path, struct stat *stbuf)
206 int ret = 0;
207 char *source_path;
208 struct gstfs_file_info *converted;
210 source_path = get_source_path(path);
212 if (stat(source_path, stbuf))
213 ret = -errno;
214 else if ((converted = gstfs_lookup(path)))
215 stbuf->st_size = converted->len;
217 g_free(source_path);
218 return ret;
221 static int read_cb(char *buf, size_t size, void *data)
223 struct gstfs_file_info *info = (struct gstfs_file_info *) data;
225 size_t newsz = info->len + size;
227 if (info->alloc_len < newsz)
229 info->alloc_len = max(info->alloc_len * 2, newsz);
230 info->buf = realloc(info->buf, info->alloc_len);
231 if (!info->buf)
232 return -ENOMEM;
235 memcpy(&info->buf[info->len], buf, size);
236 info->len += size;
237 return 0;
240 int gstfs_read(const char *path, char *buf, size_t size, off_t offset,
241 struct fuse_file_info *fi)
243 struct gstfs_file_info *info = gstfs_lookup(path);
244 size_t count = 0;
246 if (!info)
247 return -ENOENT;
249 pthread_mutex_lock(&info->mutex);
251 if (!info->buf)
252 transcode(mount_info.pipeline, info->src_filename, read_cb, info);
254 if (info->len <= offset)
255 goto out;
257 count = min(info->len - offset, size);
259 memcpy(buf, &info->buf[offset], count);
261 out:
262 pthread_mutex_unlock(&info->mutex);
263 return count;
266 int gstfs_open(const char *path, struct fuse_file_info *fi)
268 struct gstfs_file_info *info = gstfs_lookup(path);
269 if (!info)
270 return -ENOENT;
272 return 0;
276 * Copy all entries from source mount, replacing extensions along the way.
278 int gstfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
279 off_t offset, struct fuse_file_info *fi)
281 struct dirent *dirent;
282 DIR *dir;
283 char *source_path;
285 source_path = get_source_path(path);
286 dir = opendir(source_path);
288 if (!dir)
289 return -ENOENT;
291 while ((dirent = readdir(dir)))
293 char *s = g_strdup(dirent->d_name);
294 s = replace_ext(s, mount_info.src_ext, mount_info.dst_ext);
295 filler(buf, s, NULL, 0);
297 g_free(s);
299 closedir(dir);
301 return 0;
304 static struct fuse_operations gstfs_opers = {
305 .readdir = gstfs_readdir,
306 .statfs = gstfs_statfs,
307 .getattr = gstfs_getattr,
308 .open = gstfs_open,
309 .read = gstfs_read
312 static struct fuse_opt gstfs_opts[] = {
313 GSTFS_OPT_KEY("src=%s", src_mnt, 0),
314 GSTFS_OPT_KEY("src_ext=%s", src_ext, 0),
315 GSTFS_OPT_KEY("dst_ext=%s", dst_ext, 0),
316 GSTFS_OPT_KEY("ncache=%d", max_cache_entries, 0),
317 GSTFS_OPT_KEY("pipeline=%s", pipeline, 0),
318 FUSE_OPT_END
322 int main(int argc, char *argv[])
324 char pwd[2048];
325 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
327 if (fuse_opt_parse(&args, &mount_info, gstfs_opts, NULL) == -1)
328 return -1;
330 if (!mount_info.src_mnt ||
331 !mount_info.src_ext ||
332 !mount_info.dst_ext ||
333 !mount_info.pipeline)
335 usage(argv[0]);
336 return -1;
339 if (!getcwd(pwd, sizeof(pwd)))
341 perror("gstfs");
342 return -1;
345 mount_info.src_mnt = canonize(pwd, mount_info.src_mnt);
347 if (mount_info.max_cache_entries == 0)
348 mount_info.max_cache_entries = 50;
350 pthread_mutex_init(&mount_info.cache_mutex, NULL);
351 mount_info.file_cache = g_hash_table_new(g_str_hash, g_str_equal);
352 mount_info.cache_lru = g_queue_new();
354 gst_init(&argc, &argv);
355 return fuse_main(args.argc, args.argv, &gstfs_opers, NULL);