Skip to content

Commit fb6c511

Browse files
committed
feat(file): add support linux inotify
Signed-off-by: Jianhui Zhao <zhaojh329@gmail.com>
1 parent 21302fc commit fb6c511

File tree

3 files changed

+241
-0
lines changed

3 files changed

+241
-0
lines changed

examples/inotify.lua

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/usr/bin/env eco
2+
3+
local file = require 'eco.file'
4+
5+
local watcher, err = file.inotify()
6+
if not watcher then
7+
error(err)
8+
end
9+
10+
local wd, err = watcher:add('/tmp/')
11+
if not wd then
12+
error(err)
13+
end
14+
15+
while true do
16+
local s, err = watcher:wait()
17+
if not s then
18+
error(err)
19+
end
20+
21+
print(s.name, s.event, s.mask & file.IN_ISDIR > 0 and 'ISDIR' or '')
22+
end

file.c

+68
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include <sys/sendfile.h>
77
#include <sys/statvfs.h>
8+
#include <sys/inotify.h>
89
#include <sys/file.h>
910
#include <stdlib.h>
1011
#include <unistd.h>
@@ -386,6 +387,52 @@ static int lua_flock(lua_State *L)
386387
return 1;
387388
}
388389

390+
static int lua_inotify_init(lua_State *L)
391+
{
392+
int fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
393+
if (fd < 0) {
394+
lua_pushnil(L);
395+
lua_pushstring(L, strerror(errno));
396+
return 2;
397+
}
398+
399+
lua_pushinteger(L, fd);
400+
return 1;
401+
}
402+
403+
static int lua_inotify_add_watch(lua_State *L)
404+
{
405+
int fd = luaL_checkinteger(L, 1);
406+
const char *pathname = luaL_checkstring(L, 2);
407+
uint32_t mask = luaL_checkinteger(L, 3);
408+
int wd;
409+
410+
wd = inotify_add_watch(fd, pathname, mask);
411+
if (wd < 0) {
412+
lua_pushnil(L);
413+
lua_pushstring(L, strerror(errno));
414+
return 2;
415+
}
416+
417+
lua_pushinteger(L, wd);
418+
return 1;
419+
}
420+
421+
static int lua_inotify_rm_watch(lua_State *L)
422+
{
423+
int fd = luaL_checkinteger(L, 1);
424+
int wd = luaL_checkinteger(L, 2);
425+
426+
if (inotify_rm_watch(fd, wd)) {
427+
lua_pushnil(L);
428+
lua_pushstring(L, strerror(errno));
429+
return 2;
430+
}
431+
432+
lua_pushboolean(L, true);
433+
return 1;
434+
}
435+
389436
static const luaL_Reg funcs[] = {
390437
{"open", lua_file_open},
391438
{"close", lua_file_close},
@@ -401,6 +448,9 @@ static const luaL_Reg funcs[] = {
401448
{"dirname", lua_dirname},
402449
{"basename", lua_basename},
403450
{"flock", lua_flock},
451+
{"inotify_init", lua_inotify_init},
452+
{"inotify_add_watch", lua_inotify_add_watch},
453+
{"inotify_rm_watch", lua_inotify_rm_watch},
404454
{NULL, NULL}
405455
};
406456

@@ -444,6 +494,24 @@ int luaopen_eco_core_file(lua_State *L)
444494
lua_add_constant(L, "LOCK_EX", LOCK_EX);
445495
lua_add_constant(L, "LOCK_UN", LOCK_UN);
446496

497+
/* inotify */
498+
lua_add_constant(L, "IN_ACCESS", IN_ACCESS);
499+
lua_add_constant(L, "IN_MODIFY", IN_MODIFY);
500+
lua_add_constant(L, "IN_ATTRIB", IN_ATTRIB);
501+
lua_add_constant(L, "IN_CLOSE_WRITE", IN_CLOSE_WRITE);
502+
lua_add_constant(L, "IN_CLOSE_NOWRITE", IN_CLOSE_NOWRITE);
503+
lua_add_constant(L, "IN_CLOSE", IN_CLOSE);
504+
lua_add_constant(L, "IN_OPEN", IN_OPEN);
505+
lua_add_constant(L, "IN_MOVED_FROM", IN_MOVED_FROM);
506+
lua_add_constant(L, "IN_MOVED_TO", IN_MOVED_TO);
507+
lua_add_constant(L, "IN_MOVE", IN_MOVE);
508+
lua_add_constant(L, "IN_CREATE", IN_CREATE);
509+
lua_add_constant(L, "IN_DELETE", IN_DELETE);
510+
lua_add_constant(L, "IN_DELETE_SELF", IN_DELETE_SELF);
511+
lua_add_constant(L, "IN_MOVE_SELF", IN_MOVE_SELF);
512+
lua_add_constant(L, "IN_ALL_EVENTS", IN_ALL_EVENTS);
513+
lua_add_constant(L, "IN_ISDIR", IN_ISDIR);
514+
447515
eco_new_metatable(L, ECO_FILE_DIR_MT, dir_methods);
448516
lua_pushcclosure(L, lua_file_dir, 1);
449517
lua_setfield(L, -2, "dir");

file.lua

+151
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,155 @@ function M.sync()
8080
return true
8181
end
8282

83+
local inotify_methods = {}
84+
85+
local function read_inotify_event(self, timeout)
86+
local data = self.data
87+
local wd, mask, _, len = string.unpack('I4I4I4I4', data)
88+
local name
89+
90+
if len > 0 then
91+
name = data:sub(16, len + 16):gsub('\0+', '')
92+
end
93+
94+
data = data:sub(len + 17)
95+
96+
if #data > 0 then
97+
self.data = data
98+
else
99+
self.data = nil
100+
end
101+
102+
local path = self.watchs[wd]
103+
if not path then
104+
return self:wait(timeout)
105+
end
106+
107+
if len > 0 then
108+
if path:sub(#path) ~= '/' then
109+
path = path .. '/'
110+
end
111+
112+
name = path .. name
113+
else
114+
name = path
115+
end
116+
117+
local event
118+
119+
if mask & file.IN_ACCESS > 0 then
120+
event = 'ACCESS'
121+
elseif mask & file.IN_MODIFY > 0 then
122+
event = 'MODIFY'
123+
elseif mask & file.IN_ATTRIB > 0 then
124+
event = 'ATTRIB'
125+
elseif mask & file.IN_CLOSE > 0 then
126+
event = 'CLOSE'
127+
elseif mask & file.IN_OPEN > 0 then
128+
event = 'OPEN'
129+
elseif mask & file.IN_MOVE > 0 then
130+
event = 'MOVE'
131+
elseif mask & file.IN_CREATE > 0 then
132+
event = 'CREATE'
133+
elseif mask & file.IN_DELETE > 0 then
134+
event = 'DELETE'
135+
elseif mask & file.IN_DELETE_SELF > 0 then
136+
event = 'DELETE_SELF'
137+
elseif mask & file.IN_MOVE_SELF > 0 then
138+
event = 'MOVE_SELF'
139+
end
140+
141+
if not event then
142+
return self:wait(timeout)
143+
end
144+
145+
return {
146+
name = name,
147+
event = event,
148+
mask = mask
149+
}
150+
end
151+
152+
--[[
153+
wait the next event and return a table represent an event containing the following fields.
154+
name: filename associated with the event
155+
event: event name, supports ACCESS, MODIFY, ATTRIB, CLOSE, OPEN, MOVE, CREATE, DELETE, DELETE_SELF, MOVE_SELF
156+
mask: contains bits that describe the event that occurred
157+
--]]
158+
function inotify_methods:wait(timeout)
159+
if self.data then
160+
return read_inotify_event(self, timeout)
161+
end
162+
163+
local ok, err = self.iow:wait(timeout)
164+
if not ok then
165+
return nil, err
166+
end
167+
168+
local data, err = file.read(self.fd, 1024)
169+
if not data then
170+
return nil, err
171+
end
172+
173+
self.data = data
174+
175+
return read_inotify_event(self, timeout)
176+
end
177+
178+
-- add a watch to an initialized inotify instance
179+
-- you can set events be of interest to you via the second argument, defaults to `file.IN_ALL_EVENTS`.
180+
-- return the watch descriptor will be used in `del` method.
181+
function inotify_methods:add(path, mask)
182+
local wd, err = file.inotify_add_watch(self.fd, path, mask or file.IN_ALL_EVENTS)
183+
if not wd then
184+
return nil, err
185+
end
186+
187+
self.watchs[wd] = path
188+
189+
return wd
190+
end
191+
192+
-- remove an existing watch from an inotify instance
193+
-- wd: the watch descriptor returned via `add`
194+
function inotify_methods:del(wd)
195+
local ok, err = file.inotify_rm_watch(self.fd, wd)
196+
if not ok then
197+
return nil, err
198+
end
199+
200+
self.watchs[wd] = nil
201+
202+
return ok
203+
end
204+
205+
function inotify_methods:close()
206+
if self.fd < 0 then
207+
return
208+
end
209+
210+
self.iow:cancel()
211+
file.close(self.fd)
212+
self.fd = -1
213+
end
214+
215+
local inotify_mt = {
216+
__index = inotify_methods,
217+
__gc = inotify_methods.close
218+
}
219+
220+
-- create an inotify instance
221+
function M.inotify()
222+
local fd, err = file.inotify_init()
223+
if not fd then
224+
return nil, err
225+
end
226+
227+
return setmetatable({
228+
fd = fd,
229+
watchs = {},
230+
iow = eco.watcher(eco.IO, fd),
231+
}, inotify_mt)
232+
end
233+
83234
return setmetatable(M, { __index = file })

0 commit comments

Comments
 (0)