Added listing one subdirectory of a watched directory.
[erlang-gen_inotify.git] / src / gen_inotify.erl
1 %%%---------------------------------------------------------------------------
2 %%% @doc
3 %%%   `inotify(7)' bindings for Erlang.
4 %%% @end
5 %%%---------------------------------------------------------------------------
6
7 -module(gen_inotify).
8
9 %% public interface
10 -export([open/0, open/1, close/1]).
11 -export([add/3, update/3, remove/2, list/1]).
12 -export([controlling_process/2]).
13 -export([format_error/1]).
14
15 -export_type([handle/0, message/0, posix/0]).
16 -export_type([flag/0, flag_event/0, flag_scan/0]).
17
18 %%%---------------------------------------------------------------------------
19 %%% types
20
21 -define(DRIVER_NAME, "gen_inotify_drv").
22
23 -type handle() :: port().
24
25 -type cookie() :: non_neg_integer().
26
27 -type message() ::
28     {inotify, Handle :: handle(), Path :: file:filename() | undefined,
29       Cookie :: cookie(), Flags :: [flag() | flag_event() | flag_scan(), ...]}
30   | {inotify_error, Handle :: handle(),
31       Error :: queue_overflow | posix() | {file:filename(), posix()}}.
32 %% Filename is an absolute path. `undefined' should never happen.
33 %%
34 %% Flags `unmount' and `watch_removed' are always sent with no other
35 %% accompanying flag.
36 %%
37 %% If `is_dir' flag is present, it's always the first one.
38 %%
39 %% If a directory was added with `scan' option, it will result with zero or
40 %% more messages with `present' and `{name,Basename}' flags (those flags can
41 %% never be encountered without `scan' flag). These messages will always have
42 %% the same order of flags: `is_dir' (if applicable), `present', and
43 %% `{name,_}' (this one is a convenience helper that carries the last
44 %% fragment of `Path'). Listing error is signaled as a message
45 %% `{inotify_error,Handle,{Path,Errno}}'. The messages look like following:
46 %%
47 %% <ul>
48 %%   <li>`{inotify, Handle, Path, _Cookie, [is_dir, present, {name, Basename}]}'</li>
49 %%   <li>`{inotify, Handle, Path, _Cookie, [present, {name, Basename}]}'</li>
50 %%   <li>`{inotify_error, Handle, {Path, Errno}}'
51 %%        (`Errno' is {@type posix()})</li>
52 %% </ul>
53 %%
54 %% Note that `scan' option will trigger read events, like `open' or
55 %% `close_nowrite'.
56
57 -type flag() :: access
58               | modify
59               | attrib
60               | create
61               | delete
62               | open
63               | close_write | close_nowrite
64               | move_from | move_to
65               | delete_self
66               | move_self.
67
68 -type flag_event() :: watch_removed
69                     | is_dir
70                     | unmount.
71 %% See {@type message()} for details about these flags' positions and company.
72
73 -type flag_scan() :: present | {name, Basename :: file:filename()}.
74
75 -type posix() :: inet:posix().
76
77 %%%---------------------------------------------------------------------------
78 %%% public interface
79 %%%---------------------------------------------------------------------------
80
81 %% @doc Open a new inotify handle.
82
83 -spec open() ->
84   {ok, handle()} | {error, system_limit | posix()}.
85
86 open() ->
87   open([]).
88
89 %% @doc Open a new inotify handle.
90
91 -spec open(Options :: [Option]) ->
92   {ok, handle()} | {error, badarg | system_limit | posix()}
93   when Option :: recursive | real_path.
94
95 open(Options) ->
96   case build_open_options_data(Options) of
97     {ok, OptData} ->
98       try open_port({spawn_driver, ?DRIVER_NAME}, [binary]) of
99         Handle ->
100           port_control(Handle, 0, OptData),
101           {ok, Handle}
102       catch
103         error:Reason ->
104           {error, Reason}
105       end;
106     {error, badarg} ->
107       {error, badarg}
108   end.
109
110 %%----------------------------------------------------------
111 %% build_open_options_data() {{{
112
113 %% @doc Translate list of options to a `port_control(Port,0,_)' request
114 %%   payload.
115
116 -spec build_open_options_data(Options :: [Option]) ->
117   {ok, {Recursive :: boolean(), UseRealPath :: boolean()}} | {error, badarg}
118   when Option :: recursive | real_path.
119
120 build_open_options_data(Options) ->
121   DefaultOptions = {false, false}, % `{Recursive, UseRealPath}'
122   try lists:foldr(fun add_open_option/2, DefaultOptions, Options) of
123     {false, false} -> {ok, <<0:8, 0:8>>};
124     {true,  false} -> {ok, <<1:8, 0:8>>};
125     {false, true}  -> {ok, <<0:8, 1:8>>};
126     {true,  true}  -> {ok, <<1:8, 1:8>>}
127   catch
128     _:_ -> {error, badarg}
129   end.
130
131 %% @doc Workhorse for {@link build_open_options_data/1}.
132
133 -spec add_open_option(Option, {boolean(), boolean()}) ->
134   {boolean(), boolean()}
135   when Option :: recursive | real_path.
136
137 add_open_option(recursive, {_Recursive, UseRealPath}) -> {true, UseRealPath};
138 add_open_option(real_path, {Recursive, _UseRealPath}) -> {Recursive, true}.
139
140 %% }}}
141 %%----------------------------------------------------------
142
143 %% @doc Close a handle.
144
145 -spec close(handle()) ->
146   ok.
147
148 close(Handle) ->
149   try
150     unlink(Handle),
151     port_close(Handle)
152   catch
153     % this could be caused by port already being closed, which is expected in
154     % some cases
155     error:badarg -> ignore
156   end,
157   ok.
158
159 %% @doc Assign a new owner to a handle.
160
161 -spec controlling_process(handle(), pid()) ->
162   ok | {error, not_owner | closed | badarg}.
163
164 controlling_process(Handle, Pid) ->
165   try erlang:port_info(Handle, connected) of
166     {connected, Pid} ->
167       ok; % already the owner
168     {connected, Owner} when Owner /= self() ->
169       {error, not_owner};
170     {connected, _OldOwner} ->
171       try
172         port_connect(Handle, Pid),
173         unlink(Handle),
174         ok
175       catch
176         _:_ ->
177           {error, closed}
178       end;
179     undefined ->
180       {error, closed}
181   catch
182     _:_ ->
183       {error, badarg}
184   end.
185
186 %% @doc Monitor a file or directory.
187 %%
188 %%   If the path was already watched, any previous flags will be replaced.
189 %%
190 %%   `close' and `move' flags are shorthands for `close_write'
191 %%   + `close_nowrite' and for `move_from' + `move_to', respectively.
192
193 -spec add(handle(), file:filename(), [flag() | close | move | Option]) ->
194   ok | {error, badarg | posix()}
195   when Option :: scan | follow_symlink | unwatch_on_unlink | once | if_dir.
196
197 add(Handle, Path, Flags) ->
198   case build_flags(Flags, add) of
199     {ok, Value} ->
200       try port_control(Handle, 1, [<<Value:32>>, Path]) of
201         <<>> -> ok;
202         ErrorName -> {error, binary_to_atom(ErrorName, latin1)}
203       catch
204         _:_ -> {error, badarg}
205       end;
206     {error, badarg} ->
207       {error, badarg}
208   end.
209
210 %% @doc Add events to watch for for a file or directory.
211 %%
212 %%   If the path was not watched yet, it's added.
213 %%
214 %%   `close' and `move' flags are shorthands for `close_write'
215 %%   + `close_nowrite' and for `move_from' + `move_to', respectively.
216
217 -spec update(handle(), file:filename(), [flag() | close | move | Option]) ->
218   ok | {error, badarg | posix()}
219   when Option :: scan | follow_symlink | unwatch_on_unlink | once | if_dir.
220
221 update(Handle, Path, Flags) ->
222   case build_flags(Flags, update) of
223     {ok, Value} ->
224       try port_control(Handle, 1, [<<Value:32>>, Path]) of
225         <<>> -> ok;
226         ErrorName -> {error, binary_to_atom(ErrorName, latin1)}
227       catch
228         _:_ -> {error, badarg}
229       end;
230     {error, badarg} ->
231       {error, badarg}
232   end.
233
234 %%----------------------------------------------------------
235 %% build_flags() {{{
236
237 %% @doc Translate list of flags to an integer to be sent to port.
238
239 -spec build_flags(Flags :: [Flag], add | update) ->
240   {ok, non_neg_integer()} | {error, badarg}
241   when Flag :: flag() | follow_symlink | unwatch_on_unlink | once | if_dir.
242
243 build_flags(Flags, Op) ->
244   % NOTE: start with "don't follow symlinks" set and subtract it if
245   % `follow_symlink' flag is present
246   try lists:foldl(fun add_flag/2, 16#1000, Flags) of
247     Result when Op == add    -> {ok, Result};
248     Result when Op == update -> {ok, Result bor 16#010000}
249   catch
250     _:_ -> {error, badarg}
251   end.
252
253 %% @doc Workhorse for {@link build_flags/1}.
254
255 -spec add_flag(Flag, non_neg_integer()) ->
256   non_neg_integer()
257   when Flag :: flag()
258              | scan | follow_symlink | unwatch_on_unlink | once | if_dir.
259
260 add_flag(access,            Flags) -> Flags bor 16#0001;
261 add_flag(modify,            Flags) -> Flags bor 16#0002;
262 add_flag(attrib,            Flags) -> Flags bor 16#0004;
263 add_flag(create,            Flags) -> Flags bor 16#0008;
264 add_flag(delete,            Flags) -> Flags bor 16#0010;
265 add_flag(open,              Flags) -> Flags bor 16#0020;
266 add_flag(close,             Flags) -> Flags bor 16#0040 bor 16#0080;
267 add_flag(close_write,       Flags) -> Flags bor 16#0040;
268 add_flag(close_nowrite,     Flags) -> Flags bor 16#0080;
269 add_flag(move,              Flags) -> Flags bor 16#0100 bor 16#0200;
270 add_flag(move_from,         Flags) -> Flags bor 16#0100;
271 add_flag(move_to,           Flags) -> Flags bor 16#0200;
272 add_flag(delete_self,       Flags) -> Flags bor 16#0400;
273 add_flag(move_self,         Flags) -> Flags bor 16#0800;
274 add_flag(follow_symlink,    Flags) -> Flags band bnot 16#1000; % reverse flag
275 add_flag(unwatch_on_unlink, Flags) -> Flags bor 16#2000;
276 add_flag(once,              Flags) -> Flags bor 16#4000;
277 add_flag(if_dir,            Flags) -> Flags bor 16#8000;
278 add_flag(scan,              Flags) -> Flags bor 16#100000.
279
280 %% }}}
281 %%----------------------------------------------------------
282
283 %% @doc Stop watching a file or directory.
284 %%
285 %%   After this function is called, the owner will receive {@type message()}
286 %%   with `watch_removed' flag.
287
288 -spec remove(handle(), file:filename()) ->
289   ok | {error, badarg}.
290
291 remove(Handle, Path) ->
292   try port_control(Handle, 2, Path) of
293     <<>> -> ok;
294     ErrorName -> {error, binary_to_atom(ErrorName, latin1)}
295   catch
296     _:_ -> {error, badarg}
297   end.
298
299 %% @doc List watched paths.
300
301 -spec list(handle()) ->
302   {ok, [Entry]} | {error, badarg}
303   when Entry :: {file:filename(), [flag()]}.
304
305 list(Handle) ->
306   try port_control(Handle, 3, <<>>) of
307     <<Count:32>> ->
308       Entries = receive_listing(Handle, Count, []),
309       {ok, Entries}
310   catch
311     _:_ -> {error, badarg}
312   end.
313
314 %% @doc Workhorse for {@link list/1}.
315
316 -spec receive_listing(handle(), non_neg_integer(), [Entry]) ->
317   [Entry]
318   when Entry :: {file:filename(), [flag()]}.
319
320 receive_listing(_Handle, 0 = _Count, Entries) ->
321   Entries;
322 receive_listing(Handle, Count, Entries) when Count > 0 ->
323   receive
324     {inotify_listing, Handle, _WD, Path, Flags} ->
325       receive_listing(Handle, Count - 1, [{Path, Flags} | Entries])
326   end.
327
328 %%%---------------------------------------------------------------------------
329
330 %% @doc Format a reason from error tuple as a usable error message.
331
332 -spec format_error(Reason :: term()) ->
333   string().
334
335 format_error(queue_overflow) ->
336   "overflow in inotify event queue";
337 format_error(badarg) ->
338   "bad argument";
339 format_error(closed) ->
340   "file descriptor closed";
341 format_error({Path, Errno}) ->
342   "error while scanning " ++ Path ++ ": " ++ inet:format_error(Errno);
343 format_error(Errno) ->
344   inet:format_error(Errno).
345
346 %%%---------------------------------------------------------------------------
347 %%% vim:ft=erlang:foldmethod=marker