added overlay option
[rrq/fusefile.git] / fusefile.c
index b9d3b2d68b7ee71ba0ab2080187d6f2584c3f52e..8371d1f6c9448d538daa7b4ba1f2fc89feb89676 100644 (file)
@@ -53,7 +53,9 @@ static struct {
     time_t mtime;
     time_t ctime;
 } times;
-    
+
+static struct Source overlay;
+
 #if DEBUG
 static void print_source(struct Source *p) {
     fprintf( stderr, "%p { %s, %ld, %ld, %ld, %d }\n",
@@ -61,49 +63,21 @@ static void print_source(struct Source *p) {
 }
 #endif
 
-// Scan the source specification, and return the length of the
-// inclusion. "filename/from,to"
-// filename
-// filename/from
-// filename/-to
-// filename/from-to
-static size_t scan_source(char *in,struct Source *p) {
-    int e = strlen( in );
-    int i = e-1;
-    int s = -1;
-    int m = -1;
-    // scan for last '/' and last '-'
-    for ( ; i >= 0; i-- ) {
-       if ( in[i] == '/' ) {
-           s = i;
-           break;
-       }
-       if ( in[i] == '-' ) {
-           m = i;
-       }
-    }
-#if DEBUG
-    fprintf( stderr, "m=%d s=%d\n", m, s );
-#endif
-    // Copy the filename, and set from and to
-    p->filename = strndup( in, ( s < 0 )? e : s );
-    struct stat buf;
-    if ( stat( p->filename, & buf ) ) {
-       perror( p->filename );
-       return 1; 
-    }
-    p->from = ( s < 0 )? 0 : atol( in+s+1 );
-    if ( p->from < 0 ) {
-       p->from = 0;
-    }
-#if DEBUG
-    fprintf( stderr, "p->from=%ld\n", p->from );
-#endif
-    p->to = ( m < 0 )? buf.st_size : atol( in+m+1 );
-    if ( p->from > p->to || p->to > buf.st_size ) {
-       return 1;
+static char *range;
+static unsigned int c;
+static int RANGE(int s,int n ) {
+    return ( s == n ) && *(range+c) == 0;
+}
+
+static void usage();
+
+static void setup_overlay(char *filename) {
+    overlay.filename = filename;
+    overlay.fd = open( filename, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR );
+    if ( overlay.fd < 0 ) {
+       perror( filename );
+       usage();
     }
-    return 0;
 }
 
 static int setup_sources(char **argv,int i,int n) {
@@ -115,22 +89,75 @@ static int setup_sources(char **argv,int i,int n) {
     int j = 0;
     sources.size = 0;
     for ( ; j < n; i++, j++ ) {
+       struct stat filestat;
        struct Source *p = sources.array + j;
-       if ( scan_source( argv[i], p ) ) {
-           // should free everything malloc-ed
-           return 1;
-       }
-       p->start = sources.size;
-       sources.size += p->to - p->from;
+       // Open the fragment file rw if possible, else ro
+       range = strrchr( argv[i], '/' ); // last '/'
+       p->filename = range? strndup( argv[i], range - argv[i] ) : argv[i];
        p->fd = open( p->filename, O_RDWR );
+       int rdonly = 0;
        if ( p->fd < 0 ) {
-           fprintf( stderr, "** %s opened read-only\n", p->filename );
+           rdonly = 1;
            p->fd = open( p->filename, O_RDONLY );
        }
        if ( p->fd < 0 ) {
            perror( p->filename );
+           return 1; // Error return
+       }
+       if ( stat( p->filename, &filestat ) ) {
+           perror( p->filename );
+           return 1; 
+       }
+       if ( rdonly ) {
+           fprintf( stderr, "** %s opened read-only\n", p->filename );
+       }
+       p->from = 0;
+       p->to = filestat.st_size;
+       // Process any range variation
+       if ( range && *(++range) ) {
+           int a,b;
+           if ( 0 ) {
+           } else if ( RANGE( sscanf( range, "%d:%d%n", &a, &b, &c ), 2 )) {
+               p->from = ( a < 0 )? ( p->to + a ) : a;
+               p->to = ( b < 0 )? ( p->to + b ) : b;
+           } else if ( RANGE( sscanf( range, "%d+%d%n", &a, &b, &c ), 2 )) {
+               p->from = ( a < 0 )? ( p->to + a ) : a;
+               p->to = ( ( b < 0 )? p->to : p->from ) + b;
+           } else if ( RANGE( sscanf( range, "%d+%n", &a, &c ), 1 )) {
+               p->from = ( a < 0 )? ( p->to + a ) : a;
+           } else if ( RANGE( sscanf( range, ":%d%n", &b, &c ), 1 )) {
+               p->to = ( b < 0 )? ( p->to + b ) : b;
+           } else if ( RANGE( sscanf( range, "%d:%n", &a, &c ), 1 )) {
+               p->from = ( a < 0 )? ( p->to + a ) : a;
+           } else if ( RANGE( sscanf( range, "%d%n", &a, &c ), 1 )) {
+               if ( a >= 0 ) {
+                   p->from = a;
+               } else {
+                   p->from = p->to + a;
+               }
+           } else if ( RANGE( sscanf( range, ":%n", &c), 0 ) ) {
+               // to end from start
+           } else {
+               fprintf( stderr, "** BAD RANGE: %s\n", argv[i] );
+               return 1;
+           }
+       }
+       if ( ( filestat.st_mode &  S_IFMT ) == S_IFCHR ) {
+           filestat.st_size = p->to; // Pretend size of character device
+       }
+       if ( p->from < 0 ) {
+           p->from = 0;
+       }
+       if ( p->to > filestat.st_size ) {
+           p->to = filestat.st_size;
+       }
+       if ( p->from >= p->to || p->from >= filestat.st_size ) {
+           fprintf( stderr, "** BAD RANGE: %s [%ld:%ld]\n",
+                    argv[i], p->from, p->to );
            return 1;
        }
+       p->start = sources.size; // the fusefile position of fragment
+       sources.size += p->to - p->from;
 #if DEBUG
        print_source( p );
 #endif
@@ -186,17 +213,78 @@ static int find_source(off_t offset) {
     if ( offset >= sources.size ) {
        return -1;
     }
+#if DEBUG
+    fprintf( stderr, "find_source( %ld )\n", offset );
+#endif
     while ( lo + 1 < hi ) {
        int m = ( lo + hi ) / 2;
        if ( offset < sources.array[ m ].start ) {
+#if DEBUG
+           fprintf( stderr, "  offset < [%d].start: %ld\n",
+                    m, sources.array[ m ].start );
+#endif
            hi = m;
        } else {
+#if DEBUG
+           fprintf( stderr, "  offset >= [%d].start: %ld\n",
+                    m, sources.array[ m ].start );
+#endif
            lo = m;
        }
     }
+#if DEBUG
+    fprintf( stderr, "found %d\n", lo );
+#endif
     return lo;
 }
 
+#define OBUFSZ 1048576
+static int overlay_merge(char *buf,off_t off,size_t size) {
+    static char obuf[ OBUFSZ ];
+#if DEBUG
+    fprintf( stderr, "merge %ld %ld\n", off, size );
+#endif
+    while ( size > 0 ) {
+       size_t n = size < OBUFSZ? size : OBUFSZ;
+       off_t ox = lseek( overlay.fd, off, SEEK_SET );
+#if DEBUG
+       fprintf( stderr, "  seek %ld %ld %ld\n", off, ox, n );
+#endif
+       if ( ox < 0 ) {
+           perror( overlay.filename );
+           return -ENOENT;
+       }
+       if ( ox < off ) {
+           break;
+       }
+       n = read( overlay.fd, obuf, n );
+#if DEBUG
+       fprintf( stderr, "  got %ld\n", n );
+#endif
+       if ( n < 0 ) {
+           perror( overlay.filename );
+            return -ENOENT;
+       }
+       if ( n == 0 ) {
+           break;
+       }
+       char *p = obuf;
+       while ( n-- > 0 ) {
+           if ( *p ) {
+               *buf = *p;
+           }
+           p++;
+           buf++;
+           size--;
+           off++;
+       }
+    }
+#if DEBUG
+    fprintf( stderr, "merged\n" );
+#endif
+    return 0;
+}
+
 // Read <size> bytes from <offset> in file
 static int fusefile_read(const char *path, char *buf, size_t size,
                         off_t off, struct fuse_file_info *fi)
@@ -210,10 +298,10 @@ static int fusefile_read(const char *path, char *buf, size_t size,
 #if DEBUG
     fprintf( stderr, "read %ld %ld\n", off, size );
 #endif
-    size_t rr = 0;
+    size_t rr = 0; // total reading
     while ( size > 0 ) {
 #if DEBUG
-       fprintf( stderr, "find_source %ld %ld\n", off, size );
+       fprintf( stderr, "  find_source %ld %ld\n", off, size );
 #endif
        int i = find_source( off );
        if ( i < 0 ) {
@@ -231,16 +319,26 @@ static int fusefile_read(const char *path, char *buf, size_t size,
        if ( n > size ) {
            n = size;
        }
+#if DEBUG
+       fprintf( stderr, "  seek fd=%d to %ld\n", sources.array[i].fd, b );
+#endif
        if ( lseek( sources.array[i].fd, b, SEEK_SET ) < 0 ) {
            perror( sources.array[i].filename );
            return -ENOENT;
        }
 #if DEBUG
-       fprintf( stderr, "get %ld bytes at %ld\n", n, rr );
+       fprintf( stderr, "  now read %ld from fd=%d\n",
+                n, sources.array[i].fd );
 #endif
        ssize_t r = read( sources.array[i].fd, buf + rr, n );
+       if ( overlay.filename ) {
+           int x = overlay_merge( buf + rr, off + rr, r );
+           if ( x ) {
+               return x;
+           }
+       }
 #if DEBUG
-       fprintf( stderr, "got %ld bytes\n", r );
+       fprintf( stderr, "  got %ld bytes\n", r );
 #endif
        if ( r < 0 ) {
            perror( sources.array[i].filename );
@@ -253,6 +351,9 @@ static int fusefile_read(const char *path, char *buf, size_t size,
        off += r;
        size -= r;
     }
+#if DEBUG
+    fprintf( stderr, "  total reading %ld bytes\n", rr );
+#endif
     return rr;
 }
 
@@ -287,7 +388,8 @@ static int write_block(off_t off,const char *buf,size_t size) {
        if ( index < 0 ) {
            return -EIO; // past EOF
        }
-       struct Source *source = &sources.array[ index ];
+       struct Source *source =
+           overlay.filename? &overlay : &sources.array[ index ];
        off_t from = off - source->start + source->from;
        off_t max = source->to - from;
        if ( lseek( source->fd, from, SEEK_SET ) < 0 ) {
@@ -445,7 +547,7 @@ static struct fuse_operations fusefile_oper = {
 static void usage() {
     char *usage =
 "Usage: fusefile [ <fuse options> ] <mount> <file/from-to> ... \n"
-"Mounts a virtual, read-only file that is a concatenation of file fragments\n"
+"Mounts a virtual, file that is a concatenation of file fragments\n"
        ;
     fprintf( stderr, "%s", usage );
     exit( 1 );
@@ -504,9 +606,19 @@ int main(int argc, char *argv[])
     }
     fuseargc = i;
     mnt = argv[ i++ ]; // First non-option argument is the mount pount
+    char *overlaytag = "-overlay:";
+    int overlaytagsize = strlen( overlaytag );
+    if ( strncmp( argv[i], overlaytag, overlaytagsize ) == 0 ) {
+       // consume "-overlay:filename"
+       setup_overlay( argv[i++] + overlaytagsize ); // Need a writable file
+       if ( i >= argc ) {
+           usage();
+       }
+    }
     if ( setup_sources( argv, i, argc-i ) ) {
        return 1;
     }
+    overlay.to = sources.size; // Register total size.
     if ( stat( mnt, &stbuf ) == -1 ) {
        int fd = open( mnt, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR );
        if ( fd < 0 ) {
@@ -528,6 +640,16 @@ int main(int argc, char *argv[])
        times.ctime = stbuf.st_ctime;
     }
 
+    {
+       int fd = open( mnt, O_RDWR, S_IRUSR | S_IWUSR );
+       if ( fd < 0 ) {
+           perror( mnt );
+           return 1;
+       }
+       if ( lseek( fd, sources.size, SEEK_SET ) < 0 ) {
+           return -EIO;
+       }
+    }
     fuseargc = setup_argv( fuseargc, &argv );
     struct fuse_args args = FUSE_ARGS_INIT( fuseargc, argv );
     if ( fuse_parse_cmdline( &args, &mnt, &mt, &fg ) ) {