more argument polishing
[rrq/fusefile.git] / fusefile.c
1 /***
2     fusefile - overlay a file path with a concatenation of parts of
3     other files, read only.
4
5     Copyright (C) 2019  Ralph Ronnquist
6
7     This program is free software: you can redistribute it and/or
8     modify it under the terms of the GNU General Public License as
9     published by the Free Software Foundation, either version 3 of the
10     License, or (at your option) any later version.
11
12     This program is distributed in the hope that it will be useful,
13     but WITHOUT ANY WARRANTY; without even the implied warranty of
14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15     General Public License for more details.
16
17     You should have received a copy of the GNU General Public License
18     along with this program. If not, see
19     <http://www.gnu.org/licenses/>.
20
21     This source was inspired by the "null.c" example of the libfuse
22     sources, which is distributed under GPL2, and copyright (C)
23     2001-2007 Miklos Szeredi <miklos@szeredi.hu>.
24 */
25
26 #define FUSE_USE_VERSION 33
27
28 #include <fuse.h>
29 #include <fuse/fuse_lowlevel.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
34 #include <time.h>
35 #include <errno.h>
36
37 struct Source {
38     char *filename;
39     ssize_t from;
40     ssize_t to;
41     ssize_t start; // starting position in concatenated file
42     int fd;
43 };
44
45 static struct {
46     struct Source *array;
47     int count;
48     ssize_t size;
49 } sources;
50
51 static struct {
52     time_t atime;
53     time_t mtime;
54     time_t ctime;
55 } times;
56     
57 #if DEBUG
58 static void print_source(struct Source *p) {
59     fprintf( stderr, "%p { %s, %ld, %ld, %ld, %d }\n",
60              p, p->filename, p->from, p->to, p->start, p-> fd );
61 }
62 #endif
63
64 static char *range;
65 static unsigned int c;
66 static int RANGE(int s,int n ) {
67     return ( s == n ) && *(range+c) == 0;
68 }
69
70 static int setup_sources(char **argv,int i,int n) {
71     sources.array = calloc( n, sizeof( struct Source ) );
72     if ( sources.array == 0 ) {
73         return 1;
74     }
75     sources.count = n;
76     int j = 0;
77     sources.size = 0;
78     for ( ; j < n; i++, j++ ) {
79         struct stat filestat;
80         struct Source *p = sources.array + j;
81         // Open the fragment file rw if possible, else ro
82         range = strrchr( argv[i], '/' ); // last '/'
83         p->filename = range? strndup( argv[i], range - argv[i] ) : argv[i];
84         p->fd = open( p->filename, O_RDWR );
85         int rdonly = 0;
86         if ( p->fd < 0 ) {
87             rdonly = 1;
88             p->fd = open( p->filename, O_RDONLY );
89         }
90         if ( p->fd < 0 ) {
91             perror( p->filename );
92             return 1; // Error return
93         }
94         if ( stat( p->filename, &filestat ) ) {
95             perror( p->filename );
96             return 1; 
97         }
98         if ( rdonly ) {
99             fprintf( stderr, "** %s opened read-only\n", p->filename );
100         }
101         p->from = 0;
102         p->to = filestat.st_size;
103         // Process any range variation
104         if ( range && *(++range) ) {
105             int a,b;
106             if ( 0 ) {
107             } else if ( RANGE( sscanf( range, "%d:%d%n", &a, &b, &c ), 2 )) {
108                 p->from = ( a < 0 )? ( p->to + a ) : a;
109                 p->to = ( b < 0 )? ( p->to + b ) : b;
110             } else if ( RANGE( sscanf( range, "%d+%d%n", &a, &b, &c ), 2 )) {
111                 p->from = ( a < 0 )? ( p->to + a ) : a;
112                 p->to = ( ( b < 0 )? p->to : p->from ) + b;
113             } else if ( RANGE( sscanf( range, "%d+%n", &a, &c ), 1 )) {
114                 p->from = ( a < 0 )? ( p->to + a ) : a;
115             } else if ( RANGE( sscanf( range, ":%d%n", &b, &c ), 1 )) {
116                 p->to = ( b < 0 )? ( p->to + b ) : b;
117             } else if ( RANGE( sscanf( range, "%d:%n", &a, &c ), 1 )) {
118                 p->from = ( a < 0 )? ( p->to + a ) : a;
119             } else if ( RANGE( sscanf( range, "%d%n", &a, &c ), 1 )) {
120                 if ( a >= 0 ) {
121                     p->from = a;
122                 } else {
123                     p->from = p->to + a;
124                 }
125             } else if ( RANGE( sscanf( range, ":%n", &c), 0 ) ) {
126                 // to end from start
127             } else {
128                 fprintf( stderr, "** BAD RANGE: %s\n", argv[i] );
129                 return 1;
130             }
131         }
132         if ( ( filestat.st_mode &  S_IFMT ) == S_IFCHR ) {
133             filestat.st_size = p->to; // Pretend size of character device
134         }
135         if ( p->from < 0 ) {
136             p->from = 0;
137         }
138         if ( p->to > filestat.st_size ) {
139             p->to = filestat.st_size;
140         }
141         if ( p->from >= p->to || p->from >= filestat.st_size ) {
142             fprintf( stderr, "** BAD RANGE: %s [%ld:%ld]\n",
143                      argv[i], p->from, p->to );
144             return 1;
145         }
146         p->start = sources.size; // the fusefile position of fragment
147         sources.size += p->to - p->from;
148 #if DEBUG
149         print_source( p );
150 #endif
151     }
152     return 0;
153 }
154
155 static int fusefile_getattr(const char *path,struct stat *stbuf) {
156 #if DEBUG
157     fprintf( stderr, "fusefile_getattr( %s )\n", path );
158 #endif
159     if ( strcmp( path, "/" ) != 0 ) {
160         return -ENOENT;
161     }
162 #if DEBUG
163     fprintf( stderr, "getattr %ld\n", sources.size );
164 #endif
165     memset( stbuf, 0, sizeof( struct stat ) );
166     stbuf->st_mode = S_IFREG | 0644; // Hmmm
167     stbuf->st_nlink = 1;
168     stbuf->st_size = sources.size;
169     stbuf->st_atime = times.atime;
170     stbuf->st_mtime = times.mtime;
171     stbuf->st_ctime = times.ctime;
172     stbuf->st_uid = getuid();
173     stbuf->st_gid = getgid();
174     return 0;
175 }
176
177 static int fusefile_chmod(const char *path,mode_t m) {
178 #if DEBUG
179     fprintf( stderr, "fusefile_chmod( %s, %d )\n", path, m );
180 #endif
181     return -1;
182 }
183
184 static int fusefile_open(const char *path,struct fuse_file_info *fi) {
185 #if DEBUG
186     fprintf( stderr, "fusefile_open( %s, %d )\n", path, fi->flags );
187     fprintf( stderr, "fixing( %d )\n", fi->flags | O_CLOEXEC );
188 #endif
189     if ( strcmp( path, "/" ) != 0 ) {
190         return -ENOENT;
191     }
192     // set O-CLOEXEC  for this opening?
193     times.atime = time( 0 );
194     return 0;
195 }
196
197 static int find_source(off_t offset) {
198     int lo = 0;
199     int hi = sources.count;
200     if ( offset >= sources.size ) {
201         return -1;
202     }
203     while ( lo + 1 < hi ) {
204         int m = ( lo + hi ) / 2;
205         if ( offset < sources.array[ m ].start ) {
206             hi = m;
207         } else {
208             lo = m;
209         }
210     }
211     return lo;
212 }
213
214 // Read <size> bytes from <offset> in file
215 static int fusefile_read(const char *path, char *buf, size_t size,
216                          off_t off, struct fuse_file_info *fi)
217 {
218 #if DEBUG
219     fprintf( stderr, "fusefile_read( %s )\n", path );
220 #endif
221     if( strcmp( path, "/" ) != 0 ) {
222         return -ENOENT;
223     }
224 #if DEBUG
225     fprintf( stderr, "read %ld %ld\n", off, size );
226 #endif
227     size_t rr = 0;
228     while ( size > 0 ) {
229 #if DEBUG
230         fprintf( stderr, "find_source %ld %ld\n", off, size );
231 #endif
232         int i = find_source( off );
233         if ( i < 0 ) {
234             return ( off == sources.size )? rr : -ENOENT;
235         }
236         if ( sources.array[i].fd < 0 ) {
237             return -ENOENT;
238         }
239 #if DEBUG
240         print_source( &sources.array[i] );
241 #endif
242         times.atime = time( 0 );
243         size_t b = off - sources.array[i].start + sources.array[i].from;
244         size_t n = sources.array[i].to - b;
245         if ( n > size ) {
246             n = size;
247         }
248         if ( lseek( sources.array[i].fd, b, SEEK_SET ) < 0 ) {
249             perror( sources.array[i].filename );
250             return -ENOENT;
251         }
252 #if DEBUG
253         fprintf( stderr, "get %ld bytes at %ld\n", n, rr );
254 #endif
255         ssize_t r = read( sources.array[i].fd, buf + rr, n );
256 #if DEBUG
257         fprintf( stderr, "got %ld bytes\n", r );
258 #endif
259         if ( r < 0 ) {
260             perror( sources.array[i].filename );
261             return -ENOENT;
262         }
263         if ( r == 0 ) {
264             break;
265         }
266         rr += r;
267         off += r;
268         size -= r;
269     }
270     return rr;
271 }
272
273 /**
274  * Poll for IO readiness.
275  */
276 int fusefile_poll(const char *path, struct fuse_file_info *fi,
277                    struct fuse_pollhandle *ph, unsigned *reventsp )
278 {
279 #if DEBUG
280     fprintf( stderr, "fusefile_poll( %s ) %p %d\n", path, ph, *reventsp );
281 #endif
282     if( strcmp( path, "/" ) != 0 ) {
283         return -ENOENT;
284     }
285     if ( ph ) {
286         return fuse_notify_poll( ph );
287     }
288     return 0;
289 }
290
291
292 /**
293  * Write a full block of data over the sources at the offset
294  */
295 static int write_block(off_t off,const char *buf,size_t size) {
296 #if DEBUG
297     fprintf( stderr, "write_block( %ld, ?, %ld )\n", off, size );
298 #endif
299     while ( size > 0 ) {
300         int index = find_source( off ); // index of source file
301         if ( index < 0 ) {
302             return -EIO; // past EOF
303         }
304         struct Source *source = &sources.array[ index ];
305         off_t from = off - source->start + source->from;
306         off_t max = source->to - from;
307         if ( lseek( source->fd, from, SEEK_SET ) < 0 ) {
308             return -EIO;
309         }
310         ssize_t todo = ( size < max )? size : max;
311         while ( todo > 0 ) {
312             times.mtime = time( 0 );
313             ssize_t n = write( source->fd, buf, todo );
314             if ( n <= 0 ) {
315                 return -EIO; // Something wrong
316             }
317             buf += n;
318             todo -= n;
319             size -= n;
320             off += n;
321         }
322     }
323     return 0;
324 }
325
326 static int fusefile_write_buf(const char *path, struct fuse_bufvec *buf,
327                               off_t off, struct fuse_file_info *fi) {
328 #if DEBUG
329     fprintf( stderr, "fusefile_write_buf( %s )\n", path );
330 #endif
331     if ( strcmp( path, "/" ) != 0 ) {
332         return -ENOENT;
333     }
334
335     size_t size = 0;
336     int i;
337     for ( i = 0; i < buf->count; i++ ) {
338         struct fuse_buf *p = &buf->buf[i];
339         if ( p->flags & FUSE_BUF_IS_FD ) {
340 #if DEBUG
341             fprintf( stderr, "Content held in a file ... HELP!!\n" );
342 #endif
343             return -EIO;
344         }
345         if ( write_block( off, (char*) p->mem, p->size ) < 0 ) {
346             return -EIO;
347         }
348         size += p->size;
349     }
350 #if DEBUG
351     fprintf( stderr, "fusefile_write_buf written %ld\n", size );
352 #endif
353     return size;
354 }
355
356 /**
357  * Write a fragment at <off>. This overwrites files.
358  */
359 static int fusefile_write(const char *path, const char *buf, size_t size,
360                           off_t off, struct fuse_file_info *fi)
361 {
362 #if DEBUG
363     fprintf( stderr, "fusefile_write( %s %ld )\n", path, size );
364 #endif
365     if ( strcmp( path, "/" ) != 0 ) {
366         return -ENOENT;
367     }
368
369     if ( write_block( off, buf, size ) < 0 ) {
370         return -EIO;
371     }
372     return size;
373 }
374
375 static void fusefile_destroy(void *data) {
376     char *mnt = (char*) data; // As passed to fuse_main
377 #if DEBUG
378     fprintf( stderr, "fusefile_destroy( %s )\n", mnt? mnt : "" );
379 #endif
380     if ( mnt ) {
381         unlink( mnt );
382     }
383 }
384
385 static int fusefile_flush(const char *path, struct fuse_file_info *info) {
386 #if DEBUG
387     fprintf( stderr, "fusefile_flush( %s )\n", path );
388 #endif
389     if ( strcmp( path, "/" ) != 0 ) {
390         return -ENOENT;
391     }
392     return 0;
393 }
394
395 static int fusefile_release(const char *path, struct fuse_file_info *fi) {
396 #if DEBUG
397     fprintf( stderr, "fusefile_release( %s, %d )\n", path, fi->flags );
398 #endif
399     if ( strcmp( path, "/" ) != 0 ) {
400         return -ENOENT;
401     }
402     return 0;
403 }
404
405 static int fusefile_fsync(const char *path, int x, struct fuse_file_info *fi) {
406 #if DEBUG
407     fprintf( stderr, "fusefile_fsync( %s, %d )\n", path, x );
408 #endif
409     if ( strcmp( path, "/" ) != 0 ) {
410         return -ENOENT;
411     }
412     return 0;
413 }
414
415 /**
416  * 
417  */
418 static int fusefile_truncate(const char *path, off_t len) {
419 #if DEBUG
420     fprintf( stderr, "fusefile_truncate( %s, %ld )\n", path, len );
421 #endif
422     if ( strcmp( path, "/" ) != 0 ) {
423         return -ENOENT;
424     }
425     return -EIO;
426 }
427
428 void *fusefile_init(struct fuse_conn_info *fci) {
429 #if DEBUG
430     fprintf( stderr, "fusefile_init( %d, %d )\n", fci->async_read, fci->want );
431 #endif
432     // Disable asynchronous reading
433     fci->async_read = 0;
434     fci->want &= ~FUSE_CAP_ASYNC_READ;
435 #if DEBUG
436     fprintf( stderr, "fusefile_init( %d, %d )\n", fci->async_read, fci->want );
437 #endif
438     return 0;
439 }
440
441 static struct fuse_operations fusefile_oper = {
442     .getattr = fusefile_getattr,
443     .chmod = fusefile_chmod,
444     .open = fusefile_open,
445     .read = fusefile_read,
446     .poll = fusefile_poll,
447     .write = fusefile_write,
448     .write_buf = fusefile_write_buf,
449     .destroy = fusefile_destroy,
450     .flush = fusefile_flush,
451     .release = fusefile_release,
452     .fsync = fusefile_fsync,
453     .truncate = fusefile_truncate,
454     //.truncate = fusefile_truncate,
455     //.release = fusefile_release,
456     .init = fusefile_init,
457 };
458
459 static void usage() {
460     char *usage =
461 "Usage: fusefile [ <fuse options> ] <mount> <file/from-to> ... \n"
462 "Mounts a virtual, read-only file that is a concatenation of file fragments\n"
463         ;
464     fprintf( stderr, "%s", usage );
465     exit( 1 );
466 }
467
468 /**
469  * Set up the arguments for the fuse_main call, adding our own.
470  * argv[argc] is the mount point argument
471  */
472 static int setup_argv(int argc,char ***argv) {
473     // note: (*argv)[ argc ] is the mount point argument
474     char *OURS[] = {
475         "-odefault_permissions",
476         (*argv)[ argc ]
477     };
478 #define OURSN ( sizeof( OURS ) / sizeof( char* ) )
479     int N = argc + OURSN;
480     // Allocate new arg array plus terminating null pointer
481     char **out = malloc( ( N + 1 ) * sizeof( char* ) ); 
482     int i;
483     for ( i = 0; i < argc; i++ ) {
484         out[ i ] = (*argv)[i];
485         //fprintf( stderr, " %s", out[ i ] );
486     }
487     for ( i = 0; i < OURSN; i++ ) {
488         out[ argc + i ] = OURS[i];
489         //fprintf( stderr, " %s", out[ i ] );
490     }
491     out[ N ] = 0;
492     //fprintf( stderr, "\n" );
493     (*argv) = out;
494     return N; // Don't include the terminating null pointer
495 }
496
497 /**
498  * Mount a concatenation of files,
499  * [ <fuse options> ] <mount> <file/from-to> ...
500  */
501 int main(int argc, char *argv[])
502 {
503     char *mnt;
504     int mt;
505     int fg;
506     int i;
507     int fuseargc;
508     struct stat stbuf;
509     int temporary = 0;
510     // Scan past options
511     for ( i = 1; i < argc; i++ ) {
512         if ( *argv[i] != '-' ) {
513             break;
514         }
515     }
516     if ( i > argc - 2 ) { // At least mount point plus one source
517         usage();
518     }
519     fuseargc = i;
520     mnt = argv[ i++ ]; // First non-option argument is the mount pount
521     if ( setup_sources( argv, i, argc-i ) ) {
522         return 1;
523     }
524     if ( stat( mnt, &stbuf ) == -1 ) {
525         int fd = open( mnt, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR );
526         if ( fd < 0 ) {
527             perror( mnt );
528             return 1;
529         }
530         time_t now = time( 0 );
531         times.atime = now;
532         times.mtime = now;
533         times.ctime = now;
534         temporary = 1;
535         close( fd );
536     } else if ( ! S_ISREG( stbuf.st_mode ) ) {
537         fprintf( stderr, "mountpoint is not a regular file\n" );
538         return 1;
539     } else {
540         times.atime = stbuf.st_atime;
541         times.mtime = stbuf.st_mtime;
542         times.ctime = stbuf.st_ctime;
543     }
544
545     fuseargc = setup_argv( fuseargc, &argv );
546     struct fuse_args args = FUSE_ARGS_INIT( fuseargc, argv );
547     if ( fuse_parse_cmdline( &args, &mnt, &mt, &fg ) ) {
548         return 1;
549     }
550     fuse_opt_free_args( &args );
551     if ( ! mnt ) {
552         fprintf( stderr, "missing mountpoint parameter\n" );
553         return 1;
554     }
555     return fuse_main( fuseargc, argv, &fusefile_oper, temporary? mnt : NULL );
556 }