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 void* dfuse_init(fuse_conn_info* conn)
183     {
184         attach();
185         auto t = cast(Operations*) fuse_get_context().private_data;
186         (*t).initialize();
187         return t;
188     }
189 
190     private void dfuse_destroy(void* data)
191     {
192         /* this is an ugly hack at the moment. We need to somehow detach all
193            threads from the runtime because after fuse_main finishes the pthreads
194            are joined. We circumvent that problem by just exiting while our
195            threads still run. */
196         import std.c.process;
197         exit(0);
198     }
199 } /* extern(C) */
200 
201 export class FuseException : Exception
202 {
203     public int errno;
204     this(int errno, string file = __FILE__, size_t line = __LINE__,
205          Throwable next = null)
206     {
207         super("Fuse Exception", file, line, next);
208         this.errno = errno;
209     }
210 }
211 
212 /**
213  * An object oriented wrapper around fuse_operations.
214  */
215 export class Operations
216 {
217     /**
218      * Runs on filesystem creation
219      */
220     void initialize()
221     {
222     }
223 
224     /**
225      * Called to get a stat(2) structure for a path.
226      */
227     void getattr(const(char)[] path, ref stat_t stat)
228     {
229         throw new FuseException(errno.EOPNOTSUPP);
230     }
231 
232     /**
233      * Read path into the provided buffer beginning at offset.
234      *
235      * Params:
236      *   path   = The path to the file to read.
237      *   buf    = The buffer to read the data into.
238      *   offset = An offset to start reading at.
239      * Returns: The amount of bytes read.
240      */
241     ulong read(const(char)[] path, ubyte[] buf, ulong offset)
242     {
243         throw new FuseException(errno.EOPNOTSUPP);
244     }
245 
246     /**
247      * Write the given data to the file.
248      *
249      * Params:
250      *   path   = The path to the file to write.
251      *   buf    = A read-only buffer containing the data to write.
252      *   offset = An offset to start writing at.
253      * Returns: The amount of bytes written.
254      */
255     int write(const(char)[] path, in ubyte[] data, ulong offset)
256     {
257         throw new FuseException(errno.EOPNOTSUPP);
258     }
259 
260     /**
261      * Truncate a file to the given length.
262      * Params:
263      *   path   = The path to the file to trunate.
264      *   length = Truncate file to this given length.
265      */
266     void truncate(const(char)[] path, ulong length)
267     {
268         throw new FuseException(errno.EOPNOTSUPP);
269     }
270 
271     /**
272      * Returns a list of files and directory names in the given folder. Note
273      * that you have to return . and ..
274      *
275      * Params:
276      *   path = The path to the directory.
277      * Returns: An array of filenames.
278      */
279     string[] readdir(const(char)[] path)
280     {
281         throw new FuseException(errno.EOPNOTSUPP);
282     }
283 
284     /**
285      * Reads the link identified by path into the given buffer.
286      *
287      * Params:
288      *   path = The path to the directory.
289      */
290     ulong readlink(const(char)[] path, ubyte[] buf)
291     {
292         throw new FuseException(errno.EOPNOTSUPP);
293     }
294 
295     /**
296      * Determine if the user has access to the given path.
297      *
298      * Params:
299      *   path = The path to check.
300      *   mode = An flag indicating what to check for. See access(2) for
301      *          supported modes.
302      * Returns: True on success otherwise false.
303      */
304     bool access(const(char)[] path, int mode)
305     {
306         throw new FuseException(errno.EOPNOTSUPP);
307     }
308 
309     void exception(Exception e)
310     {
311     }
312 }
313 
314 /**
315  * A wrapper around fuse_main()
316  */
317 export class Fuse
318 {
319 private:
320     bool foreground;
321     bool threaded;
322     string fsname;
323 
324 public:
325     this(string fsname)
326     {
327         this(fsname, false, true);
328     }
329 
330     this(string fsname, bool foreground, bool threaded)
331     {
332         this.fsname = fsname;
333         this.foreground = foreground;
334         this.threaded = threaded;
335     }
336 
337     void mount(Operations ops, const string mountpoint, string[] mountopts)
338     {
339         string [] args = [this.fsname];
340 
341         args ~= mountpoint;
342 
343         if(mountopts.length > 0)
344         {
345             args ~= format("-o%s", mountopts.join(","));
346         }
347 
348         if(this.foreground)
349         {
350             args ~= "-f";
351         }
352 
353         if(!this.threaded)
354         {
355             args ~= "-s";
356         }
357 
358         debug writefln("fuse arguments s=(%s)", args);
359 
360         fuse_operations fops;
361 
362         fops.init = &dfuse_init;
363         fops.access = &dfuse_access;
364         fops.getattr = &dfuse_getattr;
365         fops.readdir = &dfuse_readdir;
366         fops.read = &dfuse_read;
367         fops.write = &dfuse_write;
368         fops.truncate = &dfuse_truncate;
369         fops.readlink = &dfuse_readlink;
370         fops.destroy = &dfuse_destroy;
371 
372         /* Create c-style arguments from a string[] array. */
373         auto cargs = array(map!(a => toStringz(a))(args));
374         int length = cast(int) cargs.length;
375         static if(length.max < cargs.length.max)
376         {
377             /* This is an unsafe cast that we need to do for C compat.
378                Enforce unlike assert will be checked in opt-builds as well. */
379             import std.exception : enforce;
380             enforce(length >= 0);
381             enforce(length == cargs.length);
382         }
383 
384         fuse_main(length, cast(char**) cargs.ptr, &fops, &ops);
385     }
386 }