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 * Creates a "key=value" string from the given key and value
490 * Returns: Newly allocated string in "key=value" form
493 static char* alloc_env(const char *key
, const char *value
) {
494 size_t keylen
, vallen
;
497 keylen
= strlen(key
);
498 vallen
= strlen(value
) + 1;
500 combined
= xmalloc(keylen
+ vallen
+ 1);
501 memcpy(combined
, key
, keylen
);
502 combined
[keylen
] = '=';
503 memcpy(&combined
[keylen
+ 1], value
, vallen
);
509 * Executes a rule. Contains evaluation of all conditions prior
512 * @1 Hotplug event structure
513 * @2 The rule to be executed
515 * Returns: 0 if success, -1 if the whole event is to be
516 * discared, 1 if bail out of this particular rule was required
518 int rule_execute(struct hotplug2_event_t
*event
, struct rule_t
*rule
) {
522 for (i
= 0; i
< rule
->conditions_c
; i
++) {
523 if (rule_condition_eval(event
, &(rule
->conditions
[i
])) != EVAL_MATCH
)
530 env
= xmalloc(sizeof(char *) * event
->env_vars_c
);
531 for (i
= 0; i
< event
->env_vars_c
; i
++) {
532 env
[i
] = alloc_env(event
->env_vars
[i
].key
, event
->env_vars
[i
].value
);
536 for (i
= 0; i
< rule
->actions_c
; i
++) {
537 switch (rule
->actions
[i
].type
) {
538 case ACT_STOP_PROCESSING
:
541 case ACT_STOP_IF_FAILED
:
548 case ACT_NEXT_IF_FAILED
:
552 case ACT_MAKE_DEVICE
:
553 last_rv
= make_dev_from_event(event
, rule
->actions
[i
].parameter
[0], strtoul(rule
->actions
[i
].parameter
[1], NULL
, 0));
556 last_rv
= chmod_file(event
, rule
->actions
[i
].parameter
[0], rule
->actions
[i
].parameter
[1]);
560 last_rv
= chown_chgrp(event
, rule
->actions
[i
].type
, rule
->actions
[i
].parameter
[0], rule
->actions
[i
].parameter
[1]);
563 last_rv
= make_symlink(event
, rule
->actions
[i
].parameter
[0], rule
->actions
[i
].parameter
[1]);
566 last_rv
= exec_shell(event
, rule
->actions
[i
].parameter
[0]);
568 case ACT_RUN_NOSHELL
:
569 last_rv
= exec_noshell(event
, rule
->actions
[i
].parameter
[0], rule
->actions
[i
].parameter
);
572 last_rv
= setenv(rule
->actions
[i
].parameter
[0], rule
->actions
[i
].parameter
[1], 1);
575 last_rv
= unlink(rule
->actions
[i
].parameter
[0]);
576 rmdir_p(rule
->actions
[i
].parameter
[0]);
587 for (i
= 0; i
< event
->env_vars_c
; i
++) {
588 unsetenv(event
->env_vars
[i
].key
);
597 * Sets the flags of the given rule.
603 void rule_flags(struct rule_t
*rule
) {
606 for (i
= 0; i
< rule
->actions_c
; i
++) {
607 switch (rule
->actions
[i
].type
) {
608 case ACT_FLAG_NOTHROTTLE
:
609 rule
->flags
|= FLAG_NOTHROTTLE
;
618 * Checks whether the given character should initiate
621 * @1 Character to examine
623 * Returns: 1 if it should, 0 otherwise
625 static inline int isinitiator(int c
) {
638 * Appends a character to a buffer. Enlarges if necessary.
640 * @1 Pointer to the buffer
641 * @2 Pointer to buffer size
642 * @3 Pointer to last buffer character
643 * @4 Appended character
647 static inline void add_buffer(char **buf
, int *blen
, int *slen
, char c
) {
648 if (*slen
+ 1 >= *blen
) {
650 *buf
= xrealloc(*buf
, *blen
);
654 (*buf
)[*slen
+1] = '\0';
659 * Parses a string into a syntactically acceptable value.
662 * @2 Pointer to the new position
664 * Returns: Newly allocated string.
666 static char *rules_get_value(char *input
, char **nptr
) {
667 int quotes
= QUOTES_NONE
;
676 if (isinitiator(*ptr
)) {
677 add_buffer(&buf
, &blen
, &slen
, *ptr
);
682 while (isspace(*ptr
) && *ptr
!= '\0')
690 quotes
= QUOTES_DOUBLE
;
694 quotes
= QUOTES_SINGLE
;
699 if (quotes
!= QUOTES_NONE
) {
700 while (quotes
!= QUOTES_NONE
) {
704 add_buffer(&buf
, &blen
, &slen
, *ptr
);
707 if (quotes
== QUOTES_DOUBLE
)
708 quotes
= QUOTES_NONE
;
711 if (quotes
== QUOTES_SINGLE
)
712 quotes
= QUOTES_NONE
;
715 add_buffer(&buf
, &blen
, &slen
, *ptr
);
721 while (!isspace(*ptr
) && *ptr
!= '\0') {
722 if (isinitiator(*ptr
))
728 add_buffer(&buf
, &blen
, &slen
, *ptr
);
734 while (isspace(*ptr
) && *ptr
!= '\0')
744 * Releases all memory associated with the ruleset. TODO: Make
745 * the behavior same for all _free() functions, ie. either
746 * release the given pointer itself or keep it, but do it
749 * @1 The ruleset to be freed
753 void rules_free(struct rules_t
*rules
) {
756 for (i
= 0; i
< rules
->rules_c
; i
++) {
757 for (j
= 0; j
< rules
->rules
[i
].actions_c
; j
++) {
758 if (rules
->rules
[i
].actions
[j
].parameter
!= NULL
) {
759 for (k
= 0; rules
->rules
[i
].actions
[j
].parameter
[k
] != NULL
; k
++)
760 free(rules
->rules
[i
].actions
[j
].parameter
[k
]);
761 free(rules
->rules
[i
].actions
[j
].parameter
);
764 for (j
= 0; j
< rules
->rules
[i
].conditions_c
; j
++) {
765 free(rules
->rules
[i
].conditions
[j
].key
);
766 free(rules
->rules
[i
].conditions
[j
].value
);
768 free(rules
->rules
[i
].actions
);
769 free(rules
->rules
[i
].conditions
);
775 * Includes a rule file.
778 * @2 The ruleset structure
780 * Returns: 0 if success, -1 otherwise
782 int rules_include(const char *filename
, struct rules_t
**return_rules
) {
783 struct filemap_t filemap
;
784 struct rules_t
*rules
;
786 if (map_file(filename
, &filemap
)) {
787 ERROR("rules parse","Unable to open/mmap rules file.");
791 rules
= rules_from_config((char*)(filemap
.map
), *return_rules
);
793 ERROR("rules parse","Unable to parse rules file.");
796 *return_rules
= rules
;
798 unmap_file(&filemap
);
804 * Parses an entire file of rules.
806 * @1 The whole file in memory or mmap'd
808 * Returns: A newly allocated ruleset.
810 struct rules_t
*rules_from_config(char *input
, struct rules_t
*return_rules
) {
811 #define last_rule return_rules->rules[return_rules->rules_c - 1]
820 * BIIIG cleanup... Use callbacks for actions and for internal actions.
824 struct key_rec_t conditions
[] = { /*NOTE: We never have parameters for conditions. */
825 {"is", 0, COND_MATCH_IS
},
826 {"==", 0, COND_MATCH_CMP
},
827 {"!=", 0, COND_NMATCH_CMP
},
828 {"~~", 0, COND_MATCH_RE
},
829 {"!~", 0, COND_NMATCH_RE
},
833 struct key_rec_t actions
[] = {
834 /*one line / one command*/
835 {"run", 1, ACT_RUN_SHELL
},
836 {"exec", -1, ACT_RUN_NOSHELL
},
837 {"break", 0, ACT_STOP_PROCESSING
},
838 {"break_if_failed", 0, ACT_STOP_IF_FAILED
},
839 {"next", 0, ACT_NEXT_EVENT
},
840 {"next_if_failed", 0, ACT_NEXT_IF_FAILED
},
841 {"chown", 2, ACT_CHOWN
},
842 {"chmod", 2, ACT_CHMOD
},
843 {"chgrp", 2, ACT_CHGRP
},
844 {"setenv", 2, ACT_SETENV
},
845 {"remove", 1, ACT_REMOVE
},
846 {"nothrottle", 0, ACT_FLAG_NOTHROTTLE
},
847 {"printdebug", 0, ACT_DEBUG
},
849 {"symlink", 2, ACT_SYMLINK
},
850 {"softlink", 2, ACT_SYMLINK
},
852 {"mknod", 2, ACT_MAKE_DEVICE
},
853 {"makedev", 2, ACT_MAKE_DEVICE
},
858 * A little trick for inclusion.
860 if (return_rules
== NULL
) {
861 return_rules
= xmalloc(sizeof(struct rules_t
));
862 return_rules
->rules_c
= 1;
863 return_rules
->rules
= xmalloc(sizeof(struct rule_t
) * return_rules
->rules_c
);
871 last_rule
.actions
= NULL
;
872 last_rule
.actions_c
= 0;
873 last_rule
.conditions
= NULL
;
874 last_rule
.conditions_c
= 0;
878 buf
= rules_get_value(input
, &input
);
880 ERROR("rules_get_value", "Malformed rule - unable to read!");
886 /* Skip to next line */
887 while (*input
!= '\0' && *input
!= '\n')
892 } else if (buf
[0] == '$') {
896 * Warning, hack ahead...
898 if (!strcmp("include", buf
)) {
899 buf
= rules_get_value(input
, &input
);
900 if (rules_include(buf
, &return_rules
)) {
901 ERROR("rules_include", "Unable to include ruleset '%s'!", buf
);
911 last_rule
.conditions_c
++;
912 last_rule
.conditions
= xrealloc(last_rule
.conditions
, sizeof(struct condition_t
) * last_rule
.conditions_c
);
913 last_rule
.conditions
[last_rule
.conditions_c
-1].key
= strdup(buf
);
915 status
= STATUS_CONDTYPE
;
917 case STATUS_CONDTYPE
:
918 last_rule
.conditions
[last_rule
.conditions_c
-1].type
= -1;
920 for (i
= 0; conditions
[i
].key
!= NULL
; i
++) {
921 if (!strcmp(conditions
[i
].key
, buf
)) {
922 last_rule
.conditions
[last_rule
.conditions_c
-1].type
= conditions
[i
].type
;
927 if (last_rule
.conditions
[last_rule
.conditions_c
-1].type
== -1) {
928 ERROR("rules_get_value / status / condtype", "Malformed rule - unknown condition type.");
932 status
= STATUS_VALUE
;
935 last_rule
.conditions
[last_rule
.conditions_c
-1].value
= strdup(buf
);
937 status
= STATUS_INITIATOR
;
939 case STATUS_INITIATOR
:
940 if (!strcmp(buf
, ",") || !strcmp(buf
, ";")) {
942 } else if (!strcmp(buf
, "{")) {
943 status
= STATUS_ACTION
;
945 ERROR("rules_get_value / status / initiator", "Malformed rule - unknown initiator.");
950 if (!strcmp(buf
, "}")) {
952 return_rules
->rules_c
++;
953 return_rules
->rules
= xrealloc(return_rules
->rules
, sizeof(struct rule_t
) * return_rules
->rules_c
);
955 last_rule
.actions
= NULL
;
956 last_rule
.actions_c
= 0;
957 last_rule
.conditions
= NULL
;
958 last_rule
.conditions_c
= 0;
962 last_rule
.actions_c
++;
963 last_rule
.actions
= xrealloc(last_rule
.actions
, sizeof(struct action_t
) * last_rule
.actions_c
);
964 last_rule
.actions
[last_rule
.actions_c
-1].parameter
= NULL
;
965 last_rule
.actions
[last_rule
.actions_c
-1].type
= -1;
967 for (i
= 0; actions
[i
].key
!= NULL
; i
++) {
968 if (!strcmp(actions
[i
].key
, buf
)) {
969 last_rule
.actions
[last_rule
.actions_c
-1].type
= actions
[i
].type
;
974 if (last_rule
.actions
[last_rule
.actions_c
-1].type
== -1) {
975 ERROR("rules_get_value / status / action", "Malformed rule - unknown action: %s.", buf
);
979 if (actions
[i
].param
> 0) {
980 last_rule
.actions
[last_rule
.actions_c
-1].parameter
= xmalloc(sizeof(char*) * (actions
[i
].param
+ 1));
981 last_rule
.actions
[last_rule
.actions_c
-1].parameter
[actions
[i
].param
] = NULL
;
983 for (j
= 0; j
< actions
[i
].param
; j
++) {
984 last_rule
.actions
[last_rule
.actions_c
-1].parameter
[j
] = rules_get_value(input
, &input
);
985 if (!strcmp(last_rule
.actions
[last_rule
.actions_c
-1].parameter
[j
], "}")) {
986 ERROR("rules_get_value / status / action", "Malformed rule - not enough parameters passed.");
990 last_rule
.actions
[last_rule
.actions_c
-1].parameter
[j
] = replace_str(last_rule
.actions
[last_rule
.actions_c
-1].parameter
[j
], "\\}", "}");
992 } else if (actions
[i
].param
== -1) {
994 last_rule
.actions
[last_rule
.actions_c
-1].parameter
= xmalloc(sizeof(char*) * (j
+ 1));
995 last_rule
.actions
[last_rule
.actions_c
-1].parameter
[j
] = rules_get_value(input
, &input
);
996 while (last_rule
.actions
[last_rule
.actions_c
-1].parameter
[j
] != NULL
) {
997 if (!strcmp(last_rule
.actions
[last_rule
.actions_c
-1].parameter
[j
], ";")) {
1000 if (!strcmp(last_rule
.actions
[last_rule
.actions_c
-1].parameter
[j
], "}")) {
1001 ERROR("rules_get_value / status / action", "Malformed rule - missing parameter terminator ';'.");
1002 status
= STATUS_KEY
;
1005 if (last_rule
.actions
[last_rule
.actions_c
-1].parameter
[j
][0] == '\0') {
1006 ERROR("rules_get_value / status / action", "Malformed rule - missing parameter terminator ';'.");
1007 status
= STATUS_KEY
;
1010 last_rule
.actions
[last_rule
.actions_c
-1].parameter
[j
] = replace_str(last_rule
.actions
[last_rule
.actions_c
-1].parameter
[j
], "\\}", "}");
1011 last_rule
.actions
[last_rule
.actions_c
-1].parameter
[j
] = replace_str(last_rule
.actions
[last_rule
.actions_c
-1].parameter
[j
], "\\;", ";");
1014 last_rule
.actions
[last_rule
.actions_c
-1].parameter
= xrealloc(last_rule
.actions
[last_rule
.actions_c
-1].parameter
, sizeof(char*) * (j
+ 1));
1015 last_rule
.actions
[last_rule
.actions_c
-1].parameter
[j
] = rules_get_value(input
, &input
);
1017 free(last_rule
.actions
[last_rule
.actions_c
-1].parameter
[j
]);
1018 last_rule
.actions
[last_rule
.actions_c
-1].parameter
[j
] = NULL
;
1021 if (status
== STATUS_KEY
) {
1022 return_rules
->rules_c
++;
1023 return_rules
->rules
= xrealloc(return_rules
->rules
, sizeof(struct rule_t
) * return_rules
->rules_c
);
1025 last_rule
.actions
= NULL
;
1026 last_rule
.actions_c
= 0;
1027 last_rule
.conditions
= NULL
;
1028 last_rule
.conditions_c
= 0;
1034 } while (*input
!= '\0' && !terminate
);
1037 /* A little bit hacky cleanup */
1039 return_rules
->rules_c
--;
1040 return return_rules
;
1043 * We don't want to cleanup if we're nested.
1046 rules_free(return_rules
);