initial commit
authorRalph Ronnquist <ralph.ronnquist@gmail.com>
Mon, 11 Mar 2019 00:55:22 +0000 (11:55 +1100)
committerRalph Ronnquist <ralph.ronnquist@gmail.com>
Mon, 11 Mar 2019 00:55:22 +0000 (11:55 +1100)
Makefile [new file with mode: 0644]
fusefile.8 [new file with mode: 0644]
fusefile.c [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..d3c33ef
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,10 @@
+BINS = fusefile
+
+default: $(BINS)
+
+fusefile: CFLAGS = -Wall -D_FILE_OFFSET_BITS=64
+fusefile: LDFLAGS = -lfuse
+fusefile: fusefile.c
+
+clean:
+       rm $(BINS)
diff --git a/fusefile.8 b/fusefile.8
new file mode 100644 (file)
index 0000000..5dba7e4
--- /dev/null
@@ -0,0 +1,66 @@
+.mso www.tmac
+.TH fusefile 8
+.SH NAME
+fusefile \- FUSE file mount for combining file fragments read-only
+
+.SH SYNOPSIS
+.B fusefile \fR[fuse options\fR] \fBmountpoint\fR \fIfilename/from-to\fR ...
+
+.SH DESCRIPTION
+
+\fBfusefile\fR is FUSE file mount that presents a series of fragments of
+other files as a contiguous concatenation. It bind mounts a driver on
+top of the file mountpoint to present the nominated file fragments as
+a single, contiguous file.
+
+The fragment arguments include the filename of a source file, and
+optionally start and end byte positions. All in all there five
+variations:
+.TP
+\fIfilename\fR
+include all of the file.
+.TP
+\fIfilename/\fR
+include all of the file named with "/" in the pathname. This case
+requires a final "/", since the last "/" separates the filename from
+the position details.
+.TP
+\fIfilename/from\fR
+include the file from the given start position, to end.
+.TP
+\fIfilename/-to\fR
+include the file from beginning to the given end position (not
+included).
+.TP
+\fIfilename/from-to\fR
+include the file from the given start position, up to the given end
+position (not included).
+
+.SH EXAMPLES
+
+Insert file "y" into file "x" at position 1200:
+.RS
+\fB$ fusefile -ononempty x x/-1200 y x/1200\fR
+.RE
+The bind mount shadows the original file "x", and presents the
+composite instead.
+
+Make file y be a swap of the beginning and end of file "x", at position 2442:
+.RS
+\fB$ fusefile y x/2442 x/-2442\fR
+.RE
+
+.SH NOTES
+
+Note that \fBfusefile\fR opens the nominated source file(s) before bind
+mounting. With the fuse option \fI-ononempty\fR it will bind over an
+non-empty file, which may be useful. The source file descriptors
+remain open, but the source fragments are not recomputed. If a source
+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.
+
+.SH AUTHOR
+
+Ralph Rönnquist <ralph.ronnquist@gmail.com>
diff --git a/fusefile.c b/fusefile.c
new file mode 100644 (file)
index 0000000..632221f
--- /dev/null
@@ -0,0 +1,285 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU GPL.
+  See the file COPYING.
+
+  Overlay a file path with a concatenation of parts of other files.
+  read only
+*/
+
+#define FUSE_USE_VERSION 31
+
+#include <fuse/fuse.h>
+#include <fuse/fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+
+struct Source {
+    char *filename;
+    size_t from;
+    size_t to;
+    size_t start; // starting position in concatenated file
+    int fd;
+};
+
+static struct {
+    struct Source *array;
+    int count;
+    size_t size;
+} sources;
+
+#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 );
+}
+#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;
+       }
+    }
+    // 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;
+    }
+    p->to = ( m < 0 )? buf.st_size : atol( in+m+1 );
+    if ( p->from > p->to || p->to > buf.st_size ) {
+       return 1;
+    }
+    return 0;
+}
+
+static int setup_sources(char **argv,int i,int n) {
+    sources.array = calloc( n, sizeof( struct Source ) );
+    if ( sources.array == 0 ) {
+       return 1;
+    }
+    sources.count = n;
+    int j = 0;
+    sources.size = 0;
+    for ( ; j < n; i++, j++ ) {
+       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;
+       p->fd = open( p->filename, O_RDONLY );
+       if ( p->fd < 0 ) {
+           perror( p->filename );
+           return 1;
+       }
+#if DEBUG
+       print_source( p );
+#endif
+    }
+    return 0;
+}
+
+static int fusefile_getattr(const char *path, struct stat *stbuf )
+{
+    if ( strcmp( path, "/" ) != 0 ) {
+       return -ENOENT;
+    }
+#if DEBUG
+    fprintf( stderr, "getattr %ld\n", sources.size );
+#endif
+    memset( stbuf, 0, sizeof( struct stat ) );
+    stbuf->st_mode = S_IFREG | 0444; // Hmmm
+    stbuf->st_nlink = 1;
+    stbuf->st_size = sources.size;
+    time_t now = time( 0 );
+    stbuf->st_atime = now;
+    stbuf->st_mtime = now;
+    stbuf->st_ctime = now;
+    stbuf->st_uid = getuid();
+    stbuf->st_gid = getgid();
+    return 0;
+}
+
+static int fusefile_open(const char *path, struct fuse_file_info *fi)
+{
+    if ( strcmp( path, "/" ) != 0 ) {
+       return -ENOENT;
+    }
+    return 0;
+}
+
+static int find_source(off_t offset) {
+    int lo = 0;
+    int hi = sources.count;
+    if ( offset > sources.size ) {
+       return -1;
+    }
+    while ( lo < hi ) {
+       int m = ( lo + hi ) / 2;
+       if ( sources.array[m].start > offset ) {
+           hi = m;
+       } else if ( m+1 < hi && sources.array[m+1].start < offset ) {
+           lo = m+1;
+       } else {
+           return m;
+       }
+    }
+    return lo;
+}
+
+// Read <size> bytes from <offset> in file
+static int fusefile_read(const char *path, char *buf, size_t size,
+                     off_t offset, struct fuse_file_info *fi)
+{
+    if( strcmp( path, "/" ) != 0 ) {
+       return -ENOENT;
+    }
+#if DEBUG
+    fprintf( stderr, "read %ld %ld\n", offset, size );
+#endif
+    size_t rr = 0;
+    while ( size > 0 ) {
+#if DEBUG
+       fprintf( stderr, "find_source %ld %ld\n", offset, size );
+#endif
+       int i = find_source( offset );
+       if ( i < 0 ) {
+           return -ENOENT;
+       }
+       if ( sources.array[i].fd < 0 ) {
+           return -ENOENT;
+       }
+#if DEBUG
+       print_source( &sources.array[i] );
+#endif
+       size_t b = offset - sources.array[i].start + sources.array[i].from;
+       size_t n = sources.array[i].to - b;
+       if ( n > size ) {
+           n = size;
+       }
+       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 );
+#endif
+       ssize_t r = read( sources.array[i].fd, buf + rr, n );
+#if DEBUG
+       fprintf( stderr, "got %ld bytes\n", r );
+#endif
+       if ( r < 0 ) {
+           perror( sources.array[i].filename );
+           return -ENOENT;
+       }
+       if ( r == 0 ) {
+           break;
+       }
+       rr += r;
+       offset += r;
+       size -= r;
+    }
+    return rr;
+}
+
+static void fusefile_destroy(void *data) {
+    char *mnt = (char*) data;
+    if ( mnt ) {
+       unlink( mnt );
+    }
+}
+
+static struct fuse_operations fusefile_oper = {
+    .getattr        = fusefile_getattr,
+    .open           = fusefile_open,
+    .read           = fusefile_read,
+    .destroy = fusefile_destroy,
+};
+
+static void usage() {
+    char *usage =
+"Usage: catfs [ <fuse options> ] <mount> <file/from-to> ... \n"
+"Mount a concatenation of files\n"
+       ;
+    fprintf( stderr, "%s", usage );
+    exit( 1 );
+}
+
+/**
+ * Mount a concatenation of files,
+ * [ <fuse options> ] <mount> <file:from,to> ...
+ */
+int main(int argc, char *argv[])
+{
+    char *mnt;
+    int mt;
+    int fg;
+    int i;
+    struct stat stbuf;
+    int temporary = 0;
+    // Scan past options
+    for ( i = 1; i < argc; i++ ) {
+       if ( *argv[i] != '-' ) {
+           break;
+       }
+    }
+    if ( i > argc - 2 ) { // At least one source
+       usage();
+    }
+    i++;
+    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 ) {
+           perror( mnt );
+           return 1;
+       }
+       temporary = 1;
+       close( fd );
+    } else if ( ! S_ISREG( stbuf.st_mode ) ) {
+       fprintf( stderr, "mountpoint is not a regular file\n" );
+       return 1;
+    }
+    struct fuse_args args = FUSE_ARGS_INIT( i, argv );
+    if ( fuse_parse_cmdline( &args, &mnt, &mt, &fg ) ) {
+       return 1;
+    }
+    fuse_opt_free_args( &args );
+    if ( ! mnt ) {
+       fprintf( stderr, "missing mountpoint parameter\n" );
+       return 1;
+    }
+    return fuse_main( i, argv, &fusefile_oper, temporary? mnt : NULL );
+}