aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCamil Staps2016-05-21 14:04:49 +0200
committerCamil Staps2016-05-21 14:04:49 +0200
commit25cc129876ec0e556d409a0f50454eb400d3a54b (patch)
tree27dfb315f3caeeee1d08d9e7bcaf2b48695d2288
parentAlso pass event->name through; inotify_loop_forever (diff)
Documentation; inotify_is_event; timeout for polling
-rw-r--r--Inotify.dcl71
-rw-r--r--Inotify.icl35
-rw-r--r--inotify_c.c60
-rw-r--r--test.icl23
-rw-r--r--test.prj2
-rw-r--r--test_reload.icl7
6 files changed, 167 insertions, 31 deletions
diff --git a/Inotify.dcl b/Inotify.dcl
index 3811abc..c3a3e05 100644
--- a/Inotify.dcl
+++ b/Inotify.dcl
@@ -3,27 +3,84 @@ definition module Inotify
from Data.Either import ::Either
from Data.Maybe import ::Maybe
+// Inotify file descriptor
:: *Inotify st
+// Inotify watch descriptor
:: INWatch
+// Inotify event mask
:: INMask :== Int
+// Inotify event
:: INEvent :== Int
+
+// Inotify callback: event, maybe filename, state, world -> state, world
:: INCallback st :== INEvent (Maybe String) st *World -> *(st, *World)
(|-) infixl 6 :: (INMask INMask -> INMask)
+/* Initialise an inotify file descriptor with some state */
inotify_init :: st -> Maybe *(Inotify st)
+
+/* Close an inotify file descriptor and get back the state */
inotify_close :: *(Inotify st) -> st
-inotify_add_watch :: (INCallback st) !Int !String !*(Inotify st)
- -> *(Either Int INWatch, *Inotify st)
-inotify_rm_watch :: !INWatch !*(Inotify st) -> *(Bool, *Inotify st)
+/**
+ * Add a watch on some file
+ *
+ * INCallback st: the callback for events
+ * INMask: a mask of events to watch for
+ * String: the filename
+ * Inotify st: the inotify file descriptor
+ *
+ * Either Int INWatch: either an error code or a watch descriptor
+ * Inotify st: the new file descriptor
+ */
+inotify_add_watch :: (INCallback st) !INMask !String !*(Inotify st)
+ -> *(!Either Int INWatch, !*Inotify st)
+
+/**
+ * Remove a watch
+ *
+ * INWatch: the watch to remove
+ * Inotify st: the inotify file descriptor
+ *
+ * Bool: success
+ * Inotify st: the new file descriptor
+ */
+inotify_rm_watch :: !INWatch !*(Inotify st) -> *(!Bool, !*Inotify st)
+
+/**
+ * Poll an inotify file descriptor; i.e. wait for new events
+ *
+ * Maybe Int timeout in milliseconds (Nothing for no timeout)
+ *
+ * Int the number of events
+ */
+inotify_poll :: !(Maybe Int) !*(Inotify st) -> *(!Int, !*Inotify st)
-inotify_poll :: *(Inotify st) -> *Inotify st
-inotify_check :: *(Inotify st) *World -> *(*Inotify st, *World)
+/**
+ * Check for new events and call callbacks
+ */
+inotify_check :: !*(Inotify st) !*World -> *(!*Inotify st, !*World)
-inotify_loop_forever :: *(Inotify st) *World -> *(*Inotify st, *World)
+/**
+ * Check if an event matches a mask
+ */
+inotify_is_event :: INMask INEvent -> Bool
+
+/**
+ * Combination of inotify_poll and inotify_check that will return only if no
+ * events were given when a timeout occurred.
+ */
+inotify_loop_with_timeout :: !(Maybe Int) !*(Inotify st) !*World -> *(!*Inotify st, !*World)
+
+/**
+ * inotify_loop_with_timeout with Nothing as timeout (will never return)
+ */
+inotify_loop_forever :: !*(Inotify st) !*World -> *(!*Inotify st, !*World)
+
+/*** Begin inotify.h ***/
IN_ACCESS :== 0x00000001 // File was accessed
IN_MODIFY :== 0x00000002 // File was modified
@@ -52,3 +109,5 @@ IN_ALL_EVENTS :==
(IN_ACCESS |- IN_MODIFY |- IN_ATTRIB |- IN_CLOSE_WRITE |- IN_CLOSE_NOWRITE
|- IN_OPEN |- IN_MOVED_FROM |- IN_MOVED_TO |- IN_DELETE |- IN_CREATE |-
IN_DELETE_SELF |- IN_MOVE_SELF)
+
+/*** End inotify.h ***/
diff --git a/Inotify.icl b/Inotify.icl
index e676f03..205aba9 100644
--- a/Inotify.icl
+++ b/Inotify.icl
@@ -42,8 +42,8 @@ where
ccall close "I:V:A"
}
-inotify_add_watch :: (INCallback st) !Int !String !*(Inotify st)
- -> *(Either Int INWatch, *Inotify st)
+inotify_add_watch :: (INCallback st) !INMask !String !*(Inotify st)
+ -> *(!Either Int INWatch, !*Inotify st)
inotify_add_watch f mask fname inot=:{fd,watches}
= let (w, fd`) = c_add_watch fd fname mask in
( if (w == -1) (Left errno) (Right w)
@@ -55,7 +55,7 @@ where
ccall clean_inotify_add_watch "ISI:VII"
}
-inotify_rm_watch :: !INWatch !*(Inotify st) -> *(Bool, *Inotify st)
+inotify_rm_watch :: !INWatch !*(Inotify st) -> *(!Bool, !*Inotify st)
inotify_rm_watch w inot=:{fd}
= case c_inotify_rm_watch fd w of (0, fd`) = (True, {inot & fd=fd`})
(_, fd`) = (False, {inot & fd=fd`})
@@ -65,15 +65,17 @@ where
ccall clean_inotify_rm_watch "II:VII"
}
-inotify_poll :: *(Inotify st) -> *Inotify st
-inotify_poll inot=:{fd} = let (_,fd`) = c_poll fd in { inot & fd=fd` }
+inotify_poll :: !(Maybe Int) !*(Inotify st) -> *(!Int, !*Inotify st)
+inotify_poll mbTo inot=:{fd} = let (n,fd`)=c_poll fd to in (n, {inot & fd=fd`})
where
- c_poll :: !*Int -> *(!Int, !*Int)
- c_poll fd = code {
- ccall clean_poll "I:VII"
+ to = if (isNothing mbTo) -1 (fromJust mbTo)
+
+ c_poll :: !*Int !Int -> *(!Int, !*Int)
+ c_poll fd timeout = code {
+ ccall clean_poll "II:VII"
}
-inotify_check :: *(Inotify st) *World -> *(*Inotify st, *World)
+inotify_check :: !*(Inotify st) !*World -> *(!*Inotify st, !*World)
inotify_check inot=:{fd,watches,state} w
# (ok, wds, masks, fnames, fd) = c_check fd
inot = { inot & fd=fd }
@@ -117,11 +119,18 @@ where
ccall clean_inotify_check "I:VISSSI"
}
-inotify_loop_forever :: *(Inotify st) *World -> *(*Inotify st, *World)
-inotify_loop_forever inot w
- # inot = inotify_poll inot
+inotify_is_event :: INMask INEvent -> Bool
+inotify_is_event mask ev = ev bitand mask <> 0
+
+inotify_loop_with_timeout :: !(Maybe Int) !*(Inotify st) !*World -> *(!*Inotify st, !*World)
+inotify_loop_with_timeout to inot w
+ # (n,inot) = inotify_poll to inot
+ | n == 0 = (inot,w)
# (inot,w) = inotify_check inot w
- = inotify_loop_forever inot w
+ = inotify_loop_with_timeout to inot w
+
+inotify_loop_forever :: !*(Inotify st) !*World -> *(!*Inotify st, !*World)
+inotify_loop_forever inot w = inotify_loop_with_timeout Nothing inot w
errno :: Int
errno = err 0
diff --git a/inotify_c.c b/inotify_c.c
index c81f57d..e63ebfb 100644
--- a/inotify_c.c
+++ b/inotify_c.c
@@ -9,6 +9,10 @@
#include "Clean.h"
+/**
+ * Cast a CleanString to a char*
+ * The result should be freed.
+ */
char* clstocs(CleanString* cs) {
char* s = calloc(CleanStringLength(cs) + 1, 1);
uint8_t i;
@@ -18,12 +22,20 @@ char* clstocs(CleanString* cs) {
return s;
}
+/** The empty string, as a CleanString */
static struct {int length; char chars[1]; } empty_string = {0,""};
+/**
+ * Get the errno. The parameter is ignored, it is just there because ccalls
+ * need to have an argument.
+ */
int clean_errno(int ignored) {
return errno;
}
+/**
+ * Initialise an inotify file descriptor and change to NONBLOCK mode.
+ */
int clean_inotify_init(int ignored) {
int fd;
fd = inotify_init();
@@ -33,6 +45,15 @@ int clean_inotify_init(int ignored) {
return fd;
}
+/**
+ * Add a watch on some file.
+ *
+ * fd The inotify file descriptor
+ * fname The file to watch
+ * mask A mask of events to watch on
+ * re_watch Will be set to the resulting watch descriptor
+ * re_fd Will be set to fd (needed for uniqueness)
+ */
void clean_inotify_add_watch(int fd, CleanString* fname_, int mask,
int *re_watch, int *re_fd) {
char* fname = clstocs(fname_);
@@ -41,21 +62,56 @@ void clean_inotify_add_watch(int fd, CleanString* fname_, int mask,
*re_fd = fd;
}
+/**
+ * Remove a watch descriptor.
+ *
+ * fd The inotify file descriptor
+ * watch The watch descriptor to remove
+ * re_code Will be set to the return code of inotify_rm_watch
+ * re_fd Will be set to fd (needed for uniqueness)
+ */
void clean_inotify_rm_watch(int fd, int watch, int *re_code, int *re_fd) {
*re_fd = fd;
*re_code = inotify_rm_watch(fd, watch);
}
-void clean_poll(int fd, int *re_nrevents, int *re_fd) {
+/**
+ * Poll an inotify file descriptor
+ *
+ * fd The inotify file descriptor to poll
+ * timeout The timeout (negative for no timeout)
+ * re_nrevents Will be set to the number of polled events
+ * re_fd Will be set to fd (needed for uniqueness)
+ */
+void clean_poll(int fd, int timeout, int *re_nrevents, int *re_fd) {
struct pollfd pfd = {fd, POLLIN, 0};
- *re_nrevents = poll(&pfd, 1, -1);
+ *re_nrevents = poll(&pfd, 1, timeout);
*re_fd = fd;
}
+/**
+ * CleanStrings that are returned from clean_inotify_check (so that we don't
+ * have to malloc all the time.)
+ */
static CleanStringVariable(wds_string, 1024);
static CleanStringVariable(masks_string, 1024);
static CleanStringVariable(names_string, 4096);
+/**
+ * Check for events on an inotify file descriptor.
+ *
+ * fd The inotify file descriptor
+ * re_ok Will be set to 1 on success, 0 on failure
+ * re_wds An array of ints, the watch descriptors that had events
+ * re_masks An array of ints, the events
+ * re_fnames A list of strings, the filenames of the events (may be empty)
+ * re_fd Will be set to fd (needed for uniqueness)
+ *
+ * re_wds, re_masks and re_fnames are hacks because ccall doesn't allow
+ * returning {#Int} or {#String}. The int arrays can be read by taking 4 chars
+ * at a time and casting that to an int. The string array can be read by
+ * splitting on \0 (since they are filenames, \0 cannot occur).
+ */
void clean_inotify_check(int fd,
int *re_ok, CleanString* re_wds, CleanString* re_masks,
CleanString* re_fnames, int *re_fd) {
diff --git a/test.icl b/test.icl
index b598c0f..75cc04b 100644
--- a/test.icl
+++ b/test.icl
@@ -5,24 +5,35 @@ import Data.Either
import Data.Maybe
import Inotify
+:: Void = Void
+
+/**
+ * test: Example usage of Inotify
+ *
+ * This will show all events on files file1 and file2. They should exist before
+ * starting this program.
+ *
+ * When nothing has happened for 10s, the program will exit.
+ */
+
Start w
-# (Just inot) = inotify_init 0
+# (Just inot) = inotify_init Void
# (Right watch, inot)
= inotify_add_watch (echo "file1") IN_ALL_EVENTS "file1" inot
# (Right watch, inot)
= inotify_add_watch (echo "file2") IN_ALL_EVENTS "file2" inot
# (io,w) = stdio w
-# io = io <<< "Do something with file1 or file2\n"
+# io = io <<< "You have 10 seconds to do something with file1 or file2\n"
# (ok,w) = fclose io w
-# (inot, w) = inotify_loop_forever inot w
+# (inot, w) = inotify_loop_with_timeout (Just 10000) inot w
= inotify_close inot
where
- echo :: String INEvent (Maybe String) Int *World -> *(Int, *World)
- echo fname ev f i w
+ echo :: String INEvent (Maybe String) Void *World -> *(Void, *World)
+ echo fname ev f _ w
# (io,w) = stdio w
# io = io <<< "EVENT: ["<<< fname <<<"; "<<< ev <<<"; "<<< f <<<"]\n"
# (ok,w) = fclose io w
- = (i, w)
+ = (Void, w)
instance <<< (Maybe a) | <<< a
where
diff --git a/test.prj b/test.prj
index 34f78dc..bb1bd54 100644
--- a/test.prj
+++ b/test.prj
@@ -24,7 +24,7 @@ Global
Time: False
Stack: False
Output
- Output: ShowConstructors
+ Output: NoConsole
Font: Monaco
FontSize: 9
WriteStdErr: False
diff --git a/test_reload.icl b/test_reload.icl
index 052c4c9..6b71c8f 100644
--- a/test_reload.icl
+++ b/test_reload.icl
@@ -22,6 +22,7 @@ import Inotify
*
* Test it by running `make run_test_reload`, then editing `changeme` below and
* running `make test_reload` to recompile.
+ * You can also use `touch test_reload`.
*/
my_name :== "test_reload"
@@ -33,10 +34,10 @@ verbose :== False
Start w
# (io,w) = stdio w
-# io = io <<< changeme <<< "\n"
+# io = io <<< changeme <<< " (edit changeme and recompile)\n"
# (ok,w) = fclose io w
# (Just inot) = inotify_init Void
-# (Right watch,inot) = inotify_add_watch reload IN_ALL_EVENTS my_dir inot
+# (Right watch,inot) = inotify_add_watch reload IN_ATTRIB my_dir inot
# (inot,w) = inotify_loop_forever inot w
= inotify_close inot
where
@@ -45,7 +46,7 @@ where
# w = echo (\f -> f <<< "event: " <<< ev <<< "; " <<< mbName) w
| isNothing mbName = (Void, w)
# (Just name) = mbName
- | ev bitand IN_ATTRIB <> 0 && name == my_name
+ | inotify_is_event IN_ATTRIB ev && name == my_name
# w = echo (\f -> f <<< "reloading...") w
# w = exit 127 w
= (Void, w)