From: Ralph Ronnquist Date: Mon, 11 Mar 2019 00:55:22 +0000 (+1100) Subject: initial commit X-Git-Tag: 0.3~16 X-Git-Url: https://git.rrq.au/?a=commitdiff_plain;h=81d273f526ffaa4d00d63bf3221ee43b405a678b;p=rrq%2Ffusefile.git initial commit --- 81d273f526ffaa4d00d63bf3221ee43b405a678b diff --git a/Makefile b/Makefile new file mode 100644 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 index 0000000..5dba7e4 --- /dev/null +++ b/fusefile.8 @@ -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 diff --git a/fusefile.c b/fusefile.c new file mode 100644 index 0000000..632221f --- /dev/null +++ b/fusefile.c @@ -0,0 +1,285 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + 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 +#include +#include +#include +#include +#include +#include +#include + +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 bytes from 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 [ ] ... \n" +"Mount a concatenation of files\n" + ; + fprintf( stderr, "%s", usage ); + exit( 1 ); +} + +/** + * Mount a concatenation of files, + * [ ] ... + */ +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 ); +}