}
case TK_A_ATTR: {
char buf[UDEV_PATH_SIZE], value[UDEV_NAME_SIZE];
- const char *val, *key_name = token->data;
+ const char *key = token->data;
+
+ /* First, try to resolve "[<SUBSYSTEM>/<KERNEL>]<attribute>" format. */
+ r = udev_resolve_subsys_kernel(key, buf, sizeof(buf), false);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (ERRNO_IS_NEG_DEVICE_ABSENT(r)) {
+ log_event_warning_errno(event, token, r, "Failed to resolve sysfs attribute \"%s\", ignoring: %m", key);
+ return true;
+ }
+ if (r < 0) {
+ /* If not, make the path to sysfs attribute absolute, to make '*' resolvable by attr_subst_subdir(). */
+ const char *syspath;
+ r = sd_device_get_syspath(dev, &syspath);
+ if (r < 0)
+ return log_event_error_errno(event, token, r, "Failed to get syspath: %m");
- if (udev_resolve_subsys_kernel(key_name, buf, sizeof(buf), false) < 0 &&
- sd_device_get_syspath(dev, &val) >= 0) {
bool truncated;
- strscpyl_full(buf, sizeof(buf), &truncated, val, "/", key_name, NULL);
+ strscpyl_full(buf, sizeof(buf), &truncated, syspath, "/", key, NULL);
if (truncated) {
log_event_warning(event, token,
"The path to the attribute \"%s/%s\" is too long, refusing to set the attribute.",
- val, key_name);
+ syspath, key);
+ return true;
+ }
+
+ /* Resolve '*' in the path. */
+ r = attr_subst_subdir(buf);
+ if (r < 0) {
+ log_event_error_errno(event, token, r, "Could not find file matches \"%s\", ignoring: %m", buf);
return true;
}
}
- r = attr_subst_subdir(buf);
+ /* Then, make the path relative again. This also checks if the path being inside of the sysfs. */
+ _cleanup_free_ char *resolved = NULL;
+ _cleanup_close_ int fd = -EBADF;
+ r = device_chase(dev, buf, /* flags = */ 0, &resolved, &fd);
if (r < 0) {
- log_event_error_errno(event, token, r, "Could not find file matches \"%s\", ignoring: %m", buf);
+ log_event_error_errno(event, token, r, "Could not chase sysfs attribute \"%s\", ignoring: %m", buf);
return true;
}
+ /* Apply formatting to the value. */
if (!apply_format_value(event, token, value, sizeof(value), "attribute value"))
return true;
if (EVENT_MODE_DESTRUCTIVE(event)) {
- log_event_debug(event, token, "Writing \"%s\" to sysfs attribute \"%s\".", value, buf);
- r = write_string_file(buf, value,
- WRITE_STRING_FILE_VERIFY_ON_FAILURE |
- WRITE_STRING_FILE_DISABLE_BUFFER |
- WRITE_STRING_FILE_AVOID_NEWLINE |
- WRITE_STRING_FILE_VERIFY_IGNORE_NEWLINE);
+ log_event_debug(event, token, "Writing \"%s\" to sysfs attribute \"%s\".", value, resolved);
+ r = sd_device_set_sysattr_value(dev, resolved, value);
if (r < 0)
- log_event_error_errno(event, token, r, "Failed to write \"%s\" to sysfs attribute \"%s\", ignoring: %m", value, buf);
+ log_event_error_errno(event, token, r, "Failed to write \"%s\" to sysfs attribute \"%s\", ignoring: %m", value, resolved);
else {
- event_cache_written_sysattr(event, buf, value);
+ event_cache_written_sysattr(event, resolved, value);
log_event_done(event, token);
}
} else {
- log_event_debug(event, token, "Running in test mode, skipping writing \"%s\" to sysfs attribute \"%s\".", value, buf);
+ log_event_debug(event, token, "Running in test mode, skipping writing \"%s\" to sysfs attribute \"%s\".", value, resolved);
- r = verify_regular_at(AT_FDCWD, buf, /* follow = */ false);
+ /* We assume the attribute is writable if the path points to a regular file, and cache
+ * the value to make it shown by OPTIONS="dump" or obtained by later ATTR match token. */
+ r = fd_verify_regular(fd);
if (r < 0 && !ERRNO_IS_NEG_PRIVILEGE(r))
- log_event_error_errno(event, token, r, "Failed to verify sysfs attribute \"%s\" is a regular file: %m", buf);
- else
- event_cache_written_sysattr(event, buf, value);
+ log_event_error_errno(event, token, r, "Failed to verify sysfs attribute \"%s\" is a regular file: %m", resolved);
+ else {
+ event_cache_written_sysattr(event, resolved, value);
+
+ _cleanup_free_ char *copied = strdup(value);
+ if (!copied)
+ return log_oom();
+
+ r = device_cache_sysattr_value(dev, resolved, value, /* error = */ 0);
+ if (r < 0)
+ log_event_warning_errno(event, token, r, "Failed to cache sysfs attribute \"%s\", ignoring: %m", resolved);
+ else if (r > 0) {
+ TAKE_PTR(resolved);
+ TAKE_PTR(copied);
+ }
+ }
}
return true;
}