1 /*****************************************************************************\
3 * | || | ___ | |_ _ __ | | _ _ __ _ |_ ) *
4 * | __ |/ _ \| _|| '_ \| || || |/ _` | / / *
5 * |_||_|\___/ \__|| .__/|_| \_,_|\__, |/___| *
7 \*****************************************************************************/
21 #include <sys/types.h>
24 #include "mem_utils.h"
25 #include "filemap_utils.h"
31 * Function supplementing 'mkdir -p'.
33 * @1 Path to be mkdir'd
37 static void mkdir_p(char *path
) {
45 if (S_ISDIR(statbuf
.st_mode
)) {
50 for (ptr
= path
; ptr
!= NULL
; ptr
= strchr(ptr
, '/')) {
60 if (errno
!= 0 && errno
!= EEXIST
)
70 * Function supplementing 'rmdir -p'.
72 * @1 Path to be rmdir'd
76 static void rmdir_p(char *path
) {
82 ptr
= strrchr(path
, '/');
95 * Replaces all needles by a given value.
97 * @1 Haystack (which gets free'd in the function)
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
;
107 size_t replacement_len
;
110 if (replacement
== NULL
|| *replacement
=='\0')
113 if (needle
== NULL
|| *needle
=='\0')
118 for (ptr
= hay
; *ptr
!= '\0'; ++ptr
) {
119 if (needle
[j
] == *ptr
) {
121 if (needle
[j
] == '\0') {
122 *(ptr
-j
+1) = '\0'; // mark occurence
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);
143 while (occurences
-- > 0) {
148 memcpy(bptr
, start
, ptr
- start
);
152 memcpy(bptr
, replacement
, replacement_len
);
153 bptr
+=replacement_len
;
162 memcpy(bptr
, start
, ptr
- start
);
173 * Trivial utility, figuring out whether a character is escaped or not.
176 * @2 Pointer to the character in question
178 * Returns: 1 if escaped, 0 otherwise
180 static inline int isescaped(char *hay
, char *ptr
) {
184 if (*(ptr
-1) != '\\')
191 * Performs replacement of all keys by their value based on the hotplug
192 * event structure. Keys are identified as strings %KEY%.
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, '%');
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
);
227 hay
= replace_str(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
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
;
247 major
= get_hotplug2_value_by_key(event
, "MAJOR");
251 minor
= get_hotplug2_value_by_key(event
, "MINOR");
255 devpath
= get_hotplug2_value_by_key(event
, "DEVPATH");
259 subsystem
= get_hotplug2_value_by_key(event
, "SUBSYSTEM");
260 if (!strcmp(subsystem
, "block"))
265 path
= replace_key_by_value(path
, event
);
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
);
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
) {
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
);
306 if (waitpid(p
, &status
, 0) == -1)
309 return WEXITSTATUS(status
);
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
) {
325 application
= replace_key_by_value(strdup(application
), event
);
326 rv
= WEXITSTATUS(system(application
));
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
) {
343 target
= replace_key_by_value(strdup(target
), event
);
344 linkname
= replace_key_by_value(strdup(linkname
), event
);
347 rv
= symlink(target
, linkname
);
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
) {
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));
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
) {
394 file
= replace_key_by_value(strdup(file
), event
);
395 param
= replace_key_by_value(strdup(param
), event
);
401 pwd
= getpwnam(param
);
402 rv
= chown(file
, pwd
->pw_uid
, -1);
405 grp
= getgrnam(param
);
406 rv
= chown(file
, -1, grp
->gr_gid
);
417 * Prints all uevent keys.
419 * @1 Hotplug event structure
423 static void print_debug(struct hotplug2_event_t
*event
) {
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
439 int rule_condition_eval(struct hotplug2_event_t
*event
, struct condition_t
*condition
) {
441 char *event_value
= NULL
;
444 event_value
= get_hotplug2_value_by_key(event
, condition
->key
);
446 switch (condition
->type
) {
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
)
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
)
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
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
) {
497 for (i
= 0; i
< rule
->conditions_c
; i
++) {
498 if (rule_condition_eval(event
, &(rule
->conditions
[i
])) != EVAL_MATCH
)
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
:
512 case ACT_STOP_IF_FAILED
:
519 case ACT_NEXT_IF_FAILED
:
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));
527 last_rv
= chmod_file(event
, rule
->actions
[i
].parameter
[0], rule
->actions
[i
].parameter
[1]);
531 last_rv
= chown_chgrp(event
, rule
->actions
[i
].type
, rule
->actions
[i
].parameter
[0], rule
->actions
[i
].parameter
[1]);
534 last_rv
= make_symlink(event
, rule
->actions
[i
].parameter
[0], rule
->actions
[i
].parameter
[1]);
537 last_rv
= exec_shell(event
, rule
->actions
[i
].parameter
[0]);
539 case ACT_RUN_NOSHELL
:
540 last_rv
= exec_noshell(event
, rule
->actions
[i
].parameter
[0], rule
->actions
[i
].parameter
);
543 last_rv
= setenv(rule
->actions
[i
].parameter
[0], rule
->actions
[i
].parameter
[1], 1);
546 last_rv
= unlink(rule
->actions
[i
].parameter
[0]);
547 rmdir_p(rule
->actions
[i
].parameter
[0]);
560 * Sets the flags of the given rule.
566 void rule_flags(struct rule_t
*rule
) {
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
;
581 * Checks whether the given character should initiate
584 * @1 Character to examine
586 * Returns: 1 if it should, 0 otherwise
588 static inline int isinitiator(int c
) {
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
610 static inline void add_buffer(char **buf
, int *blen
, int *slen
, char c
) {
611 if (*slen
+ 1 >= *blen
) {
613 *buf
= xrealloc(*buf
, *blen
);
617 (*buf
)[*slen
+1] = '\0';
622 * Parses a string into a syntactically acceptable value.
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
;
639 if (isinitiator(*ptr
)) {
640 add_buffer(&buf
, &blen
, &slen
, *ptr
);
645 while (isspace(*ptr
) && *ptr
!= '\0')
653 quotes
= QUOTES_DOUBLE
;
657 quotes
= QUOTES_SINGLE
;
662 if (quotes
!= QUOTES_NONE
) {
663 while (quotes
!= QUOTES_NONE
) {
667 add_buffer(&buf
, &blen
, &slen
, *ptr
);
670 if (quotes
== QUOTES_DOUBLE
)
671 quotes
= QUOTES_NONE
;
674 if (quotes
== QUOTES_SINGLE
)
675 quotes
= QUOTES_NONE
;
678 add_buffer(&buf
, &blen
, &slen
, *ptr
);
684 while (!isspace(*ptr
) && *ptr
!= '\0') {
685 if (isinitiator(*ptr
))
691 add_buffer(&buf
, &blen
, &slen
, *ptr
);
697 while (isspace(*ptr
) && *ptr
!= '\0')
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
712 * @1 The ruleset to be freed
716 void rules_free(struct rules_t
*rules
) {
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
);
738 * Includes a rule file.
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.");
754 rules
= rules_from_config((char*)(filemap
.map
), *return_rules
);
756 ERROR("rules parse","Unable to parse rules file.");
759 *return_rules
= rules
;
761 unmap_file(&filemap
);
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]
783 * BIIIG cleanup... Use callbacks for actions and for internal actions.
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
},
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
},
812 {"symlink", 2, ACT_SYMLINK
},
813 {"softlink", 2, ACT_SYMLINK
},
815 {"mknod", 2, ACT_MAKE_DEVICE
},
816 {"makedev", 2, ACT_MAKE_DEVICE
},
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
);
834 last_rule
.actions
= NULL
;
835 last_rule
.actions_c
= 0;
836 last_rule
.conditions
= NULL
;
837 last_rule
.conditions_c
= 0;
841 buf
= rules_get_value(input
, &input
);
843 ERROR("rules_get_value", "Malformed rule - unable to read!");
849 /* Skip to next line */
850 while (*input
!= '\0' && *input
!= '\n')
855 } else if (buf
[0] == '$') {
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
);
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
;
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
;
890 if (last_rule
.conditions
[last_rule
.conditions_c
-1].type
== -1) {
891 ERROR("rules_get_value / status / condtype", "Malformed rule - unknown condition type.");
895 status
= STATUS_VALUE
;
898 last_rule
.conditions
[last_rule
.conditions_c
-1].value
= strdup(buf
);
900 status
= STATUS_INITIATOR
;
902 case STATUS_INITIATOR
:
903 if (!strcmp(buf
, ",") || !strcmp(buf
, ";")) {
905 } else if (!strcmp(buf
, "{")) {
906 status
= STATUS_ACTION
;
908 ERROR("rules_get_value / status / initiator", "Malformed rule - unknown initiator.");
913 if (!strcmp(buf
, "}")) {
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;
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
;
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
);
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.");
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) {
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
], ";")) {
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 ';'.");
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 ';'.");
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
], "\\;", ";");
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;
997 } while (*input
!= '\0' && !terminate
);
1000 /* A little bit hacky cleanup */
1002 return_rules
->rules_c
--;
1003 return return_rules
;
1006 * We don't want to cleanup if we're nested.
1009 rules_free(return_rules
);