+/* We use additional bytes in make_device() */
+#define SCRATCH_SIZE 128
+
+#if ENABLE_FEATURE_MDEV_CONF
+
+static void make_default_cur_rule(void)
+{
+ memset(&G.cur_rule, 0, sizeof(G.cur_rule));
+ G.cur_rule.maj = -1; /* "not a @major,minor rule" */
+ G.cur_rule.mode = 0660;
+}
+
+static void clean_up_cur_rule(void)
+{
+ struct envmatch *e;
+
+ free(G.cur_rule.envvar);
+ free(G.cur_rule.ren_mov);
+ if (G.cur_rule.regex_compiled)
+ regfree(&G.cur_rule.match);
+ IF_FEATURE_MDEV_EXEC(free(G.cur_rule.r_cmd);)
+ e = G.cur_rule.envmatch;
+ while (e) {
+ free(e->envname);
+ regfree(&e->match);
+ e = e->next;
+ }
+ make_default_cur_rule();
+}
+
+static char *parse_envmatch_pfx(char *val)
+{
+ struct envmatch **nextp = &G.cur_rule.envmatch;
+
+ for (;;) {
+ struct envmatch *e;
+ char *semicolon;
+ char *eq = strchr(val, '=');
+ if (!eq /* || eq == val? */)
+ return val;
+ if (endofname(val) != eq)
+ return val;
+ semicolon = strchr(eq, ';');
+ if (!semicolon)
+ return val;
+ /* ENVVAR=regex;... */
+ *nextp = e = xzalloc(sizeof(*e));
+ nextp = &e->next;
+ e->envname = xstrndup(val, eq - val);
+ *semicolon = '\0';
+ xregcomp(&e->match, eq + 1, REG_EXTENDED);
+ *semicolon = ';';
+ val = semicolon + 1;
+ }
+}
+
+static void parse_next_rule(void)
+{
+ /* Note: on entry, G.cur_rule is set to default */
+ while (1) {
+ char *tokens[4];
+ char *val;
+
+ /* No PARSE_EOL_COMMENTS, because command may contain '#' chars */
+ if (!config_read(G.parser, tokens, 4, 3, "# \t", PARSE_NORMAL & ~PARSE_EOL_COMMENTS))
+ break;
+
+ /* Fields: [-]regex uid:gid mode [alias] [cmd] */
+ dbg3("token1:'%s'", tokens[1]);
+
+ /* 1st field */
+ val = tokens[0];
+ G.cur_rule.keep_matching = ('-' == val[0]);
+ val += G.cur_rule.keep_matching; /* swallow leading dash */
+ val = parse_envmatch_pfx(val);
+ if (val[0] == '@') {
+ /* @major,minor[-minor2] */
+ /* (useful when name is ambiguous:
+ * "/sys/class/usb/lp0" and
+ * "/sys/class/printer/lp0")
+ */
+ int sc = sscanf(val, "@%u,%u-%u", &G.cur_rule.maj, &G.cur_rule.min0, &G.cur_rule.min1);
+ if (sc < 2 || G.cur_rule.maj < 0) {
+ bb_error_msg("bad @maj,min on line %d", G.parser->lineno);
+ goto next_rule;
+ }
+ if (sc == 2)
+ G.cur_rule.min1 = G.cur_rule.min0;
+ } else {
+ char *eq = strchr(val, '=');
+ if (val[0] == '$') {
+ /* $ENVVAR=regex ... */
+ val++;
+ if (!eq) {
+ bb_error_msg("bad $envvar=regex on line %d", G.parser->lineno);
+ goto next_rule;
+ }
+ G.cur_rule.envvar = xstrndup(val, eq - val);
+ val = eq + 1;
+ }
+ xregcomp(&G.cur_rule.match, val, REG_EXTENDED);
+ G.cur_rule.regex_compiled = 1;
+ }
+
+ /* 2nd field: uid:gid - device ownership */
+ if (get_uidgid(&G.cur_rule.ugid, tokens[1]) == 0) {
+ bb_error_msg("unknown user/group '%s' on line %d", tokens[1], G.parser->lineno);
+ goto next_rule;
+ }
+
+ /* 3rd field: mode - device permissions */
+ G.cur_rule.mode = bb_parse_mode(tokens[2], G.cur_rule.mode);
+
+ /* 4th field (opt): ">|=alias" or "!" to not create the node */
+ val = tokens[3];
+ if (ENABLE_FEATURE_MDEV_RENAME && val && strchr(">=!", val[0])) {
+ char *s = skip_non_whitespace(val);
+ G.cur_rule.ren_mov = xstrndup(val, s - val);
+ val = skip_whitespace(s);
+ }
+
+ if (ENABLE_FEATURE_MDEV_EXEC && val && val[0]) {
+ const char *s = "$@*";
+ const char *s2 = strchr(s, val[0]);
+ if (!s2) {
+ bb_error_msg("bad line %u", G.parser->lineno);
+ goto next_rule;
+ }
+ IF_FEATURE_MDEV_EXEC(G.cur_rule.r_cmd = xstrdup(val);)
+ }
+
+ return;
+ next_rule:
+ clean_up_cur_rule();
+ } /* while (config_read) */
+
+ dbg3("config_close(G.parser)");
+ config_close(G.parser);
+ G.parser = NULL;
+
+ return;
+}
+
+/* If mdev -s, we remember rules in G.rule_vec[].
+ * Otherwise, there is no point in doing it, and we just
+ * save only one parsed rule in G.cur_rule.
+ */
+static const struct rule *next_rule(void)
+{
+ struct rule *rule;
+
+ /* Open conf file if we didn't do it yet */
+ if (!G.parser && G.filename) {
+ dbg3("config_open('%s')", G.filename);
+ G.parser = config_open2(G.filename, fopen_for_read);
+ G.filename = NULL;
+ }
+
+ if (G.rule_vec) {
+ /* mdev -s */
+ /* Do we have rule parsed already? */
+ if (G.rule_vec[G.rule_idx]) {
+ dbg3("< G.rule_vec[G.rule_idx:%d]=%p", G.rule_idx, G.rule_vec[G.rule_idx]);
+ return G.rule_vec[G.rule_idx++];
+ }
+ make_default_cur_rule();
+ } else {
+ /* not mdev -s */
+ clean_up_cur_rule();
+ }
+
+ /* Parse one more rule if file isn't fully read */
+ rule = &G.cur_rule;
+ if (G.parser) {
+ parse_next_rule();
+ if (G.rule_vec) { /* mdev -s */
+ rule = xmemdup(&G.cur_rule, sizeof(G.cur_rule));
+ G.rule_vec = xrealloc_vector(G.rule_vec, 4, G.rule_idx);
+ G.rule_vec[G.rule_idx++] = rule;
+ dbg3("> G.rule_vec[G.rule_idx:%d]=%p", G.rule_idx, G.rule_vec[G.rule_idx]);
+ }
+ }
+
+ return rule;
+}
+
+static int env_matches(struct envmatch *e)
+{
+ while (e) {
+ int r;
+ char *val = getenv(e->envname);
+ if (!val)
+ return 0;
+ r = regexec(&e->match, val, /*size*/ 0, /*range[]*/ NULL, /*eflags*/ 0);
+ if (r != 0) /* no match */
+ return 0;
+ e = e->next;
+ }
+ return 1;
+}
+
+#else
+
+# define next_rule() (&G.cur_rule)
+
+#endif
+
+static void mkdir_recursive(char *name)
+{
+ /* if name has many levels ("dir1/dir2"),
+ * bb_make_directory() will create dir1 according to umask,
+ * not according to its "mode" parameter.
+ * Since we run with umask=0, need to temporarily switch it.
+ */
+ umask(022); /* "dir1" (if any) will be 0755 too */
+ bb_make_directory(name, 0755, FILEUTILS_RECUR);
+ umask(0);
+}