add writability
authorRalph Ronnquist <ralph.ronnquist@gmail.com>
Thu, 26 May 2022 02:37:33 +0000 (12:37 +1000)
committerRalph Ronnquist <ralph.ronnquist@gmail.com>
Thu, 26 May 2022 02:37:33 +0000 (12:37 +1000)
Makefile
README.adoc
fusefile.8
fusefile.c

index 8103f1464ba42d2544210b150ffe3b6cbdd96e13..c2a0ba66fdb62d94ab77c8c75ca851008ac50d71 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -2,7 +2,11 @@ BINS = fusefile
 
 default: $(BINS)
 
 
 default: $(BINS)
 
-fusefile: CFLAGS = -Wall -D_FILE_OFFSET_BITS=64
+ifneq (${DEBUG},)
+fusefile: CFLAGS += -DDEBUG=1 -g
+endif
+
+fusefile: CFLAGS += -Wall -D_FILE_OFFSET_BITS=64
 fusefile: LDFLAGS = -lfuse -pthread
 
 .INTERMEDIATE: fusefile.o
 fusefile: LDFLAGS = -lfuse -pthread
 
 .INTERMEDIATE: fusefile.o
@@ -14,6 +18,9 @@ fusefile: fusefile.o
 clean:
        rm -f $(BINS)
 
 clean:
        rm -f $(BINS)
 
+README.html: README.adoc
+       asciidoctor $< > $@
+
 # Building a debian package
 
 SBINDIR = $(DESTDIR)/usr/sbin
 # Building a debian package
 
 SBINDIR = $(DESTDIR)/usr/sbin
index 6d9f7751ed3a6376ef5de4c6adb32c9e927a2218..a37d36d40c0e75d2409119e781fbc24923afe4e6 100644 (file)
@@ -5,8 +5,8 @@ This project implements a "fuse" device to mount as a single file that
 is a concatenation of fragments of one or more files. By default the
 fused file is read-only.
 
 is a concatenation of fragments of one or more files. By default the
 fused file is read-only.
 
-A writeable fused file is set up by
-associating the mount with a scratch-pad
+A writeable fused file is set up by associating the mount with a
+"scratch pad file"
 
 FUSE file mount for combining file fragments.
  
 
 FUSE file mount for combining file fragments.
  
@@ -34,6 +34,13 @@ variations:
 
  * __filename/from-to__ include the file from the given start position, up to the given end position (not included). 
 
 
  * __filename/from-to__ include the file from the given start position, up to the given end position (not included). 
 
+ * *pad=*_filename_ when this is given as first argument, the fused
+ file is set up as a writable random-access file, where the write
+ events are captured appended to the nominated "pad" file. The new
+ content is inserted into the fused file but not the original files,
+ and fragments are split up and adjusted as needed so as to make the
+ write events appear as insertions inteo the fused file.
+
 ## EXAMPLES
 
 Insert file "y" into file "x" at position 1200:
 ## EXAMPLES
 
 Insert file "y" into file "x" at position 1200:
@@ -58,6 +65,12 @@ file changes or reduces in size, anything may happen.
 If the mountpoint file doesn't exist, then **fusefile** creates it,
 and removes it when unmounted.
 
 If the mountpoint file doesn't exist, then **fusefile** creates it,
 and removes it when unmounted.
 
+When a "pad" file is used, it is updated as an "ar" archive where each
+write event is a new member appended at the end. The "pad" member has
+two additional, newline-terminated text lines with the insertion
+position and the member size (ascii decimal digits), before the actual
+insertion event content.
+
 ## AUTHOR
 
 Ralph Rönnquist <ralph.ronnquist@gmail.com> 
 ## AUTHOR
 
 Ralph Rönnquist <ralph.ronnquist@gmail.com> 
index 5dba7e47ea60b6476cff4d7f10761f0d3fbb130e..7051d94aa5277aee40ce2bf6070d685f5e5e5eb0 100644 (file)
@@ -35,6 +35,15 @@ included).
 \fIfilename/from-to\fR
 include the file from the given start position, up to the given end
 position (not included).
 \fIfilename/from-to\fR
 include the file from the given start position, up to the given end
 position (not included).
+.TP
+\fBpad=\fIfilename\fR
+
+when this is given as first argument, the fused file is set up as a
+writable random-access file, where the write events are captured
+appended to the nominated "pad" file. The new content is inserted into
+the fused file but not the original files, and fragments are split up
+and adjusted as needed so as to make the write events appear as
+insertions inteo the fused file.
 
 .SH EXAMPLES
 
 
 .SH EXAMPLES
 
@@ -61,6 +70,12 @@ file changes or reduces in size, anything may happen.
 If the mountpoint file doesn't exist, then \fBfusefile\fR creates it,
 and removes it when unmounted.
 
 If the mountpoint file doesn't exist, then \fBfusefile\fR creates it,
 and removes it when unmounted.
 
+When a "pad" file is used, it is updated as an "ar" archive where each
+write event is a new member appended at the end. The "pad" member has
+two additional, newline-terminated text lines with the insertion
+position and the member size (ascii decimal digits), before the actual
+insertion event content.
+
 .SH AUTHOR
 
 Ralph Rönnquist <ralph.ronnquist@gmail.com>
 .SH AUTHOR
 
 Ralph Rönnquist <ralph.ronnquist@gmail.com>
index 00f7f7fd33e3565e6cff5966cbf7690ed58273c8..41c6ed6042f6ca9462b02f19b45f6136a8eb0ae3 100644 (file)
@@ -23,9 +23,9 @@
     2001-2007 Miklos Szeredi <miklos@szeredi.hu>.
 */
 
     2001-2007 Miklos Szeredi <miklos@szeredi.hu>.
 */
 
-#define FUSE_USE_VERSION 31
+#define FUSE_USE_VERSION 33
 
 
-#include <fuse/fuse.h>
+#include <fuse.h>
 #include <fuse/fuse_lowlevel.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <fuse/fuse_lowlevel.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -45,13 +45,24 @@ struct Source {
 static struct {
     struct Source *array;
     int count;
 static struct {
     struct Source *array;
     int count;
+    int limit;
     ssize_t size;
 } sources;
 
     ssize_t size;
 } sources;
 
+#define SOURCEARRAYP(i) ((void*)&sources.array[ i ])
+
+/**
+ * Holds info about scratch pad file for 'write' events.
+ */
+static struct {
+    char *filename;
+    int fd;
+} pad;
+
 #if DEBUG
 static void print_source(struct Source *p) {
     fprintf( stderr, "%p { %s, %ld, %ld, %ld, %d }\n",
 #if DEBUG
 static void print_source(struct Source *p) {
     fprintf( stderr, "%p { %s, %ld, %ld, %ld, %d }\n",
-            p, p->filename, p->from, p->to, p->start, p-> fd );
+            p, p->filename, p->from, p->to, p->start, p->fd );
 }
 #endif
 
 }
 #endif
 
@@ -76,7 +87,9 @@ static size_t scan_source(char *in,struct Source *p) {
            m = i;
        }
     }
            m = i;
        }
     }
+#if DEBUG
     fprintf( stderr, "m=%d s=%d\n", m, s );
     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;
     // Copy the filename, and set from and to
     p->filename = strndup( in, ( s < 0 )? e : s );
     struct stat buf;
@@ -88,7 +101,9 @@ static size_t scan_source(char *in,struct Source *p) {
     if ( p->from < 0 ) {
        p->from = 0;
     }
     if ( p->from < 0 ) {
        p->from = 0;
     }
+#if DEBUG
     fprintf( stderr, "p->from=%ld\n", p->from );
     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;
     p->to = ( m < 0 )? buf.st_size : atol( in+m+1 );
     if ( p->from > p->to || p->to > buf.st_size ) {
        return 1;
@@ -102,6 +117,7 @@ static int setup_sources(char **argv,int i,int n) {
        return 1;
     }
     sources.count = n;
        return 1;
     }
     sources.count = n;
+    sources.limit = n;
     int j = 0;
     sources.size = 0;
     for ( ; j < n; i++, j++ ) {
     int j = 0;
     sources.size = 0;
     for ( ; j < n; i++, j++ ) {
@@ -124,8 +140,10 @@ static int setup_sources(char **argv,int i,int n) {
     return 0;
 }
 
     return 0;
 }
 
-static int fusefile_getattr(const char *path, struct stat *stbuf )
-{
+static int fusefile_getattr(const char *path,struct stat *stbuf) {
+#if DEBUG
+    fprintf( stderr, "fusefile_getattr( %s )\n", path );
+#endif
     if ( strcmp( path, "/" ) != 0 ) {
        return -ENOENT;
     }
     if ( strcmp( path, "/" ) != 0 ) {
        return -ENOENT;
     }
@@ -134,6 +152,9 @@ static int fusefile_getattr(const char *path, struct stat *stbuf )
 #endif
     memset( stbuf, 0, sizeof( struct stat ) );
     stbuf->st_mode = S_IFREG | 0444; // Hmmm
 #endif
     memset( stbuf, 0, sizeof( struct stat ) );
     stbuf->st_mode = S_IFREG | 0444; // Hmmm
+    if ( pad.filename ) {
+       stbuf->st_mode |= 0200;
+    }
     stbuf->st_nlink = 1;
     stbuf->st_size = sources.size;
     time_t now = time( 0 );
     stbuf->st_nlink = 1;
     stbuf->st_size = sources.size;
     time_t now = time( 0 );
@@ -145,11 +166,15 @@ static int fusefile_getattr(const char *path, struct stat *stbuf )
     return 0;
 }
 
     return 0;
 }
 
-static int fusefile_open(const char *path, struct fuse_file_info *fi)
-{
+static int fusefile_open(const char *path,struct fuse_file_info *fi) {
+#if DEBUG
+    fprintf( stderr, "fusefile_open( %s, %d )\n", path, fi->flags );
+    fprintf( stderr, "fixing( %d )\n", fi->flags | O_CLOEXEC );
+#endif
     if ( strcmp( path, "/" ) != 0 ) {
        return -ENOENT;
     }
     if ( strcmp( path, "/" ) != 0 ) {
        return -ENOENT;
     }
+    // set O-CLOEXEC  for this opening?
     return 0;
 }
 
     return 0;
 }
 
@@ -172,22 +197,74 @@ static int find_source(off_t offset) {
     return lo;
 }
 
     return lo;
 }
 
-// Read <size> bytes from <offset> in file
+/**
+ * Insert a source fragment description into the table at <off>.
+ */
+static int insert_source(struct Source *source,size_t off) {
+    int index = find_source( off );
+    // Ensure at least 5 "free" Sources in <source.array>
+    // and allocate space for 20 new otherwise.
+    if ( sources.count + 5 > sources.limit ) {
+       size_t size = sources.limit + 20;
+       struct Source *new = realloc(
+           sources.array, size * sizeof( struct Source ) );
+       if ( new == 0 ) {
+           return -1;
+       }
+       sources.array = new;
+       sources.limit = size;
+    }
+    if ( index < sources.count ) {
+       ssize_t b = ( sources.count - index ) * sizeof(struct Source);
+       if ( sources.array[ index ].start < off ) {
+           // Split the <index> record at <off>
+           // and adjust index
+           b *= 2;
+           memcpy( SOURCEARRAYP( index+2 ), SOURCEARRAYP( index ), b );
+           sources.count += 2;
+           b = off - sources.array[ index ].start;
+           sources.array[ index + 2 ].from += b; // adjust tail fragment
+           sources.array[ index++ ].to = b; // adjust head fragment
+       } else {
+           // Insert the new source at <index>
+           memcpy( SOURCEARRAYP( index+1 ), SOURCEARRAYP( index ), b );
+           sources.count += 1;
+       }
+    } else {
+       // Append the new source to <sources> (at <index>)
+       sources.count += 1;
+    }
+    sources.array[ index ].filename = source->filename;
+    sources.array[ index ].fd = source->fd;
+    sources.array[ index ].from = source->from;
+    sources.array[ index ].to = source->to;
+    for ( ; index < sources.count; index++ ) {
+       sources.array[ index ].start = off;
+       off += sources.array[ index ].to - sources.array[ index ].from;
+    }
+    sources.size = off;
+    return index;
+}
+
+// Read <size> bytes from <off> in file
 static int fusefile_read(const char *path, char *buf, size_t size,
 static int fusefile_read(const char *path, char *buf, size_t size,
-                     off_t offset, struct fuse_file_info *fi)
+                        off_t off, struct fuse_file_info *fi)
 {
 {
-    if( strcmp( path, "/" ) != 0 ) {
+#if DEBUG
+    fprintf( stderr, "fusefile_read( %s )\n", path );
+#endif
+    if ( strcmp( path, "/" ) != 0 ) {
        return -ENOENT;
     }
 #if DEBUG
        return -ENOENT;
     }
 #if DEBUG
-    fprintf( stderr, "read %ld %ld\n", offset, size );
+    fprintf( stderr, "read %ld %ld\n", off, size );
 #endif
     size_t rr = 0;
     while ( size > 0 ) {
 #if DEBUG
 #endif
     size_t rr = 0;
     while ( size > 0 ) {
 #if DEBUG
-       fprintf( stderr, "find_source %ld %ld\n", offset, size );
+       fprintf( stderr, "find_source %ld %ld\n", off, size );
 #endif
 #endif
-       int i = find_source( offset );
+       int i = find_source( off );
        if ( i < 0 ) {
            return -ENOENT;
        }
        if ( i < 0 ) {
            return -ENOENT;
        }
@@ -197,7 +274,7 @@ static int fusefile_read(const char *path, char *buf, size_t size,
 #if DEBUG
        print_source( &sources.array[i] );
 #endif
 #if DEBUG
        print_source( &sources.array[i] );
 #endif
-       size_t b = offset - sources.array[i].start + sources.array[i].from;
+       size_t b = off - sources.array[i].start + sources.array[i].from;
        size_t n = sources.array[i].to - b;
        if ( n > size ) {
            n = size;
        size_t n = sources.array[i].to - b;
        if ( n > size ) {
            n = size;
@@ -221,24 +298,125 @@ static int fusefile_read(const char *path, char *buf, size_t size,
            break;
        }
        rr += r;
            break;
        }
        rr += r;
-       offset += r;
+       off += r;
        size -= r;
     }
     return rr;
 }
 
        size -= r;
     }
     return rr;
 }
 
+/**
+ * Write a full block of data.
+ */
+static int write_block(int fd,const char *buf,size_t size) {
+    size_t orig = size;
+    while ( size > 0 ) {
+       ssize_t n = write( fd, buf, size );
+       if ( n <= 0 ) {
+           return n;
+       }
+    }
+    return orig;
+}
+
+/**
+ * Insert a fragment at <off>. The data is appended to the pad file,
+ * and a descriptor is inserted; the fragment containing <off> is
+ * first split, unless <off> is at its start, and then new fragment
+ * descriptor is inserted.
+ */
+static int fusefile_write(const char *path, const char *buf, size_t size,
+                         off_t off, struct fuse_file_info *fi)
+{
+#if DEBUG
+    fprintf( stderr, "fusefile_write( %s )\n", path );
+#endif
+    if ( strcmp( path, "/" ) != 0 ) {
+       return -ENOENT;
+    }
+#if DEBUG
+    fprintf( stderr, "write %ld %ld\n", off, size );
+#endif
+    // Ensure a pad was nominated
+    if ( pad.filename == 0 ) {
+       return 1;
+    }
+    static char meta[ 100 ];
+    sprintf( meta, "%ld\n%ld\n", off, size );
+    if ( write_block( pad.fd, meta, strlen( meta ) ) <= 0 ) {
+       perror( pad.filename );
+       return -EIO;
+    }
+    struct Source source = {
+       .filename = pad.filename,
+       .fd = pad.fd,
+       .from = lseek( pad.fd, 0, SEEK_END ),
+       .to = 0,
+       .start = 0
+    };
+    ssize_t n = write_block( pad.fd, buf, size );
+    if ( n != size ) {
+       return n;
+    }
+    source.to = source.from + size;
+    insert_source( &source, off );
+    return size;
+}
+
 static void fusefile_destroy(void *data) {
 static void fusefile_destroy(void *data) {
-    char *mnt = (char*) data;
+    char *mnt = (char*) data; // As passed to fuse_main
+#if DEBUG
+    fprintf( stderr, "fusefile_destroy( %s )\n", mnt? mnt : "" );
+#endif
     if ( mnt ) {
        unlink( mnt );
     }
 }
 
     if ( mnt ) {
        unlink( mnt );
     }
 }
 
+static int fusefile_flush(const char *path, struct fuse_file_info *info) {
+#if DEBUG
+    fprintf( stderr, "fusefile_flush( %s )\n", path );
+#endif
+    if ( strcmp( path, "/" ) != 0 ) {
+       return -ENOENT;
+    }
+    return 0;
+}
+
+static int fusefile_release(const char *path, struct fuse_file_info *fi) {
+#if DEBUG
+    fprintf( stderr, "fusefile_release( %s, %d )\n", path, fi->flags );
+#endif
+    if ( strcmp( path, "/" ) != 0 ) {
+       return -ENOENT;
+    }
+    return 0;
+}
+
+static int fusefile_fsync(const char *path, int x, struct fuse_file_info *fi) {
+#if DEBUG
+    fprintf( stderr, "fusefile_fsync( %s, %d )\n", path, x );
+#endif
+    if ( strcmp( path, "/" ) != 0 ) {
+       return -ENOENT;
+    }
+    return 0;
+}
+
 static struct fuse_operations fusefile_oper = {
     .getattr = fusefile_getattr,
     .open = fusefile_open,
     .read = fusefile_read,
 static struct fuse_operations fusefile_oper = {
     .getattr = fusefile_getattr,
     .open = fusefile_open,
     .read = fusefile_read,
+    .write = fusefile_write,
     .destroy = fusefile_destroy,
     .destroy = fusefile_destroy,
+    .flush = fusefile_flush,
+    .release = fusefile_release,
+    .fsync = fusefile_fsync,
+    //void *(*init) (struct fuse_conn_info *conn);
+    //int (*write_buf) (const char *, struct fuse_bufvec *buf, off_t off,
+    //           struct fuse_file_info *);
+    //.truncate = fusefile_truncate,
+    //.flush = fusefile_flush,
+    //.release = fusefile_release,
 };
 
 static void usage() {
 };
 
 static void usage() {
@@ -250,6 +428,19 @@ static void usage() {
     exit( 1 );
 }
 
     exit( 1 );
 }
 
+/**
+ * Set up the arguments for the fuse_main call, adding our own.
+ */
+static int setup_argv(int argc,char ***argv) {
+    int n = argc + 1;
+    char **out = calloc( n--, sizeof( char* ) );
+    memcpy( (void*) out, (void*) (*argv), argc-- * sizeof( char* ) ); 
+    out[ n++ ] = out[ argc ]; // mount point
+    out[ argc++ ] = "-odefault_permissions";
+    (*argv) = out;
+    return n;
+}
+
 /**
  * Mount a concatenation of files,
  * [ <fuse options> ] <mount> <file/from-to> ...
 /**
  * Mount a concatenation of files,
  * [ <fuse options> ] <mount> <file/from-to> ...
@@ -260,6 +451,7 @@ int main(int argc, char *argv[])
     int mt;
     int fg;
     int i;
     int mt;
     int fg;
     int i;
+    int fuseargc;
     struct stat stbuf;
     int temporary = 0;
     // Scan past options
     struct stat stbuf;
     int temporary = 0;
     // Scan past options
@@ -268,14 +460,27 @@ int main(int argc, char *argv[])
            break;
        }
     }
            break;
        }
     }
-    if ( i > argc - 2 ) { // At least one source
+    if ( i > argc - 2 ) { // At least mount point plus one source
        usage();
     }
        usage();
     }
-    i++;
+    mnt = argv[ i++ ]; // First non-option argument is the mount pount
+    fuseargc = i;
+    if ( strncmp( argv[ i ], "pad=", 4 ) == 0 ) {
+       // First argument is the pad, if any, signaled with "pad=" prefix
+       pad.filename = argv[ i++ ] + 4; // (also move arg index)
+#if DEBUG
+       fprintf( stderr, "scratch pad=%s\n", pad.filename );
+#endif
+       pad.fd = open( pad.filename, O_RDWR | O_CREAT, 0600 );
+       if ( pad.fd < 0 ) {
+           perror( pad.filename );
+           exit( errno );
+       }
+       lseek( pad.fd, 0, SEEK_END ); // initial seek
+    }
     if ( setup_sources( argv, i, argc-i ) ) {
        return 1;
     }
     if ( setup_sources( argv, i, argc-i ) ) {
        return 1;
     }
-    mnt = argv[i-1];
     if ( stat( mnt, &stbuf ) == -1 ) {
        int fd = open( mnt, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR );
        if ( fd < 0 ) {
     if ( stat( mnt, &stbuf ) == -1 ) {
        int fd = open( mnt, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR );
        if ( fd < 0 ) {
@@ -288,7 +493,8 @@ int main(int argc, char *argv[])
        fprintf( stderr, "mountpoint is not a regular file\n" );
        return 1;
     }
        fprintf( stderr, "mountpoint is not a regular file\n" );
        return 1;
     }
-    struct fuse_args args = FUSE_ARGS_INIT( i, argv );
+    fuseargc = setup_argv( fuseargc, &argv );
+    struct fuse_args args = FUSE_ARGS_INIT( fuseargc, argv );
     if ( fuse_parse_cmdline( &args, &mnt, &mt, &fg ) ) {
        return 1;
     }
     if ( fuse_parse_cmdline( &args, &mnt, &mt, &fg ) ) {
        return 1;
     }
@@ -297,5 +503,5 @@ int main(int argc, char *argv[])
        fprintf( stderr, "missing mountpoint parameter\n" );
        return 1;
     }
        fprintf( stderr, "missing mountpoint parameter\n" );
        return 1;
     }
-    return fuse_main( i, argv, &fusefile_oper, temporary? mnt : NULL );
+    return fuse_main( fuseargc, argv, &fusefile_oper, temporary? mnt : NULL );
 }
 }