1 /*
2  * MP3FS: A read-only FUSE filesystem which transcodes audio formats
3  * (currently FLAC) to MP3 on the fly when opened and read. See README
4  * for more details.
5  *
6  * Copyright (C) 2006-2008 David Collett
7  * Copyright (C) 2008-2012 K. Henriksson
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22  */
23 
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <errno.h>
28 #include <dirent.h>
29 #include <limits.h>
30 #include <fcntl.h>
31 #include <unistd.h>
32 
33 #include "transcode.h"
34 
35 /*
36  * Translate file names from FUSE to the original absolute path. A buffer
37  * is allocated using malloc for the translated path. It is the caller's
38  * responsibility to free it.
39  */
translate_path(const char * path)40 char* translate_path(const char* path) {
41     char* result;
42     /*
43      * Allocate buffer. The +2 is for the terminating '\0' and to
44      * accomodate possibly translating .mp3 to .flac later.
45      */
46     result = malloc(strlen(params.basepath) + strlen(path) + 2);
47 
48     if (result) {
49         strcpy(result, params.basepath);
50         strcat(result, path);
51     }
52 
53     return result;
54 }
55 
56 /* Convert file name from source to destination name. The new extension will
57  * be copied in place, and the passed path must be large enough to hold the
58  * new name.
59  */
transcoded_name(char * path)60 void transcoded_name(char* path) {
61     char* ext = strrchr(path, '.');
62 
63     if (ext && check_decoder(ext + 1)) {
64         strcpy(ext + 1, params.desttype);
65     }
66 }
67 
68 /*
69  * Given the destination (post-transcode) file name, determine the name of
70  * the original file to be transcoded. The new extension will be copied in
71  * place, and the passed path must be large enough to hold the new name.
72  */
find_original(char * path)73 void find_original(char* path) {
74     char* ext = strrchr(path, '.');
75 
76     if (ext && strcmp(ext + 1, params.desttype) == 0) {
77         for (size_t i=0; i<decoder_list_len; ++i) {
78             strcpy(ext + 1, decoder_list[i]);
79             if (access(path, F_OK) == 0) {
80                 /* File exists with this extension */
81                 return;
82             } else {
83                 /* File does not exist; not an error */
84                 errno = 0;
85             }
86         }
87         /* Source file exists with no supported extension, restore path */
88         strcpy(ext + 1, params.desttype);
89     }
90 }
91 
mp3fs_readlink(const char * path,char * buf,size_t size)92 static int mp3fs_readlink(const char *path, char *buf, size_t size) {
93     char* origpath;
94     ssize_t len;
95 
96     mp3fs_debug("readlink %s", path);
97 
98     errno = 0;
99 
100     origpath = translate_path(path);
101     if (!origpath) {
102         goto translate_fail;
103     }
104 
105     find_original(origpath);
106 
107     len = readlink(origpath, buf, size - 2);
108     if (len == -1) {
109         goto readlink_fail;
110     }
111 
112     buf[len] = '\0';
113 
114     transcoded_name(buf);
115 
116 readlink_fail:
117     free(origpath);
118 translate_fail:
119     return -errno;
120 }
121 
mp3fs_readdir(const char * path,void * buf,fuse_fill_dir_t filler,off_t offset,struct fuse_file_info * fi)122 static int mp3fs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
123                          off_t offset, struct fuse_file_info *fi) {
124     (void)offset;
125     (void)fi;
126     char* origpath;
127     char* origfile;
128     DIR *dp;
129     struct dirent *de;
130 
131     mp3fs_debug("readdir %s", path);
132 
133     errno = 0;
134 
135     origpath = translate_path(path);
136     if (!origpath) {
137         goto translate_fail;
138     }
139 
140     /* 2 for directory separator and NULL byte */
141     origfile = malloc(strlen(origpath) + NAME_MAX + 2);
142     if (!origfile) {
143         goto origfile_fail;
144     }
145 
146     dp = opendir(origpath);
147     if (!dp) {
148         goto opendir_fail;
149     }
150 
151     while ((de = readdir(dp))) {
152         struct stat st;
153 
154         snprintf(origfile, strlen(origpath) + NAME_MAX + 2, "%s/%s", origpath,
155                  de->d_name);
156 
157         if (lstat(origfile, &st) == -1) {
158             goto stat_fail;
159         } else {
160             if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
161                 // TODO: Make this safe if converting from short to long ext.
162                 transcoded_name(de->d_name);
163             }
164         }
165 
166         if (filler(buf, de->d_name, &st, 0)) break;
167     }
168 
169 stat_fail:
170     closedir(dp);
171 opendir_fail:
172     free(origfile);
173 origfile_fail:
174     free(origpath);
175 translate_fail:
176     return -errno;
177 }
178 
mp3fs_getattr(const char * path,struct stat * stbuf)179 static int mp3fs_getattr(const char *path, struct stat *stbuf) {
180     char* origpath;
181     struct transcoder* trans;
182 
183     mp3fs_debug("getattr %s", path);
184 
185     errno = 0;
186 
187     origpath = translate_path(path);
188     if (!origpath) {
189         goto translate_fail;
190     }
191 
192     /* pass-through for regular files */
193     if (lstat(origpath, stbuf) == 0) {
194         goto passthrough;
195     } else {
196         /* Not really an error. */
197         errno = 0;
198     }
199 
200     find_original(origpath);
201 
202     if (lstat(origpath, stbuf) == -1) {
203         goto stat_fail;
204     }
205 
206     /*
207      * Get size for resulting mp3 from regular file, otherwise it's a
208      * symbolic link. */
209     if (S_ISREG(stbuf->st_mode)) {
210         trans = transcoder_new(origpath);
211         if (!trans) {
212             goto transcoder_fail;
213         }
214 
215         stbuf->st_size = transcoder_get_size(trans);
216         stbuf->st_blocks = (stbuf->st_size + 512 - 1) / 512;
217 
218         transcoder_finish(trans);
219         transcoder_delete(trans);
220     }
221 
222 transcoder_fail:
223 stat_fail:
224 passthrough:
225     free(origpath);
226 translate_fail:
227     return -errno;
228 }
229 
mp3fs_open(const char * path,struct fuse_file_info * fi)230 static int mp3fs_open(const char *path, struct fuse_file_info *fi) {
231     char* origpath;
232     struct transcoder* trans;
233     int fd;
234 
235     mp3fs_debug("open %s", path);
236 
237     errno = 0;
238 
239     origpath = translate_path(path);
240     if (!origpath) {
241         goto translate_fail;
242     }
243 
244     fd = open(origpath, fi->flags);
245 
246     /* File does exist, but can't be opened. */
247     if (fd == -1 && errno != ENOENT) {
248         goto open_fail;
249     } else {
250         /* Not really an error. */
251         errno = 0;
252     }
253 
254     /* File is real and can be opened. */
255     if (fd != -1) {
256         close(fd);
257         goto passthrough;
258     }
259 
260     find_original(origpath);
261 
262     trans = transcoder_new(origpath);
263     if (!trans) {
264         goto transcoder_fail;
265     }
266 
267     /* Store transcoder in the fuse_file_info structure. */
268     fi->fh = (uint64_t)trans;
269 
270 transcoder_fail:
271 passthrough:
272 open_fail:
273     free(origpath);
274 translate_fail:
275     return -errno;
276 }
277 
mp3fs_read(const char * path,char * buf,size_t size,off_t offset,struct fuse_file_info * fi)278 static int mp3fs_read(const char *path, char *buf, size_t size, off_t offset,
279                       struct fuse_file_info *fi) {
280     char* origpath;
281     int fd;
282     ssize_t read = 0;
283     struct transcoder* trans;
284 
285     mp3fs_debug("read %s: %zu bytes from %jd", path, size, (intmax_t)offset);
286 
287     errno = 0;
288 
289     origpath = translate_path(path);
290     if (!origpath) {
291         goto translate_fail;
292     }
293 
294     /* If this is a real file, pass the call through. */
295     fd = open(origpath, O_RDONLY);
296     if (fd != -1) {
297         read = pread(fd, buf, size, offset);
298         close(fd);
299         goto passthrough;
300     } else if (errno != ENOENT) {
301         /* File does exist, but can't be opened. */
302         goto open_fail;
303     } else {
304         /* File does not exist, and this is fine. */
305         errno = 0;
306     }
307 
308     trans = (struct transcoder*)fi->fh;
309 
310     if (!trans) {
311         mp3fs_error("Tried to read from unopen file: %s", origpath);
312         goto transcoder_fail;
313     }
314 
315     read = transcoder_read(trans, buf, offset, size);
316 
317 transcoder_fail:
318 passthrough:
319 open_fail:
320     free(origpath);
321 translate_fail:
322     if (read) {
323         return (int)read;
324     } else {
325         return -errno;
326     }
327 }
328 
mp3fs_statfs(const char * path,struct statvfs * stbuf)329 static int mp3fs_statfs(const char *path, struct statvfs *stbuf) {
330     char* origpath;
331 
332     mp3fs_debug("statfs %s", path);
333 
334     errno = 0;
335 
336     origpath = translate_path(path);
337     if (!origpath) {
338         goto translate_fail;
339     }
340 
341     statvfs(origpath, stbuf);
342 
343     free(origpath);
344 translate_fail:
345     return -errno;
346 }
347 
mp3fs_release(const char * path,struct fuse_file_info * fi)348 static int mp3fs_release(const char *path, struct fuse_file_info *fi) {
349     struct transcoder* trans;
350 
351     mp3fs_debug("release %s", path);
352 
353     trans = (struct transcoder*)fi->fh;
354     if (trans) {
355         transcoder_finish(trans);
356         transcoder_delete(trans);
357     }
358 
359     return 0;
360 }
361 
362 /* We need synchronous reads. */
mp3fs_init(struct fuse_conn_info * conn)363 static void *mp3fs_init(struct fuse_conn_info *conn) {
364     conn->async_read = 0;
365 
366     return NULL;
367 }
368 
369 struct fuse_operations mp3fs_ops = {
370     .getattr  = mp3fs_getattr,
371     .readlink = mp3fs_readlink,
372     .readdir  = mp3fs_readdir,
373     .open     = mp3fs_open,
374     .read     = mp3fs_read,
375     .statfs   = mp3fs_statfs,
376     .release  = mp3fs_release,
377     .init     = mp3fs_init,
378 };
379