ambrop7 14 лет назад
Родитель
Сommit
9d33c746ff

+ 1 - 0
blog_channels.txt

@@ -27,6 +27,7 @@ ncd_index 4
 ncd_alias 4
 ncd_process_manager 4
 ncd_ondemand 4
+ncd_foreach 4
 ncd_net_backend_waitdevice 4
 ncd_net_backend_waitlink 4
 ncd_net_backend_badvpn 4

+ 4 - 0
generated/blog_channel_ncd_foreach.h

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

+ 66 - 65
generated/blog_channels_defines.h

@@ -27,68 +27,69 @@
 #define BLOG_CHANNEL_ncd_alias 26
 #define BLOG_CHANNEL_ncd_process_manager 27
 #define BLOG_CHANNEL_ncd_ondemand 28
-#define BLOG_CHANNEL_ncd_net_backend_waitdevice 29
-#define BLOG_CHANNEL_ncd_net_backend_waitlink 30
-#define BLOG_CHANNEL_ncd_net_backend_badvpn 31
-#define BLOG_CHANNEL_ncd_net_backend_wpa_supplicant 32
-#define BLOG_CHANNEL_ncd_net_backend_rfkill 33
-#define BLOG_CHANNEL_ncd_net_up 34
-#define BLOG_CHANNEL_ncd_net_dns 35
-#define BLOG_CHANNEL_ncd_net_iptables 36
-#define BLOG_CHANNEL_ncd_net_ipv4_addr 37
-#define BLOG_CHANNEL_ncd_net_ipv4_route 38
-#define BLOG_CHANNEL_ncd_net_ipv4_dhcp 39
-#define BLOG_CHANNEL_ncd_net_ipv4_arp_probe 40
-#define BLOG_CHANNEL_ncd_net_watch_interfaces 41
-#define BLOG_CHANNEL_ncd_sys_watch_input 42
-#define BLOG_CHANNEL_ncd_sys_evdev 43
-#define BLOG_CHANNEL_ncd_sys_watch_directory 44
-#define BLOG_CHANNEL_StreamPeerIO 45
-#define BLOG_CHANNEL_DatagramPeerIO 46
-#define BLOG_CHANNEL_BReactor 47
-#define BLOG_CHANNEL_BSignal 48
-#define BLOG_CHANNEL_FragmentProtoAssembler 49
-#define BLOG_CHANNEL_BPredicate 50
-#define BLOG_CHANNEL_ServerConnection 51
-#define BLOG_CHANNEL_Listener 52
-#define BLOG_CHANNEL_DataProto 53
-#define BLOG_CHANNEL_FrameDecider 54
-#define BLOG_CHANNEL_BSocksClient 55
-#define BLOG_CHANNEL_BDHCPClientCore 56
-#define BLOG_CHANNEL_BDHCPClient 57
-#define BLOG_CHANNEL_NCDIfConfig 58
-#define BLOG_CHANNEL_BUnixSignal 59
-#define BLOG_CHANNEL_BProcess 60
-#define BLOG_CHANNEL_PRStreamSink 61
-#define BLOG_CHANNEL_PRStreamSource 62
-#define BLOG_CHANNEL_PacketProtoDecoder 63
-#define BLOG_CHANNEL_DPRelay 64
-#define BLOG_CHANNEL_BThreadWork 65
-#define BLOG_CHANNEL_DPReceive 66
-#define BLOG_CHANNEL_BInputProcess 67
-#define BLOG_CHANNEL_NCDUdevMonitorParser 68
-#define BLOG_CHANNEL_NCDUdevMonitor 69
-#define BLOG_CHANNEL_NCDUdevCache 70
-#define BLOG_CHANNEL_NCDUdevManager 71
-#define BLOG_CHANNEL_BTime 72
-#define BLOG_CHANNEL_BEncryption 73
-#define BLOG_CHANNEL_SPProtoDecoder 74
-#define BLOG_CHANNEL_LineBuffer 75
-#define BLOG_CHANNEL_BTap 76
-#define BLOG_CHANNEL_lwip 77
-#define BLOG_CHANNEL_NCDConfigParser 78
-#define BLOG_CHANNEL_nsskey 79
-#define BLOG_CHANNEL_addr 80
-#define BLOG_CHANNEL_PasswordListener 81
-#define BLOG_CHANNEL_NCDInterfaceMonitor 82
-#define BLOG_CHANNEL_NCDRfkillMonitor 83
-#define BLOG_CHANNEL_udpgw 84
-#define BLOG_CHANNEL_UdpGwClient 85
-#define BLOG_CHANNEL_SocksUdpGwClient 86
-#define BLOG_CHANNEL_BNetwork 87
-#define BLOG_CHANNEL_BConnection 88
-#define BLOG_CHANNEL_BSSLConnection 89
-#define BLOG_CHANNEL_BDatagram 90
-#define BLOG_CHANNEL_PeerChat 91
-#define BLOG_CHANNEL_BArpProbe 92
-#define BLOG_NUM_CHANNELS 93
+#define BLOG_CHANNEL_ncd_foreach 29
+#define BLOG_CHANNEL_ncd_net_backend_waitdevice 30
+#define BLOG_CHANNEL_ncd_net_backend_waitlink 31
+#define BLOG_CHANNEL_ncd_net_backend_badvpn 32
+#define BLOG_CHANNEL_ncd_net_backend_wpa_supplicant 33
+#define BLOG_CHANNEL_ncd_net_backend_rfkill 34
+#define BLOG_CHANNEL_ncd_net_up 35
+#define BLOG_CHANNEL_ncd_net_dns 36
+#define BLOG_CHANNEL_ncd_net_iptables 37
+#define BLOG_CHANNEL_ncd_net_ipv4_addr 38
+#define BLOG_CHANNEL_ncd_net_ipv4_route 39
+#define BLOG_CHANNEL_ncd_net_ipv4_dhcp 40
+#define BLOG_CHANNEL_ncd_net_ipv4_arp_probe 41
+#define BLOG_CHANNEL_ncd_net_watch_interfaces 42
+#define BLOG_CHANNEL_ncd_sys_watch_input 43
+#define BLOG_CHANNEL_ncd_sys_evdev 44
+#define BLOG_CHANNEL_ncd_sys_watch_directory 45
+#define BLOG_CHANNEL_StreamPeerIO 46
+#define BLOG_CHANNEL_DatagramPeerIO 47
+#define BLOG_CHANNEL_BReactor 48
+#define BLOG_CHANNEL_BSignal 49
+#define BLOG_CHANNEL_FragmentProtoAssembler 50
+#define BLOG_CHANNEL_BPredicate 51
+#define BLOG_CHANNEL_ServerConnection 52
+#define BLOG_CHANNEL_Listener 53
+#define BLOG_CHANNEL_DataProto 54
+#define BLOG_CHANNEL_FrameDecider 55
+#define BLOG_CHANNEL_BSocksClient 56
+#define BLOG_CHANNEL_BDHCPClientCore 57
+#define BLOG_CHANNEL_BDHCPClient 58
+#define BLOG_CHANNEL_NCDIfConfig 59
+#define BLOG_CHANNEL_BUnixSignal 60
+#define BLOG_CHANNEL_BProcess 61
+#define BLOG_CHANNEL_PRStreamSink 62
+#define BLOG_CHANNEL_PRStreamSource 63
+#define BLOG_CHANNEL_PacketProtoDecoder 64
+#define BLOG_CHANNEL_DPRelay 65
+#define BLOG_CHANNEL_BThreadWork 66
+#define BLOG_CHANNEL_DPReceive 67
+#define BLOG_CHANNEL_BInputProcess 68
+#define BLOG_CHANNEL_NCDUdevMonitorParser 69
+#define BLOG_CHANNEL_NCDUdevMonitor 70
+#define BLOG_CHANNEL_NCDUdevCache 71
+#define BLOG_CHANNEL_NCDUdevManager 72
+#define BLOG_CHANNEL_BTime 73
+#define BLOG_CHANNEL_BEncryption 74
+#define BLOG_CHANNEL_SPProtoDecoder 75
+#define BLOG_CHANNEL_LineBuffer 76
+#define BLOG_CHANNEL_BTap 77
+#define BLOG_CHANNEL_lwip 78
+#define BLOG_CHANNEL_NCDConfigParser 79
+#define BLOG_CHANNEL_nsskey 80
+#define BLOG_CHANNEL_addr 81
+#define BLOG_CHANNEL_PasswordListener 82
+#define BLOG_CHANNEL_NCDInterfaceMonitor 83
+#define BLOG_CHANNEL_NCDRfkillMonitor 84
+#define BLOG_CHANNEL_udpgw 85
+#define BLOG_CHANNEL_UdpGwClient 86
+#define BLOG_CHANNEL_SocksUdpGwClient 87
+#define BLOG_CHANNEL_BNetwork 88
+#define BLOG_CHANNEL_BConnection 89
+#define BLOG_CHANNEL_BSSLConnection 90
+#define BLOG_CHANNEL_BDatagram 91
+#define BLOG_CHANNEL_PeerChat 92
+#define BLOG_CHANNEL_BArpProbe 93
+#define BLOG_NUM_CHANNELS 94

+ 1 - 0
generated/blog_channels_list.h

@@ -27,6 +27,7 @@
 {.name = "ncd_alias", .loglevel = 4},
 {.name = "ncd_process_manager", .loglevel = 4},
 {.name = "ncd_ondemand", .loglevel = 4},
+{.name = "ncd_foreach", .loglevel = 4},
 {.name = "ncd_net_backend_waitdevice", .loglevel = 4},
 {.name = "ncd_net_backend_waitlink", .loglevel = 4},
 {.name = "ncd_net_backend_badvpn", .loglevel = 4},

+ 1 - 0
ncd/CMakeLists.txt

@@ -61,6 +61,7 @@ add_executable(badvpn-ncd
     modules/alias.c
     modules/process_manager.c
     modules/ondemand.c
+    modules/foreach.c
     modules/net_backend_waitdevice.c
     modules/net_backend_waitlink.c
     modules/net_backend_badvpn.c

+ 503 - 0
ncd/modules/foreach.c

@@ -0,0 +1,503 @@
+/**
+ * @file foreach.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.
+ * 
+ * @section DESCRIPTION
+ * 
+ * Synopsis:
+ *   foreach(list list, string template, list args)
+ * 
+ * Description:
+ *   Initializes a template process for each element of list, sequentially,
+ *   obeying to the usual execution model of NCD.
+ *   It's equivalent to (except for special variables):
+ * 
+ *   call(template, args);
+ *   ...
+ *   call(template, args); # one call() for every element of list
+ * 
+ * Template process specials:
+ * 
+ *   _index - index of the list element corresponding to the template process,
+ *            as a decimal string, starting from zero
+ *   _elem - element of list corresponding to the template process
+ *   _caller.X - X as seen from the foreach() statement
+ */
+
+#include <stdlib.h>
+
+#include <misc/balloc.h>
+#include <misc/string_begins_with.h>
+#include <system/BReactor.h>
+#include <ncd/NCDModule.h>
+
+#include <generated/blog_channel_ncd_foreach.h>
+
+#define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+#define ISTATE_WORKING 1
+#define ISTATE_UP 2
+#define ISTATE_WAITING 3
+#define ISTATE_TERMINATING 4
+
+#define ESTATE_FORGOTTEN 1
+#define ESTATE_DOWN 2
+#define ESTATE_UP 3
+#define ESTATE_WAITING 4
+#define ESTATE_TERMINATING 5
+
+struct element;
+
+struct instance {
+    NCDModuleInst *i;
+    char *template_name;
+    NCDValue *args;
+    BTimer timer;
+    size_t num_elems;
+    struct element *elems;
+    size_t gp; // good pointer
+    size_t ip; // initialized pointer
+    int state;
+};
+
+struct element {
+    struct instance *inst;
+    size_t i;
+    NCDValue *value;
+    NCDModuleProcess process;
+    int state;
+};
+
+static void assert_state (struct instance *o);
+static void work (struct instance *o);
+static void advance (struct instance *o);
+static void timer_handler (struct instance *o);
+static void element_process_handler_event (struct element *e, int event);
+static int element_process_func_getspecialvar (struct element *e, const char *name, NCDValue *out);
+static NCDModuleInst * element_process_func_getspecialobj (struct element *e, const char *name);
+static void instance_free (struct instance *o);
+
+static void assert_state (struct instance *o)
+{
+    ASSERT(o->gp <= o->num_elems)
+    ASSERT(o->ip <= o->num_elems)
+    ASSERT(o->gp <= o->ip)
+    
+    // check GP
+    for (size_t i = 0; i < o->gp; i++) {
+        if (o->gp > 0 && i == o->gp - 1) {
+            ASSERT(o->elems[i].state == ESTATE_UP || o->elems[i].state == ESTATE_DOWN ||
+                   o->elems[i].state == ESTATE_WAITING)
+            
+        } else {
+            ASSERT(o->elems[i].state == ESTATE_UP)
+        }
+    }
+    
+    // check IP
+    size_t ip = o->num_elems;
+    while (ip > 0 && o->elems[ip - 1].state == ESTATE_FORGOTTEN) {
+        ip--;
+    }
+    ASSERT(o->ip == ip)
+}
+
+static void work (struct instance *o)
+{
+    assert_state(o);
+    
+    // stop timer
+    BReactor_RemoveTimer(o->i->reactor, &o->timer);
+    
+    if (o->state == ISTATE_WAITING) {
+        return;
+    }
+    
+    if (o->state == ISTATE_UP && !(o->gp == o->ip && o->gp == o->num_elems && (o->gp == 0 || o->elems[o->gp - 1].state == ESTATE_UP))) {
+        // signal down
+        NCDModuleInst_Backend_Down(o->i);
+        
+        // set state waiting
+        o->state = ISTATE_WAITING;
+        return;
+    }
+    
+    if (o->gp < o->ip) {
+        // get last element
+        struct element *le = &o->elems[o->ip - 1];
+        ASSERT(le->state != ESTATE_FORGOTTEN)
+        
+        // start terminating if not already
+        if (le->state != ESTATE_TERMINATING) {
+            // request termination
+            NCDModuleProcess_Terminate(&le->process);
+            
+            // set element state terminating
+            le->state = ESTATE_TERMINATING;
+        }
+        
+        return;
+    }
+    
+    if (o->state == ISTATE_TERMINATING) {
+        // finally die
+        instance_free(o);
+        return;
+    }
+    
+    if (o->gp == o->num_elems && (o->gp == 0 || o->elems[o->gp - 1].state == ESTATE_UP)) {
+        if (o->state == ISTATE_WORKING) {
+            // signal up
+            NCDModuleInst_Backend_Up(o->i);
+            
+            // set state up
+            o->state = ISTATE_UP;
+        }
+        
+        return;
+    }
+    
+    if (o->gp > 0 && o->elems[o->gp - 1].state == ESTATE_WAITING) {
+        // get last element
+        struct element *le = &o->elems[o->gp - 1];
+        
+        // continue process
+        NCDModuleProcess_Continue(&le->process);
+        
+        // set state down
+        le->state = ESTATE_DOWN;
+        return;
+    }
+    
+    if (o->gp > 0 && o->elems[o->gp - 1].state == ESTATE_DOWN) {
+        return;
+    }
+    
+    ASSERT(o->gp == 0 || o->elems[o->gp - 1].state == ESTATE_UP)
+    
+    advance(o);
+    return;
+}
+
+static void advance (struct instance *o)
+{
+    assert_state(o);
+    ASSERT(o->gp == o->ip)
+    ASSERT(o->gp < o->num_elems)
+    ASSERT(o->gp == 0 || o->elems[o->gp - 1].state == ESTATE_UP)
+    ASSERT(o->elems[o->gp].state == ESTATE_FORGOTTEN)
+    
+    // get next element
+    struct element *e = &o->elems[o->gp];
+    
+    // copy arguments
+    NCDValue args;
+    if (!NCDValue_InitCopy(&args, o->args)) {
+        ModuleLog(o->i, BLOG_ERROR, "NCDValue_InitCopy failed");
+        goto fail;
+    }
+    
+    // init process
+    if (!NCDModuleProcess_Init(&e->process, o->i, o->template_name, args, e, (NCDModuleProcess_handler_event)element_process_handler_event)) {
+        ModuleLog(o->i, BLOG_ERROR, "NCDModuleProcess_Init failed");
+        NCDValue_Free(&args);
+        goto fail;
+    }
+    
+    // set special functions
+    NCDModuleProcess_SetSpecialFuncs(&e->process,
+                                     (NCDModuleProcess_func_getspecialvar)element_process_func_getspecialvar,
+                                     (NCDModuleProcess_func_getspecialobj)element_process_func_getspecialobj);
+    
+    // set element state down
+    e->state = ESTATE_DOWN;
+    
+    // increment GP and IP
+    o->gp++;
+    o->ip++;
+    return;
+    
+fail:
+    // set timer
+    BReactor_SetTimer(o->i->reactor, &o->timer);
+}
+
+static void timer_handler (struct instance *o)
+{
+    assert_state(o);
+    ASSERT(o->gp == o->ip)
+    ASSERT(o->gp < o->num_elems)
+    ASSERT(o->gp == 0 || o->elems[o->gp - 1].state == ESTATE_UP)
+    ASSERT(o->elems[o->gp].state == ESTATE_FORGOTTEN)
+    
+    advance(o);
+    return;
+}
+
+static void element_process_handler_event (struct element *e, int event)
+{
+    struct instance *o = e->inst;
+    assert_state(o);
+    ASSERT(e->i < o->ip)
+    ASSERT(e->state != ESTATE_FORGOTTEN)
+    
+    switch (event) {
+        case NCDMODULEPROCESS_EVENT_UP: {
+            ASSERT(e->state == ESTATE_DOWN)
+            ASSERT(o->gp == o->ip)
+            ASSERT(o->gp == e->i + 1)
+            
+            // set element state up
+            e->state = ESTATE_UP;
+        } break;
+        
+        case NCDMODULEPROCESS_EVENT_DOWN: {
+            ASSERT(e->state == ESTATE_UP)
+            
+            // set element state waiting
+            e->state = ESTATE_WAITING;
+            
+            // bump down GP
+            if (o->gp > e->i + 1) {
+                o->gp = e->i + 1;
+            }
+        } break;
+        
+        case NCDMODULEPROCESS_EVENT_TERMINATED: {
+            ASSERT(e->state == ESTATE_TERMINATING)
+            ASSERT(o->gp < o->ip)
+            ASSERT(o->ip == e->i + 1)
+            
+            // free process
+            NCDModuleProcess_Free(&e->process);
+            
+            // set element state forgotten
+            e->state = ESTATE_FORGOTTEN;
+            
+            // decrement IP
+            o->ip--;
+        } break;
+        
+        default: ASSERT(0);
+    }
+    
+    work(o);
+    return;
+}
+
+static int element_process_func_getspecialvar (struct element *e, const char *name, NCDValue *out)
+{
+    struct instance *o = e->inst;
+    ASSERT(e->state != ESTATE_FORGOTTEN)
+    
+    if (e->i >= o->gp) {
+        BLog(BLOG_ERROR, "tried to resolve variable %s but it's dirty", name);
+        return 0;
+    }
+    
+    if (!strcmp(name, "_index")) {
+        char str[64];
+        snprintf(str, sizeof(str), "%zu", e->i);
+        
+        if (!NCDValue_InitString(out, str)) {
+            ModuleLog(o->i, BLOG_ERROR, "NCDValue_InitString failed");
+            return 0;
+        }
+        
+        return 1;
+    }
+    
+    if (!strcmp(name, "_elem")) {
+        if (!NCDValue_InitCopy(out, e->value)) {
+            ModuleLog(o->i, BLOG_ERROR, "NCDValue_InitCopy failed");
+            return 0;
+        }
+        
+        return 1;
+    }
+    
+    size_t p;
+    if (p = string_begins_with(name, "_caller.")) {
+        return NCDModuleInst_Backend_GetVar(o->i, name + p, out);
+    }
+    
+    return 0;
+}
+
+static NCDModuleInst * element_process_func_getspecialobj (struct element *e, const char *name)
+{
+    struct instance *o = e->inst;
+    ASSERT(e->state != ESTATE_FORGOTTEN)
+    
+    if (e->i >= o->gp) {
+        BLog(BLOG_ERROR, "tried to resolve object %s but it's dirty", name);
+        return NULL;
+    }
+    
+    size_t p;
+    if (p = string_begins_with(name, "_caller.")) {
+        return NCDModuleInst_Backend_GetObj(o->i, name + p);
+    }
+    
+    return NULL;
+}
+
+static void func_new (NCDModuleInst *i)
+{
+    // allocate instance
+    struct instance *o = malloc(sizeof(*o));
+    if (!o) {
+        ModuleLog(i, BLOG_ERROR, "failed to allocate instance");
+        goto fail0;
+    }
+    NCDModuleInst_Backend_SetUser(i, o);
+    
+    // init arguments
+    o->i = i;
+    
+    // read arguments
+    NCDValue *arg_list;
+    NCDValue *arg_template;
+    NCDValue *arg_args;
+    if (!NCDValue_ListRead(i->args, 3, &arg_list, &arg_template, &arg_args)) {
+        ModuleLog(i, BLOG_ERROR, "wrong arity");
+        goto fail1;
+    }
+    if (NCDValue_Type(arg_list) != NCDVALUE_LIST || NCDValue_Type(arg_template) != NCDVALUE_STRING ||
+        NCDValue_Type(arg_args) != NCDVALUE_LIST
+    ) {
+        ModuleLog(i, BLOG_ERROR, "wrong type");
+        goto fail1;
+    }
+    o->template_name = NCDValue_StringValue(arg_template);
+    o->args = arg_args;
+    
+    // init timer
+    BTimer_Init(&o->timer, 5000, (BTimer_handler)timer_handler, o);
+    
+    // count elements
+    o->num_elems = NCDValue_ListCount(arg_list);
+    
+    // allocate elements
+    if (!(o->elems = BAllocArray(o->num_elems, sizeof(o->elems[0])))) {
+        ModuleLog(i, BLOG_ERROR, "BAllocArray failed");
+        goto fail1;
+    }
+    
+    NCDValue *ev = NCDValue_ListFirst(arg_list);
+    
+    for (size_t i = 0; i < o->num_elems; i++) {
+        struct element *e = &o->elems[i];
+        
+        // set instance
+        e->inst = o;
+        
+        // set index
+        e->i = i;
+        
+        // set value
+        e->value = ev;
+        
+        // set state forgotten
+        e->state = ESTATE_FORGOTTEN;
+        
+        ev = NCDValue_ListNext(arg_list, ev);
+    }
+    
+    // set GP and IP zero
+    o->gp = 0;
+    o->ip = 0;
+    
+    // set state working
+    o->state = ISTATE_WORKING;
+    
+    work(o);
+    return;
+    
+fail1:
+    free(o);
+fail0:
+    NCDModuleInst_Backend_SetError(i);
+    NCDModuleInst_Backend_Dead(i);
+}
+
+static void instance_free (struct instance *o)
+{
+    NCDModuleInst *i = o->i;
+    ASSERT(o->gp == 0)
+    ASSERT(o->ip == 0)
+    
+    // free elements
+    BFree(o->elems);
+    
+    // free timer
+    BReactor_RemoveTimer(o->i->reactor, &o->timer);
+    
+    // free instance
+    free(o);
+    
+    NCDModuleInst_Backend_Dead(i);
+}
+
+static void func_die (void *vo)
+{
+    struct instance *o = vo;
+    assert_state(o);
+    ASSERT(o->state != ISTATE_TERMINATING)
+    
+    // set GP zero
+    o->gp = 0;
+    
+    // set state terminating
+    o->state = ISTATE_TERMINATING;
+    
+    work(o);
+    return;
+}
+
+static void func_clean (void *vo)
+{
+    struct instance *o = vo;
+    
+    if (o->state != ISTATE_WAITING) {
+        return;
+    }
+    
+    // set state working
+    o->state = ISTATE_WORKING;
+    
+    work(o);
+    return;
+}
+
+static const struct NCDModule modules[] = {
+    {
+        .type = "foreach",
+        .func_new = func_new,
+        .func_die = func_die,
+        .func_clean = func_clean
+    }, {
+        .type = NULL
+    }
+};
+
+const struct NCDModuleGroup ncdmodule_foreach = {
+    .modules = modules
+};

+ 2 - 0
ncd/modules/modules.h

@@ -51,6 +51,7 @@ extern const struct NCDModuleGroup ncdmodule_index;
 extern const struct NCDModuleGroup ncdmodule_alias;
 extern const struct NCDModuleGroup ncdmodule_process_manager;
 extern const struct NCDModuleGroup ncdmodule_ondemand;
+extern const struct NCDModuleGroup ncdmodule_foreach;
 extern const struct NCDModuleGroup ncdmodule_net_backend_waitdevice;
 extern const struct NCDModuleGroup ncdmodule_net_backend_waitlink;
 extern const struct NCDModuleGroup ncdmodule_net_backend_badvpn;
@@ -99,6 +100,7 @@ static const struct NCDModuleGroup *ncd_modules[] = {
     &ncdmodule_alias,
     &ncdmodule_process_manager,
     &ncdmodule_ondemand,
+    &ncdmodule_foreach,
     &ncdmodule_net_backend_waitdevice,
     &ncdmodule_net_backend_waitlink,
     &ncdmodule_net_backend_badvpn,