1 /// Reactor friendly interface for Linux's inotify 2 module mecca.reactor.io.inotify; 3 4 // Licensed under the Boost license. Full copyright information in the AUTHORS file 5 6 version (linux): 7 8 import core.sys.linux.sys.inotify; 9 10 import mecca.lib.exception; 11 import mecca.lib..string; 12 import mecca.lib.typedid; 13 import mecca.log; 14 import mecca.reactor.io.fd; 15 import mecca.reactor; 16 17 private extern(C) nothrow @nogc { 18 int inotify_init1(int flags) @safe; 19 //int inotify_add_watch(int fd, const(char)* name, uint mask); 20 //int inotify_rm_watch(int fd, uint wd) @safe; 21 22 enum NAME_MAX = 255; 23 } 24 25 /// Watch point handle type 26 alias WatchDescriptor = TypedIdentifier!("WatchDescriptor", int, -1, -1); 27 28 /** 29 * iNotify type. 30 * 31 * Create as many instances of this as you need. Each one has its own inotify file descriptor, and manages its own events 32 */ 33 struct Inotifier { 34 /** 35 * The type of reported events 36 * 37 * Full documentation is available in the <a href="https://linux.die.net/man/7/inotify">inotify man page</a>, except for the name 38 * field, which returns a D string. 39 */ 40 struct Event { 41 @disable this(this); 42 43 inotify_event event; 44 @property WatchDescriptor wd() const pure nothrow @safe @nogc { 45 return WatchDescriptor( event.wd ); 46 } 47 48 @property string name() const nothrow @trusted @nogc { 49 if( event.len==0 ) 50 return null; 51 52 import std..string : indexOf; 53 54 const char* basePtr = event.name.ptr; 55 string ret = cast(immutable(char)[])basePtr[0 .. event.len]; 56 auto nullIdx = indexOf( ret, '\0' ); 57 ASSERT!"Name returned from inotify not null terminated"( nullIdx>=0 ); 58 return ret[0..nullIdx]; 59 } 60 61 alias event this; 62 } 63 64 enum IN_ACCESS = .IN_ACCESS; /// See definition in the <a href="https://linux.die.net/man/7/inotify">inotify man page</a>. 65 enum IN_MODIFY = .IN_MODIFY; /// ditto 66 enum IN_ATTRIB = .IN_ATTRIB; /// ditto 67 enum IN_CLOSE_WRITE = .IN_CLOSE_WRITE; /// ditto 68 enum IN_CLOSE_NOWRITE = .IN_CLOSE_NOWRITE; /// ditto 69 enum IN_OPEN = .IN_OPEN; /// ditto 70 enum IN_MOVED_FROM = .IN_MOVED_FROM; /// ditto 71 enum IN_MOVED_TO = .IN_MOVED_TO; /// ditto 72 enum IN_CREATE = .IN_CREATE; /// ditto 73 enum IN_DELETE = .IN_DELETE; /// ditto 74 enum IN_DELETE_SELF = .IN_DELETE_SELF; /// ditto 75 enum IN_MOVE_SELF = .IN_MOVE_SELF; /// ditto 76 //enum IN_UNMOUNT = .IN_UNMOUNT; /// ditto 77 enum IN_Q_OVERFLOW = .IN_Q_OVERFLOW; /// ditto 78 enum IN_IGNORED = .IN_IGNORED; /// ditto 79 enum IN_CLOSE = .IN_CLOSE; /// ditto 80 enum IN_MOVE = .IN_MOVE; /// ditto 81 enum IN_ONLYDIR = .IN_ONLYDIR; /// ditto 82 enum IN_DONT_FOLLOW = .IN_DONT_FOLLOW; /// ditto 83 enum IN_EXCL_UNLINK = .IN_EXCL_UNLINK; /// ditto 84 enum IN_MASK_ADD = .IN_MASK_ADD; /// ditto 85 enum IN_ISDIR = .IN_ISDIR; /// ditto 86 enum IN_ONESHOT = .IN_ONESHOT; /// ditto 87 enum IN_ALL_EVENTS = .IN_ALL_EVENTS; /// ditto 88 private: 89 ReactorFD fd; 90 91 enum EVENTS_BUFFER_SIZE = 512; 92 93 // Cache for already extracted events 94 static assert( EVENTS_BUFFER_SIZE >= inotify_event.sizeof + NAME_MAX + 1, "events buffer not big enough for one event" ); 95 void[EVENTS_BUFFER_SIZE] eventsBuffer; 96 uint bufferSize; 97 uint bufferConsumed; 98 99 public: 100 /// call before using the inotifyer 101 void open() @safe @nogc { 102 ASSERT!"Inotifier.open called twice"( !fd.isValid ); 103 int inotifyFd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); 104 errnoEnforceNGC( inotifyFd>=0, "inotify_init failed" ); 105 fd = ReactorFD( inotifyFd, true ); 106 } 107 108 ~this() nothrow @safe @nogc { 109 close(); 110 } 111 112 /// call when you're done with the inotifyer 113 void close() nothrow @safe @nogc { 114 fd.close(); 115 } 116 117 /// Report whether Inotifier is open 118 bool isOpen() const nothrow @safe @nogc { 119 return fd.isValid; 120 } 121 122 /** 123 * add or change a watch point 124 * 125 * Params: 126 * path = path to be watched 127 * mask = the mask of events to be watched 128 * 129 * Returns: 130 * the WatchDescriptor of the added or modified watch point. 131 */ 132 WatchDescriptor watch( const(char)[] path, uint mask ) @trusted @nogc { 133 auto wd = WatchDescriptor( fd.osCallErrno!(.inotify_add_watch)(ToStringz!(NAME_MAX+1)(path), mask) ); 134 135 return wd; 136 } 137 138 /** 139 * remove a previously registered watch point 140 * 141 * Params: 142 * wd = the WatchDescriptor returned by watch 143 */ 144 void removeWatch( WatchDescriptor wd ) @trusted @nogc { 145 fd.osCallErrno!(.inotify_rm_watch)( wd.value ); 146 } 147 148 /** 149 * get one pending inotify event 150 * 151 * This function may sleep. 152 * 153 * Returns: 154 * A pointer to a const Event. This pointer remains valid until the next call to `getEvent` or `consumeAllEvents`. 155 * 156 * Bugs: 157 * Only one fiber at a time may use this function 158 */ 159 const(Event)* getEvent() @trusted @nogc { 160 if( bufferSize==bufferConsumed ) { 161 bufferConsumed = bufferSize = 0; 162 163 bufferSize = cast(uint) fd.read( eventsBuffer ); 164 } 165 166 assertGT( bufferSize, bufferConsumed, "getEvent with no events" ); 167 assertGE( bufferConsumed - bufferSize, inotify_event.sizeof, "events buffer with partial event" ); 168 169 const(Event)* ret = cast(Event*)(&eventsBuffer[bufferConsumed]); 170 bufferConsumed += inotify_event.sizeof + ret.len; 171 172 return ret; 173 } 174 175 /** 176 * Discard all pending events. 177 * 178 * This function discards all pending events, both from the memory cache and from the OS. All events in the inotify fd are discarded, 179 * regardless of which watch descriptor they belong to. 180 * 181 * This function does not sleep. 182 */ 183 void consumeAllEvents() @trusted @nogc { 184 with( theReactor.criticalSection ) { 185 // Clear the memory cache 186 bufferConsumed = bufferSize = 0; 187 188 bool moreEvents = true; 189 do { 190 import core.sys.posix.unistd : read; 191 import core.stdc.errno; 192 193 auto size = fd.osCall!(read)(eventsBuffer.ptr, eventsBuffer.length); 194 if( size<0 ) { 195 errnoEnforceNGC( errno==EAGAIN || errno==EWOULDBLOCK, "inotify read failed" ); 196 moreEvents = false; 197 } 198 } while( moreEvents ); 199 } 200 } 201 } 202 203 unittest { 204 void watcher(string path) { 205 Inotifier inote; 206 inote.open(); 207 208 auto wd = inote.watch(path, IN_MODIFY|IN_ATTRIB|IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO|IN_ONLYDIR); 209 DEBUG!"watch registration returned %s"(wd); 210 211 while( true ) { 212 auto event = inote.getEvent(); 213 INFO!"handle %s mask %x cookie %s len %s name %s"( event.wd, event.mask, event.cookie, event.len, event.name ); 214 } 215 } 216 217 void testBody() { 218 import core.sys.posix.stdlib; 219 import std..string; 220 import std.file; 221 import std.process; 222 import std.exception; 223 224 import mecca.lib.time; 225 226 string iPath = format("%s/meccaUT-XXXXXX\0", tempDir()); 227 char[] path; 228 path.length = iPath.length; 229 path[] = iPath[]; 230 errnoEnforce( mkdtemp(path.ptr) !is null ); 231 scope(exit) execute( ["rm", "-rf", path] ); 232 233 iPath.length = 0; 234 iPath ~= path[0..$-1]; 235 DEBUG!"Using directory %s for inotify tests"( iPath ); 236 237 theReactor.spawnFiber( &watcher, iPath ); 238 239 theReactor.yield(); 240 241 execute( ["touch", iPath ~ "/file1"] ); 242 rename( iPath ~ "/file1", iPath ~ "/file2" ); 243 244 theReactor.sleep(1.msecs); 245 } 246 247 testWithReactor(&testBody); 248 }