Added "foreach" to unlang
authorAlan T. DeKok <aland@freeradius.org>
Fri, 27 May 2011 10:58:39 +0000 (12:58 +0200)
committerAlan T. DeKok <aland@freeradius.org>
Fri, 27 May 2011 11:03:32 +0000 (13:03 +0200)
There's no "break" yet.

man/man5/unlang.5
src/main/modcall.c
src/main/xlat.c

index 64ebbba..93b75f5 100644 (file)
@@ -10,7 +10,7 @@
 .RE
 .sp
 ..
-.TH unlang 5 "19 May 2010" "" "FreeRADIUS Processing un-language"
+.TH unlang 5 "27 May 2011" "" "FreeRADIUS Processing un-language"
 .SH NAME
 unlang \- FreeRADIUS Processing un\-language
 .SH DESCRIPTION
@@ -91,6 +91,30 @@ returned false, and if the specified condition evaluates to true.
 .br
        }
 .DE
+.IP foreach
+.br
+Loops over a named variable, running the block for each copy of the
+named variable.  The return value of the block is the return value of
+the last statement executed.  There is currently no way to exit the
+loop early.
+
+The attribute name is just the name, e.g. reply:Reply-Message, with
+none of the usual variable referenced %{...}.  This is because it is a
+reference to the attribute, and not an expansion of the attribute.
+
+Inside of the "foreach" block, the attribute which is being looped
+over can be referenced as "Foreach-Variable-#".  Where "#" is the
+depth of the loop, starting at "0".  e.g. "Foreach-Variable-0".  The
+loops can be nested up to eight (8) deep, though this is not
+recommended.
+
+.DS
+       foreach Attribute-Name {
+.br
+               ...
+.br
+       }
+.DE
 .IP switch
 .br
 Evaluate the given string, and choose the first matching "case"
index f1908dd..aeb00a0 100644 (file)
@@ -51,6 +51,7 @@ struct modcallable {
        enum { MOD_SINGLE = 1, MOD_GROUP, MOD_LOAD_BALANCE, MOD_REDUNDANT_LOAD_BALANCE,
 #ifdef WITH_UNLANG
               MOD_IF, MOD_ELSE, MOD_ELSIF, MOD_UPDATE, MOD_SWITCH, MOD_CASE,
+              MOD_FOREACH,
 #endif
               MOD_POLICY, MOD_REFERENCE, MOD_XLAT } type;
        int method;
@@ -339,6 +340,7 @@ static const char *group_name[] = {
        "update",
        "switch",
        "case",
+       "foreach",
 #endif
        "policy"
 };
@@ -479,6 +481,52 @@ int modcall(int component, modcallable *c, REQUEST *request)
                        }
                        goto handle_result;
                }
+
+               if (child->type == MOD_FOREACH) {
+                       int i, depth = -1;
+                       VALUE_PAIR *vp;
+                       modgroup *g = mod_callabletogroup(child);
+
+                       for (i = 0; i < 8; i++) {
+                               if (!request_data_get(request, radius_get_vp, i)) {
+                                       depth = i;
+                                       break;
+                               }
+                       }
+
+                       if (depth < 0) {
+                               RDEBUG("ERROR: Foreach Nesting too deep!");
+                               myresult = RLM_MODULE_FAIL;
+                               goto handle_result;
+                       }
+
+                       if (radius_get_vp(request, child->name, &vp)) {
+                               RDEBUG2("Foreach %s", child->name);
+                               while (vp) {
+                                       request_data_add(request, modcall,
+                                                        depth, vp, NULL);
+
+                                       myresult = modcall(component,
+                                                          g->children,
+                                                          request);
+                                       if (myresult == MOD_ACTION_RETURN) {
+                                               myresult = RLM_MODULE_OK;
+                                               break;
+                                       }
+                                       vp = pairfind(vp->next,
+                                                     vp->attribute,
+                                                     vp->vendor);
+                               }
+
+                               /*
+                                *      Delete the cached attribute.
+                                */
+                               request_data_get(request, radius_get_vp, depth);
+                       }
+
+                       myresult = RLM_MODULE_OK;
+                       goto handle_result;
+               }
 #endif
        
                if (child->type == MOD_REFERENCE) {
@@ -552,6 +600,7 @@ int modcall(int component, modcallable *c, REQUEST *request)
                        case MOD_ELSE:
                        case MOD_ELSIF:
                        case MOD_CASE:
+                       case MOD_FOREACH:
 #endif
                        case MOD_GROUP:
                        case MOD_POLICY: /* same as MOD_GROUP */
@@ -757,6 +806,7 @@ int modcall(int component, modcallable *c, REQUEST *request)
                        case MOD_ELSE:
                        case MOD_ELSIF:
                        case MOD_CASE:
+                       case MOD_FOREACH:
 #endif
                        case MOD_GROUP:
                        case MOD_POLICY: /* same as MOD_GROUP */
@@ -794,10 +844,9 @@ int modcall(int component, modcallable *c, REQUEST *request)
                 */
                if (!stack.children[stack.pointer]) {
                do_return:
-                       rad_assert(stack.pointer > 0);
                        myresult = stack.result[stack.pointer];
+                       if (stack.pointer == 0) break;
                        stack.pointer--;
-
                        if (stack.pointer == 0) break;
 
                        RDEBUG2("%.*s- %s %s returns %s",
@@ -1432,7 +1481,7 @@ static modcallable *do_compile_modswitch(modcallable *parent,
        }
 
        if (!cf_item_find_next(cs, NULL)) {
-               cf_log_err(cf_sectiontoitem(cs), "'switch' statments cannot be empty.");
+               cf_log_err(cf_sectiontoitem(cs), "'switch' statements cannot be empty.");
                return NULL;
        }
 
@@ -1479,6 +1528,33 @@ static modcallable *do_compile_modswitch(modcallable *parent,
        csingle->type = MOD_SWITCH;
        return csingle;
 }
+
+static modcallable *do_compile_modforeach(modcallable *parent,
+                                         int component, CONF_SECTION *cs,
+                                         const char *name2)
+{
+       modcallable *csingle;
+
+       component = component;  /* -Wunused */
+
+       if (!cf_section_name2(cs)) {
+               cf_log_err(cf_sectiontoitem(cs),
+                          "You must specify an attribute to loop over in 'foreach'.");
+               return NULL;
+       }
+
+       if (!cf_item_find_next(cs, NULL)) {
+               cf_log_err(cf_sectiontoitem(cs), "'foreach' blocks cannot be empty.");
+               return NULL;
+       }
+
+       csingle= do_compile_modgroup(parent, component, cs,
+                                    GROUPTYPE_SIMPLE, GROUPTYPE_SIMPLE);
+       if (!csingle) return NULL;
+       csingle->name = name2;
+       csingle->type = MOD_FOREACH;
+       return csingle;
+}
 #endif
 
 static modcallable *do_compile_modserver(modcallable *parent,
@@ -1800,6 +1876,15 @@ static modcallable *do_compile_modsingle(modcallable *parent,
                        }
 
                        return csingle;
+
+               } else  if (strcmp(modrefname, "foreach") == 0) {
+                       *modname = name2;
+
+                       csingle = do_compile_modforeach(parent, component, cs,
+                                                       name2);
+                       if (!csingle) return NULL;
+
+                       return csingle;
 #endif
                } /* else it's something like sql { fail = 1 ...} */
 
index 3e3edad..57d3b5b 100644 (file)
@@ -53,6 +53,19 @@ static const char * const internal_xlat[] = {"check",
                                             "outer.reply",
                                             "outer.control",
                                             NULL};
+#ifdef WITH_UNLANG
+static const char * const xlat_foreach_names[] = {"Foreach-Variable-0",
+                                                 "Foreach-Variable-1",
+                                                 "Foreach-Variable-2",
+                                                 "Foreach-Variable-3",
+                                                 "Foreach-Variable-4",
+                                                 "Foreach-Variable-5",
+                                                 "Foreach-Variable-6",
+                                                 "Foreach-Variable-7",
+                                                 "Foreach-Variable-8",
+                                                 "Foreach-Variable-9",
+                                                 NULL};
+#endif
 
 #if REQUEST_MAX_REGEX > 8
 #error Please fix the following line
@@ -445,6 +458,30 @@ static size_t xlat_integer(UNUSED void *instance, REQUEST *request,
        return snprintf(out, outlen, "%u", vp->vp_integer);
 }
 
+
+#ifdef WITH_UNLANG
+/*
+ *     Dynamically translate for check:, request:, reply:, etc.
+ */
+static size_t xlat_foreach(void *instance, REQUEST *request,
+                         char *fmt, char *out, size_t outlen,
+                         RADIUS_ESCAPE_STRING func)
+{
+       VALUE_PAIR      *vp;
+
+       /*
+        *      See modcall, "FOREACH" for how this works.
+        */
+       vp = request_data_reference(request, radius_get_vp, *(int*) instance);
+       if (!vp) {
+               *out = '\0';
+               return 0;
+       }
+
+       return valuepair2str(out, outlen, vp, vp->type, func);  
+}
+#endif
+
 /*
  *     Print data as string, if possible.
  */
@@ -702,6 +739,14 @@ int xlat_register(const char *module, RAD_XLAT_FUNC func, void *instance)
                        c = xlat_find(internal_xlat[i]);
                        rad_assert(c != NULL);
                        c->internal = TRUE;
+
+#ifdef WITH_UNLANG
+                       xlat_register(xlat_foreach_names[i],
+                                     xlat_foreach, &xlat_inst[i]);
+                       c = xlat_find(xlat_foreach_names[i]);
+                       rad_assert(c != NULL);
+                       c->internal = TRUE;
+#endif
                }
 
                /*