1 /**
2     A tiny library to work with Linux's kernel inotify subsystem.
3 
4 */
5 module dinotify;
6 
7 private:
8 
9 ///
10 unittest
11 {
12     import std.file, std.stdio : writeln;
13     auto monitor = iNotify();
14     monitor.add(tempDir, IN_CREATE | IN_DELETE);
15     ubyte[] data = [1, 2, 3, 4];
16     write(tempDir ~ "/killme", data);
17     auto events = monitor.read();
18     assert(events[0].mask & IN_CREATE);
19     assert(events[0].name == "killme");
20     remove(tempDir ~ "/killme");
21     events = monitor.read();
22     assert(events[0].mask & IN_DELETE);
23     // Note: doesn't track nested directories
24     mkdir(tempDir ~ "/some-dir");
25     write(tempDir ~ "/some-dir/victim", data);
26     events = monitor.read();
27     assert(events.length == 1);
28     assert(events[0].mask & IN_CREATE);
29     assert(events[0].name == "some-dir");
30     remove(tempDir ~ "/some-dir/victim");
31     rmdir(tempDir ~ "/some-dir/");
32 
33 }
34 
35 import core.sys.posix.unistd;
36 import core.sys.linux.sys.inotify;
37 import std.exception;
38 
39 // core.sys.linux is lacking, so just list proper prototypes on our own
40 extern(C){
41     size_t strnlen(const(char)* s, size_t maxlen);
42     enum NAME_MAX = 255;
43 }
44 
45 auto size(ref inotify_event e) { return e.sizeof + e.len; }
46 
47 // Get name out of event structure
48 const(char)[] name(ref inotify_event e)
49 {
50     auto ptr = cast(const(char)*)(&e+1); 
51     auto len = strnlen(ptr, e.len);
52     return ptr[0..len];
53 }
54 
55 auto maxEvent(){ return inotify_event.sizeof + NAME_MAX + 1; }
56 
57 /// Type-safe watch descriptor to help discern it from normal file descriptors
58 public struct Watch{
59     private int wd;   
60 }
61 
62 /// D-ified intofiy event, holds slice to temporary buffer with z-string.
63 public struct Event{
64     uint mask, cookie;
65     const(char)[] name;
66 }
67 
68 public struct INotify{
69     private int fd = -1; // inotify fd
70     private ubyte[] buffer;
71     private Event[] events;
72     
73     private this(int fd){
74         enforce(fd >= 0, "failed to init inotify");
75         this.fd = fd;
76         buffer = new ubyte[20*maxEvent];
77     }
78 
79     @disable this(this);
80 
81     /// Add path to watch set of this INotify instance
82     Watch add(const (char)* path, uint mask){
83         auto w = Watch(inotify_add_watch(fd, path, mask));
84         enforce(w.wd >= 0, "failed to add inotify watch");
85         return w;
86     }
87 
88     /// ditto
89     Watch add(const (char)[] path, uint mask){
90         auto zpath = path ~ '\0';
91         return add(zpath.ptr, mask);
92     }
93 
94     /// Remove watch descriptor from this this INotify instance
95     void remove(Watch w){
96         enforce(inotify_rm_watch(fd, w.wd) == 0,
97             "failed to remove inotify watch");
98     }
99 
100     /**
101         Issue a blocking read to get a bunch of events,
102         there is at least one event in the returned slice.
103 
104         Note that returned slice is mutable.
105         This indicates that it is invalidated on 
106         the next call to read, just like byLine in std.stdio.
107     */
108     Event[] read()
109     {
110         long len = .read(fd, buffer.ptr, buffer.length);
111         enforce(len > 0, "failed to read inotify event");
112         ubyte* head = buffer.ptr;
113         events.length = 0;
114         events.assumeSafeAppend();        
115         while(len > 0){
116             auto eptr = cast(inotify_event*)head;
117             auto sz = size(*eptr);
118             head += sz;
119             len -= sz;
120             events ~= Event(eptr.mask, eptr.cookie, name(*eptr));
121         }
122         return events;
123     }
124     
125     ~this(){
126         if(fd >= 0)
127             close(fd);
128     }
129 }
130 
131 ///
132 public auto iNotify(){ return INotify(inotify_init()); }