sd-device.c revision bba061662b0f759abb43bad60c9733305c191045
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen/***
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen This file is part of systemd.
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen Copyright 2008-2012 Kay Sievers <kay@vrfy.org>
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen Copyright 2014 Tom Gundersen <teg@jklm.no>
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen systemd is free software; you can redistribute it and/or modify it
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen under the terms of the GNU Lesser General Public License as published by
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen the Free Software Foundation; either version 2.1 of the License, or
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen (at your option) any later version.
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen systemd is distributed in the hope that it will be useful, but
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen WITHOUT ANY WARRANTY; without even the implied warranty of
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen Lesser General Public License for more details.
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen You should have received a copy of the GNU Lesser General Public License
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen along with systemd; If not, see <http://www.gnu.org/licenses/>.
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen***/
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#include <ctype.h>
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#include <sys/types.h>
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#include <net/if.h>
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#include "util.h"
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#include "macro.h"
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#include "path-util.h"
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#include "strxcpyx.h"
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#include "fileio.h"
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#include "hashmap.h"
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#include "set.h"
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#include "strv.h"
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#include "sd-device.h"
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#include "device-util.h"
920b52e4909d9dc812817fd8b82f83ca23a11c91Thomas Hindoe Paaboel Andersen#include "device-private.h"
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#include "device-internal.h"
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersenint device_new_aux(sd_device **ret) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen _cleanup_device_unref_ sd_device *device = NULL;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
79008bddf679a5e0900369950eb346c9fa687107Lennart Poettering assert(ret);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
f0213e3796b4dd66e546e2de4d677db319f9171bTom Gundersen device = new0(sd_device, 1);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (!device)
e53fc357a9bb9d0a5362ccc4246d598cb0febd5eLennart Poettering return -ENOMEM;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen device->n_ref = 1;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen device->watch_handle = -1;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen *ret = device;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen device = NULL;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return 0;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen}
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
f0213e3796b4dd66e546e2de4d677db319f9171bTom Gundersen_public_ sd_device *sd_device_ref(sd_device *device) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (device)
e53fc357a9bb9d0a5362ccc4246d598cb0febd5eLennart Poettering assert_se(++ device->n_ref >= 2);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return device;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen}
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen_public_ sd_device *sd_device_unref(sd_device *device) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (device && -- device->n_ref == 0) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen sd_device_unref(device->parent);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen free(device->syspath);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen free(device->sysname);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen free(device->devtype);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen free(device->devname);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen free(device->subsystem);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen free(device->driver);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen free(device->id_filename);
1c4baffc1895809bae9ac36b670af90a4cb9cd7dTom Gundersen free(device->properties_strv);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen free(device->properties_nulstr);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen ordered_hashmap_free_free_free(device->properties);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen ordered_hashmap_free_free_free(device->properties_db);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen hashmap_free_free_free(device->sysattr_values);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen set_free_free(device->sysattrs);
1c4baffc1895809bae9ac36b670af90a4cb9cd7dTom Gundersen set_free_free(device->tags);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen set_free_free(device->devlinks);
e53fc357a9bb9d0a5362ccc4246d598cb0febd5eLennart Poettering
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen free(device);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen }
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return NULL;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen}
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersenint device_add_property_aux(sd_device *device, const char *_key, const char *_value, bool db) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen OrderedHashmap **properties;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen assert(device);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen assert(_key);
1c4baffc1895809bae9ac36b670af90a4cb9cd7dTom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (db)
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen properties = &device->properties_db;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen else
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen properties = &device->properties;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (_value) {
1c4baffc1895809bae9ac36b670af90a4cb9cd7dTom Gundersen _cleanup_free_ char *key = NULL, *value = NULL, *old_key = NULL, *old_value = NULL;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen int r;
e53fc357a9bb9d0a5362ccc4246d598cb0febd5eLennart Poettering
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen r = ordered_hashmap_ensure_allocated(properties, &string_hash_ops);
45af44d47da6933b260c734ad9ff721f63f80a4dTom Gundersen if (r < 0)
45af44d47da6933b260c734ad9ff721f63f80a4dTom Gundersen return r;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen key = strdup(_key);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (!key)
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return -ENOMEM;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen value = strdup(_value);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (!value)
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return -ENOMEM;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen old_value = ordered_hashmap_get2(*properties, key, (void**) &old_key);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen r = ordered_hashmap_replace(*properties, key, value);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (r < 0)
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return r;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen key = NULL;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen value = NULL;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen } else {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen _cleanup_free_ char *key = NULL;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen _cleanup_free_ char *value = NULL;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen value = ordered_hashmap_remove2(*properties, _key, (void**) &key);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen }
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
79008bddf679a5e0900369950eb346c9fa687107Lennart Poettering if (!db) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen device->properties_generation ++;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen device->properties_buf_outdated = true;
f0213e3796b4dd66e546e2de4d677db319f9171bTom Gundersen }
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return 0;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen}
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersenint device_add_property_internal(sd_device *device, const char *key, const char *value) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return device_add_property_aux(device, key, value, false);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen}
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersenint device_set_syspath(sd_device *device, const char *_syspath, bool verify) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen _cleanup_free_ char *syspath = NULL;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen const char *devpath;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen int r;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen assert(device);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen assert(_syspath);
f0213e3796b4dd66e546e2de4d677db319f9171bTom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen /* must be a subdirectory of /sys */
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (!path_startswith(_syspath, "/sys/")) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen log_debug("sd-device: syspath '%s' is not a subdirectory of /sys", _syspath);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return -EINVAL;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen }
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (verify) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen r = readlink_and_canonicalize(_syspath, &syspath);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (r == -EINVAL) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen /* not a symlink */
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen syspath = canonicalize_file_name(_syspath);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (!syspath) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen log_debug("sd-device: could not canonicalize '%s': %m", _syspath);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return -errno;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen }
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen /* ignore errors due to the link not being a symlink */
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen } else if (r < 0 && r != -EINVAL) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen log_debug("sd-device: could not get target of '%s': %s", _syspath, strerror(-r));
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return r;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen }
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (path_startswith(syspath, "/sys/devices/")) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen char *path;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen /* all 'devices' require an 'uevent' file */
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen path = strjoina(syspath, "/uevent");
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen r = access(path, F_OK);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (r < 0) {
be19c5b5e0c0f78b8429b126936fa15856550a23David Herrmann log_debug("sd-device: %s does not have an uevent file: %m", syspath);
be19c5b5e0c0f78b8429b126936fa15856550a23David Herrmann return -errno;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen }
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen } else {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen /* everything else just just needs to be a directory */
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (!is_dir(syspath, false)) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen log_debug("sd-device: %s is not a directory", syspath);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return -EINVAL;
be19c5b5e0c0f78b8429b126936fa15856550a23David Herrmann }
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen }
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen } else {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen syspath = strdup(_syspath);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (!syspath)
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return -ENOMEM;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen }
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b45e4eb679ad0c9a77c4fe6e404c8842d4097fdbTom Gundersen devpath = syspath + strlen("/sys");
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen r = device_add_property_internal(device, "DEVPATH", devpath);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (r < 0)
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return r;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen free(device->syspath);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen device->syspath = syspath;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen syspath = NULL;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen device->devpath = devpath;
e0ee46f29028e291eb67f435aff1b6202d75d9d6Lennart Poettering
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return 0;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen}
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen_public_ int sd_device_new_from_syspath(sd_device **ret, const char *syspath) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen _cleanup_device_unref_ sd_device *device = NULL;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen int r;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen assert_return(ret, -EINVAL);
e3dca0089b7b50e2ec21409d1292727921d06102Tom Gundersen assert_return(syspath, -EINVAL);
e3dca0089b7b50e2ec21409d1292727921d06102Tom Gundersen
e3dca0089b7b50e2ec21409d1292727921d06102Tom Gundersen r = device_new_aux(&device);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (r < 0)
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return r;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen r = device_set_syspath(device, syspath, true);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (r < 0)
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return r;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen *ret = device;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen device = NULL;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return 0;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen}
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen_public_ int sd_device_new_from_devnum(sd_device **ret, char type, dev_t devnum) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen char *syspath;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen char id[DECIMAL_STR_MAX(unsigned) * 2 + 1];
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen assert_return(ret, -EINVAL);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen assert_return(type == 'b' || type == 'c', -EINVAL);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen /* use /sys/dev/{block,char}/<maj>:<min> link */
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen snprintf(id, sizeof(id), "%u:%u", major(devnum), minor(devnum));
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
syspath = strjoina("/sys/dev/", (type == 'b' ? "block" : "char"), "/", id);
return sd_device_new_from_syspath(ret, syspath);
}
_public_ int sd_device_new_from_subsystem_sysname(sd_device **ret, const char *subsystem, const char *sysname) {
char *syspath;
assert_return(ret, -EINVAL);
assert_return(subsystem, -EINVAL);
assert_return(sysname, -EINVAL);
if (streq(subsystem, "subsystem")) {
syspath = strjoina("/sys/subsystem/", sysname);
if (access(syspath, F_OK) >= 0)
return sd_device_new_from_syspath(ret, syspath);
syspath = strjoina("/sys/bus/", sysname);
if (access(syspath, F_OK) >= 0)
return sd_device_new_from_syspath(ret, syspath);
syspath = strjoina("/sys/class/", sysname);
if (access(syspath, F_OK) >= 0)
return sd_device_new_from_syspath(ret, syspath);
} else if (streq(subsystem, "module")) {
syspath = strjoina("/sys/module/", sysname);
if (access(syspath, F_OK) >= 0)
return sd_device_new_from_syspath(ret, syspath);
} else if (streq(subsystem, "drivers")) {
char subsys[PATH_MAX];
char *driver;
strscpy(subsys, sizeof(subsys), sysname);
driver = strchr(subsys, ':');
if (driver) {
driver[0] = '\0';
driver++;
syspath = strjoina("/sys/subsystem/", subsys, "/drivers/", driver);
if (access(syspath, F_OK) >= 0)
return sd_device_new_from_syspath(ret, syspath);
syspath = strjoina("/sys/bus/", subsys, "/drivers/", driver);
if (access(syspath, F_OK) >= 0)
return sd_device_new_from_syspath(ret, syspath);
} else
return -EINVAL;
} else {
syspath = strjoina("/sys/subsystem/", subsystem, "/devices/", sysname);
if (access(syspath, F_OK) >= 0)
return sd_device_new_from_syspath(ret, syspath);
syspath = strjoina("/sys/bus/", subsystem, "/devices/", sysname);
if (access(syspath, F_OK) >= 0)
return sd_device_new_from_syspath(ret, syspath);
syspath = strjoina("/sys/class/", subsystem, "/", sysname);
if (access(syspath, F_OK) >= 0)
return sd_device_new_from_syspath(ret, syspath);
}
return -ENOENT;
}
int device_set_devtype(sd_device *device, const char *_devtype) {
_cleanup_free_ char *devtype = NULL;
int r;
assert(device);
assert(_devtype);
devtype = strdup(_devtype);
if (!devtype)
return -ENOMEM;
r = device_add_property_internal(device, "DEVTYPE", devtype);
if (r < 0)
return r;
free(device->devtype);
device->devtype = devtype;
devtype = NULL;
return 0;
}
int device_set_ifindex(sd_device *device, const char *_ifindex) {
int ifindex, r;
assert(device);
assert(_ifindex);
r = safe_atoi(_ifindex, &ifindex);
if (r < 0)
return r;
if (ifindex <= 0)
return -EINVAL;
r = device_add_property_internal(device, "IFINDEX", _ifindex);
if (r < 0)
return r;
device->ifindex = ifindex;
return 0;
}
int device_set_devname(sd_device *device, const char *_devname) {
_cleanup_free_ char *devname = NULL;
int r;
assert(device);
assert(_devname);
if (_devname[0] != '/') {
r = asprintf(&devname, "/dev/%s", _devname);
if (r < 0)
return -ENOMEM;
} else {
devname = strdup(_devname);
if (!devname)
return -ENOMEM;
}
r = device_add_property_internal(device, "DEVNAME", devname);
if (r < 0)
return r;
free(device->devname);
device->devname = devname;
devname = NULL;
return 0;
}
int device_set_devmode(sd_device *device, const char *_devmode) {
unsigned devmode;
int r;
assert(device);
assert(_devmode);
r = safe_atou(_devmode, &devmode);
if (r < 0)
return r;
if (devmode > 07777)
return -EINVAL;
r = device_add_property_internal(device, "DEVMODE", _devmode);
if (r < 0)
return r;
device->devmode = devmode;
return 0;
}
int device_set_devnum(sd_device *device, const char *major, const char *minor) {
unsigned maj = 0, min = 0;
int r;
assert(device);
assert(major);
r = safe_atou(major, &maj);
if (r < 0)
return r;
if (!maj)
return 0;
if (minor) {
r = safe_atou(minor, &min);
if (r < 0)
return r;
}
r = device_add_property_internal(device, "MAJOR", major);
if (r < 0)
return r;
if (minor) {
r = device_add_property_internal(device, "MINOR", minor);
if (r < 0)
return r;
}
device->devnum = makedev(maj, min);
return 0;
}
static int handle_uevent_line(sd_device *device, const char *key, const char *value, const char **major, const char **minor) {
int r;
assert(device);
assert(key);
assert(value);
assert(major);
assert(minor);
if (streq(key, "DEVTYPE")) {
r = device_set_devtype(device, value);
if (r < 0)
return r;
} else if (streq(key, "IFINDEX")) {
r = device_set_ifindex(device, value);
if (r < 0)
return r;
} else if (streq(key, "DEVNAME")) {
r = device_set_devname(device, value);
if (r < 0)
return r;
} else if (streq(key, "DEVMODE")) {
r = device_set_devmode(device, value);
if (r < 0)
return r;
} else if (streq(key, "MAJOR"))
*major = value;
else if (streq(key, "MINOR"))
*minor = value;
else {
r = device_add_property_internal(device, key, value);
if (r < 0)
return r;
}
return 0;
}
int device_read_uevent_file(sd_device *device) {
_cleanup_free_ char *uevent = NULL;
const char *syspath, *key, *value, *major = NULL, *minor = NULL;
char *path;
size_t uevent_len;
unsigned i;
int r;
enum {
PRE_KEY,
KEY,
PRE_VALUE,
VALUE,
INVALID_LINE,
} state = PRE_KEY;
assert(device);
if (device->uevent_loaded || device->sealed)
return 0;
r = sd_device_get_syspath(device, &syspath);
if (r < 0)
return r;
path = strjoina(syspath, "/uevent");
r = read_full_file(path, &uevent, &uevent_len);
if (r == -EACCES)
/* empty uevent files may be write-only */
return 0;
else if (r < 0) {
log_debug("sd-device: failed to read uevent file '%s': %s", path, strerror(-r));
return r;
}
for (i = 0; i < uevent_len; i++) {
switch (state) {
case PRE_KEY:
if (!strchr(NEWLINE, uevent[i])) {
key = &uevent[i];
state = KEY;
}
break;
case KEY:
if (uevent[i] == '=') {
uevent[i] = '\0';
state = PRE_VALUE;
} else if (strchr(NEWLINE, uevent[i])) {
uevent[i] = '\0';
log_debug("sd-device: ignoring invalid uevent line '%s'", key);
state = PRE_KEY;
}
break;
case PRE_VALUE:
value = &uevent[i];
state = VALUE;
break;
case VALUE:
if (strchr(NEWLINE, uevent[i])) {
uevent[i] = '\0';
r = handle_uevent_line(device, key, value, &major, &minor);
if (r < 0)
log_debug("sd-device: failed to handle uevent entry '%s=%s': %s", key, value, strerror(-r));
state = PRE_KEY;
}
break;
default:
assert_not_reached("invalid state when parsing uevent file");
}
}
if (major) {
r = device_set_devnum(device, major, minor);
if (r < 0)
log_debug("sd-device: could not set 'MAJOR=%s' or 'MINOR=%s' from '%s': %s", major, minor, path, strerror(-r));
}
device->uevent_loaded = true;
return 0;
}
_public_ int sd_device_get_ifindex(sd_device *device, int *ifindex) {
int r;
assert_return(device, -EINVAL);
assert_return(ifindex, -EINVAL);
r = device_read_uevent_file(device);
if (r < 0)
return r;
*ifindex = device->ifindex;
return 0;
}
_public_ int sd_device_new_from_device_id(sd_device **ret, const char *id) {
int r;
assert_return(ret, -EINVAL);
assert_return(id, -EINVAL);
switch (id[0]) {
case 'b':
case 'c':
{
char type;
int maj, min;
r = sscanf(id, "%c%i:%i", &type, &maj, &min);
if (r != 3)
return -EINVAL;
return sd_device_new_from_devnum(ret, type, makedev(maj, min));
}
case 'n':
{
_cleanup_device_unref_ sd_device *device = NULL;
_cleanup_close_ int sk = -1;
struct ifreq ifr = {};
int ifindex;
r = safe_atoi(&id[1], &ifr.ifr_ifindex);
if (r < 0)
return r;
else if (ifr.ifr_ifindex <= 0)
return -EINVAL;
sk = socket(PF_INET, SOCK_DGRAM, 0);
if (sk < 0)
return -errno;
r = ioctl(sk, SIOCGIFNAME, &ifr);
if (r < 0)
return -errno;
r = sd_device_new_from_subsystem_sysname(&device, "net", ifr.ifr_name);
if (r < 0)
return r;
r = sd_device_get_ifindex(device, &ifindex);
if (r < 0)
return r;
/* this si racey, so we might end up with the wrong device */
if (ifr.ifr_ifindex != ifindex)
return -ENODEV;
*ret = device;
device = NULL;
return 0;
}
case '+':
{
char subsys[PATH_MAX];
char *sysname;
(void)strscpy(subsys, sizeof(subsys), id + 1);
sysname = strchr(subsys, ':');
if (!sysname)
return -EINVAL;
sysname[0] = '\0';
sysname ++;
return sd_device_new_from_subsystem_sysname(ret, subsys, sysname);
}
default:
return -EINVAL;
}
}
_public_ int sd_device_get_syspath(sd_device *device, const char **ret) {
assert_return(device, -EINVAL);
assert_return(ret, -EINVAL);
assert(path_startswith(device->syspath, "/sys/"));
*ret = device->syspath;
return 0;
}
static int device_new_from_child(sd_device **ret, sd_device *child) {
_cleanup_free_ char *path = NULL;
const char *subdir, *syspath;
int r;
assert(ret);
assert(child);
r = sd_device_get_syspath(child, &syspath);
if (r < 0)
return r;
path = strdup(syspath);
if (!path)
return -ENOMEM;
subdir = path + strlen("/sys");
for (;;) {
char *pos;
pos = strrchr(subdir, '/');
if (!pos || pos < subdir + 2)
break;
*pos = '\0';
r = sd_device_new_from_syspath(ret, path);
if (r < 0)
continue;
return 0;
}
return -ENOENT;
}
_public_ int sd_device_get_parent(sd_device *child, sd_device **ret) {
assert_return(ret, -EINVAL);
assert_return(child, -EINVAL);
if (!child->parent_set) {
child->parent_set = true;
(void)device_new_from_child(&child->parent, child);
}
if (!child->parent)
return -ENOENT;
*ret = child->parent;
return 0;
}
int device_set_subsystem(sd_device *device, const char *_subsystem) {
_cleanup_free_ char *subsystem = NULL;
int r;
assert(device);
assert(_subsystem);
subsystem = strdup(_subsystem);
if (!subsystem)
return -ENOMEM;
r = device_add_property_internal(device, "SUBSYSTEM", subsystem);
if (r < 0)
return r;
free(device->subsystem);
device->subsystem = subsystem;
subsystem = NULL;
device->subsystem_set = true;
return 0;
}
_public_ int sd_device_get_subsystem(sd_device *device, const char **ret) {
assert_return(ret, -EINVAL);
assert_return(device, -EINVAL);
if (!device->subsystem_set) {
_cleanup_free_ char *subsystem = NULL;
const char *syspath;
char *path;
int r;
/* read 'subsystem' link */
r = sd_device_get_syspath(device, &syspath);
if (r < 0)
return r;
path = strjoina(syspath, "/subsystem");
r = readlink_value(path, &subsystem);
if (r >= 0)
r = device_set_subsystem(device, subsystem);
/* use implicit names */
else if (path_startswith(device->devpath, "/module/"))
r = device_set_subsystem(device, "module");
else if (strstr(device->devpath, "/drivers/"))
r = device_set_subsystem(device, "drivers");
else if (path_startswith(device->devpath, "/subsystem/") ||
path_startswith(device->devpath, "/class/") ||
path_startswith(device->devpath, "/buss/"))
r = device_set_subsystem(device, "subsystem");
if (r < 0)
return r;
device->subsystem_set = true;
}
*ret = device->subsystem;
return 0;
}
_public_ int sd_device_get_devtype(sd_device *device, const char **devtype) {
int r;
assert(devtype);
assert(device);
r = device_read_uevent_file(device);
if (r < 0)
return r;
*devtype = device->devtype;
return 0;
}
_public_ int sd_device_get_parent_with_subsystem_devtype(sd_device *child, const char *subsystem, const char *devtype, sd_device **ret) {
sd_device *parent = NULL;
int r;
assert_return(child, -EINVAL);
assert_return(subsystem, -EINVAL);
r = sd_device_get_parent(child, &parent);
while (r >= 0) {
const char *parent_subsystem = NULL;
const char *parent_devtype = NULL;
(void)sd_device_get_subsystem(parent, &parent_subsystem);
if (streq_ptr(parent_subsystem, subsystem)) {
if (!devtype)
break;
(void)sd_device_get_devtype(parent, &parent_devtype);
if (streq_ptr(parent_devtype, devtype))
break;
}
r = sd_device_get_parent(parent, &parent);
}
if (r < 0)
return r;
*ret = parent;
return 0;
}
_public_ int sd_device_get_devnum(sd_device *device, dev_t *devnum) {
int r;
assert_return(device, -EINVAL);
assert_return(devnum, -EINVAL);
r = device_read_uevent_file(device);
if (r < 0)
return r;
*devnum = device->devnum;
return 0;
}
int device_set_driver(sd_device *device, const char *_driver) {
_cleanup_free_ char *driver = NULL;
int r;
assert(device);
assert(_driver);
driver = strdup(_driver);
if (!driver)
return -ENOMEM;
r = device_add_property_internal(device, "DRIVER", driver);
if (r < 0)
return r;
free(device->driver);
device->driver = driver;
driver = NULL;
device->driver_set = true;
return 0;
}
_public_ int sd_device_get_driver(sd_device *device, const char **ret) {
assert_return(device, -EINVAL);
assert_return(ret, -EINVAL);
if (!device->driver_set) {
_cleanup_free_ char *driver = NULL;
const char *syspath;
char *path;
int r;
r = sd_device_get_syspath(device, &syspath);
if (r < 0)
return r;
path = strjoina(syspath, "/driver");
r = readlink_value(path, &driver);
if (r >= 0) {
r = device_set_driver(device, driver);
if (r < 0)
return r;
}
}
*ret = device->driver;
return 0;
}
_public_ int sd_device_get_devpath(sd_device *device, const char **devpath) {
assert_return(device, -EINVAL);
assert_return(devpath, -EINVAL);
assert(device->devpath);
assert(device->devpath[0] == '/');
*devpath = device->devpath;
return 0;
}
_public_ int sd_device_get_devname(sd_device *device, const char **devname) {
int r;
assert_return(device, -EINVAL);
assert_return(devname, -EINVAL);
r = device_read_uevent_file(device);
if (r < 0)
return r;
if (!device->devname)
return -ENOENT;
assert(path_startswith(device->devname, "/dev/"));
*devname = device->devname;
return 0;
}
static int device_set_sysname(sd_device *device) {
_cleanup_free_ char *sysname = NULL;
const char *sysnum = NULL;
const char *pos;
size_t len = 0;
pos = strrchr(device->devpath, '/');
if (!pos)
return -EINVAL;
pos ++;
/* devpath is not a root directory */
if (*pos == '\0' || pos <= device->devpath)
return -EINVAL;
sysname = strdup(pos);
if (!sysname)
return -ENOMEM;
/* some devices have '!' in their name, change that to '/' */
while (sysname[len] != '\0') {
if (sysname[len] == '!')
sysname[len] = '/';
len ++;
}
/* trailing number */
while (len > 0 && isdigit(sysname[--len]))
sysnum = &sysname[len];
if (len == 0)
sysnum = NULL;
free(device->sysname);
device->sysname = sysname;
sysname = NULL;
device->sysnum = sysnum;
device->sysname_set = true;
return 0;
}
_public_ int sd_device_get_sysname(sd_device *device, const char **ret) {
int r;
assert_return(device, -EINVAL);
assert_return(ret, -EINVAL);
if (!device->sysname_set) {
r = device_set_sysname(device);
if (r < 0)
return r;
}
*ret = device->sysname;
return 0;
}
_public_ int sd_device_get_sysnum(sd_device *device, const char **ret) {
int r;
assert_return(device, -EINVAL);
assert_return(ret, -EINVAL);
if (!device->sysname_set) {
r = device_set_sysname(device);
if (r < 0)
return r;
}
*ret = device->sysnum;
return 0;
}
static bool is_valid_tag(const char *tag) {
assert(tag);
return !strchr(tag, ':') && !strchr(tag, ' ');
}
int device_add_tag(sd_device *device, const char *tag) {
int r;
assert(device);
assert(tag);
if (!is_valid_tag(tag))
return -EINVAL;
r = set_ensure_allocated(&device->tags, &string_hash_ops);
if (r < 0)
return r;
r = set_put_strdup(device->tags, tag);
if (r < 0)
return r;
device->tags_generation ++;
device->property_tags_outdated = true;
return 0;
}
int device_add_devlink(sd_device *device, const char *devlink) {
int r;
assert(device);
assert(devlink);
r = set_ensure_allocated(&device->devlinks, &string_hash_ops);
if (r < 0)
return r;
r = set_put_strdup(device->devlinks, devlink);
if (r < 0)
return r;
device->devlinks_generation ++;
device->property_devlinks_outdated = true;
return 0;
}
static int device_add_property_internal_from_string(sd_device *device, const char *str) {
_cleanup_free_ char *key = NULL;
char *value;
assert(device);
assert(str);
key = strdup(str);
if (!key)
return -ENOMEM;
value = strchr(key, '=');
if (!value)
return -EINVAL;
*value = '\0';
if (isempty(++value))
value = NULL;
return device_add_property_internal(device, key, value);
}
int device_set_usec_initialized(sd_device *device, const char *initialized) {
uint64_t usec_initialized;
int r;
assert(device);
assert(initialized);
r = safe_atou64(initialized, &usec_initialized);
if (r < 0)
return r;
r = device_add_property_internal(device, "USEC_INITIALIZED", initialized);
if (r < 0)
return r;
device->usec_initialized = usec_initialized;
return 0;
}
static int handle_db_line(sd_device *device, char key, const char *value) {
char *path;
int r;
assert(device);
assert(value);
switch (key) {
case 'G':
r = device_add_tag(device, value);
if (r < 0)
return r;
break;
case 'S':
path = strjoina("/dev/", value);
r = device_add_devlink(device, path);
if (r < 0)
return r;
break;
case 'E':
r = device_add_property_internal_from_string(device, value);
if (r < 0)
return r;
break;
case 'I':
r = device_set_usec_initialized(device, value);
if (r < 0)
return r;
break;
case 'L':
r = safe_atoi(value, &device->devlink_priority);
if (r < 0)
return r;
break;
case 'W':
r = safe_atoi(value, &device->watch_handle);
if (r < 0)
return r;
break;
default:
log_debug("device db: unknown key '%c'", key);
}
return 0;
}
int device_get_id_filename(sd_device *device, const char **ret) {
assert(device);
assert(ret);
if (!device->id_filename) {
_cleanup_free_ char *id = NULL;
const char *subsystem;
dev_t devnum;
int ifindex, r;
r = sd_device_get_subsystem(device, &subsystem);
if (r < 0)
return r;
r = sd_device_get_devnum(device, &devnum);
if (r < 0)
return r;
r = sd_device_get_ifindex(device, &ifindex);
if (r < 0)
return r;
if (major(devnum) > 0) {
/* use dev_t -- b259:131072, c254:0 */
r = asprintf(&id, "%c%u:%u",
streq(subsystem, "block") ? 'b' : 'c',
major(devnum), minor(devnum));
if (r < 0)
return -errno;
} else if (ifindex > 0) {
/* use netdev ifindex -- n3 */
r = asprintf(&id, "n%u", ifindex);
if (r < 0)
return -errno;
} else {
/* use $subsys:$sysname -- pci:0000:00:1f.2
* sysname() has '!' translated, get it from devpath
*/
const char *sysname;
sysname = basename(device->devpath);
if (!sysname)
return -EINVAL;
r = asprintf(&id, "+%s:%s", subsystem, sysname);
if (r < 0)
return -errno;
}
device->id_filename = id;
id = NULL;
}
*ret = device->id_filename;
return 0;
}
static int device_read_db(sd_device *device) {
_cleanup_free_ char *db = NULL;
char *path;
const char *id, *value;
char key;
size_t db_len;
unsigned i;
int r;
enum {
PRE_KEY,
KEY,
PRE_VALUE,
VALUE,
INVALID_LINE,
} state = PRE_KEY;
if (device->db_loaded || device->sealed)
return 0;
r = device_get_id_filename(device, &id);
if (r < 0)
return r;
path = strjoina("/run/udev/data/", id);
r = read_full_file(path, &db, &db_len);
if (r < 0) {
if (r == -ENOENT)
return 0;
else {
log_debug("sd-device: failed to read db '%s': %s", path, strerror(-r));
return r;
}
}
/* devices with a database entry are initialized */
device->is_initialized = true;;
for (i = 0; i < db_len; i++) {
switch (state) {
case PRE_KEY:
if (!strchr(NEWLINE, db[i])) {
key = db[i];
state = KEY;
}
break;
case KEY:
if (db[i] != ':') {
log_debug("sd-device: ignoring invalid db entry with key '%c'", key);
state = INVALID_LINE;
} else {
db[i] = '\0';
state = PRE_VALUE;
}
break;
case PRE_VALUE:
value = &db[i];
state = VALUE;
break;
case INVALID_LINE:
if (strchr(NEWLINE, db[i]))
state = PRE_KEY;
break;
case VALUE:
if (strchr(NEWLINE, db[i])) {
db[i] = '\0';
r = handle_db_line(device, key, value);
if (r < 0)
log_debug("sd-device: failed to handle db entry '%c:%s': %s", key, value, strerror(-r));
state = PRE_KEY;
}
break;
default:
assert_not_reached("invalid state when parsing db");
}
}
device->db_loaded = true;
return 0;
}
_public_ int sd_device_get_is_initialized(sd_device *device, int *initialized) {
int r;
assert_return(device, -EINVAL);
assert_return(initialized, -EINVAL);
r = device_read_db(device);
if (r < 0)
return r;
*initialized = device->is_initialized;
return 0;
}
_public_ int sd_device_get_usec_since_initialized(sd_device *device, uint64_t *usec) {
usec_t now_ts;
int r;
assert_return(device, -EINVAL);
assert_return(usec, -EINVAL);
r = device_read_db(device);
if (r < 0)
return r;
if (!device->is_initialized)
return -EBUSY;
if (!device->usec_initialized)
return -ENODATA;
now_ts = now(clock_boottime_or_monotonic());
if (now_ts < device->usec_initialized)
return -EIO;
*usec = now_ts - device->usec_initialized;
return 0;
}
_public_ const char *sd_device_get_tag_first(sd_device *device) {
assert_return(device, NULL);
(void) device_read_db(device);
device->tags_iterator_generation = device->tags_generation;
device->tags_iterator = ITERATOR_FIRST;
return set_iterate(device->tags, &device->tags_iterator);
}
_public_ const char *sd_device_get_tag_next(sd_device *device) {
assert_return(device, NULL);
(void) device_read_db(device);
if (device->tags_iterator_generation != device->tags_generation)
return NULL;
return set_iterate(device->tags, &device->tags_iterator);
}
_public_ const char *sd_device_get_devlink_first(sd_device *device) {
assert_return(device, NULL);
(void) device_read_db(device);
device->devlinks_iterator_generation = device->devlinks_generation;
device->devlinks_iterator = ITERATOR_FIRST;
return set_iterate(device->devlinks, &device->devlinks_iterator);
}
_public_ const char *sd_device_get_devlink_next(sd_device *device) {
assert_return(device, NULL);
(void) device_read_db(device);
if (device->devlinks_iterator_generation != device->devlinks_generation)
return NULL;
return set_iterate(device->devlinks, &device->devlinks_iterator);
}
static int device_properties_prepare(sd_device *device) {
int r;
assert(device);
r = device_read_uevent_file(device);
if (r < 0)
return r;
r = device_read_db(device);
if (r < 0)
return r;
if (device->property_devlinks_outdated) {
char *devlinks = NULL;
const char *devlink;
devlink = sd_device_get_devlink_first(device);
if (devlink)
devlinks = strdupa(devlink);
while ((devlink = sd_device_get_devlink_next(device)))
devlinks = strjoina(devlinks, " ", devlink);
r = device_add_property_internal(device, "DEVLINKS", devlinks);
if (r < 0)
return r;
device->property_devlinks_outdated = false;
}
if (device->property_tags_outdated) {
char *tags = NULL;
const char *tag;
tag = sd_device_get_tag_first(device);
if (tag)
tags = strjoina(":", tag);
while ((tag = sd_device_get_tag_next(device)))
tags = strjoina(tags, ":", tag);
tags = strjoina(tags, ":");
r = device_add_property_internal(device, "TAGS", tags);
if (r < 0)
return r;
device->property_tags_outdated = false;
}
return 0;
}
_public_ const char *sd_device_get_property_first(sd_device *device, const char **_value) {
const char *key;
const char *value;
int r;
assert_return(device, NULL);
r = device_properties_prepare(device);
if (r < 0)
return NULL;
device->properties_iterator_generation = device->properties_generation;
device->properties_iterator = ITERATOR_FIRST;
value = ordered_hashmap_iterate(device->properties, &device->properties_iterator, (const void**)&key);
if (_value)
*_value = value;
return key;
}
_public_ const char *sd_device_get_property_next(sd_device *device, const char **_value) {
const char *key;
const char *value;
int r;
assert_return(device, NULL);
r = device_properties_prepare(device);
if (r < 0)
return NULL;
if (device->properties_iterator_generation != device->properties_generation)
return NULL;
value = ordered_hashmap_iterate(device->properties, &device->properties_iterator, (const void**)&key);
if (_value)
*_value = value;
return key;
}
static int device_sysattrs_read_all(sd_device *device) {
_cleanup_closedir_ DIR *dir = NULL;
const char *syspath;
struct dirent *dent;
int r;
assert(device);
if (device->sysattrs_read)
return 0;
r = sd_device_get_syspath(device, &syspath);
if (r < 0)
return r;
dir = opendir(syspath);
if (!dir)
return -errno;
r = set_ensure_allocated(&device->sysattrs, &string_hash_ops);
if (r < 0)
return r;
for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
char *path;
struct stat statbuf;
/* only handle symlinks and regular files */
if (dent->d_type != DT_LNK && dent->d_type != DT_REG)
continue;
path = strjoina(syspath, "/", dent->d_name);
if (lstat(path, &statbuf) != 0)
continue;
if (!(statbuf.st_mode & S_IRUSR))
continue;
r = set_put_strdup(device->sysattrs, dent->d_name);
if (r < 0)
return r;
}
device->sysattrs_read = true;
return 0;
}
_public_ const char *sd_device_get_sysattr_first(sd_device *device) {
int r;
assert_return(device, NULL);
if (!device->sysattrs_read) {
r = device_sysattrs_read_all(device);
if (r < 0) {
errno = -r;
return NULL;
}
}
device->sysattrs_iterator = ITERATOR_FIRST;
return set_iterate(device->sysattrs, &device->sysattrs_iterator);
}
_public_ const char *sd_device_get_sysattr_next(sd_device *device) {
assert_return(device, NULL);
if (!device->sysattrs_read)
return NULL;
return set_iterate(device->sysattrs, &device->sysattrs_iterator);
}
_public_ int sd_device_has_tag(sd_device *device, const char *tag) {
assert_return(device, -EINVAL);
assert_return(tag, -EINVAL);
(void) device_read_db(device);
return !!set_contains(device->tags, tag);
}
_public_ int sd_device_get_property_value(sd_device *device, const char *key, const char **_value) {
char *value;
int r;
assert_return(device, -EINVAL);
assert_return(key, -EINVAL);
assert_return(_value, -EINVAL);
r = device_properties_prepare(device);
if (r < 0)
return r;
value = ordered_hashmap_get(device->properties, key);
if (!value)
return -ENOENT;
*_value = value;
return 0;
}
/* replaces the value if it already exists */
static int device_add_sysattr_value(sd_device *device, const char *_key, char *value) {
_cleanup_free_ char *key = NULL;
_cleanup_free_ char *value_old = NULL;
int r;
assert(device);
assert(_key);
r = hashmap_ensure_allocated(&device->sysattr_values, &string_hash_ops);
if (r < 0)
return r;
value_old = hashmap_remove2(device->sysattr_values, _key, (void **)&key);
if (!key) {
key = strdup(_key);
if (!key)
return -ENOMEM;
}
r = hashmap_put(device->sysattr_values, key, value);
if (r < 0)
return r;
key = NULL;
return 0;
}
static int device_get_sysattr_value(sd_device *device, const char *_key, const char **_value) {
const char *key = NULL, *value;
assert(device);
assert(_key);
value = hashmap_get2(device->sysattr_values, _key, (void **) &key);
if (!key)
return -ENOENT;
if (_value)
*_value = value;
return 0;
}
/* We cache all sysattr lookups. If an attribute does not exist, it is stored
* with a NULL value in the cache, otherwise the returned string is stored */
_public_ int sd_device_get_sysattr_value(sd_device *device, const char *sysattr, const char **_value) {
_cleanup_free_ char *value = NULL;
const char *syspath, *cached_value = NULL;
char *path;
struct stat statbuf;
int r;
assert_return(device, -EINVAL);
assert_return(sysattr, -EINVAL);
/* look for possibly already cached result */
r = device_get_sysattr_value(device, sysattr, &cached_value);
if (r != -ENOENT) {
if (r < 0)
return r;
if (!cached_value)
/* we looked up the sysattr before and it did not exist */
return -ENOENT;
if (_value)
*_value = cached_value;
return 0;
}
r = sd_device_get_syspath(device, &syspath);
if (r < 0)
return r;
path = strjoina(syspath, "/", sysattr);
r = lstat(path, &statbuf);
if (r < 0) {
/* remember that we could not access the sysattr */
r = device_add_sysattr_value(device, sysattr, NULL);
if (r < 0)
return r;
return -ENOENT;
} else if (S_ISLNK(statbuf.st_mode)) {
/* Some core links return only the last element of the target path,
* these are just values, the paths should not be exposed. */
if (STR_IN_SET(sysattr, "driver", "subsystem", "module")) {
r = readlink_value(path, &value);
if (r < 0)
return r;
} else
return -EINVAL;
} else if (S_ISDIR(statbuf.st_mode)) {
/* skip directories */
return -EINVAL;
} else if (!(statbuf.st_mode & S_IRUSR)) {
/* skip non-readable files */
return -EPERM;
} else {
size_t size;
/* read attribute value */
r = read_full_file(path, &value, &size);
if (r < 0)
return r;
/* drop trailing newlines */
while (size > 0 && value[--size] == '\n')
value[size] = '\0';
}
r = device_add_sysattr_value(device, sysattr, value);
if (r < 0)
return r;
*_value = value;
value = NULL;
return 0;
}
static void device_remove_sysattr_value(sd_device *device, const char *_key) {
_cleanup_free_ char *key = NULL;
_cleanup_free_ char *value = NULL;
assert(device);
assert(_key);
value = hashmap_remove2(device->sysattr_values, _key, (void **) &key);
return;
}
/* set the attribute and save it in the cache. If a NULL value is passed the
* attribute is cleared from the cache */
_public_ int sd_device_set_sysattr_value(sd_device *device, const char *sysattr, char *_value) {
_cleanup_close_ int fd = -1;
_cleanup_free_ char *value = NULL;
const char *syspath;
char *path;
struct stat statbuf;
size_t value_len = 0;
ssize_t size;
int r;
assert_return(device, -EINVAL);
assert_return(sysattr, -EINVAL);
if (!_value) {
device_remove_sysattr_value(device, sysattr);
return 0;
}
r = sd_device_get_syspath(device, &syspath);
if (r < 0)
return r;
path = strjoina(syspath, "/", sysattr);
r = lstat(path, &statbuf);
if (r < 0) {
value = strdup("");
if (!value)
return -ENOMEM;
r = device_add_sysattr_value(device, sysattr, value);
if (r < 0)
return r;
return -ENXIO;
}
if (S_ISLNK(statbuf.st_mode))
return -EINVAL;
/* skip directories */
if (S_ISDIR(statbuf.st_mode))
return -EISDIR;
/* skip non-readable files */
if ((statbuf.st_mode & S_IRUSR) == 0)
return -EACCES;
value_len = strlen(_value);
/* drop trailing newlines */
while (value_len > 0 && _value[value_len - 1] == '\n')
_value[--value_len] = '\0';
/* value length is limited to 4k */
if (value_len > 4096)
return -EINVAL;
fd = open(path, O_WRONLY | O_CLOEXEC);
if (fd < 0)
return -errno;
value = strdup(_value);
if (!value)
return -ENOMEM;
size = write(fd, value, value_len);
if (size < 0)
return -errno;
if ((size_t)size != value_len)
return -EIO;
r = device_add_sysattr_value(device, sysattr, value);
if (r < 0)
return r;
value = NULL;
return 0;
}