]>
Commit | Line | Data |
---|---|---|
e7145211 | 1 | /* SPDX-License-Identifier: GPL-2.0+ */ |
2232cac8 | 2 | |
2232cac8 | 3 | #include <ctype.h> |
2232cac8 | 4 | |
b5efdb8a | 5 | #include "alloc-util.h" |
2c21044f | 6 | #include "conf-files.h" |
116b91e8 | 7 | #include "def.h" |
480ecb7d | 8 | #include "device-util.h" |
8fb3f009 | 9 | #include "dirent-util.h" |
4f5dd394 | 10 | #include "escape.h" |
3ffd4af2 | 11 | #include "fd-util.h" |
fae0f8a0 | 12 | #include "fileio.h" |
4b3b5bc7 | 13 | #include "format-util.h" |
fdd21be6 | 14 | #include "fs-util.h" |
7d50b32a | 15 | #include "glob-util.h" |
5ea78a39 YW |
16 | #include "libudev-util.h" |
17 | #include "mkdir.h" | |
25de7aa7 | 18 | #include "nulstr-util.h" |
b4ba2fe3 | 19 | #include "parse-util.h" |
4f5dd394 | 20 | #include "path-util.h" |
88b013b2 | 21 | #include "proc-cmdline.h" |
8fcde012 | 22 | #include "stat-util.h" |
84b6ad70 | 23 | #include "strv.h" |
5ea78a39 | 24 | #include "strxcpyx.h" |
f4cf2e5b | 25 | #include "sysctl-util.h" |
07a26e42 | 26 | #include "udev-builtin.h" |
25de7aa7 YW |
27 | #include "udev-event.h" |
28 | #include "udev-rules.h" | |
b1d4f8e1 | 29 | #include "user-util.h" |
2232cac8 | 30 | |
116b91e8 | 31 | #define RULES_DIRS (const char* const*) CONF_PATHS_STRV("udev/rules.d") |
c7521974 | 32 | |
25de7aa7 YW |
33 | static void udev_rule_token_free(UdevRuleToken *token) { |
34 | free(token); | |
915bf0f6 KS |
35 | } |
36 | ||
25de7aa7 YW |
37 | static void udev_rule_line_clear_tokens(UdevRuleLine *rule_line) { |
38 | UdevRuleToken *i, *next; | |
912541b0 | 39 | |
25de7aa7 | 40 | assert(rule_line); |
4052400f | 41 | |
25de7aa7 YW |
42 | LIST_FOREACH_SAFE(tokens, i, next, rule_line->tokens) |
43 | udev_rule_token_free(i); | |
912541b0 | 44 | |
25de7aa7 | 45 | rule_line->tokens = NULL; |
5d6a1fa6 KS |
46 | } |
47 | ||
25de7aa7 YW |
48 | static void udev_rule_line_free(UdevRuleLine *rule_line) { |
49 | if (!rule_line) | |
50 | return; | |
912541b0 | 51 | |
25de7aa7 | 52 | udev_rule_line_clear_tokens(rule_line); |
c7521974 | 53 | |
25de7aa7 YW |
54 | if (rule_line->rule_file) { |
55 | if (rule_line->rule_file->current_line == rule_line) | |
56 | rule_line->rule_file->current_line = rule_line->rule_lines_prev; | |
57 | ||
58 | LIST_REMOVE(rule_lines, rule_line->rule_file->rule_lines, rule_line); | |
912541b0 | 59 | } |
25de7aa7 YW |
60 | |
61 | free(rule_line->line); | |
62 | free(rule_line); | |
4052400f | 63 | } |
154a7b84 | 64 | |
25de7aa7 YW |
65 | DEFINE_TRIVIAL_CLEANUP_FUNC(UdevRuleLine*, udev_rule_line_free); |
66 | ||
67 | static void udev_rule_file_free(UdevRuleFile *rule_file) { | |
68 | UdevRuleLine *i, *next; | |
69 | ||
70 | if (!rule_file) | |
71 | return; | |
72 | ||
73 | LIST_FOREACH_SAFE(rule_lines, i, next, rule_file->rule_lines) | |
74 | udev_rule_line_free(i); | |
912541b0 | 75 | |
25de7aa7 YW |
76 | free(rule_file->filename); |
77 | free(rule_file); | |
4052400f | 78 | } |
530727ae | 79 | |
25de7aa7 YW |
80 | UdevRules *udev_rules_free(UdevRules *rules) { |
81 | UdevRuleFile *i, *next; | |
82 | ||
83 | if (!rules) | |
84 | return NULL; | |
85 | ||
86 | LIST_FOREACH_SAFE(rule_files, i, next, rules->rule_files) | |
87 | udev_rule_file_free(i); | |
88 | ||
89 | hashmap_free_free_key(rules->known_users); | |
90 | hashmap_free_free_key(rules->known_groups); | |
91 | return mfree(rules); | |
c7521974 | 92 | } |
a27cd06c | 93 | |
25de7aa7 | 94 | static void log_unknown_owner(sd_device *dev, UdevRules *rules, int error, const char *entity, const char *name) { |
2da03cbf | 95 | if (IN_SET(abs(error), ENOENT, ESRCH)) |
25de7aa7 | 96 | log_rule_error(dev, rules, "Unknown %s '%s', ignoring", entity, name); |
2da03cbf | 97 | else |
25de7aa7 | 98 | log_rule_error_errno(dev, rules, error, "Failed to resolve %s '%s', ignoring: %m", entity, name); |
2da03cbf ZJS |
99 | } |
100 | ||
25de7aa7 YW |
101 | static int rule_resolve_user(UdevRules *rules, const char *name, uid_t *ret) { |
102 | _cleanup_free_ char *n = NULL; | |
103 | uid_t uid; | |
104 | void *val; | |
23bf8dd7 | 105 | int r; |
912541b0 | 106 | |
25de7aa7 YW |
107 | assert(rules); |
108 | assert(name); | |
109 | ||
110 | val = hashmap_get(rules->known_users, name); | |
111 | if (val) { | |
112 | *ret = PTR_TO_UID(val); | |
113 | return 0; | |
114 | } | |
115 | ||
116 | r = get_user_creds(&name, &uid, NULL, NULL, NULL, USER_CREDS_ALLOW_MISSING); | |
117 | if (r < 0) { | |
118 | log_unknown_owner(NULL, rules, r, "user", name); | |
119 | *ret = UID_INVALID; | |
120 | return 0; | |
912541b0 | 121 | } |
912541b0 | 122 | |
25de7aa7 YW |
123 | n = strdup(name); |
124 | if (!n) | |
530727ae YW |
125 | return -ENOMEM; |
126 | ||
25de7aa7 YW |
127 | r = hashmap_ensure_allocated(&rules->known_users, &string_hash_ops); |
128 | if (r < 0) | |
129 | return r; | |
130 | ||
131 | r = hashmap_put(rules->known_users, n, UID_TO_PTR(uid)); | |
132 | if (r < 0) | |
133 | return r; | |
134 | ||
135 | TAKE_PTR(n); | |
136 | *ret = uid; | |
137 | return 0; | |
154a7b84 KS |
138 | } |
139 | ||
25de7aa7 YW |
140 | static int rule_resolve_group(UdevRules *rules, const char *name, gid_t *ret) { |
141 | _cleanup_free_ char *n = NULL; | |
142 | gid_t gid; | |
143 | void *val; | |
23bf8dd7 | 144 | int r; |
912541b0 | 145 | |
25de7aa7 YW |
146 | assert(rules); |
147 | assert(name); | |
148 | ||
149 | val = hashmap_get(rules->known_groups, name); | |
150 | if (val) { | |
151 | *ret = PTR_TO_GID(val); | |
152 | return 0; | |
153 | } | |
154 | ||
155 | r = get_group_creds(&name, &gid, USER_CREDS_ALLOW_MISSING); | |
156 | if (r < 0) { | |
157 | log_unknown_owner(NULL, rules, r, "group", name); | |
158 | *ret = GID_INVALID; | |
159 | return 0; | |
912541b0 | 160 | } |
912541b0 | 161 | |
25de7aa7 YW |
162 | n = strdup(name); |
163 | if (!n) | |
530727ae YW |
164 | return -ENOMEM; |
165 | ||
25de7aa7 YW |
166 | r = hashmap_ensure_allocated(&rules->known_groups, &string_hash_ops); |
167 | if (r < 0) | |
168 | return r; | |
169 | ||
170 | r = hashmap_put(rules->known_groups, n, GID_TO_PTR(gid)); | |
171 | if (r < 0) | |
172 | return r; | |
173 | ||
174 | TAKE_PTR(n); | |
175 | *ret = gid; | |
176 | return 0; | |
154a7b84 KS |
177 | } |
178 | ||
25de7aa7 YW |
179 | static UdevRuleSubstituteType rule_get_substitution_type(const char *str) { |
180 | assert(str); | |
912541b0 | 181 | |
25de7aa7 YW |
182 | if (str[0] == '[') |
183 | return SUBST_TYPE_SUBSYS; | |
184 | if (strchr(str, '%') || strchr(str, '$')) | |
185 | return SUBST_TYPE_FORMAT; | |
186 | return SUBST_TYPE_PLAIN; | |
187 | } | |
912541b0 | 188 | |
25de7aa7 YW |
189 | static void rule_line_append_token(UdevRuleLine *rule_line, UdevRuleToken *token) { |
190 | assert(rule_line); | |
191 | assert(token); | |
912541b0 | 192 | |
25de7aa7 YW |
193 | if (rule_line->current_token) |
194 | LIST_APPEND(tokens, rule_line->current_token, token); | |
195 | else | |
196 | LIST_APPEND(tokens, rule_line->tokens, token); | |
912541b0 | 197 | |
25de7aa7 YW |
198 | rule_line->current_token = token; |
199 | } | |
912541b0 | 200 | |
25de7aa7 YW |
201 | static int rule_line_add_token(UdevRuleLine *rule_line, UdevRuleTokenType type, UdevRuleOperatorType op, char *value, void *data) { |
202 | UdevRuleToken *token; | |
203 | UdevRuleMatchType match_type = _MATCH_TYPE_INVALID; | |
204 | UdevRuleSubstituteType subst_type = _SUBST_TYPE_INVALID; | |
205 | bool remove_trailing_whitespace = false; | |
206 | size_t len; | |
912541b0 | 207 | |
25de7aa7 YW |
208 | assert(rule_line); |
209 | assert(type >= 0 && type < _TK_TYPE_MAX); | |
210 | assert(op >= 0 && op < _OP_TYPE_MAX); | |
211 | ||
212 | if (type < _TK_M_MAX) { | |
213 | assert(value); | |
214 | assert(IN_SET(op, OP_MATCH, OP_NOMATCH)); | |
215 | ||
216 | if (type == TK_M_SUBSYSTEM && STR_IN_SET(value, "subsystem", "bus", "class")) | |
217 | match_type = MATCH_TYPE_SUBSYSTEM; | |
218 | else if (isempty(value)) | |
219 | match_type = MATCH_TYPE_EMPTY; | |
220 | else if (streq(value, "?*")) { | |
221 | /* Convert KEY=="?*" -> KEY!="" */ | |
222 | match_type = MATCH_TYPE_EMPTY; | |
223 | op = op == OP_MATCH ? OP_NOMATCH : OP_MATCH; | |
224 | } else if (string_is_glob(value)) | |
225 | match_type = MATCH_TYPE_GLOB; | |
226 | else | |
227 | match_type = MATCH_TYPE_PLAIN; | |
912541b0 | 228 | |
25de7aa7 YW |
229 | if (type < TK_M_TEST || type == TK_M_RESULT) { |
230 | /* Convert value string to nulstr. */ | |
231 | len = strlen(value); | |
232 | if (len > 1 && (value[len - 1] == '|' || strstr(value, "||"))) { | |
233 | /* In this case, just replacing '|' -> '\0' does not work... */ | |
234 | _cleanup_free_ char *tmp = NULL; | |
235 | char *i, *j; | |
236 | bool v = true; | |
237 | ||
238 | tmp = strdup(value); | |
239 | if (!tmp) | |
240 | return log_oom(); | |
912541b0 | 241 | |
25de7aa7 YW |
242 | for (i = tmp, j = value; *i != '\0'; i++) |
243 | if (*i == '|') | |
244 | v = true; | |
245 | else { | |
246 | if (v) { | |
247 | *j++ = '\0'; | |
248 | v = false; | |
249 | } | |
250 | *j++ = *i; | |
251 | } | |
252 | j[0] = j[1] = '\0'; | |
253 | } else { | |
254 | /* Simple conversion. */ | |
255 | char *i; | |
256 | ||
257 | for (i = value; *i != '\0'; i++) | |
258 | if (*i == '|') | |
259 | *i = '\0'; | |
260 | } | |
261 | } | |
912541b0 KS |
262 | } |
263 | ||
25de7aa7 YW |
264 | if (IN_SET(type, TK_M_ATTR, TK_M_PARENTS_ATTR)) { |
265 | assert(value); | |
266 | assert(data); | |
be4bedd1 | 267 | |
25de7aa7 YW |
268 | len = strlen(value); |
269 | if (len > 0 && !isspace(value[len - 1])) | |
270 | remove_trailing_whitespace = true; | |
912541b0 | 271 | |
25de7aa7 YW |
272 | subst_type = rule_get_substitution_type((const char*) data); |
273 | } | |
fae0f8a0 | 274 | |
25de7aa7 YW |
275 | token = new(UdevRuleToken, 1); |
276 | if (!token) | |
277 | return -ENOMEM; | |
fae0f8a0 | 278 | |
25de7aa7 YW |
279 | *token = (UdevRuleToken) { |
280 | .type = type, | |
281 | .op = op, | |
282 | .value = value, | |
283 | .data = data, | |
284 | .match_type = match_type, | |
285 | .attr_subst_type = subst_type, | |
286 | .attr_match_remove_trailing_whitespace = remove_trailing_whitespace, | |
287 | }; | |
fae0f8a0 | 288 | |
25de7aa7 YW |
289 | rule_line_append_token(rule_line, token); |
290 | ||
291 | if (token->type == TK_A_NAME) | |
292 | SET_FLAG(rule_line->type, LINE_HAS_NAME, true); | |
293 | ||
294 | else if (IN_SET(token->type, TK_A_DEVLINK, | |
295 | TK_A_OWNER, TK_A_GROUP, TK_A_MODE, | |
296 | TK_A_OWNER_ID, TK_A_GROUP_ID, TK_A_MODE_ID)) | |
297 | SET_FLAG(rule_line->type, LINE_HAS_DEVLINK, true); | |
298 | ||
299 | else if (token->type >= _TK_A_MIN || | |
300 | IN_SET(token->type, | |
301 | TK_M_IMPORT_FILE, TK_M_IMPORT_PROGRAM, TK_M_IMPORT_BUILTIN, | |
302 | TK_M_IMPORT_DB, TK_M_IMPORT_CMDLINE, TK_M_IMPORT_PARENT)) | |
303 | SET_FLAG(rule_line->type, LINE_UPDATE_SOMETHING, true); | |
fae0f8a0 | 304 | |
912541b0 | 305 | return 0; |
bd0ed2ff KS |
306 | } |
307 | ||
25de7aa7 YW |
308 | static int parse_token(UdevRules *rules, const char *key, char *attr, UdevRuleOperatorType op, char *value) { |
309 | bool is_match = IN_SET(op, OP_MATCH, OP_NOMATCH); | |
310 | UdevRuleLine *rule_line; | |
a7521142 | 311 | int r; |
912541b0 | 312 | |
25de7aa7 YW |
313 | assert(rules); |
314 | assert(rules->current_file); | |
315 | assert(rules->current_file->current_line); | |
316 | assert(key); | |
317 | assert(value); | |
318 | ||
319 | rule_line = rules->current_file->current_line; | |
320 | ||
321 | if (streq(key, "ACTION")) { | |
322 | if (attr) | |
323 | return log_token_invalid_attr(rules, key); | |
324 | if (!is_match) | |
325 | return log_token_invalid_op(rules, key); | |
326 | ||
327 | r = rule_line_add_token(rule_line, TK_M_ACTION, op, value, NULL); | |
328 | } else if (streq(key, "DEVPATH")) { | |
329 | if (attr) | |
330 | return log_token_invalid_attr(rules, key); | |
331 | if (!is_match) | |
332 | return log_token_invalid_op(rules, key); | |
333 | ||
334 | r = rule_line_add_token(rule_line, TK_M_DEVPATH, op, value, NULL); | |
335 | } else if (streq(key, "KERNEL")) { | |
336 | if (attr) | |
337 | return log_token_invalid_attr(rules, key); | |
338 | if (!is_match) | |
339 | return log_token_invalid_op(rules, key); | |
340 | ||
341 | r = rule_line_add_token(rule_line, TK_M_KERNEL, op, value, NULL); | |
342 | } else if (streq(key, "SYMLINK")) { | |
343 | if (attr) | |
344 | return log_token_invalid_attr(rules, key); | |
345 | if (op == OP_REMOVE) | |
346 | return log_token_invalid_op(rules, key); | |
347 | ||
d7aee41d YW |
348 | if (!is_match) { |
349 | if (udev_check_format(value) < 0) | |
350 | log_token_invalid_value(rules, key, value); | |
351 | ||
352 | r = rule_line_add_token(rule_line, TK_A_DEVLINK, op, value, NULL); | |
353 | } else | |
354 | r = rule_line_add_token(rule_line, TK_M_DEVLINK, op, value, NULL); | |
25de7aa7 YW |
355 | } else if (streq(key, "NAME")) { |
356 | if (attr) | |
357 | return log_token_invalid_attr(rules, key); | |
358 | if (op == OP_REMOVE) | |
359 | return log_token_invalid_op(rules, key); | |
360 | if (op == OP_ADD) { | |
361 | log_token_warning(rules, "%s key takes '==', '!=', '=', or ':=' operator, assuming '=', but please fix it.", key); | |
362 | op = OP_ASSIGN; | |
363 | } | |
364 | ||
365 | if (!is_match) { | |
366 | if (streq(value, "%k")) | |
367 | return log_token_error_errno(rules, SYNTHETIC_ERRNO(EINVAL), | |
368 | "Ignoring NAME=\"%%k\" is ignored, as it breaks kernel supplied names."); | |
369 | if (isempty(value)) | |
370 | return log_token_error_errno(rules, SYNTHETIC_ERRNO(EINVAL), | |
371 | "Ignoring NAME=\"\", as udev will not delete any device nodes."); | |
d7aee41d YW |
372 | if (udev_check_format(value) < 0) |
373 | log_token_invalid_value(rules, key, value); | |
374 | ||
25de7aa7 YW |
375 | r = rule_line_add_token(rule_line, TK_A_NAME, op, value, NULL); |
376 | } else | |
377 | r = rule_line_add_token(rule_line, TK_M_NAME, op, value, NULL); | |
378 | } else if (streq(key, "ENV")) { | |
379 | if (isempty(attr)) | |
380 | return log_token_invalid_attr(rules, key); | |
381 | if (op == OP_REMOVE) | |
382 | return log_token_invalid_op(rules, key); | |
383 | if (op == OP_ASSIGN_FINAL) { | |
384 | log_token_warning(rules, "%s key takes '==', '!=', '=', or '+=' operator, assuming '=', but please fix it.", key); | |
385 | op = OP_ASSIGN; | |
386 | } | |
912541b0 | 387 | |
25de7aa7 YW |
388 | if (!is_match) { |
389 | if (STR_IN_SET(attr, | |
390 | "ACTION", "DEVLINKS", "DEVNAME", "DEVPATH", "DEVTYPE", "DRIVER", | |
391 | "IFINDEX", "MAJOR", "MINOR", "SEQNUM", "SUBSYSTEM", "TAGS")) | |
392 | return log_token_error_errno(rules, SYNTHETIC_ERRNO(EINVAL), | |
393 | "Invalid ENV attribute. '%s' cannot be set.", attr); | |
912541b0 | 394 | |
d7aee41d YW |
395 | if (udev_check_format(value) < 0) |
396 | log_token_invalid_value(rules, key, value); | |
25de7aa7 YW |
397 | r = rule_line_add_token(rule_line, TK_A_ENV, op, value, attr); |
398 | } else | |
399 | r = rule_line_add_token(rule_line, TK_M_ENV, op, value, attr); | |
400 | } else if (streq(key, "TAG")) { | |
401 | if (attr) | |
402 | return log_token_invalid_attr(rules, key); | |
403 | if (op == OP_ASSIGN_FINAL) { | |
404 | log_token_warning(rules, "%s key takes '==', '!=', '=', or '+=' operator, assuming '=', but please fix it.", key); | |
405 | op = OP_ASSIGN; | |
912541b0 | 406 | } |
319c6700 | 407 | |
d7aee41d YW |
408 | if (!is_match) { |
409 | if (isempty(value) || udev_check_format(value) < 0) | |
410 | log_token_invalid_value(rules, key, value); | |
411 | r = rule_line_add_token(rule_line, TK_A_TAG, op, value, NULL); | |
412 | } else | |
413 | r = rule_line_add_token(rule_line, TK_M_TAG, op, value, NULL); | |
25de7aa7 YW |
414 | } else if (streq(key, "SUBSYSTEM")) { |
415 | if (attr) | |
416 | return log_token_invalid_attr(rules, key); | |
417 | if (!is_match) | |
418 | return log_token_invalid_op(rules, key); | |
419 | ||
420 | if (STR_IN_SET(value, "bus", "class")) | |
421 | log_token_warning(rules, "'%s' must be specified as 'subsystem'; please fix it", value); | |
422 | ||
423 | r = rule_line_add_token(rule_line, TK_M_SUBSYSTEM, op, value, NULL); | |
424 | } else if (streq(key, "DRIVER")) { | |
425 | if (attr) | |
426 | return log_token_invalid_attr(rules, key); | |
427 | if (!is_match) | |
428 | return log_token_invalid_op(rules, key); | |
429 | ||
430 | r = rule_line_add_token(rule_line, TK_M_DRIVER, op, value, NULL); | |
431 | } else if (streq(key, "ATTR")) { | |
432 | if (isempty(attr)) | |
d7aee41d YW |
433 | return log_token_invalid_attr(rules, key); |
434 | if (udev_check_format(attr) < 0) | |
435 | log_token_invalid_attr_format(rules, key, attr); | |
25de7aa7 YW |
436 | if (op == OP_REMOVE) |
437 | return log_token_invalid_op(rules, key); | |
438 | if (IN_SET(op, OP_ADD, OP_ASSIGN_FINAL)) { | |
439 | log_token_warning(rules, "%s key takes '==', '!=', or '=' operator, assuming '=', but please fix it.", key); | |
440 | op = OP_ASSIGN; | |
441 | } | |
912541b0 | 442 | |
d7aee41d YW |
443 | if (!is_match) { |
444 | if (udev_check_format(value) < 0) | |
445 | log_token_invalid_value(rules, key, value); | |
446 | ||
447 | r = rule_line_add_token(rule_line, TK_A_ATTR, op, value, attr); | |
448 | } else | |
449 | r = rule_line_add_token(rule_line, TK_M_ATTR, op, value, attr); | |
25de7aa7 YW |
450 | } else if (streq(key, "SYSCTL")) { |
451 | if (isempty(attr)) | |
452 | return log_token_invalid_attr(rules, key); | |
d7aee41d YW |
453 | if (udev_check_format(attr) < 0) |
454 | log_token_invalid_attr_format(rules, key, attr); | |
25de7aa7 YW |
455 | if (op == OP_REMOVE) |
456 | return log_token_invalid_op(rules, key); | |
457 | if (IN_SET(op, OP_ADD, OP_ASSIGN_FINAL)) { | |
458 | log_token_warning(rules, "%s key takes '==', '!=', or '=' operator, assuming '=', but please fix it.", key); | |
459 | op = OP_ASSIGN; | |
460 | } | |
3b64e4d4 | 461 | |
d7aee41d YW |
462 | if (!is_match) { |
463 | if (udev_check_format(value) < 0) | |
464 | log_token_invalid_value(rules, key, value); | |
465 | ||
466 | r = rule_line_add_token(rule_line, TK_A_SYSCTL, op, value, attr); | |
467 | } else | |
468 | r = rule_line_add_token(rule_line, TK_M_SYSCTL, op, value, attr); | |
25de7aa7 YW |
469 | } else if (streq(key, "KERNELS")) { |
470 | if (attr) | |
471 | return log_token_invalid_attr(rules, key); | |
472 | if (!is_match) | |
473 | return log_token_invalid_op(rules, key); | |
474 | ||
475 | r = rule_line_add_token(rule_line, TK_M_PARENTS_KERNEL, op, value, NULL); | |
476 | } else if (streq(key, "SUBSYSTEMS")) { | |
477 | if (attr) | |
478 | return log_token_invalid_attr(rules, key); | |
479 | if (!is_match) | |
480 | return log_token_invalid_op(rules, key); | |
481 | ||
482 | r = rule_line_add_token(rule_line, TK_M_PARENTS_SUBSYSTEM, op, value, NULL); | |
483 | } else if (streq(key, "DRIVERS")) { | |
484 | if (attr) | |
485 | return log_token_invalid_attr(rules, key); | |
486 | if (!is_match) | |
487 | return log_token_invalid_op(rules, key); | |
488 | ||
489 | r = rule_line_add_token(rule_line, TK_M_PARENTS_DRIVER, op, value, NULL); | |
490 | } else if (streq(key, "ATTRS")) { | |
491 | if (isempty(attr)) | |
492 | return log_token_invalid_attr(rules, key); | |
d7aee41d YW |
493 | if (udev_check_format(attr) < 0) |
494 | log_token_invalid_attr_format(rules, key, attr); | |
25de7aa7 YW |
495 | if (!is_match) |
496 | return log_token_invalid_op(rules, key); | |
497 | ||
498 | if (startswith(attr, "device/")) | |
499 | log_token_warning(rules, "'device' link may not be available in future kernels; please fix it."); | |
500 | if (strstr(attr, "../")) | |
501 | log_token_warning(rules, "Direct reference to parent sysfs directory, may break in future kernels; please fix it."); | |
502 | ||
503 | r = rule_line_add_token(rule_line, TK_M_PARENTS_ATTR, op, value, attr); | |
504 | } else if (streq(key, "TAGS")) { | |
505 | if (attr) | |
506 | return log_token_invalid_attr(rules, key); | |
507 | if (!is_match) | |
508 | return log_token_invalid_op(rules, key); | |
509 | ||
510 | r = rule_line_add_token(rule_line, TK_M_PARENTS_TAG, op, value, NULL); | |
511 | } else if (streq(key, "TEST")) { | |
512 | mode_t mode = MODE_INVALID; | |
513 | ||
514 | if (!isempty(attr)) { | |
515 | r = parse_mode(attr, &mode); | |
516 | if (r < 0) | |
517 | return log_token_error_errno(rules, r, "Failed to parse mode '%s': %m", attr); | |
518 | } | |
d7aee41d YW |
519 | if (isempty(value) || udev_check_format(value) < 0) |
520 | log_token_invalid_value(rules, key, value); | |
25de7aa7 YW |
521 | if (!is_match) |
522 | return log_token_invalid_op(rules, key); | |
523 | ||
524 | r = rule_line_add_token(rule_line, TK_M_TEST, op, value, MODE_TO_PTR(mode)); | |
525 | } else if (streq(key, "PROGRAM")) { | |
526 | if (attr) | |
527 | return log_token_invalid_attr(rules, key); | |
d7aee41d YW |
528 | if (isempty(value) || udev_check_format(value) < 0) |
529 | log_token_invalid_value(rules, key, value); | |
25de7aa7 YW |
530 | if (op == OP_REMOVE) |
531 | return log_token_invalid_op(rules, key); | |
532 | if (!is_match) { | |
533 | if (op == OP_ASSIGN) | |
534 | log_token_debug(rules, "Operator '=' is specified to %s key, assuming '=='.", key); | |
535 | else | |
536 | log_token_warning(rules, "%s key takes '==' or '!=' operator, assuming '==', but please fix it.", key); | |
537 | op = OP_MATCH; | |
538 | } | |
912541b0 | 539 | |
25de7aa7 YW |
540 | r = rule_line_add_token(rule_line, TK_M_PROGRAM, op, value, NULL); |
541 | } else if (streq(key, "IMPORT")) { | |
542 | if (isempty(attr)) | |
543 | return log_token_invalid_attr(rules, key); | |
d7aee41d YW |
544 | if (isempty(value) || udev_check_format(value) < 0) |
545 | log_token_invalid_value(rules, key, value); | |
25de7aa7 YW |
546 | if (op == OP_REMOVE) |
547 | return log_token_invalid_op(rules, key); | |
548 | if (!is_match) { | |
549 | if (op == OP_ASSIGN) | |
550 | log_token_debug(rules, "Operator '=' is specified to %s key, assuming '=='.", key); | |
551 | else | |
552 | log_token_warning(rules, "%s key takes '==' or '!=' operator, assuming '==', but please fix it.", key); | |
553 | op = OP_MATCH; | |
554 | } | |
e2ecb34f | 555 | |
25de7aa7 YW |
556 | if (streq(attr, "file")) |
557 | r = rule_line_add_token(rule_line, TK_M_IMPORT_FILE, op, value, NULL); | |
558 | else if (streq(attr, "program")) { | |
559 | UdevBuiltinCommand cmd; | |
f4850a1d | 560 | |
25de7aa7 YW |
561 | cmd = udev_builtin_lookup(value); |
562 | if (cmd >= 0) { | |
563 | log_token_debug(rules,"Found builtin command '%s' for %s, replacing attribute", value, key); | |
564 | r = rule_line_add_token(rule_line, TK_M_IMPORT_BUILTIN, op, value, UDEV_BUILTIN_CMD_TO_PTR(cmd)); | |
565 | } else | |
566 | r = rule_line_add_token(rule_line, TK_M_IMPORT_PROGRAM, op, value, NULL); | |
567 | } else if (streq(attr, "builtin")) { | |
568 | UdevBuiltinCommand cmd; | |
569 | ||
570 | cmd = udev_builtin_lookup(value); | |
571 | if (cmd < 0) | |
572 | return log_token_error_errno(rules, SYNTHETIC_ERRNO(EINVAL), | |
573 | "Unknown builtin command: %s", value); | |
574 | r = rule_line_add_token(rule_line, TK_M_IMPORT_BUILTIN, op, value, UDEV_BUILTIN_CMD_TO_PTR(cmd)); | |
575 | } else if (streq(attr, "db")) | |
576 | r = rule_line_add_token(rule_line, TK_M_IMPORT_DB, op, value, NULL); | |
577 | else if (streq(attr, "cmdline")) | |
578 | r = rule_line_add_token(rule_line, TK_M_IMPORT_CMDLINE, op, value, NULL); | |
579 | else if (streq(attr, "parent")) | |
580 | r = rule_line_add_token(rule_line, TK_M_IMPORT_PARENT, op, value, NULL); | |
581 | else | |
582 | return log_token_invalid_attr(rules, key); | |
583 | } else if (streq(key, "RESULT")) { | |
584 | if (attr) | |
585 | return log_token_invalid_attr(rules, key); | |
586 | if (!is_match) | |
587 | return log_token_invalid_op(rules, key); | |
588 | ||
589 | r = rule_line_add_token(rule_line, TK_M_RESULT, op, value, NULL); | |
590 | } else if (streq(key, "OPTIONS")) { | |
591 | char *tmp; | |
592 | ||
593 | if (attr) | |
594 | return log_token_invalid_attr(rules, key); | |
595 | if (is_match || op == OP_REMOVE) | |
596 | return log_token_invalid_op(rules, key); | |
597 | if (op == OP_ADD) { | |
598 | log_token_debug(rules, "Operator '+=' is specified to %s key, assuming '='.", key); | |
599 | op = OP_ASSIGN; | |
600 | } | |
f4850a1d | 601 | |
25de7aa7 YW |
602 | if (streq(value, "string_escape=none")) |
603 | r = rule_line_add_token(rule_line, TK_A_OPTIONS_STRING_ESCAPE_NONE, op, NULL, NULL); | |
604 | else if (streq(value, "string_escape=replace")) | |
605 | r = rule_line_add_token(rule_line, TK_A_OPTIONS_STRING_ESCAPE_REPLACE, op, NULL, NULL); | |
606 | else if (streq(value, "db_persist")) | |
607 | r = rule_line_add_token(rule_line, TK_A_OPTIONS_DB_PERSIST, op, NULL, NULL); | |
608 | else if (streq(value, "watch")) | |
609 | r = rule_line_add_token(rule_line, TK_A_OPTIONS_INOTIFY_WATCH, op, NULL, INT_TO_PTR(1)); | |
610 | else if (streq(value, "nowatch")) | |
611 | r = rule_line_add_token(rule_line, TK_A_OPTIONS_INOTIFY_WATCH, op, NULL, INT_TO_PTR(0)); | |
612 | else if ((tmp = startswith(value, "static_node="))) | |
613 | r = rule_line_add_token(rule_line, TK_A_OPTIONS_STATIC_NODE, op, tmp, NULL); | |
614 | else if ((tmp = startswith(value, "link_priority="))) { | |
615 | int prio; | |
616 | ||
617 | r = safe_atoi(tmp, &prio); | |
618 | if (r < 0) | |
619 | return log_token_error_errno(rules, r, "Failed to parse link priority '%s': %m", tmp); | |
620 | r = rule_line_add_token(rule_line, TK_A_OPTIONS_DEVLINK_PRIORITY, op, NULL, INT_TO_PTR(prio)); | |
621 | } else { | |
622 | log_token_warning(rules, "Invalid value for OPTIONS key, ignoring: '%s'", value); | |
623 | return 0; | |
624 | } | |
625 | } else if (streq(key, "OWNER")) { | |
626 | uid_t uid; | |
f4850a1d | 627 | |
25de7aa7 YW |
628 | if (attr) |
629 | return log_token_invalid_attr(rules, key); | |
630 | if (is_match || op == OP_REMOVE) | |
631 | return log_token_invalid_op(rules, key); | |
632 | if (op == OP_ADD) { | |
633 | log_token_warning(rules, "%s key takes '=' or ':=' operator, assuming '=', but please fix it.", key); | |
634 | op = OP_ASSIGN; | |
635 | } | |
f4850a1d | 636 | |
25de7aa7 YW |
637 | if (parse_uid(value, &uid) >= 0) |
638 | r = rule_line_add_token(rule_line, TK_A_OWNER_ID, op, NULL, UID_TO_PTR(uid)); | |
639 | else if (rules->resolve_name_timing == RESOLVE_NAME_EARLY && | |
640 | rule_get_substitution_type(value) == SUBST_TYPE_PLAIN) { | |
641 | r = rule_resolve_user(rules, value, &uid); | |
642 | if (r < 0) | |
643 | return log_token_error_errno(rules, r, "Failed to resolve user name '%s': %m", value); | |
644 | ||
645 | r = rule_line_add_token(rule_line, TK_A_OWNER_ID, op, NULL, UID_TO_PTR(uid)); | |
d7aee41d YW |
646 | } else if (rules->resolve_name_timing != RESOLVE_NAME_NEVER) { |
647 | if (isempty(value) || udev_check_format(value) < 0) | |
648 | log_token_invalid_value(rules, key, value); | |
25de7aa7 | 649 | r = rule_line_add_token(rule_line, TK_A_OWNER, op, value, NULL); |
d7aee41d | 650 | } else { |
25de7aa7 YW |
651 | log_token_debug(rules, "Resolving user name is disabled, ignoring %s=%s", key, value); |
652 | return 0; | |
653 | } | |
654 | } else if (streq(key, "GROUP")) { | |
655 | gid_t gid; | |
656 | ||
657 | if (attr) | |
658 | return log_token_invalid_attr(rules, key); | |
659 | if (is_match || op == OP_REMOVE) | |
660 | return log_token_invalid_op(rules, key); | |
661 | if (op == OP_ADD) { | |
662 | log_token_warning(rules, "%s key takes '=' or ':=' operator, assuming '=', but please fix it.", key); | |
663 | op = OP_ASSIGN; | |
664 | } | |
665 | ||
666 | if (parse_gid(value, &gid) >= 0) | |
667 | r = rule_line_add_token(rule_line, TK_A_GROUP_ID, op, NULL, GID_TO_PTR(gid)); | |
668 | else if (rules->resolve_name_timing == RESOLVE_NAME_EARLY && | |
669 | rule_get_substitution_type(value) == SUBST_TYPE_PLAIN) { | |
670 | r = rule_resolve_group(rules, value, &gid); | |
671 | if (r < 0) | |
672 | return log_token_error_errno(rules, r, "Failed to resolve group name '%s': %m", value); | |
673 | ||
674 | r = rule_line_add_token(rule_line, TK_A_GROUP_ID, op, NULL, GID_TO_PTR(gid)); | |
d7aee41d YW |
675 | } else if (rules->resolve_name_timing != RESOLVE_NAME_NEVER) { |
676 | if (isempty(value) || udev_check_format(value) < 0) | |
677 | log_token_invalid_value(rules, key, value); | |
25de7aa7 | 678 | r = rule_line_add_token(rule_line, TK_A_GROUP, op, value, NULL); |
d7aee41d | 679 | } else { |
25de7aa7 YW |
680 | log_token_debug(rules, "Resolving group name is disabled, ignoring %s=%s", key, value); |
681 | return 0; | |
682 | } | |
683 | } else if (streq(key, "MODE")) { | |
684 | mode_t mode; | |
685 | ||
686 | if (attr) | |
687 | return log_token_invalid_attr(rules, key); | |
688 | if (is_match || op == OP_REMOVE) | |
689 | return log_token_invalid_op(rules, key); | |
690 | if (op == OP_ADD) { | |
691 | log_token_warning(rules, "%s key takes '=' or ':=' operator, assuming '=', but please fix it.", key); | |
692 | op = OP_ASSIGN; | |
693 | } | |
694 | ||
695 | if (parse_mode(value, &mode) >= 0) | |
696 | r = rule_line_add_token(rule_line, TK_A_MODE_ID, op, NULL, MODE_TO_PTR(mode)); | |
d7aee41d YW |
697 | else { |
698 | if (isempty(value) || udev_check_format(value) < 0) | |
699 | log_token_invalid_value(rules, key, value); | |
25de7aa7 | 700 | r = rule_line_add_token(rule_line, TK_A_MODE, op, value, NULL); |
d7aee41d | 701 | } |
25de7aa7 YW |
702 | } else if (streq(key, "SECLABEL")) { |
703 | if (isempty(attr)) | |
704 | return log_token_invalid_attr(rules, key); | |
d7aee41d YW |
705 | if (isempty(value) || udev_check_format(value) < 0) |
706 | log_token_invalid_value(rules, key, value); | |
25de7aa7 YW |
707 | if (is_match || op == OP_REMOVE) |
708 | return log_token_invalid_op(rules, key); | |
709 | if (op == OP_ASSIGN_FINAL) { | |
710 | log_token_warning(rules, "%s key takes '=' or '+=' operator, assuming '=', but please fix it.", key); | |
711 | op = OP_ASSIGN; | |
712 | } | |
713 | ||
714 | r = rule_line_add_token(rule_line, TK_A_SECLABEL, op, value, NULL); | |
715 | } else if (streq(key, "RUN")) { | |
716 | if (is_match || op == OP_REMOVE) | |
717 | return log_token_invalid_op(rules, key); | |
d7aee41d YW |
718 | if (isempty(value) || udev_check_format(value) < 0) |
719 | log_token_invalid_value(rules, key, value); | |
25de7aa7 YW |
720 | if (!attr || streq(attr, "program")) |
721 | r = rule_line_add_token(rule_line, TK_A_RUN_PROGRAM, op, value, NULL); | |
722 | else if (streq(attr, "builtin")) { | |
723 | UdevBuiltinCommand cmd; | |
724 | ||
725 | cmd = udev_builtin_lookup(value); | |
726 | if (cmd < 0) | |
727 | return log_token_error_errno(rules, SYNTHETIC_ERRNO(EINVAL), | |
728 | "Unknown builtin command '%s', ignoring", value); | |
729 | r = rule_line_add_token(rule_line, TK_A_RUN_BUILTIN, op, value, UDEV_BUILTIN_CMD_TO_PTR(cmd)); | |
730 | } else | |
731 | return log_token_invalid_attr(rules, key); | |
732 | } else if (streq(key, "GOTO")) { | |
733 | if (attr) | |
734 | return log_token_invalid_attr(rules, key); | |
735 | if (op != OP_ASSIGN) | |
736 | return log_token_invalid_op(rules, key); | |
737 | if (FLAGS_SET(rule_line->type, LINE_HAS_GOTO)) { | |
738 | log_token_warning(rules, "Contains multiple GOTO key, ignoring GOTO=\"%s\".", value); | |
739 | return 0; | |
912541b0 | 740 | } |
25de7aa7 YW |
741 | |
742 | rule_line->goto_label = value; | |
743 | SET_FLAG(rule_line->type, LINE_HAS_GOTO, true); | |
744 | return 1; | |
745 | } else if (streq(key, "LABEL")) { | |
746 | if (attr) | |
747 | return log_token_invalid_attr(rules, key); | |
748 | if (op != OP_ASSIGN) | |
749 | return log_token_invalid_op(rules, key); | |
750 | ||
751 | rule_line->label = value; | |
752 | SET_FLAG(rule_line->type, LINE_HAS_LABEL, true); | |
753 | return 1; | |
754 | } else | |
755 | return log_token_error_errno(rules, SYNTHETIC_ERRNO(EINVAL), "Invalid key '%s'", key); | |
756 | if (r < 0) | |
757 | return log_oom(); | |
758 | ||
759 | return 1; | |
0ea5e96e KS |
760 | } |
761 | ||
25de7aa7 YW |
762 | static UdevRuleOperatorType parse_operator(const char *op) { |
763 | assert(op); | |
764 | ||
765 | if (startswith(op, "==")) | |
766 | return OP_MATCH; | |
767 | if (startswith(op, "!=")) | |
768 | return OP_NOMATCH; | |
769 | if (startswith(op, "+=")) | |
770 | return OP_ADD; | |
771 | if (startswith(op, "-=")) | |
772 | return OP_REMOVE; | |
773 | if (startswith(op, "=")) | |
774 | return OP_ASSIGN; | |
775 | if (startswith(op, ":=")) | |
776 | return OP_ASSIGN_FINAL; | |
777 | ||
778 | return _OP_TYPE_INVALID; | |
779 | } | |
912541b0 | 780 | |
25de7aa7 YW |
781 | static int parse_line(char **line, char **ret_key, char **ret_attr, UdevRuleOperatorType *ret_op, char **ret_value) { |
782 | char *key_begin, *key_end, *attr, *tmp, *value, *i, *j; | |
783 | UdevRuleOperatorType op; | |
912541b0 | 784 | |
25de7aa7 YW |
785 | assert(line); |
786 | assert(*line); | |
787 | assert(ret_key); | |
788 | assert(ret_op); | |
789 | assert(ret_value); | |
912541b0 | 790 | |
25de7aa7 | 791 | key_begin = skip_leading_chars(*line, WHITESPACE ","); |
912541b0 | 792 | |
25de7aa7 YW |
793 | if (isempty(key_begin)) |
794 | return 0; | |
795 | ||
796 | for (key_end = key_begin; ; key_end++) { | |
797 | if (key_end[0] == '\0') | |
704dbfb2 | 798 | return -EINVAL; |
25de7aa7 | 799 | if (strchr(WHITESPACE "={", key_end[0])) |
912541b0 | 800 | break; |
25de7aa7 | 801 | if (strchr("+-!:", key_end[0]) && key_end[1] == '=') |
912541b0 | 802 | break; |
25de7aa7 YW |
803 | } |
804 | if (key_end[0] == '{') { | |
805 | attr = key_end + 1; | |
806 | tmp = strchr(attr, '}'); | |
807 | if (!tmp) | |
808 | return -EINVAL; | |
809 | *tmp++ = '\0'; | |
810 | } else { | |
811 | attr = NULL; | |
812 | tmp = key_end; | |
912541b0 KS |
813 | } |
814 | ||
25de7aa7 YW |
815 | tmp = skip_leading_chars(tmp, NULL); |
816 | op = parse_operator(tmp); | |
817 | if (op < 0) | |
704dbfb2 | 818 | return -EINVAL; |
912541b0 | 819 | |
25de7aa7 | 820 | key_end[0] = '\0'; |
912541b0 | 821 | |
25de7aa7 YW |
822 | tmp += op == OP_ASSIGN ? 1 : 2; |
823 | value = skip_leading_chars(tmp, NULL); | |
912541b0 | 824 | |
25de7aa7 YW |
825 | /* value must be double quotated */ |
826 | if (value[0] != '"') | |
704dbfb2 | 827 | return -EINVAL; |
25de7aa7 | 828 | value++; |
7e760b79 | 829 | |
25de7aa7 YW |
830 | /* unescape double quotation '\"' -> '"' */ |
831 | for (i = j = value; ; i++, j++) { | |
832 | if (*i == '"') | |
7e760b79 | 833 | break; |
25de7aa7 | 834 | if (*i == '\0') |
704dbfb2 | 835 | return -EINVAL; |
25de7aa7 YW |
836 | if (i[0] == '\\' && i[1] == '"') |
837 | i++; | |
838 | *j = *i; | |
839 | } | |
840 | j[0] = '\0'; | |
841 | ||
842 | *line = i+1; | |
843 | *ret_key = key_begin; | |
844 | *ret_attr = attr; | |
845 | *ret_op = op; | |
846 | *ret_value = value; | |
847 | return 1; | |
848 | } | |
7e760b79 | 849 | |
25de7aa7 YW |
850 | static void sort_tokens(UdevRuleLine *rule_line) { |
851 | UdevRuleToken *head_old; | |
7e760b79 | 852 | |
25de7aa7 | 853 | assert(rule_line); |
912541b0 | 854 | |
25de7aa7 YW |
855 | head_old = TAKE_PTR(rule_line->tokens); |
856 | rule_line->current_token = NULL; | |
95776dc6 | 857 | |
25de7aa7 YW |
858 | while (!LIST_IS_EMPTY(head_old)) { |
859 | UdevRuleToken *t, *min_token = NULL; | |
860 | ||
861 | LIST_FOREACH(tokens, t, head_old) | |
862 | if (!min_token || min_token->type > t->type) | |
863 | min_token = t; | |
864 | ||
865 | LIST_REMOVE(tokens, head_old, min_token); | |
866 | rule_line_append_token(rule_line, min_token); | |
912541b0 | 867 | } |
6880b25d | 868 | } |
95776dc6 | 869 | |
25de7aa7 YW |
870 | static int rule_add_line(UdevRules *rules, const char *line_str, unsigned line_nr) { |
871 | _cleanup_(udev_rule_line_freep) UdevRuleLine *rule_line = NULL; | |
872 | _cleanup_free_ char *line = NULL; | |
873 | UdevRuleFile *rule_file; | |
874 | char *p; | |
875 | int r; | |
912541b0 | 876 | |
25de7aa7 YW |
877 | assert(rules); |
878 | assert(rules->current_file); | |
879 | assert(line_str); | |
6e2efb6c | 880 | |
25de7aa7 | 881 | rule_file = rules->current_file; |
912541b0 | 882 | |
25de7aa7 YW |
883 | if (isempty(line_str)) |
884 | return 0; | |
912541b0 | 885 | |
25de7aa7 YW |
886 | line = strdup(line_str); |
887 | if (!line) | |
888 | return log_oom(); | |
67e4b385 | 889 | |
25de7aa7 YW |
890 | rule_line = new(UdevRuleLine, 1); |
891 | if (!rule_line) | |
892 | return log_oom(); | |
912541b0 | 893 | |
25de7aa7 YW |
894 | *rule_line = (UdevRuleLine) { |
895 | .line = TAKE_PTR(line), | |
896 | .line_number = line_nr, | |
897 | .rule_file = rule_file, | |
898 | }; | |
912541b0 | 899 | |
25de7aa7 YW |
900 | if (rule_file->current_line) |
901 | LIST_APPEND(rule_lines, rule_file->current_line, rule_line); | |
902 | else | |
903 | LIST_APPEND(rule_lines, rule_file->rule_lines, rule_line); | |
e57e7bc1 | 904 | |
25de7aa7 | 905 | rule_file->current_line = rule_line; |
912541b0 | 906 | |
25de7aa7 YW |
907 | for (p = rule_line->line; !isempty(p); ) { |
908 | char *key, *attr, *value; | |
909 | UdevRuleOperatorType op; | |
912541b0 | 910 | |
25de7aa7 YW |
911 | r = parse_line(&p, &key, &attr, &op, &value); |
912 | if (r < 0) | |
913 | return log_token_error_errno(rules, r, "Invalid key/value pair, ignoring."); | |
914 | if (r == 0) | |
915 | break; | |
912541b0 | 916 | |
25de7aa7 | 917 | r = parse_token(rules, key, attr, op, value); |
ef660d07 YW |
918 | if (r < 0) |
919 | return r; | |
25de7aa7 | 920 | } |
912541b0 | 921 | |
25de7aa7 YW |
922 | if (rule_line->type == 0) { |
923 | log_token_warning(rules, "The line takes no effect, ignoring."); | |
924 | return 0; | |
912541b0 | 925 | } |
25de7aa7 YW |
926 | |
927 | sort_tokens(rule_line); | |
928 | TAKE_PTR(rule_line); | |
912541b0 | 929 | return 0; |
724257d9 GKH |
930 | } |
931 | ||
25de7aa7 YW |
932 | static void rule_resolve_goto(UdevRuleFile *rule_file) { |
933 | UdevRuleLine *line, *line_next, *i; | |
912541b0 | 934 | |
25de7aa7 | 935 | assert(rule_file); |
912541b0 | 936 | |
25de7aa7 YW |
937 | /* link GOTOs to LABEL rules in this file to be able to fast-forward */ |
938 | LIST_FOREACH_SAFE(rule_lines, line, line_next, rule_file->rule_lines) { | |
939 | if (!FLAGS_SET(line->type, LINE_HAS_GOTO)) | |
940 | continue; | |
912541b0 | 941 | |
25de7aa7 YW |
942 | LIST_FOREACH_AFTER(rule_lines, i, line) |
943 | if (streq_ptr(i->label, line->goto_label)) { | |
944 | line->goto_line = i; | |
945 | break; | |
912541b0 KS |
946 | } |
947 | ||
25de7aa7 YW |
948 | if (!line->goto_line) { |
949 | log_error("%s:%u: GOTO=\"%s\" has no matching label, ignoring", | |
950 | rule_file->filename, line->line_number, line->goto_label); | |
912541b0 | 951 | |
25de7aa7 YW |
952 | SET_FLAG(line->type, LINE_HAS_GOTO, false); |
953 | line->goto_label = NULL; | |
912541b0 | 954 | |
25de7aa7 YW |
955 | if ((line->type & ~LINE_HAS_LABEL) == 0) { |
956 | log_notice("%s:%u: The line takes no effect any more, dropping", | |
957 | rule_file->filename, line->line_number); | |
958 | if (line->type == LINE_HAS_LABEL) | |
959 | udev_rule_line_clear_tokens(line); | |
960 | else | |
961 | udev_rule_line_free(line); | |
912541b0 | 962 | } |
25de7aa7 | 963 | } |
912541b0 | 964 | } |
c7521974 KS |
965 | } |
966 | ||
9a07157d | 967 | static int parse_file(UdevRules *rules, const char *filename) { |
25de7aa7 | 968 | _cleanup_free_ char *continuation = NULL, *name = NULL; |
6c8aaf0c | 969 | _cleanup_fclose_ FILE *f = NULL; |
25de7aa7 | 970 | UdevRuleFile *rule_file; |
f10aa08e | 971 | bool ignore_line = false; |
25de7aa7 YW |
972 | unsigned line_nr = 0; |
973 | int r; | |
912541b0 | 974 | |
ed88bcfb ZJS |
975 | f = fopen(filename, "re"); |
976 | if (!f) { | |
977 | if (errno == ENOENT) | |
978 | return 0; | |
fae0f8a0 LP |
979 | |
980 | return -errno; | |
775f8b3c | 981 | } |
912541b0 | 982 | |
ed88bcfb ZJS |
983 | if (null_or_empty_fd(fileno(f))) { |
984 | log_debug("Skipping empty file: %s", filename); | |
985 | return 0; | |
25de7aa7 | 986 | } |
912541b0 | 987 | |
25de7aa7 YW |
988 | log_debug("Reading rules file: %s", filename); |
989 | ||
990 | name = strdup(filename); | |
991 | if (!name) | |
992 | return log_oom(); | |
993 | ||
994 | rule_file = new(UdevRuleFile, 1); | |
995 | if (!rule_file) | |
996 | return log_oom(); | |
997 | ||
998 | *rule_file = (UdevRuleFile) { | |
999 | .filename = TAKE_PTR(name), | |
1000 | }; | |
1001 | ||
1002 | if (rules->current_file) | |
1003 | LIST_APPEND(rule_files, rules->current_file, rule_file); | |
1004 | else | |
1005 | LIST_APPEND(rule_files, rules->rule_files, rule_file); | |
1006 | ||
1007 | rules->current_file = rule_file; | |
912541b0 | 1008 | |
f10aa08e YW |
1009 | for (;;) { |
1010 | _cleanup_free_ char *buf = NULL; | |
912541b0 | 1011 | size_t len; |
f10aa08e YW |
1012 | char *line; |
1013 | ||
1014 | r = read_line(f, UTIL_LINE_SIZE, &buf); | |
1015 | if (r < 0) | |
1016 | return r; | |
1017 | if (r == 0) | |
1018 | break; | |
912541b0 | 1019 | |
912541b0 | 1020 | line_nr++; |
25de7aa7 | 1021 | line = skip_leading_chars(buf, NULL); |
912541b0 | 1022 | |
f10aa08e | 1023 | if (line[0] == '#') |
912541b0 KS |
1024 | continue; |
1025 | ||
1026 | len = strlen(line); | |
912541b0 | 1027 | |
f10aa08e YW |
1028 | if (continuation && !ignore_line) { |
1029 | if (strlen(continuation) + len >= UTIL_LINE_SIZE) | |
1030 | ignore_line = true; | |
1031 | ||
1032 | if (!strextend(&continuation, line, NULL)) | |
1033 | return log_oom(); | |
1034 | ||
1035 | if (!ignore_line) { | |
1036 | line = continuation; | |
1037 | len = strlen(line); | |
1038 | } | |
912541b0 KS |
1039 | } |
1040 | ||
e8b2737f | 1041 | if (len > 0 && line[len - 1] == '\\') { |
f10aa08e YW |
1042 | if (ignore_line) |
1043 | continue; | |
1044 | ||
1045 | line[len - 1] = '\0'; | |
1046 | if (!continuation) { | |
1047 | continuation = strdup(line); | |
1048 | if (!continuation) | |
1049 | return log_oom(); | |
1050 | } | |
1051 | ||
912541b0 KS |
1052 | continue; |
1053 | } | |
f10aa08e YW |
1054 | |
1055 | if (ignore_line) | |
25de7aa7 | 1056 | log_error("%s:%u: Line is too long, ignored", filename, line_nr); |
e8b2737f | 1057 | else if (len > 0) |
25de7aa7 | 1058 | (void) rule_add_line(rules, line, line_nr); |
f10aa08e YW |
1059 | |
1060 | continuation = mfree(continuation); | |
1061 | ignore_line = false; | |
912541b0 | 1062 | } |
912541b0 | 1063 | |
25de7aa7 | 1064 | rule_resolve_goto(rule_file); |
912541b0 | 1065 | return 0; |
c7521974 KS |
1066 | } |
1067 | ||
9a07157d ZJS |
1068 | int udev_rules_new(UdevRules **ret_rules, ResolveNameTiming resolve_name_timing) { |
1069 | _cleanup_(udev_rules_freep) UdevRules *rules = NULL; | |
1d791281 ZJS |
1070 | _cleanup_strv_free_ char **files = NULL; |
1071 | char **f; | |
775f8b3c | 1072 | int r; |
912541b0 | 1073 | |
c4d44cba YW |
1074 | assert(resolve_name_timing >= 0 && resolve_name_timing < _RESOLVE_NAME_TIMING_MAX); |
1075 | ||
9a07157d | 1076 | rules = new(UdevRules, 1); |
1017d66b | 1077 | if (!rules) |
1d791281 | 1078 | return -ENOMEM; |
1017d66b | 1079 | |
9a07157d | 1080 | *rules = (UdevRules) { |
c4d44cba | 1081 | .resolve_name_timing = resolve_name_timing, |
1017d66b | 1082 | }; |
912541b0 | 1083 | |
25de7aa7 | 1084 | (void) udev_rules_check_timestamp(rules); |
3b8c1cb0 | 1085 | |
116b91e8 | 1086 | r = conf_files_list_strv(&files, ".rules", NULL, 0, RULES_DIRS); |
1d791281 ZJS |
1087 | if (r < 0) |
1088 | return log_error_errno(r, "Failed to enumerate rules files: %m"); | |
912541b0 | 1089 | |
775f8b3c | 1090 | STRV_FOREACH(f, files) |
25de7aa7 | 1091 | (void) parse_file(rules, *f); |
912541b0 | 1092 | |
1d791281 ZJS |
1093 | *ret_rules = TAKE_PTR(rules); |
1094 | return 0; | |
c7521974 KS |
1095 | } |
1096 | ||
9a07157d | 1097 | bool udev_rules_check_timestamp(UdevRules *rules) { |
5c11fbe3 KS |
1098 | if (!rules) |
1099 | return false; | |
1100 | ||
116b91e8 | 1101 | return paths_check_timestamp(RULES_DIRS, &rules->dirs_ts_usec, true); |
6ada823a KS |
1102 | } |
1103 | ||
25de7aa7 YW |
1104 | static bool token_match_string(UdevRuleToken *token, const char *str) { |
1105 | const char *i, *value; | |
912541b0 KS |
1106 | bool match = false; |
1107 | ||
25de7aa7 YW |
1108 | assert(token); |
1109 | assert(token->value); | |
1110 | assert(token->type < _TK_M_MAX); | |
912541b0 | 1111 | |
25de7aa7 YW |
1112 | str = strempty(str); |
1113 | value = token->value; | |
1114 | ||
1115 | switch (token->match_type) { | |
1116 | case MATCH_TYPE_EMPTY: | |
1117 | match = isempty(str); | |
912541b0 | 1118 | break; |
25de7aa7 YW |
1119 | case MATCH_TYPE_SUBSYSTEM: |
1120 | value = "subsystem\0class\0bus\0"; | |
1121 | _fallthrough_; | |
1122 | case MATCH_TYPE_PLAIN: | |
1123 | NULSTR_FOREACH(i, value) | |
1124 | if (streq(i, str)) { | |
1125 | match = true; | |
1126 | break; | |
912541b0 | 1127 | } |
25de7aa7 YW |
1128 | break; |
1129 | case MATCH_TYPE_GLOB: | |
1130 | NULSTR_FOREACH(i, value) | |
1131 | if ((fnmatch(i, str, 0) == 0)) { | |
1132 | match = true; | |
1133 | break; | |
912541b0 | 1134 | } |
912541b0 | 1135 | break; |
25de7aa7 YW |
1136 | default: |
1137 | assert_not_reached("Invalid match type"); | |
912541b0 KS |
1138 | } |
1139 | ||
25de7aa7 | 1140 | return token->op == (match ? OP_MATCH : OP_NOMATCH); |
6880b25d KS |
1141 | } |
1142 | ||
25de7aa7 | 1143 | static bool token_match_attr(UdevRuleToken *token, sd_device *dev, UdevEvent *event) { |
5ba7e798 YW |
1144 | char nbuf[UTIL_NAME_SIZE], vbuf[UTIL_NAME_SIZE]; |
1145 | const char *name, *value; | |
912541b0 | 1146 | |
25de7aa7 YW |
1147 | assert(token); |
1148 | assert(dev); | |
1149 | assert(event); | |
1150 | ||
1151 | name = (const char*) token->data; | |
1152 | ||
1153 | switch (token->attr_subst_type) { | |
1154 | case SUBST_TYPE_FORMAT: | |
1155 | (void) udev_event_apply_format(event, name, nbuf, sizeof(nbuf), false); | |
912541b0 | 1156 | name = nbuf; |
4831981d | 1157 | _fallthrough_; |
25de7aa7 | 1158 | case SUBST_TYPE_PLAIN: |
5ba7e798 | 1159 | if (sd_device_get_sysattr_value(dev, name, &value) < 0) |
605aa52f | 1160 | return false; |
912541b0 | 1161 | break; |
25de7aa7 | 1162 | case SUBST_TYPE_SUBSYS: |
76b9bdd9 | 1163 | if (util_resolve_subsys_kernel(name, vbuf, sizeof(vbuf), true) < 0) |
605aa52f | 1164 | return false; |
912541b0 KS |
1165 | value = vbuf; |
1166 | break; | |
1167 | default: | |
25de7aa7 | 1168 | assert_not_reached("Invalid attribute substitution type"); |
912541b0 KS |
1169 | } |
1170 | ||
1171 | /* remove trailing whitespace, if not asked to match for it */ | |
25de7aa7 YW |
1172 | if (token->attr_match_remove_trailing_whitespace) { |
1173 | if (value != vbuf) { | |
1174 | strscpy(vbuf, sizeof(vbuf), value); | |
1175 | value = vbuf; | |
912541b0 | 1176 | } |
25de7aa7 YW |
1177 | |
1178 | delete_trailing_chars(vbuf, NULL); | |
912541b0 KS |
1179 | } |
1180 | ||
25de7aa7 | 1181 | return token_match_string(token, value); |
6880b25d KS |
1182 | } |
1183 | ||
25de7aa7 YW |
1184 | static int get_property_from_string(char *line, char **ret_key, char **ret_value) { |
1185 | char *key, *val; | |
1186 | size_t len; | |
6880b25d | 1187 | |
25de7aa7 YW |
1188 | assert(line); |
1189 | assert(ret_key); | |
1190 | assert(ret_value); | |
1191 | ||
1192 | /* find key */ | |
1193 | key = skip_leading_chars(line, NULL); | |
912541b0 | 1194 | |
25de7aa7 YW |
1195 | /* comment or empty line */ |
1196 | if (IN_SET(key[0], '#', '\0')) | |
d838e145 | 1197 | return 0; |
912541b0 | 1198 | |
25de7aa7 YW |
1199 | /* split key/value */ |
1200 | val = strchr(key, '='); | |
1201 | if (!val) | |
1202 | return -EINVAL; | |
1203 | *val++ = '\0'; | |
cf697ec0 | 1204 | |
25de7aa7 YW |
1205 | key = strstrip(key); |
1206 | if (isempty(key)) | |
1207 | return -EINVAL; | |
912541b0 | 1208 | |
25de7aa7 YW |
1209 | val = strstrip(val); |
1210 | if (isempty(val)) | |
1211 | return -EINVAL; | |
912541b0 | 1212 | |
25de7aa7 YW |
1213 | /* unquote */ |
1214 | if (IN_SET(val[0], '"', '\'')) { | |
1215 | len = strlen(val); | |
1216 | if (len == 1 || val[len-1] != val[0]) | |
1217 | return -EINVAL; | |
1218 | val[len-1] = '\0'; | |
1219 | val++; | |
1220 | } | |
adeba500 | 1221 | |
25de7aa7 YW |
1222 | *ret_key = key; |
1223 | *ret_value = val; | |
1224 | return 0; | |
1225 | } | |
f4cf2e5b | 1226 | |
25de7aa7 YW |
1227 | static int import_parent_into_properties(sd_device *dev, const char *filter) { |
1228 | const char *key, *val; | |
1229 | sd_device *parent; | |
1230 | int r; | |
f4cf2e5b | 1231 | |
25de7aa7 YW |
1232 | assert(dev); |
1233 | assert(filter); | |
912541b0 | 1234 | |
25de7aa7 YW |
1235 | r = sd_device_get_parent(dev, &parent); |
1236 | if (r == -ENOENT) | |
1237 | return 0; | |
1238 | if (r < 0) | |
1239 | return r; | |
1240 | ||
1241 | FOREACH_DEVICE_PROPERTY(parent, key, val) { | |
1242 | if (fnmatch(filter, key, 0) != 0) | |
912541b0 | 1243 | continue; |
25de7aa7 YW |
1244 | r = device_add_property(dev, key, val); |
1245 | if (r < 0) | |
1246 | return r; | |
1247 | } | |
912541b0 | 1248 | |
25de7aa7 YW |
1249 | return 1; |
1250 | } | |
912541b0 | 1251 | |
25de7aa7 YW |
1252 | static int attr_subst_subdir(char attr[static UTIL_PATH_SIZE]) { |
1253 | _cleanup_closedir_ DIR *dir = NULL; | |
1254 | struct dirent *dent; | |
1255 | char buf[UTIL_PATH_SIZE], *p; | |
1256 | const char *tail; | |
1257 | size_t len, size; | |
912541b0 | 1258 | |
25de7aa7 YW |
1259 | tail = strstr(attr, "/*/"); |
1260 | if (!tail) | |
1261 | return 0; | |
912541b0 | 1262 | |
25de7aa7 YW |
1263 | len = tail - attr + 1; /* include slash at the end */ |
1264 | tail += 2; /* include slash at the beginning */ | |
88b013b2 | 1265 | |
25de7aa7 YW |
1266 | p = buf; |
1267 | size = sizeof(buf); | |
1268 | size -= strnpcpy(&p, size, attr, len); | |
88b013b2 | 1269 | |
25de7aa7 YW |
1270 | dir = opendir(buf); |
1271 | if (!dir) | |
1272 | return -errno; | |
912541b0 | 1273 | |
25de7aa7 YW |
1274 | FOREACH_DIRENT_ALL(dent, dir, break) { |
1275 | if (dent->d_name[0] == '.') | |
1276 | continue; | |
1277 | ||
1278 | strscpyl(p, size, dent->d_name, tail, NULL); | |
1279 | if (faccessat(dirfd(dir), p, F_OK, 0) < 0) | |
1280 | continue; | |
1281 | ||
1282 | strcpy(attr, buf); | |
1283 | return 0; | |
1284 | } | |
1285 | ||
1286 | return -ENOENT; | |
1287 | } | |
1288 | ||
1289 | static int udev_rule_apply_token_to_event( | |
1290 | UdevRules *rules, | |
1291 | sd_device *dev, | |
1292 | UdevEvent *event, | |
1293 | usec_t timeout_usec, | |
1294 | Hashmap *properties_list) { | |
1295 | ||
1296 | UdevRuleToken *token; | |
1297 | char buf[UTIL_PATH_SIZE]; | |
1298 | const char *val; | |
1299 | size_t count; | |
1300 | bool match; | |
1301 | int r; | |
1302 | ||
1303 | assert(rules); | |
1304 | assert(dev); | |
1305 | assert(event); | |
1306 | ||
1307 | /* This returns the following values: | |
1308 | * 0 on the current token does not match the event, | |
1309 | * 1 on the current token matches the event, and | |
1310 | * negative errno on some critical errors. */ | |
1311 | ||
1312 | token = rules->current_file->current_line->current_token; | |
1313 | ||
1314 | switch (token->type) { | |
1315 | case TK_M_ACTION: { | |
1316 | DeviceAction a; | |
1317 | ||
1318 | r = device_get_action(dev, &a); | |
1319 | if (r < 0) | |
1320 | return log_rule_error_errno(dev, rules, r, "Failed to get uevent action type: %m"); | |
1321 | ||
1322 | return token_match_string(token, device_action_to_string(a)); | |
1323 | } | |
1324 | case TK_M_DEVPATH: | |
1325 | r = sd_device_get_devpath(dev, &val); | |
1326 | if (r < 0) | |
1327 | return log_rule_error_errno(dev, rules, r, "Failed to get devpath: %m"); | |
1328 | ||
1329 | return token_match_string(token, val); | |
1330 | case TK_M_KERNEL: | |
1331 | case TK_M_PARENTS_KERNEL: | |
1332 | r = sd_device_get_sysname(dev, &val); | |
1333 | if (r < 0) | |
1334 | return log_rule_error_errno(dev, rules, r, "Failed to get sysname: %m"); | |
1335 | ||
1336 | return token_match_string(token, val); | |
1337 | case TK_M_DEVLINK: | |
1338 | FOREACH_DEVICE_DEVLINK(dev, val) | |
1339 | if (token_match_string(token, strempty(startswith(val, "/dev/")))) | |
1340 | return token->op == OP_MATCH; | |
1341 | return token->op == OP_NOMATCH; | |
1342 | case TK_M_NAME: | |
1343 | return token_match_string(token, event->name); | |
1344 | case TK_M_ENV: | |
1345 | if (sd_device_get_property_value(dev, (const char*) token->data, &val) < 0) | |
1346 | val = hashmap_get(properties_list, token->data); | |
1347 | ||
1348 | return token_match_string(token, val); | |
1349 | case TK_M_TAG: | |
1350 | case TK_M_PARENTS_TAG: | |
1351 | FOREACH_DEVICE_TAG(dev, val) | |
1352 | if (token_match_string(token, val)) | |
1353 | return token->op == OP_MATCH; | |
1354 | return token->op == OP_NOMATCH; | |
1355 | case TK_M_SUBSYSTEM: | |
1356 | case TK_M_PARENTS_SUBSYSTEM: | |
1357 | r = sd_device_get_subsystem(dev, &val); | |
1358 | if (r == -ENOENT) | |
1359 | val = NULL; | |
1360 | else if (r < 0) | |
1361 | return log_rule_error_errno(dev, rules, r, "Failed to get subsystem: %m"); | |
1362 | ||
1363 | return token_match_string(token, val); | |
1364 | case TK_M_DRIVER: | |
1365 | case TK_M_PARENTS_DRIVER: | |
1366 | r = sd_device_get_driver(dev, &val); | |
1367 | if (r == -ENOENT) | |
1368 | val = NULL; | |
1369 | else if (r < 0) | |
1370 | return log_rule_error_errno(dev, rules, r, "Failed to get driver: %m"); | |
1371 | ||
1372 | return token_match_string(token, val); | |
1373 | case TK_M_ATTR: | |
1374 | case TK_M_PARENTS_ATTR: | |
1375 | return token_match_attr(token, dev, event); | |
1376 | case TK_M_SYSCTL: { | |
1377 | _cleanup_free_ char *value = NULL; | |
1378 | ||
1379 | (void) udev_event_apply_format(event, (const char*) token->data, buf, sizeof(buf), false); | |
1380 | r = sysctl_read(sysctl_normalize(buf), &value); | |
1381 | if (r < 0 && r != -ENOENT) | |
1382 | return log_rule_error_errno(dev, rules, r, "Failed to read sysctl '%s': %m", buf); | |
1383 | ||
1384 | return token_match_string(token, strstrip(value)); | |
1385 | } | |
1386 | case TK_M_TEST: { | |
1387 | mode_t mode = PTR_TO_MODE(token->data); | |
1388 | struct stat statbuf; | |
1389 | ||
1390 | (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false); | |
1391 | if (!path_is_absolute(buf) && | |
1392 | util_resolve_subsys_kernel(buf, buf, sizeof(buf), false) < 0) { | |
1393 | char tmp[UTIL_PATH_SIZE]; | |
1394 | ||
1395 | r = sd_device_get_syspath(dev, &val); | |
1396 | if (r < 0) | |
1397 | return log_rule_error_errno(dev, rules, r, "Failed to get syspath: %m"); | |
1398 | ||
1399 | strscpy(tmp, sizeof(tmp), buf); | |
1400 | strscpyl(buf, sizeof(buf), val, "/", tmp, NULL); | |
1401 | } | |
1402 | ||
1403 | r = attr_subst_subdir(buf); | |
1404 | if (r == -ENOENT) | |
1405 | return token->op == OP_NOMATCH; | |
1406 | if (r < 0) | |
1407 | return log_rule_error_errno(dev, rules, r, "Failed to test the existence of '%s': %m", buf); | |
1408 | ||
1409 | if (stat(buf, &statbuf) < 0) | |
1410 | return token->op == OP_NOMATCH; | |
1411 | ||
1412 | if (mode == MODE_INVALID) | |
1413 | return token->op == OP_MATCH; | |
1414 | ||
1415 | match = (((statbuf.st_mode ^ mode) & 07777) == 0); | |
1416 | return token->op == (match ? OP_MATCH : OP_NOMATCH); | |
1417 | } | |
1418 | case TK_M_PROGRAM: { | |
1419 | char result[UTIL_LINE_SIZE]; | |
1420 | ||
1421 | event->program_result = mfree(event->program_result); | |
1422 | (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false); | |
1423 | log_rule_debug(dev, rules, "Running PROGRAM '%s'", buf); | |
1424 | ||
1425 | r = udev_event_spawn(event, timeout_usec, true, buf, result, sizeof(result)); | |
1426 | if (r < 0) | |
1427 | return log_rule_error_errno(dev, rules, r, "Failed to execute '%s': %m", buf); | |
1428 | if (r > 0) | |
1429 | return token->op == OP_NOMATCH; | |
1430 | ||
1431 | delete_trailing_chars(result, "\n"); | |
1432 | count = util_replace_chars(result, UDEV_ALLOWED_CHARS_INPUT); | |
1433 | if (count > 0) | |
1434 | log_rule_debug(dev, rules, "Replaced %zu character(s) from result of '%s'", | |
1435 | count, buf); | |
1436 | ||
1437 | event->program_result = strdup(result); | |
1438 | return token->op == OP_MATCH; | |
1439 | } | |
1440 | case TK_M_IMPORT_FILE: { | |
1441 | _cleanup_fclose_ FILE *f = NULL; | |
1442 | ||
1443 | (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false); | |
1444 | log_rule_debug(dev, rules, "Importing properties from '%s'", buf); | |
1445 | ||
1446 | f = fopen(buf, "re"); | |
1447 | if (!f) { | |
1448 | if (errno != ENOENT) | |
1449 | return log_rule_error_errno(dev, rules, errno, | |
1450 | "Failed to open '%s': %m", buf); | |
1451 | return token->op == OP_NOMATCH; | |
1452 | } | |
912541b0 | 1453 | |
25de7aa7 YW |
1454 | for (;;) { |
1455 | _cleanup_free_ char *line = NULL; | |
1456 | char *key, *value; | |
1457 | ||
1458 | r = read_line(f, LONG_LINE_MAX, &line); | |
1459 | if (r < 0) { | |
1460 | log_rule_debug_errno(dev, rules, r, | |
1461 | "Failed to read '%s', ignoring: %m", buf); | |
1462 | return token->op == OP_NOMATCH; | |
1463 | } | |
1464 | if (r == 0) | |
912541b0 | 1465 | break; |
25de7aa7 YW |
1466 | |
1467 | r = get_property_from_string(line, &key, &value); | |
23bf8dd7 | 1468 | if (r < 0) { |
25de7aa7 YW |
1469 | log_rule_debug_errno(dev, rules, r, |
1470 | "Failed to parse key and value from '%s', ignoring: %m", | |
1471 | line); | |
1472 | continue; | |
23bf8dd7 | 1473 | } |
25de7aa7 YW |
1474 | |
1475 | r = device_add_property(dev, key, value); | |
1476 | if (r < 0) | |
1477 | return log_rule_error_errno(dev, rules, r, | |
1478 | "Failed to add property %s=%s: %m", | |
1479 | key, value); | |
912541b0 | 1480 | } |
912541b0 | 1481 | |
25de7aa7 YW |
1482 | return token->op == OP_MATCH; |
1483 | } | |
1484 | case TK_M_IMPORT_PROGRAM: { | |
1485 | char result[UTIL_LINE_SIZE], *line, *pos; | |
1486 | ||
1487 | (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false); | |
1488 | log_rule_debug(dev, rules, "Importing properties from results of '%s'", buf); | |
1489 | ||
1490 | r = udev_event_spawn(event, timeout_usec, true, buf, result, sizeof result); | |
1491 | if (r < 0) | |
1492 | return log_rule_error_errno(dev, rules, r, "Failed to execute '%s': %m", buf); | |
1493 | if (r > 0) { | |
1494 | log_rule_debug(dev, rules, "Command \"%s\" returned %d (error), ignoring", buf, r); | |
1495 | return token->op == OP_NOMATCH; | |
1496 | } | |
1497 | ||
1498 | for (line = result; !isempty(line); line = pos) { | |
1499 | char *key, *value; | |
1500 | ||
1501 | pos = strchr(line, '\n'); | |
1502 | if (pos) | |
1503 | *pos++ = '\0'; | |
1504 | ||
1505 | r = get_property_from_string(line, &key, &value); | |
23bf8dd7 | 1506 | if (r < 0) { |
25de7aa7 YW |
1507 | log_rule_debug_errno(dev, rules, r, |
1508 | "Failed to parse key and value from '%s', ignoring: %m", | |
1509 | line); | |
1510 | continue; | |
23bf8dd7 | 1511 | } |
25de7aa7 YW |
1512 | |
1513 | r = device_add_property(dev, key, value); | |
1514 | if (r < 0) | |
1515 | return log_rule_error_errno(dev, rules, r, | |
1516 | "Failed to add property %s=%s: %m", | |
1517 | key, value); | |
912541b0 | 1518 | } |
912541b0 | 1519 | |
25de7aa7 YW |
1520 | return token->op == OP_MATCH; |
1521 | } | |
1522 | case TK_M_IMPORT_BUILTIN: { | |
1523 | UdevBuiltinCommand cmd = PTR_TO_UDEV_BUILTIN_CMD(token->data); | |
1524 | unsigned mask = 1U << (int) cmd; | |
1525 | ||
1526 | if (udev_builtin_run_once(cmd)) { | |
1527 | /* check if we ran already */ | |
1528 | if (event->builtin_run & mask) { | |
1529 | log_rule_debug(dev, rules, "Skipping builtin '%s' in IMPORT key", | |
1530 | udev_builtin_name(cmd)); | |
1531 | /* return the result from earlier run */ | |
1532 | return token->op == (event->builtin_ret & mask ? OP_NOMATCH : OP_MATCH); | |
912541b0 | 1533 | } |
25de7aa7 YW |
1534 | /* mark as ran */ |
1535 | event->builtin_run |= mask; | |
912541b0 | 1536 | } |
25de7aa7 YW |
1537 | |
1538 | (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false); | |
1539 | log_rule_debug(dev, rules, "Importing properties from results of builtin command '%s'", buf); | |
1540 | ||
1541 | r = udev_builtin_run(dev, cmd, buf, false); | |
1542 | if (r < 0) { | |
1543 | /* remember failure */ | |
1544 | log_rule_debug_errno(dev, rules, r, "Failed to run builtin '%s': %m", buf); | |
1545 | event->builtin_ret |= mask; | |
1546 | } | |
1547 | return token->op == (r >= 0 ? OP_MATCH : OP_NOMATCH); | |
1548 | } | |
1549 | case TK_M_IMPORT_DB: { | |
1550 | if (!event->dev_db_clone) | |
1551 | return token->op == OP_NOMATCH; | |
1552 | r = sd_device_get_property_value(event->dev_db_clone, token->value, &val); | |
1553 | if (r == -ENOENT) | |
1554 | return token->op == OP_NOMATCH; | |
1555 | if (r < 0) | |
1556 | return log_rule_error_errno(dev, rules, r, | |
1557 | "Failed to get property '%s' from database: %m", | |
1558 | token->value); | |
1559 | ||
1560 | r = device_add_property(dev, token->value, val); | |
1561 | if (r < 0) | |
1562 | return log_rule_error_errno(dev, rules, r, "Failed to add property '%s=%s': %m", | |
1563 | token->value, val); | |
1564 | return token->op == OP_MATCH; | |
1565 | } | |
1566 | case TK_M_IMPORT_CMDLINE: { | |
1567 | _cleanup_free_ char *value = NULL; | |
1568 | ||
1569 | r = proc_cmdline_get_key(token->value, PROC_CMDLINE_VALUE_OPTIONAL, &value); | |
1570 | if (r < 0) | |
1571 | return log_rule_error_errno(dev, rules, r, | |
1572 | "Failed to read '%s' option from /proc/cmdline: %m", | |
1573 | token->value); | |
1574 | if (r == 0) | |
1575 | return token->op == OP_NOMATCH; | |
1576 | ||
1577 | r = device_add_property(dev, token->value, value ?: "1"); | |
1578 | if (r < 0) | |
1579 | return log_rule_error_errno(dev, rules, r, "Failed to add property '%s=%s': %m", | |
1580 | token->value, value ?: "1"); | |
1581 | return token->op == OP_MATCH; | |
1582 | } | |
1583 | case TK_M_IMPORT_PARENT: { | |
1584 | (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false); | |
1585 | r = import_parent_into_properties(dev, buf); | |
1586 | if (r < 0) | |
1587 | return log_rule_error_errno(dev, rules, r, | |
1588 | "Failed to import properties '%s' from parent: %m", | |
1589 | buf); | |
1590 | return token->op == (r > 0 ? OP_MATCH : OP_NOMATCH); | |
1591 | } | |
1592 | case TK_M_RESULT: | |
1593 | return token_match_string(token, event->program_result); | |
1594 | case TK_A_OPTIONS_STRING_ESCAPE_NONE: | |
1595 | event->esc = ESCAPE_NONE; | |
1596 | break; | |
1597 | case TK_A_OPTIONS_STRING_ESCAPE_REPLACE: | |
1598 | event->esc = ESCAPE_REPLACE; | |
1599 | break; | |
1600 | case TK_A_OPTIONS_DB_PERSIST: | |
1601 | device_set_db_persist(dev); | |
1602 | break; | |
1603 | case TK_A_OPTIONS_INOTIFY_WATCH: | |
1604 | if (event->inotify_watch_final) | |
912541b0 | 1605 | break; |
25de7aa7 YW |
1606 | if (token->op == OP_ASSIGN_FINAL) |
1607 | event->inotify_watch_final = true; | |
1608 | ||
1609 | event->inotify_watch = token->data; | |
1610 | break; | |
1611 | case TK_A_OPTIONS_DEVLINK_PRIORITY: | |
1612 | device_set_devlink_priority(dev, PTR_TO_INT(token->data)); | |
1613 | break; | |
1614 | case TK_A_OWNER: { | |
1615 | char owner[UTIL_NAME_SIZE]; | |
1616 | const char *ow = owner; | |
1617 | ||
1618 | if (event->owner_final) | |
912541b0 | 1619 | break; |
25de7aa7 YW |
1620 | if (token->op == OP_ASSIGN_FINAL) |
1621 | event->owner_final = true; | |
1622 | ||
1623 | (void) udev_event_apply_format(event, token->value, owner, sizeof(owner), false); | |
1624 | r = get_user_creds(&ow, &event->uid, NULL, NULL, NULL, USER_CREDS_ALLOW_MISSING); | |
1625 | if (r < 0) | |
1626 | log_unknown_owner(dev, rules, r, "user", owner); | |
1627 | else | |
1628 | log_rule_debug(dev, rules, "OWNER %s(%u)", owner, event->uid); | |
1629 | break; | |
1630 | } | |
1631 | case TK_A_GROUP: { | |
1632 | char group[UTIL_NAME_SIZE]; | |
1633 | const char *gr = group; | |
1634 | ||
1635 | if (event->group_final) | |
912541b0 | 1636 | break; |
25de7aa7 YW |
1637 | if (token->op == OP_ASSIGN_FINAL) |
1638 | event->group_final = true; | |
c26547d6 | 1639 | |
25de7aa7 YW |
1640 | (void) udev_event_apply_format(event, token->value, group, sizeof(group), false); |
1641 | r = get_group_creds(&gr, &event->gid, USER_CREDS_ALLOW_MISSING); | |
1642 | if (r < 0) | |
1643 | log_unknown_owner(dev, rules, r, "group", group); | |
1644 | else | |
1645 | log_rule_debug(dev, rules, "GROUP %s(%u)", group, event->gid); | |
1646 | break; | |
1647 | } | |
1648 | case TK_A_MODE: { | |
1649 | char mode_str[UTIL_NAME_SIZE]; | |
d838e145 | 1650 | |
25de7aa7 YW |
1651 | if (event->mode_final) |
1652 | break; | |
1653 | if (token->op == OP_ASSIGN_FINAL) | |
1654 | event->mode_final = true; | |
4f985bd8 | 1655 | |
25de7aa7 YW |
1656 | (void) udev_event_apply_format(event, token->value, mode_str, sizeof(mode_str), false); |
1657 | r = parse_mode(mode_str, &event->mode); | |
1658 | if (r < 0) | |
1659 | log_rule_error_errno(dev, rules, r, "Failed to parse mode '%s', ignoring: %m", mode_str); | |
1660 | else | |
1661 | log_rule_debug(dev, rules, "MODE %#o", event->mode); | |
1662 | break; | |
1663 | } | |
1664 | case TK_A_OWNER_ID: | |
1665 | if (event->owner_final) | |
1666 | break; | |
1667 | if (token->op == OP_ASSIGN_FINAL) | |
1668 | event->owner_final = true; | |
1669 | if (!token->data) | |
1670 | break; | |
1671 | event->uid = PTR_TO_UID(token->data); | |
1672 | log_rule_debug(dev, rules, "OWNER %u", event->uid); | |
1673 | break; | |
1674 | case TK_A_GROUP_ID: | |
1675 | if (event->group_final) | |
1676 | break; | |
1677 | if (token->op == OP_ASSIGN_FINAL) | |
1678 | event->group_final = true; | |
1679 | if (!token->data) | |
1680 | break; | |
1681 | event->gid = PTR_TO_GID(token->data); | |
1682 | log_rule_debug(dev, rules, "GROUP %u", event->gid); | |
1683 | break; | |
1684 | case TK_A_MODE_ID: | |
1685 | if (event->mode_final) | |
1686 | break; | |
1687 | if (token->op == OP_ASSIGN_FINAL) | |
1688 | event->mode_final = true; | |
1689 | if (!token->data) | |
1690 | break; | |
1691 | event->mode = PTR_TO_MODE(token->data); | |
1692 | log_rule_debug(dev, rules, "MODE %#o", event->mode); | |
1693 | break; | |
1694 | case TK_A_SECLABEL: { | |
1695 | _cleanup_free_ char *name = NULL, *label = NULL; | |
1696 | char label_str[UTIL_LINE_SIZE] = {}; | |
d838e145 | 1697 | |
25de7aa7 YW |
1698 | name = strdup((const char*) token->data); |
1699 | if (!name) | |
1700 | return log_oom(); | |
d838e145 | 1701 | |
25de7aa7 YW |
1702 | (void) udev_event_apply_format(event, token->value, label_str, sizeof(label_str), false); |
1703 | if (!isempty(label_str)) | |
1704 | label = strdup(label_str); | |
1705 | else | |
1706 | label = strdup(token->value); | |
1707 | if (!label) | |
1708 | return log_oom(); | |
a6ca3c19 | 1709 | |
25de7aa7 YW |
1710 | if (token->op == OP_ASSIGN) |
1711 | ordered_hashmap_clear_free_free(event->seclabel_list); | |
07845c14 | 1712 | |
25de7aa7 YW |
1713 | r = ordered_hashmap_ensure_allocated(&event->seclabel_list, NULL); |
1714 | if (r < 0) | |
1715 | return log_oom(); | |
07845c14 | 1716 | |
25de7aa7 YW |
1717 | r = ordered_hashmap_put(event->seclabel_list, name, label); |
1718 | if (r < 0) | |
1719 | return log_oom(); | |
1720 | log_rule_debug(dev, rules, "SECLABEL{%s}='%s'", name, label); | |
1721 | name = label = NULL; | |
1722 | break; | |
1723 | } | |
1724 | case TK_A_ENV: { | |
1725 | const char *name = (const char*) token->data; | |
1726 | char value_new[UTIL_NAME_SIZE], *p = value_new; | |
1727 | size_t l = sizeof(value_new); | |
07845c14 | 1728 | |
25de7aa7 YW |
1729 | if (isempty(token->value)) { |
1730 | if (token->op == OP_ADD) | |
912541b0 | 1731 | break; |
25de7aa7 YW |
1732 | r = device_add_property(dev, name, NULL); |
1733 | if (r < 0) | |
1734 | return log_rule_error_errno(dev, rules, r, "Failed to remove property '%s': %m", name); | |
912541b0 KS |
1735 | break; |
1736 | } | |
912541b0 | 1737 | |
25de7aa7 YW |
1738 | if (token->op == OP_ADD && |
1739 | sd_device_get_property_value(dev, name, &val) >= 0) | |
1740 | l = strpcpyl(&p, l, val, " ", NULL); | |
1741 | ||
1742 | (void) udev_event_apply_format(event, token->value, p, l, false); | |
d838e145 | 1743 | |
25de7aa7 YW |
1744 | r = device_add_property(dev, name, value_new); |
1745 | if (r < 0) | |
1746 | return log_rule_error_errno(dev, rules, r, "Failed to add property '%s=%s': %m", name, value_new); | |
1747 | break; | |
1748 | } | |
1749 | case TK_A_TAG: { | |
1750 | (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false); | |
1751 | if (token->op == OP_ASSIGN) | |
1752 | device_cleanup_tags(dev); | |
1753 | ||
1754 | if (buf[strspn(buf, ALPHANUMERICAL "-_")] != '\0') { | |
1755 | log_rule_error(dev, rules, "Invalid tag name '%s', ignoring", buf); | |
912541b0 KS |
1756 | break; |
1757 | } | |
25de7aa7 YW |
1758 | if (token->op == OP_REMOVE) |
1759 | device_remove_tag(dev, buf); | |
1760 | else { | |
1761 | r = device_add_tag(dev, buf); | |
1762 | if (r < 0) | |
1763 | return log_rule_error_errno(dev, rules, r, "Failed to add tag '%s': %m", buf); | |
1764 | } | |
1765 | break; | |
1766 | } | |
1767 | case TK_A_NAME: { | |
1768 | if (event->name_final) | |
1769 | break; | |
1770 | if (token->op == OP_ASSIGN_FINAL) | |
1771 | event->name_final = true; | |
912541b0 | 1772 | |
25de7aa7 YW |
1773 | (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false); |
1774 | if (IN_SET(event->esc, ESCAPE_UNSET, ESCAPE_REPLACE)) { | |
1775 | count = util_replace_chars(buf, "/"); | |
912541b0 | 1776 | if (count > 0) |
25de7aa7 YW |
1777 | log_rule_debug(dev, rules, "Replaced %zu character(s) from result of NAME=\"%s\"", |
1778 | count, token->value); | |
912541b0 | 1779 | } |
25de7aa7 YW |
1780 | if (sd_device_get_devnum(dev, NULL) >= 0 && |
1781 | (sd_device_get_devname(dev, &val) < 0 || | |
1782 | !streq_ptr(buf, startswith(val, "/dev/")))) { | |
1783 | log_rule_error(dev, rules, | |
1784 | "Kernel device nodes cannot be renamed, ignoring NAME=\"%s\"; please fix it.", | |
1785 | token->value); | |
912541b0 KS |
1786 | break; |
1787 | } | |
25de7aa7 YW |
1788 | if (free_and_strdup(&event->name, buf) < 0) |
1789 | return log_oom(); | |
1790 | ||
1791 | log_rule_debug(dev, rules, "NAME '%s'", event->name); | |
1792 | break; | |
1793 | } | |
1794 | case TK_A_DEVLINK: { | |
1795 | char *p; | |
1796 | ||
1797 | if (event->devlink_final) | |
1798 | break; | |
1799 | if (sd_device_get_devnum(dev, NULL) < 0) | |
1800 | break; | |
1801 | if (token->op == OP_ASSIGN_FINAL) | |
1802 | event->devlink_final = true; | |
1803 | if (IN_SET(token->op, OP_ASSIGN, OP_ASSIGN_FINAL)) | |
1804 | device_cleanup_devlinks(dev); | |
1805 | ||
1806 | /* allow multiple symlinks separated by spaces */ | |
1807 | (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), event->esc != ESCAPE_NONE); | |
1808 | if (event->esc == ESCAPE_UNSET) | |
1809 | count = util_replace_chars(buf, "/ "); | |
1810 | else if (event->esc == ESCAPE_REPLACE) | |
1811 | count = util_replace_chars(buf, "/"); | |
1812 | else | |
1813 | count = 0; | |
1814 | if (count > 0) | |
1815 | log_rule_debug(dev, rules, "Replaced %zu character(s) from result of LINK", count); | |
1816 | ||
1817 | p = skip_leading_chars(buf, NULL); | |
1818 | while (!isempty(p)) { | |
1819 | char filename[UTIL_PATH_SIZE], *next; | |
1820 | ||
1821 | next = strchr(p, ' '); | |
1822 | if (next) { | |
1823 | *next++ = '\0'; | |
1824 | next = skip_leading_chars(next, NULL); | |
1825 | } | |
1826 | ||
1827 | strscpyl(filename, sizeof(filename), "/dev/", p, NULL); | |
1828 | r = device_add_devlink(dev, filename); | |
f4cf2e5b | 1829 | if (r < 0) |
25de7aa7 YW |
1830 | return log_rule_error_errno(dev, rules, r, "Failed to add devlink '%s': %m", filename); |
1831 | ||
1832 | log_rule_debug(dev, rules, "LINK '%s'", p); | |
1833 | p = next; | |
1834 | } | |
1835 | break; | |
1836 | } | |
1837 | case TK_A_ATTR: { | |
1838 | const char *key_name = (const char*) token->data; | |
1839 | char value[UTIL_NAME_SIZE]; | |
1840 | ||
1841 | if (util_resolve_subsys_kernel(key_name, buf, sizeof(buf), false) < 0 && | |
1842 | sd_device_get_syspath(dev, &val) >= 0) | |
1843 | strscpyl(buf, sizeof(buf), val, "/", key_name, NULL); | |
1844 | ||
1845 | r = attr_subst_subdir(buf); | |
1846 | if (r < 0) { | |
1847 | log_rule_error_errno(dev, rules, r, "Could not find file matches '%s', ignoring: %m", buf); | |
f4cf2e5b KS |
1848 | break; |
1849 | } | |
25de7aa7 | 1850 | (void) udev_event_apply_format(event, token->value, value, sizeof(value), false); |
6cdc62aa | 1851 | |
25de7aa7 YW |
1852 | log_rule_debug(dev, rules, "ATTR '%s' writing '%s'", buf, value); |
1853 | r = write_string_file(buf, value, WRITE_STRING_FILE_VERIFY_ON_FAILURE | WRITE_STRING_FILE_DISABLE_BUFFER); | |
1854 | if (r < 0) | |
1855 | log_rule_error_errno(dev, rules, r, "Failed to write ATTR{%s}, ignoring: %m", buf); | |
1856 | break; | |
1857 | } | |
1858 | case TK_A_SYSCTL: { | |
1859 | char value[UTIL_NAME_SIZE]; | |
1860 | ||
1861 | (void) udev_event_apply_format(event, (const char*) token->data, buf, sizeof(buf), false); | |
1862 | (void) udev_event_apply_format(event, token->value, value, sizeof(value), false); | |
1863 | sysctl_normalize(buf); | |
1864 | log_rule_debug(dev, rules, "SYSCTL '%s' writing '%s'", buf, value); | |
1865 | r = sysctl_write(buf, value); | |
1866 | if (r < 0) | |
1867 | log_rule_error_errno(dev, rules, r, "Failed to write SYSCTL{%s}='%s', ignoring: %m", buf, value); | |
1868 | break; | |
1869 | } | |
1870 | case TK_A_RUN_BUILTIN: | |
1871 | case TK_A_RUN_PROGRAM: { | |
1872 | _cleanup_free_ char *cmd = NULL; | |
29448498 | 1873 | |
25de7aa7 YW |
1874 | if (event->run_final) |
1875 | break; | |
1876 | if (token->op == OP_ASSIGN_FINAL) | |
1877 | event->run_final = true; | |
29448498 | 1878 | |
25de7aa7 YW |
1879 | if (IN_SET(token->op, OP_ASSIGN, OP_ASSIGN_FINAL)) |
1880 | ordered_hashmap_clear_free_key(event->run_list); | |
29448498 | 1881 | |
25de7aa7 YW |
1882 | r = ordered_hashmap_ensure_allocated(&event->run_list, NULL); |
1883 | if (r < 0) | |
1884 | return log_oom(); | |
29448498 | 1885 | |
1448820a YW |
1886 | (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false); |
1887 | ||
1888 | cmd = strdup(buf); | |
25de7aa7 YW |
1889 | if (!cmd) |
1890 | return log_oom(); | |
29448498 | 1891 | |
25de7aa7 YW |
1892 | r = ordered_hashmap_put(event->run_list, cmd, token->data); |
1893 | if (r < 0) | |
1894 | return log_oom(); | |
83cd6b75 | 1895 | |
25de7aa7 YW |
1896 | TAKE_PTR(cmd); |
1897 | ||
1898 | log_rule_debug(dev, rules, "RUN '%s'", token->value); | |
1899 | break; | |
1900 | } | |
1901 | case TK_A_OPTIONS_STATIC_NODE: | |
1902 | /* do nothing for events. */ | |
1903 | break; | |
1904 | default: | |
1905 | assert_not_reached("Invalid token type"); | |
1906 | } | |
1907 | ||
1908 | return true; | |
1909 | } | |
1910 | ||
1911 | static bool token_is_for_parents(UdevRuleToken *token) { | |
1912 | return token->type >= TK_M_PARENTS_KERNEL && token->type <= TK_M_PARENTS_TAG; | |
1913 | } | |
1914 | ||
1915 | static int udev_rule_apply_parent_token_to_event( | |
1916 | UdevRules *rules, | |
1917 | UdevEvent *event) { | |
1918 | ||
1919 | UdevRuleLine *line; | |
1920 | UdevRuleToken *head; | |
1921 | int r; | |
1922 | ||
1923 | line = rules->current_file->current_line; | |
1924 | head = rules->current_file->current_line->current_token; | |
1925 | event->dev_parent = event->dev; | |
1926 | for (;;) { | |
1927 | LIST_FOREACH(tokens, line->current_token, head) { | |
1928 | if (!token_is_for_parents(line->current_token)) | |
1929 | return true; /* All parent tokens match. */ | |
1930 | r = udev_rule_apply_token_to_event(rules, event->dev_parent, event, 0, NULL); | |
1931 | if (r < 0) | |
1932 | return r; | |
1933 | if (r == 0) | |
912541b0 | 1934 | break; |
25de7aa7 YW |
1935 | } |
1936 | if (!line->current_token) | |
1937 | /* All parent tokens match. But no assign tokens in the line. Hmm... */ | |
1938 | return true; | |
1939 | ||
1940 | if (sd_device_get_parent(event->dev_parent, &event->dev_parent) < 0) { | |
1941 | event->dev_parent = NULL; | |
1942 | return false; | |
1943 | } | |
1944 | } | |
1945 | } | |
1946 | ||
1947 | static int udev_rule_apply_line_to_event( | |
1948 | UdevRules *rules, | |
1949 | UdevEvent *event, | |
1950 | usec_t timeout_usec, | |
1951 | Hashmap *properties_list, | |
1952 | UdevRuleLine **next_line) { | |
1953 | ||
1954 | UdevRuleLine *line = rules->current_file->current_line; | |
1955 | UdevRuleLineType mask = LINE_HAS_GOTO | LINE_UPDATE_SOMETHING; | |
1956 | UdevRuleToken *token, *next_token; | |
1957 | bool parents_done = false; | |
1958 | DeviceAction action; | |
1959 | int r; | |
912541b0 | 1960 | |
25de7aa7 YW |
1961 | r = device_get_action(event->dev, &action); |
1962 | if (r < 0) | |
1963 | return r; | |
1964 | ||
1965 | if (action != DEVICE_ACTION_REMOVE) { | |
1966 | if (sd_device_get_devnum(event->dev, NULL) >= 0) | |
1967 | mask |= LINE_HAS_DEVLINK; | |
1968 | ||
1969 | if (sd_device_get_ifindex(event->dev, NULL) >= 0) | |
1970 | mask |= LINE_HAS_NAME; | |
1971 | } | |
1972 | ||
1973 | if ((line->type & mask) == 0) | |
1974 | return 0; | |
1975 | ||
1976 | event->esc = ESCAPE_UNSET; | |
1977 | LIST_FOREACH_SAFE(tokens, token, next_token, line->tokens) { | |
1978 | line->current_token = token; | |
1979 | ||
1980 | if (token_is_for_parents(token)) { | |
1981 | if (parents_done) | |
1982 | continue; | |
1983 | ||
1984 | r = udev_rule_apply_parent_token_to_event(rules, event); | |
1985 | if (r <= 0) | |
1986 | return r; | |
1987 | ||
1988 | parents_done = true; | |
1989 | continue; | |
912541b0 KS |
1990 | } |
1991 | ||
25de7aa7 YW |
1992 | r = udev_rule_apply_token_to_event(rules, event->dev, event, timeout_usec, properties_list); |
1993 | if (r <= 0) | |
1994 | return r; | |
912541b0 | 1995 | } |
d838e145 | 1996 | |
25de7aa7 YW |
1997 | if (line->goto_line) |
1998 | *next_line = line->goto_line; | |
1999 | ||
d838e145 | 2000 | return 0; |
6880b25d | 2001 | } |
761dfddc | 2002 | |
25de7aa7 YW |
2003 | int udev_rules_apply_to_event( |
2004 | UdevRules *rules, | |
2005 | UdevEvent *event, | |
2006 | usec_t timeout_usec, | |
2007 | Hashmap *properties_list) { | |
2008 | ||
2009 | UdevRuleFile *file; | |
2010 | UdevRuleLine *next_line; | |
2011 | int r; | |
2012 | ||
2013 | assert(rules); | |
2014 | assert(event); | |
2015 | ||
2016 | LIST_FOREACH(rule_files, file, rules->rule_files) { | |
2017 | rules->current_file = file; | |
2018 | LIST_FOREACH_SAFE(rule_lines, file->current_line, next_line, file->rule_lines) { | |
2019 | r = udev_rule_apply_line_to_event(rules, event, timeout_usec, properties_list, &next_line); | |
2020 | if (r < 0) | |
2021 | return r; | |
2022 | } | |
2023 | } | |
2024 | ||
2025 | return 0; | |
2026 | } | |
2027 | ||
2028 | static int apply_static_dev_perms(const char *devnode, uid_t uid, gid_t gid, mode_t mode, char **tags) { | |
2029 | char device_node[UTIL_PATH_SIZE], tags_dir[UTIL_PATH_SIZE], tag_symlink[UTIL_PATH_SIZE]; | |
2030 | _cleanup_free_ char *unescaped_filename = NULL; | |
2031 | struct stat stats; | |
84b6ad70 | 2032 | char **t; |
fdd21be6 | 2033 | int r; |
912541b0 | 2034 | |
25de7aa7 YW |
2035 | assert(devnode); |
2036 | ||
2037 | if (uid == UID_INVALID && gid == GID_INVALID && mode == MODE_INVALID && !tags) | |
84b6ad70 | 2038 | return 0; |
912541b0 | 2039 | |
25de7aa7 YW |
2040 | strscpyl(device_node, sizeof(device_node), "/dev/", devnode, NULL); |
2041 | if (stat(device_node, &stats) < 0) { | |
2042 | if (errno != ENOENT) | |
2043 | return log_error_errno(errno, "Failed to stat %s: %m", device_node); | |
2044 | return 0; | |
2045 | } | |
84b6ad70 | 2046 | |
25de7aa7 YW |
2047 | if (!S_ISBLK(stats.st_mode) && !S_ISCHR(stats.st_mode)) { |
2048 | log_warning("%s is neither block nor character device, ignoring.", device_node); | |
2049 | return 0; | |
2050 | } | |
912541b0 | 2051 | |
25de7aa7 YW |
2052 | if (!strv_isempty(tags)) { |
2053 | unescaped_filename = xescape(devnode, "/."); | |
2054 | if (!unescaped_filename) | |
2055 | return log_oom(); | |
2056 | } | |
ca2bb160 | 2057 | |
25de7aa7 YW |
2058 | /* export the tags to a directory as symlinks, allowing otherwise dead nodes to be tagged */ |
2059 | STRV_FOREACH(t, tags) { | |
2060 | strscpyl(tags_dir, sizeof(tags_dir), "/run/udev/static_node-tags/", *t, "/", NULL); | |
2061 | r = mkdir_p(tags_dir, 0755); | |
2062 | if (r < 0) | |
2063 | return log_error_errno(r, "Failed to create %s: %m", tags_dir); | |
d6f116a7 | 2064 | |
25de7aa7 YW |
2065 | strscpyl(tag_symlink, sizeof(tag_symlink), tags_dir, unescaped_filename, NULL); |
2066 | r = symlink(device_node, tag_symlink); | |
2067 | if (r < 0 && errno != EEXIST) | |
2068 | return log_error_errno(errno, "Failed to create symlink %s -> %s: %m", | |
2069 | tag_symlink, device_node); | |
2070 | } | |
84b6ad70 | 2071 | |
25de7aa7 YW |
2072 | /* don't touch the permissions if only the tags were set */ |
2073 | if (uid == UID_INVALID && gid == GID_INVALID && mode == MODE_INVALID) | |
2074 | return 0; | |
84b6ad70 | 2075 | |
25de7aa7 YW |
2076 | if (mode == MODE_INVALID) |
2077 | mode = gid_is_valid(gid) ? 0660 : 0600; | |
2078 | if (!uid_is_valid(uid)) | |
2079 | uid = 0; | |
2080 | if (!gid_is_valid(gid)) | |
2081 | gid = 0; | |
84b6ad70 | 2082 | |
25de7aa7 YW |
2083 | r = chmod_and_chown(device_node, mode, uid, gid); |
2084 | if (r < 0) | |
2085 | return log_error_errno(errno, "Failed to chown '%s' %u %u: %m", | |
2086 | device_node, uid, gid); | |
2087 | else | |
2088 | log_debug("chown '%s' %u:%u", device_node, uid, gid); | |
84b6ad70 | 2089 | |
25de7aa7 YW |
2090 | (void) utimensat(AT_FDCWD, device_node, NULL, 0); |
2091 | return 0; | |
2092 | } | |
84b6ad70 | 2093 | |
25de7aa7 YW |
2094 | static int udev_rule_line_apply_static_dev_perms(UdevRuleLine *rule_line) { |
2095 | UdevRuleToken *token; | |
2096 | _cleanup_free_ char **tags = NULL; | |
2097 | uid_t uid = UID_INVALID; | |
2098 | gid_t gid = GID_INVALID; | |
2099 | mode_t mode = MODE_INVALID; | |
2100 | int r; | |
d6f116a7 | 2101 | |
25de7aa7 | 2102 | assert(rule_line); |
912541b0 | 2103 | |
25de7aa7 YW |
2104 | if (!FLAGS_SET(rule_line->type, LINE_HAS_STATIC_NODE)) |
2105 | return 0; | |
912541b0 | 2106 | |
25de7aa7 YW |
2107 | LIST_FOREACH(tokens, token, rule_line->tokens) |
2108 | if (token->type == TK_A_OWNER_ID) | |
2109 | uid = PTR_TO_UID(token->data); | |
2110 | else if (token->type == TK_A_GROUP_ID) | |
2111 | gid = PTR_TO_GID(token->data); | |
2112 | else if (token->type == TK_A_MODE_ID) | |
2113 | mode = PTR_TO_MODE(token->data); | |
2114 | else if (token->type == TK_A_TAG) { | |
2115 | r = strv_extend(&tags, token->value); | |
2116 | if (r < 0) | |
2117 | return log_oom(); | |
2118 | } else if (token->type == TK_A_OPTIONS_STATIC_NODE) { | |
2119 | r = apply_static_dev_perms(token->value, uid, gid, mode, tags); | |
2120 | if (r < 0) | |
2121 | return r; | |
912541b0 KS |
2122 | } |
2123 | ||
25de7aa7 YW |
2124 | return 0; |
2125 | } | |
2126 | ||
2127 | int udev_rules_apply_static_dev_perms(UdevRules *rules) { | |
2128 | UdevRuleFile *file; | |
2129 | UdevRuleLine *line; | |
2130 | int r; | |
2131 | ||
2132 | assert(rules); | |
84b6ad70 | 2133 | |
25de7aa7 YW |
2134 | LIST_FOREACH(rule_files, file, rules->rule_files) |
2135 | LIST_FOREACH(rule_lines, line, file->rule_lines) { | |
2136 | r = udev_rule_line_apply_static_dev_perms(line); | |
2137 | if (r < 0) | |
2138 | return r; | |
84b6ad70 | 2139 | } |
84b6ad70 | 2140 | |
fdd21be6 | 2141 | return 0; |
761dfddc | 2142 | } |