|
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
|
|