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