41c6ed6042f6ca9462b02f19b45f6136a8eb0ae3
[rrq/fusefile.git] / fusefile.c
1 /***
2     fusefile - overlay a file path with a concatenation of parts of
3     other files, read only.
4
5     Copyright (C) 2019  Ralph Ronnquist
6
7     This program is free software: you can redistribute it and/or
8     modify it under the terms of the GNU General Public License as
9     published by the Free Software Foundation, either version 3 of the
10     License, or (at your option) any later version.
11
12     This program is distributed in the hope that it will be useful,
13     but WITHOUT ANY WARRANTY; without even the implied warranty of
14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15     General Public License for more details.
16
17     You should have received a copy of the GNU General Public License
18     along with this program. If not, see
19     <http://www.gnu.org/licenses/>.
20
21     This source was inspired by the "null.c" example of the libfuse
22     sources, which is distributed under GPL2, and copyright (C)
23     2001-2007 Miklos Szeredi <miklos@szeredi.hu>.
24 */
25
26 #define FUSE_USE_VERSION 33
27
28 #include <fuse.h>
29 #include <fuse/fuse_lowlevel.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
34 #include <time.h>
35 #include <errno.h>
36
37 struct Source {
38     char *filename;
39     ssize_t from;
40     ssize_t to;
41     ssize_t start; // starting position in concatenated file
42     int fd;
43 };
44
45 static struct {
46     struct Source *array;
47     int count;
48     int limit;
49     ssize_t size;
50 } sources;
51
52 #define SOURCEARRAYP(i) ((void*)&sources.array[ i ])
53
54 /**
55  * Holds info about scratch pad file for 'write' events.
56  */
57 static struct {
58     char *filename;
59     int fd;
60 } pad;
61
62 #if DEBUG
63 static void print_source(struct Source *p) {
64     fprintf( stderr, "%p { %s, %ld, %ld, %ld, %d }\n",
65              p, p->filename, p->from, p->to, p->start, p->fd );
66 }
67 #endif
68
69 // Scan the source specification, and return the length of the
70 // inclusion. "filename/from,to"
71 // filename
72 // filename/from
73 // filename/-to
74 // filename/from-to
75 static size_t scan_source(char *in,struct Source *p) {
76     int e = strlen( in );
77     int i = e-1;
78     int s = -1;
79     int m = -1;
80     // scan for last '/' and last '-'
81     for ( ; i >= 0; i-- ) {
82         if ( in[i] == '/' ) {
83             s = i;
84             break;
85         }
86         if ( in[i] == '-' ) {
87             m = i;
88         }
89     }
90 #if DEBUG
91     fprintf( stderr, "m=%d s=%d\n", m, s );
92 #endif
93     // Copy the filename, and set from and to
94     p->filename = strndup( in, ( s < 0 )? e : s );
95     struct stat buf;
96     if ( stat( p->filename, & buf ) ) {
97         perror( p->filename );
98         return 1; 
99     }
100     p->from = ( s < 0 )? 0 : atol( in+s+1 );
101     if ( p->from < 0 ) {
102         p->from = 0;
103     }
104 #if DEBUG
105     fprintf( stderr, "p->from=%ld\n", p->from );
106 #endif
107     p->to = ( m < 0 )? buf.st_size : atol( in+m+1 );
108     if ( p->from > p->to || p->to > buf.st_size ) {
109         return 1;
110     }
111     return 0;
112 }
113
114 static int setup_sources(char **argv,int i,int n) {
115     sources.array = calloc( n, sizeof( struct Source ) );
116     if ( sources.array == 0 ) {
117         return 1;
118     }
119     sources.count = n;
120     sources.limit = n;
121     int j = 0;
122     sources.size = 0;
123     for ( ; j < n; i++, j++ ) {
124         struct Source *p = sources.array + j;
125         if ( scan_source( argv[i], p ) ) {
126             // should free everything malloc-ed
127             return 1;
128         }
129         p->start = sources.size;
130         sources.size += p->to - p->from;
131         p->fd = open( p->filename, O_RDONLY );
132         if ( p->fd < 0 ) {
133             perror( p->filename );
134             return 1;
135         }
136 #if DEBUG
137         print_source( p );
138 #endif
139     }
140     return 0;
141 }
142
143 static int fusefile_getattr(const char *path,struct stat *stbuf) {
144 #if DEBUG
145     fprintf( stderr, "fusefile_getattr( %s )\n", path );
146 #endif
147     if ( strcmp( path, "/" ) != 0 ) {
148         return -ENOENT;
149     }
150 #if DEBUG
151     fprintf( stderr, "getattr %ld\n", sources.size );
152 #endif
153     memset( stbuf, 0, sizeof( struct stat ) );
154     stbuf->st_mode = S_IFREG | 0444; // Hmmm
155     if ( pad.filename ) {
156         stbuf->st_mode |= 0200;
157     }
158     stbuf->st_nlink = 1;
159     stbuf->st_size = sources.size;
160     time_t now = time( 0 );
161     stbuf->st_atime = now;
162     stbuf->st_mtime = now;
163     stbuf->st_ctime = now;
164     stbuf->st_uid = getuid();
165     stbuf->st_gid = getgid();
166     return 0;
167 }
168
169 static int fusefile_open(const char *path,struct fuse_file_info *fi) {
170 #if DEBUG
171     fprintf( stderr, "fusefile_open( %s, %d )\n", path, fi->flags );
172     fprintf( stderr, "fixing( %d )\n", fi->flags | O_CLOEXEC );
173 #endif
174     if ( strcmp( path, "/" ) != 0 ) {
175         return -ENOENT;
176     }
177     // set O-CLOEXEC  for this opening?
178     return 0;
179 }
180
181 static int find_source(off_t offset) {
182     int lo = 0;
183     int hi = sources.count;
184     if ( offset > sources.size ) {
185         return -1;
186     }
187     while ( lo < hi ) {
188         int m = ( lo + hi ) / 2;
189         if ( sources.array[m].start > offset ) {
190             hi = m;
191         } else if ( m+1 < hi && sources.array[m+1].start < offset ) {
192             lo = m+1;
193         } else {
194             return m;
195         }
196     }
197     return lo;
198 }
199
200 /**
201  * Insert a source fragment description into the table at <off>.
202  */
203 static int insert_source(struct Source *source,size_t off) {
204     int index = find_source( off );
205     // Ensure at least 5 "free" Sources in <source.array>
206     // and allocate space for 20 new otherwise.
207     if ( sources.count + 5 > sources.limit ) {
208         size_t size = sources.limit + 20;
209         struct Source *new = realloc(
210             sources.array, size * sizeof( struct Source ) );
211         if ( new == 0 ) {
212             return -1;
213         }
214         sources.array = new;
215         sources.limit = size;
216     }
217     if ( index < sources.count ) {
218         ssize_t b = ( sources.count - index ) * sizeof(struct Source);
219         if ( sources.array[ index ].start < off ) {
220             // Split the <index> record at <off>
221             // and adjust index
222             b *= 2;
223             memcpy( SOURCEARRAYP( index+2 ), SOURCEARRAYP( index ), b );
224             sources.count += 2;
225             b = off - sources.array[ index ].start;
226             sources.array[ index + 2 ].from += b; // adjust tail fragment
227             sources.array[ index++ ].to = b; // adjust head fragment
228         } else {
229             // Insert the new source at <index>
230             memcpy( SOURCEARRAYP( index+1 ), SOURCEARRAYP( index ), b );
231             sources.count += 1;
232         }
233     } else {
234         // Append the new source to <sources> (at <index>)
235         sources.count += 1;
236     }
237     sources.array[ index ].filename = source->filename;
238     sources.array[ index ].fd = source->fd;
239     sources.array[ index ].from = source->from;
240     sources.array[ index ].to = source->to;
241     for ( ; index < sources.count; index++ ) {
242         sources.array[ index ].start = off;
243         off += sources.array[ index ].to - sources.array[ index ].from;
244     }
245     sources.size = off;
246     return index;
247 }
248
249 // Read <size> bytes from <off> in file
250 static int fusefile_read(const char *path, char *buf, size_t size,
251                          off_t off, struct fuse_file_info *fi)
252 {
253 #if DEBUG
254     fprintf( stderr, "fusefile_read( %s )\n", path );
255 #endif
256     if ( strcmp( path, "/" ) != 0 ) {
257         return -ENOENT;
258     }
259 #if DEBUG
260     fprintf( stderr, "read %ld %ld\n", off, size );
261 #endif
262     size_t rr = 0;
263     while ( size > 0 ) {
264 #if DEBUG
265         fprintf( stderr, "find_source %ld %ld\n", off, size );
266 #endif
267         int i = find_source( off );
268         if ( i < 0 ) {
269             return -ENOENT;
270         }
271         if ( sources.array[i].fd < 0 ) {
272             return -ENOENT;
273         }
274 #if DEBUG
275         print_source( &sources.array[i] );
276 #endif
277         size_t b = off - sources.array[i].start + sources.array[i].from;
278         size_t n = sources.array[i].to - b;
279         if ( n > size ) {
280             n = size;
281         }
282         if ( lseek( sources.array[i].fd, b, SEEK_SET ) < 0 ) {
283             perror( sources.array[i].filename );
284             return -ENOENT;
285         }
286 #if DEBUG
287         fprintf( stderr, "get %ld bytes at %ld\n", n, rr );
288 #endif
289         ssize_t r = read( sources.array[i].fd, buf + rr, n );
290 #if DEBUG
291         fprintf( stderr, "got %ld bytes\n", r );
292 #endif
293         if ( r < 0 ) {
294             perror( sources.array[i].filename );
295             return -ENOENT;
296         }
297         if ( r == 0 ) {
298             break;
299         }
300         rr += r;
301         off += r;
302         size -= r;
303     }
304     return rr;
305 }
306
307 /**
308  * Write a full block of data.
309  */
310 static int write_block(int fd,const char *buf,size_t size) {
311     size_t orig = size;
312     while ( size > 0 ) {
313         ssize_t n = write( fd, buf, size );
314         if ( n <= 0 ) {
315             return n;
316         }
317     }
318     return orig;
319 }
320
321 /**
322  * Insert a fragment at <off>. The data is appended to the pad file,
323  * and a descriptor is inserted; the fragment containing <off> is
324  * first split, unless <off> is at its start, and then new fragment
325  * descriptor is inserted.
326  */
327 static int fusefile_write(const char *path, const char *buf, size_t size,
328                           off_t off, struct fuse_file_info *fi)
329 {
330 #if DEBUG
331     fprintf( stderr, "fusefile_write( %s )\n", path );
332 #endif
333     if ( strcmp( path, "/" ) != 0 ) {
334         return -ENOENT;
335     }
336 #if DEBUG
337     fprintf( stderr, "write %ld %ld\n", off, size );
338 #endif
339     // Ensure a pad was nominated
340     if ( pad.filename == 0 ) {
341         return 1;
342     }
343     static char meta[ 100 ];
344     sprintf( meta, "%ld\n%ld\n", off, size );
345     if ( write_block( pad.fd, meta, strlen( meta ) ) <= 0 ) {
346         perror( pad.filename );
347         return -EIO;
348     }
349     struct Source source = {
350         .filename = pad.filename,
351         .fd = pad.fd,
352         .from = lseek( pad.fd, 0, SEEK_END ),
353         .to = 0,
354         .start = 0
355     };
356     ssize_t n = write_block( pad.fd, buf, size );
357     if ( n != size ) {
358         return n;
359     }
360     source.to = source.from + size;
361     insert_source( &source, off );
362     return size;
363 }
364
365 static void fusefile_destroy(void *data) {
366     char *mnt = (char*) data; // As passed to fuse_main
367 #if DEBUG
368     fprintf( stderr, "fusefile_destroy( %s )\n", mnt? mnt : "" );
369 #endif
370     if ( mnt ) {
371         unlink( mnt );
372     }
373 }
374
375 static int fusefile_flush(const char *path, struct fuse_file_info *info) {
376 #if DEBUG
377     fprintf( stderr, "fusefile_flush( %s )\n", path );
378 #endif
379     if ( strcmp( path, "/" ) != 0 ) {
380         return -ENOENT;
381     }
382     return 0;
383 }
384
385 static int fusefile_release(const char *path, struct fuse_file_info *fi) {
386 #if DEBUG
387     fprintf( stderr, "fusefile_release( %s, %d )\n", path, fi->flags );
388 #endif
389     if ( strcmp( path, "/" ) != 0 ) {
390         return -ENOENT;
391     }
392     return 0;
393 }
394
395 static int fusefile_fsync(const char *path, int x, struct fuse_file_info *fi) {
396 #if DEBUG
397     fprintf( stderr, "fusefile_fsync( %s, %d )\n", path, x );
398 #endif
399     if ( strcmp( path, "/" ) != 0 ) {
400         return -ENOENT;
401     }
402     return 0;
403 }
404
405 static struct fuse_operations fusefile_oper = {
406     .getattr = fusefile_getattr,
407     .open = fusefile_open,
408     .read = fusefile_read,
409     .write = fusefile_write,
410     .destroy = fusefile_destroy,
411     .flush = fusefile_flush,
412     .release = fusefile_release,
413     .fsync = fusefile_fsync,
414     //void *(*init) (struct fuse_conn_info *conn);
415     //int (*write_buf) (const char *, struct fuse_bufvec *buf, off_t off,
416     //            struct fuse_file_info *);
417     //.truncate = fusefile_truncate,
418     //.flush = fusefile_flush,
419     //.release = fusefile_release,
420 };
421
422 static void usage() {
423     char *usage =
424 "Usage: fusefile [ <fuse options> ] <mount> <file/from-to> ... \n"
425 "Mounts a virtual, read-only file that is a concatenation of file fragments\n"
426         ;
427     fprintf( stderr, "%s", usage );
428     exit( 1 );
429 }
430
431 /**
432  * Set up the arguments for the fuse_main call, adding our own.
433  */
434 static int setup_argv(int argc,char ***argv) {
435     int n = argc + 1;
436     char **out = calloc( n--, sizeof( char* ) );
437     memcpy( (void*) out, (void*) (*argv), argc-- * sizeof( char* ) ); 
438     out[ n++ ] = out[ argc ]; // mount point
439     out[ argc++ ] = "-odefault_permissions";
440     (*argv) = out;
441     return n;
442 }
443
444 /**
445  * Mount a concatenation of files,
446  * [ <fuse options> ] <mount> <file/from-to> ...
447  */
448 int main(int argc, char *argv[])
449 {
450     char *mnt;
451     int mt;
452     int fg;
453     int i;
454     int fuseargc;
455     struct stat stbuf;
456     int temporary = 0;
457     // Scan past options
458     for ( i = 1; i < argc; i++ ) {
459         if ( *argv[i] != '-' ) {
460             break;
461         }
462     }
463     if ( i > argc - 2 ) { // At least mount point plus one source
464         usage();
465     }
466     mnt = argv[ i++ ]; // First non-option argument is the mount pount
467     fuseargc = i;
468     if ( strncmp( argv[ i ], "pad=", 4 ) == 0 ) {
469         // First argument is the pad, if any, signaled with "pad=" prefix
470         pad.filename = argv[ i++ ] + 4; // (also move arg index)
471 #if DEBUG
472         fprintf( stderr, "scratch pad=%s\n", pad.filename );
473 #endif
474         pad.fd = open( pad.filename, O_RDWR | O_CREAT, 0600 );
475         if ( pad.fd < 0 ) {
476             perror( pad.filename );
477             exit( errno );
478         }
479         lseek( pad.fd, 0, SEEK_END ); // initial seek
480     }
481     if ( setup_sources( argv, i, argc-i ) ) {
482         return 1;
483     }
484     if ( stat( mnt, &stbuf ) == -1 ) {
485         int fd = open( mnt, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR );
486         if ( fd < 0 ) {
487             perror( mnt );
488             return 1;
489         }
490         temporary = 1;
491         close( fd );
492     } else if ( ! S_ISREG( stbuf.st_mode ) ) {
493         fprintf( stderr, "mountpoint is not a regular file\n" );
494         return 1;
495     }
496     fuseargc = setup_argv( fuseargc, &argv );
497     struct fuse_args args = FUSE_ARGS_INIT( fuseargc, argv );
498     if ( fuse_parse_cmdline( &args, &mnt, &mt, &fg ) ) {
499         return 1;
500     }
501     fuse_opt_free_args( &args );
502     if ( ! mnt ) {
503         fprintf( stderr, "missing mountpoint parameter\n" );
504         return 1;
505     }
506     return fuse_main( fuseargc, argv, &fusefile_oper, temporary? mnt : NULL );
507 }