Explorar el Código

add udevmonitor subsystem

ambrop7 hace 15 años
padre
commit
03d5d5eeff

+ 1 - 0
CMakeLists.txt

@@ -147,6 +147,7 @@ if (NOT WIN32)
     add_subdirectory(ipc)
     add_subdirectory(process)
     add_subdirectory(inputprocess)
+    add_subdirectory(udevmonitor)
 endif ()
 
 # example programs

+ 4 - 0
blog_channels.txt

@@ -63,3 +63,7 @@ DPRelay 4
 BThreadWork 4
 DPReceive 4
 BInputProcess 4
+NCDUdevMonitorParser 4
+NCDUdevMonitor 4
+NCDUdevCache 4
+NCDUdevManager 4

+ 4 - 0
generated/blog_channel_NCDUdevCache.h

@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_NCDUdevCache

+ 4 - 0
generated/blog_channel_NCDUdevManager.h

@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_NCDUdevManager

+ 4 - 0
generated/blog_channel_NCDUdevMonitor.h

@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_NCDUdevMonitor

+ 4 - 0
generated/blog_channel_NCDUdevMonitorParser.h

@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_NCDUdevMonitorParser

+ 5 - 1
generated/blog_channels_defines.h

@@ -63,4 +63,8 @@
 #define BLOG_CHANNEL_BThreadWork 62
 #define BLOG_CHANNEL_DPReceive 63
 #define BLOG_CHANNEL_BInputProcess 64
-#define BLOG_NUM_CHANNELS 65
+#define BLOG_CHANNEL_NCDUdevMonitorParser 65
+#define BLOG_CHANNEL_NCDUdevMonitor 66
+#define BLOG_CHANNEL_NCDUdevCache 67
+#define BLOG_CHANNEL_NCDUdevManager 68
+#define BLOG_NUM_CHANNELS 69

+ 4 - 0
generated/blog_channels_list.h

@@ -63,3 +63,7 @@
 {.name = "BThreadWork", .loglevel = 4},
 {.name = "DPReceive", .loglevel = 4},
 {.name = "BInputProcess", .loglevel = 4},
+{.name = "NCDUdevMonitorParser", .loglevel = 4},
+{.name = "NCDUdevMonitor", .loglevel = 4},
+{.name = "NCDUdevCache", .loglevel = 4},
+{.name = "NCDUdevManager", .loglevel = 4},

+ 7 - 0
udevmonitor/CMakeLists.txt

@@ -0,0 +1,7 @@
+add_library(udevmonitor
+    NCDUdevMonitorParser.c
+    NCDUdevMonitor.c
+    NCDUdevCache.c
+    NCDUdevManager.c
+)
+target_link_libraries(udevmonitor system flow inputprocess stringmap)

+ 415 - 0
udevmonitor/NCDUdevCache.c

@@ -0,0 +1,415 @@
+/**
+ * @file NCDUdevCache.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <misc/offset.h>
+#include <misc/string_begins_with.h>
+#include <misc/concat_strings.h>
+#include <system/BLog.h>
+
+#include <udevmonitor/NCDUdevCache.h>
+
+#include <generated/blog_channel_NCDUdevCache.h>
+
+static int string_comparator (void *unused, const char **str1, const char **str2)
+{
+    int c = strcmp(*str1, *str2);
+    if (c < 0) {
+        return -1;
+    }
+    if (c > 0) {
+        return 1;
+    }
+    return 0;
+}
+
+static void free_device (NCDUdevCache *o, struct NCDUdevCache_device *device)
+{
+    if (device->is_cleaned) {
+        // remove from cleaned devices list
+        LinkedList1_Remove(&o->cleaned_devices_list, &device->cleaned_devices_list_node);
+    } else {
+        // remove from devices tree
+        BAVL_Remove(&o->devices_tree, &device->devices_tree_node);
+    }
+    
+    // free map
+    BStringMap_Free(&device->map);
+    
+    // free structure
+    free(device);
+}
+
+static struct NCDUdevCache_device * lookup_device (NCDUdevCache *o, const char *devpath)
+{
+    BAVLNode *tree_node = BAVL_LookupExact(&o->devices_tree, &devpath);
+    if (!tree_node) {
+        return NULL;
+    }
+    struct NCDUdevCache_device *device = UPPER_OBJECT(tree_node, struct NCDUdevCache_device, devices_tree_node);
+    ASSERT(!device->is_cleaned)
+    
+    return device;
+}
+
+static void rename_devices (NCDUdevCache *o, const char *prefix, const char *new_prefix)
+{
+    ASSERT(strlen(prefix) > 0)
+    
+    size_t prefix_len = strlen(prefix);
+    
+    // lookup prefix
+    BAVLNode *tree_node = BAVL_Lookup(&o->devices_tree, &prefix);
+    if (!tree_node) {
+        return;
+    }
+    struct NCDUdevCache_device *device = UPPER_OBJECT(tree_node, struct NCDUdevCache_device, devices_tree_node);
+    ASSERT(!device->is_cleaned)
+    
+    // if the result does not begin with prefix, we might gave gotten the device before all
+    // devices beginning with prefix, so skip it
+    if (!string_begins_with(device->devpath, prefix)) {
+        tree_node = BAVL_GetNext(&o->devices_tree, tree_node);
+    }
+    
+    while (tree_node) {
+        // get next node (must be here because we rename this device)
+        BAVLNode *next_tree_node = BAVL_GetNext(&o->devices_tree, tree_node);
+        
+        // get device
+        device = UPPER_OBJECT(tree_node, struct NCDUdevCache_device, devices_tree_node);
+        ASSERT(!device->is_cleaned)
+        
+        // if it doesn't begin with prefix, we're done
+        if (!string_begins_with(device->devpath, prefix)) {
+            break;
+        }
+        
+        // build new devpath
+        char *new_devpath = concat_strings(2, new_prefix, device->devpath + prefix_len);
+        if (!new_devpath) {
+            BLog(BLOG_ERROR, "concat_strings failed");
+            goto fail_loop0;
+        }
+        
+        // make sure the new name does not exist
+        if (BAVL_LookupExact(&o->devices_tree, &new_devpath)) {
+            BLog(BLOG_ERROR, "rename destination already exists");
+            goto fail_loop1;
+        }
+        
+        BLog(BLOG_DEBUG, "rename %s -> %s", device->devpath, new_devpath);
+        
+        // remove from tree
+        BAVL_Remove(&o->devices_tree, &device->devices_tree_node);
+        
+        // update devpath in map
+        if (!BStringMap_Set(&device->map, "DEVPATH", new_devpath)) {
+            BLog(BLOG_ERROR, "BStringMap_Set failed");
+            ASSERT_EXECUTE(BAVL_Insert(&o->devices_tree, &device->devices_tree_node, NULL))
+            goto fail_loop1;
+        }
+        
+        // update devpath pointer
+        device->devpath = BStringMap_Get(&device->map, "DEVPATH");
+        ASSERT(device->devpath)
+        
+        // insert to tree
+        ASSERT_EXECUTE(BAVL_Insert(&o->devices_tree, &device->devices_tree_node, NULL))
+        
+    fail_loop1:
+        free(new_devpath);
+    fail_loop0:
+        tree_node = next_tree_node;
+    }
+}
+
+static int add_device (NCDUdevCache *o, BStringMap map)
+{
+    ASSERT(BStringMap_Get(&map, "DEVPATH"))
+    
+    // alloc structure
+    struct NCDUdevCache_device *device = malloc(sizeof(*device));
+    if (!device) {
+        BLog(BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    
+    // init map
+    device->map = map;
+    
+    // set device path
+    device->devpath = BStringMap_Get(&device->map, "DEVPATH");
+    
+    // insert to devices tree
+    BAVLNode *ex_node;
+    if (!BAVL_Insert(&o->devices_tree, &device->devices_tree_node, &ex_node)) {
+        BLog(BLOG_DEBUG, "update %s", device->devpath);
+        
+        // get existing device
+        struct NCDUdevCache_device *ex_device = UPPER_OBJECT(ex_node, struct NCDUdevCache_device, devices_tree_node);
+        ASSERT(!ex_device->is_cleaned)
+        
+        // remove exiting device
+        free_device(o, ex_device);
+        
+        // insert
+        ASSERT_EXECUTE(BAVL_Insert(&o->devices_tree, &device->devices_tree_node, NULL))
+    } else {
+        BLog(BLOG_DEBUG, "add %s", device->devpath);
+    }
+    
+    // set not cleaned
+    device->is_cleaned = 0;
+    
+    // set refreshed
+    device->is_refreshed = 1;
+    
+    return 1;
+    
+fail0:
+    return 0;
+}
+
+void NCDUdevCache_Init (NCDUdevCache *o)
+{
+    // init devices tree
+    BAVL_Init(&o->devices_tree, OFFSET_DIFF(struct NCDUdevCache_device, devpath, devices_tree_node), (BAVL_comparator)string_comparator, NULL);
+    
+    // init cleaned devices list
+    LinkedList1_Init(&o->cleaned_devices_list);
+    
+    DebugObject_Init(&o->d_obj);
+}
+
+void NCDUdevCache_Free (NCDUdevCache *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free cleaned devices
+    LinkedList1Node *list_node;
+    while (list_node = LinkedList1_GetFirst(&o->cleaned_devices_list)) {
+        struct NCDUdevCache_device *device = UPPER_OBJECT(list_node, struct NCDUdevCache_device, cleaned_devices_list_node);
+        ASSERT(device->is_cleaned)
+        free_device(o, device);
+    }
+    
+    // free devices
+    BAVLNode *tree_node;
+    while (tree_node = BAVL_GetFirst(&o->devices_tree)) {
+        struct NCDUdevCache_device *device = UPPER_OBJECT(tree_node, struct NCDUdevCache_device, devices_tree_node);
+        ASSERT(!device->is_cleaned)
+        free_device(o, device);
+    }
+}
+
+const BStringMap * NCDUdevCache_Query (NCDUdevCache *o, const char *devpath)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // lookup device
+    struct NCDUdevCache_device *device = lookup_device(o, devpath);
+    if (!device) {
+        return NULL;
+    }
+    
+    // return map
+    return &device->map;
+}
+
+int NCDUdevCache_Event (NCDUdevCache *o, BStringMap map)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // get device path
+    const char *devpath = BStringMap_Get(&map, "DEVPATH");
+    if (!devpath) {
+        BLog(BLOG_ERROR, "missing DEVPATH");
+        goto fail;
+    }
+    
+    // get action
+    const char *action = BStringMap_Get(&map, "ACTION");
+    
+    // if this is a remove event, remove device if we have it
+    if (action && !strcmp(action, "remove")) {
+        // remove existing device
+        struct NCDUdevCache_device *device = lookup_device(o, devpath);
+        if (device) {
+            BLog(BLOG_DEBUG, "remove %s", devpath);
+            free_device(o, device);
+        } else {
+            BLog(BLOG_DEBUG, "remove unknown %s", devpath);
+        }
+        
+        // eat map
+        BStringMap_Free(&map);
+        
+        return 1;
+    }
+    
+    // if this is a move event, remove old device and contaned devices
+    if (action && !strcmp(action, "move")) {
+        const char *devpath_old = BStringMap_Get(&map, "DEVPATH_OLD");
+        if (!devpath_old) {
+            goto fail_rename0;
+        }
+        
+        // remove old device
+        struct NCDUdevCache_device *old_device = lookup_device(o, devpath_old);
+        if (old_device) {
+            BLog(BLOG_DEBUG, "remove moved %s", old_device->devpath);
+            free_device(o, old_device);
+        }
+        
+        // construct prefix "<devpath_old>/" and new prefix "<devpath>/"
+        char *prefix = concat_strings(2, devpath_old, "/");
+        if (!prefix) {
+            BLog(BLOG_ERROR, "concat_strings failed");
+            goto fail_rename0;;
+        }
+        char *new_prefix = concat_strings(2, devpath, "/");
+        if (!new_prefix) {
+            BLog(BLOG_ERROR, "concat_strings failed");
+            goto fail_rename1;
+        }
+        
+        // rename devices with paths starting with prefix
+        rename_devices(o, prefix, new_prefix);
+        
+        free(new_prefix);
+    fail_rename1:
+        free(prefix);
+    fail_rename0:;
+    }
+    
+    // add device
+    if (!add_device(o, map)) {
+        BLog(BLOG_DEBUG, "failed to add device %s", devpath);
+        goto fail;
+    }
+    
+    return 1;
+    
+fail:
+    return 0;
+}
+
+void NCDUdevCache_StartClean (NCDUdevCache *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // mark all devices not refreshed
+    BAVLNode *tree_node = BAVL_GetFirst(&o->devices_tree);
+    while (tree_node) {
+        struct NCDUdevCache_device *device = UPPER_OBJECT(tree_node, struct NCDUdevCache_device, devices_tree_node);
+        ASSERT(!device->is_cleaned)
+        
+        // set device not refreshed
+        device->is_refreshed = 0;
+        
+        tree_node = BAVL_GetNext(&o->devices_tree, tree_node);
+    }
+}
+
+void NCDUdevCache_FinishClean (NCDUdevCache *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // move all devices not marked refreshed to the cleaned devices list
+    BAVLNode *tree_node = BAVL_GetFirst(&o->devices_tree);
+    while (tree_node) {
+        BAVLNode *next_tree_node = BAVL_GetNext(&o->devices_tree, tree_node);
+        
+        struct NCDUdevCache_device *device = UPPER_OBJECT(tree_node, struct NCDUdevCache_device, devices_tree_node);
+        ASSERT(!device->is_cleaned)
+        
+        if (!device->is_refreshed) {
+            BLog(BLOG_DEBUG, "clean %s", device->devpath);
+            
+            // remove from devices tree
+            BAVL_Remove(&o->devices_tree, &device->devices_tree_node);
+            
+            // insert to cleaned devices list
+            LinkedList1_Append(&o->cleaned_devices_list, &device->cleaned_devices_list_node);
+            
+            // set device cleaned
+            device->is_cleaned = 1;
+        }
+        
+        tree_node = next_tree_node;
+    }
+}
+
+int NCDUdevCache_GetCleanedDevice (NCDUdevCache *o, BStringMap *out_map)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // get cleaned device
+    LinkedList1Node *list_node = LinkedList1_GetFirst(&o->cleaned_devices_list);
+    if (!list_node) {
+        return 0;
+    }
+    struct NCDUdevCache_device *device = UPPER_OBJECT(list_node, struct NCDUdevCache_device, cleaned_devices_list_node);
+    ASSERT(device->is_cleaned)
+    
+    // remove from cleaned devices list
+    LinkedList1_Remove(&o->cleaned_devices_list, &device->cleaned_devices_list_node);
+    
+    // give away map
+    *out_map = device->map;
+    
+    // free structure
+    free(device);
+    
+    return 1;
+}
+
+const char * NCDUdevCache_First (NCDUdevCache *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    BAVLNode *tree_node = BAVL_GetFirst(&o->devices_tree);
+    if (!tree_node) {
+        return NULL;
+    }
+    struct NCDUdevCache_device *device = UPPER_OBJECT(tree_node, struct NCDUdevCache_device, devices_tree_node);
+    ASSERT(!device->is_cleaned)
+    
+    return device->devpath;
+}
+
+const char * NCDUdevCache_Next (NCDUdevCache *o, const char *key)
+{
+    ASSERT(BAVL_LookupExact(&o->devices_tree, &key))
+    
+    BAVLNode *tree_node = BAVL_GetNext(&o->devices_tree, BAVL_LookupExact(&o->devices_tree, &key));
+    if (!tree_node) {
+        return NULL;
+    }
+    struct NCDUdevCache_device *device = UPPER_OBJECT(tree_node, struct NCDUdevCache_device, devices_tree_node);
+    ASSERT(!device->is_cleaned)
+    
+    return device->devpath;
+}

+ 59 - 0
udevmonitor/NCDUdevCache.h

@@ -0,0 +1,59 @@
+/**
+ * @file NCDUdevCache.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef BADVPN_UDEVMONITOR_NCDUDEVCACHE_H
+#define BADVPN_UDEVMONITOR_NCDUDEVCACHE_H
+
+#include <misc/debug.h>
+#include <structure/BAVL.h>
+#include <structure/LinkedList1.h>
+#include <system/DebugObject.h>
+#include <stringmap/BStringMap.h>
+
+struct NCDUdevCache_device {
+    BStringMap map;
+    const char *devpath;
+    int is_cleaned;
+    union {
+        BAVLNode devices_tree_node;
+        LinkedList1Node cleaned_devices_list_node;
+    };
+    int is_refreshed;
+};
+
+typedef struct {
+    BAVL devices_tree;
+    LinkedList1 cleaned_devices_list;
+    DebugObject d_obj;
+} NCDUdevCache;
+
+void NCDUdevCache_Init (NCDUdevCache *o);
+void NCDUdevCache_Free (NCDUdevCache *o);
+const BStringMap * NCDUdevCache_Query (NCDUdevCache *o, const char *devpath);
+int NCDUdevCache_Event (NCDUdevCache *o, BStringMap map) WARN_UNUSED;
+void NCDUdevCache_StartClean (NCDUdevCache *o);
+void NCDUdevCache_FinishClean (NCDUdevCache *o);
+int NCDUdevCache_GetCleanedDevice (NCDUdevCache *o, BStringMap *out_map);
+const char * NCDUdevCache_First (NCDUdevCache *o);
+const char * NCDUdevCache_Next (NCDUdevCache *o, const char *key);
+
+#endif

+ 535 - 0
udevmonitor/NCDUdevManager.c

@@ -0,0 +1,535 @@
+/**
+ * @file NCDUdevManager.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <misc/offset.h>
+#include <system/BLog.h>
+
+#include <udevmonitor/NCDUdevManager.h>
+
+#include <generated/blog_channel_NCDUdevManager.h>
+
+#define RESTART_TIMER_TIME 5000
+
+static int event_to_map (NCDUdevMonitor *monitor, BStringMap *out_map);
+static void free_event (NCDUdevClient *o, struct NCDUdevClient_event *e);
+static void queue_event (NCDUdevManager *o, NCDUdevMonitor *monitor, NCDUdevClient *client);
+static void queue_mapless_event (NCDUdevManager *o, const char *devpath, NCDUdevClient *client);
+static void process_event (NCDUdevManager *o, NCDUdevMonitor *monitor);
+static void try_monitor (NCDUdevManager *o);
+static void reset_monitor (NCDUdevManager *o);
+static void timer_handler (NCDUdevManager *o);
+static void monitor_handler_event (NCDUdevManager *o);
+static void monitor_handler_error (NCDUdevManager *o, int is_error);
+static void info_monitor_handler_event (NCDUdevManager *o);
+static void info_monitor_handler_error (NCDUdevManager *o, int is_error);
+static void next_job_handler (NCDUdevClient *o);
+
+static int event_to_map (NCDUdevMonitor *monitor, BStringMap *out_map)
+{
+    NCDUdevMonitor_AssertReady(monitor);
+    
+    // init map
+    BStringMap_Init(out_map);
+    
+    // insert properties to map
+    int num_properties = NCDUdevMonitor_GetNumProperties(monitor);
+    for (int i = 0; i < num_properties; i++) {
+        const char *name;
+        const char *value;
+        NCDUdevMonitor_GetProperty(monitor, i, &name, &value);
+        
+        if (!BStringMap_Set(out_map, name, value)) {
+            BLog(BLOG_ERROR, "BStringMap_Set failed");
+            goto fail1;
+        }
+    }
+    
+    return 1;
+    
+fail1:
+    BStringMap_Free(out_map);
+    return 0;
+}
+
+static void free_event (NCDUdevClient *o, struct NCDUdevClient_event *e)
+{
+    // remove from events list
+    LinkedList1_Remove(&o->events_list, &e->events_list_node);
+    
+    // free map
+    if (e->have_map) {
+        BStringMap_Free(&e->map);
+    }
+    
+    // free devpath
+    free(e->devpath);
+    
+    // free structure
+    free(e);
+}
+
+static void queue_event (NCDUdevManager *o, NCDUdevMonitor *monitor, NCDUdevClient *client)
+{
+    NCDUdevMonitor_AssertReady(monitor);
+    
+    // alloc event
+    struct NCDUdevClient_event *e = malloc(sizeof(*e));
+    if (!e) {
+        BLog(BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    
+    // build map
+    if (!event_to_map(monitor, &e->map)) {
+        goto fail1;
+    }
+    
+    // set have map
+    e->have_map = 1;
+    
+    // get devpath
+    const char *devpath = BStringMap_Get(&e->map, "DEVPATH");
+    if (!devpath) {
+        BLog(BLOG_ERROR, "DEVPATH missing");
+        goto fail2;
+    }
+    
+    // copy devpath
+    if (!(e->devpath = strdup(devpath))) {
+        BLog(BLOG_ERROR, "strdup failed");
+        goto fail2;
+    }
+    
+    // insert to client's events list
+    LinkedList1_Append(&client->events_list, &e->events_list_node);
+    
+    // if client is running, set next job
+    if (client->running) {
+        BPending_Set(&client->next_job);
+    }
+    
+    return;
+    
+fail2:
+    BStringMap_Free(&e->map);
+fail1:
+    free(e);
+fail0:
+    return;
+}
+
+static void queue_mapless_event (NCDUdevManager *o, const char *devpath, NCDUdevClient *client)
+{
+    // alloc event
+    struct NCDUdevClient_event *e = malloc(sizeof(*e));
+    if (!e) {
+        BLog(BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    
+    // set have no map
+    e->have_map = 0;
+    
+    // copy devpath
+    if (!(e->devpath = strdup(devpath))) {
+        BLog(BLOG_ERROR, "strdup failed");
+        goto fail1;
+    }
+    
+    // insert to client's events list
+    LinkedList1_Append(&client->events_list, &e->events_list_node);
+    
+    // if client is running, set next job
+    if (client->running) {
+        BPending_Set(&client->next_job);
+    }
+    
+    return;
+    
+fail1:
+    free(e);
+fail0:
+    return;
+}
+
+static void process_event (NCDUdevManager *o, NCDUdevMonitor *monitor)
+{
+    NCDUdevMonitor_AssertReady(monitor);
+    
+    // build map from event
+    BStringMap map;
+    if (!event_to_map(monitor, &map)) {
+        BLog(BLOG_ERROR, "failed to build map");
+        return;
+    }
+    
+    // pass event to cache
+    if (!NCDUdevCache_Event(&o->cache, map)) {
+        BLog(BLOG_ERROR, "failed to cache");
+        BStringMap_Free(&map);
+        return;
+    }
+    
+    // queue event to clients
+    LinkedList1Node *list_node = LinkedList1_GetFirst(&o->clients_list);
+    while (list_node) {
+        NCDUdevClient *client = UPPER_OBJECT(list_node, NCDUdevClient, clients_list_node);
+        queue_event(o, monitor, client);
+        list_node = LinkedList1Node_Next(list_node);
+    }
+}
+
+static void try_monitor (NCDUdevManager *o)
+{
+    ASSERT(!o->have_monitor)
+    ASSERT(!BTimer_IsRunning(&o->restart_timer))
+    
+    // init monitor
+    if (!NCDUdevMonitor_Init(&o->monitor, o->reactor, o->manager, 0, o,
+        (NCDUdevMonitor_handler_event)monitor_handler_event,
+        (NCDUdevMonitor_handler_error)monitor_handler_error
+    )) {
+        BLog(BLOG_ERROR, "NCDUdevMonitor_Init failed");
+        
+        // set restart timer
+        BReactor_SetTimer(o->reactor, &o->restart_timer);
+        return;
+    }
+    
+    // set have monitor
+    o->have_monitor = 1;
+    
+    // set not have info monitor
+    o->have_info_monitor = 0;
+}
+
+static void reset_monitor (NCDUdevManager *o)
+{
+    ASSERT(o->have_monitor)
+    ASSERT(!o->have_info_monitor)
+    ASSERT(!BTimer_IsRunning(&o->restart_timer))
+    
+    // free monitor
+    NCDUdevMonitor_Free(&o->monitor);
+    
+    // set have no monitor
+    o->have_monitor = 0;
+    
+    // set restart timer
+    BReactor_SetTimer(o->reactor, &o->restart_timer);
+}
+
+static void timer_handler (NCDUdevManager *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(!o->have_monitor)
+    
+    // try again
+    try_monitor(o);
+}
+
+static void monitor_handler_event (NCDUdevManager *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->have_monitor)
+    ASSERT(!o->have_info_monitor)
+    ASSERT(!BTimer_IsRunning(&o->restart_timer))
+    
+    if (NCDUdevMonitor_IsReadyEvent(&o->monitor)) {
+        BLog(BLOG_INFO, "monitor ready");
+        
+        // init info monitor
+        if (!NCDUdevMonitor_Init(&o->info_monitor, o->reactor, o->manager, 1, o,
+            (NCDUdevMonitor_handler_event)info_monitor_handler_event,
+            (NCDUdevMonitor_handler_error)info_monitor_handler_error
+        )) {
+            BLog(BLOG_ERROR, "NCDUdevMonitor_Init failed");
+            reset_monitor(o);
+            return;
+        }
+        
+        // set have info monitor
+        o->have_info_monitor = 1;
+        
+        // start cache cleanup
+        NCDUdevCache_StartClean(&o->cache);
+        
+        // hold processing monitor events until info monitor is done
+        return;
+    }
+    
+    // accept event
+    NCDUdevMonitor_Done(&o->monitor);
+    
+    // process event
+    process_event(o, &o->monitor);
+}
+
+static void monitor_handler_error (NCDUdevManager *o, int is_error)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->have_monitor)
+    ASSERT(!BTimer_IsRunning(&o->restart_timer))
+    
+    BLog(BLOG_ERROR, "monitor error");
+    
+    if (o->have_info_monitor) {
+        // free info monitor
+        NCDUdevMonitor_Free(&o->info_monitor);
+        
+        // set have no info monitor
+        o->have_info_monitor = 0;
+    }
+    
+    // reset monitor
+    reset_monitor(o);
+}
+
+static void info_monitor_handler_event (NCDUdevManager *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->have_monitor)
+    ASSERT(o->have_info_monitor)
+    ASSERT(!BTimer_IsRunning(&o->restart_timer))
+    
+    // accept event
+    NCDUdevMonitor_Done(&o->info_monitor);
+    
+    // process event
+    process_event(o, &o->info_monitor);
+}
+
+static void info_monitor_handler_error (NCDUdevManager *o, int is_error)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->have_monitor)
+    ASSERT(o->have_info_monitor)
+    ASSERT(!BTimer_IsRunning(&o->restart_timer))
+    
+    if (is_error) {
+        BLog(BLOG_ERROR, "info monitor error");
+    } else {
+        BLog(BLOG_INFO, "info monitor finished");
+    }
+    
+    // free info monitor
+    NCDUdevMonitor_Free(&o->info_monitor);
+    
+    // set have no info monitor
+    o->have_info_monitor = 0;
+    
+    if (is_error) {
+        // reset monitor
+        reset_monitor(o);
+    } else {
+        // continue processing monitor events
+        NCDUdevMonitor_Done(&o->monitor);
+        
+        // finish cache cleanup
+        NCDUdevCache_FinishClean(&o->cache);
+        
+        // collect cleaned devices
+        BStringMap map;
+        while (NCDUdevCache_GetCleanedDevice(&o->cache, &map)) {
+            // get devpath
+            const char *devpath = BStringMap_Get(&map, "DEVPATH");
+            ASSERT(devpath)
+            
+            // queue mapless event to clients
+            LinkedList1Node *list_node = LinkedList1_GetFirst(&o->clients_list);
+            while (list_node) {
+                NCDUdevClient *client = UPPER_OBJECT(list_node, NCDUdevClient, clients_list_node);
+                queue_mapless_event(o, devpath, client);
+                list_node = LinkedList1Node_Next(list_node);
+            }
+            
+            BStringMap_Free(&map);
+        }
+    }
+}
+
+static void next_job_handler (NCDUdevClient *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(!LinkedList1_IsEmpty(&o->events_list))
+    ASSERT(o->running)
+    
+    // get event
+    struct NCDUdevClient_event *e = UPPER_OBJECT(LinkedList1_GetFirst(&o->events_list), struct NCDUdevClient_event, events_list_node);
+    
+    // grab map from event
+    int have_map = e->have_map;
+    BStringMap map = e->map;
+    
+    // grab devpath from event
+    char *devpath = e->devpath;
+    
+    // remove from events list
+    LinkedList1_Remove(&o->events_list, &e->events_list_node);
+    
+    // free structure
+    free(e);
+    
+    // schedule next event if needed
+    if (!LinkedList1_IsEmpty(&o->events_list)) {
+        BPending_Set(&o->next_job);
+    }
+    
+    // give map to handler
+    o->handler(o->user, devpath, have_map, map);
+    return;
+}
+
+void NCDUdevManager_Init (NCDUdevManager *o, BReactor *reactor, BProcessManager *manager)
+{
+    // init arguments
+    o->reactor = reactor;
+    o->manager = manager;
+    
+    // init clients list
+    LinkedList1_Init(&o->clients_list);
+    
+    // init cache
+    NCDUdevCache_Init(&o->cache);
+    
+    // init restart timer
+    BTimer_Init(&o->restart_timer, RESTART_TIMER_TIME, (BTimer_handler)timer_handler, o);
+    
+    // set have no monitor
+    o->have_monitor = 0;
+    
+    DebugObject_Init(&o->d_obj);
+}
+
+void NCDUdevManager_Free (NCDUdevManager *o)
+{
+    DebugObject_Free(&o->d_obj);
+    ASSERT(LinkedList1_IsEmpty(&o->clients_list))
+    
+    if (o->have_monitor) {
+        // free info monitor
+        if (o->have_info_monitor) {
+            NCDUdevMonitor_Free(&o->info_monitor);
+        }
+        
+        // free monitor
+        NCDUdevMonitor_Free(&o->monitor);
+    }
+    
+    // free restart timer
+    BReactor_RemoveTimer(o->reactor, &o->restart_timer);
+    
+    // free cache
+    NCDUdevCache_Free(&o->cache);
+}
+
+const BStringMap * NCDUdevManager_Query (NCDUdevManager *o, const char *devpath)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return NCDUdevCache_Query(&o->cache, devpath);
+}
+
+void NCDUdevClient_Init (NCDUdevClient *o, NCDUdevManager *m, void *user,
+                         NCDUdevClient_handler handler)
+{
+    DebugObject_Access(&m->d_obj);
+    
+    // init arguments
+    o->m = m;
+    o->user = user;
+    o->handler = handler;
+    
+    // insert to manager's list
+    LinkedList1_Append(&m->clients_list, &o->clients_list_node);
+    
+    // init events list
+    LinkedList1_Init(&o->events_list);
+    
+    // init next job
+    BPending_Init(&o->next_job, BReactor_PendingGroup(m->reactor), (BPending_handler)next_job_handler, o);
+    
+    // set running
+    o->running = 1;
+    
+    // queue all devices from cache
+    const char *devpath = NCDUdevCache_First(&m->cache);
+    while (devpath) {
+        queue_mapless_event(m, devpath, o);
+        devpath = NCDUdevCache_Next(&m->cache, devpath);
+    }
+    
+    // if this is the first client, init monitor
+    if (!m->have_monitor && !BTimer_IsRunning(&m->restart_timer)) {
+        try_monitor(m);
+    }
+    
+    DebugObject_Init(&o->d_obj);
+}
+
+void NCDUdevClient_Free (NCDUdevClient *o)
+{
+    DebugObject_Free(&o->d_obj);
+    NCDUdevManager *m = o->m;
+    
+    // free events
+    LinkedList1Node *list_node;
+    while (list_node = LinkedList1_GetFirst(&o->events_list)) {
+        struct NCDUdevClient_event *e = UPPER_OBJECT(list_node, struct NCDUdevClient_event, events_list_node);
+        free_event(o, e);
+    }
+    
+    // free next job
+    BPending_Free(&o->next_job);
+    
+    // remove from manager's list
+    LinkedList1_Remove(&m->clients_list, &o->clients_list_node);
+}
+
+void NCDUdevClient_Pause (NCDUdevClient *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->running)
+    
+    // set not running
+    o->running = 0;
+    
+    // unset next job to avoid reporting queued events
+    BPending_Unset(&o->next_job);
+}
+
+void NCDUdevClient_Continue (NCDUdevClient *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(!o->running)
+    
+    // set running
+    o->running = 1;
+    
+    // set next job if we have events queued
+    if (!LinkedList1_IsEmpty(&o->events_list)) {
+        BPending_Set(&o->next_job);
+    }
+}

+ 76 - 0
udevmonitor/NCDUdevManager.h

@@ -0,0 +1,76 @@
+/**
+ * @file NCDUdevManager.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef BADVPN_UDEVMONITOR_NCDUDEVMANAGER_H
+#define BADVPN_UDEVMONITOR_NCDUDEVMANAGER_H
+
+#include <misc/debug.h>
+#include <structure/LinkedList1.h>
+#include <system/DebugObject.h>
+#include <udevmonitor/NCDUdevMonitor.h>
+#include <udevmonitor/NCDUdevCache.h>
+#include <stringmap/BStringMap.h>
+
+typedef void (*NCDUdevClient_handler) (void *user, char *devpath, int have_map, BStringMap map);
+
+typedef struct {
+    BReactor *reactor;
+    BProcessManager *manager;
+    LinkedList1 clients_list;
+    NCDUdevCache cache;
+    BTimer restart_timer;
+    int have_monitor;
+    NCDUdevMonitor monitor;
+    int have_info_monitor;
+    NCDUdevMonitor info_monitor;
+    DebugObject d_obj;
+} NCDUdevManager;
+
+typedef struct {
+    NCDUdevManager *m;
+    void *user;
+    NCDUdevClient_handler handler;
+    LinkedList1Node clients_list_node;
+    LinkedList1 events_list;
+    BPending next_job;
+    int running;
+    DebugObject d_obj;
+} NCDUdevClient;
+
+struct NCDUdevClient_event {
+    char *devpath;
+    int have_map;
+    BStringMap map;
+    LinkedList1Node events_list_node;
+};
+
+void NCDUdevManager_Init (NCDUdevManager *o, BReactor *reactor, BProcessManager *manager);
+void NCDUdevManager_Free (NCDUdevManager *o);
+const BStringMap * NCDUdevManager_Query (NCDUdevManager *o, const char *devpath);
+
+void NCDUdevClient_Init (NCDUdevClient *o, NCDUdevManager *m, void *user,
+                         NCDUdevClient_handler handler);
+void NCDUdevClient_Free (NCDUdevClient *o);
+void NCDUdevClient_Pause (NCDUdevClient *o);
+void NCDUdevClient_Continue (NCDUdevClient *o);
+
+#endif

+ 211 - 0
udevmonitor/NCDUdevMonitor.c

@@ -0,0 +1,211 @@
+/**
+ * @file NCDUdevMonitor.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stddef.h>
+
+#include <system/BLog.h>
+
+#include <udevmonitor/NCDUdevMonitor.h>
+
+#include <generated/blog_channel_NCDUdevMonitor.h>
+
+#define STDBUF_EXEC "/usr/bin/stdbuf"
+#define UDEVADM_EXEC "/sbin/udevadm"
+#define PARSER_BUF_SIZE 16384
+#define PARSER_MAX_PROPERTIES 256
+
+static void report_error (NCDUdevMonitor *o)
+{
+    ASSERT(!o->process_running)
+    ASSERT(!o->input_running)
+    
+    DEBUGERROR(&o->d_err, o->handler_error(o->user, (o->process_was_error || o->input_was_error)));
+}
+
+static void process_handler_terminated (NCDUdevMonitor *o, int normally, uint8_t normally_exit_status)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->process_running)
+    
+    BLog(BLOG_INFO, "process terminated");
+    
+    // set process not running (so we don't try to kill it)
+    o->process_running = 0;
+    
+    // remember process error
+    o->process_was_error = !(normally && normally_exit_status == 0);
+    
+    if (!o->input_running) {
+        report_error(o);
+        return;
+    }
+}
+
+static void process_handler_closed (NCDUdevMonitor *o, int is_error)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->input_running)
+    
+    if (is_error) {
+        BLog(BLOG_ERROR, "pipe error");
+    } else {
+        BLog(BLOG_INFO, "pipe closed");
+    }
+    
+    // disconnect connector
+    StreamRecvConnector_DisconnectInput(&o->connector);
+    
+    // set input not running
+    o->input_running = 0;
+    
+    // remember input error
+    o->input_was_error = is_error;
+    
+    if (!o->process_running) {
+        report_error(o);
+        return;
+    }
+}
+
+static void parser_handler (NCDUdevMonitor *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    o->handler_event(o->user);
+    return;
+}
+
+int NCDUdevMonitor_Init (NCDUdevMonitor *o, BReactor *reactor, BProcessManager *manager, int is_info_mode, void *user,
+                         NCDUdevMonitor_handler_event handler_event,
+                         NCDUdevMonitor_handler_error handler_error)
+{
+    ASSERT(is_info_mode == 0 || is_info_mode == 1)
+    
+    // init arguments
+    o->user = user;
+    o->handler_event = handler_event;
+    o->handler_error = handler_error;
+    
+    // construct arguments
+    const char *argv_monitor[] = {STDBUF_EXEC, "-o", "L", UDEVADM_EXEC, "monitor", "--udev", "--property", NULL};
+    const char *argv_info[] = {STDBUF_EXEC, "-o", "L", UDEVADM_EXEC, "info", "--query", "all", "--export-db", NULL};
+    const char **argv = (is_info_mode ? argv_info : argv_monitor);
+    
+    // init process
+    if (!BInputProcess_Init(&o->process, STDBUF_EXEC, (char **)argv, NULL, reactor, manager, o,
+                            (BInputProcess_handler_terminated)process_handler_terminated,
+                            (BInputProcess_handler_closed)process_handler_closed
+    )) {
+        BLog(BLOG_ERROR, "BInputProcess_Init failed");
+        goto fail0;
+    }
+    
+    // init connector
+    StreamRecvConnector_Init(&o->connector, BReactor_PendingGroup(reactor));
+    StreamRecvConnector_ConnectInput(&o->connector, BInputProcess_GetInput(&o->process));
+    
+    // init parser
+    if (!NCDUdevMonitorParser_Init(&o->parser, StreamRecvConnector_GetOutput(&o->connector), PARSER_BUF_SIZE, PARSER_MAX_PROPERTIES,
+                                   is_info_mode, BReactor_PendingGroup(reactor), o,
+                                   (NCDUdevMonitorParser_handler)parser_handler
+    )) {
+        BLog(BLOG_ERROR, "NCDUdevMonitorParser_Init failed");
+        goto fail1;
+    }
+    
+    // set process running, input running
+    o->process_running = 1;
+    o->input_running = 1;
+    
+    DebugError_Init(&o->d_err, BReactor_PendingGroup(reactor));
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail1:
+    StreamRecvConnector_Free(&o->connector);
+    BInputProcess_Kill(&o->process);
+    BInputProcess_Free(&o->process);
+fail0:
+    return 0;
+}
+
+void NCDUdevMonitor_Free (NCDUdevMonitor *o)
+{
+    DebugObject_Free(&o->d_obj);
+    DebugError_Free(&o->d_err);
+    
+    // free parser
+    NCDUdevMonitorParser_Free(&o->parser);
+    
+    // free connector
+    StreamRecvConnector_Free(&o->connector);
+    
+    // kill process it it's running
+    if (o->process_running) {
+        BInputProcess_Kill(&o->process);
+    }
+    
+    // free process
+    BInputProcess_Free(&o->process);
+}
+
+void NCDUdevMonitor_Done (NCDUdevMonitor *o)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    NCDUdevMonitorParser_AssertReady(&o->parser);
+    
+    NCDUdevMonitorParser_Done(&o->parser);
+}
+
+int NCDUdevMonitor_IsReadyEvent (NCDUdevMonitor *o)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    NCDUdevMonitorParser_AssertReady(&o->parser);
+    
+    return NCDUdevMonitorParser_IsReadyEvent(&o->parser);
+}
+void NCDUdevMonitor_AssertReady (NCDUdevMonitor *o)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    NCDUdevMonitorParser_AssertReady(&o->parser);
+}
+
+int NCDUdevMonitor_GetNumProperties (NCDUdevMonitor *o)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    NCDUdevMonitorParser_AssertReady(&o->parser);
+    
+    return NCDUdevMonitorParser_GetNumProperties(&o->parser);
+}
+
+void NCDUdevMonitor_GetProperty (NCDUdevMonitor *o, int index, const char **name, const char **value)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    NCDUdevMonitorParser_AssertReady(&o->parser);
+    
+    NCDUdevMonitorParser_GetProperty(&o->parser, index, name, value);
+}

+ 60 - 0
udevmonitor/NCDUdevMonitor.h

@@ -0,0 +1,60 @@
+/**
+ * @file NCDUdevMonitor.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef BADVPN_UDEVMONITOR_NCDUDEVMONITOR_H
+#define BADVPN_UDEVMONITOR_NCDUDEVMONITOR_H
+
+#include <misc/debug.h>
+#include <misc/debugerror.h>
+#include <flow/StreamRecvConnector.h>
+#include <inputprocess/BInputProcess.h>
+#include <udevmonitor/NCDUdevMonitorParser.h>
+
+typedef void (*NCDUdevMonitor_handler_event) (void *user);
+typedef void (*NCDUdevMonitor_handler_error) (void *user, int is_error);
+
+typedef struct {
+    void *user;
+    NCDUdevMonitor_handler_event handler_event;
+    NCDUdevMonitor_handler_error handler_error;
+    BInputProcess process;
+    int process_running;
+    int process_was_error;
+    int input_running;
+    int input_was_error;
+    StreamRecvConnector connector;
+    NCDUdevMonitorParser parser;
+    DebugObject d_obj;
+    DebugError d_err;
+} NCDUdevMonitor;
+
+int NCDUdevMonitor_Init (NCDUdevMonitor *o, BReactor *reactor, BProcessManager *manager, int is_info_mode, void *user,
+                         NCDUdevMonitor_handler_event handler_event,
+                         NCDUdevMonitor_handler_error handler_error) WARN_UNUSED;
+void NCDUdevMonitor_Free (NCDUdevMonitor *o);
+void NCDUdevMonitor_Done (NCDUdevMonitor *o);
+void NCDUdevMonitor_AssertReady (NCDUdevMonitor *o);
+int NCDUdevMonitor_IsReadyEvent (NCDUdevMonitor *o);
+int NCDUdevMonitor_GetNumProperties (NCDUdevMonitor *o);
+void NCDUdevMonitor_GetProperty (NCDUdevMonitor *o, int index, const char **name, const char **value);
+
+#endif

+ 345 - 0
udevmonitor/NCDUdevMonitorParser.c

@@ -0,0 +1,345 @@
+/**
+ * @file NCDUdevMonitorParser.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <misc/string_begins_with.h>
+#include <misc/balloc.h>
+#include <system/BLog.h>
+
+#include <udevmonitor/NCDUdevMonitorParser.h>
+
+#include <generated/blog_channel_NCDUdevMonitorParser.h>
+
+#define PROPERTY_REGEX "^([^=]+)=(.*)$"
+
+static uint8_t * find_end (uint8_t *buf, size_t len)
+{
+    while (len >= 2) {
+        if (buf[0] == '\n' && buf[1] == '\n') {
+            return (buf + 2);
+        }
+        buf++;
+        len--;
+    }
+    
+    return NULL;
+}
+
+static int parse_property (NCDUdevMonitorParser *o, char *data)
+{
+    ASSERT(o->ready_num_properties >= 0)
+    ASSERT(o->ready_num_properties <= o->max_properties)
+    
+    if (o->ready_num_properties == o->max_properties) {
+        BLog(BLOG_ERROR, "too many properties");
+        return 0;
+    }
+    struct NCDUdevMonitorParser_property *prop = &o->ready_properties[o->ready_num_properties];
+    
+    // execute property regex
+    regmatch_t matches[3];
+    if (regexec(&o->property_regex, data, 3, matches, 0) != 0) {
+        BLog(BLOG_ERROR, "failed to parse property");
+        return 0;
+    }
+    
+    // extract components
+    prop->name = data + matches[1].rm_so;
+    *(data + matches[1].rm_eo) = '\0';
+    prop->value = data + matches[2].rm_so;
+    *(data + matches[2].rm_eo) = '\0';
+    
+    // register property
+    o->ready_num_properties++;
+    
+    return 1;
+}
+
+static int parse_message (NCDUdevMonitorParser *o)
+{
+    ASSERT(!o->is_ready)
+    ASSERT(o->ready_len >= 2)
+    ASSERT(o->buf[o->ready_len - 2] == '\n')
+    ASSERT(o->buf[o->ready_len - 1] == '\n')
+    
+    // zero terminate message (replacing the second newline)
+    o->buf[o->ready_len - 1] = '\0';
+    
+    // start parsing
+    char *line = o->buf;
+    int first_line = 1;
+    
+    // set is not ready event
+    o->ready_is_ready_event = 0;
+    
+    // init properties
+    o->ready_num_properties = 0;
+    
+    do {
+        // find end of line
+        char *line_end = strchr(line, '\n');
+        ASSERT(line_end)
+        
+        // zero terminate line
+        *line_end = '\0';
+        
+        if (o->is_info_mode) {
+            // parse prefix
+            if (strlen(line) < 3 || line[1] != ':' || line[2] != ' ') {
+                BLog(BLOG_ERROR, "failed to parse head");
+                return 0;
+            }
+            char line_type = line[0];
+            char *line_value = line + 3;
+            
+            if (first_line) {
+                if (line_type != 'P') {
+                    BLog(BLOG_ERROR, "wrong first line type");
+                    return 0;
+                }
+            } else {
+                if (line_type == 'E') {
+                    if (!parse_property(o, line_value)) {
+                        return 0;
+                    }
+                }
+            }
+        } else {
+            if (first_line) {
+                // is this the initial informational message?
+                if (string_begins_with(line, "monitor")) {
+                    o->ready_is_ready_event = 1;
+                    break;
+                }
+                
+                // check first line
+                if (!string_begins_with(line, "UDEV ")) {
+                    BLog(BLOG_ERROR, "failed to parse head");
+                    return 0;
+                }
+            } else {
+                if (!parse_property(o, line)) {
+                    return 0;
+                }
+            }
+        }
+        
+        first_line = 0;
+        line = line_end + 1;
+    } while (*line);
+    
+    // set ready
+    o->is_ready = 1;
+    
+    return 1;
+}
+
+static void process_data (NCDUdevMonitorParser *o)
+{
+    ASSERT(!o->is_ready)
+    
+    while (1) {
+        // look for end of event
+        uint8_t *c = find_end(o->buf, o->buf_used);
+        if (!c) {
+            // check for out of buffer condition
+            if (o->buf_used == o->buf_size) {
+                BLog(BLOG_ERROR, "out of buffer");
+                o->buf_used = 0;
+            }
+            
+            // receive more data
+            StreamRecvInterface_Receiver_Recv(o->input, o->buf + o->buf_used, o->buf_size - o->buf_used);
+            return;
+        }
+        
+        // remember message length
+        o->ready_len = c - o->buf;
+        
+        // parse message
+        if (parse_message(o)) {
+            break;
+        }
+        
+        // shift buffer
+        memmove(o->buf, o->buf + o->ready_len, o->buf_used - o->ready_len);
+        o->buf_used -= o->ready_len;
+    }
+    
+    // call handler
+    o->handler(o->user);
+    return;
+}
+
+static void input_handler_done (NCDUdevMonitorParser *o, int data_len)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(!o->is_ready)
+    ASSERT(data_len > 0)
+    ASSERT(data_len <= o->buf_size - o->buf_used)
+    
+    // increment buffer position
+    o->buf_used += data_len;
+    
+    // process data
+    process_data(o);
+    return;
+}
+
+static void done_job_handler (NCDUdevMonitorParser *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->is_ready)
+    
+    // shift buffer
+    memmove(o->buf, o->buf + o->ready_len, o->buf_used - o->ready_len);
+    o->buf_used -= o->ready_len;
+    
+    // set not ready
+    o->is_ready = 0;
+    
+    // process data
+    process_data(o);
+    return;
+}
+
+int NCDUdevMonitorParser_Init (NCDUdevMonitorParser *o, StreamRecvInterface *input, int buf_size, int max_properties,
+                               int is_info_mode, BPendingGroup *pg, void *user,
+                               NCDUdevMonitorParser_handler handler)
+{
+    ASSERT(buf_size > 0)
+    ASSERT(max_properties >= 0)
+    ASSERT(is_info_mode == 0 || is_info_mode == 1)
+    
+    // init arguments
+    o->input = input;
+    o->buf_size = buf_size;
+    o->max_properties = max_properties;
+    o->is_info_mode = is_info_mode;
+    o->user = user;
+    o->handler = handler;
+    
+    // init input
+    StreamRecvInterface_Receiver_Init(o->input, (StreamRecvInterface_handler_done)input_handler_done, o);
+    
+    // init property regex
+    if (regcomp(&o->property_regex, PROPERTY_REGEX, REG_EXTENDED) != 0) {
+        BLog(BLOG_ERROR, "regcomp failed");
+        goto fail1;
+    }
+    
+    // init done job
+    BPending_Init(&o->done_job, pg, (BPending_handler)done_job_handler, o);
+    
+    // allocate buffer
+    if (!(o->buf = malloc(o->buf_size))) {
+        BLog(BLOG_ERROR, "malloc failed");
+        goto fail2;
+    }
+    
+    // set buffer position
+    o->buf_used = 0;
+    
+    // set not ready
+    o->is_ready = 0;
+    
+    // allocate properties
+    if (!(o->ready_properties = BAllocArray(o->max_properties, sizeof(o->ready_properties[0])))) {
+        BLog(BLOG_ERROR, "BAllocArray failed");
+        goto fail3;
+    }
+    
+    // start receiving
+    StreamRecvInterface_Receiver_Recv(o->input, o->buf, o->buf_size);
+    
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail3:
+    free(o->buf);
+fail2:
+    BPending_Free(&o->done_job);
+    regfree(&o->property_regex);
+fail1:
+    return 0;
+}
+
+void NCDUdevMonitorParser_Free (NCDUdevMonitorParser *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free properties
+    BFree(o->ready_properties);
+    
+    // free buffer
+    free(o->buf);
+    
+    // free done job
+    BPending_Free(&o->done_job);
+    
+    // free property regex
+    regfree(&o->property_regex);
+}
+
+void NCDUdevMonitorParser_AssertReady (NCDUdevMonitorParser *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->is_ready)
+}
+
+void NCDUdevMonitorParser_Done (NCDUdevMonitorParser *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->is_ready)
+    
+    // schedule done job
+    BPending_Set(&o->done_job);
+}
+
+int NCDUdevMonitorParser_IsReadyEvent (NCDUdevMonitorParser *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->is_ready)
+    
+    return o->ready_is_ready_event;
+}
+
+int NCDUdevMonitorParser_GetNumProperties (NCDUdevMonitorParser *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->is_ready)
+    
+    return o->ready_num_properties;
+}
+
+void NCDUdevMonitorParser_GetProperty (NCDUdevMonitorParser *o, int index, const char **name, const char **value)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->is_ready)
+    ASSERT(index >= 0)
+    ASSERT(index < o->ready_num_properties)
+    
+    *name = o->ready_properties[index].name;
+    *value = o->ready_properties[index].value;
+}

+ 69 - 0
udevmonitor/NCDUdevMonitorParser.h

@@ -0,0 +1,69 @@
+/**
+ * @file NCDUdevMonitorParser.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * This file is part of BadVPN.
+ * 
+ * BadVPN is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ * 
+ * BadVPN is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef BADVPN_UDEVMONITOR_NCDUDEVMONITORPARSER_H
+#define BADVPN_UDEVMONITOR_NCDUDEVMONITORPARSER_H
+
+#include <stdint.h>
+#include <regex.h>
+
+#include <misc/debug.h>
+#include <system/DebugObject.h>
+#include <flow/StreamRecvInterface.h>
+
+typedef void (*NCDUdevMonitorParser_handler) (void *user);
+
+struct NCDUdevMonitorParser_property {
+    char *name;
+    char *value;
+};
+
+typedef struct {
+    StreamRecvInterface *input;
+    int buf_size;
+    int max_properties;
+    int is_info_mode;
+    void *user;
+    NCDUdevMonitorParser_handler handler;
+    regex_t property_regex;
+    BPending done_job;
+    uint8_t *buf;
+    int buf_used;
+    int is_ready;
+    int ready_len;
+    int ready_is_ready_event;
+    struct NCDUdevMonitorParser_property *ready_properties;
+    int ready_num_properties;
+    DebugObject d_obj;
+} NCDUdevMonitorParser;
+
+int NCDUdevMonitorParser_Init (NCDUdevMonitorParser *o, StreamRecvInterface *input, int buf_size, int max_properties,
+                               int is_info_mode, BPendingGroup *pg, void *user,
+                               NCDUdevMonitorParser_handler handler) WARN_UNUSED;
+void NCDUdevMonitorParser_Free (NCDUdevMonitorParser *o);
+void NCDUdevMonitorParser_AssertReady (NCDUdevMonitorParser *o);
+void NCDUdevMonitorParser_Done (NCDUdevMonitorParser *o);
+int NCDUdevMonitorParser_IsReadyEvent (NCDUdevMonitorParser *o);
+int NCDUdevMonitorParser_GetNumProperties (NCDUdevMonitorParser *o);
+void NCDUdevMonitorParser_GetProperty (NCDUdevMonitorParser *o, int index, const char **name, const char **value);
+
+#endif