change version
[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 static struct {
53     time_t atime;
54     time_t mtime;
55     time_t ctime;
56 } times;
57     
58 #define SOURCEARRAYP(i) ((void*)&sources.array[ i ])
59
60 /**
61  * Holds info about scratch pad file for 'write' events.
62  */
63 static struct {
64     char *filename;
65     int fd;
66 } pad;
67
68 #if DEBUG
69 static void print_source(struct Source *p) {
70     fprintf( stderr, "%p { %s, %ld, %ld, %ld, %d }\n",
71              p, p->filename, p->from, p->to, p->start, p->fd );
72 }
73 #endif
74
75 // Scan the source specification, and return the length of the
76 // inclusion. "filename/from,to"
77 // filename
78 // filename/from
79 // filename/-to
80 // filename/from-to
81 static size_t scan_source(char *in,struct Source *p) {
82     int e = strlen( in );
83     int i = e-1;
84     int s = -1;
85     int m = -1;
86     // scan for last '/' and last '-'
87     for ( ; i >= 0; i-- ) {
88         if ( in[i] == '/' ) {
89             s = i;
90             break;
91         }
92         if ( in[i] == '-' ) {
93             m = i;
94         }
95     }
96 #if DEBUG
97     fprintf( stderr, "m=%d s=%d\n", m, s );
98 #endif
99     // Copy the filename, and set from and to
100     p->filename = strndup( in, ( s < 0 )? e : s );
101     struct stat buf;
102     if ( stat( p->filename, & buf ) ) {
103         perror( p->filename );
104         return 1; 
105     }
106     p->from = ( s < 0 )? 0 : atol( in+s+1 );
107     if ( p->from < 0 ) {
108         p->from = 0;
109     }
110 #if DEBUG
111     fprintf( stderr, "p->from=%ld\n", p->from );
112 #endif
113     p->to = ( m < 0 )? buf.st_size : atol( in+m+1 );
114     if ( p->from > p->to || p->to > buf.st_size ) {
115         return 1;
116     }
117     return 0;
118 }
119
120 static int setup_sources(char **argv,int i,int n) {
121     sources.array = calloc( n, sizeof( struct Source ) );
122     if ( sources.array == 0 ) {
123         return 1;
124     }
125     sources.count = n;
126     sources.limit = n;
127     int j = 0;
128     sources.size = 0;
129     for ( ; j < n; i++, j++ ) {
130         struct Source *p = sources.array + j;
131         if ( scan_source( argv[i], p ) ) {
132             // should free everything malloc-ed
133             return 1;
134         }
135         p->start = sources.size;
136         sources.size += p->to - p->from;
137         p->fd = open( p->filename, O_RDONLY );
138         if ( p->fd < 0 ) {
139             perror( p->filename );
140             return 1;
141         }
142 #if DEBUG
143         print_source( p );
144 #endif
145     }
146     return 0;
147 }
148
149 static int fusefile_getattr(const char *path,struct stat *stbuf) {
150 #if DEBUG
151     fprintf( stderr, "fusefile_getattr( %s )\n", path );
152 #endif
153     if ( strcmp( path, "/" ) != 0 ) {
154         return -ENOENT;
155     }
156 #if DEBUG
157     fprintf( stderr, "getattr %ld\n", sources.size );
158 #endif
159     memset( stbuf, 0, sizeof( struct stat ) );
160     stbuf->st_mode = S_IFREG | 0444; // Hmmm
161     if ( pad.filename ) {
162         stbuf->st_mode |= 0200;
163     }
164     stbuf->st_nlink = 1;
165     stbuf->st_size = sources.size;
166     stbuf->st_atime = times.atime;
167     stbuf->st_mtime = times.mtime;
168     stbuf->st_ctime = times.ctime;
169     stbuf->st_uid = getuid();
170     stbuf->st_gid = getgid();
171     return 0;
172 }
173
174 static int fusefile_open(const char *path,struct fuse_file_info *fi) {
175 #if DEBUG
176     fprintf( stderr, "fusefile_open( %s, %d )\n", path, fi->flags );
177     fprintf( stderr, "fixing( %d )\n", fi->flags | O_CLOEXEC );
178 #endif
179     if ( strcmp( path, "/" ) != 0 ) {
180         return -ENOENT;
181     }
182     // set O-CLOEXEC  for this opening?
183     times.atime = time( 0 );
184     return 0;
185 }
186
187 static int find_source(off_t offset) {
188     int lo = 0;
189     int hi = sources.count;
190     if ( offset > sources.size ) {
191         return -1;
192     }
193     while ( lo + 1 < hi ) {
194         int m = ( lo + hi ) / 2;
195         if ( offset < sources.array[ m ].start ) {
196             hi = m;
197         } else {
198             lo = m;
199         }
200     }
201     return lo;
202 }
203
204 /**
205  * Insert a source fragment description into the table at <off>.
206  */
207 static int insert_source(struct Source *source,size_t off) {
208     int index = find_source( off );
209     int i;
210     // Ensure at least 5 "free" Sources in <source.array>
211     // and allocate space for 20 new otherwise.
212     if ( sources.count + 5 > sources.limit ) {
213         size_t size = sources.limit + 20;
214         struct Source *new = realloc(
215             sources.array, size * sizeof( struct Source ) );
216         if ( new == 0 ) {
217             return -1;
218         }
219         sources.array = new;
220         sources.limit = size;
221     }
222 #if DEBUG
223     fprintf( stderr, "index=%d\n", index );
224 #endif
225     if ( index < sources.count ) {
226         ssize_t b = ( sources.count - index ) * sizeof(struct Source);
227 #if DEBUG
228     fprintf( stderr, "b=%ld\n", b );
229 #endif
230         if ( sources.array[ index ].start < off ) {
231             // Split the <index> record at <off>
232             // and adjust index
233             memcpy( SOURCEARRAYP( index+2 ), SOURCEARRAYP( index ), b );
234             sources.count += 2;
235             b = off - sources.array[ index ].start;
236             sources.array[ index + 2 ].from += b; // adjust tail fragment
237             sources.array[ index++ ].to = b; // adjust head fragment
238 #if DEBUG
239             print_source( &sources.array[ index-1 ] );
240             print_source( &sources.array[ index ] );
241             print_source( &sources.array[ index+1 ] );
242             fprintf( stderr, "---\n");
243 #endif
244         } else {
245             // Insert the new source at <index>
246             memcpy( SOURCEARRAYP( index+1 ), SOURCEARRAYP( index ), b );
247             sources.count += 1;
248         }
249     } else {
250         // Append the new source to <sources> (at <index>)
251         sources.count += 1;
252     }
253     sources.array[ index ].filename = source->filename;
254     sources.array[ index ].fd = source->fd;
255     sources.array[ index ].from = source->from;
256     sources.array[ index ].to = source->to;
257     for ( i = index; i < sources.count; i++ ) {
258         sources.array[ i ].start = off;
259         off += sources.array[ i ].to - sources.array[ i ].from;
260 #if DEBUG
261         print_source( &sources.array[ i ] );
262 #endif
263     }
264     sources.size = off;
265 #if DEBUG
266     fprintf( stderr, "count=%d size=%ld\n", sources.count, sources.size );
267 #endif
268     return index;
269 }
270
271 // Read <size> bytes from <off> in file
272 static int fusefile_read(const char *path, char *buf, size_t size,
273                          off_t off, struct fuse_file_info *fi)
274 {
275 #if DEBUG
276     fprintf( stderr, "fusefile_read( %s )\n", path );
277 #endif
278     if ( strcmp( path, "/" ) != 0 ) {
279         return -ENOENT;
280     }
281 #if DEBUG
282     fprintf( stderr, "read %ld %ld\n", off, size );
283 #endif
284     size_t rr = 0;
285     while ( size > 0 ) {
286 #if DEBUG
287         fprintf( stderr, "find_source %ld %ld\n", off, size );
288 #endif
289         int i = find_source( off );
290         if ( i < 0 ) {
291             return -ENOENT;
292         }
293         if ( sources.array[i].fd < 0 ) {
294             return -ENOENT;
295         }
296 #if DEBUG
297         print_source( &sources.array[i] );
298 #endif
299         size_t b = off - sources.array[i].start + sources.array[i].from;
300         size_t n = sources.array[i].to - b;
301         if ( n > size ) {
302             n = size;
303         }
304         if ( lseek( sources.array[i].fd, b, SEEK_SET ) < 0 ) {
305             perror( sources.array[i].filename );
306             return -ENOENT;
307         }
308 #if DEBUG
309         fprintf( stderr, "get %ld bytes at %ld\n", n, rr );
310 #endif
311         ssize_t r = read( sources.array[i].fd, buf + rr, n );
312 #if DEBUG
313         fprintf( stderr, "got %ld bytes\n", r );
314 #endif
315         if ( r < 0 ) {
316             perror( sources.array[i].filename );
317             return -ENOENT;
318         }
319         if ( r == 0 ) {
320             break;
321         }
322         rr += r;
323         off += r;
324         size -= r;
325     }
326     times.atime = time( 0 );
327     return rr;
328 }
329
330 /**
331  * Write a full block of data.
332  */
333 static int write_block(int fd,const char *buf,size_t size) {
334     size_t orig = size;
335     while ( size > 0 ) {
336         ssize_t n = write( fd, buf, size );
337         if ( n <= 0 ) {
338             return n;
339         }
340         buf += n;
341         size -= n;
342     }
343     return orig;
344 }
345
346 static int fusefile_write_buf(const char *path, struct fuse_bufvec *buf,
347                               off_t off, struct fuse_file_info *fi) {
348 #if DEBUG
349     fprintf( stderr, "fusefile_write_buf( %s )\n", path );
350 #endif
351     if ( strcmp( path, "/" ) != 0 ) {
352         return -ENOENT;
353     }
354     
355     // Ensure a pad was nominated
356     if ( pad.filename == 0 ) {
357         return 1;
358     }
359
360     // Determine total size
361     size_t size = 0;
362     int i;
363 #if DEBUG
364     fprintf( stderr, "count = %ld\n", buf->count );
365 #endif
366     for ( i = 0; i < buf->count; i++ ) {
367         struct fuse_buf *p = &buf->buf[ i ];
368         size += p->size;
369     }
370     static char meta[ 100 ];
371     sprintf( meta, "%ld\n%ld\n", off, size );
372 #if DEBUG
373     fprintf( stderr, "meta( %ld %ld )\n", off, size );
374 #endif
375     if ( write_block( pad.fd, meta, strlen( meta ) ) <= 0 ) {
376         perror( pad.filename );
377         return -EIO;
378     }
379     struct Source source = {
380         .filename = pad.filename,
381         .fd = pad.fd,
382         .from = lseek( pad.fd, 0, SEEK_END ),
383         .to = 0,
384         .start = 0
385     };
386     for ( i = 0; i < buf->count; i++ ) {
387         struct fuse_buf *p = &buf->buf[i];
388         if ( p->flags & FUSE_BUF_IS_FD ) {
389 #if DEBUG
390             fprintf( stderr, "Content held in a file ... HELP!!\n" );
391 #endif
392             return -EIO;
393         } else {
394             ssize_t n = write_block( pad.fd, (char*) p->mem, p->size );
395             if ( n != p->size ) {
396                 return -EIO;
397             }
398         }
399     }
400     source.to = source.from + size;
401     insert_source( &source, off );
402     times.mtime = time( 0 );
403     return size;
404 }
405
406 /**
407  * Insert a fragment at <off>. The data is appended to the pad file,
408  * and a descriptor is inserted; the fragment containing <off> is
409  * first split, unless <off> is at its start, and then new fragment
410  * descriptor is inserted.
411  */
412 static int fusefile_write(const char *path, const char *buf, size_t size,
413                           off_t off, struct fuse_file_info *fi)
414 {
415 #if DEBUG
416     fprintf( stderr, "fusefile_write( %s %ld )\n", path, size );
417 #endif
418     if ( strcmp( path, "/" ) != 0 ) {
419         return -ENOENT;
420     }
421
422     // Ensure a pad was nominated
423     if ( pad.filename == 0 ) {
424         return 1;
425     }
426     static char meta[ 100 ];
427     sprintf( meta, "%ld\n%ld\n", off, size );
428     if ( write_block( pad.fd, meta, strlen( meta ) ) <= 0 ) {
429         perror( pad.filename );
430         return -EIO;
431     }
432     struct Source source = {
433         .filename = pad.filename,
434         .fd = pad.fd,
435         .from = lseek( pad.fd, 0, SEEK_END ),
436         .to = 0,
437         .start = 0
438     };
439     ssize_t n = write_block( pad.fd, buf, size );
440     if ( n != size ) {
441         return n;
442     }
443     source.to = source.from + size;
444     insert_source( &source, off );
445     times.mtime = time( 0 );
446     return size;
447 }
448
449 static void fusefile_destroy(void *data) {
450     char *mnt = (char*) data; // As passed to fuse_main
451 #if DEBUG
452     fprintf( stderr, "fusefile_destroy( %s )\n", mnt? mnt : "" );
453 #endif
454     if ( mnt ) {
455         unlink( mnt );
456     }
457 }
458
459 static int fusefile_flush(const char *path, struct fuse_file_info *info) {
460 #if DEBUG
461     fprintf( stderr, "fusefile_flush( %s )\n", path );
462 #endif
463     if ( strcmp( path, "/" ) != 0 ) {
464         return -ENOENT;
465     }
466     return 0;
467 }
468
469 static int fusefile_release(const char *path, struct fuse_file_info *fi) {
470 #if DEBUG
471     fprintf( stderr, "fusefile_release( %s, %d )\n", path, fi->flags );
472 #endif
473     if ( strcmp( path, "/" ) != 0 ) {
474         return -ENOENT;
475     }
476     return 0;
477 }
478
479 static int fusefile_fsync(const char *path, int x, struct fuse_file_info *fi) {
480 #if DEBUG
481     fprintf( stderr, "fusefile_fsync( %s, %d )\n", path, x );
482 #endif
483     if ( strcmp( path, "/" ) != 0 ) {
484         return -ENOENT;
485     }
486     return 0;
487 }
488
489 /**
490  * 
491  */
492 static int fusefile_truncate(const char *path, off_t len) {
493 #if DEBUG
494     fprintf( stderr, "fusefile_truncate( %s, %ld )\n", path, len );
495 #endif
496     if ( strcmp( path, "/" ) != 0 ) {
497         return -ENOENT;
498     }
499     return -EIO;
500 }
501
502 static struct fuse_operations fusefile_oper = {
503     .getattr = fusefile_getattr,
504     .open = fusefile_open,
505     .read = fusefile_read,
506     .write = fusefile_write,
507     .write_buf = fusefile_write_buf,
508     .destroy = fusefile_destroy,
509     .flush = fusefile_flush,
510     .release = fusefile_release,
511     .fsync = fusefile_fsync,
512     .truncate = fusefile_truncate,
513     //.truncate = fusefile_truncate,
514     //.release = fusefile_release,
515     //void *(*init) (struct fuse_conn_info *conn);
516 };
517
518 static void usage() {
519     char *usage =
520 "Usage: fusefile [ <fuse options> ] <mount> <file/from-to> ... \n"
521 "Mounts a virtual, read-only file that is a concatenation of file fragments\n"
522         ;
523     fprintf( stderr, "%s", usage );
524     exit( 1 );
525 }
526
527 /**
528  * Set up the arguments for the fuse_main call, adding our own.
529  */
530 static int setup_argv(int argc,char ***argv) {
531     int n = argc + 1;
532     char **out = calloc( n--, sizeof( char* ) );
533     memcpy( (void*) out, (void*) (*argv), argc-- * sizeof( char* ) ); 
534     out[ n++ ] = out[ argc ]; // mount point
535     out[ argc++ ] = "-odefault_permissions";
536     (*argv) = out;
537     return n;
538 }
539
540 /**
541  * Mount a concatenation of files,
542  * [ <fuse options> ] <mount> <file/from-to> ...
543  */
544 int main(int argc, char *argv[])
545 {
546     char *mnt;
547     int mt;
548     int fg;
549     int i;
550     int fuseargc;
551     struct stat stbuf;
552     int temporary = 0;
553     // Scan past options
554     for ( i = 1; i < argc; i++ ) {
555         if ( *argv[i] != '-' ) {
556             break;
557         }
558     }
559     if ( i > argc - 2 ) { // At least mount point plus one source
560         usage();
561     }
562     mnt = argv[ i++ ]; // First non-option argument is the mount pount
563     fuseargc = i;
564     if ( strncmp( argv[ i ], "pad=", 4 ) == 0 ) {
565         // First argument is the pad, if any, signaled with "pad=" prefix
566         pad.filename = argv[ i++ ] + 4; // (also move arg index)
567 #if DEBUG
568         fprintf( stderr, "scratch pad=%s\n", pad.filename );
569 #endif
570         pad.fd = open( pad.filename, O_RDWR | O_CREAT, 0600 );
571         if ( pad.fd < 0 ) {
572             perror( pad.filename );
573             exit( errno );
574         }
575         lseek( pad.fd, 0, SEEK_END ); // initial seek
576     }
577     if ( setup_sources( argv, i, argc-i ) ) {
578         return 1;
579     }
580     if ( stat( mnt, &stbuf ) == -1 ) {
581         int fd = open( mnt, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR );
582         if ( fd < 0 ) {
583             perror( mnt );
584             return 1;
585         }
586         time_t now = time( 0 );
587         times.atime = now;
588         times.mtime = now;
589         times.ctime = now;
590         temporary = 1;
591         close( fd );
592     } else if ( ! S_ISREG( stbuf.st_mode ) ) {
593         fprintf( stderr, "mountpoint is not a regular file\n" );
594         return 1;
595     } else {
596         times.atime = stbuf.st_atime;
597         times.mtime = stbuf.st_mtime;
598         times.ctime = stbuf.st_ctime;
599     }
600
601     fuseargc = setup_argv( fuseargc, &argv );
602     struct fuse_args args = FUSE_ARGS_INIT( fuseargc, argv );
603     if ( fuse_parse_cmdline( &args, &mnt, &mt, &fg ) ) {
604         return 1;
605     }
606     fuse_opt_free_args( &args );
607     if ( ! mnt ) {
608         fprintf( stderr, "missing mountpoint parameter\n" );
609         return 1;
610     }
611     return fuse_main( fuseargc, argv, &fusefile_oper, temporary? mnt : NULL );
612 }