hotplug2: patches from OpenWRT/svn
[tomato.git] / release / src / router / hotplug2 / rules.c
blobe2d40e3c3539b4a6247bfbcb3aad6f2894603c9c
1 /*****************************************************************************\
2 * _ _ _ _ ___ *
3 * | || | ___ | |_ _ __ | | _ _ __ _ |_ ) *
4 * | __ |/ _ \| _|| '_ \| || || |/ _` | / / *
5 * |_||_|\___/ \__|| .__/|_| \_,_|\__, |/___| *
6 * |_| |___/ *
7 \*****************************************************************************/
9 #include <fcntl.h>
10 #include <stdio.h>
11 #include <unistd.h>
12 #include <regex.h>
13 #include <ctype.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <errno.h>
17 #include <libgen.h>
18 #include <pwd.h>
19 #include <grp.h>
20 #include <sys/wait.h>
21 #include <sys/types.h>
22 #include <sys/stat.h>
24 #include "mem_utils.h"
25 #include "filemap_utils.h"
26 #include "hotplug2.h"
27 #include "rules.h"
30 /**
31 * Function supplementing 'mkdir -p'.
33 * @1 Path to be mkdir'd
35 * Returns: void
37 static void mkdir_p(char *path) {
38 char *ptr;
39 struct stat statbuf;
41 path = strdup(path);
42 path = dirname(path);
43 stat(path, &statbuf);
44 /* All is well... */
45 if (S_ISDIR(statbuf.st_mode)) {
46 free(path);
47 return;
50 for (ptr = path; ptr != NULL; ptr = strchr(ptr, '/')) {
51 if (ptr == path) {
52 ptr++;
53 continue;
56 errno = 0;
57 *ptr='\0';
58 mkdir(path, 0755);
59 *ptr='/';
60 if (errno != 0 && errno != EEXIST)
61 break;
63 ptr++;
65 mkdir(path, 0755);
66 free(path);
69 /**
70 * Function supplementing 'rmdir -p'.
72 * @1 Path to be rmdir'd
74 * Returns: void
76 static void rmdir_p(char *path) {
77 char *ptr;
79 path = strdup(path);
80 ptr = path;
81 while (ptr != NULL) {
82 ptr = strrchr(path, '/');
83 if (ptr == NULL)
84 break;
86 *ptr = '\0';
88 if (rmdir(path))
89 break;
91 free(path);
94 /**
95 * Replaces all needles by a given value.
97 * @1 Haystack (which gets free'd in the function)
98 * @2 Needle
99 * @3 Needle replacement
101 * Returns: Newly allocated haysteck after replacement.
103 static char *replace_str(char *hay, char *needle, char *replacement) {
104 char *ptr, *start, *bptr, *buf;
105 int occurences, j;
106 size_t needle_len;
107 size_t replacement_len;
108 size_t haystack_len;
110 if (replacement == NULL || *replacement=='\0')
111 return hay;
113 if (needle == NULL || *needle=='\0')
114 return hay;
116 occurences = 0;
117 j = 0;
118 for (ptr = hay; *ptr != '\0'; ++ptr) {
119 if (needle[j] == *ptr) {
120 ++j;
121 if (needle[j] == '\0') {
122 *(ptr-j+1) = '\0'; // mark occurence
123 occurences++;
124 j = 0;
126 } else {
127 j=0;
131 if (occurences <= 0)
132 return hay;
134 haystack_len = (size_t)(ptr - hay);
135 replacement_len = strlen(replacement);
136 needle_len = strlen(needle);
138 buf = xmalloc(haystack_len + (replacement_len - needle_len) * occurences + 1);
139 start = hay;
140 ptr = hay;
142 bptr = buf;
143 while (occurences-- > 0) {
144 while (*ptr != '\0')
145 ++ptr;
147 if (ptr-start > 0) {
148 memcpy(bptr, start, ptr - start);
149 bptr +=ptr - start;
152 memcpy(bptr, replacement, replacement_len);
153 bptr+=replacement_len;
154 ptr += needle_len;
155 start = ptr;
158 while (*ptr != '\0')
159 ++ptr;
161 if (ptr-start > 0) {
162 memcpy(bptr, start, ptr - start);
163 bptr +=ptr - start;
165 *bptr='\0';
167 free(hay);
169 return buf;
173 * Trivial utility, figuring out whether a character is escaped or not.
175 * @1 Haystack
176 * @2 Pointer to the character in question
178 * Returns: 1 if escaped, 0 otherwise
180 static inline int isescaped(char *hay, char *ptr) {
181 if (ptr <= hay)
182 return 0;
184 if (*(ptr-1) != '\\')
185 return 0;
187 return 1;
191 * Performs replacement of all keys by their value based on the hotplug
192 * event structure. Keys are identified as strings %KEY%.
194 * @1 Haystack
195 * @2 Hotplug event structure
197 * Returns: Newly allocated haystack (old is freed)
199 static char *replace_key_by_value(char *hay, struct hotplug2_event_t *event) {
200 char *sptr = hay, *ptr = hay;
201 char *buf, *replacement;
203 while ((sptr = strchr(sptr, '%')) != NULL) {
204 ptr = strchr(sptr+1, '%');
205 if (ptr != NULL) {
206 buf = xmalloc(ptr - sptr + 2);
207 buf[ptr - sptr + 1] = '\0';
208 memcpy(buf, sptr, ptr - sptr + 1);
210 buf[ptr - sptr] = '\0';
211 replacement = get_hotplug2_value_by_key(event, &buf[1]);
212 buf[ptr - sptr] = '%';
214 if (replacement != NULL) {
215 hay = replace_str(hay, buf, replacement);
216 sptr = hay;
217 } else {
218 sptr++;
221 free(buf);
222 } else {
223 sptr++;
227 hay = replace_str(hay, "%\\", "%");
229 return hay;
233 * Obtains all information from hotplug event structure about a device node.
234 * Creates the device node at a given path (expandable by keys) and with
235 * given mode.
237 * @1 Hotplug event structure
238 * @2 Path (may contain keys)
239 * @3 Mode of the file
241 * Returns: 0 if success, non-zero otherwise
243 static int make_dev_from_event(struct hotplug2_event_t *event, char *path, mode_t devmode) {
244 char *subsystem, *major, *minor, *devpath;
245 int rv = 1;
247 major = get_hotplug2_value_by_key(event, "MAJOR");
248 if (major == NULL)
249 goto return_value;
251 minor = get_hotplug2_value_by_key(event, "MINOR");
252 if (minor == NULL)
253 goto return_value;
255 devpath = get_hotplug2_value_by_key(event, "DEVPATH");
256 if (devpath == NULL)
257 goto return_value;
259 subsystem = get_hotplug2_value_by_key(event, "SUBSYSTEM");
260 if (!strcmp(subsystem, "block"))
261 devmode |= S_IFBLK;
262 else
263 devmode |= S_IFCHR;
265 path = replace_key_by_value(path, event);
266 mkdir_p(path);
267 rv = mknod(path, devmode, makedev(atoi(major), atoi(minor)));
270 * Fixes an issue caused by devmode being modified by umask.
272 chmod(path, devmode);
274 free(path);
276 return_value:
277 return rv;
281 * Execute an application without invoking a shell.
283 * @1 Hotplug event structure
284 * @2 Path to the application, with expandable keys
285 * @3 Argv for the application, with expandable keys
287 * Returns: Exit status of the application.
289 static int exec_noshell(struct hotplug2_event_t *event, char *application, char **argv) {
290 pid_t p;
291 int i, status;
293 p = fork();
294 switch (p) {
295 case -1:
296 return -1;
297 case 0:
298 application = replace_key_by_value(strdup(application), event);
299 for (i = 0; argv[i] != NULL; i++) {
300 argv[i] = replace_key_by_value(argv[i], event);
302 execvp(application, argv);
303 exit(127);
304 break;
305 default:
306 if (waitpid(p, &status, 0) == -1)
307 return -1;
309 return WEXITSTATUS(status);
310 break;
315 * Execute an application while invoking a shell.
317 * @1 Hotplug event structure
318 * @2 The application and all its arguments, with expandable keys
320 * Returns: Exit status of the application.
322 static int exec_shell(struct hotplug2_event_t *event, char *application) {
323 int rv;
325 application = replace_key_by_value(strdup(application), event);
326 rv = WEXITSTATUS(system(application));
327 free(application);
328 return rv;
332 * Create a symlink, with necessary parent directories.
334 * @1 Hotplug event structure
335 * @2 Link target, with expandable keys
336 * @3 Link name, with expandable keys
338 * Returns: return value of symlink()
340 static int make_symlink(struct hotplug2_event_t *event, char *target, char *linkname) {
341 int rv;
343 target = replace_key_by_value(strdup(target), event);
344 linkname = replace_key_by_value(strdup(linkname), event);
346 mkdir_p(linkname);
347 rv = symlink(target, linkname);
349 free(target);
350 free(linkname);
352 return rv;
356 * Chmod a given file.
358 * @1 Hotplug event structure
359 * @2 File name, with expandable keys
360 * @3 Chmod value, with expandable keys
362 * Returns: return value of chmod()
364 static int chmod_file(struct hotplug2_event_t *event, char *file, char *value) {
365 int rv;
367 file = replace_key_by_value(strdup(file), event);
368 value = replace_key_by_value(strdup(value), event);
370 rv = chmod(file, strtoul(value, 0, 8));
372 free(file);
373 free(value);
375 return rv;
380 * Change owner or group of a given file.
382 * @1 Hotplug event structure
383 * @2 Whether we chown or chgrp
384 * @3 Filename, with expandable keys
385 * @4 Group or user name, with expandable keys
387 * Returns: return value of chown()
389 static int chown_chgrp(struct hotplug2_event_t *event, int action, char *file, char *param) {
390 struct group *grp;
391 struct passwd *pwd;
392 int rv;
394 file = replace_key_by_value(strdup(file), event);
395 param = replace_key_by_value(strdup(param), event);
397 rv = -1;
399 switch (action) {
400 case ACT_CHOWN:
401 pwd = getpwnam(param);
402 rv = chown(file, pwd->pw_uid, -1);
403 break;
404 case ACT_CHGRP:
405 grp = getgrnam(param);
406 rv = chown(file, -1, grp->gr_gid);
407 break;
410 free(file);
411 free(param);
413 return rv;
417 * Prints all uevent keys.
419 * @1 Hotplug event structure
421 * Returns: void
423 static void print_debug(struct hotplug2_event_t *event) {
424 int i;
426 for (i = 0; i < event->env_vars_c; i++)
427 printf("%s=%s\n", event->env_vars[i].key, event->env_vars[i].value);
431 * Evaluates a condition according to a given hotplug event structure.
433 * @1 Hotplug event structure
434 * @2 Condition to be evaluated
436 * Returns: 1 if match, 0 if no match, EVAL_NOT_AVAILABLE if unable to
437 * perform evaluation
439 int rule_condition_eval(struct hotplug2_event_t *event, struct condition_t *condition) {
440 int rv;
441 char *event_value = NULL;
442 regex_t preg;
444 event_value = get_hotplug2_value_by_key(event, condition->key);
446 switch (condition->type) {
447 case COND_MATCH_CMP:
448 case COND_NMATCH_CMP:
449 if (event_value == NULL)
450 return EVAL_NOT_AVAILABLE;
452 rv = strcmp(condition->value, event_value) ? EVAL_NOT_MATCH : EVAL_MATCH;
453 if (condition->type == COND_NMATCH_CMP)
454 rv = !rv;
456 return rv;
458 case COND_MATCH_RE:
459 case COND_NMATCH_RE:
460 if (event_value == NULL)
461 return EVAL_NOT_AVAILABLE;
463 regcomp(&preg, condition->value, REG_EXTENDED | REG_NOSUB);
465 rv = regexec(&preg, event_value, 0, NULL, 0) ? EVAL_NOT_MATCH : EVAL_MATCH;
466 if (condition->type == COND_NMATCH_RE)
467 rv = !rv;
469 regfree(&preg);
471 return rv;
473 case COND_MATCH_IS:
474 if (!strcasecmp(condition->value, "set"))
475 return event_value != NULL;
477 if (!strcasecmp(condition->value, "unset"))
478 return event_value == NULL;
481 return EVAL_NOT_AVAILABLE;
485 * Executes a rule. Contains evaluation of all conditions prior
486 * to execution.
488 * @1 Hotplug event structure
489 * @2 The rule to be executed
491 * Returns: 0 if success, -1 if the whole event is to be
492 * discared, 1 if bail out of this particular rule was required
494 int rule_execute(struct hotplug2_event_t *event, struct rule_t *rule) {
495 int i, last_rv;
497 for (i = 0; i < rule->conditions_c; i++) {
498 if (rule_condition_eval(event, &(rule->conditions[i])) != EVAL_MATCH)
499 return 0;
502 last_rv = 0;
504 for (i = 0; i < event->env_vars_c; i++)
505 setenv(event->env_vars[i].key, event->env_vars[i].value, 1);
507 for (i = 0; i < rule->actions_c; i++) {
508 switch (rule->actions[i].type) {
509 case ACT_STOP_PROCESSING:
510 return 1;
511 break;
512 case ACT_STOP_IF_FAILED:
513 if (last_rv != 0)
514 return 1;
515 break;
516 case ACT_NEXT_EVENT:
517 return -1;
518 break;
519 case ACT_NEXT_IF_FAILED:
520 if (last_rv != 0)
521 return -1;
522 break;
523 case ACT_MAKE_DEVICE:
524 last_rv = make_dev_from_event(event, rule->actions[i].parameter[0], strtoul(rule->actions[i].parameter[1], NULL, 0));
525 break;
526 case ACT_CHMOD:
527 last_rv = chmod_file(event, rule->actions[i].parameter[0], rule->actions[i].parameter[1]);
528 break;
529 case ACT_CHOWN:
530 case ACT_CHGRP:
531 last_rv = chown_chgrp(event, rule->actions[i].type, rule->actions[i].parameter[0], rule->actions[i].parameter[1]);
532 break;
533 case ACT_SYMLINK:
534 last_rv = make_symlink(event, rule->actions[i].parameter[0], rule->actions[i].parameter[1]);
535 break;
536 case ACT_RUN_SHELL:
537 last_rv = exec_shell(event, rule->actions[i].parameter[0]);
538 break;
539 case ACT_RUN_NOSHELL:
540 last_rv = exec_noshell(event, rule->actions[i].parameter[0], rule->actions[i].parameter);
541 break;
542 case ACT_SETENV:
543 last_rv = setenv(rule->actions[i].parameter[0], rule->actions[i].parameter[1], 1);
544 break;
545 case ACT_REMOVE:
546 last_rv = unlink(rule->actions[i].parameter[0]);
547 rmdir_p(rule->actions[i].parameter[0]);
548 break;
549 case ACT_DEBUG:
550 print_debug(event);
551 last_rv = 0;
552 break;
556 return 0;
560 * Sets the flags of the given rule.
562 * @1 Rule structure
564 * Returns: void
566 void rule_flags(struct rule_t *rule) {
567 int i;
569 for (i = 0; i < rule->actions_c; i++) {
570 switch (rule->actions[i].type) {
571 case ACT_FLAG_NOTHROTTLE:
572 rule->flags |= FLAG_NOTHROTTLE;
573 break;
577 return;
581 * Checks whether the given character should initiate
582 * further parsing.
584 * @1 Character to examine
586 * Returns: 1 if it should, 0 otherwise
588 static inline int isinitiator(int c) {
589 switch (c) {
590 case ',':
591 case ';':
592 case '{':
593 case '}':
594 return 1;
597 return 0;
601 * Appends a character to a buffer. Enlarges if necessary.
603 * @1 Pointer to the buffer
604 * @2 Pointer to buffer size
605 * @3 Pointer to last buffer character
606 * @4 Appended character
608 * Returns: void
610 static inline void add_buffer(char **buf, int *blen, int *slen, char c) {
611 if (*slen + 1 >= *blen) {
612 *blen = *blen + 64;
613 *buf = xrealloc(*buf, *blen);
616 (*buf)[*slen] = c;
617 (*buf)[*slen+1] = '\0';
618 *slen += 1;
622 * Parses a string into a syntactically acceptable value.
624 * @1 Input string
625 * @2 Pointer to the new position
627 * Returns: Newly allocated string.
629 static char *rules_get_value(char *input, char **nptr) {
630 int quotes = QUOTES_NONE;
631 char *ptr = input;
633 int blen, slen;
634 char *buf;
636 blen = slen = 0;
637 buf = NULL;
639 if (isinitiator(*ptr)) {
640 add_buffer(&buf, &blen, &slen, *ptr);
641 ptr++;
642 goto return_value;
645 while (isspace(*ptr) && *ptr != '\0')
646 ptr++;
648 if (*ptr == '\0')
649 return NULL;
651 switch (*ptr) {
652 case '"':
653 quotes = QUOTES_DOUBLE;
654 ptr++;
655 break;
656 case '\'':
657 quotes = QUOTES_SINGLE;
658 ptr++;
659 break;
662 if (quotes != QUOTES_NONE) {
663 while (quotes != QUOTES_NONE) {
664 switch (*ptr) {
665 case '\\':
666 ptr++;
667 add_buffer(&buf, &blen, &slen, *ptr);
668 break;
669 case '"':
670 if (quotes == QUOTES_DOUBLE)
671 quotes = QUOTES_NONE;
672 break;
673 case '\'':
674 if (quotes == QUOTES_SINGLE)
675 quotes = QUOTES_NONE;
676 break;
677 default:
678 add_buffer(&buf, &blen, &slen, *ptr);
679 break;
681 ptr++;
683 } else {
684 while (!isspace(*ptr) && *ptr != '\0') {
685 if (isinitiator(*ptr))
686 break;
688 if (*ptr == '\\')
689 ptr++;
691 add_buffer(&buf, &blen, &slen, *ptr);
692 ptr++;
696 return_value:
697 while (isspace(*ptr) && *ptr != '\0')
698 ptr++;
700 if (nptr != NULL)
701 *nptr = ptr;
703 return buf;
707 * Releases all memory associated with the ruleset. TODO: Make
708 * the behavior same for all _free() functions, ie. either
709 * release the given pointer itself or keep it, but do it
710 * in all functions!
712 * @1 The ruleset to be freed
714 * Returns: void
716 void rules_free(struct rules_t *rules) {
717 int i, j, k;
719 for (i = 0; i < rules->rules_c; i++) {
720 for (j = 0; j < rules->rules[i].actions_c; j++) {
721 if (rules->rules[i].actions[j].parameter != NULL) {
722 for (k = 0; rules->rules[i].actions[j].parameter[k] != NULL; k++)
723 free(rules->rules[i].actions[j].parameter[k]);
724 free(rules->rules[i].actions[j].parameter);
727 for (j = 0; j < rules->rules[i].conditions_c; j++) {
728 free(rules->rules[i].conditions[j].key);
729 free(rules->rules[i].conditions[j].value);
731 free(rules->rules[i].actions);
732 free(rules->rules[i].conditions);
734 free(rules->rules);
738 * Includes a rule file.
740 * @1 Filename
741 * @2 The ruleset structure
743 * Returns: 0 if success, -1 otherwise
745 int rules_include(const char *filename, struct rules_t **return_rules) {
746 struct filemap_t filemap;
747 struct rules_t *rules;
749 if (map_file(filename, &filemap)) {
750 ERROR("rules parse","Unable to open/mmap rules file.");
751 return -1;
754 rules = rules_from_config((char*)(filemap.map), *return_rules);
755 if (rules == NULL) {
756 ERROR("rules parse","Unable to parse rules file.");
757 return -1;
759 *return_rules = rules;
761 unmap_file(&filemap);
763 return 0;
767 * Parses an entire file of rules.
769 * @1 The whole file in memory or mmap'd
771 * Returns: A newly allocated ruleset.
773 struct rules_t *rules_from_config(char *input, struct rules_t *return_rules) {
774 #define last_rule return_rules->rules[return_rules->rules_c - 1]
775 int nested;
776 int status;
777 int terminate;
778 char *buf;
781 * TODO: cleanup
783 * BIIIG cleanup... Use callbacks for actions and for internal actions.
786 int i, j;
787 struct key_rec_t conditions[] = { /*NOTE: We never have parameters for conditions. */
788 {"is", 0, COND_MATCH_IS},
789 {"==", 0, COND_MATCH_CMP},
790 {"!=", 0, COND_NMATCH_CMP},
791 {"~~", 0, COND_MATCH_RE},
792 {"!~", 0, COND_NMATCH_RE},
793 {NULL, 0, -1}
796 struct key_rec_t actions[] = {
797 /*one line / one command*/
798 {"run", 1, ACT_RUN_SHELL},
799 {"exec", -1, ACT_RUN_NOSHELL},
800 {"break", 0, ACT_STOP_PROCESSING},
801 {"break_if_failed", 0, ACT_STOP_IF_FAILED},
802 {"next", 0, ACT_NEXT_EVENT},
803 {"next_if_failed", 0, ACT_NEXT_IF_FAILED},
804 {"chown", 2, ACT_CHOWN},
805 {"chmod", 2, ACT_CHMOD},
806 {"chgrp", 2, ACT_CHGRP},
807 {"setenv", 2, ACT_SETENV},
808 {"remove", 1, ACT_REMOVE},
809 {"nothrottle", 0, ACT_FLAG_NOTHROTTLE},
810 {"printdebug", 0, ACT_DEBUG},
811 /*symlink*/
812 {"symlink", 2, ACT_SYMLINK},
813 {"softlink", 2, ACT_SYMLINK},
814 /*makedev*/
815 {"mknod", 2, ACT_MAKE_DEVICE},
816 {"makedev", 2, ACT_MAKE_DEVICE},
817 {NULL, 0, -1}
821 * A little trick for inclusion.
823 if (return_rules == NULL) {
824 return_rules = xmalloc(sizeof(struct rules_t));
825 return_rules->rules_c = 1;
826 return_rules->rules = xmalloc(sizeof(struct rule_t) * return_rules->rules_c);
827 nested = 0;
828 } else {
829 nested = 1;
832 status = STATUS_KEY;
834 last_rule.actions = NULL;
835 last_rule.actions_c = 0;
836 last_rule.conditions = NULL;
837 last_rule.conditions_c = 0;
839 terminate = 0;
840 do {
841 buf = rules_get_value(input, &input);
842 if (buf == NULL) {
843 ERROR("rules_get_value", "Malformed rule - unable to read!");
844 terminate = 1;
845 break;
848 if (buf[0] == '#') {
849 /* Skip to next line */
850 while (*input != '\0' && *input != '\n')
851 input++;
853 free(buf);
854 continue;
855 } else if (buf[0] == '$') {
856 buf++;
859 * Warning, hack ahead...
861 if (!strcmp("include", buf)) {
862 buf = rules_get_value(input, &input);
863 if (rules_include(buf, &return_rules)) {
864 ERROR("rules_include", "Unable to include ruleset '%s'!", buf);
868 free(buf);
869 continue;
872 switch (status) {
873 case STATUS_KEY:
874 last_rule.conditions_c++;
875 last_rule.conditions = xrealloc(last_rule.conditions, sizeof(struct condition_t) * last_rule.conditions_c);
876 last_rule.conditions[last_rule.conditions_c-1].key = strdup(buf);
878 status = STATUS_CONDTYPE;
879 break;
880 case STATUS_CONDTYPE:
881 last_rule.conditions[last_rule.conditions_c-1].type = -1;
883 for (i = 0; conditions[i].key != NULL; i++) {
884 if (!strcmp(conditions[i].key, buf)) {
885 last_rule.conditions[last_rule.conditions_c-1].type = conditions[i].type;
886 break;
890 if (last_rule.conditions[last_rule.conditions_c-1].type == -1) {
891 ERROR("rules_get_value / status / condtype", "Malformed rule - unknown condition type.");
892 terminate = 1;
895 status = STATUS_VALUE;
896 break;
897 case STATUS_VALUE:
898 last_rule.conditions[last_rule.conditions_c-1].value = strdup(buf);
900 status = STATUS_INITIATOR;
901 break;
902 case STATUS_INITIATOR:
903 if (!strcmp(buf, ",") || !strcmp(buf, ";")) {
904 status = STATUS_KEY;
905 } else if (!strcmp(buf, "{")) {
906 status = STATUS_ACTION;
907 } else {
908 ERROR("rules_get_value / status / initiator", "Malformed rule - unknown initiator.");
909 terminate = 1;
911 break;
912 case STATUS_ACTION:
913 if (!strcmp(buf, "}")) {
914 status = STATUS_KEY;
915 return_rules->rules_c++;
916 return_rules->rules = xrealloc(return_rules->rules, sizeof(struct rule_t) * return_rules->rules_c);
918 last_rule.actions = NULL;
919 last_rule.actions_c = 0;
920 last_rule.conditions = NULL;
921 last_rule.conditions_c = 0;
922 break;
925 last_rule.actions_c++;
926 last_rule.actions = xrealloc(last_rule.actions, sizeof(struct action_t) * last_rule.actions_c);
927 last_rule.actions[last_rule.actions_c-1].parameter = NULL;
928 last_rule.actions[last_rule.actions_c-1].type = -1;
930 for (i = 0; actions[i].key != NULL; i++) {
931 if (!strcmp(actions[i].key, buf)) {
932 last_rule.actions[last_rule.actions_c-1].type = actions[i].type;
933 break;
937 if (last_rule.actions[last_rule.actions_c-1].type == -1) {
938 ERROR("rules_get_value / status / action", "Malformed rule - unknown action: %s.", buf);
939 terminate = 1;
942 if (actions[i].param > 0) {
943 last_rule.actions[last_rule.actions_c-1].parameter = xmalloc(sizeof(char*) * (actions[i].param + 1));
944 last_rule.actions[last_rule.actions_c-1].parameter[actions[i].param] = NULL;
946 for (j = 0; j < actions[i].param; j++) {
947 last_rule.actions[last_rule.actions_c-1].parameter[j] = rules_get_value(input, &input);
948 if (!strcmp(last_rule.actions[last_rule.actions_c-1].parameter[j], "}")) {
949 ERROR("rules_get_value / status / action", "Malformed rule - not enough parameters passed.");
950 status = STATUS_KEY;
951 break;
953 last_rule.actions[last_rule.actions_c-1].parameter[j] = replace_str(last_rule.actions[last_rule.actions_c-1].parameter[j], "\\}", "}");
955 } else if (actions[i].param == -1) {
956 j = 0;
957 last_rule.actions[last_rule.actions_c-1].parameter = xmalloc(sizeof(char*) * (j + 1));
958 last_rule.actions[last_rule.actions_c-1].parameter[j] = rules_get_value(input, &input);
959 while (last_rule.actions[last_rule.actions_c-1].parameter[j] != NULL) {
960 if (!strcmp(last_rule.actions[last_rule.actions_c-1].parameter[j], ";")) {
961 break;
963 if (!strcmp(last_rule.actions[last_rule.actions_c-1].parameter[j], "}")) {
964 ERROR("rules_get_value / status / action", "Malformed rule - missing parameter terminator ';'.");
965 status = STATUS_KEY;
966 break;
968 if (last_rule.actions[last_rule.actions_c-1].parameter[j][0] == '\0') {
969 ERROR("rules_get_value / status / action", "Malformed rule - missing parameter terminator ';'.");
970 status = STATUS_KEY;
971 break;
973 last_rule.actions[last_rule.actions_c-1].parameter[j] = replace_str(last_rule.actions[last_rule.actions_c-1].parameter[j], "\\}", "}");
974 last_rule.actions[last_rule.actions_c-1].parameter[j] = replace_str(last_rule.actions[last_rule.actions_c-1].parameter[j], "\\;", ";");
976 j++;
977 last_rule.actions[last_rule.actions_c-1].parameter = xrealloc(last_rule.actions[last_rule.actions_c-1].parameter, sizeof(char*) * (j + 1));
978 last_rule.actions[last_rule.actions_c-1].parameter[j] = rules_get_value(input, &input);
980 free(last_rule.actions[last_rule.actions_c-1].parameter[j]);
981 last_rule.actions[last_rule.actions_c-1].parameter[j] = NULL;
984 if (status == STATUS_KEY) {
985 return_rules->rules_c++;
986 return_rules->rules = xrealloc(return_rules->rules, sizeof(struct rule_t) * return_rules->rules_c);
988 last_rule.actions = NULL;
989 last_rule.actions_c = 0;
990 last_rule.conditions = NULL;
991 last_rule.conditions_c = 0;
993 break;
996 free(buf);
997 } while (*input != '\0' && !terminate);
999 if (!terminate) {
1000 /* A little bit hacky cleanup */
1001 if (!nested)
1002 return_rules->rules_c--;
1003 return return_rules;
1004 } else {
1006 * We don't want to cleanup if we're nested.
1008 if (!nested) {
1009 rules_free(return_rules);
1010 free(return_rules);
1013 return NULL;