Fossil SCM

fossil-scm / src / fusefs.c
Blame History Raw 368 lines
1
/*
2
** Copyright (c) 2014 D. Richard Hipp
3
**
4
** This program is free software; you can redistribute it and/or
5
** modify it under the terms of the Simplified BSD License (also
6
** known as the "2-Clause License" or "FreeBSD License".)
7
**
8
** This program is distributed in the hope that it will be useful,
9
** but without any warranty; without even the implied warranty of
10
** merchantability or fitness for a particular purpose.
11
**
12
** Author contact information:
13
** [email protected]
14
** http://www.hwaci.com/drh/
15
**
16
*******************************************************************************
17
**
18
** This module implements the userspace side of a Fuse Filesystem that
19
** contains all check-ins for a fossil repository.
20
**
21
** This module is a mostly a no-op unless compiled with -DFOSSIL_HAVE_FUSEFS.
22
** The FOSSIL_HAVE_FUSEFS should be omitted on systems that lack support for
23
** the Fuse Filesystem, of course.
24
*/
25
#include "config.h"
26
#include <stdio.h>
27
#include <string.h>
28
#ifdef FOSSIL_HAVE_FUSEFS
29
#include <errno.h>
30
#include <fcntl.h>
31
#include <stdlib.h>
32
#include <unistd.h>
33
#include <sys/types.h>
34
#include "fusefs.h"
35
36
#define FUSE_USE_VERSION 26
37
#include <fuse.h>
38
39
/*
40
** Global state information about the archive
41
*/
42
static struct sGlobal {
43
/* A cache of a single check-in manifest */
44
int rid; /* rid for the cached manifest */
45
char *zSymName; /* Symbolic name corresponding to rid */
46
Manifest *pMan; /* The cached manifest */
47
/* A cache of a single file within a single check-in */
48
int iFileRid; /* Check-in ID for the cached file */
49
ManifestFile *pFile; /* Name of a cached file */
50
Blob content; /* Content of the cached file */
51
/* Parsed path */
52
char *az[3]; /* 0=type, 1=id, 2=path */
53
} fusefs;
54
55
/*
56
** Clear the fusefs.az[] array.
57
*/
58
static void fusefs_clear_path(void){
59
int i;
60
for(i=0; i<count(fusefs.az); i++){
61
fossil_free(fusefs.az[i]);
62
fusefs.az[i] = 0;
63
}
64
}
65
66
/*
67
** Split of the input path into 0, 1, 2, or 3 elements in fusefs.az[].
68
** Return the number of elements.
69
**
70
** Any prior path parse is deleted.
71
*/
72
static int fusefs_parse_path(const char *zPath){
73
int i, j;
74
fusefs_clear_path();
75
if( strcmp(zPath,"/")==0 ) return 0;
76
for(i=0, j=1; i<2 && zPath[j]; i++){
77
int jStart = j;
78
while( zPath[j] && zPath[j]!='/' ){ j++; }
79
fusefs.az[i] = mprintf("%.*s", j-jStart, &zPath[jStart]);
80
if( zPath[j] ) j++;
81
}
82
if( zPath[j] ) fusefs.az[i++] = fossil_strdup(&zPath[j]);
83
return i;
84
}
85
86
/*
87
** Reclaim memory used by the fusefs local variable.
88
*/
89
static void fusefs_reset(void){
90
blob_reset(&fusefs.content);
91
manifest_destroy(fusefs.pMan);
92
fusefs.pMan = 0;
93
fossil_free(fusefs.zSymName);
94
fusefs.zSymName = 0;
95
fusefs.pFile = 0;
96
}
97
98
/*
99
** Load manifest rid into the cache.
100
*/
101
static void fusefs_load_rid(int rid, const char *zSymName){
102
if( fusefs.rid==rid && fusefs.pMan!=0 ) return;
103
fusefs_reset();
104
fusefs.zSymName = fossil_strdup(zSymName);
105
fusefs.pMan = manifest_get(rid, CFTYPE_MANIFEST, 0);
106
fusefs.rid = rid;
107
}
108
109
/*
110
** Locate the rid corresponding to a symbolic name
111
*/
112
static int fusefs_name_to_rid(const char *zSymName){
113
if( fusefs.rid>0 && strcmp(zSymName, fusefs.zSymName)==0 ){
114
return fusefs.rid;
115
}else{
116
return symbolic_name_to_rid(zSymName, "ci");
117
}
118
}
119
120
121
/*
122
** Implementation of stat()
123
*/
124
static int fusefs_getattr(const char *zPath, struct stat *stbuf){
125
int n, rid;
126
ManifestFile *pFile;
127
char *zDir;
128
stbuf->st_uid = getuid();
129
stbuf->st_gid = getgid();
130
n = fusefs_parse_path(zPath);
131
if( n==0 ){
132
stbuf->st_mode = S_IFDIR | 0555;
133
stbuf->st_nlink = 2;
134
return 0;
135
}
136
if( strcmp(fusefs.az[0],"checkins")!=0 ) return -ENOENT;
137
if( n==1 ){
138
stbuf->st_mode = S_IFDIR | 0111;
139
stbuf->st_nlink = 2;
140
return 0;
141
}
142
rid = fusefs_name_to_rid(fusefs.az[1]);
143
if( rid<=0 ) return -ENOENT;
144
if( n==2 ){
145
stbuf->st_mode = S_IFDIR | 0555;
146
stbuf->st_nlink = 2;
147
return 0;
148
}
149
fusefs_load_rid(rid, fusefs.az[1]);
150
if( fusefs.pMan==0 ) return -ENOENT;
151
stbuf->st_mtime = (fusefs.pMan->rDate - 2440587.5)*86400.0;
152
pFile = manifest_file_seek(fusefs.pMan, fusefs.az[2], 0);
153
if( pFile ){
154
static Stmt q;
155
stbuf->st_mode = S_IFREG |
156
(manifest_file_mperm(pFile)==PERM_EXE ? 0555 : 0444);
157
stbuf->st_nlink = 1;
158
db_static_prepare(&q, "SELECT size FROM blob WHERE uuid=$uuid");
159
db_bind_text(&q, "$uuid", pFile->zUuid);
160
if( db_step(&q)==SQLITE_ROW ){
161
stbuf->st_size = db_column_int(&q, 0);
162
}
163
db_reset(&q);
164
return 0;
165
}
166
zDir = mprintf("%s/", fusefs.az[2]);
167
pFile = manifest_file_seek(fusefs.pMan, zDir, 1);
168
fossil_free(zDir);
169
if( pFile==0 ) return -ENOENT;
170
n = (int)strlen(fusefs.az[2]);
171
if( strncmp(fusefs.az[2], pFile->zName, n)!=0 ) return -ENOENT;
172
if( pFile->zName[n]!='/' ) return -ENOENT;
173
stbuf->st_mode = S_IFDIR | 0555;
174
stbuf->st_nlink = 2;
175
return 0;
176
}
177
178
/*
179
** Implementation of readdir()
180
*/
181
static int fusefs_readdir(
182
const char *zPath,
183
void *buf,
184
fuse_fill_dir_t filler,
185
off_t offset,
186
struct fuse_file_info *fi
187
){
188
int n, rid;
189
ManifestFile *pFile;
190
const char *zPrev = "";
191
int nPrev = 0;
192
char *z;
193
int cnt = 0;
194
n = fusefs_parse_path(zPath);
195
if( n==0 ){
196
filler(buf, ".", NULL, 0);
197
filler(buf, "..", NULL, 0);
198
filler(buf, "checkins", NULL, 0);
199
return 0;
200
}
201
if( strcmp(fusefs.az[0],"checkins")!=0 ) return -ENOENT;
202
if( n==1 ) return -ENOENT;
203
rid = fusefs_name_to_rid(fusefs.az[1]);
204
if( rid<=0 ) return -ENOENT;
205
fusefs_load_rid(rid, fusefs.az[1]);
206
if( fusefs.pMan==0 ) return -ENOENT;
207
filler(buf, ".", NULL, 0);
208
filler(buf, "..", NULL, 0);
209
manifest_file_rewind(fusefs.pMan);
210
if( n==2 ){
211
while( (pFile = manifest_file_next(fusefs.pMan, 0))!=0 ){
212
if( nPrev>0 && strncmp(pFile->zName, zPrev, nPrev)==0
213
&& pFile->zName[nPrev]=='/' ) continue;
214
zPrev = pFile->zName;
215
for(nPrev=0; zPrev[nPrev] && zPrev[nPrev]!='/'; nPrev++){}
216
z = mprintf("%.*s", nPrev, zPrev);
217
filler(buf, z, NULL, 0);
218
fossil_free(z);
219
cnt++;
220
}
221
}else{
222
char *zBase = mprintf("%s/", fusefs.az[2]);
223
int nBase = (int)strlen(zBase);
224
while( (pFile = manifest_file_next(fusefs.pMan, 0))!=0 ){
225
if( strcmp(pFile->zName, zBase)>=0 ) break;
226
}
227
while( pFile && strncmp(zBase, pFile->zName, nBase)==0 ){
228
if( nPrev==0 || strncmp(pFile->zName+nBase, zPrev, nPrev)!=0 ){
229
zPrev = pFile->zName+nBase;
230
for(nPrev=0; zPrev[nPrev] && zPrev[nPrev]!='/'; nPrev++){}
231
if( zPrev[nPrev]=='/' ){
232
z = mprintf("%.*s", nPrev, zPrev);
233
filler(buf, z, NULL, 0);
234
fossil_free(z);
235
}else{
236
filler(buf, zPrev, NULL, 0);
237
nPrev = 0;
238
}
239
cnt++;
240
}
241
pFile = manifest_file_next(fusefs.pMan, 0);
242
}
243
fossil_free(zBase);
244
}
245
return cnt>0 ? 0 : -ENOENT;
246
}
247
248
249
/*
250
** Implementation of read()
251
*/
252
static int fusefs_read(
253
const char *zPath,
254
char *buf,
255
size_t size,
256
off_t offset,
257
struct fuse_file_info *fi
258
){
259
int n, rid;
260
n = fusefs_parse_path(zPath);
261
if( n<3 ) return -ENOENT;
262
if( strcmp(fusefs.az[0], "checkins")!=0 ) return -ENOENT;
263
rid = fusefs_name_to_rid(fusefs.az[1]);
264
if( rid<=0 ) return -ENOENT;
265
fusefs_load_rid(rid, fusefs.az[1]);
266
if( fusefs.pFile!=0 && strcmp(fusefs.az[2], fusefs.pFile->zName)!=0 ){
267
fusefs.pFile = 0;
268
blob_reset(&fusefs.content);
269
}
270
fusefs.pFile = manifest_file_seek(fusefs.pMan, fusefs.az[2], 0);
271
if( fusefs.pFile==0 ) return -ENOENT;
272
rid = uuid_to_rid(fusefs.pFile->zUuid, 0);
273
blob_reset(&fusefs.content);
274
content_get(rid, &fusefs.content);
275
if( offset>blob_size(&fusefs.content) ) return 0;
276
if( offset+size>blob_size(&fusefs.content) ){
277
size = blob_size(&fusefs.content) - offset;
278
}
279
memcpy(buf, blob_buffer(&fusefs.content)+offset, size);
280
return size;
281
}
282
283
static struct fuse_operations fusefs_methods = {
284
.getattr = fusefs_getattr,
285
.readdir = fusefs_readdir,
286
.read = fusefs_read,
287
};
288
#endif /* FOSSIL_HAVE_FUSEFS */
289
290
/*
291
** COMMAND: fusefs*
292
**
293
** Usage: %fossil fusefs [--debug] DIRECTORY
294
**
295
** This command uses the Fuse Filesystem (FuseFS) to mount a directory
296
** at DIRECTORY that contains the content of all check-ins in the
297
** repository. The names of files are DIRECTORY/checkins/VERSION/PATH
298
** where DIRECTORY is the root of the mount, VERSION is any valid
299
** check-in name (examples: "trunk" or "tip" or a tag or any unique
300
** prefix of an artifact hash, etc) and PATH is the pathname of the file in
301
** the check-in. If DIRECTORY does not exist, then an attempt is made
302
** to create it.
303
**
304
** The DIRECTORY/checkins directory is not searchable so one cannot
305
** do "ls DIRECTORY/checkins" to get a listing of all possible check-in
306
** names. There are countless variations on check-in names and it is
307
** impractical to list them all. But all other directories are searchable
308
** and so the "ls" command will work everywhere else in the fusefs
309
** file hierarchy.
310
**
311
** The FuseFS typically only works on Linux, and then only on Linux
312
** systems that have the right kernel drivers and have installed the
313
** appropriate support libraries.
314
**
315
** After stopping the "fossil fusefs" command, it might also be necessary
316
** to run "fusermount -u DIRECTORY" to reset the FuseFS before using it
317
** again.
318
*/
319
void fusefs_cmd(void){
320
#ifdef FOSSIL_HAVE_FUSEFS
321
char *zMountPoint;
322
char *azNewArgv[5];
323
int doDebug = find_option("debug","d",0)!=0;
324
325
db_find_and_open_repository(0,0);
326
verify_all_options();
327
blob_init(&fusefs.content, 0, 0);
328
if( g.argc!=3 ) usage("DIRECTORY");
329
zMountPoint = g.argv[2];
330
if( file_mkdir(zMountPoint, ExtFILE, 0) ){
331
fossil_fatal("cannot make directory [%s]", zMountPoint);
332
}
333
azNewArgv[0] = g.argv[0];
334
azNewArgv[1] = doDebug ? "-d" : "-f";
335
azNewArgv[2] = "-s";
336
azNewArgv[3] = zMountPoint;
337
azNewArgv[4] = 0;
338
g.localOpen = 0; /* Prevent tags like "current" and "prev" */
339
fuse_main(4, azNewArgv, &fusefs_methods, NULL);
340
fusefs_reset();
341
fusefs_clear_path();
342
#else
343
fprintf(stderr, "The FuseFS is not available in this build.\n");
344
exit(1);
345
#endif /* FOSSIL_HAVE_FUSEFS */
346
}
347
348
/*
349
** Return version numbers for the FUSE header that was used at compile-time
350
** and/or the FUSE library that was loaded at runtime.
351
*/
352
const char *fusefs_lib_version(void){
353
#if defined(FOSSIL_HAVE_FUSEFS) && FUSE_MAJOR_VERSION>=3
354
return fuse_pkgversion();
355
#else
356
return "unknown";
357
#endif
358
}
359
360
const char *fusefs_inc_version(void){
361
#ifdef FOSSIL_HAVE_FUSEFS
362
return COMPILER_STRINGIFY(FUSE_MAJOR_VERSION) "."
363
COMPILER_STRINGIFY(FUSE_MINOR_VERSION);
364
#else
365
return "unknown";
366
#endif
367
}
368

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button