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 fused.fuse;
11 
12 /* reexport stat_t */
13 public import core.sys.posix.fcntl;
14 public import core.sys.posix.utime;
15 
16 import std.algorithm;
17 import std.array;
18 import std.conv;
19 import std.stdio;
20 import std.string;
21 import std.process;
22 import errno = core.stdc.errno;
23 import core.stdc.string;
24 import core.sys.posix.signal;
25 
26 import c.fuse.fuse;
27 
28 import core.thread : thread_attachThis, thread_detachThis;
29 import core.sys.posix.pthread;
30 
31 /**
32  * libfuse is handling the thread creation and we cannot hook into it. However
33  * we need to make the GC aware of the threads. So for any call to a handler
34  * we check if the current thread is attached and attach it if necessary.
35  */
36 private int threadAttached = false;
37 private pthread_cleanup cleanup;
38 
39 extern(C) void detach(void* ptr) nothrow
40 {
41     import std.exception;
42     collectException(thread_detachThis());
43 }
44 
45 private void attach()
46 {
47     if (!threadAttached)
48     {
49         thread_attachThis();
50         cleanup.push(&detach, cast(void*) null);
51         threadAttached = true;
52     }
53 }
54 
55 /**
56  * A template to wrap C function calls and support exceptions to indicate
57  * errors.
58  *
59  * The templates passes the Operations object to the lambda as the first
60  * arguemnt.
61  */
62 private auto call(alias fn)()
63 {
64     attach();
65     auto t = cast(Operations*) fuse_get_context().private_data;
66     try
67     {
68         return fn(*t);
69     }
70     catch (FuseException fe)
71     {
72         /* errno is used to indicate an error to libfuse */
73         errno.errno = fe.errno;
74         return -fe.errno;
75     }
76     catch (Exception e)
77     {
78         (*t).exception(e);
79         return -errno.EIO;
80     }
81 }
82 
83 /* C calling convention compatible function to hand into libfuse which wrap
84  * the call to our Operations object.
85  *
86  * Note that we convert our * char pointer to an array using the
87  * ptr[0..len] syntax.
88  */
89 extern(System)
90 {
91     private int dfuse_access(const char* path, int mode)
92     {
93         return call!(
94             (Operations t)
95             {
96                 if(t.access(path[0..path.strlen], mode))
97                 {
98                     return 0;
99                 }
100                 return -1;
101             })();
102     }
103 
104     private int dfuse_getattr(const char*  path, stat_t* st)
105     {
106         return call!(
107             (Operations t)
108             {
109                 t.getattr(path[0..path.strlen], *st);
110                 return 0;
111             })();
112     }
113 
114     private int dfuse_readdir(const char* path, void* buf,
115             fuse_fill_dir_t filler, off_t offset, fuse_file_info* fi)
116     {
117         return call!(
118             (Operations t)
119             {
120                 foreach(file; t.readdir(path[0..path.strlen]))
121                 {
122                     filler(buf, cast(char*) toStringz(file), null, 0);
123                 }
124                 return 0;
125             })();
126     }
127 
128     private int dfuse_readlink(const char* path, char* buf, size_t size)
129     {
130         return call!(
131             (Operations t)
132             {
133                 auto length = t.readlink(path[0..path.strlen],
134                     (cast(ubyte*)buf)[0..size]);
135                 /* Null-terminate the string and copy it over to the buffer. */
136                 assert(length <= size);
137                 buf[length] = '\0';
138 
139                 return 0;
140             })();
141     }
142 
143     private int dfuse_open(const char* path, fuse_file_info* fi)
144     {
145         return call!(
146             (Operations t)
147             {
148                 t.open(path[0..path.strlen]);
149                 return 0;
150             })();
151     }
152 
153     private int dfuse_release(const char* path, fuse_file_info* fi)
154     {
155         return call!(
156             (Operations t)
157             {
158                 t.release(path[0..path.strlen]);
159                 return 0;
160             })();
161     }
162 
163     private int dfuse_read(const char* path, char* buf, size_t size,
164                            off_t offset, fuse_file_info* fi)
165     {
166         /* Ensure at compile time that off_t and size_t fit into an ulong. */
167         static assert(ulong.max >= size_t.max);
168         static assert(ulong.max >= off_t.max);
169 
170         return call!(
171             (Operations t)
172             {
173                 auto bbuf = cast(ubyte*) buf;
174                 return cast(int) t.read(path[0..path.strlen], bbuf[0..size],
175                     to!ulong(offset));
176             })();
177     }
178 
179     private int dfuse_write(const char* path, char* data, size_t size,
180                             off_t offset, fuse_file_info* fi)
181     {
182         static assert(ulong.max >= size_t.max);
183         static assert(ulong.max >= off_t.max);
184 
185         return call!(
186             (Operations t)
187             {
188                 auto bdata = cast(ubyte*) data;
189                 return t.write(path[0..path.strlen], bdata[0..size],
190                     to!ulong(offset));
191             })();
192     }
193 
194     private int dfuse_truncate(const char* path, off_t length)
195     {
196         static assert(ulong.max >= off_t.max);
197         return call!(
198             (Operations t)
199             {
200                 t.truncate(path[0..path.strlen], to!ulong(length));
201                 return 0;
202             })();
203     }
204 
205     private int dfuse_mknod(const char* path, mode_t mod, dev_t dev)
206     {
207         static assert(ulong.max >= dev_t.max);
208         static assert(uint.max >= mode_t.max);
209         return call!(
210             (Operations t)
211             {
212                 t.mknod(path[0..path.strlen], mod, dev);
213                 return 0;
214             })();
215     }
216 
217     private int dfuse_unlink(const char* path)
218     {
219         return call!(
220             (Operations t)
221             {
222                 t.unlink(path[0..path.strlen]);
223                 return 0;
224             })();
225     }
226 
227     private int dfuse_mkdir(const char * path, mode_t mode)
228     {
229         static assert(uint.max >= mode_t.max);
230         return call!(
231             (Operations t)
232             {
233                 t.mkdir(path[0..path.strlen], mode.to!uint);
234                 return 0;
235             })();
236     }
237     private int dfuse_rmdir(const char * path)
238     {
239         return call!(
240             (Operations t)
241             {
242                 t.rmdir(path[0..path.strlen]);
243                 return 0;
244             })();
245     }
246 
247     private int dfuse_rename(const char* orig, const char* dest) {
248         return call!(
249             (Operations t)
250             {
251                 t.rename(orig[0..orig.strlen], dest[0..dest.strlen]);
252                 return 0;
253             })();
254     }
255 
256     private int dfuse_chmod(const char* path, mode_t mode) {
257         return call!(
258             (Operations t)
259             {
260                 t.chmod(path[0 .. path.strlen], mode);
261                 return 0;
262             }
263         )();
264     }
265 
266     private int dfuse_utime(const char* path, utimbuf* time) {
267         return call!(
268             (Operations t)
269             {
270                 t.utime(path[0 .. path.strlen], time);
271                 return 0;
272             }
273         );
274     }
275 
276     private int dfuse_symlink(const char* target, char* link) {
277         return call!(
278             (Operations t)
279             {
280                 t.symlink(target[0 .. target.strlen], link[0 .. link.strlen]);
281                 return 0;
282             }
283         );
284     }
285 
286     private int dfuse_chown(const char* path, uid_t uid, gid_t gid) {
287         return call!(
288             (Operations t)
289             {
290                 t.chown(path[0 .. path.strlen], uid, gid);
291                 return 0;
292             }
293         );
294     }
295 
296     private void* dfuse_init(fuse_conn_info* conn)
297     {
298         attach();
299         auto t = cast(Operations*) fuse_get_context().private_data;
300         (*t).initialize();
301         return t;
302     }
303 
304     private void dfuse_destroy(void* data)
305     {
306         /* this is an ugly hack at the moment. We need to somehow detach all
307            threads from the runtime because after fuse_main finishes the pthreads
308            are joined. We circumvent that problem by just exiting while our
309            threads still run. */
310         import core.stdc.stdlib : exit;
311         exit(0);
312     }
313 } /* extern(C) */
314 
315 export class FuseException : Exception
316 {
317     public int errno;
318     this(int errno, string file = __FILE__, size_t line = __LINE__,
319          Throwable next = null)
320     {
321         super("Fuse Exception", file, line, next);
322         this.errno = errno;
323     }
324 }
325 
326 /**
327  * An object oriented wrapper around fuse_operations.
328  */
329 export class Operations
330 {
331     /**
332      * Runs on filesystem creation
333      */
334     void initialize()
335     {
336     }
337 
338     /**
339      * Called to get a stat(2) structure for a path.
340      */
341     void getattr(const(char)[] path, ref stat_t stat)
342     {
343         throw new FuseException(errno.EOPNOTSUPP);
344     }
345 
346     /**
347      * Read path into the provided buffer beginning at offset.
348      *
349      * Params:
350      *   path   = The path to the file to read.
351      *   buf    = The buffer to read the data into.
352      *   offset = An offset to start reading at.
353      * Returns: The amount of bytes read.
354      */
355     ulong read(const(char)[] path, ubyte[] buf, ulong offset)
356     {
357         throw new FuseException(errno.EOPNOTSUPP);
358     }
359 
360     /**
361      * Write the given data to the file.
362      *
363      * Params:
364      *   path   = The path to the file to write.
365      *   buf    = A read-only buffer containing the data to write.
366      *   offset = An offset to start writing at.
367      * Returns: The amount of bytes written.
368      */
369     int write(const(char)[] path, in ubyte[] data, ulong offset)
370     {
371         throw new FuseException(errno.EOPNOTSUPP);
372     }
373 
374     /**
375      * Truncate a file to the given length.
376      * Params:
377      *   path   = The path to the file to trunate.
378      *   length = Truncate file to this given length.
379      */
380     void truncate(const(char)[] path, ulong length)
381     {
382         throw new FuseException(errno.EOPNOTSUPP);
383     }
384 
385     /**
386      * Returns a list of files and directory names in the given folder. Note
387      * that you have to return . and ..
388      *
389      * Params:
390      *   path = The path to the directory.
391      * Returns: An array of filenames.
392      */
393     string[] readdir(const(char)[] path)
394     {
395         throw new FuseException(errno.EOPNOTSUPP);
396     }
397 
398     /**
399      * Reads the link identified by path into the given buffer.
400      *
401      * Params:
402      *   path = The path to the directory.
403      */
404     size_t readlink(const(char)[] path, ubyte[] buf)
405     {
406         throw new FuseException(errno.EOPNOTSUPP);
407     }
408 
409     /**
410      * Determine if the user has access to the given path.
411      *
412      * Params:
413      *   path = The path to check.
414      *   mode = An flag indicating what to check for. See access(2) for
415      *          supported modes.
416      * Returns: True on success otherwise false.
417      */
418     bool access(const(char)[] path, int mode)
419     {
420         throw new FuseException(errno.EOPNOTSUPP);
421     }
422 
423     /**
424      * Changes the mode of a path.
425      *
426      * Params:
427      *   path = The path to check.
428      *   mode = The mode to set.
429      */
430     void chmod(const(char)[] path, mode_t mode)
431     {
432         throw new FuseException(errno.EOPNOTSUPP);
433     }
434 
435     /**
436      * Sets access and modification time.
437      *
438      * Params:
439      *   path = The patch to modify.
440      *   time = The time to set.
441      */
442     void utime(const(char)[] path, utimbuf* time)
443     {
444         throw new FuseException(errno.EOPNOTSUPP);
445     }
446 
447     /**
448      * Creates a symlink.
449      *
450      * Params:
451      *   path = The path to create.
452      *   target = The target of the link.
453      */
454     void symlink(const(char)[] target, const(char)[] path)
455     {
456         throw new FuseException(errno.EOPNOTSUPP);
457     }
458 
459     /**
460      * Changes ownership of a file.
461      *
462      * Params:
463      *   path = Path to the file.
464      *   uid = New user ID.
465      *   gid = New group ID.
466      */
467     void chown(const(char)[] path, uid_t uid, gid_t gid)
468     {
469         throw new FuseException(errno.EOPNOTSUPP);
470     }
471 
472     void mknod(const(char)[] path, int mod, ulong dev)
473     {
474         throw new FuseException(errno.EOPNOTSUPP);
475     }
476 
477     void unlink(const(char)[] path)
478     {
479         throw new FuseException(errno.EOPNOTSUPP);
480     }
481 
482     void mkdir(const(char)[] path, uint mode)
483     {
484         throw new FuseException(errno.EOPNOTSUPP);
485     }
486 
487     void rmdir(const(char)[] path)
488     {
489         throw new FuseException(errno.EOPNOTSUPP);
490     }
491 
492     void rename(const(char)[] orig, const(char)[] dest)
493     {
494         throw new FuseException(errno.EOPNOTSUPP);
495     }
496 
497     void open(const(char)[] path)
498     {
499         throw new FuseException(errno.EOPNOTSUPP);
500     }
501 
502     void release(const(char)[] path)
503     {
504         throw new FuseException(errno.EOPNOTSUPP);
505     }
506 
507     void exception(Exception e)
508     {
509     }
510 }
511 
512 /**
513  * A wrapper around fuse_main()
514  */
515 export class Fuse
516 {
517 private:
518     bool foreground;
519     bool threaded;
520     string fsname;
521     int pid;
522 
523 public:
524     this(string fsname)
525     {
526         this(fsname, false, true);
527     }
528 
529     this(string fsname, bool foreground, bool threaded)
530     {
531         this.fsname = fsname;
532         this.foreground = foreground;
533         this.threaded = threaded;
534     }
535 
536     void mount(Operations ops, const string mountpoint, string[] mountopts)
537     {
538         string [] args = [this.fsname];
539 
540         args ~= mountpoint;
541 
542         if(mountopts.length > 0)
543         {
544             args ~= format("-o%s", mountopts.join(","));
545         }
546 
547         if(this.foreground)
548         {
549             args ~= "-f";
550         }
551 
552         if(!this.threaded)
553         {
554             args ~= "-s";
555         }
556 
557         debug writefln("fuse arguments s=(%s)", args);
558 
559         fuse_operations fops;
560 
561         fops.init = &dfuse_init;
562         fops.access = &dfuse_access;
563         fops.getattr = &dfuse_getattr;
564         fops.readdir = &dfuse_readdir;
565         fops.open = &dfuse_open;
566         fops.release = &dfuse_release;
567         fops.read = &dfuse_read;
568         fops.write = &dfuse_write;
569         fops.truncate = &dfuse_truncate;
570         fops.readlink = &dfuse_readlink;
571         fops.destroy = &dfuse_destroy;
572         fops.mknod = &dfuse_mknod;
573         fops.unlink = &dfuse_unlink;
574         fops.mkdir = &dfuse_mkdir;
575         fops.rmdir = &dfuse_rmdir;
576         fops.rename = &dfuse_rename;
577         fops.chmod = &dfuse_chmod;
578         fops.utime = &dfuse_utime;
579         fops.symlink = &dfuse_symlink;
580         fops.chown = &dfuse_chown;
581 
582         /* Create c-style arguments from a string[] array. */
583         auto cargs = array(map!(a => toStringz(a))(args));
584         int length = cast(int) cargs.length;
585         static if(length.max < cargs.length.max)
586         {
587             /* This is an unsafe cast that we need to do for C compat.
588                Enforce unlike assert will be checked in opt-builds as well. */
589             import std.exception : enforce;
590             enforce(length >= 0);
591             enforce(length == cargs.length);
592         }
593 
594         this.pid = thisProcessID();
595         fuse_main(length, cast(char**) cargs.ptr, &fops, &ops);
596     }
597 
598     void exit()
599     {
600         kill(this.pid, SIGINT);
601     }
602 }