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