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