├── rebar.lock ├── rebar3 ├── AUTHORS ├── c_src ├── erl_comm.h ├── inotify_erlang.h ├── erl_comm.c ├── inotify_driver.c └── inotify_erlang.c ├── rebar.config ├── .gitignore ├── Makefile ├── src ├── inotify.app.src ├── inotify_evt.erl ├── inotify_server.erl └── inotify.erl ├── ChangeLog ├── LICENSE ├── include └── inotify.hrl ├── README.md ├── doc └── overview.edoc └── test └── inotify_test.erl /rebar.lock: -------------------------------------------------------------------------------- 1 | []. 2 | -------------------------------------------------------------------------------- /rebar3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sheyll/inotify/HEAD/rebar3 -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Jeff Miller (original author) 2 | Alexander Harju (bug fixes) 3 | Mats Cronqvist (bug fixes, build system) 4 | Sven Heyll (rebar port) 5 | Goel Deepak -------------------------------------------------------------------------------- /c_src/erl_comm.h: -------------------------------------------------------------------------------- 1 | #ifndef ERL_COMM_H 2 | #define ERL_COMM_H 3 | 4 | int read_cmd(char **pbuf, int *size, int *curpos); 5 | int read_exact(char *buf, int len); 6 | int write_cmd(ei_x_buff *buff); 7 | int write_exact(char *buf, int len); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {plugins, [pc]}. 2 | 3 | {provider_hooks, 4 | [ 5 | {pre, 6 | [ 7 | {compile, {pc, compile}}, 8 | {clean, {pc, clean}} 9 | ] 10 | } 11 | ] 12 | }. 13 | 14 | {port_specs, [{"priv/inotify", ["c_src/*.c"]}]}. 15 | 16 | {cover_enabled, true}. 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Files generated by some editors 2 | *~ 3 | 4 | # Files generated by "rebar compile" 5 | *.o 6 | *.beam 7 | /ebin/*.app 8 | /priv/inotify 9 | 10 | # Files generated by "rebar doc" 11 | /doc/*.html 12 | /doc/erlang.png 13 | /doc/stylesheet.css 14 | /doc/edoc-info 15 | 16 | # This happens during testing 17 | /.eunit 18 | /.markdown-preview.html 19 | c_src/*.d 20 | _build/ 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REBAR="./rebar3" 2 | SHELL = /bin/sh 3 | 4 | .DEFAULT_GOAL := compile 5 | 6 | .PHONY: publish test dialyzer compile clean 7 | 8 | publish: clean compile dialyzer test 9 | $(REBAR) hex publish 10 | $(REBAR) hex docs 11 | 12 | test: compile 13 | $(REBAR) eunit 14 | 15 | dialyzer: compile 16 | $(REBAR) dialyzer 17 | 18 | compile: 19 | $(REBAR) compile 20 | 21 | clean: 22 | $(REBAR) clean 23 | rm -f doc/*.html 24 | rm -f doc/*.css 25 | rm -f doc/*.png 26 | rm -f TEST-*.xml 27 | -------------------------------------------------------------------------------- /src/inotify.app.src: -------------------------------------------------------------------------------- 1 | {application, inotify, [{description, "Linux file alternation monitor."}, 2 | {vsn, "0.4.3"}, 3 | {registered, [inotify_server, inotify_evt]}, 4 | {mod, {inotify, []}}, 5 | {registered, []}, 6 | {included_applications, []}, 7 | {applications, [kernel, stdlib]}, 8 | {env, []}, 9 | {start_phases, []}, 10 | {maintainers, ["Sven Heyll"]}, 11 | {licenses, ["MIT"]}, 12 | {links, [{"Github", "https://github.com/sheyll/inotify"}]} 13 | ]}. 14 | -------------------------------------------------------------------------------- /c_src/inotify_erlang.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef NOTE_H 3 | #define NOTE_H 4 | #include 5 | 6 | struct note_entry_s { 7 | int fd; 8 | struct note_entry_s *next; 9 | }; 10 | 11 | typedef struct note_entry_s note_entry_t; 12 | 13 | struct note_s { 14 | note_entry_t *ent; 15 | }; 16 | typedef struct note_s note_t; 17 | 18 | 19 | note_t *note_init(); 20 | int note_setup_select(note_t *note, fd_set *readfds); 21 | void note_destroy(note_t *note); 22 | int note_read(note_t *note, fd_set *readfds); 23 | int note_list(note_t *note, char *buf, int *index); 24 | int note_open(note_t *note, char *buf, int *index); 25 | int note_close(note_t *note, char *buf, int *index); 26 | int note_add(note_t *note, char *buf, int *index); 27 | int note_remove(note_t *note, char *buf, int *index); 28 | #endif 29 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2018-04-12 Version 0.4.3 2 | 3 | * Merge pull request #5 from **Neeraj** to fix handling of long paths 4 | 5 | 2018-02-15 Sven Heyll 6 | 7 | * Switch to rebar3 8 | 9 | * Add a Makefile 10 | 11 | * Support hex.pm 12 | 13 | * Update documentation 14 | 15 | 2013-01-01 Sven Heyll 16 | 17 | * Move src/test.erl to priv/test.erl 18 | 19 | * Remove auto tools support 20 | 21 | * Add rebar configuration 22 | 23 | * Add OTP Application 24 | 25 | * Add OTP gen_event 26 | 27 | 2010-02-01 Mats Cronqvist 28 | 29 | * c_src/inotify_erlang.c (note_remove): 30 | driver will always return either a 2-tuple (synchronously) or a 31 | 5-tuple (asynchronously) 32 | 33 | * src/inotify.erl (test_start): 34 | see above 35 | 36 | * src/inoteefy.erl (talk_to_port): 37 | fixed race condition between synchronous and asynchronous events 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy 2 | of this software and associated documentation files (the "Software"), to deal 3 | in the Software without restriction, including without limitation the rights 4 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 5 | copies of the Software, and to permit persons to whom the Software is 6 | furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in 9 | all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 17 | THE SOFTWARE. 18 | -------------------------------------------------------------------------------- /include/inotify.hrl: -------------------------------------------------------------------------------- 1 | %%%============================================================================= 2 | %%% Created : 10 May 2010 by mats cronqvist 3 | %%% Modified : 1 Jan 2013 by sven heyll 4 | %%%============================================================================= 5 | 6 | -ifndef(inotify). 7 | -define(inotify, true). 8 | 9 | -define(inotify_msg(Mask, Cookie, Name), 10 | {inotify_msg, Mask, Cookie, Name}). 11 | 12 | %% inotify masks 13 | -define(ALL ,all). 14 | -define(ACCESS ,access). 15 | -define(ATTRIB ,attrib). 16 | -define(CLOSE_WRITE ,close_write). 17 | -define(CLOSE_NOWRITE ,close_nowrite). 18 | -define(CLOSE ,close). 19 | -define(CREATE ,create). 20 | -define(DELETE ,delete). 21 | -define(DELETE_SELF ,delete_self). 22 | -define(MODIFY ,modify). 23 | -define(MOVE_SELF ,move_self). 24 | -define(MOVED_FROM ,moved_from). 25 | -define(MOVED_TO ,moved_to). 26 | -define(MOVE ,move). 27 | -define(OPEN ,open). 28 | -define(DONT_FOLLOW ,dont_follow). 29 | -define(MASK_ADD ,mask_add). 30 | -define(ONLYDIR ,onlydir). 31 | 32 | 33 | -endif. %% inotify 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Linux inotify for Erlang 2 | ======================== 3 | 4 | Inotify is on erlang port for the Linux inotify API allowing one to monitor 5 | changes to files and directory in the filesystem. 6 | 7 | * [API Documentation](https://hexdocs.pm/inotify/) 8 | * [Rebar3 Installation (hex)](https://hex.pm/packages/inotify/) 9 | 10 | Example 11 | ------- 12 | 13 | inotify_demo() -> 14 | inotify:start(x,y), 15 | TmpDir = inotify:watch("/tmp/", ?ALL), 16 | inotify:add_handler(TmpDir, ?MODULE, self()), 17 | 18 | file:open("/tmp/foo_bar_inotify_test", [read, write]), 19 | 20 | receive 21 | {[create],0,"foo_bar_inotify_test"} -> 22 | io:format("Yeah!~n"); 23 | end. 24 | 25 | 26 | For the full example refer to the unit test: 27 | https://github.com/sheyll/inotify/blob/master/test/inotify_test.erl 28 | 29 | 30 | Release History 31 | --------------- 32 | 33 | * 20180416 version 0.4.3 fix long file name handling 34 | * 20180215 version 0.4.2 rebar3 and hex.pm support 35 | * 2015???? version 0.4.1 don't build on windows 36 | * 20130101 version 0.4.0 switch to rebar 37 | * 20100206 version 0.3 on github 38 | * 20090221 release 0.2 bug fix 39 | * 20080929 initial release version 0.1 40 | -------------------------------------------------------------------------------- /doc/overview.edoc: -------------------------------------------------------------------------------- 1 | @version 0.4.2 2 | @title inotify - Erlang OTP application for Linux file monitoring 3 | @doc 4 | 5 | == Introduction == 6 | 7 | Inotify allows to monitor changes to files and directory in the 8 | filesystem. It uses Linux `inotify' functions, hence the name. 9 | 10 | This is an OTP application. When started files and directories can be 11 | watched by calling {@link inotify:watch/1}. 12 | 13 | All file notifications are published via a gen_event. 14 | 15 | == Installation == 16 | 17 | The application must included in the reltool.config and the applications using 18 | this application must have a corresponding entry in their '*.app' files. 19 | 20 | This is an OTP application which must be started before it can be used 21 | by other applications. 22 | 23 | == Playing around == 24 | 25 | If building from git: 26 | ``` 27 | make compile 28 | ''' 29 | You can play around with it by doing this: 30 | ``` 31 | $ export ERL_LIBS=$(pwd) ; erl 32 | 33 | application:start(inotify). 34 | Ref = inotify:watch("/tmp"). 35 | inotify:print_events(Ref). 36 | ''' 37 | Now add a file in "/tmp": 38 | ``` 39 | touch /tmp/test_inotify 40 | ''' 41 | You should see some info being printed by inotify. 42 | To stop watching: 43 | ``` 44 | inotify:unwatch(Ref) 45 | ''' 46 | 47 | == Real usage == 48 | 49 | The API is exposed by a single module: {@link inotify}. 50 | 51 | To be able to associate events each call to {@link inotify:watch/1} 52 | returns a unique reference that is also contained in each event. 53 | 54 | NOTE: Currently each file or directory can only be monitored once. Each 55 | call to {@link inotify:watch/1} with the same path will overwrite the 56 | previous. 57 | 58 | Users can either write a full blown gen_event handler and add it to 59 | `inotify_evt' via {@link gen_event:add_handler/3} or they can implement 60 | the `inotify' behaviour and call {@link inotify:add_handler/3}. 61 | 62 | In that case `Module:inotify_evt(...)' will be called for each 63 | event corresponding to 'Tag'. 64 | -------------------------------------------------------------------------------- /c_src/erl_comm.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "erl_comm.h" 6 | 7 | /* 8 | * inputs 9 | * pbuf a pointer to a pointer to the buffer to place the message in 10 | * size size of buf 11 | * curpos current offset into buf. Should be set to 0 before 12 | * initial call 13 | * 14 | * returns < 0 on read() error 15 | * == 0 on read() returning 0 16 | * 1 when there is more to read 17 | * 2 when the message is complete 18 | */ 19 | int read_cmd(char **pbuf, int *size, int *curpos) 20 | { 21 | int len; 22 | int count; 23 | int desired; 24 | 25 | char *buf = *pbuf; 26 | if (*curpos < 2) { 27 | /* read header */ 28 | count = read(0, buf + *curpos, 2 - *curpos); 29 | 30 | if (count <= 0) 31 | return(count); /* Error or fd is closed */ 32 | 33 | *curpos += count; 34 | if (*curpos < 2) 35 | return(1); 36 | } 37 | /* calculate the total message length and 38 | * the desired amount to read taking into account 39 | * the ammount already read 40 | */ 41 | len = ((buf[0] << 8) & 0x0000ff00) | (buf[1] & 0x00ff); 42 | desired = len - *curpos + 2; 43 | 44 | /* check buffer size and realloc if necessary */ 45 | if (len > *size) { 46 | char *newbuf = (char *) realloc(buf, len); 47 | if (newbuf == NULL) 48 | return -1; 49 | memset(*pbuf, 0, len - (*size)); 50 | *pbuf = newbuf; 51 | buf = *pbuf; 52 | *size = len; 53 | } 54 | 55 | /* read message body */ 56 | count = read(0, buf + *curpos, desired); 57 | if (count <= 0) 58 | return(0); 59 | 60 | *curpos += count; 61 | return(2); 62 | } 63 | 64 | int write_cmd(ei_x_buff *buff) { 65 | char li; 66 | 67 | li = (buff->index >> 8) & 0xff; 68 | write_exact(&li, 1); 69 | li = buff->index & 0xff; 70 | write_exact(&li, 1); 71 | 72 | return write_exact(buff->buff, buff->index); 73 | } 74 | 75 | int write_exact(char *buf, int len) { 76 | int i, wrote = 0; 77 | 78 | do { 79 | if ((i = write(1, buf+wrote, len-wrote)) <= 0) 80 | return(i); 81 | wrote += i; 82 | } while (wrote 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "inotify_erlang.h" 8 | #include "erl_comm.h" 9 | 10 | #define BUFFER_SIZE 100 11 | 12 | int setup_select(note_t *nlist, fd_set *readfds) { 13 | int topfd, maxfd = 0; 14 | 15 | /* pselect initialisation */ 16 | FD_ZERO(readfds); 17 | FD_SET(0, readfds); 18 | 19 | topfd = note_setup_select(nlist, readfds); 20 | if (topfd > maxfd) 21 | maxfd = topfd; 22 | 23 | return maxfd; 24 | } 25 | 26 | int process_command(const char *command, char *buf, int *index, note_t *nlist) { 27 | ei_x_buff result; 28 | 29 | if (!strcmp("open", command)) { 30 | /* function open() */ 31 | note_open(nlist, buf, index); 32 | return(0); 33 | } 34 | 35 | if (!strcmp("add", command)) { 36 | /* function int = add(int fd, char *path, int mask) */ 37 | note_add(nlist, buf, index); 38 | return(0); 39 | } 40 | 41 | if (!strcmp("remove", command)) { 42 | /* function remove(fd, wd) */ 43 | note_remove(nlist, buf, index); 44 | return(0); 45 | } 46 | 47 | if (!strcmp("list", command)) { 48 | /* function list(nlist) */ 49 | note_list(nlist, buf, index); 50 | return(0); 51 | } 52 | 53 | if (!strcmp("close", command)) { 54 | /* function close */ 55 | note_close(nlist, buf, index); 56 | return(0); 57 | } 58 | 59 | /* Prepare the output buffer that will hold {ok, Result} or {error, Reason} */ 60 | if (ei_x_new_with_version(&result) || 61 | ei_x_encode_tuple_header(&result, 2) || 62 | ei_x_encode_atom(&result, "error") || 63 | ei_x_encode_atom(&result, "unsupported_command")) 64 | return(-1); 65 | 66 | write_cmd(&result); 67 | ei_x_free(&result); 68 | return(0); 69 | } 70 | 71 | int main() { 72 | /* variables for erlang interface */ 73 | int index, version, arity; 74 | int size = BUFFER_SIZE; 75 | int cmdpos, result; 76 | char *inbuf = NULL; 77 | char command[MAXATOMLEN]; 78 | 79 | /* varables for pselect */ 80 | int maxfd, retval; 81 | fd_set readfds; 82 | 83 | /* inotify initialisation */ 84 | note_t *nlist; 85 | if ((nlist = note_init()) == NULL) { 86 | perror("note_init()"); 87 | exit(1); 88 | 89 | } 90 | 91 | inbuf = (char*) malloc(size); 92 | if (inbuf == NULL) { 93 | perror("malloc()"); 94 | exit(1); 95 | } 96 | 97 | cmdpos = 0; 98 | maxfd = setup_select(nlist, &readfds); 99 | 100 | while ((retval = select(maxfd + 1, &readfds, NULL, NULL, NULL)) >= 0) { 101 | if FD_ISSET(0, &readfds) { 102 | memset(inbuf, 0, size); 103 | index = 0; 104 | result = read_cmd(&inbuf, &size, &cmdpos); 105 | 106 | if (result == 0) { 107 | free(inbuf); 108 | exit(1); 109 | } else if (result < 0) { 110 | /* exit(1); */ 111 | } else if (result == 1) { 112 | } else { 113 | 114 | /* must add two(2) to inbuf pointer to skip message length header */ 115 | if (ei_decode_version(inbuf+2, &index, &version) || 116 | ei_decode_tuple_header(inbuf+2, &index, &arity) || 117 | ei_decode_atom(inbuf+2, &index, command)) { 118 | free(inbuf); 119 | exit(4); 120 | } 121 | 122 | process_command(command, inbuf+2, &index, nlist); 123 | 124 | /* reset position of inbuf */ 125 | cmdpos = 0; 126 | } 127 | } 128 | /* check other file descriptors */ 129 | note_read(nlist, &readfds); 130 | 131 | maxfd = setup_select(nlist, &readfds); 132 | } 133 | free(inbuf); 134 | exit(10); 135 | } 136 | 137 | -------------------------------------------------------------------------------- /test/inotify_test.erl: -------------------------------------------------------------------------------- 1 | %%%============================================================================= 2 | %%% @author Sven Heyll 3 | %%% @copyright (C) 2013, Sven Heyll 4 | %%%============================================================================= 5 | 6 | -module(inotify_test). 7 | 8 | -export([inotify_event/3]). 9 | 10 | -include_lib("eunit/include/eunit.hrl"). 11 | -include("inotify.hrl"). 12 | 13 | %%%============================================================================= 14 | %%% TESTS 15 | %%%============================================================================= 16 | 17 | monitor_file_test() -> 18 | process_flag(trap_exit, true), 19 | file:delete("/tmp/inotify_test22222222222222222222222222222222222222222222222222222222222222222222222221232132132131232132132132132132132132213"), 20 | 21 | inotify:start(x,y), 22 | Ref1 = inotify:watch("/tmp/", ?ALL), 23 | ok = inotify:add_handler(Ref1, ?MODULE, self()), 24 | receive after 100 -> ok end, 25 | 26 | {ok, H} = file:open("/tmp/inotify_test22222222222222222222222222222222222222222222222222222222222222222222222221232132132131232132132132132132132132213", [read, write]), 27 | 28 | receive 29 | {[create],0,"inotify_test22222222222222222222222222222222222222222222222222222222222222222222222221232132132131232132132132132132132132213"} -> pass; 30 | M1 -> throw({expected_message_file_create_but_got, M1}) 31 | after 1000 -> throw(expected_create) 32 | end, 33 | receive 34 | {[open],0,"inotify_test22222222222222222222222222222222222222222222222222222222222222222222222221232132132131232132132132132132132132213"} -> pass; 35 | M2 -> throw({expected_message_file_open_but_got, M2}) 36 | after 1000 -> throw(expected_open) 37 | end, 38 | Ref2 = inotify:watch("/tmp/inotify_test22222222222222222222222222222222222222222222222222222222222222222222222221232132132131232132132132132132132132213"), 39 | inotify:unwatch(Ref1), 40 | ok = inotify:add_handler(Ref2, ?MODULE, self()), 41 | receive after 100 -> ok end, 42 | io:write(H, "test"), 43 | receive 44 | {[modify],0,""} -> pass; 45 | M3 -> throw({expected_message_file_modify_but_got, M3}) 46 | after 1000 -> throw(expected_modify) 47 | end, 48 | ok = file:close(H), 49 | receive 50 | {[close_write],0,""} -> pass; 51 | M4 -> throw({expected_message_file_close_write_but_got, M4}) 52 | after 1000 -> throw(expected_close_write) 53 | end, 54 | Ref3 = inotify:watch("/tmp/", ?ALL), 55 | ok = inotify:add_handler(Ref3, ?MODULE, self()), 56 | receive after 100 -> ok end, 57 | ok = file:delete("/tmp/inotify_test22222222222222222222222222222222222222222222222222222222222222222222222221232132132131232132132132132132132132213"), 58 | receive 59 | {[attrib],0,""} -> pass 60 | after 1000 -> 61 | receive X1 -> throw({expected, X1}) 62 | after 1000 -> throw(expected_attrib) 63 | end 64 | end, 65 | receive 66 | {[delete_self],0,""} -> pass 67 | after 1000 -> 68 | receive X2 -> throw({expected, X2}) 69 | after 1000 -> throw(expected_delete_self) 70 | end 71 | end, 72 | receive 73 | {[ignored],0,""} -> pass 74 | after 1000 -> 75 | receive X3 -> throw({expected, X3}) 76 | after 1000 -> throw(expected_ignored) 77 | end 78 | end, 79 | receive 80 | {[delete],0,"inotify_test22222222222222222222222222222222222222222222222222222222222222222222222221232132132131232132132132132132132132213"} -> pass 81 | after 1000 -> 82 | receive X4 -> throw({expected, X4}) 83 | after 1000 -> throw(expected_delete) 84 | end 85 | end. 86 | 87 | inotify_event(TestPid, Ref, Msg = ?inotify_msg(M, C, N)) -> 88 | io:format("$$$ ~p: ~p~n", [Ref, Msg]), 89 | TestPid ! {M, C, N}, 90 | ok. 91 | -------------------------------------------------------------------------------- /src/inotify_evt.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Sven Heyll 3 | %%% @copyright (C) 2013, Sven Heyll 4 | %%% @doc 5 | %%% Event manager for file monitoring events. 6 | %%% This is an internal module. 7 | %%% @end 8 | %%% Created : 1 Jan 2013 by Sven Heyll 9 | %%%------------------------------------------------------------------- 10 | -module(inotify_evt). 11 | 12 | -behaviour(gen_event). 13 | 14 | %% API 15 | -export([start_link/0, publish/2, add_handler/3, remove_all_handlers/1]). 16 | 17 | %% gen_event callbacks 18 | -export([init/1, handle_event/2, handle_call/2, 19 | handle_info/2, terminate/2, code_change/3]). 20 | 21 | -define(SERVER, ?MODULE). 22 | 23 | -include("inotify.hrl"). 24 | 25 | %%%=================================================================== 26 | %%% API 27 | %%%=================================================================== 28 | 29 | %%-------------------------------------------------------------------- 30 | %% @private 31 | %%-------------------------------------------------------------------- 32 | start_link() -> 33 | gen_event:start_link({local, ?SERVER}). 34 | 35 | %%-------------------------------------------------------------------- 36 | %% @private 37 | %%-------------------------------------------------------------------- 38 | publish(EventTag, Msg) -> 39 | gen_event:notify(?MODULE, {?MODULE, EventTag, Msg}). 40 | 41 | %%-------------------------------------------------------------------- 42 | %% @private 43 | %%-------------------------------------------------------------------- 44 | add_handler(EventTag, Module, Arg) -> 45 | gen_event:add_handler(?MODULE, 46 | {?MODULE, Arg}, 47 | {EventTag, Module, Arg}). 48 | 49 | %%-------------------------------------------------------------------- 50 | %% @private 51 | %%-------------------------------------------------------------------- 52 | remove_all_handlers(EventTag) -> 53 | gen_event:sync_notify(?MODULE, {remove_all_handlers, EventTag}). 54 | 55 | %%%=================================================================== 56 | %%% gen_event callbacks 57 | %%%=================================================================== 58 | 59 | %%-------------------------------------------------------------------- 60 | %% @private 61 | %%-------------------------------------------------------------------- 62 | init({EventTag, Module, Arg}) -> 63 | {ok, {EventTag, Module, Arg}}. 64 | 65 | %%-------------------------------------------------------------------- 66 | %% @private 67 | %%-------------------------------------------------------------------- 68 | handle_event({remove_all_handlers, EventTag}, 69 | {EventTag, _Module, _Arg}) -> 70 | remove_handler; 71 | 72 | handle_event({?MODULE, EventTag, Msg = ?inotify_msg(_, _, _)}, 73 | State = {EventTag, Module, Arg}) -> 74 | case Module:inotify_event(Arg, EventTag, Msg) of 75 | ok -> 76 | {ok, State}; 77 | remove_handler -> 78 | remove_handler 79 | end; 80 | 81 | handle_event(_Event, State) -> 82 | {ok, State}. 83 | 84 | %%-------------------------------------------------------------------- 85 | %% @private 86 | %%-------------------------------------------------------------------- 87 | handle_call(_Request, State) -> 88 | Reply = ok, 89 | {ok, Reply, State}. 90 | 91 | %%-------------------------------------------------------------------- 92 | %% @private 93 | %%-------------------------------------------------------------------- 94 | handle_info(_Info, State) -> 95 | {ok, State}. 96 | 97 | %%-------------------------------------------------------------------- 98 | %% @private 99 | %%-------------------------------------------------------------------- 100 | terminate(_Reason, _State) -> 101 | ok. 102 | 103 | %%-------------------------------------------------------------------- 104 | %% @private 105 | %%-------------------------------------------------------------------- 106 | code_change(_OldVsn, State, _Extra) -> 107 | {ok, State}. 108 | 109 | %%%=================================================================== 110 | %%% Internal functions 111 | %%%=================================================================== 112 | -------------------------------------------------------------------------------- /src/inotify_server.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% Created : 28 Jan 2010 by Mats Cronqvist 3 | %%% Converted to an OTP application by Sven Heyll 4 | %%% 5 | %%% @doc 6 | %%% A `gen_server' that provides access to low level Linux `inotify'. 7 | %%% This is an internal module. Use {@link inotify} instead. 8 | %%% @end 9 | %%%------------------------------------------------------------------- 10 | 11 | -module(inotify_server). 12 | 13 | -behaviour(gen_server). 14 | 15 | -author('Mats Cronqvist'). 16 | -author('Sven Heyll'). 17 | 18 | %% API 19 | -export([start_link/0, 20 | watch/2, 21 | watch/3, 22 | unwatch/1]). 23 | 24 | %% gen_server exports 25 | -export([init/1, 26 | terminate/2, 27 | handle_cast/2, 28 | handle_info/2, 29 | handle_call/3, 30 | code_change/3]). 31 | 32 | -define(SERVER, ?MODULE). 33 | 34 | -include("inotify.hrl"). 35 | 36 | -record(ld,{port, fd}). 37 | 38 | -define(log(T), 39 | error_logger:info_report( 40 | [process_info(self(), current_function), 41 | {line, ?LINE}, 42 | T])). 43 | 44 | %%%=================================================================== 45 | %%% API 46 | %%%=================================================================== 47 | 48 | %%-------------------------------------------------------------------- 49 | %% @private 50 | %%-------------------------------------------------------------------- 51 | start_link() -> 52 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 53 | 54 | %%-------------------------------------------------------------------- 55 | %% @private 56 | %%-------------------------------------------------------------------- 57 | watch(File, EventTag) -> 58 | gen_server:call(?SERVER, {watch, {File, EventTag, ?ALL}}). 59 | 60 | %%-------------------------------------------------------------------- 61 | %% @private 62 | %%-------------------------------------------------------------------- 63 | watch(File, EventTag, Mask) -> 64 | gen_server:call(?SERVER, {watch, {File, EventTag, Mask}}). 65 | 66 | %%-------------------------------------------------------------------- 67 | %% @private 68 | %%-------------------------------------------------------------------- 69 | unwatch(EventTag) -> 70 | gen_server:cast(?SERVER, {unwatch, EventTag}). 71 | 72 | %%%=================================================================== 73 | %%% gen_server callbacks 74 | %%%=================================================================== 75 | 76 | %%-------------------------------------------------------------------- 77 | %% @private 78 | %%-------------------------------------------------------------------- 79 | init([]) -> 80 | process_flag(trap_exit, true), 81 | Port = open_port(), 82 | {ok, FD} = talk_to_port(Port , {open}), 83 | {ok, #ld{port = Port, fd = FD}}. 84 | 85 | %%-------------------------------------------------------------------- 86 | %% @private 87 | %%-------------------------------------------------------------------- 88 | terminate(_, LD) -> 89 | talk_to_port(LD#ld.port, {close, LD#ld.fd}). 90 | 91 | %%-------------------------------------------------------------------- 92 | %% @private 93 | %%-------------------------------------------------------------------- 94 | handle_info({Port, {data,Msg}}, LD = #ld{port = Port}) -> 95 | maybe_call_back(binary_to_term(Msg)), 96 | {noreply, LD}; 97 | 98 | handle_info(Msg, LD) -> 99 | ?log({unknown_message, Msg}), 100 | {noreply, LD}. 101 | 102 | %%-------------------------------------------------------------------- 103 | %% @private 104 | %%-------------------------------------------------------------------- 105 | handle_cast(stop, LD) -> 106 | {stop,normal, LD}; 107 | 108 | handle_cast({unwatch, Unwatch}, LD) -> 109 | {noreply, do_unwatch(Unwatch, LD)}; 110 | 111 | handle_cast(Msg, LD) -> 112 | ?log({unknown_message, Msg}), 113 | {noreply, LD}. 114 | 115 | %%-------------------------------------------------------------------- 116 | %% @private 117 | %%-------------------------------------------------------------------- 118 | handle_call({watch,Watch},_From,LD) -> 119 | {Reply, NewLD} = do_watch(Watch, LD), 120 | {reply, Reply, NewLD}. 121 | 122 | %%-------------------------------------------------------------------- 123 | %% @private 124 | %%-------------------------------------------------------------------- 125 | code_change(_OldVsn, State, _Extra) -> 126 | {ok, State}. 127 | 128 | %%%=================================================================== 129 | %%% Internal functions 130 | %%%=================================================================== 131 | 132 | %%-------------------------------------------------------------------- 133 | %% @private 134 | %%-------------------------------------------------------------------- 135 | open_port() -> 136 | E = filename:join([code:priv_dir(inotify), "inotify"]), 137 | open_port({spawn, E},[{packet, 2}, binary, exit_status]). 138 | 139 | %%-------------------------------------------------------------------- 140 | %% @private 141 | %%-------------------------------------------------------------------- 142 | maybe_call_back({event, WD, Mask, Cookie, Name}) -> 143 | case get({wd, WD}) of 144 | undefined -> 145 | case Mask of 146 | [ignored] -> 147 | ok; 148 | _ -> 149 | ?log([{got_event_without_callback, WD, Mask, Cookie, Name}]) 150 | end; 151 | Tag -> 152 | inotify_evt:publish(Tag, ?inotify_msg(Mask, Cookie, Name)) 153 | end. 154 | 155 | %%-------------------------------------------------------------------- 156 | %% @private 157 | %%-------------------------------------------------------------------- 158 | do_watch({File, Tag, Mask},LD) -> 159 | try 160 | {ok, WD} = talk_to_port(LD#ld.port, {add, LD#ld.fd, File, Mask}), 161 | put({tag, Tag}, WD), 162 | put({wd, WD}, Tag), 163 | {ok, LD} 164 | catch 165 | C:R -> 166 | ?log([{error_watching_file, File, Tag}, {C, R}]), 167 | {{error, C, R}, LD} 168 | end. 169 | 170 | %%-------------------------------------------------------------------- 171 | %% @private 172 | %%-------------------------------------------------------------------- 173 | do_unwatch(Tag, LD) -> 174 | case get({tag, Tag}) of 175 | undefined -> 176 | ?log([{not_watching, Tag}]), 177 | LD; 178 | WD -> 179 | try 180 | talk_to_port(LD#ld.port, {remove, LD#ld.fd, WD}) 181 | catch 182 | C:R -> 183 | ?log([{error_unwatching, Tag},{C, R}]) 184 | end, 185 | erase({tag, Tag}), 186 | erase({wd, WD}), 187 | LD 188 | end. 189 | 190 | %%-------------------------------------------------------------------- 191 | %% @private 192 | %%-------------------------------------------------------------------- 193 | talk_to_port(Port,Msg) -> 194 | try 195 | erlang:port_command(Port, term_to_binary(Msg)), 196 | receive 197 | {Port, {data, D = <<131, 104, 2, _/binary>>}} -> 198 | binary_to_term(D) 199 | after 1000 -> 200 | throw(talk_to_port_timeout) 201 | end 202 | catch 203 | _:R -> 204 | throw({talking_to_port_failed, {R, Port, Msg}}) 205 | end. 206 | -------------------------------------------------------------------------------- /src/inotify.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% Created : 28 Jan 2010 by Mats Cronqvist 3 | %%% Converted to an OTP application by Sven Heyll 4 | %%% 5 | %%% @doc 6 | %%% Application that monitors files via Linux `inotify'. A user can tell 7 | %%% `inotify' to {@link watch/2} for specific events on several 8 | %%% files/directories. To actually get any events the User must call {@link 9 | %%% add_handler/3}. 10 | %%% 11 | %%% To {@link unwatch/1} something the User must pass the return value of {@link 12 | %%% watch/2} which is a unique reference to the monitor create by {@link 13 | %%% watch/2}. 14 | %%% 15 | %%% This module also defines a behaviour, with a single function 16 | %%% `inotify_event(Arg, EventRef, Msg)' 17 | %%% 18 | %%% A callback for file monitoring event handler added by {@link add_handler/3}. 19 | %%% 20 | %%% `Arg' is the user provided extra argument. 21 | %%% 22 | %%% `EventRef' is the value returned by {@link watch/2}. 23 | %%% 24 | %%% The `Msg' parameter of the callback function should be pattern-matched 25 | %%% with the macro `?inotify_msg(Mask, Cookie, OptionalName)' contained 26 | %%% in `include/inotify.hrl'. 27 | %%% 28 | %%% `Mask' is a list of atoms describing the nature of the event. Refer to the 29 | %%% Linux inotify man page for a detailed description. 30 | %%% 31 | %%% `Cookie' is 0 except when a file is moved, where it is used to identify 32 | %%% `move_from' and `move_to' events belonging to the same move-operation. 33 | %%% 34 | %%% `OptionalName' contains the relative file name when monitoring whole 35 | %%% directories. E.g. when monitoring "/tmp" the following event might be 36 | %%% created when opening "/tmp/xxx": `?inotify_msg([open], 0, "xxx")'. 37 | %%% @end 38 | %%%------------------------------------------------------------------- 39 | 40 | -module(inotify). 41 | 42 | -behaviour(application). 43 | -behaviour(supervisor). 44 | 45 | %% API 46 | -export([watch/1, watch/2, unwatch/1, add_handler/3, print_events/1]). 47 | 48 | %% Application callbacks 49 | -export([start/2, stop/1]). 50 | 51 | %% Supervisor callbacks 52 | -export([init/1]). 53 | 54 | %% inotify_evt callbacks 55 | -export([inotify_event/3]). 56 | 57 | -export_type([mask/0, msg/0]). 58 | 59 | -define(SERVER, inotify_server). 60 | -define(EVENT, inotify_evt). 61 | 62 | -include("inotify.hrl"). 63 | 64 | -type msg() :: ?inotify_msg(Mask :: [mask()], 65 | Cookie :: non_neg_integer(), 66 | OptionalName :: string()). 67 | %% A file monitoring message. 68 | 69 | -type mask() :: ?ALL | 70 | ?ACCESS | 71 | ?ATTRIB | 72 | ?CLOSE_WRITE | 73 | ?CLOSE_NOWRITE | 74 | ?CLOSE | 75 | ?CREATE | 76 | ?DELETE | 77 | ?DELETE_SELF | 78 | ?MODIFY | 79 | ?MOVE_SELF | 80 | ?MOVED_FROM | 81 | ?MOVED_TO | 82 | ?MOVE | 83 | ?OPEN | 84 | ?DONT_FOLLOW | 85 | ?MASK_ADD | 86 | ?ONLYDIR. 87 | %% The type of a file monitoring event. 88 | 89 | %%%=================================================================== 90 | %%% Behaviour definition 91 | %%%=================================================================== 92 | 93 | %%-------------------------------------------------------------------- 94 | %% FUCK YOU EDOC! 95 | %%-------------------------------------------------------------------- 96 | -callback inotify_event(Arg :: term(), 97 | EventRef :: term(), 98 | Msg :: inotify:msg()) -> 99 | ok | remove_handler. 100 | 101 | %%%=================================================================== 102 | %%% API 103 | %%%=================================================================== 104 | 105 | %%-------------------------------------------------------------------- 106 | %% @doc 107 | %% Add or modify a file/directory monitor for all file events. Events for the 108 | %% file/directory will be published via a {@link inotify_evt}. The events 109 | %% resulting from this call will be identified by the unique reference returned 110 | %% by this function, no matter if the operation was successful or not. 111 | %% 112 | %% This reference must also be passed to {@link unwatch/1}. 113 | %% @see inotify:add_handler/3 114 | %% @see inotify:unwatch/1 115 | %% @end 116 | %%-------------------------------------------------------------------- 117 | -spec watch(string()) -> 118 | reference(). 119 | watch(File) -> 120 | watch(File, ?ALL). 121 | 122 | %%-------------------------------------------------------------------- 123 | %% @doc 124 | %% Add or modify a file/directory monitor for specific events. This 125 | %% differs from {@link watch/1} by the additional `mask' argument. 126 | %% Calling {@link watch/1} is equivalant to calling {@link watch/2} 127 | %% with `?ALL' as second parameter. 128 | %% 129 | %% @see inotify:watch/1 130 | %% @end 131 | %%-------------------------------------------------------------------- 132 | -spec watch(string(), mask() | [mask()]) -> 133 | reference(). 134 | watch(File, Mask) -> 135 | EventTag = make_ref(), 136 | ok = inotify_server:watch(File, EventTag, Mask), 137 | EventTag. 138 | 139 | %%-------------------------------------------------------------------- 140 | %% @doc 141 | %% Remove a monitor added via {@link watch/2}, `Ref' identifies the 142 | %% monitor. 143 | %% @end 144 | %%-------------------------------------------------------------------- 145 | -spec unwatch(reference()) -> 146 | ok. 147 | unwatch(Ref) -> 148 | inotify_evt:remove_all_handlers(Ref), 149 | inotify_server:unwatch(Ref). 150 | 151 | %%-------------------------------------------------------------------- 152 | %% @doc 153 | %% Add an event handler that receives all events generated by a file monitor 154 | %% identified by a tag that was passed to {@link inotify:watch/2}. When an event 155 | %% occurs for `Ref' the handler will call `Module:inotify_event(Arg, 156 | %% EventTag, Msg)' as defined by the `callback' definition in {@link 157 | %% inotify_event/3}. 158 | %% @end 159 | %%-------------------------------------------------------------------- 160 | -spec add_handler(reference(), module(), term()) -> 161 | ok. 162 | add_handler(Ref, Module, Arg) -> 163 | inotify_evt:add_handler(Ref, Module, Arg). 164 | 165 | %%-------------------------------------------------------------------- 166 | %% @doc 167 | %% Add an event handler that prints to stdout events. The events to print are 168 | %% identified by a reference returned by {@link watch/2}. 169 | %% @end 170 | %%-------------------------------------------------------------------- 171 | -spec print_events(reference()) -> 172 | ok. 173 | print_events(Ref) -> 174 | inotify_evt:add_handler(Ref, ?MODULE, []). 175 | 176 | 177 | %%%=================================================================== 178 | %%% Application callbacks 179 | %%%=================================================================== 180 | 181 | %%-------------------------------------------------------------------- 182 | %% @private 183 | %%-------------------------------------------------------------------- 184 | start(_StartType, _StartArg) -> 185 | supervisor:start_link(?MODULE, []). 186 | 187 | %%-------------------------------------------------------------------- 188 | %% @private 189 | %%-------------------------------------------------------------------- 190 | stop(_) -> 191 | ok. 192 | 193 | %%%=================================================================== 194 | %%% supervisor callbacks 195 | %%%=================================================================== 196 | 197 | %%-------------------------------------------------------------------- 198 | %% @private 199 | %%-------------------------------------------------------------------- 200 | init([]) -> 201 | {ok, {{one_for_all, 1, 1}, 202 | [{?EVENT, {?EVENT, start_link, []}, 203 | permanent, 2000, worker, [?EVENT]}, 204 | {?SERVER, {?SERVER, start_link, []}, 205 | permanent, 2000, worker, [?SERVER]}]}}. 206 | 207 | %%%=================================================================== 208 | %%% inotify_evt callbacks 209 | %%%=================================================================== 210 | 211 | %%-------------------------------------------------------------------- 212 | %% @doc 213 | %% This module actually implements the {@link inotify_evt} behaviour, with a 214 | %% function that simply prints the events to stdout. 215 | %% 216 | %% This function can be used as an example for how to implement a callback. 217 | %% 218 | %% @see print_events/1 219 | %% @end 220 | %%-------------------------------------------------------------------- 221 | inotify_event([], Ref, ?inotify_msg(Masks, Cookie, OptionalName)) -> 222 | io:format("[INOTIFY] - ~p - ~p ~p ~p~n", [Ref, Masks, Cookie, OptionalName]). 223 | -------------------------------------------------------------------------------- /c_src/inotify_erlang.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "inotify_erlang.h" 11 | #include "erl_comm.h" 12 | 13 | #define EVENT_SIZE (sizeof (struct inotify_event)) 14 | #define BUF_LEN (1024 * (EVENT_SIZE + 16)) 15 | #define MSG_LEN (EVENT_SIZE + 16) 16 | 17 | /*********************** note_send_errno **********/ 18 | int note_send_errno() { 19 | ei_x_buff errmsg; 20 | 21 | if (ei_x_new_with_version(&errmsg) || 22 | ei_x_encode_tuple_header(&errmsg, 2)) 23 | return(-1); 24 | ei_x_encode_atom(&errmsg, "error"); /* element 1 */ 25 | switch (errno) { 26 | case EACCES: 27 | ei_x_encode_atom(&errmsg, "eacces"); 28 | break; 29 | case EBADF: 30 | ei_x_encode_atom(&errmsg, "ebadf"); 31 | break; 32 | case EFAULT: 33 | ei_x_encode_atom(&errmsg, "efault"); 34 | break; 35 | case EINVAL: 36 | ei_x_encode_atom(&errmsg, "einval"); 37 | break; 38 | case ENOMEM: 39 | ei_x_encode_atom(&errmsg, "enomem"); 40 | break; 41 | case ENOSPC: 42 | ei_x_encode_atom(&errmsg, "enospc"); 43 | break; 44 | case ENOTTY: 45 | ei_x_encode_atom(&errmsg, "enotty"); 46 | break; 47 | default: 48 | ei_x_encode_atom(&errmsg, "unknown_errno"); 49 | break; 50 | } 51 | write_cmd(&errmsg); 52 | ei_x_free(&errmsg); 53 | 54 | return 0; 55 | } 56 | 57 | /*********************** note_init ****************/ 58 | note_t *note_init() { 59 | note_t *note; 60 | 61 | if ((note = calloc(1, sizeof(note_t))) == NULL) { 62 | return(NULL); 63 | } 64 | 65 | return(note); 66 | } 67 | 68 | /*********************** note_encode_mask ***********/ 69 | void note_encode_mask(ei_x_buff *outp, ulong mask) { 70 | /* debugging: fprintf(stderr, "note_encode_mask %ld\r\n", mask); */ 71 | if (mask & IN_ACCESS) { 72 | ei_x_encode_list_header(outp,1); 73 | ei_x_encode_atom(outp, "access"); 74 | } 75 | if (mask & IN_ATTRIB) { 76 | ei_x_encode_list_header(outp,1); 77 | ei_x_encode_atom(outp, "attrib"); 78 | } 79 | if (mask & IN_CLOSE_WRITE) { 80 | ei_x_encode_list_header(outp,1); 81 | ei_x_encode_atom(outp, "close_write"); 82 | } 83 | if (mask & IN_CLOSE_NOWRITE) { 84 | ei_x_encode_list_header(outp,1); 85 | ei_x_encode_atom(outp, "close_nowrite"); 86 | } 87 | if (mask & IN_CREATE) { 88 | ei_x_encode_list_header(outp,1); 89 | ei_x_encode_atom(outp, "create"); 90 | } 91 | if (mask & IN_DELETE) { 92 | ei_x_encode_list_header(outp,1); 93 | ei_x_encode_atom(outp, "delete"); 94 | } 95 | if (mask & IN_DELETE_SELF) { 96 | ei_x_encode_list_header(outp,1); 97 | ei_x_encode_atom(outp, "delete_self"); 98 | } 99 | if (mask & IN_MODIFY) { 100 | ei_x_encode_list_header(outp,1); 101 | ei_x_encode_atom(outp, "modify"); 102 | } 103 | if (mask & IN_MOVE_SELF) { 104 | ei_x_encode_list_header(outp,1); 105 | ei_x_encode_atom(outp, "move_self"); 106 | } 107 | if (mask & IN_MOVED_FROM) { 108 | ei_x_encode_list_header(outp,1); 109 | ei_x_encode_atom(outp, "move_from"); 110 | } 111 | if (mask & IN_MOVED_TO) { 112 | ei_x_encode_list_header(outp,1); 113 | ei_x_encode_atom(outp, "move_to"); 114 | } 115 | if (mask & IN_OPEN) { 116 | ei_x_encode_list_header(outp,1); 117 | ei_x_encode_atom(outp, "open"); 118 | } 119 | if (mask & IN_IGNORED) { 120 | ei_x_encode_list_header(outp,1); 121 | ei_x_encode_atom(outp, "ignored"); 122 | } 123 | if (mask & IN_ISDIR) { 124 | ei_x_encode_list_header(outp,1); 125 | ei_x_encode_atom(outp, "isdir"); 126 | } 127 | if (mask & IN_Q_OVERFLOW) { 128 | ei_x_encode_list_header(outp,1); 129 | ei_x_encode_atom(outp, "q_overflow"); 130 | } 131 | if (mask & IN_UNMOUNT) { 132 | ei_x_encode_list_header(outp,1); 133 | ei_x_encode_atom(outp, "unmount"); 134 | } 135 | 136 | ei_x_encode_empty_list(outp); 137 | }; 138 | 139 | int note_decode_mask_atom(const char *atom, ulong *maskout) { 140 | if (!strcmp("all", atom)) { 141 | *maskout |= IN_ALL_EVENTS; 142 | } else if (!strcmp("access", atom)) { 143 | *maskout |= IN_ACCESS; 144 | } else if (!strcmp("attrib", atom)) { 145 | *maskout |= IN_ATTRIB; 146 | } else if (!strcmp("close_write", atom)) { 147 | *maskout |= IN_CLOSE_WRITE; 148 | } else if (!strcmp("close_nowrite", atom)) { 149 | *maskout |= IN_CLOSE_NOWRITE; 150 | } else if (!strcmp("close", atom)) { 151 | *maskout |= IN_CLOSE; 152 | } else if (!strcmp("create", atom)) { 153 | *maskout |= IN_CREATE; 154 | } else if (!strcmp("delete", atom)) { 155 | *maskout |= IN_DELETE; 156 | } else if (!strcmp("delete_self", atom)) { 157 | *maskout |= IN_DELETE_SELF; 158 | } else if (!strcmp("modify", atom)) { 159 | *maskout |= IN_MODIFY; 160 | } else if (!strcmp("move_self", atom)) { 161 | *maskout |= IN_MOVE_SELF; 162 | } else if (!strcmp("moved_from", atom)) { 163 | *maskout |= IN_MOVED_FROM; 164 | } else if (!strcmp("moved_to", atom)) { 165 | *maskout |= IN_MOVED_TO; 166 | } else if (!strcmp("move", atom)) { 167 | *maskout |= IN_MOVE; 168 | } else if (!strcmp("open", atom)) { 169 | *maskout |= IN_OPEN; 170 | } else if (!strcmp("dont_follow", atom)) { 171 | *maskout |= IN_DONT_FOLLOW; 172 | } else if (!strcmp("mask_add", atom)) { 173 | *maskout |= IN_MASK_ADD; 174 | } else if (!strcmp("onlydir", atom)) { 175 | *maskout |= IN_ONLYDIR; 176 | } else { 177 | return(-1); 178 | } 179 | return(0); 180 | } 181 | /*********************** note_decode_mask ***********/ 182 | int note_decode_mask(char *buf, int *index, ulong *maskout) { 183 | int arity, count; 184 | int termtype, size; 185 | char mstr[100]; 186 | 187 | ei_get_type(buf, index, &termtype, &size); 188 | if (termtype == ERL_ATOM_EXT) { 189 | if (ei_decode_atom(buf, index, mstr) < 0) 190 | return(-1); 191 | 192 | if (note_decode_mask_atom(mstr, maskout) < 0) 193 | return(-1); 194 | } else if (termtype == ERL_LIST_EXT) { 195 | if (ei_decode_list_header(buf, index, &arity) < 0) { 196 | return(-1); 197 | } 198 | 199 | if (arity == 0) { /* empty list */ 200 | return(-1); 201 | } 202 | 203 | for(count = 0; count < arity; count++) { 204 | ei_get_type(buf, index, &termtype, &size); 205 | if (termtype == ERL_ATOM_EXT) { 206 | if (ei_decode_atom(buf, index, mstr) < 0) 207 | return(-1); 208 | 209 | if (note_decode_mask_atom(mstr, maskout) < 0) 210 | return(-1); 211 | } 212 | } 213 | } else { 214 | /* another type which is not of interest */ 215 | return(-1); 216 | } 217 | return(0); 218 | } 219 | /*********************** note_setup_select ***********/ 220 | int note_setup_select(note_t *note, fd_set *readfds) { 221 | note_entry_t *cur; 222 | int maxfd = 0; 223 | 224 | for(cur = note->ent; cur != NULL; cur = cur->next) { 225 | FD_SET(cur->fd, readfds); 226 | if (cur->fd > maxfd) 227 | maxfd = cur->fd; 228 | } 229 | return maxfd; 230 | } 231 | 232 | /*********************** note_destroy ****************/ 233 | void note_destroy(note_t *note) { 234 | note_entry_t *cur, *prev; 235 | 236 | cur = note->ent; 237 | while (cur != NULL) { 238 | close(cur->fd); 239 | prev = cur; 240 | cur = cur->next; 241 | free(prev); 242 | } 243 | free(note); 244 | } 245 | 246 | /*********************** note_read **************** 247 | * 248 | * 249 | */ 250 | int note_read_send(int len, char *buf) { 251 | int idx = 0; 252 | ei_x_buff result; 253 | 254 | while (idx < len) { 255 | struct inotify_event *event; 256 | event = (struct inotify_event *) &buf[idx]; 257 | 258 | /* encoding msg */ 259 | /* Output buffer that will hold {event, Wd, Mask, Cookie, Name} */ 260 | if (ei_x_new_with_version(&result) || 261 | ei_x_encode_tuple_header(&result, 5)) return(-1); 262 | 263 | /* 264 | fprintf(stderr, 265 | "inotify_erlang:note_read len: %d idx: %d event %d %x %x %s\r\n", 266 | len, idx, event->wd, event->mask, event->cookie, event->name); 267 | */ 268 | 269 | ei_x_encode_atom(&result, "event"); /* element 1 */ 270 | ei_x_encode_ulong(&result, event->wd); /* element 2 */ 271 | note_encode_mask(&result, event->mask); /* element 3 */ 272 | ei_x_encode_ulong(&result, event->cookie); /* element 4 */ 273 | if ( 0 < event->len ) { 274 | ei_x_encode_string(&result, event->name); /* element 5 */ 275 | }else{ 276 | ei_x_encode_string(&result, ""); 277 | } 278 | 279 | write_cmd(&result); 280 | ei_x_free(&result); 281 | idx += EVENT_SIZE + event->len; 282 | } 283 | return 0; 284 | } 285 | 286 | /*********************** note_read **************** 287 | * sends messages of format 288 | * EVENT_MSG, size, event 289 | * 290 | */ 291 | int note_read(note_t *note, fd_set *readfds) { 292 | char buf[BUF_LEN]; 293 | int len, rc, fd = 0; 294 | note_entry_t *cur; 295 | 296 | for(cur = note->ent; cur != NULL; cur = cur->next) { 297 | if (FD_ISSET(cur->fd, readfds)) { 298 | fd = cur->fd; 299 | 300 | memset(buf, 0, BUF_LEN); 301 | rc = ioctl(fd, FIONREAD, &len ); 302 | 303 | if (rc != 0) 304 | /* send error code to erlang */ 305 | note_send_errno(); 306 | 307 | if (len <= 0) 308 | continue; 309 | 310 | /* FIXME: potential buffer over flow bug here */ 311 | read(fd, buf, len); 312 | 313 | if (len < 0) { 314 | perror("note_read read()"); 315 | } else if (len > 0) { 316 | if (note_read_send(len, buf) < 0) 317 | return(-1); 318 | } 319 | } 320 | } 321 | return(0); 322 | } 323 | 324 | /*********************** note_list ****************/ 325 | int note_list(note_t *note, char *buf, int *index) { 326 | note_entry_t *cur; 327 | ei_x_buff result; 328 | 329 | /* Output buffer that will hold {ok, Result} or {error, Reason} */ 330 | if (ei_x_new_with_version(&result) || 331 | ei_x_encode_tuple_header(&result, 2)) return(-1); 332 | ei_x_encode_atom(&result, "ok"); 333 | 334 | for (cur = note->ent; cur != NULL; cur = cur->next) { 335 | ei_x_encode_list_header(&result,1); 336 | ei_x_encode_ulong(&result, cur->fd); 337 | } ei_x_encode_empty_list(&result); 338 | write_cmd(&result); 339 | ei_x_free(&result); 340 | return(0); 341 | } 342 | 343 | /*********************** note_open **************** 344 | * buffer format is 345 | * {} 346 | * 347 | * returns 348 | * {ok, Fd} 349 | * {error, Errno} 350 | */ 351 | int note_open(note_t *note, char *buf, int *count) { 352 | uint32_t fd; 353 | note_entry_t *newent; 354 | ei_x_buff result; 355 | 356 | /* Output buffer that will hold {ok, Result} or {error, Reason} */ 357 | if (ei_x_new_with_version(&result) || 358 | ei_x_encode_tuple_header(&result, 2)) return(-1); 359 | 360 | if ((newent = (note_entry_t *)calloc(1,sizeof(note_entry_t))) == NULL) { 361 | if (ei_x_encode_atom(&result, "error") || 362 | ei_x_encode_atom(&result, "alloc_failed")) 363 | return(-1); 364 | write_cmd(&result); 365 | ei_x_free(&result); 366 | return(0); 367 | } 368 | 369 | if ((fd = inotify_init()) < 0) { 370 | free(newent); 371 | if (ei_x_encode_atom(&result, "error") || 372 | ei_x_encode_atom(&result, "inotify_init")) 373 | return(-1); 374 | write_cmd(&result); 375 | ei_x_free(&result); 376 | return(0); 377 | } 378 | 379 | newent->fd = fd; 380 | newent->next = note->ent; 381 | note->ent = newent; 382 | 383 | 384 | if (ei_x_encode_atom(&result, "ok") || 385 | ei_x_encode_ulong(&result, fd)) return(-1); 386 | 387 | write_cmd(&result); 388 | ei_x_free(&result); 389 | return(0); 390 | } 391 | /*********************** note_close ****************/ 392 | int note_close(note_t *note, char *buf, int *count) { 393 | note_entry_t **curpp, *nextp; 394 | ulong fd; 395 | ei_x_buff result; 396 | 397 | if (ei_decode_ulong(buf, count, &fd)) return(-1); 398 | 399 | for(curpp = &(note->ent); *curpp != NULL; curpp = &(*curpp)->next) { 400 | if (fd == (*curpp)->fd) { 401 | close(fd); 402 | nextp = (*curpp)->next; 403 | free(*curpp); 404 | (*curpp) = nextp; 405 | /* Output buffer that will hold {ok, Result} or {error, Reason} */ 406 | if (ei_x_new_with_version(&result) || 407 | ei_x_encode_tuple_header(&result, 2)) return(-1); 408 | if (ei_x_encode_atom(&result, "ok") || 409 | ei_x_encode_ulong(&result, fd)) return(-1); 410 | 411 | write_cmd(&result); 412 | ei_x_free(&result); 413 | return(0); 414 | } 415 | } 416 | 417 | if (ei_x_new_with_version(&result) || 418 | ei_x_encode_tuple_header(&result, 2)) return(-1); 419 | if (ei_x_encode_atom(&result, "error") || 420 | ei_x_encode_atom(&result, "unknown")) return(-1); 421 | 422 | write_cmd(&result); 423 | ei_x_free(&result); 424 | return(0); 425 | } 426 | 427 | /*********************** note_add **************** 428 | * buffer format is 429 | * {ulong Fd, string Pathname, ulong Mask} 430 | * 431 | * returns 432 | * {ok, Wd} 433 | * {error, Errstr} 434 | * 435 | */ 436 | int note_add(note_t *note, char *buf, int *index) { 437 | ulong fd, mask; 438 | int wd; 439 | char pathname[512]; 440 | ei_x_buff result; 441 | 442 | /* Output buffer that will hold {ok, Result} or {error, Reason} */ 443 | if (ei_x_new_with_version(&result) || 444 | ei_x_encode_tuple_header(&result, 2)) return(-1); 445 | 446 | if (ei_decode_ulong(buf, index, &fd) || 447 | ei_decode_string(buf, index, pathname)) return(-1); 448 | 449 | mask = 0; 450 | if (note_decode_mask(buf, index, &mask) < 0) { 451 | if (ei_x_encode_atom(&result, "error") || 452 | ei_x_encode_atom(&result, "bad_mask")) 453 | return(-1); 454 | write_cmd(&result); 455 | ei_x_free(&result); 456 | return(0); 457 | } 458 | 459 | if ((wd = inotify_add_watch(fd, pathname, mask)) < 0) { 460 | return note_send_errno(); 461 | } 462 | 463 | if (ei_x_encode_atom(&result, "ok") || 464 | ei_x_encode_ulong(&result, wd)) return(-1); 465 | write_cmd(&result); 466 | ei_x_free(&result); 467 | return(0); 468 | } 469 | 470 | /*********************** note_remove **************** 471 | * buffer format is 472 | * {ulong fd, ulong wd} 473 | * 474 | * returns 475 | * {ok,wd} 476 | * {error, errno} 477 | */ 478 | int note_remove(note_t *note, char *buf, int *index) { 479 | ulong fd, wd; 480 | ei_x_buff result; 481 | 482 | if (ei_decode_ulong(buf, index, &fd) || 483 | ei_decode_ulong(buf, index, &wd)) return(-1); 484 | 485 | 486 | if (inotify_rm_watch(fd, wd) < 0) { 487 | /* Output buffer that will hold {error, Reason} or {ok, Wd}*/ 488 | return note_send_errno(); 489 | } 490 | 491 | if (ei_x_new_with_version(&result) || 492 | ei_x_encode_tuple_header(&result, 2)) return(-1); 493 | if (ei_x_encode_atom(&result, "ok") || 494 | ei_x_encode_ulong(&result, wd)) return(-1); 495 | write_cmd(&result); 496 | ei_x_free(&result); 497 | return(0); 498 | } 499 | --------------------------------------------------------------------------------