1 /** 2 * ae.sys.inotify 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Vladimir Panteleev <vladimir@thecybershadow.net> 12 */ 13 14 module ae.sys.inotify; 15 16 version(linux): 17 18 import core.sys.posix.unistd; 19 import core.sys.linux.sys.inotify; 20 21 import std.exception; 22 import std.stdio; 23 import std.string; 24 25 import ae.net.asockets; 26 import ae.utils.meta : singleton; 27 28 struct INotify 29 { 30 /// http://man7.org/linux/man-pages/man7/inotify.7.html 31 enum Mask : uint32_t 32 { 33 access = IN_ACCESS , 34 modify = IN_MODIFY , 35 attrib = IN_ATTRIB , 36 closeWrite = IN_CLOSE_WRITE , 37 closeNoWrite = IN_CLOSE_NOWRITE, 38 open = IN_OPEN , 39 movedFrom = IN_MOVED_FROM , 40 movedTo = IN_MOVED_TO , 41 create = IN_CREATE , 42 remove = IN_DELETE , 43 removeSelf = IN_DELETE_SELF , 44 moveSelf = IN_MOVE_SELF , 45 46 unmount = IN_UMOUNT , 47 qOverflow = IN_Q_OVERFLOW , 48 ignored = IN_IGNORED , 49 close = IN_CLOSE , 50 move = IN_MOVE , 51 onlyDir = IN_ONLYDIR , 52 dontFollow = IN_DONT_FOLLOW , 53 exclUnlink = IN_EXCL_UNLINK , 54 maskAdd = IN_MASK_ADD , 55 isDir = IN_ISDIR , 56 oneShot = IN_ONESHOT , 57 allEvents = IN_ALL_EVENTS , 58 } 59 60 static struct WatchDescriptor { int wd; } 61 62 alias INotifyHandler = void delegate(in char[] name, Mask mask, uint cookie); 63 64 WatchDescriptor add(string path, Mask mask, INotifyHandler handler) 65 { 66 if (fd < 0) 67 start(); 68 auto wd = inotify_add_watch(fd, path.toStringz(), mask); 69 errnoEnforce(wd >= 0, "inotify_add_watch"); 70 handlers[wd] = handler; 71 return WatchDescriptor(wd); 72 } 73 74 void remove(WatchDescriptor wd) 75 { 76 auto result = inotify_rm_watch(fd, wd.wd); 77 errnoEnforce(result >= 0, "inotify_rm_watch"); 78 handlers.remove(wd.wd); 79 if (!handlers.length) 80 stop(); 81 } 82 83 private: 84 int fd = -1; 85 FileConnection conn; 86 87 INotifyHandler[int] handlers; 88 89 void start() 90 { 91 assert(fd < 0, "Already started"); 92 fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); 93 errnoEnforce(fd >= 0, "inotify_init1"); 94 95 conn = new FileConnection(fd); 96 //conn.daemon = true; 97 conn.handleReadData = &onReadData; 98 } 99 100 void stop() 101 { 102 assert(fd >= 0, "Not started"); 103 conn.disconnect(); 104 fd = -1; 105 } 106 107 void onReadData(Data data) 108 { 109 while (data.length) 110 { 111 enforce(data.length >= inotify_event.sizeof, "Insufficient bytes for inotify_event"); 112 auto pheader = cast(inotify_event*)data.contents.ptr; 113 auto end = inotify_event.sizeof + pheader.len; 114 enforce(data.length >= end, "Insufficient bytes for inotify name"); 115 auto name = cast(char[])data.contents[inotify_event.sizeof .. end]; 116 117 auto p = name.indexOf('\0'); 118 if (p >= 0) 119 name = name[0..p]; 120 121 if (pheader.wd == -1) 122 { 123 // Overflow - notify all watch descriptors 124 foreach (wd, handler; handlers) 125 handler(name, cast(Mask)pheader.mask, pheader.cookie); 126 } 127 else 128 { 129 auto phandler = pheader.wd in handlers; 130 enforce(phandler, "Unregistered inotify watch descriptor"); 131 (*phandler)(name, cast(Mask)pheader.mask, pheader.cookie); 132 } 133 data = data[end..$]; 134 } 135 } 136 } 137 138 INotify iNotify; 139 140 unittest 141 { 142 import std.file, ae.sys.file; 143 144 if ("tmp".exists) "tmp".removeRecurse(); 145 mkdir("tmp"); 146 scope(exit) "tmp".removeRecurse(); 147 148 INotify.Mask[] events; 149 INotify.WatchDescriptor wd; 150 wd = iNotify.add("tmp", INotify.Mask.create | INotify.Mask.remove, 151 (in char[] name, INotify.Mask mask, uint cookie) 152 { 153 assert(name == "killme"); 154 events ~= mask; 155 if (events.length == 2) 156 iNotify.remove(wd); 157 } 158 ); 159 touch("tmp/killme"); 160 remove("tmp/killme"); 161 socketManager.loop(); 162 163 assert(events == [INotify.Mask.create, INotify.Mask.remove]); 164 }