added licence terms
[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 31
27
28 #include <fuse/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     size_t from;
40     size_t to;
41     size_t start; // starting position in concatenated file
42     int fd;
43 };
44
45 static struct {
46     struct Source *array;
47     int count;
48     size_t size;
49 } sources;
50
51 #if DEBUG
52 static void print_source(struct Source *p) {
53     fprintf( stderr, "%p { %s, %ld, %ld, %ld, %d }\n",
54              p, p->filename, p->from, p->to, p->start, p-> fd );
55 }
56 #endif
57
58 // Scan the source specification, and return the length of the
59 // inclusion. "filename/from,to"
60 // filename
61 // filename/from
62 // filename/-to
63 // filename/from-to
64 static size_t scan_source(char *in,struct Source *p) {
65     int e = strlen( in );
66     int i = e-1;
67     int s = -1;
68     int m = -1;
69     // scan for last '/' and last '-'
70     for ( ; i >= 0; i-- ) {
71         if ( in[i] == '/' ) {
72             s = i;
73             break;
74         }
75         if ( in[i] == '-' ) {
76             m = i;
77         }
78     }
79     // Copy the filename, and set from and to
80     p->filename = strndup( in, ( s < 0 )? e : s );
81     struct stat buf;
82     if ( stat( p->filename, & buf ) ) {
83         perror( p->filename );
84         return 1; 
85     }
86     p->from = ( s < 0 )? 0 : atol( in+s+1 );
87     if ( p->from < 0 ) {
88         p->from = 0;
89     }
90     p->to = ( m < 0 )? buf.st_size : atol( in+m+1 );
91     if ( p->from > p->to || p->to > buf.st_size ) {
92         return 1;
93     }
94     return 0;
95 }
96
97 static int setup_sources(char **argv,int i,int n) {
98     sources.array = calloc( n, sizeof( struct Source ) );
99     if ( sources.array == 0 ) {
100         return 1;
101     }
102     sources.count = n;
103     int j = 0;
104     sources.size = 0;
105     for ( ; j < n; i++, j++ ) {
106         struct Source *p = sources.array + j;
107         if ( scan_source( argv[i], p ) ) {
108             // should free everything malloc-ed
109             return 1;
110         }
111         p->start = sources.size;
112         sources.size += p->to - p->from;
113         p->fd = open( p->filename, O_RDONLY );
114         if ( p->fd < 0 ) {
115             perror( p->filename );
116             return 1;
117         }
118 #if DEBUG
119         print_source( p );
120 #endif
121     }
122     return 0;
123 }
124
125 static int fusefile_getattr(const char *path, struct stat *stbuf )
126 {
127     if ( strcmp( path, "/" ) != 0 ) {
128         return -ENOENT;
129     }
130 #if DEBUG
131     fprintf( stderr, "getattr %ld\n", sources.size );
132 #endif
133     memset( stbuf, 0, sizeof( struct stat ) );
134     stbuf->st_mode = S_IFREG | 0444; // Hmmm
135     stbuf->st_nlink = 1;
136     stbuf->st_size = sources.size;
137     time_t now = time( 0 );
138     stbuf->st_atime = now;
139     stbuf->st_mtime = now;
140     stbuf->st_ctime = now;
141     stbuf->st_uid = getuid();
142     stbuf->st_gid = getgid();
143     return 0;
144 }
145
146 static int fusefile_open(const char *path, struct fuse_file_info *fi)
147 {
148     if ( strcmp( path, "/" ) != 0 ) {
149         return -ENOENT;
150     }
151     return 0;
152 }
153
154 static int find_source(off_t offset) {
155     int lo = 0;
156     int hi = sources.count;
157     if ( offset > sources.size ) {
158         return -1;
159     }
160     while ( lo < hi ) {
161         int m = ( lo + hi ) / 2;
162         if ( sources.array[m].start > offset ) {
163             hi = m;
164         } else if ( m+1 < hi && sources.array[m+1].start < offset ) {
165             lo = m+1;
166         } else {
167             return m;
168         }
169     }
170     return lo;
171 }
172
173 // Read <size> bytes from <offset> in file
174 static int fusefile_read(const char *path, char *buf, size_t size,
175                      off_t offset, struct fuse_file_info *fi)
176 {
177     if( strcmp( path, "/" ) != 0 ) {
178         return -ENOENT;
179     }
180 #if DEBUG
181     fprintf( stderr, "read %ld %ld\n", offset, size );
182 #endif
183     size_t rr = 0;
184     while ( size > 0 ) {
185 #if DEBUG
186         fprintf( stderr, "find_source %ld %ld\n", offset, size );
187 #endif
188         int i = find_source( offset );
189         if ( i < 0 ) {
190             return -ENOENT;
191         }
192         if ( sources.array[i].fd < 0 ) {
193             return -ENOENT;
194         }
195 #if DEBUG
196         print_source( &sources.array[i] );
197 #endif
198         size_t b = offset - sources.array[i].start + sources.array[i].from;
199         size_t n = sources.array[i].to - b;
200         if ( n > size ) {
201             n = size;
202         }
203         if ( lseek( sources.array[i].fd, b, SEEK_SET ) < 0 ) {
204             perror( sources.array[i].filename );
205             return -ENOENT;
206         }
207 #if DEBUG
208         fprintf( stderr, "get %ld bytes at %ld\n", n, rr );
209 #endif
210         ssize_t r = read( sources.array[i].fd, buf + rr, n );
211 #if DEBUG
212         fprintf( stderr, "got %ld bytes\n", r );
213 #endif
214         if ( r < 0 ) {
215             perror( sources.array[i].filename );
216             return -ENOENT;
217         }
218         if ( r == 0 ) {
219             break;
220         }
221         rr += r;
222         offset += r;
223         size -= r;
224     }
225     return rr;
226 }
227
228 static void fusefile_destroy(void *data) {
229     char *mnt = (char*) data;
230     if ( mnt ) {
231         unlink( mnt );
232     }
233 }
234
235 static struct fuse_operations fusefile_oper = {
236     .getattr = fusefile_getattr,
237     .open = fusefile_open,
238     .read = fusefile_read,
239     .destroy = fusefile_destroy,
240 };
241
242 static void usage() {
243     char *usage =
244 "Usage: fusefile [ <fuse options> ] <mount> <file/from-to> ... \n"
245 "Mounts a virtual, read-only file that is a concatenation of file fragments\n"
246         ;
247     fprintf( stderr, "%s", usage );
248     exit( 1 );
249 }
250
251 /**
252  * Mount a concatenation of files,
253  * [ <fuse options> ] <mount> <file/from-to> ...
254  */
255 int main(int argc, char *argv[])
256 {
257     char *mnt;
258     int mt;
259     int fg;
260     int i;
261     struct stat stbuf;
262     int temporary = 0;
263     // Scan past options
264     for ( i = 1; i < argc; i++ ) {
265         if ( *argv[i] != '-' ) {
266             break;
267         }
268     }
269     if ( i > argc - 2 ) { // At least one source
270         usage();
271     }
272     i++;
273     if ( setup_sources( argv, i, argc-i ) ) {
274         return 1;
275     }
276     mnt = argv[i-1];
277     if ( stat( mnt, &stbuf ) == -1 ) {
278         int fd = open( mnt, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR );
279         if ( fd < 0 ) {
280             perror( mnt );
281             return 1;
282         }
283         temporary = 1;
284         close( fd );
285     } else if ( ! S_ISREG( stbuf.st_mode ) ) {
286         fprintf( stderr, "mountpoint is not a regular file\n" );
287         return 1;
288     }
289     struct fuse_args args = FUSE_ARGS_INIT( i, argv );
290     if ( fuse_parse_cmdline( &args, &mnt, &mt, &fg ) ) {
291         return 1;
292     }
293     fuse_opt_free_args( &args );
294     if ( ! mnt ) {
295         fprintf( stderr, "missing mountpoint parameter\n" );
296         return 1;
297     }
298     return fuse_main( i, argv, &fusefile_oper, temporary? mnt : NULL );
299 }