added overlay option
[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     ssize_t size;
49 } sources;
50
51 static struct {
52     time_t atime;
53     time_t mtime;
54     time_t ctime;
55 } times;
56
57 static struct Source overlay;
58
59 #if DEBUG
60 static void print_source(struct Source *p) {
61     fprintf( stderr, "%p { %s, %ld, %ld, %ld, %d }\n",
62              p, p->filename, p->from, p->to, p->start, p-> fd );
63 }
64 #endif
65
66 static char *range;
67 static unsigned int c;
68 static int RANGE(int s,int n ) {
69     return ( s == n ) && *(range+c) == 0;
70 }
71
72 static void usage();
73
74 static void setup_overlay(char *filename) {
75     overlay.filename = filename;
76     overlay.fd = open( filename, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR );
77     if ( overlay.fd < 0 ) {
78         perror( filename );
79         usage();
80     }
81 }
82
83 static int setup_sources(char **argv,int i,int n) {
84     sources.array = calloc( n, sizeof( struct Source ) );
85     if ( sources.array == 0 ) {
86         return 1;
87     }
88     sources.count = n;
89     int j = 0;
90     sources.size = 0;
91     for ( ; j < n; i++, j++ ) {
92         struct stat filestat;
93         struct Source *p = sources.array + j;
94         // Open the fragment file rw if possible, else ro
95         range = strrchr( argv[i], '/' ); // last '/'
96         p->filename = range? strndup( argv[i], range - argv[i] ) : argv[i];
97         p->fd = open( p->filename, O_RDWR );
98         int rdonly = 0;
99         if ( p->fd < 0 ) {
100             rdonly = 1;
101             p->fd = open( p->filename, O_RDONLY );
102         }
103         if ( p->fd < 0 ) {
104             perror( p->filename );
105             return 1; // Error return
106         }
107         if ( stat( p->filename, &filestat ) ) {
108             perror( p->filename );
109             return 1; 
110         }
111         if ( rdonly ) {
112             fprintf( stderr, "** %s opened read-only\n", p->filename );
113         }
114         p->from = 0;
115         p->to = filestat.st_size;
116         // Process any range variation
117         if ( range && *(++range) ) {
118             int a,b;
119             if ( 0 ) {
120             } else if ( RANGE( sscanf( range, "%d:%d%n", &a, &b, &c ), 2 )) {
121                 p->from = ( a < 0 )? ( p->to + a ) : a;
122                 p->to = ( b < 0 )? ( p->to + b ) : b;
123             } else if ( RANGE( sscanf( range, "%d+%d%n", &a, &b, &c ), 2 )) {
124                 p->from = ( a < 0 )? ( p->to + a ) : a;
125                 p->to = ( ( b < 0 )? p->to : p->from ) + b;
126             } else if ( RANGE( sscanf( range, "%d+%n", &a, &c ), 1 )) {
127                 p->from = ( a < 0 )? ( p->to + a ) : a;
128             } else if ( RANGE( sscanf( range, ":%d%n", &b, &c ), 1 )) {
129                 p->to = ( b < 0 )? ( p->to + b ) : b;
130             } else if ( RANGE( sscanf( range, "%d:%n", &a, &c ), 1 )) {
131                 p->from = ( a < 0 )? ( p->to + a ) : a;
132             } else if ( RANGE( sscanf( range, "%d%n", &a, &c ), 1 )) {
133                 if ( a >= 0 ) {
134                     p->from = a;
135                 } else {
136                     p->from = p->to + a;
137                 }
138             } else if ( RANGE( sscanf( range, ":%n", &c), 0 ) ) {
139                 // to end from start
140             } else {
141                 fprintf( stderr, "** BAD RANGE: %s\n", argv[i] );
142                 return 1;
143             }
144         }
145         if ( ( filestat.st_mode &  S_IFMT ) == S_IFCHR ) {
146             filestat.st_size = p->to; // Pretend size of character device
147         }
148         if ( p->from < 0 ) {
149             p->from = 0;
150         }
151         if ( p->to > filestat.st_size ) {
152             p->to = filestat.st_size;
153         }
154         if ( p->from >= p->to || p->from >= filestat.st_size ) {
155             fprintf( stderr, "** BAD RANGE: %s [%ld:%ld]\n",
156                      argv[i], p->from, p->to );
157             return 1;
158         }
159         p->start = sources.size; // the fusefile position of fragment
160         sources.size += p->to - p->from;
161 #if DEBUG
162         print_source( p );
163 #endif
164     }
165     return 0;
166 }
167
168 static int fusefile_getattr(const char *path,struct stat *stbuf) {
169 #if DEBUG
170     fprintf( stderr, "fusefile_getattr( %s )\n", path );
171 #endif
172     if ( strcmp( path, "/" ) != 0 ) {
173         return -ENOENT;
174     }
175 #if DEBUG
176     fprintf( stderr, "getattr %ld\n", sources.size );
177 #endif
178     memset( stbuf, 0, sizeof( struct stat ) );
179     stbuf->st_mode = S_IFREG | 0644; // Hmmm
180     stbuf->st_nlink = 1;
181     stbuf->st_size = sources.size;
182     stbuf->st_atime = times.atime;
183     stbuf->st_mtime = times.mtime;
184     stbuf->st_ctime = times.ctime;
185     stbuf->st_uid = getuid();
186     stbuf->st_gid = getgid();
187     return 0;
188 }
189
190 static int fusefile_chmod(const char *path,mode_t m) {
191 #if DEBUG
192     fprintf( stderr, "fusefile_chmod( %s, %d )\n", path, m );
193 #endif
194     return -1;
195 }
196
197 static int fusefile_open(const char *path,struct fuse_file_info *fi) {
198 #if DEBUG
199     fprintf( stderr, "fusefile_open( %s, %d )\n", path, fi->flags );
200     fprintf( stderr, "fixing( %d )\n", fi->flags | O_CLOEXEC );
201 #endif
202     if ( strcmp( path, "/" ) != 0 ) {
203         return -ENOENT;
204     }
205     // set O-CLOEXEC  for this opening?
206     times.atime = time( 0 );
207     return 0;
208 }
209
210 static int find_source(off_t offset) {
211     int lo = 0;
212     int hi = sources.count;
213     if ( offset >= sources.size ) {
214         return -1;
215     }
216 #if DEBUG
217     fprintf( stderr, "find_source( %ld )\n", offset );
218 #endif
219     while ( lo + 1 < hi ) {
220         int m = ( lo + hi ) / 2;
221         if ( offset < sources.array[ m ].start ) {
222 #if DEBUG
223             fprintf( stderr, "  offset < [%d].start: %ld\n",
224                      m, sources.array[ m ].start );
225 #endif
226             hi = m;
227         } else {
228 #if DEBUG
229             fprintf( stderr, "  offset >= [%d].start: %ld\n",
230                      m, sources.array[ m ].start );
231 #endif
232             lo = m;
233         }
234     }
235 #if DEBUG
236     fprintf( stderr, "found %d\n", lo );
237 #endif
238     return lo;
239 }
240
241 #define OBUFSZ 1048576
242 static int overlay_merge(char *buf,off_t off,size_t size) {
243     static char obuf[ OBUFSZ ];
244 #if DEBUG
245     fprintf( stderr, "merge %ld %ld\n", off, size );
246 #endif
247     while ( size > 0 ) {
248         size_t n = size < OBUFSZ? size : OBUFSZ;
249         off_t ox = lseek( overlay.fd, off, SEEK_SET );
250 #if DEBUG
251         fprintf( stderr, "  seek %ld %ld %ld\n", off, ox, n );
252 #endif
253         if ( ox < 0 ) {
254             perror( overlay.filename );
255             return -ENOENT;
256         }
257         if ( ox < off ) {
258             break;
259         }
260         n = read( overlay.fd, obuf, n );
261 #if DEBUG
262         fprintf( stderr, "  got %ld\n", n );
263 #endif
264         if ( n < 0 ) {
265             perror( overlay.filename );
266             return -ENOENT;
267         }
268         if ( n == 0 ) {
269             break;
270         }
271         char *p = obuf;
272         while ( n-- > 0 ) {
273             if ( *p ) {
274                 *buf = *p;
275             }
276             p++;
277             buf++;
278             size--;
279             off++;
280         }
281     }
282 #if DEBUG
283     fprintf( stderr, "merged\n" );
284 #endif
285     return 0;
286 }
287
288 // Read <size> bytes from <offset> in file
289 static int fusefile_read(const char *path, char *buf, size_t size,
290                          off_t off, struct fuse_file_info *fi)
291 {
292 #if DEBUG
293     fprintf( stderr, "fusefile_read( %s )\n", path );
294 #endif
295     if( strcmp( path, "/" ) != 0 ) {
296         return -ENOENT;
297     }
298 #if DEBUG
299     fprintf( stderr, "read %ld %ld\n", off, size );
300 #endif
301     size_t rr = 0; // total reading
302     while ( size > 0 ) {
303 #if DEBUG
304         fprintf( stderr, "  find_source %ld %ld\n", off, size );
305 #endif
306         int i = find_source( off );
307         if ( i < 0 ) {
308             return ( off == sources.size )? rr : -ENOENT;
309         }
310         if ( sources.array[i].fd < 0 ) {
311             return -ENOENT;
312         }
313 #if DEBUG
314         print_source( &sources.array[i] );
315 #endif
316         times.atime = time( 0 );
317         size_t b = off - sources.array[i].start + sources.array[i].from;
318         size_t n = sources.array[i].to - b;
319         if ( n > size ) {
320             n = size;
321         }
322 #if DEBUG
323         fprintf( stderr, "  seek fd=%d to %ld\n", sources.array[i].fd, b );
324 #endif
325         if ( lseek( sources.array[i].fd, b, SEEK_SET ) < 0 ) {
326             perror( sources.array[i].filename );
327             return -ENOENT;
328         }
329 #if DEBUG
330         fprintf( stderr, "  now read %ld from fd=%d\n",
331                  n, sources.array[i].fd );
332 #endif
333         ssize_t r = read( sources.array[i].fd, buf + rr, n );
334         if ( overlay.filename ) {
335             int x = overlay_merge( buf + rr, off + rr, r );
336             if ( x ) {
337                 return x;
338             }
339         }
340 #if DEBUG
341         fprintf( stderr, "  got %ld bytes\n", r );
342 #endif
343         if ( r < 0 ) {
344             perror( sources.array[i].filename );
345             return -ENOENT;
346         }
347         if ( r == 0 ) {
348             break;
349         }
350         rr += r;
351         off += r;
352         size -= r;
353     }
354 #if DEBUG
355     fprintf( stderr, "  total reading %ld bytes\n", rr );
356 #endif
357     return rr;
358 }
359
360 /**
361  * Poll for IO readiness.
362  */
363 int fusefile_poll(const char *path, struct fuse_file_info *fi,
364                    struct fuse_pollhandle *ph, unsigned *reventsp )
365 {
366 #if DEBUG
367     fprintf( stderr, "fusefile_poll( %s ) %p %d\n", path, ph, *reventsp );
368 #endif
369     if( strcmp( path, "/" ) != 0 ) {
370         return -ENOENT;
371     }
372     if ( ph ) {
373         return fuse_notify_poll( ph );
374     }
375     return 0;
376 }
377
378
379 /**
380  * Write a full block of data over the sources at the offset
381  */
382 static int write_block(off_t off,const char *buf,size_t size) {
383 #if DEBUG
384     fprintf( stderr, "write_block( %ld, ?, %ld )\n", off, size );
385 #endif
386     while ( size > 0 ) {
387         int index = find_source( off ); // index of source file
388         if ( index < 0 ) {
389             return -EIO; // past EOF
390         }
391         struct Source *source =
392             overlay.filename? &overlay : &sources.array[ index ];
393         off_t from = off - source->start + source->from;
394         off_t max = source->to - from;
395         if ( lseek( source->fd, from, SEEK_SET ) < 0 ) {
396             return -EIO;
397         }
398         ssize_t todo = ( size < max )? size : max;
399         while ( todo > 0 ) {
400             times.mtime = time( 0 );
401             ssize_t n = write( source->fd, buf, todo );
402             if ( n <= 0 ) {
403                 return -EIO; // Something wrong
404             }
405             buf += n;
406             todo -= n;
407             size -= n;
408             off += n;
409         }
410     }
411     return 0;
412 }
413
414 static int fusefile_write_buf(const char *path, struct fuse_bufvec *buf,
415                               off_t off, struct fuse_file_info *fi) {
416 #if DEBUG
417     fprintf( stderr, "fusefile_write_buf( %s )\n", path );
418 #endif
419     if ( strcmp( path, "/" ) != 0 ) {
420         return -ENOENT;
421     }
422
423     size_t size = 0;
424     int i;
425     for ( i = 0; i < buf->count; i++ ) {
426         struct fuse_buf *p = &buf->buf[i];
427         if ( p->flags & FUSE_BUF_IS_FD ) {
428 #if DEBUG
429             fprintf( stderr, "Content held in a file ... HELP!!\n" );
430 #endif
431             return -EIO;
432         }
433         if ( write_block( off, (char*) p->mem, p->size ) < 0 ) {
434             return -EIO;
435         }
436         size += p->size;
437     }
438 #if DEBUG
439     fprintf( stderr, "fusefile_write_buf written %ld\n", size );
440 #endif
441     return size;
442 }
443
444 /**
445  * Write a fragment at <off>. This overwrites files.
446  */
447 static int fusefile_write(const char *path, const char *buf, size_t size,
448                           off_t off, struct fuse_file_info *fi)
449 {
450 #if DEBUG
451     fprintf( stderr, "fusefile_write( %s %ld )\n", path, size );
452 #endif
453     if ( strcmp( path, "/" ) != 0 ) {
454         return -ENOENT;
455     }
456
457     if ( write_block( off, buf, size ) < 0 ) {
458         return -EIO;
459     }
460     return size;
461 }
462
463 static void fusefile_destroy(void *data) {
464     char *mnt = (char*) data; // As passed to fuse_main
465 #if DEBUG
466     fprintf( stderr, "fusefile_destroy( %s )\n", mnt? mnt : "" );
467 #endif
468     if ( mnt ) {
469         unlink( mnt );
470     }
471 }
472
473 static int fusefile_flush(const char *path, struct fuse_file_info *info) {
474 #if DEBUG
475     fprintf( stderr, "fusefile_flush( %s )\n", path );
476 #endif
477     if ( strcmp( path, "/" ) != 0 ) {
478         return -ENOENT;
479     }
480     return 0;
481 }
482
483 static int fusefile_release(const char *path, struct fuse_file_info *fi) {
484 #if DEBUG
485     fprintf( stderr, "fusefile_release( %s, %d )\n", path, fi->flags );
486 #endif
487     if ( strcmp( path, "/" ) != 0 ) {
488         return -ENOENT;
489     }
490     return 0;
491 }
492
493 static int fusefile_fsync(const char *path, int x, struct fuse_file_info *fi) {
494 #if DEBUG
495     fprintf( stderr, "fusefile_fsync( %s, %d )\n", path, x );
496 #endif
497     if ( strcmp( path, "/" ) != 0 ) {
498         return -ENOENT;
499     }
500     return 0;
501 }
502
503 /**
504  * 
505  */
506 static int fusefile_truncate(const char *path, off_t len) {
507 #if DEBUG
508     fprintf( stderr, "fusefile_truncate( %s, %ld )\n", path, len );
509 #endif
510     if ( strcmp( path, "/" ) != 0 ) {
511         return -ENOENT;
512     }
513     return -EIO;
514 }
515
516 void *fusefile_init(struct fuse_conn_info *fci) {
517 #if DEBUG
518     fprintf( stderr, "fusefile_init( %d, %d )\n", fci->async_read, fci->want );
519 #endif
520     // Disable asynchronous reading
521     fci->async_read = 0;
522     fci->want &= ~FUSE_CAP_ASYNC_READ;
523 #if DEBUG
524     fprintf( stderr, "fusefile_init( %d, %d )\n", fci->async_read, fci->want );
525 #endif
526     return 0;
527 }
528
529 static struct fuse_operations fusefile_oper = {
530     .getattr = fusefile_getattr,
531     .chmod = fusefile_chmod,
532     .open = fusefile_open,
533     .read = fusefile_read,
534     .poll = fusefile_poll,
535     .write = fusefile_write,
536     .write_buf = fusefile_write_buf,
537     .destroy = fusefile_destroy,
538     .flush = fusefile_flush,
539     .release = fusefile_release,
540     .fsync = fusefile_fsync,
541     .truncate = fusefile_truncate,
542     //.truncate = fusefile_truncate,
543     //.release = fusefile_release,
544     .init = fusefile_init,
545 };
546
547 static void usage() {
548     char *usage =
549 "Usage: fusefile [ <fuse options> ] <mount> <file/from-to> ... \n"
550 "Mounts a virtual, file that is a concatenation of file fragments\n"
551         ;
552     fprintf( stderr, "%s", usage );
553     exit( 1 );
554 }
555
556 /**
557  * Set up the arguments for the fuse_main call, adding our own.
558  * argv[argc] is the mount point argument
559  */
560 static int setup_argv(int argc,char ***argv) {
561     // note: (*argv)[ argc ] is the mount point argument
562     char *OURS[] = {
563         "-odefault_permissions",
564         (*argv)[ argc ]
565     };
566 #define OURSN ( sizeof( OURS ) / sizeof( char* ) )
567     int N = argc + OURSN;
568     // Allocate new arg array plus terminating null pointer
569     char **out = malloc( ( N + 1 ) * sizeof( char* ) ); 
570     int i;
571     for ( i = 0; i < argc; i++ ) {
572         out[ i ] = (*argv)[i];
573         //fprintf( stderr, " %s", out[ i ] );
574     }
575     for ( i = 0; i < OURSN; i++ ) {
576         out[ argc + i ] = OURS[i];
577         //fprintf( stderr, " %s", out[ i ] );
578     }
579     out[ N ] = 0;
580     //fprintf( stderr, "\n" );
581     (*argv) = out;
582     return N; // Don't include the terminating null pointer
583 }
584
585 /**
586  * Mount a concatenation of files,
587  * [ <fuse options> ] <mount> <file/from-to> ...
588  */
589 int main(int argc, char *argv[])
590 {
591     char *mnt;
592     int mt;
593     int fg;
594     int i;
595     int fuseargc;
596     struct stat stbuf;
597     int temporary = 0;
598     // Scan past options
599     for ( i = 1; i < argc; i++ ) {
600         if ( *argv[i] != '-' ) {
601             break;
602         }
603     }
604     if ( i > argc - 2 ) { // At least mount point plus one source
605         usage();
606     }
607     fuseargc = i;
608     mnt = argv[ i++ ]; // First non-option argument is the mount pount
609     char *overlaytag = "-overlay:";
610     int overlaytagsize = strlen( overlaytag );
611     if ( strncmp( argv[i], overlaytag, overlaytagsize ) == 0 ) {
612         // consume "-overlay:filename"
613         setup_overlay( argv[i++] + overlaytagsize ); // Need a writable file
614         if ( i >= argc ) {
615             usage();
616         }
617     }
618     if ( setup_sources( argv, i, argc-i ) ) {
619         return 1;
620     }
621     overlay.to = sources.size; // Register total size.
622     if ( stat( mnt, &stbuf ) == -1 ) {
623         int fd = open( mnt, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR );
624         if ( fd < 0 ) {
625             perror( mnt );
626             return 1;
627         }
628         time_t now = time( 0 );
629         times.atime = now;
630         times.mtime = now;
631         times.ctime = now;
632         temporary = 1;
633         close( fd );
634     } else if ( ! S_ISREG( stbuf.st_mode ) ) {
635         fprintf( stderr, "mountpoint is not a regular file\n" );
636         return 1;
637     } else {
638         times.atime = stbuf.st_atime;
639         times.mtime = stbuf.st_mtime;
640         times.ctime = stbuf.st_ctime;
641     }
642
643     {
644         int fd = open( mnt, O_RDWR, S_IRUSR | S_IWUSR );
645         if ( fd < 0 ) {
646             perror( mnt );
647             return 1;
648         }
649         if ( lseek( fd, sources.size, SEEK_SET ) < 0 ) {
650             return -EIO;
651         }
652     }
653     fuseargc = setup_argv( fuseargc, &argv );
654     struct fuse_args args = FUSE_ARGS_INIT( fuseargc, argv );
655     if ( fuse_parse_cmdline( &args, &mnt, &mt, &fg ) ) {
656         return 1;
657     }
658     fuse_opt_free_args( &args );
659     if ( ! mnt ) {
660         fprintf( stderr, "missing mountpoint parameter\n" );
661         return 1;
662     }
663     return fuse_main( fuseargc, argv, &fusefile_oper, temporary? mnt : NULL );
664 }