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()); }