From 26b59ee2e0ee0a47f19153e629ce0c7a94bb134d Mon Sep 17 00:00:00 2001 From: Ralph Ronnquist Date: Thu, 26 May 2022 12:37:33 +1000 Subject: [PATCH] add writability --- Makefile | 9 +- README.adoc | 17 +++- fusefile.8 | 15 ++++ fusefile.c | 248 +++++++++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 265 insertions(+), 24 deletions(-) diff --git a/Makefile b/Makefile index 8103f14..c2a0ba6 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,11 @@ BINS = fusefile 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 @@ -14,6 +18,9 @@ fusefile: fusefile.o clean: rm -f $(BINS) +README.html: README.adoc + asciidoctor $< > $@ + # Building a debian package SBINDIR = $(DESTDIR)/usr/sbin diff --git a/README.adoc b/README.adoc index 6d9f775..a37d36d 100644 --- a/README.adoc +++ b/README.adoc @@ -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. -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. @@ -34,6 +34,13 @@ variations: * __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: @@ -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. +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 diff --git a/fusefile.8 b/fusefile.8 index 5dba7e4..7051d94 100644 --- a/fusefile.8 +++ b/fusefile.8 @@ -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). +.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 @@ -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. +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 diff --git a/fusefile.c b/fusefile.c index 00f7f7f..41c6ed6 100644 --- a/fusefile.c +++ b/fusefile.c @@ -23,9 +23,9 @@ 2001-2007 Miklos Szeredi . */ -#define FUSE_USE_VERSION 31 +#define FUSE_USE_VERSION 33 -#include +#include #include #include #include @@ -45,13 +45,24 @@ struct Source { static struct { struct Source *array; int count; + int limit; 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", - p, p->filename, p->from, p->to, p->start, p-> fd ); + p, p->filename, p->from, p->to, p->start, p->fd ); } #endif @@ -76,7 +87,9 @@ static size_t scan_source(char *in,struct Source *p) { 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; @@ -88,7 +101,9 @@ static size_t scan_source(char *in,struct Source *p) { 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; @@ -102,6 +117,7 @@ static int setup_sources(char **argv,int i,int n) { return 1; } sources.count = n; + sources.limit = n; 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; } -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; } @@ -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 + if ( pad.filename ) { + stbuf->st_mode |= 0200; + } 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; } -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; } + // set O-CLOEXEC for this opening? return 0; } @@ -172,22 +197,74 @@ static int find_source(off_t offset) { return lo; } -// Read bytes from in file +/** + * Insert a source fragment description into the table at . + */ +static int insert_source(struct Source *source,size_t off) { + int index = find_source( off ); + // Ensure at least 5 "free" Sources in + // 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 record at + // 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 + memcpy( SOURCEARRAYP( index+1 ), SOURCEARRAYP( index ), b ); + sources.count += 1; + } + } else { + // Append the new source to (at ) + 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 bytes from in file 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 - 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 - fprintf( stderr, "find_source %ld %ld\n", offset, size ); + fprintf( stderr, "find_source %ld %ld\n", off, size ); #endif - int i = find_source( offset ); + int i = find_source( off ); 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 - 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; @@ -221,24 +298,125 @@ static int fusefile_read(const char *path, char *buf, size_t size, break; } rr += r; - offset += r; + off += r; 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 . The data is appended to the pad file, + * and a descriptor is inserted; the fragment containing is + * first split, unless 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) { - 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 ); } } +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, + .write = fusefile_write, .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() { @@ -250,6 +428,19 @@ static void usage() { 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, * [ ] ... @@ -260,6 +451,7 @@ int main(int argc, char *argv[]) int mt; int fg; int i; + int fuseargc; struct stat stbuf; int temporary = 0; // Scan past options @@ -268,14 +460,27 @@ int main(int argc, char *argv[]) break; } } - if ( i > argc - 2 ) { // At least one source + if ( i > argc - 2 ) { // At least mount point plus one source 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; } - mnt = argv[i-1]; 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; } - 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; } @@ -297,5 +503,5 @@ int main(int argc, char *argv[]) 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 ); } -- 2.39.2