Skip to content

Commit

Permalink
feat(file): add support linux inotify
Browse files Browse the repository at this point in the history
Signed-off-by: Jianhui Zhao <[email protected]>
  • Loading branch information
zhaojh329 committed May 13, 2024
1 parent 21302fc commit fb6c511
Show file tree
Hide file tree
Showing 3 changed files with 241 additions and 0 deletions.
22 changes: 22 additions & 0 deletions examples/inotify.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env eco

local file = require 'eco.file'

local watcher, err = file.inotify()
if not watcher then
error(err)
end

local wd, err = watcher:add('/tmp/')
if not wd then
error(err)
end

while true do
local s, err = watcher:wait()
if not s then
error(err)
end

print(s.name, s.event, s.mask & file.IN_ISDIR > 0 and 'ISDIR' or '')
end
68 changes: 68 additions & 0 deletions file.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include <sys/sendfile.h>
#include <sys/statvfs.h>
#include <sys/inotify.h>
#include <sys/file.h>
#include <stdlib.h>
#include <unistd.h>
Expand Down Expand Up @@ -386,6 +387,52 @@ static int lua_flock(lua_State *L)
return 1;
}

static int lua_inotify_init(lua_State *L)
{
int fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
if (fd < 0) {
lua_pushnil(L);
lua_pushstring(L, strerror(errno));
return 2;
}

lua_pushinteger(L, fd);
return 1;
}

static int lua_inotify_add_watch(lua_State *L)
{
int fd = luaL_checkinteger(L, 1);
const char *pathname = luaL_checkstring(L, 2);
uint32_t mask = luaL_checkinteger(L, 3);
int wd;

wd = inotify_add_watch(fd, pathname, mask);
if (wd < 0) {
lua_pushnil(L);
lua_pushstring(L, strerror(errno));
return 2;
}

lua_pushinteger(L, wd);
return 1;
}

static int lua_inotify_rm_watch(lua_State *L)
{
int fd = luaL_checkinteger(L, 1);
int wd = luaL_checkinteger(L, 2);

if (inotify_rm_watch(fd, wd)) {
lua_pushnil(L);
lua_pushstring(L, strerror(errno));
return 2;
}

lua_pushboolean(L, true);
return 1;
}

static const luaL_Reg funcs[] = {
{"open", lua_file_open},
{"close", lua_file_close},
Expand All @@ -401,6 +448,9 @@ static const luaL_Reg funcs[] = {
{"dirname", lua_dirname},
{"basename", lua_basename},
{"flock", lua_flock},
{"inotify_init", lua_inotify_init},
{"inotify_add_watch", lua_inotify_add_watch},
{"inotify_rm_watch", lua_inotify_rm_watch},
{NULL, NULL}
};

Expand Down Expand Up @@ -444,6 +494,24 @@ int luaopen_eco_core_file(lua_State *L)
lua_add_constant(L, "LOCK_EX", LOCK_EX);
lua_add_constant(L, "LOCK_UN", LOCK_UN);

/* inotify */
lua_add_constant(L, "IN_ACCESS", IN_ACCESS);
lua_add_constant(L, "IN_MODIFY", IN_MODIFY);
lua_add_constant(L, "IN_ATTRIB", IN_ATTRIB);
lua_add_constant(L, "IN_CLOSE_WRITE", IN_CLOSE_WRITE);
lua_add_constant(L, "IN_CLOSE_NOWRITE", IN_CLOSE_NOWRITE);
lua_add_constant(L, "IN_CLOSE", IN_CLOSE);
lua_add_constant(L, "IN_OPEN", IN_OPEN);
lua_add_constant(L, "IN_MOVED_FROM", IN_MOVED_FROM);
lua_add_constant(L, "IN_MOVED_TO", IN_MOVED_TO);
lua_add_constant(L, "IN_MOVE", IN_MOVE);
lua_add_constant(L, "IN_CREATE", IN_CREATE);
lua_add_constant(L, "IN_DELETE", IN_DELETE);
lua_add_constant(L, "IN_DELETE_SELF", IN_DELETE_SELF);
lua_add_constant(L, "IN_MOVE_SELF", IN_MOVE_SELF);
lua_add_constant(L, "IN_ALL_EVENTS", IN_ALL_EVENTS);
lua_add_constant(L, "IN_ISDIR", IN_ISDIR);

eco_new_metatable(L, ECO_FILE_DIR_MT, dir_methods);
lua_pushcclosure(L, lua_file_dir, 1);
lua_setfield(L, -2, "dir");
Expand Down
151 changes: 151 additions & 0 deletions file.lua
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,155 @@ function M.sync()
return true
end

local inotify_methods = {}

local function read_inotify_event(self, timeout)
local data = self.data
local wd, mask, _, len = string.unpack('I4I4I4I4', data)
local name

if len > 0 then
name = data:sub(16, len + 16):gsub('\0+', '')
end

data = data:sub(len + 17)

if #data > 0 then
self.data = data
else
self.data = nil
end

local path = self.watchs[wd]
if not path then
return self:wait(timeout)
end

if len > 0 then
if path:sub(#path) ~= '/' then
path = path .. '/'
end

name = path .. name
else
name = path
end

local event

if mask & file.IN_ACCESS > 0 then
event = 'ACCESS'
elseif mask & file.IN_MODIFY > 0 then
event = 'MODIFY'
elseif mask & file.IN_ATTRIB > 0 then
event = 'ATTRIB'
elseif mask & file.IN_CLOSE > 0 then
event = 'CLOSE'
elseif mask & file.IN_OPEN > 0 then
event = 'OPEN'
elseif mask & file.IN_MOVE > 0 then
event = 'MOVE'
elseif mask & file.IN_CREATE > 0 then
event = 'CREATE'
elseif mask & file.IN_DELETE > 0 then
event = 'DELETE'
elseif mask & file.IN_DELETE_SELF > 0 then
event = 'DELETE_SELF'
elseif mask & file.IN_MOVE_SELF > 0 then
event = 'MOVE_SELF'
end

if not event then
return self:wait(timeout)
end

return {
name = name,
event = event,
mask = mask
}
end

--[[
wait the next event and return a table represent an event containing the following fields.
name: filename associated with the event
event: event name, supports ACCESS, MODIFY, ATTRIB, CLOSE, OPEN, MOVE, CREATE, DELETE, DELETE_SELF, MOVE_SELF
mask: contains bits that describe the event that occurred
--]]
function inotify_methods:wait(timeout)
if self.data then
return read_inotify_event(self, timeout)
end

local ok, err = self.iow:wait(timeout)
if not ok then
return nil, err
end

local data, err = file.read(self.fd, 1024)
if not data then
return nil, err
end

self.data = data

return read_inotify_event(self, timeout)
end

-- add a watch to an initialized inotify instance
-- you can set events be of interest to you via the second argument, defaults to `file.IN_ALL_EVENTS`.
-- return the watch descriptor will be used in `del` method.
function inotify_methods:add(path, mask)
local wd, err = file.inotify_add_watch(self.fd, path, mask or file.IN_ALL_EVENTS)
if not wd then
return nil, err
end

self.watchs[wd] = path

return wd
end

-- remove an existing watch from an inotify instance
-- wd: the watch descriptor returned via `add`
function inotify_methods:del(wd)
local ok, err = file.inotify_rm_watch(self.fd, wd)
if not ok then
return nil, err
end

self.watchs[wd] = nil

return ok
end

function inotify_methods:close()
if self.fd < 0 then
return
end

self.iow:cancel()
file.close(self.fd)
self.fd = -1
end

local inotify_mt = {
__index = inotify_methods,
__gc = inotify_methods.close
}

-- create an inotify instance
function M.inotify()
local fd, err = file.inotify_init()
if not fd then
return nil, err
end

return setmetatable({
fd = fd,
watchs = {},
iow = eco.watcher(eco.IO, fd),
}, inotify_mt)
end

return setmetatable(M, { __index = file })

0 comments on commit fb6c511

Please sign in to comment.