1 /*
2  *  Copyright (c) 2014, Facebook, Inc.
3  *  All rights reserved.
4  *
5  *  This source code is licensed under the Boost-style license found in the
6  *  LICENSE file in the root directory of this source tree. An additional grant
7  *  of patent rights can be found in the PATENTS file in the same directory.
8  *
9  */
10 module dfuse.fuse;
11 
12 /* reexport stat_t */
13 public import core.sys.posix.fcntl;
14 
15 import std.algorithm;
16 import std.array;
17 import std.conv;
18 import std.stdio;
19 import std.string;
20 import errno = core.stdc.errno;
21 import core.stdc.string;
22 
23 import c.fuse.fuse;
24 
25 import core.thread : thread_attachThis, thread_detachThis;
26 import core.sys.posix.pthread;
27 
28 /**
29  * libfuse is handling the thread creation and we cannot hook into it. However
30  * we need to make the GC aware of the threads. So for any call to a handler
31  * we check if the current thread is attached and attach it if necessary.
32  */
33 private int threadAttached = false;
34 private pthread_cleanup cleanup;
35 
36 extern(C) void detach(void* ptr) nothrow
37 {
38     import std.exception;
39     collectException(thread_detachThis());
40 }
41 
42 private void attach()
43 {
44     if (!threadAttached)
45     {
46         thread_attachThis();
47         cleanup.push(&detach, cast(void*) null);
48         threadAttached = true;
49     }
50 }
51 
52 /**
53  * A template to wrap C function calls and support exceptions to indicate
54  * errors.
55  *
56  * The templates passes the Operations object to the lambda as the first
57  * arguemnt.
58  */
59 private auto call(alias fn)()
60 {
61     attach();
62     auto t = cast(Operations*) fuse_get_context().private_data;
63     try
64     {
65         return fn(*t);
66     }
67     catch (FuseException fe)
68     {
69         /* errno is used to indicate an error to libfuse */
70         errno.errno = fe.errno;
71         return -fe.errno;
72     }
73     catch (Exception e)
74     {
75         (*t).exception(e);
76         return -errno.EIO;
77     }
78 }
79 
80 /* C calling convention compatible function to hand into libfuse which wrap
81  * the call to our Operations object.
82  *
83  * Note that we convert our * char pointer to an array using the
84  * ptr[0..len] syntax.
85  */
86 extern(System)
87 {
88     private int dfuse_access(const char* path, int mode)
89     {
90         return call!(
91             (Operations t)
92             {
93                 if(t.access(path[0..path.strlen], mode))
94                 {
95                     return 0;
96                 }
97                 return -1;
98             })();
99     }
100 
101     private int dfuse_getattr(const char*  path, stat_t* st)
102     {
103         return call!(
104             (Operations t)
105             {
106                 t.getattr(path[0..path.strlen], *st);
107                 return 0;
108             })();
109     }
110 
111     private int dfuse_readdir(const char* path, void* buf,
112             fuse_fill_dir_t filler, off_t offset, fuse_file_info* fi)
113     {
114         return call!(
115             (Operations t)
116             {
117                 foreach(file; t.readdir(path[0..path.strlen]))
118                 {
119                     filler(buf, cast(char*) toStringz(file), null, 0);
120                 }
121                 return 0;
122             })();
123     }
124 
125     private int dfuse_readlink(const char* path, char* buf, size_t size)
126     {
127         return call!(
128             (Operations t)
129             {
130                 auto length = t.readlink(path[0..path.strlen],
131                     (cast(ubyte*)buf)[0..size]);
132                 /* Null-terminate the string and copy it over to the buffer. */
133                 assert(length <= size);
134                 buf[length] = '\0';
135 
136                 return 0;
137             })();
138     }
139 
140     private int dfuse_read(const char* path, char* buf, size_t size,
141                            off_t offset, fuse_file_info* fi)
142     {
143         /* Ensure at compile time that off_t and size_t fit into an ulong. */
144         static assert(ulong.max >= size_t.max);
145         static assert(ulong.max >= off_t.max);
146 
147         return call!(
148             (Operations t)
149             {
150                 auto bbuf = cast(ubyte*) buf;
151                 return cast(int) t.read(path[0..path.strlen], bbuf[0..size],
152                     to!ulong(offset));
153             })();
154     }
155 
156     private int dfuse_write(const char* path, char* data, size_t size,
157                             off_t offset, fuse_file_info* fi)
158     {
159         static assert(ulong.max >= size_t.max);
160         static assert(ulong.max >= off_t.max);
161 
162         return call!(
163             (Operations t)
164             {
165                 auto bdata = cast(ubyte*) data;
166                 return t.write(path[0..path.strlen], bdata[0..size],
167                     to!ulong(offset));
168             })();
169     }
170 
171     private int dfuse_truncate(const char* path, off_t length)
172     {
173         static assert(ulong.max >= off_t.max);
174         return call!(
175             (Operations t)
176             {
177                 t.truncate(path[0..path.strlen], to!ulong(length));
178                 return 0;
179             })();
180     }
181 
182     private int dfuse_mknod(const char* path, mode_t mod, dev_t dev)
183     {
184         static assert(ulong.max >= dev_t.max);
185         static assert(uint.max >= mode_t.max);
186         return call!(
187             (Operations t)
188             {
189                 t.mknod(path[0..path.strlen], mod, dev);
190                 return 0;
191             })();
192     }
193 
194     private int dfuse_unlink(const char* path)
195     {
196         return call!(
197             (Operations t)
198             {
199                 t.unlink(path[0..path.strlen]);
200                 return 0;
201             })();
202     }
203 
204     private int dfuse_mkdir(const char * path, mode_t mode)
205     {
206         static assert(uint.max >= mode_t.max);
207         return call!(
208             (Operations t)
209             {
210                 t.mkdir(path[0..path.strlen], mode.to!uint);
211                 return 0;
212             })();
213     }
214     private int dfuse_rmdir(const char * path)
215     {
216         return call!(
217             (Operations t)
218             {
219                 t.rmdir(path[0..path.strlen]);
220                 return 0;
221             })();
222     }
223 
224     private int dfuse_rename(const char* orig, const char* dest) {
225         return call!(
226             (Operations t)
227             {
228                 t.rename(orig[0..orig.strlen], dest[0..dest.strlen]);
229                 return 0;
230             })();
231     }
232 
233     private void* dfuse_init(fuse_conn_info* conn)
234     {
235         attach();
236         auto t = cast(Operations*) fuse_get_context().private_data;
237         (*t).initialize();
238         return t;
239     }
240 
241     private void dfuse_destroy(void* data)
242     {
243         /* this is an ugly hack at the moment. We need to somehow detach all
244            threads from the runtime because after fuse_main finishes the pthreads
245            are joined. We circumvent that problem by just exiting while our
246            threads still run. */
247         import core.stdc.stdlib;
248         exit(0);
249     }
250 } /* extern(C) */
251 
252 export class FuseException : Exception
253 {
254     public int errno;
255     this(int errno, string file = __FILE__, size_t line = __LINE__,
256          Throwable next = null)
257     {
258         super("Fuse Exception", file, line, next);
259         this.errno = errno;
260     }
261 }
262 
263 /**
264  * An object oriented wrapper around fuse_operations.
265  */
266 export class Operations
267 {
268     /**
269      * Runs on filesystem creation
270      */
271     void initialize()
272     {
273     }
274 
275     /**
276      * Called to get a stat(2) structure for a path.
277      */
278     void getattr(const(char)[] path, ref stat_t stat)
279     {
280         throw new FuseException(errno.EOPNOTSUPP);
281     }
282 
283     /**
284      * Read path into the provided buffer beginning at offset.
285      *
286      * Params:
287      *   path   = The path to the file to read.
288      *   buf    = The buffer to read the data into.
289      *   offset = An offset to start reading at.
290      * Returns: The amount of bytes read.
291      */
292     ulong read(const(char)[] path, ubyte[] buf, ulong offset)
293     {
294         throw new FuseException(errno.EOPNOTSUPP);
295     }
296 
297     /**
298      * Write the given data to the file.
299      *
300      * Params:
301      *   path   = The path to the file to write.
302      *   buf    = A read-only buffer containing the data to write.
303      *   offset = An offset to start writing at.
304      * Returns: The amount of bytes written.
305      */
306     int write(const(char)[] path, in ubyte[] data, ulong offset)
307     {
308         throw new FuseException(errno.EOPNOTSUPP);
309     }
310 
311     /**
312      * Truncate a file to the given length.
313      * Params:
314      *   path   = The path to the file to trunate.
315      *   length = Truncate file to this given length.
316      */
317     void truncate(const(char)[] path, ulong length)
318     {
319         throw new FuseException(errno.EOPNOTSUPP);
320     }
321 
322     /**
323      * Returns a list of files and directory names in the given folder. Note
324      * that you have to return . and ..
325      *
326      * Params:
327      *   path = The path to the directory.
328      * Returns: An array of filenames.
329      */
330     string[] readdir(const(char)[] path)
331     {
332         throw new FuseException(errno.EOPNOTSUPP);
333     }
334 
335     /**
336      * Reads the link identified by path into the given buffer.
337      *
338      * Params:
339      *   path = The path to the directory.
340      */
341     size_t readlink(const(char)[] path, ubyte[] buf)
342     {
343         throw new FuseException(errno.EOPNOTSUPP);
344     }
345 
346     /**
347      * Determine if the user has access to the given path.
348      *
349      * Params:
350      *   path = The path to check.
351      *   mode = An flag indicating what to check for. See access(2) for
352      *          supported modes.
353      * Returns: True on success otherwise false.
354      */
355     bool access(const(char)[] path, int mode)
356     {
357         throw new FuseException(errno.EOPNOTSUPP);
358     }
359 
360     void mknod(const(char)[] path, int mod, ulong dev)
361     {
362         throw new FuseException(errno.EOPNOTSUPP);
363     }
364 
365     void unlink(const(char)[] path)
366     {
367         throw new FuseException(errno.EOPNOTSUPP);
368     }
369 
370     void mkdir(const(char)[] path, uint mode)
371     {
372         throw new FuseException(errno.EOPNOTSUPP);
373     }
374 
375     void rmdir(const(char)[] path)
376     {
377         throw new FuseException(errno.EOPNOTSUPP);
378     }
379 
380     void rename(const(char)[] orig, const(char)[] dest)
381     {
382         throw new FuseException(errno.EOPNOTSUPP);
383     }
384 
385     void exception(Exception e)
386     {
387     }
388 }
389 
390 /**
391  * A wrapper around fuse_main()
392  */
393 export class Fuse
394 {
395 private:
396     bool foreground;
397     bool threaded;
398     string fsname;
399 
400 public:
401     this(string fsname)
402     {
403         this(fsname, false, true);
404     }
405 
406     this(string fsname, bool foreground, bool threaded)
407     {
408         this.fsname = fsname;
409         this.foreground = foreground;
410         this.threaded = threaded;
411     }
412 
413     void mount(Operations ops, const string mountpoint, string[] mountopts)
414     {
415         string [] args = [this.fsname];
416 
417         args ~= mountpoint;
418 
419         if(mountopts.length > 0)
420         {
421             args ~= format("-o%s", mountopts.join(","));
422         }
423 
424         if(this.foreground)
425         {
426             args ~= "-f";
427         }
428 
429         if(!this.threaded)
430         {
431             args ~= "-s";
432         }
433 
434         debug writefln("fuse arguments s=(%s)", args);
435 
436         fuse_operations fops;
437 
438         fops.init = &dfuse_init;
439         fops.access = &dfuse_access;
440         fops.getattr = &dfuse_getattr;
441         fops.readdir = &dfuse_readdir;
442         fops.read = &dfuse_read;
443         fops.write = &dfuse_write;
444         fops.truncate = &dfuse_truncate;
445         fops.readlink = &dfuse_readlink;
446         fops.destroy = &dfuse_destroy;
447         fops.mknod = &dfuse_mknod;
448         fops.unlink = &dfuse_unlink;
449         fops.mkdir = &dfuse_mkdir;
450         fops.rmdir = &dfuse_rmdir;
451         fops.rename = &dfuse_rename;
452 
453         /* Create c-style arguments from a string[] array. */
454         auto cargs = array(map!(a => toStringz(a))(args));
455         int length = cast(int) cargs.length;
456         static if(length.max < cargs.length.max)
457         {
458             /* This is an unsafe cast that we need to do for C compat.
459                Enforce unlike assert will be checked in opt-builds as well. */
460             import std.exception : enforce;
461             enforce(length >= 0);
462             enforce(length == cargs.length);
463         }
464 
465         fuse_main(length, cast(char**) cargs.ptr, &fops, &ops);
466     }
467 }