--- /dev/null
+.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>
--- /dev/null
+/*
+ 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 );
+}