diff -Nru bolt-0.8/.editorconfig bolt-0.9.1/.editorconfig --- bolt-0.8/.editorconfig 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/.editorconfig 2020-12-01 11:26:01.000000000 +0000 @@ -14,3 +14,7 @@ [meson.build] indent_style = space indent_size = 2 + +[.md] +indent_style = space +indent_size = 2 diff -Nru bolt-0.8/.gitlab-ci.yml bolt-0.9.1/.gitlab-ci.yml --- bolt-0.8/.gitlab-ci.yml 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/.gitlab-ci.yml 2020-12-01 11:26:01.000000000 +0000 @@ -6,7 +6,12 @@ .build-template: &build script: - docker build -t bolt-$OS -f ./contrib/Dockerfile-$OS . - - docker run -e -t -v `pwd`:/src bolt-$OS ./contrib/docker-build.sh + - mkdir build-$OS + - docker run --cap-drop=dac_override -e -t -v `pwd`:/src -v `pwd`/build-$OS:/build bolt-$OS ./contrib/docker-build.sh + artifacts: + paths: + - build-$OS/meson-logs + expire_in: 1 week fedora: stage: build @@ -31,3 +36,16 @@ variables: OS: alpine <<: *build + +coverity: + stage: build + only: + - schedules + script: + - mkdir build-coverity + - docker build --build-arg ORG=gicmo --build-arg PROJECT=bolt --build-arg TOKEN=$COVERITY_TOKEN -t bolt-coverity -f ./contrib/Dockerfile-coverity . + - docker run --rm -e COVERITY_TOKEN=$COVERITY_TOKEN -e COVERITY_EMAIL=$COVERITY_EMAIL -t -v `pwd`:/src:Z -v `pwd`/build-coverity:/build:Z bolt-coverity ./contrib/coverity.sh + artifacts: + paths: + - build-coverity/cov-int/build-log.txt + expire_in: 1 week diff -Nru bolt-0.8/.pylintrc bolt-0.9.1/.pylintrc --- bolt-0.8/.pylintrc 1970-01-01 00:00:00.000000000 +0000 +++ bolt-0.9.1/.pylintrc 2020-12-01 11:26:01.000000000 +0000 @@ -0,0 +1,3 @@ +[MASTER] +disable=missing-docstring,invalid-name,duplicate-code,superfluous-parens,too-many-locals,attribute-defined-outside-init,too-many-arguments +max-line-length=120 diff -Nru bolt-0.8/.travis.yml bolt-0.9.1/.travis.yml --- bolt-0.8/.travis.yml 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/.travis.yml 2020-12-01 11:26:01.000000000 +0000 @@ -1,17 +1,20 @@ language: c sudo: required -dist: trusty +dist: bionic services: - docker env: + - OS=alpine - OS=debian - OS=arch - OS=fedora install: -- docker build -t bolt-$OS -f ./contrib/Dockerfile-$OS . + - docker build -t bolt-$OS -f ./contrib/Dockerfile-$OS . script: -- docker run -e -t -v `pwd`:/src bolt-$OS ./contrib/docker-build.sh + - mkdir build-$OS + - sudo chown $(id -u):root build-$OS + - docker run --cap-drop=dac_override -e -t -v `pwd`:/src -v `pwd`/build-$OS:/build bolt-$OS ./contrib/docker-build.sh diff -Nru bolt-0.8/BUGS.md bolt-0.9.1/BUGS.md --- bolt-0.8/BUGS.md 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/BUGS.md 2020-12-01 11:26:01.000000000 +0000 @@ -11,7 +11,7 @@ This should normally not be necessary, except when debugging the DBus layer of boltd (the verbose output is indeed very verbose). To replace the currently running daemon and run a new instance of it -in the forground, launch the daemon with `--replace`: +in the foreground, launch the daemon with `--replace`: boltd --replace diff -Nru bolt-0.8/CHANGELOG.md bolt-0.9.1/CHANGELOG.md --- bolt-0.8/CHANGELOG.md 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/CHANGELOG.md 2020-12-01 11:26:01.000000000 +0000 @@ -1,3 +1,47 @@ +Version 0.9.1 +------------- +_Unstable icy waters_ +Released: 2020-11-30 + +* Bug fixes for integrated thunderbolt controllers: + On Ice Lake, the Thunderbolt 3 i/o subsystem is fully integrated into the die. + As a side effect it does not have a DROM, which means the host udev device + does not have the device and vendor name and id attributes. + Additionally the `unique_id` of said host controller changes with every boot, + which breaks one of the fundamental assumptions in `boltd`. Therefore a number + of bug fixes were necessary to properly support this new architecture: + + - Don't store domains where uuids change across reboots [!220] + - Fixes for the journal and the domain's acl-log [!221] + - Version the store and use that to clean up stale domains once [!226, !231] + - Host identification for embedded thunderbolt controllers [!233] + +* Various other small bug fixes and memory leak fixes. + + +Version 0.9 +----------- +_Four comes after Three_ +Released: 2020-06-15 + +* New Features: + - Add 'Generation' attribute for the Manager [!197] + - Ability to change the policy of a stored device [!202] + - The BootACL Domain property is now writable [!184] + - Support for systemd's service watchdog [!185] + - Expose Link Speed sysfs attributes [!214] + +* Improvements: + - boltclt: show timestamps in 'monitor' call [!208] + - Persist the host device [!194] + +* Bug fixes: + - Fix a flaky test [!217, #161] + - Plug small memory leaks in error conditions [!217] + - Ignore spurious wakeup device uevents for probing [!209] + - Preserve keystate when updating devices [!192] + + Version 0.8 ----------- _I owe it to the MM U!_ @@ -80,7 +124,7 @@ - The device state is verified in `Device.Authorize` [!120] - Handle empty 'keys' sysfs device attribute [!129] - Properly adjust policies when enrolling already authorized devices [!136] - - Fix potential crasher when logging assertions `g_return_if_fail` [!121] + - Fix potential crash when logging assertions `g_return_if_fail` [!121] Version 0.5 diff -Nru bolt-0.8/HACKING.md bolt-0.9.1/HACKING.md --- bolt-0.8/HACKING.md 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/HACKING.md 2020-12-01 11:26:01.000000000 +0000 @@ -44,6 +44,23 @@ gcovr -r -e cli -s + +Address Sanitizer +================= + +The Address Sanitizer can be used to detect memory errors, i.e. +memory leaks and undefined behavior. Use `clang` for this, since +there the needed library (`libasan`) is dynamically loaded, +instead of pre-loaded via `LD_PRELOAD` when using `gcc`, which +conflicts with our pre-load needed for `umockdev`. + + env CC=clang meson -Db_sanitize=address,undefined . + ninja -C test + +NB: There might be a warning that `b_lundef` is needed as well. +It seems to work just fine right now without it. + + Static analysis =============== @@ -54,7 +71,7 @@ Coverity -------- -Bolt is registerd with [coverity][coverity]. To submit a local build, +Bolt is registered with [coverity][coverity]. To submit a local build, execute the following commands (the `cov-build` [build tool][cov-build] must be in `PATH`) from the source directory: diff -Nru bolt-0.8/README.md bolt-0.9.1/README.md --- bolt-0.8/README.md 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/README.md 2020-12-01 11:26:01.000000000 +0000 @@ -1,7 +1,7 @@ bolt ==== -Userspace system daemon to enable security levels for *Thunderbolt™ 3* +Userspace system daemon to enable security levels for *Thunderbolt™* on GNU/Linux®. Introduction @@ -13,7 +13,7 @@ Devices connected via Thunderbolt can be DMA masters and thus read system memory without interference of the operating system (or even -the CPU). Version 3 of the interface provides 5 different security +the CPU). Version 3 of the interface introduced 5 different security levels, in order to mitigate the aforementioned security risk that connected devices pose to the system. The security level is set by the system firmware. diff -Nru bolt-0.8/boltd/bolt-bouncer.c bolt-0.9.1/boltd/bolt-bouncer.c --- bolt-0.8/boltd/bolt-bouncer.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/boltd/bolt-bouncer.c 2020-12-01 11:26:01.000000000 +0000 @@ -218,6 +218,13 @@ { if (bolt_streq (name, "label")) action = "org.freedesktop.bolt.manage"; + else if (bolt_streq (name, "policy")) + action = "org.freedesktop.bolt.manage"; + } + else if (bolt_streq (type_name, "BoltDomain")) + { + if (bolt_streq (name, "bootacl")) + action = "org.freedesktop.bolt.manage"; } else if (bolt_streq (type_name, "BoltManager")) { diff -Nru bolt-0.8/boltd/bolt-daemon.c bolt-0.9.1/boltd/bolt-daemon.c --- bolt-0.8/boltd/bolt-daemon.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/boltd/bolt-daemon.c 2020-12-01 11:26:01.000000000 +0000 @@ -64,7 +64,7 @@ if (source == NULL) { - bolt_warn (LOG_TOPIC ("signal"), "failed installing SIGTERM hanlder: %s", + bolt_warn (LOG_TOPIC ("signal"), "failed installing SIGTERM handler: %s", g_strerror (errno)); return; } diff -Nru bolt-0.8/boltd/bolt-device.c bolt-0.9.1/boltd/bolt-device.c --- bolt-0.8/boltd/bolt-device.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/boltd/bolt-device.c 2020-12-01 11:26:01.000000000 +0000 @@ -49,6 +49,11 @@ const GValue *value, GError **error); +static gboolean handle_set_policy (BoltExported *obj, + const char *name, + const GValue *value, + GError **error); + /* dbus method calls */ static GVariant * handle_authorize (BoltExported *object, GVariant *params, @@ -64,6 +69,7 @@ char *uid; char *name; char *vendor; + guint gen; BoltDeviceType type; BoltStatus status; @@ -77,6 +83,8 @@ guint64 conntime; guint64 authtime; + BoltLinkSpeed linkspeed; + /* when device is stored */ BoltStore *store; BoltPolicy policy; @@ -100,6 +108,7 @@ PROP_UID, PROP_NAME, PROP_VENDOR, + PROP_GEN, PROP_TYPE, PROP_STATUS, @@ -109,6 +118,7 @@ PROP_DOMAIN, PROP_CONNTIME, PROP_AUTHTIME, + PROP_LINKSPEED, PROP_STORED, PROP_POLICY, @@ -204,6 +214,10 @@ g_value_set_enum (value, dev->status); break; + case PROP_GEN: + g_value_set_uint (value, dev->gen); + break; + case PROP_AUTHFLAGS: g_value_set_flags (value, dev->aflags); break; @@ -228,6 +242,10 @@ g_value_set_uint64 (value, dev->authtime); break; + case PROP_LINKSPEED: + g_value_set_boxed (value, &dev->linkspeed); + break; + case PROP_STORED: g_value_set_boolean (value, dev->store != NULL); break; @@ -283,6 +301,10 @@ dev->vendor = g_value_dup_string (value); break; + case PROP_GEN: + dev->gen = g_value_get_uint (value); + break; + case PROP_TYPE: dev->type = g_value_get_enum (value); break; @@ -321,6 +343,13 @@ dev->authtime = g_value_get_uint64 (value); break; + case PROP_LINKSPEED: + { + BoltLinkSpeed *li = g_value_get_boxed (value); + dev->linkspeed = *li; + } + break; + case PROP_POLICY: dev->policy = g_value_get_enum (value); break; @@ -397,6 +426,14 @@ G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + props[PROP_GEN] = + g_param_spec_uint ("generation", + "Generation", + NULL, + 0, G_MAXUINT, 0, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + props[PROP_TYPE] = g_param_spec_enum ("type", "Type", NULL, @@ -422,6 +459,13 @@ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + props[PROP_LINKSPEED] = + g_param_spec_boxed ("linkspeed", + "LinkSpeed", NULL, + BOLT_TYPE_LINK_SPEED, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + props[PROP_PARENT] = g_param_spec_string ("parent", "Parent", NULL, @@ -524,10 +568,20 @@ props[PROP_LABEL], handle_set_label); + bolt_exported_class_property_setter (exported_class, + props[PROP_POLICY], + handle_set_policy); + bolt_exported_class_export_method (exported_class, "Authorize", handle_authorize); + bolt_exported_class_property_wireconv (exported_class, + props[PROP_LINKSPEED], + "linkspeed-as-variant", + bolt_link_speed_to_wire, + bolt_link_speed_from_wire); + } /* internal methods */ @@ -550,28 +604,6 @@ g_object_notify_by_pspec (G_OBJECT (dev), props[PROP_STATUS]); } -static const char * -read_sysattr_name (struct udev_device *udev, const char *attr, GError **error) -{ - g_autofree char *s = NULL; - const char *v; - - s = g_strdup_printf ("%s_name", attr); - v = udev_device_get_sysattr_value (udev, s); - - if (v != NULL) - return v; - - v = udev_device_get_sysattr_value (udev, attr); - - if (v == NULL) - g_set_error (error, - BOLT_ERROR, BOLT_ERROR_UDEV, - "failed to get sysfs attr: %s", attr); - - return v; -} - static BoltStatus bolt_status_from_info (BoltDevInfo *info) { @@ -975,6 +1007,50 @@ return ok; } +static gboolean +handle_set_policy (BoltExported *obj, + const char *name, + const GValue *value, + GError **error) +{ + BoltDevice *dev = BOLT_DEVICE (obj); + BoltPolicy before = dev->policy; + BoltPolicy policy = g_value_get_enum (value); + gboolean ok; + + if (policy == BOLT_POLICY_UNKNOWN || + policy == BOLT_POLICY_DEFAULT) + { + g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, + "invalid policy (%d)", policy); + return FALSE; + } + else if (dev->store == NULL) + { + g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, + "device is not stored"); + return FALSE; + } + + if (policy == dev->policy) + return TRUE; + + ok = bolt_store_put_device (dev->store, dev, policy, NULL, error); + + if (!ok) + { + bolt_warn_err (*error, LOG_DEV (dev), "failed to store device"); + dev->policy = before; + } + + if (policy == BOLT_POLICY_AUTO) + bolt_domain_foreach (dev->domain, bolt_bootacl_add, dev); + else if (policy == BOLT_POLICY_MANUAL) + bolt_domain_foreach (dev->domain, bolt_bootacl_del, dev); + + return ok; +} + /* dbus methods */ static void @@ -1096,10 +1172,9 @@ BoltDomain *domain, GError **error) { - BoltDevInfo info; + g_auto(BoltIdent) id = BOLT_IDENT_INIT; const char *uid; - const char *name; - const char *vendor; + BoltDevInfo info; BoltStatus status; BoltAuthFlags aflags; BoltDeviceType type; @@ -1111,20 +1186,8 @@ g_return_val_if_fail (domain != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); - uid = udev_device_get_sysattr_value (udev, "unique_id"); + uid = bolt_sysfs_device_get_unique_id (udev, error); if (uid == NULL) - { - g_set_error (error, BOLT_ERROR, BOLT_ERROR_UDEV, - "could not get unique_id for udev"); - return NULL; - } - - name = read_sysattr_name (udev, "device", error); - if (name == NULL) - return NULL; - - vendor = read_sysattr_name (udev, "vendor", error); - if (vendor == NULL) return NULL; ok = bolt_sysfs_info_for_device (udev, TRUE, &info, error); @@ -1136,6 +1199,14 @@ else type = BOLT_DEVICE_PERIPHERAL; + if (type == BOLT_DEVICE_HOST) + ok = bolt_sysfs_host_ident (udev, &id, error); + else + ok = bolt_sysfs_device_ident (udev, &id, error); + + if (!ok) + return NULL; + ct = (guint64) info.ctim; status = bolt_status_from_info (&info); @@ -1144,9 +1215,10 @@ dev = g_object_new (BOLT_TYPE_DEVICE, "uid", uid, - "name", name, - "vendor", vendor, + "name", id.name, + "vendor", id.vendor, "type", type, + "generation", info.generation, "status", status, "authflags", aflags, "sysfs-path", info.syspath, @@ -1154,6 +1226,7 @@ "parent", info.parent, "conntime", ct, "authtime", at, + "linkspeed", &info.linkspeed, NULL); return dev; @@ -1231,6 +1304,7 @@ BoltAuthFlags aflags; BoltDevInfo info; BoltStatus status; + gboolean change; gboolean ok; guint64 ct, at; @@ -1239,8 +1313,11 @@ ok = bolt_sysfs_info_for_device (udev, TRUE, &info, &err); if (!ok) - bolt_warn_err (err, LOG_DEV (dev), LOG_TOPIC ("udev"), - "failed to get device info"); + { + bolt_warn_err (err, LOG_DEV (dev), LOG_TOPIC ("udev"), + "failed to get device info"); + g_clear_error (&err); + } status = bolt_status_from_info (&info); aflags = bolt_auth_flags_from_info (&info, domain, NULL); @@ -1248,7 +1325,10 @@ ct = (guint64) info.ctim; at = bolt_status_is_authorized (status) ? ct : 0; + change = info.generation != dev->gen; + g_object_set (G_OBJECT (dev), + "generation", info.generation, "parent", info.parent, "sysfs-path", info.syspath, "domain", domain, @@ -1256,10 +1336,29 @@ "authflags", aflags, "conntime", ct, "authtime", at, + "linkspeed", &info.linkspeed, NULL); bolt_info (LOG_DEV (dev), "parent is %.13s...", dev->parent); + if (change && dev->store) + { + bolt_info (LOG_DEV (dev), LOG_TOPIC ("store"), + "updating device"); + + ok = bolt_store_put_device (dev->store, + dev, + BOLT_POLICY_DEFAULT, + NULL, + &err); + if (!ok) + { + bolt_warn_err (err, LOG_DEV (dev), LOG_TOPIC ("store"), + "failed to update device"); + g_clear_error (&err); + } + } + bolt_store_put_times (dev->store, dev->uid, NULL, "conntime", ct, "authtime", at, @@ -1310,6 +1409,7 @@ struct udev_device *udev) { g_autoptr(GError) err = NULL; + BoltLinkSpeed linkspeed; BoltAuthFlags aflags; BoltDevInfo info; BoltStatus status; @@ -1360,11 +1460,26 @@ device_set_status_internal (dev, status, TRUE); + bolt_sysfs_read_link_speed (udev, &linkspeed); + if (!bolt_link_speed_equal (&dev->linkspeed, &linkspeed)) + { + dev->linkspeed = linkspeed; + g_object_notify_by_pspec (G_OBJECT (dev), props[PROP_LINKSPEED]); + } + g_object_thaw_notify (G_OBJECT (dev)); return status; } +BoltDomain * +bolt_device_get_domain (BoltDevice *dev) +{ + g_return_val_if_fail (BOLT_IS_DEVICE (dev), NULL); + + return dev->domain; +} + BoltKeyState bolt_device_get_keystate (BoltDevice *dev) { @@ -1477,6 +1592,14 @@ return dev->vendor; } +guint +bolt_device_get_generation (BoltDevice *dev) +{ + g_return_val_if_fail (BOLT_IS_DEVICE (dev), 0); + + return dev->gen; +} + BoltDeviceType bolt_device_get_device_type (BoltDevice *dev) { @@ -1485,6 +1608,14 @@ return dev->type; } +gboolean +bolt_device_is_host (BoltDevice *dev) +{ + g_return_val_if_fail (BOLT_IS_DEVICE (dev), BOLT_DEVICE_UNKNOWN_TYPE); + + return dev->type == BOLT_DEVICE_HOST; +} + const char * bolt_device_get_label (BoltDevice *dev) { @@ -1509,7 +1640,7 @@ return dev->conntime; } -gint64 +guint64 bolt_device_get_storetime (BoltDevice *dev) { g_return_val_if_fail (BOLT_IS_DEVICE (dev), 0); @@ -1592,3 +1723,62 @@ *key = k; return TRUE; } + +/* bolt_domain_foreach helpers */ +void +bolt_bootacl_add (gpointer domain, + gpointer device) +{ + g_autoptr(GError) err = NULL; + BoltDomain *dom = BOLT_DOMAIN (domain); + BoltDevice *dev = BOLT_DEVICE (device); + const char *uid = bolt_device_get_uid (dev); + gboolean ok; + + bolt_info (LOG_TOPIC ("bootacl"), + LOG_DOM (dom), LOG_DEV (dev), + "adding %.17s... ", uid); + + if (!bolt_domain_supports_bootacl (dom)) + return; + + if (bolt_domain_bootacl_contains (dom, uid)) + return; + + ok = bolt_domain_bootacl_add (dom, uid, &err); + if (!ok) + { + bolt_warn_err (err, LOG_TOPIC ("bootacl"), + LOG_DOM (dom), LOG_DEV_UID (uid), + "could not add device"); + } +} + +void +bolt_bootacl_del (gpointer domain, + gpointer device) +{ + g_autoptr(GError) err = NULL; + BoltDomain *dom = BOLT_DOMAIN (domain); + BoltDevice *dev = BOLT_DEVICE (device); + const char *uid = bolt_device_get_uid (dev); + gboolean ok; + + bolt_info (LOG_TOPIC ("bootacl"), + LOG_DOM (dom), LOG_DEV (dev), + "removing %.17s...", uid); + + if (!bolt_domain_supports_bootacl (dom)) + return; + + if (!bolt_domain_bootacl_contains (dom, uid)) + return; + + ok = bolt_domain_bootacl_del (dom, uid, &err); + if (!ok) + { + bolt_warn_err (err, LOG_TOPIC ("bootacl"), + LOG_DEV_UID (uid), LOG_DOM (dom), + "could not remove device"); + } +} diff -Nru bolt-0.8/boltd/bolt-device.h bolt-0.9.1/boltd/bolt-device.h --- bolt-0.8/boltd/bolt-device.h 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/boltd/bolt-device.h 2020-12-01 11:26:01.000000000 +0000 @@ -66,6 +66,8 @@ GAsyncReadyCallback callback, gpointer user_data); +BoltDomain * bolt_device_get_domain (BoltDevice *dev); + BoltKeyState bolt_device_get_keystate (BoltDevice *dev); const char * bolt_device_get_name (BoltDevice *dev); @@ -90,13 +92,17 @@ BoltDeviceType bolt_device_get_device_type (BoltDevice *dev); +gboolean bolt_device_is_host (BoltDevice *dev); + const char * bolt_device_get_label (BoltDevice *dev); guint64 bolt_device_get_authtime (BoltDevice *dev); guint64 bolt_device_get_conntime (BoltDevice *dev); -gint64 bolt_device_get_storetime (BoltDevice *dev); +guint64 bolt_device_get_storetime (BoltDevice *dev); + +guint bolt_device_get_generation (BoltDevice *dev); gboolean bolt_device_has_iommu (BoltDevice *dev); @@ -114,4 +120,11 @@ BoltKey **key, GError **error); +/* bolt_domain_foreach helpers */ +void bolt_bootacl_add (gpointer domain, + gpointer device); + +void bolt_bootacl_del (gpointer domain, + gpointer device); + G_END_DECLS diff -Nru bolt-0.8/boltd/bolt-domain.c bolt-0.9.1/boltd/bolt-domain.c --- bolt-0.8/boltd/bolt-domain.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/boltd/bolt-domain.c 2020-12-01 11:26:01.000000000 +0000 @@ -34,7 +34,17 @@ #include +static void bolt_domain_store_setter (BoltDomain *domain, + const GValue *val); + static void bolt_domain_bootacl_open_log (BoltDomain *domain); +static void bolt_domain_bootacl_remove_log (BoltDomain *domain); + +/* dbus property setter */ +static gboolean handle_set_bootacl (BoltExported *obj, + const char *name, + const GValue *value, + GError **error); struct _BoltDomain { @@ -169,17 +179,7 @@ switch (prop_id) { case PROP_STORE: - if (dom->store == g_value_get_object (value)) - return; - - g_clear_object (&dom->store); - dom->store = g_value_dup_object (value); - - if (dom->store) - bolt_domain_bootacl_open_log (dom); - else - g_clear_object (&dom->acllog); - + bolt_domain_store_setter (dom, value); break; case PROP_UID: @@ -297,6 +297,10 @@ PROP_LAST, props); + bolt_exported_class_property_setter (exported_class, + props[PROP_BOOTACL], + handle_set_bootacl); + signals[SIGNAL_BOOTACL_CHANGED] = g_signal_new ("bootacl-changed", BOLT_TYPE_DOMAIN, @@ -328,6 +332,30 @@ /* */ static void +bolt_domain_store_setter (BoltDomain *domain, + const GValue *value) +{ + BoltStore *store; + + store = g_value_get_object (value); + + if (domain->store == store) + return; + + if (domain->store) + { + bolt_domain_bootacl_remove_log (domain); + g_clear_object (&domain->store); + } + + if (store != NULL) + { + domain->store = g_object_ref (store); + bolt_domain_bootacl_open_log (domain); + } +} + +static void bolt_domain_bootacl_open_log (BoltDomain *domain) { g_autoptr(GError) err = NULL; @@ -346,6 +374,33 @@ domain->acllog = log; } +static void +bolt_domain_bootacl_remove_log (BoltDomain *domain) +{ + g_autoptr(GError) err = NULL; + gboolean ok; + + if (domain->acllog == NULL) + return; + + g_clear_object (&domain->acllog); + + g_return_if_fail (domain->store != NULL); + + bolt_info (LOG_TOPIC ("bootacl"), LOG_DOM (domain), + "removing journal"); + + ok = bolt_store_del_journal (domain->store, + "bootacl", + domain->uid, + &err); + + if (!ok) + bolt_warn_err (err, LOG_TOPIC ("bootacl"), LOG_DOM (domain), + "could not remove journal"); + +} + static gboolean bolt_domain_bootacl_can_update (BoltDomain *domain, GError **error) @@ -716,7 +771,8 @@ } opath = bolt_exported_get_object_path (exported); - bolt_info (LOG_TOPIC ("dbus"), "exported domain at %s", opath); + bolt_info (LOG_TOPIC ("dbus"), LOG_DOM (domain), + "exported domain at %s", opath); } void @@ -829,6 +885,27 @@ } gboolean +bolt_domain_can_delete (BoltDomain *domain, + GError **error) +{ + BoltJournal *log; + + g_return_val_if_fail (BOLT_IS_DOMAIN (domain), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + log = domain->acllog; + + if (log && !bolt_journal_is_fresh (log)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_EMPTY, + "boot acl journal is not empty"); + return FALSE; + } + + return TRUE; +} + +gboolean bolt_domain_supports_bootacl (BoltDomain *domain) { g_return_val_if_fail (BOLT_IS_DOMAIN (domain), FALSE); @@ -1015,7 +1092,6 @@ guint ours, theirs; g_return_val_if_fail (BOLT_IS_DOMAIN (domain), FALSE); - g_return_val_if_fail (acl != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); ok = bolt_domain_bootacl_can_update (domain, error); @@ -1025,7 +1101,7 @@ online = domain->syspath != NULL; log = domain->acllog; - theirs = g_strv_length (acl); + theirs = bolt_gstrv_length0 (acl); ours = g_strv_length (domain->bootacl); if (ours != theirs) @@ -1050,7 +1126,7 @@ ok = bolt_journal_put_diff (log, diff, error); if (!ok) - return TRUE; + return FALSE; tmp = g_strdupv (acl); bolt_domain_bootacl_update (domain, &tmp, diff); @@ -1232,3 +1308,27 @@ *list = iter; } + +/* dbus property setter */ +static gboolean +handle_set_bootacl (BoltExported *obj, + const char *name, + const GValue *value, + GError **error) +{ + BoltDomain *domain = BOLT_DOMAIN (obj); + GStrv acl; + gboolean ok; + + acl = (GStrv) g_value_get_boxed (value); + + if (!bolt_uuidv_check (acl, TRUE, error)) + return FALSE; + + /* does check if we can actually update the boot-acl, + * i.e. calls bolt_domain_bootacl_can_update */ + ok = bolt_domain_bootacl_set (domain, acl, error); + + // maybe adjust the G_IO_ERROR to a G_DBUS_ERROR ? + return ok; +} diff -Nru bolt-0.8/boltd/bolt-domain.h bolt-0.9.1/boltd/bolt-domain.h --- bolt-0.8/boltd/bolt-domain.h 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/boltd/bolt-domain.h 2020-12-01 11:26:01.000000000 +0000 @@ -66,6 +66,9 @@ void bolt_domain_update_from_udev (BoltDomain *domain, struct udev_device *udev); +gboolean bolt_domain_can_delete (BoltDomain *domain, + GError **error); + /* boot acl related functions */ gboolean bolt_domain_supports_bootacl (BoltDomain *domain); diff -Nru bolt-0.8/boltd/bolt-exported.c bolt-0.9.1/boltd/bolt-exported.c --- bolt-0.8/boltd/bolt-exported.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/boltd/bolt-exported.c 2020-12-01 11:26:01.000000000 +0000 @@ -956,6 +956,44 @@ } void +bolt_exported_class_property_wireconv (BoltExportedClass *klass, + GParamSpec *spec, + const char *custom_id, + BoltConvToWire to_wire, + BoltConvFromWire from_wire) +{ + BoltExportedProp *prop; + BoltWireConv *conv; + const char *nick; + + g_return_if_fail (BOLT_IS_EXPORTED_CLASS (klass)); + g_return_if_fail (G_IS_PARAM_SPEC (spec)); + + nick = g_param_spec_get_nick (spec); + + prop = g_hash_table_lookup (klass->priv->properties, nick); + + if (prop == NULL) + { + bolt_bug (LOG_TOPIC ("dbus"), "unknown property: %s", nick); + return; + } + + conv = bolt_wire_conv_custom (prop->signature, + prop->spec, + custom_id, + to_wire, + from_wire); + + bolt_wire_conv_unref (prop->conv); + prop->conv = conv; /* transfer the reference */ + + bolt_debug (LOG_TOPIC ("dbus"), "+adjusted prop: wireconv: %s [%s]", + prop->name_bus, + bolt_wire_conv_describe (prop->conv)); +} + +void bolt_exported_class_export_method (BoltExportedClass *klass, const char *name, BoltExportedMethodHandler handler) diff -Nru bolt-0.8/boltd/bolt-exported.h bolt-0.9.1/boltd/bolt-exported.h --- bolt-0.8/boltd/bolt-exported.h 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/boltd/bolt-exported.h 2020-12-01 11:26:01.000000000 +0000 @@ -20,6 +20,8 @@ #pragma once +#include + #include G_BEGIN_DECLS @@ -87,6 +89,12 @@ GParamSpec *spec, BoltExportedSetter setter); +void bolt_exported_class_property_wireconv (BoltExportedClass *klass, + GParamSpec *spec, + const char *custom_id, + BoltConvToWire to_wire, + BoltConvFromWire from_wire); + void bolt_exported_class_export_method (BoltExportedClass *klass, const char *name, BoltExportedMethodHandler handler); diff -Nru bolt-0.8/boltd/bolt-guard.c bolt-0.9.1/boltd/bolt-guard.c --- bolt-0.8/boltd/bolt-guard.c 1970-01-01 00:00:00.000000000 +0000 +++ bolt-0.9.1/boltd/bolt-guard.c 2020-12-01 11:26:01.000000000 +0000 @@ -0,0 +1,633 @@ +/* + * Copyright © 2020 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Christian J. Kellner + */ + +#include "config.h" + +#include "bolt-error.h" +#include "bolt-guard.h" +#include "bolt-io.h" +#include "bolt-log.h" +#include "bolt-str.h" +#include "bolt-unix.h" + + +typedef enum GuardState { + GUARD_STATE_ACTIVE = 0, + GUARD_STATE_RELEASED = 1 +} GuardState; + +/* BoltGuard */ +static void bolt_guard_remove (BoltGuard *guard); + +struct _BoltGuard +{ + GObject object; + + /* book-keeping */ + GuardState state; + char *path; + + char *fifo; + guint watch; + + /* properties */ + char *id; + char *who; + pid_t pid; +}; + +enum { + PROP_GUARD_0, + + PROP_PATH, + PROP_FIFO, + + PROP_ID, + PROP_WHO, + PROP_PID, + + + PROP_GUARD_LAST +}; + +static GParamSpec *guard_props[PROP_GUARD_LAST] = { NULL, }; + +enum { + GUARD_SIGNAL_RELEASED, + SIGNAL_LAST +}; + +static guint guard_signals[SIGNAL_LAST] = {0}; + +G_DEFINE_TYPE (BoltGuard, + bolt_guard, + G_TYPE_OBJECT); + +static void +bolt_guard_dispose (GObject *object) +{ + BoltGuard *guard = BOLT_GUARD (object); + + /* remove our state file */ + bolt_guard_remove (guard); + + if (guard->state != GUARD_STATE_RELEASED) + { + /* signal to clients to that we have been released. + * NB: we must be intact for method call */ + guard->state = GUARD_STATE_RELEASED; + g_signal_emit (object, guard_signals[GUARD_SIGNAL_RELEASED], 0); + } + + G_OBJECT_CLASS (bolt_guard_parent_class)->dispose (object); +} + +static void +bolt_guard_finalize (GObject *object) +{ + BoltGuard *guard = BOLT_GUARD (object); + + if (guard->watch) + g_source_remove (guard->watch); + + g_clear_pointer (&guard->path, g_free); + g_clear_pointer (&guard->fifo, g_free); + g_clear_pointer (&guard->who, g_free); + g_clear_pointer (&guard->id, g_free); + + G_OBJECT_CLASS (bolt_guard_parent_class)->finalize (object); +} + +static void +bolt_guard_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + BoltGuard *guard = BOLT_GUARD (object); + + switch (prop_id) + { + case PROP_PATH: + g_value_set_string (value, guard->path); + break; + + case PROP_FIFO: + g_value_set_string (value, guard->fifo); + break; + + case PROP_ID: + g_value_set_string (value, guard->id); + break; + + case PROP_WHO: + g_value_set_string (value, guard->who); + break; + + case PROP_PID: + g_value_set_ulong (value, guard->pid); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + + +static void +bolt_guard_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + BoltGuard *guard = BOLT_GUARD (object); + + switch (prop_id) + { + case PROP_PATH: + guard->path = g_value_dup_string (value); + break; + + case PROP_FIFO: + guard->fifo = g_value_dup_string (value); + break; + + case PROP_ID: + guard->id = g_value_dup_string (value); + break; + + case PROP_WHO: + guard->who = g_value_dup_string (value); + break; + + case PROP_PID: + guard->pid = g_value_get_ulong (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + + +static void +bolt_guard_init (BoltGuard *guard) +{ + guard->state = GUARD_STATE_ACTIVE; +} + +static void +bolt_guard_class_init (BoltGuardClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->dispose = bolt_guard_dispose; + gobject_class->finalize = bolt_guard_finalize; + gobject_class->get_property = bolt_guard_get_property; + gobject_class->set_property = bolt_guard_set_property; + + guard_props[PROP_PATH] = + g_param_spec_string ("path", + NULL, NULL, + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + guard_props[PROP_FIFO] = + g_param_spec_string ("fifo", + NULL, NULL, + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + + guard_props[PROP_ID] = + g_param_spec_string ("id", + NULL, NULL, + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + guard_props[PROP_WHO] = + g_param_spec_string ("who", + NULL, NULL, + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + guard_props[PROP_PID] = + g_param_spec_ulong ("pid", + NULL, NULL, + 0, G_MAXULONG, 0, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, + PROP_GUARD_LAST, + guard_props); + + guard_signals[GUARD_SIGNAL_RELEASED] = + g_signal_new ("released", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + NULL, + G_TYPE_NONE, + 0); +} + +static void +bolt_guard_remove (BoltGuard *guard) +{ + g_autoptr(GError) err = NULL; + gboolean ok; + + /* we are not saved */ + if (guard->path == NULL) + return; + + /* */ + if (guard->fifo != NULL) + { + bolt_debug (LOG_TOPIC ("guard"), + "not removing guard '%s' with active fifo", + guard->id); + return; + } + + ok = bolt_unlink (guard->path, &err); + if (!ok) + { + bolt_warn_err (err, LOG_TOPIC ("guard"), + "Could not remove power guard: '%s' @ %s", + guard->id, guard->path); + return; + } + + g_clear_pointer (&guard->path, g_free); + g_object_notify_by_pspec (G_OBJECT (guard), + guard_props[PROP_PATH]); +} + +static void +bolt_guard_fifo_cleanup (BoltGuard *guard) +{ + g_autoptr(GError) err = NULL; + gboolean ok; + + if (guard->fifo == NULL) + return; + + ok = bolt_unlink (guard->fifo, &err); + if (!ok) + { + bolt_warn_err (err, LOG_TOPIC ("guard"), + "Could not remove FIFO for power guard: '%s' @ %s", + guard->id, guard->fifo); + } + + g_clear_pointer (&guard->fifo, g_free); + g_object_notify_by_pspec (G_OBJECT (guard), guard_props[PROP_FIFO]); +} + +static gboolean +bolt_guard_mkfifo (BoltGuard *guard, + GError **error) +{ + g_autoptr(GError) err = NULL; + int r; + + g_return_val_if_fail (BOLT_IS_GUARD (guard), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (guard->fifo == NULL) + guard->fifo = g_strdup_printf ("%s.fifo", guard->path); + + r = bolt_mkfifo (guard->fifo, 0600, &err); + if (r == -1 && !bolt_err_exists (err)) + return bolt_error_propagate (error, &err); + + g_object_notify_by_pspec (G_OBJECT (guard), + guard_props[PROP_FIFO]); + + return TRUE; +} + +static gboolean +guard_has_event (GIOChannel *source, + GIOCondition condition, + gpointer data) +{ + BoltGuard *guard = data; + + bolt_info (LOG_TOPIC ("guard"), "got event for guard '%s' (%x)", + guard->id, (guint) condition); + guard->watch = 0; + return FALSE; +} + +static void +guard_watch_release (gpointer data) +{ + BoltGuard *guard = data; + + if (guard->state == GUARD_STATE_RELEASED) + /* if we are already released, the FIFO was kept + * alive on purpose, so do nothing here */ + return; + + if (guard->fifo == NULL) + { + bolt_bug (LOG_TOPIC ("guard"), "FIFO event but no FIFO"); + return; + } + + bolt_guard_fifo_cleanup (guard); + + bolt_debug (LOG_TOPIC ("guard"), + "released watch reference for guard '%s'", + guard->id); + + g_object_unref (guard); +} + +int +bolt_guard_monitor (BoltGuard *guard, + GError **error) +{ + g_autoptr(GIOChannel) ch = NULL; + gboolean ok; + int fd; + + g_return_val_if_fail (BOLT_IS_GUARD (guard), -1); + g_return_val_if_fail (error == NULL || *error == NULL, -1); + + ok = bolt_guard_mkfifo (guard, error); + if (!ok) + return -1; + + /* reader */ + fd = bolt_open (guard->fifo, O_RDONLY | O_CLOEXEC | O_NONBLOCK, 0, error); + if (fd == -1) + return -1; + + ch = g_io_channel_unix_new (fd); + + g_io_channel_set_close_on_unref (ch, TRUE); + g_io_channel_set_encoding (ch, NULL, NULL); + g_io_channel_set_buffered (ch, FALSE); + g_io_channel_set_flags (ch, G_IO_FLAG_NONBLOCK, NULL); + fd = -1; /* the GIOChannel owns the fd, via _close_on_unref () */ + (void) fd; /* The above is an intentional dead store */ + + /* writer */ + fd = bolt_open (guard->fifo, O_WRONLY | O_CLOEXEC | O_NONBLOCK, 0, error); + if (fd == -1) + return -1; + + /* NB: we take a ref to the guard here */ + guard->watch = g_io_add_watch_full (ch, + G_PRIORITY_DEFAULT, + G_IO_HUP | G_IO_ERR, + guard_has_event, + g_object_ref (guard), + guard_watch_release); + + return fd; +} + +const char * +bolt_guard_get_id (BoltGuard *guard) +{ + g_return_val_if_fail (BOLT_IS_GUARD (guard), NULL); + + return guard->id; +} + +const char * +bolt_guard_get_who (BoltGuard *guard) +{ + g_return_val_if_fail (BOLT_IS_GUARD (guard), NULL); + + return guard->who; +} + +guint +bolt_guard_get_pid (BoltGuard *guard) +{ + g_return_val_if_fail (BOLT_IS_GUARD (guard), 0); + + return (guint) guard->pid; +} + +const char * +bolt_guard_get_path (BoltGuard *guard) +{ + g_return_val_if_fail (BOLT_IS_GUARD (guard), NULL); + + return guard->path; +} + +const char * +bolt_guard_get_fifo (BoltGuard *guard) +{ + g_return_val_if_fail (BOLT_IS_GUARD (guard), NULL); + + return guard->fifo; +} + +GPtrArray * +bolt_guard_recover (const char *statedir, + GError **error) +{ + + g_autoptr(GError) err = NULL; + g_autoptr(GDir) dir = NULL; + g_autoptr(GPtrArray) guards = NULL; + const char *name; + + g_return_val_if_fail (statedir != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + dir = g_dir_open (statedir, 0, error); + if (dir == NULL) + return NULL; + + guards = g_ptr_array_new_with_free_func (g_object_unref); + + while ((name = g_dir_read_name (dir)) != NULL) + { + g_autoptr(BoltGuard) guard = NULL; + int fd; + + if (!g_str_has_suffix (name, ".guard")) + continue; + + guard = bolt_guard_load (statedir, name, &err); + + if (guard == NULL) + { + bolt_warn_err (err, LOG_TOPIC ("guard"), + "could not load guard '%s'", name); + g_clear_error (&err); + continue; + } + + /* internal guards are discarded */ + if (guard->fifo == NULL) + { + bolt_info (LOG_TOPIC ("guard"), + "ignoring guard '%s' for '%s': no fifo", + guard->id, guard->who); + continue; + } + else if (!bolt_pid_is_alive (guard->pid)) + { + bolt_info (LOG_TOPIC ("guard"), + "ignoring guard '%s' for '%s': process dead", + guard->id, guard->who); + bolt_guard_fifo_cleanup (guard); + continue; + } + + fd = bolt_guard_monitor (guard, &err); + + if (fd < 0) + { + bolt_warn_err (err, "could not monitor guard '%d'", + guard->id); + g_clear_error (&err); + continue; + } + + /* close the write side */ + (void) close (fd); + + /* monitoring adds a reference that we don't want */ + g_object_unref (guard); + + g_ptr_array_add (guards, guard); + guard = NULL; + } + + return g_steal_pointer (&guards); +} + +gboolean +bolt_guard_save (BoltGuard *guard, + GFile *guarddir, + GError **error) +{ + g_autoptr(GFile) guardfile = NULL; + g_autoptr(GKeyFile) kf = NULL; + g_autofree char *path = NULL; + g_autofree char *name = NULL; + gboolean ok; + + g_return_val_if_fail (guard->path == NULL, FALSE); + + name = g_strdup_printf ("%s.guard", guard->id); + guardfile = g_file_get_child (guarddir, name); + path = g_file_get_path (guardfile); + + kf = g_key_file_new (); + + g_key_file_set_string (kf, "guard", "id", guard->id); + g_key_file_set_string (kf, "guard", "who", guard->who); + g_key_file_set_uint64 (kf, "guard", "pid", guard->pid); + + ok = g_key_file_save_to_file (kf, path, error); + + if (ok) + { + guard->path = g_steal_pointer (&path); + g_object_notify_by_pspec (G_OBJECT (guard), + guard_props[PROP_PATH]); + } + + return ok; +} + +BoltGuard * +bolt_guard_load (const char *statedir, + const char *name, + GError **error) +{ + g_autoptr(GError) err = NULL; + g_autoptr(GKeyFile) kf = NULL; + g_autofree char *path = NULL; + g_autofree char *who = NULL; + g_autofree char *id = NULL; + g_autofree char *fifo = NULL; + gboolean ok; + gulong pid; + + path = g_build_filename (statedir, name, NULL); + + kf = g_key_file_new (); + ok = g_key_file_load_from_file (kf, path, 0, error); + + if (!ok) + return NULL; + + id = g_key_file_get_string (kf, "guard", "id", &err); + + if (id == NULL) + { + g_set_error (error, BOLT_ERROR, BOLT_ERROR_FAILED, + "could not read 'id' field: %s", err->message); + return NULL; + } + + who = g_key_file_get_string (kf, "guard", "who", &err); + if (who == NULL) + { + g_set_error_literal (error, BOLT_ERROR, BOLT_ERROR_FAILED, + "field missing ('who')"); + return NULL; + } + + pid = (gulong) g_key_file_get_uint64 (kf, "guard", "pid", &err); + if (err != NULL) + { + g_set_error_literal (error, BOLT_ERROR, BOLT_ERROR_FAILED, + "field missing ('pid')"); + return NULL; + } + + fifo = g_strdup_printf ("%s.fifo", path); + + if (!g_file_test (fifo, G_FILE_TEST_EXISTS)) + g_clear_pointer (&fifo, g_free); + + return g_object_new (BOLT_TYPE_GUARD, + "path", path, + "fifo", fifo, + "id", id, + "who", who, + "pid", pid, + NULL); +} diff -Nru bolt-0.8/boltd/bolt-guard.h bolt-0.9.1/boltd/bolt-guard.h --- bolt-0.8/boltd/bolt-guard.h 1970-01-01 00:00:00.000000000 +0000 +++ bolt-0.9.1/boltd/bolt-guard.h 2020-12-01 11:26:01.000000000 +0000 @@ -0,0 +1,57 @@ +/* + * Copyright © 2020 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Christian J. Kellner + */ + +#pragma once + +#include "bolt-enums.h" +#include "bolt-exported.h" + +#include + +G_BEGIN_DECLS + +#define BOLT_TYPE_GUARD bolt_guard_get_type () +G_DECLARE_FINAL_TYPE (BoltGuard, bolt_guard, BOLT, GUARD, GObject); + +int bolt_guard_monitor (BoltGuard *guard, + GError **error); + +const char * bolt_guard_get_id (BoltGuard *guard); + +const char * bolt_guard_get_who (BoltGuard *guard); + +guint bolt_guard_get_pid (BoltGuard *guard); + +const char * bolt_guard_get_path (BoltGuard *guard); + +const char * bolt_guard_get_fifo (BoltGuard *guard); + +GPtrArray * bolt_guard_recover (const char *statedir, + GError **error); + +gboolean bolt_guard_save (BoltGuard *guard, + GFile *guarddir, + GError **error); + +BoltGuard * bolt_guard_load (const char *statedir, + const char *name, + GError **error); + +G_END_DECLS diff -Nru bolt-0.8/boltd/bolt-journal.c bolt-0.9.1/boltd/bolt-journal.c --- bolt-0.8/boltd/bolt-journal.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/boltd/bolt-journal.c 2020-12-01 11:26:01.000000000 +0000 @@ -289,6 +289,18 @@ return TRUE; } +static void +bolt_journal_set_fresh (BoltJournal *journal, + gboolean fresh) +{ + if (journal->fresh == fresh) + return; + + journal->fresh = fresh; + g_object_notify_by_pspec (G_OBJECT (journal), + journal_props[PROP_FRESH]); +} + /* public methods */ BoltJournal * @@ -337,7 +349,7 @@ g_strerror (errno)); } - journal->fresh = FALSE; + bolt_journal_set_fresh (journal, FALSE); return TRUE; } @@ -420,10 +432,13 @@ if (ok) ok = bolt_rename (path, base, error); - if (ok) - bolt_swap (journal->fd, fd); + if (!ok) + return FALSE; - return ok; + bolt_swap (journal->fd, fd); + bolt_journal_set_fresh (journal, FALSE); + + return TRUE; } GPtrArray * diff -Nru bolt-0.8/boltd/bolt-log.c bolt-0.9.1/boltd/bolt-log.c --- bolt-0.8/boltd/bolt-log.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/boltd/bolt-log.c 2020-12-01 11:26:01.000000000 +0000 @@ -294,7 +294,7 @@ struct SpecialField { const char *name; - void (*hanlder) (BoltLogCtx *ctx, + void (*handler) (BoltLogCtx *ctx, const char *key, gpointer ptr); } special_fields[] = { @@ -317,7 +317,7 @@ { if (g_str_equal (key, special_fields[i].name)) { - special_fields[i].hanlder (ctx, key, ptr); + special_fields[i].handler (ctx, key, ptr); handled = TRUE; } } diff -Nru bolt-0.8/boltd/bolt-manager.c bolt-0.9.1/boltd/bolt-manager.c --- bolt-0.8/boltd/bolt-manager.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/boltd/bolt-manager.c 2020-12-01 11:26:01.000000000 +0000 @@ -30,15 +30,16 @@ #include "bolt-store.h" #include "bolt-str.h" #include "bolt-sysfs.h" +#include "bolt-time.h" #include "bolt-udev.h" #include "bolt-unix.h" +#include "bolt-watchdog.h" #include "bolt-manager.h" #include #include -#define MSEC_PER_USEC 1000LL #define PROBING_SETTLE_TIME_MS 2000 /* in milli-seconds */ typedef struct udev_device udev_device; @@ -52,6 +53,12 @@ GCancellable *cancellable, GError **error); +static gboolean bolt_manager_store_init (BoltManager *mgr, + GError **error); + +static void bolt_manager_store_upgrade (BoltManager *mgr, + gboolean *upgraded); + /* internal manager functions */ static void manager_sd_notify_status (BoltManager *mgr); @@ -74,6 +81,8 @@ static void manager_deregister_domain (BoltManager *mgr, BoltDomain *domain); +static void manager_cleanup_stale_domains (BoltManager *mgr); + /* device related functions */ static gboolean manager_load_devices (BoltManager *mgr, GError **error); @@ -153,6 +162,10 @@ BoltStatus old, BoltManager *mgr); +static void handle_device_generation_changed (BoltDevice *dev, + GParamSpec *unused, + BoltManager *mgr); + static void handle_power_state_changed (GObject *gobject, GParamSpec *pspec, gpointer user_data); @@ -171,7 +184,7 @@ gboolean weak); /* force powering */ -static BoltPowerGuard * manager_maybe_power_controller (BoltManager *mgr); +static BoltGuard * manager_maybe_power_controller (BoltManager *mgr); /* config */ static void manager_load_user_config (BoltManager *mgr); @@ -228,6 +241,7 @@ BoltPower *power; BoltSecurity security; BoltAuthMode authmode; + guint generation; /* policy enforcer */ BoltBouncer *bouncer; @@ -242,6 +256,9 @@ guint probing_timeout; /* signal id & indicator */ gint64 probing_tstamp; /* time stamp of last activity */ guint probing_tsettle; /* how long to indicate after the last activity */ + + /* watchdog */ + BoltWatchdog *dog; }; enum { @@ -253,6 +270,7 @@ PROP_SECURITY, PROP_AUTHMODE, PROP_POWERSTATE, + PROP_GENERATION, PROP_LAST, PROP_EXPORTED = PROP_VERSION @@ -286,9 +304,13 @@ g_ptr_array_free (mgr->devices, TRUE); bolt_domain_clear (&mgr->domains); + g_clear_pointer (&mgr->config, g_key_file_unref); + g_clear_object (&mgr->power); g_clear_object (&mgr->bouncer); + g_clear_object (&mgr->dog); + G_OBJECT_CLASS (bolt_manager_parent_class)->finalize (object); } @@ -327,6 +349,10 @@ g_value_set_enum (value, bolt_power_get_state (mgr->power)); break; + case PROP_GENERATION: + g_value_set_uint (value, mgr->generation); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } @@ -336,7 +362,6 @@ bolt_manager_init (BoltManager *mgr) { mgr->devices = g_ptr_array_new_with_free_func (g_object_unref); - mgr->store = bolt_store_new (bolt_get_store_path ()); mgr->probing_roots = g_ptr_array_new_with_free_func (g_free); mgr->probing_tsettle = PROBING_SETTLE_TIME_MS; /* milliseconds */ @@ -346,14 +371,6 @@ /* default configuration */ mgr->policy = BOLT_POLICY_AUTO; mgr->authmode = BOLT_AUTH_ENABLED; - - g_signal_connect_object (mgr->store, "device-added", - G_CALLBACK (handle_store_device_added), - mgr, 0); - - g_signal_connect_object (mgr->store, "device-removed", - G_CALLBACK (handle_store_device_removed), - mgr, 0); } static void @@ -405,6 +422,12 @@ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + props[PROP_GENERATION] = + g_param_spec_uint ("generation", "Generation", NULL, + 0, G_MAXUINT, 0, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (gobject_class, PROP_LAST, props); @@ -457,14 +480,20 @@ GCancellable *cancellable, GError **error) { - g_autoptr(BoltPowerGuard) power = NULL; + g_autoptr(BoltGuard) power = NULL; BoltManager *mgr; struct udev_enumerate *enumerate; struct udev_list_entry *l, *devices; + gboolean upgraded = FALSE; gboolean ok; mgr = BOLT_MANAGER (initable); + /* store setup */ + ok = bolt_manager_store_init (mgr, error); + if (!ok) + return FALSE; + /* load dynamic user configuration */ manager_load_user_config (mgr); @@ -475,6 +504,11 @@ bolt_bouncer_add_client (mgr->bouncer, mgr); + /* watchdog setup */ + mgr->dog = bolt_watchdog_new (error); + if (mgr->dog == NULL) + return FALSE; + /* udev setup*/ bolt_info (LOG_TOPIC ("udev"), "initializing udev"); mgr->udev = bolt_udev_new ("udev", NULL, error); @@ -507,7 +541,7 @@ if (power != NULL) bolt_info (LOG_TOPIC ("manager"), "acquired power guard '%s'", - bolt_power_guard_get_id (power)); + bolt_guard_get_id (power)); /* TODO: error checking */ enumerate = bolt_udev_new_enumerate (mgr->udev, NULL); @@ -541,19 +575,76 @@ if (bolt_streq (devtype, "thunderbolt_domain")) handle_udev_domain_event (mgr, udevice, "add"); - if (!bolt_streq (devtype, "thunderbolt_device")) - continue; - - handle_udev_device_event (mgr, udevice, "add"); + if (bolt_streq (devtype, "thunderbolt_device")) + handle_udev_device_event (mgr, udevice, "add"); } udev_enumerate_unref (enumerate); + /* upgrade the store, if needed */ + bolt_manager_store_upgrade (mgr, &upgraded); + + if (upgraded) + manager_cleanup_stale_domains (mgr); + manager_sd_notify_status (mgr); return TRUE; } +static gboolean +bolt_manager_store_init (BoltManager *mgr, GError **error) +{ + bolt_info (LOG_TOPIC ("manager"), "initializing store"); + + mgr->store = bolt_store_new (bolt_get_store_path (), error); + if (mgr->store == NULL) + return FALSE; + + g_signal_connect_object (mgr->store, "device-added", + G_CALLBACK (handle_store_device_added), + mgr, 0); + + g_signal_connect_object (mgr->store, "device-removed", + G_CALLBACK (handle_store_device_removed), + mgr, 0); + + return TRUE; +} + +static void +bolt_manager_store_upgrade (BoltManager *mgr, gboolean *upgraded) +{ + g_autoptr(GError) err = NULL; + BoltStore *store = mgr->store; + guint ver; + gboolean ok; + + ver = bolt_store_get_version (store); + + if (ver == BOLT_STORE_VERSION) + { + bolt_debug (LOG_TOPIC ("store"), "store is up to date"); + return; + } + + bolt_info (LOG_TOPIC ("store"), "attempting upgrade from '%d'", + ver); + + ok = bolt_store_upgrade (store, NULL, &err); + if (!ok) + { + bolt_warn_err (err, LOG_TOPIC ("store"), "upgrade failed"); + return; + } + + ver = bolt_store_get_version (store); + bolt_info (LOG_TOPIC ("store"), "upgraded to version '%d'", + ver); + + *upgraded = TRUE; +} + /* internal functions */ static void manager_sd_notify_status (BoltManager *mgr) @@ -611,6 +702,24 @@ } } +static void +manager_maybe_set_generation (BoltManager *mgr, + guint gen) +{ + guint check; + + check = MAX (mgr->generation, gen); + + if (mgr->generation >= check) + return; + + bolt_info ("global 'generation' set to '%d'", check); + + mgr->generation = check; + g_object_notify_by_pspec (G_OBJECT (mgr), + props[PROP_GENERATION]); +} + /* domain related function */ static gboolean manager_load_domains (BoltManager *mgr, @@ -710,6 +819,58 @@ bolt_yesno (ok), empty); } +static gboolean +domain_has_stable_uuid (BoltDomain *domain, + struct udev_device *dev) +{ + g_autoptr(GError) err = NULL; + gboolean stable; + gboolean ok; + guint32 pci_id; + + /* On integrated TBT, like ICL/TGL, the uuid of the + * controller is randomly generated on *every* boot, + * and thus the uuid is not stable. */ + + /* default to FALSE, in case we have no entry */ + stable = FALSE; + + ok = bolt_sysfs_nhi_id_for_domain (dev, &pci_id, &err); + if (!ok) + { + bolt_warn_err (err, LOG_TOPIC ("udev"), LOG_DOM (domain), + "failed to get NHI for domain"); + return FALSE; + } + + ok = bolt_nhi_uuid_is_stable (pci_id, &stable, &err); + if (!ok) + bolt_warn_err (err, LOG_TOPIC ("udev"), LOG_DOM (domain), + "failed to determine if uid is stable"); + + bolt_info (LOG_TOPIC ("udev"), LOG_DOM (domain), + "uuid is stable: %s (for NHI: 0x%04x)", + bolt_yesno (stable), pci_id); + + return stable; +} + +static void +manager_store_domain (BoltManager *mgr, + BoltDomain *domain) +{ + g_autoptr(GError) err = NULL; + gboolean ok; + + bolt_info (LOG_TOPIC ("store"), LOG_DOM (domain), + "storing newly connected domain"); + + ok = bolt_store_put_domain (mgr->store, domain, &err); + if (!ok) + bolt_warn_err (err, LOG_TOPIC ("store"), LOG_DOM (domain), + "could not store domain"); +} + static BoltDomain * manager_domain_ensure (BoltManager *mgr, struct udev_device *dev) @@ -725,7 +886,6 @@ const char *op; const char *uid; gboolean iommu; - gboolean ok; /* check if we already know a domain that is the parent * of the device (dev); if not then 'dev' is very likely @@ -746,7 +906,7 @@ if (dom == NULL) return NULL; - uid = udev_device_get_sysattr_value (host, "unique_id"); + uid = bolt_sysfs_device_get_unique_id (host, NULL); /* check if we have a stored domain with a matching uid * of the host device, if so then we have just connected @@ -788,15 +948,12 @@ /* add all devices with POLICY_AUTO to the bootacl */ manager_bootacl_inital_sync (mgr, domain); - /* now store the domain (with an updated bootacl) */ - bolt_info (LOG_TOPIC ("store"), LOG_DOM (domain), - "storing newly connected domain"); - - ok = bolt_store_put_domain (mgr->store, domain, &err); - if (!ok) - bolt_warn_err (err, LOG_TOPIC ("store"), LOG_DOM (domain), - "could not store domain"); + /* now store the domain (with an updated bootacl), + * but only if its uuid is the same across reboots */ + if (domain_has_stable_uuid (domain, dom)) + manager_store_domain (mgr, domain); + /* export it on the bus and emit the added signals */ bus = bolt_exported_get_connection (BOLT_EXPORTED (mgr)); if (bus == NULL) return domain; @@ -904,20 +1061,72 @@ g_signal_connect_object (domain, "notify::security", G_CALLBACK (handle_domain_security_changed), mgr, G_CONNECT_SWAPPED); + + bolt_bouncer_add_client (mgr->bouncer, domain); } static void manager_deregister_domain (BoltManager *mgr, BoltDomain *domain) { - const char *name; - - name = bolt_domain_get_id (domain); - bolt_info (LOG_TOPIC ("domain"), "'%s' removed", name); + bolt_info (LOG_TOPIC ("manager"), LOG_DOM (domain), + "de-registered"); mgr->domains = bolt_domain_remove (mgr->domains, domain); } +static void +manager_cleanup_stale_domains (BoltManager *mgr) +{ + g_autoptr(GPtrArray) domains = NULL; + BoltDomain *iter; + guint count; + + /* Before bolt 0.9.1, domains were stored that have an + * unstable uuid, i.e. their uuids change on every boot + * and thus there will be store entries that can't be + * matched anymore and thus are "stale". + */ + + bolt_info (LOG_TOPIC ("manager"), "stale domain cleanup"); + + count = bolt_domain_count (mgr->domains); + domains = g_ptr_array_new_full (count, g_object_unref); + + iter = mgr->domains; + for (guint i = 0; i < count; i++) + { + gboolean stored = bolt_domain_is_stored (iter); + gboolean offline = !bolt_domain_is_connected (iter); + + if (stored && offline) + g_ptr_array_add (domains, g_object_ref (iter)); + + iter = bolt_domain_next (iter); + } + + for (guint i = 0; i < domains->len; i++) + { + g_autoptr(GError) err = NULL; + BoltDomain *dom = g_ptr_array_index (domains, i); + gboolean ok; + + bolt_info (LOG_DOM (dom), LOG_TOPIC ("store"), + "stale domain detected"); + + ok = bolt_store_del_domain (mgr->store, dom, &err); + + if (!ok) + { + bolt_warn_err (err, LOG_DOM (dom), + "failed to delete domain"); + continue; + } + + manager_deregister_domain (mgr, dom); + } +} + /* device related functions */ static gboolean manager_load_devices (BoltManager *mgr, @@ -966,13 +1175,38 @@ g_signal_connect_object (dev, "status-changed", G_CALLBACK (handle_device_status_changed), mgr, 0); + + if (bolt_device_is_host (dev)) + { + guint generation = bolt_device_get_generation (dev); + manager_maybe_set_generation (mgr, generation); + g_signal_connect_object (dev, "notify::generation", + G_CALLBACK (handle_device_generation_changed), + mgr, 0); + } + } static void manager_deregister_device (BoltManager *mgr, BoltDevice *dev) { + const char *opath; + g_ptr_array_remove_fast (mgr->devices, dev); + + opath = bolt_device_get_object_path (dev); + + if (opath == NULL) + return; + + bolt_exported_emit_signal (BOLT_EXPORTED (mgr), + "DeviceRemoved", + g_variant_new ("(o)", opath), + NULL); + + bolt_device_unexport (dev); + bolt_info (LOG_DEV (dev), LOG_TOPIC ("dbus"), "unexported"); } static BoltDevice * @@ -1086,6 +1320,7 @@ const char *from; const char *to; } vendors[] = { + {"Dell Inc.", "Dell" }, {"HP Inc.", "HP" }, {"Apple, Inc.", "Apple"} }; @@ -1123,7 +1358,7 @@ } /* we counted the target too, > 1 means duplicates */ - if (count > 1) + if (count > 1 && !bolt_device_is_host (target)) label = g_strdup_printf ("%s %s #%u", vendor, name, count); else label = g_strdup_printf ("%s %s", vendor, name); @@ -1232,11 +1467,28 @@ } static void +manager_do_import_device (BoltManager *mgr, + BoltDevice *dev, + BoltPolicy policy) +{ + g_autoptr(GError) err = NULL; + gboolean ok; + + ok = bolt_store_put_device (mgr->store, + dev, + policy, + NULL, + &err); + + if (!ok) + bolt_warn_err (err, LOG_DEV (dev), LOG_TOPIC ("import"), + "failed to store device"); +} + +static void manager_maybe_import (BoltManager *mgr, BoltDevice *dev) { - g_autoptr(GError) err = NULL; - g_autoptr(BoltKey) key = NULL; BoltSecurity level; BoltPolicy policy; const char *secstr; @@ -1245,17 +1497,35 @@ gboolean boot, pcie; gboolean import; gboolean iommu; - gboolean ok; - if (bolt_device_get_device_type (dev) == BOLT_DEVICE_HOST) - return; + /* This function is intentionally more verbose then it + * could be, but since it is security critical, it is + * better to be clear than concise */ g_return_if_fail (!bolt_device_get_stored (dev)); g_return_if_fail (bolt_device_is_authorized (dev)); - /* This function is intentionally more verbose then it - * could be, but since it is security critical, it is - * better to be clear than concise */ + if (bolt_device_is_host (dev)) + { + BoltDomain *dom = bolt_device_get_domain (dev); + + /* host devices are per design authorized and + * must therefore never be authorized by bolt */ + policy = BOLT_POLICY_MANUAL; + + /* Store the host device only if its domain is + * stored as well. Currently, the only reason + * for a domain to not be stored is that its + * uuid is not stable, i.e. changes every boot. + * But its uuid is in fact derived from the + * associated host device, i.e. this very host + * device here. Ergo, its uuid is unstable and + * thus it should not be stored */ + if (bolt_domain_is_stored (dom)) + manager_do_import_device (mgr, dev, policy); + + return; + } level = bolt_device_get_security (dev); iommu = bolt_device_has_iommu (dev); @@ -1280,22 +1550,12 @@ polstr = bolt_policy_to_string (policy); bolt_msg (LOG_DEV (dev), LOG_TOPIC ("import"), - "%s mode, boot: %s, key: %s -> %s", - secstr, bolt_yesno (boot), bolt_yesno (key), + "%s mode, boot: %s -> %s", + secstr, bolt_yesno (boot), (import ? polstr : "no import")); - if (!import) - return; - - ok = bolt_store_put_device (mgr->store, - dev, - policy, - key, & - err); - - if (!ok) - bolt_warn_err (err, LOG_DEV (dev), LOG_TOPIC ("import"), - "failed to store device"); + if (import) + manager_do_import_device (mgr, dev, policy); } static void @@ -1563,7 +1823,7 @@ /* filter sysfs devices (e.g. the domain) that don't have * the unique_id attribute */ - uid = udev_device_get_sysattr_value (device, "unique_id"); + uid = bolt_sysfs_device_get_unique_id (device, NULL); if (uid == NULL) return; @@ -1626,7 +1886,9 @@ dev = bolt_device_new_for_udev (udev, domain, &err); if (dev == NULL) { - bolt_warn_err (err, LOG_TOPIC ("udev"), "could not create device"); + bolt_warn_err (err, LOG_TOPIC ("udev"), + "could not create device for %s", + syspath); return; } @@ -1687,26 +1949,12 @@ handle_udev_device_removed (BoltManager *mgr, BoltDevice *dev) { - const char *opath; const char *syspath; syspath = bolt_device_get_syspath (dev); bolt_msg (LOG_DEV (dev), "removed (%s)", syspath); manager_deregister_device (mgr, dev); - - opath = bolt_device_get_object_path (dev); - - if (opath == NULL) - return; - - bolt_exported_emit_signal (BOLT_EXPORTED (mgr), - "DeviceRemoved", - g_variant_new ("(o)", opath), - NULL); - - bolt_device_unexport (dev); - bolt_info (LOG_DEV (dev), LOG_TOPIC ("dbus"), "unexported"); } static void @@ -1759,71 +2007,6 @@ bolt_device_disconnected (dev); } -typedef struct -{ - BoltManager *mgr; - const char *uid; -} BootaclCtx; - -static void -bootacl_add_dev (gpointer data, - gpointer user_data) -{ - g_autoptr(GError) err = NULL; - BoltDomain *dom = data; - BootaclCtx *ctx = user_data; - gboolean ok; - - bolt_info (LOG_TOPIC ("bootacl"), LOG_DOM (dom), - LOG_DEV_UID (ctx->uid), - "adding newly stored device to bootacl"); - - if (!bolt_domain_supports_bootacl (dom)) - return; - - if (bolt_domain_bootacl_contains (dom, ctx->uid)) - return; - - ok = bolt_domain_bootacl_add (dom, ctx->uid, &err); - if (!ok) - { - bolt_warn_err (err, LOG_TOPIC ("bootacl"), LOG_DOM (dom), - LOG_DEV_UID (ctx->uid), - "could not add device [%.17s]", - ctx->uid); - } -} - -static void -bootacl_del_dev (gpointer data, - gpointer user_data) -{ - g_autoptr(GError) err = NULL; - BoltDomain *dom = data; - BootaclCtx *ctx = user_data; - gboolean ok; - - bolt_info (LOG_TOPIC ("bootacl"), LOG_DOM (dom), - LOG_DEV_UID (ctx->uid), - "removing forgotten device from bootacl"); - - if (!bolt_domain_supports_bootacl (dom)) - return; - - if (!bolt_domain_bootacl_contains (dom, ctx->uid)) - return; - - ok = bolt_domain_bootacl_del (dom, ctx->uid, &err); - if (!ok) - { - bolt_warn_err (err, LOG_TOPIC ("bootacl"), - LOG_DEV_UID (ctx->uid), LOG_DOM (dom), - "could not remove device [%.17s]", - ctx->uid); - } -} - - static void handle_store_device_added (BoltStore *store, const char *uid, @@ -1831,7 +2014,6 @@ { g_autoptr(BoltDevice) dev = NULL; BoltDomain *dom = mgr->domains; - BootaclCtx ctx = {mgr, uid}; dev = manager_find_device_by_uid (mgr, uid, NULL); @@ -1846,7 +2028,7 @@ return; } - bolt_domain_foreach (dom, bootacl_add_dev, &ctx); + bolt_domain_foreach (dom, bolt_bootacl_add, dev); } static void @@ -1856,9 +2038,7 @@ { g_autoptr(BoltDevice) dev = NULL; BoltDomain *dom = mgr->domains; - BootaclCtx ctx = {mgr, uid}; BoltStatus status; - const char *opath; dev = manager_find_device_by_uid (mgr, uid, NULL); @@ -1875,7 +2055,7 @@ NULL); /* remove device from bootacl */ - bolt_domain_foreach (dom, bootacl_del_dev, &ctx); + bolt_domain_foreach (dom, bolt_bootacl_del, dev); status = bolt_device_get_status (dev); /* if the device is connected, keep it around */ @@ -1883,18 +2063,6 @@ return; manager_deregister_device (mgr, dev); - opath = bolt_device_get_object_path (dev); - - if (opath == NULL) - return; - - bolt_exported_emit_signal (BOLT_EXPORTED (mgr), - "DeviceRemoved", - g_variant_new ("(o)", opath), - NULL); - - bolt_device_unexport (dev); - bolt_info (LOG_DEV (dev), "unexported"); } @@ -1952,6 +2120,21 @@ } } +static void +handle_device_generation_changed (BoltDevice *dev, + GParamSpec *unused, + BoltManager *mgr) +{ + guint gen = bolt_device_get_generation (dev); + + bolt_debug (LOG_DEV (dev), LOG_TOPIC ("generation"), + "updated to: %u", gen); + + if (!bolt_device_is_host (dev)) + return; + + manager_maybe_set_generation (mgr, gen); +} static void handle_power_state_changed (GObject *gobject, @@ -1988,7 +2171,7 @@ /* dt is in microseconds, probing timeout in * milli seconds */ - timeout = mgr->probing_tsettle * MSEC_PER_USEC; + timeout = mgr->probing_tsettle * BOLT_USEC_PER_MSEC; if (dt < timeout) return G_SOURCE_CONTINUE; @@ -2029,6 +2212,16 @@ } static gboolean +device_is_wakeup (struct udev_device *dev) +{ + const char *subsys; + + subsys = udev_device_get_subsystem (dev); + + return bolt_streq (subsys, "wakeup"); +} + +static gboolean probing_add_root (BoltManager *mgr, struct udev_device *dev) { @@ -2065,6 +2258,12 @@ if (syspath == NULL) return; + /* ignore events for wakeup devices which get removed + * and added at random time without any connection to + * any physical thunderbolt device */ + if (device_is_wakeup (dev)) + return; + roots = mgr->probing_roots; for (guint i = 0; i < roots->len; i++) { @@ -2136,11 +2335,11 @@ probing_add_root (mgr, p); } -static BoltPowerGuard * +static BoltGuard * manager_maybe_power_controller (BoltManager *mgr) { g_autoptr(GError) err = NULL; - BoltPowerGuard *guard = NULL; + BoltGuard *guard = NULL; gboolean can_force_power; int n; @@ -2149,7 +2348,7 @@ if (can_force_power == FALSE) return NULL; - n = bolt_udev_count_domains (mgr->udev, &err); + n = bolt_udev_count_hosts (mgr->udev, &err); if (n < 0) { bolt_warn_err (err, LOG_TOPIC ("udev"), @@ -2175,7 +2374,7 @@ for (int i = 0; i < 25 && n < 1; i++) { g_usleep (200000); /* 200 000 us = 0.2s */ - n = bolt_udev_count_domains (mgr->udev, NULL); + n = bolt_udev_count_hosts (mgr->udev, NULL); } out: diff -Nru bolt-0.8/boltd/bolt-power.c bolt-0.9.1/boltd/bolt-power.c --- bolt-0.8/boltd/bolt-power.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/boltd/bolt-power.c 2020-12-01 11:26:01.000000000 +0000 @@ -23,11 +23,13 @@ #include "bolt-power.h" #include "bolt-config.h" +#include "bolt-dbus.h" #include "bolt-enums.h" #include "bolt-error.h" #include "bolt-fs.h" #include "bolt-log.h" #include "bolt-io.h" +#include "bolt-reaper.h" #include "bolt-str.h" #include "bolt-unix.h" @@ -42,513 +44,11 @@ #define DEFAULT_STATEDIR "power" #define STATE_FILENAME "on" -typedef struct udev_device udev_device; -G_DEFINE_AUTOPTR_CLEANUP_FUNC (udev_device, udev_device_unref); +/* prototypes */ +static void bolt_power_release (BoltPower *power, + BoltGuard *guard); -static void bolt_power_release (BoltPower *power, - BoltPowerGuard *guard); - -/* BoltPowerGuard */ -static void bolt_power_guard_remove (BoltPowerGuard *guard); - -struct _BoltPowerGuard -{ - GObject object; - - /* book-keeping */ - BoltPower *power; - char *path; - - char *fifo; - guint watch; - - /* properties */ - char *id; - char *who; - pid_t pid; -}; - -enum { - PROP_GUARD_0, - - PROP_POWER, - PROP_PATH, - PROP_FIFO, - - PROP_ID, - PROP_WHO, - PROP_PID, - - - PROP_GUARD_LAST -}; - -static GParamSpec *guard_props[PROP_GUARD_LAST] = { NULL, }; - -G_DEFINE_TYPE (BoltPowerGuard, - bolt_power_guard, - G_TYPE_OBJECT); - -static void -bolt_power_guard_finalize (GObject *object) -{ - BoltPowerGuard *guard = BOLT_POWER_GUARD (object); - - /* remove our state file */ - bolt_power_guard_remove (guard); - - /* release the lock we have to force power, - * we must be intact for method call */ - bolt_power_release (guard->power, guard); - - if (guard->watch) - g_source_remove (guard->watch); - - g_clear_pointer (&guard->fifo, g_free); - g_clear_pointer (&guard->who, g_free); - g_clear_pointer (&guard->id, g_free); - g_clear_object (&guard->power); - - G_OBJECT_CLASS (bolt_power_guard_parent_class)->finalize (object); -} - -static void -bolt_power_guard_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - BoltPowerGuard *guard = BOLT_POWER_GUARD (object); - - switch (prop_id) - { - case PROP_POWER: - g_value_set_object (value, guard->power); - break; - - case PROP_PATH: - g_value_set_string (value, guard->path); - break; - - case PROP_FIFO: - g_value_set_string (value, guard->fifo); - break; - - case PROP_ID: - g_value_set_string (value, guard->id); - break; - - case PROP_WHO: - g_value_set_string (value, guard->who); - break; - - case PROP_PID: - g_value_set_ulong (value, guard->pid); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - - -static void -bolt_power_guard_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - BoltPowerGuard *guard = BOLT_POWER_GUARD (object); - - switch (prop_id) - { - case PROP_POWER: - guard->power = g_value_dup_object (value); - break; - - case PROP_PATH: - guard->path = g_value_dup_string (value); - break; - - case PROP_FIFO: - guard->fifo = g_value_dup_string (value); - break; - - case PROP_ID: - guard->id = g_value_dup_string (value); - break; - - case PROP_WHO: - guard->who = g_value_dup_string (value); - break; - - case PROP_PID: - guard->pid = g_value_get_ulong (value); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - - -static void -bolt_power_guard_init (BoltPowerGuard *power) -{ -} - -static void -bolt_power_guard_class_init (BoltPowerGuardClass *klass) -{ - GObjectClass *gobject_class = G_OBJECT_CLASS (klass); - - gobject_class->finalize = bolt_power_guard_finalize; - gobject_class->get_property = bolt_power_guard_get_property; - gobject_class->set_property = bolt_power_guard_set_property; - - guard_props[PROP_POWER] = - g_param_spec_object ("power", - NULL, NULL, - BOLT_TYPE_POWER, - G_PARAM_READWRITE | - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_STATIC_STRINGS); - - guard_props[PROP_PATH] = - g_param_spec_string ("path", - NULL, NULL, - NULL, - G_PARAM_READWRITE | - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_STATIC_STRINGS); - - guard_props[PROP_FIFO] = - g_param_spec_string ("fifo", - NULL, NULL, - NULL, - G_PARAM_READWRITE | - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_STATIC_STRINGS); - - - guard_props[PROP_ID] = - g_param_spec_string ("id", - NULL, NULL, - NULL, - G_PARAM_READWRITE | - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_STATIC_STRINGS); - - guard_props[PROP_WHO] = - g_param_spec_string ("who", - NULL, NULL, - NULL, - G_PARAM_READWRITE | - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_STATIC_STRINGS); - - guard_props[PROP_PID] = - g_param_spec_ulong ("pid", - NULL, NULL, - 0, G_MAXULONG, 0, - G_PARAM_READWRITE | - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_STATIC_STRINGS); - - g_object_class_install_properties (gobject_class, - PROP_GUARD_LAST, - guard_props); -} - -static gboolean -bolt_power_guard_save (BoltPowerGuard *guard, - GFile *guarddir, - GError **error) -{ - g_autoptr(GFile) guardfile = NULL; - g_autoptr(GKeyFile) kf = NULL; - g_autofree char *path = NULL; - g_autofree char *name = NULL; - gboolean ok; - - name = g_strdup_printf ("%s.guard", guard->id); - guardfile = g_file_get_child (guarddir, name); - path = g_file_get_path (guardfile); - - kf = g_key_file_new (); - - g_key_file_set_string (kf, "guard", "id", guard->id); - g_key_file_set_string (kf, "guard", "who", guard->who); - g_key_file_set_uint64 (kf, "guard", "pid", guard->pid); - - ok = g_key_file_save_to_file (kf, path, error); - - if (ok) - { - guard->path = g_steal_pointer (&path); - g_object_notify_by_pspec (G_OBJECT (guard), - guard_props[PROP_PATH]); - } - - return ok; -} - -static BoltPowerGuard * -bolt_power_guard_load (BoltPower *power, - const char *name, - GError **error) -{ - g_autoptr(GError) err = NULL; - g_autoptr(GFile) guardfile = NULL; - g_autoptr(GKeyFile) kf = NULL; - g_autofree char *path = NULL; - g_autofree char *who = NULL; - g_autofree char *id = NULL; - g_autofree char *fifo = NULL; - GFile *statedir; - gboolean ok; - gulong pid; - - statedir = bolt_power_get_statedir (power); - guardfile = g_file_get_child (statedir, name); - path = g_file_get_path (guardfile); - - kf = g_key_file_new (); - ok = g_key_file_load_from_file (kf, path, 0, error); - - if (!ok) - return NULL; - - id = g_key_file_get_string (kf, "guard", "id", &err); - - if (id == NULL) - { - g_set_error (error, BOLT_ERROR, BOLT_ERROR_FAILED, - "could not read 'id' field: %s", err->message); - return NULL; - } - - who = g_key_file_get_string (kf, "guard", "who", &err); - if (who == NULL) - { - g_set_error_literal (error, BOLT_ERROR, BOLT_ERROR_FAILED, - "field missing ('who')"); - return NULL; - } - - pid = (gulong) g_key_file_get_uint64 (kf, "guard", "pid", &err); - if (err != NULL) - { - g_set_error_literal (error, BOLT_ERROR, BOLT_ERROR_FAILED, - "field missing ('pid')"); - return NULL; - } - - fifo = g_strdup_printf ("%s.fifo", path); - - if (!g_file_test (fifo, G_FILE_TEST_EXISTS)) - g_clear_pointer (&fifo, g_free); - - return g_object_new (BOLT_TYPE_POWER_GUARD, - "power", power, - "path", path, - "fifo", fifo, - "id", id, - "who", who, - "pid", pid, - NULL); -} - -static void -bolt_power_guard_remove (BoltPowerGuard *guard) -{ - g_autoptr(GError) err = NULL; - g_autofree char *parent = NULL; - gboolean ok; - - /* we are not saved */ - if (guard->path == NULL) - return; - - /* */ - if (guard->fifo != NULL) - { - bolt_debug (LOG_TOPIC ("power"), - "not removing guard '%s' with active fifo", - guard->id); - return; - } - - ok = bolt_unlink (guard->path, &err); - if (!ok) - { - bolt_warn_err (err, LOG_TOPIC ("power"), - "Could not remove power guard: '%s' @ %s", - guard->id, guard->path); - return; - } - - /* we try to remove the parent dir, which will most - * likely fail, because it is not empty, but that we - * just ignore - */ - parent = g_path_get_dirname (guard->path); - (void) rmdir (parent); - - g_clear_pointer (&guard->path, g_free); - g_object_notify_by_pspec (G_OBJECT (guard), - guard_props[PROP_PATH]); -} - -static void -bolt_power_guard_fifo_cleanup (BoltPowerGuard *guard) -{ - g_autoptr(GError) err = NULL; - gboolean ok; - - if (guard->fifo == NULL) - return; - - ok = bolt_unlink (guard->fifo, &err); - if (!ok) - { - bolt_warn_err (err, LOG_TOPIC ("power"), - "Could not remove FIFO for power guard: '%s' @ %s", - guard->id, guard->fifo); - } - - g_clear_pointer (&guard->fifo, g_free); - g_object_notify_by_pspec (G_OBJECT (guard), guard_props[PROP_FIFO]); -} - -static gboolean -bolt_power_guard_mkfifo (BoltPowerGuard *guard, - GError **error) -{ - g_autoptr(GError) err = NULL; - int r; - - g_return_val_if_fail (BOLT_IS_POWER_GUARD (guard), FALSE); - g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - - if (guard->fifo == NULL) - guard->fifo = g_strdup_printf ("%s.fifo", guard->path); - - r = bolt_mkfifo (guard->fifo, 0600, &err); - if (r == -1 && !bolt_err_exists (err)) - return bolt_error_propagate (error, &err); - - g_object_notify_by_pspec (G_OBJECT (guard), - guard_props[PROP_FIFO]); - - return TRUE; -} - -static gboolean -power_guard_has_event (GIOChannel *source, - GIOCondition condition, - gpointer data) -{ - BoltPowerGuard *guard = data; - - bolt_info (LOG_TOPIC ("power"), "got event for guard '%s' (%x)", - guard->id, (guint) condition); - guard->watch = 0; - return FALSE; -} - -static void -guard_watch_release (gpointer data) -{ - BoltPowerGuard *guard = data; - - if (guard->fifo == NULL) - { - bolt_bug (LOG_TOPIC ("power"), "FIFO event but no FIFO"); - return; - } - - bolt_power_guard_fifo_cleanup (guard); - - bolt_debug (LOG_TOPIC ("power"), - "released watch reference for guard '%s'", - guard->id); - - g_object_unref (guard); -} - -int -bolt_power_guard_monitor (BoltPowerGuard *guard, - GError **error) -{ - g_autoptr(GIOChannel) ch = NULL; - gboolean ok; - int fd; - - g_return_val_if_fail (BOLT_IS_POWER_GUARD (guard), -1); - g_return_val_if_fail (error == NULL || *error == NULL, -1); - - ok = bolt_power_guard_mkfifo (guard, error); - if (!ok) - return -1; - - /* reader */ - fd = bolt_open (guard->fifo, O_RDONLY | O_CLOEXEC | O_NONBLOCK, 0, error); - if (fd == -1) - return -1; - - ch = g_io_channel_unix_new (fd); - - g_io_channel_set_close_on_unref (ch, TRUE); - g_io_channel_set_encoding (ch, NULL, NULL); - g_io_channel_set_buffered (ch, FALSE); - g_io_channel_set_flags (ch, G_IO_FLAG_NONBLOCK, NULL); - fd = -1; /* the GIOChannel owns the fd, via _close_on_unref () */ - - /* writer */ - fd = bolt_open (guard->fifo, O_WRONLY | O_CLOEXEC | O_NONBLOCK, 0, error); - if (fd == -1) - return -1; - - /* NB: we take a ref to the guard here */ - guard->watch = g_io_add_watch_full (ch, - G_PRIORITY_DEFAULT, - G_IO_HUP | G_IO_ERR, - power_guard_has_event, - g_object_ref (guard), - guard_watch_release); - - return fd; -} - -const char * -bolt_power_guard_get_id (BoltPowerGuard *guard) -{ - g_return_val_if_fail (BOLT_IS_POWER_GUARD (guard), NULL); - - return guard->id; -} - -const char * -bolt_power_guard_get_who (BoltPowerGuard *guard) -{ - g_return_val_if_fail (BOLT_IS_POWER_GUARD (guard), NULL); - - return guard->who; -} - -guint -bolt_power_guard_get_pid (BoltPowerGuard *guard) -{ - g_return_val_if_fail (BOLT_IS_POWER_GUARD (guard), 0); - - return (guint) guard->pid; -} - -/* ****************************************************************** */ -/* BoltPower */ - -/* gobject */ +/* GObject */ static void power_initable_iface_init (GInitableIface *iface); static gboolean bolt_power_initialize (GInitable *initable, @@ -570,9 +70,6 @@ /* callbacks and signals */ static gboolean bolt_power_wait_timeout (gpointer user_data); -static gboolean bolt_power_reaper_timeout (gpointer user_data); - - static void handle_uevent_udev (BoltUdev *udev, const char *action, struct udev_device *device, @@ -605,7 +102,6 @@ * or NULL if force power is unavailable */ char *path; BoltPowerState state; - guint reaper; /* */ guint16 guard_num; @@ -648,9 +144,6 @@ bolt_power_wait_timeout (power); } - if (power->reaper != 0) - g_source_remove (power->reaper); - g_clear_pointer (&power->runpath, g_free); g_clear_object (&power->statedir); g_clear_object (&power->statefile); @@ -839,7 +332,7 @@ GError **error) { g_autoptr(GError) err = NULL; - g_autoptr(BoltPowerGuard) guard = NULL; + g_autoptr(BoltGuard) guard = NULL; g_autofree char *statedir = NULL; BoltPower *power = BOLT_POWER (initable); gboolean on = FALSE; @@ -913,74 +406,33 @@ bolt_power_recover_guards (BoltPower *power, GError **error) { - g_autoptr(GError) err = NULL; - g_autoptr(GDir) dir = NULL; + g_autoptr(GPtrArray) guards = NULL; g_autofree char *statedir = NULL; - const char *name; statedir = g_file_get_path (power->statedir); - dir = g_dir_open (statedir, 0, &err); - if (dir == NULL) + guards = bolt_guard_recover (statedir, error); + if (guards == NULL) return FALSE; - while ((name = g_dir_read_name (dir)) != NULL) + for (guint i = 0; i < guards->len; i++) { - BoltPowerGuard *guard; - - if (!g_str_has_suffix (name, ".guard")) - continue; - - guard = bolt_power_guard_load (power, name, &err); - - if (guard == NULL) - { - bolt_warn_err (err, LOG_TOPIC ("power"), - "could not load guard '%s'", name); - g_clear_error (&err); - continue; - } - - /* internal guards are discarded */ - if (bolt_streq (guard->who, "boltd")) - { - bolt_info (LOG_TOPIC ("power"), "ignoring boltd guard"); - continue; - } - else if (!bolt_pid_is_alive (guard->pid)) - { - bolt_info (LOG_TOPIC ("power"), - "ignoring guard '%s for '%s': process dead", - guard->id, guard->who); - bolt_power_guard_fifo_cleanup (guard); - continue; - } - - if (guard->fifo) - { - int fd; - fd = bolt_power_guard_monitor (guard, &err); - - if (fd < 0) - { - bolt_warn_err (err, "could not monitor guard '%d'", - guard->id); - g_clear_error (&err); - } - else - { - (void) close (fd); - - /* monitoring adds a reference that we don't want */ - g_object_unref (guard); - } - } + BoltGuard *guard = g_ptr_array_index (guards, i); + const char *id = bolt_guard_get_id (guard); + const char *who = bolt_guard_get_who (guard); + const guint pid = bolt_guard_get_pid (guard); bolt_info (LOG_TOPIC ("power"), - "guard '%s' for '%s' (pid %lu) recovered", - guard->id, guard->who, (gulong) guard->pid); + "guard '%s' for '%s' (pid %u) recovered", + id, who, pid); - g_hash_table_insert (power->guards, guard->id, guard); + g_signal_connect_object (guard, "released", + (GCallback) bolt_power_release, + power, G_CONNECT_SWAPPED); + + g_hash_table_insert (power->guards, + (gpointer) bolt_guard_get_id (guard), + (gpointer) g_object_ref (guard)); } return TRUE; @@ -1009,42 +461,6 @@ return G_SOURCE_REMOVE; } -static gboolean -bolt_power_reaper_timeout (gpointer user_data) -{ - g_autoptr(GList) keys = NULL; - BoltPower *power = user_data; - - bolt_debug (LOG_TOPIC ("power"), "looking for dead processes"); - - if (g_hash_table_size (power->guards) == 0) - { - bolt_debug (LOG_TOPIC ("power"), "reaper done"); - power->reaper = 0; - return FALSE; - } - - keys = g_hash_table_get_keys (power->guards); - - for (GList *l = keys; l != NULL; l = l->next) - { - gpointer id = l->data; - BoltPowerGuard *g = g_hash_table_lookup (power->guards, id); - - if (bolt_pid_is_alive (g->pid)) - continue; - - bolt_info (LOG_TOPIC ("power"), - "process '%lu' is dead, " - "releasing the guard '%s' for '%s'", - (gulong) g->pid, g->id, g->who); - - g_object_unref (g); - } - - return TRUE; -} - static void handle_uevent_thunderbolt (BoltPower *power, const char *action, @@ -1249,21 +665,25 @@ } static void -bolt_power_release (BoltPower *power, BoltPowerGuard *guard) +bolt_power_release (BoltPower *power, BoltGuard *guard) { + const char *id; + const char *who; gboolean ok; - ok = g_hash_table_remove (power->guards, guard->id); + id = bolt_guard_get_id (guard); + who = bolt_guard_get_who (guard); + + ok = g_hash_table_remove (power->guards, id); if (!ok) { - bolt_bug ("inactive guard ('%s', '%s') found", - guard->id, guard->who); + bolt_bug ("inactive guard ('%s', '%s') found", id, who); return; } bolt_info (LOG_TOPIC ("power"), "guard '%s' for '%s' deactivated", - guard->id, guard->who); + id, who); /* we still have active guards */ if (g_hash_table_size (power->guards) != 0) @@ -1296,54 +716,42 @@ GDBusMethodInvocation *invocation, GError **error) { - g_autoptr(BoltPowerGuard) guard = NULL; + g_autoptr(BoltGuard) guard = NULL; g_autoptr(GUnixFDList) fds = NULL; - g_autoptr(GError) err = NULL; - g_autoptr(GVariant) res = NULL; - GDBusConnection *con; BoltPower *power; - const char *sender; const char *flags; const char *who; + gboolean ok; guint pid; int fd; power = BOLT_POWER (object); - con = g_dbus_method_invocation_get_connection (invocation); - sender = g_dbus_method_invocation_get_sender (invocation); - - res = g_dbus_connection_call_sync (con, - "org.freedesktop.DBus", - "/", - "org.freedesktop.DBus", - "GetConnectionUnixProcessID", - g_variant_new ("(s)", sender), - G_VARIANT_TYPE ("(u)"), - G_DBUS_CALL_FLAGS_NONE, - -1, NULL, - &err); - - if (res == NULL) - { - g_set_error (error, BOLT_ERROR, BOLT_ERROR_FAILED, - "could not get pid of caller: %s", - err->message); - return NULL; - } - - g_variant_get (res, "(u)", &pid); + ok = bolt_dbus_get_sender_pid (invocation, &pid, error); + if (!ok) + return NULL; g_variant_get (params, "(&s&s)", &who, &flags); - /* TODO: log errors */ guard = bolt_power_acquire_full (power, who, (pid_t) pid, error); if (guard == NULL) - return NULL; + { + bolt_warn_err (*error, LOG_TOPIC ("power"), + "failed to acquire power for %s (pid %u)", + who, pid); + return NULL; + } - fd = bolt_power_guard_monitor (guard, error); + /* monitor will add a reference to guard, so freeing one + * via the auto pointer is expected and in fact desired */ + fd = bolt_guard_monitor (guard, error); if (fd == -1) - return NULL; + { + bolt_warn_err (*error, LOG_TOPIC ("power"), + "failed to monitor guard %s for %s (pid %u)", + bolt_guard_get_id (guard), who, pid); + return NULL; + } fds = g_unix_fd_list_new_from_array (&fd, 1); g_dbus_method_invocation_return_value_with_unix_fd_list (invocation, @@ -1369,11 +777,11 @@ for (GList *l = guards; l != NULL; l = l->next) { - BoltPowerGuard *g = BOLT_POWER_GUARD (l->data); + BoltGuard *g = BOLT_GUARD (l->data); g_variant_builder_add (&b, "(ssu)", - bolt_power_guard_get_id (g), - bolt_power_guard_get_who (g), - bolt_power_guard_get_pid (g)); + bolt_guard_get_id (g), + bolt_guard_get_who (g), + bolt_guard_get_pid (g)); } return g_variant_new ("(a(ssu))", &b); @@ -1417,7 +825,7 @@ return power->state; } -BoltPowerGuard * +BoltGuard * bolt_power_acquire (BoltPower *power, GError **error) { @@ -1426,7 +834,7 @@ return bolt_power_acquire_full (power, who, 0, error); } -BoltPowerGuard * +BoltGuard * bolt_power_acquire_full (BoltPower *power, const char *who, pid_t pid, @@ -1434,7 +842,7 @@ { g_autoptr(GError) err = NULL; g_autofree char *id = NULL; - BoltPowerGuard *guard; + BoltGuard *guard; gboolean ok; g_return_val_if_fail (BOLT_IS_POWER (power), NULL); @@ -1475,33 +883,33 @@ if (pid == 0) pid = getpid (); - guard = g_object_new (BOLT_TYPE_POWER_GUARD, - "power", power, + guard = g_object_new (BOLT_TYPE_GUARD, "id", id, "who", who, "pid", pid, NULL); - /* NB: we don't take a ref here, because we want the - * guard to act as RAII guard, i.e. when the client - * releases the last reference to the guard, we call - * the _release() function in the finalizer */ - g_hash_table_insert (power->guards, guard->id, guard); + g_signal_connect_object (guard, "released", + (GCallback) bolt_power_release, + power, G_CONNECT_SWAPPED); + + /* NB: we don't take a ref here, because we want the guard to + * act as RAII guard, i.e. when the client releases the last + * reference to the guard, the "released" signal will be + * triggered and thus bolt_power_release() will be called */ + g_hash_table_insert (power->guards, + (gpointer) bolt_guard_get_id (guard), + guard); bolt_info (LOG_TOPIC ("power"), "guard '%s' for '%s' active", - guard->id, guard->who); - - if (power->reaper == 0) - power->reaper = g_timeout_add_seconds (POWER_REAPER_TIMEOUT, - bolt_power_reaper_timeout, - power); + id, who); /* guard is saved so we can recover our state if we * were to crash or restarted */ - ok = bolt_power_guard_save (guard, power->statedir, &err); + ok = bolt_guard_save (guard, power->statedir, &err); if (!ok) bolt_warn_err (err, LOG_TOPIC ("power"), - "could not save guard '%s'", guard->id); + "could not save guard '%s'", id); return guard; } diff -Nru bolt-0.8/boltd/bolt-power.h bolt-0.9.1/boltd/bolt-power.h --- bolt-0.8/boltd/bolt-power.h 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/boltd/bolt-power.h 2020-12-01 11:26:01.000000000 +0000 @@ -22,6 +22,7 @@ #include "bolt-enums.h" #include "bolt-exported.h" +#include "bolt-guard.h" #include "bolt-udev.h" #include @@ -31,20 +32,6 @@ /* forward declaration */ struct udev; -/* BoltPowerGuard */ - -#define BOLT_TYPE_POWER_GUARD bolt_power_guard_get_type () -G_DECLARE_FINAL_TYPE (BoltPowerGuard, bolt_power_guard, BOLT, POWER_GUARD, GObject); - -int bolt_power_guard_monitor (BoltPowerGuard *guard, - GError **error); - -const char * bolt_power_guard_get_id (BoltPowerGuard *guard); - -const char * bolt_power_guard_get_who (BoltPowerGuard *guard); - -guint bolt_power_guard_get_pid (BoltPowerGuard *guard); - /* BoltPower */ #define BOLT_TYPE_POWER bolt_power_get_type () @@ -58,12 +45,12 @@ BoltPowerState bolt_power_get_state (BoltPower *power); -BoltPowerGuard * bolt_power_acquire_full (BoltPower *power, +BoltGuard * bolt_power_acquire_full (BoltPower *power, const char *who, pid_t pid, GError **error); -BoltPowerGuard * bolt_power_acquire (BoltPower *power, +BoltGuard * bolt_power_acquire (BoltPower *power, GError **error); GList * bolt_power_list_guards (BoltPower *power); diff -Nru bolt-0.8/boltd/bolt-reaper.c bolt-0.9.1/boltd/bolt-reaper.c --- bolt-0.8/boltd/bolt-reaper.c 1970-01-01 00:00:00.000000000 +0000 +++ bolt-0.9.1/boltd/bolt-reaper.c 2020-12-01 11:26:01.000000000 +0000 @@ -0,0 +1,249 @@ +/* + * Copyright © 2020 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Christian J. Kellner + */ + +#include "config.h" + +#include "bolt-reaper.h" + +#include "bolt-log.h" +#include "bolt-unix.h" + + +#define REAPER_TIMEOUT 20 * 1000 // seconds + + +struct _BoltReaper +{ + GObject object; + + /* */ + guint timeout; + + /* */ + GHashTable *pids; + guint timeout_id; +}; + +enum { + PROP_0, + PROP_TIMEOUT, + PROP_LAST +}; + +static GParamSpec *props[PROP_LAST] = { NULL, }; + +enum { + PROCESS_DIED, + SIGNAL_LAST +}; + +static guint signals[SIGNAL_LAST] = {0}; + +G_DEFINE_TYPE (BoltReaper, + bolt_reaper, + G_TYPE_OBJECT); + + +static void +bolt_reaper_finalize (GObject *object) +{ + BoltReaper *reaper = BOLT_REAPER (object); + + g_clear_handle_id (&reaper->timeout_id, g_source_remove); + g_hash_table_unref (reaper->pids); + + G_OBJECT_CLASS (bolt_reaper_parent_class)->finalize (object); +} + +static void +bolt_reaper_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + BoltReaper *reaper = BOLT_REAPER (object); + + switch (prop_id) + { + + case PROP_TIMEOUT: + g_value_set_uint (value, reaper->timeout); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +bolt_reaper_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + BoltReaper *reaper = BOLT_REAPER (object); + + switch (prop_id) + { + case PROP_TIMEOUT: + reaper->timeout = g_value_get_uint (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +bolt_reaper_init (BoltReaper *reaper) +{ + reaper->pids = g_hash_table_new_full (g_direct_hash, + g_direct_equal, + NULL, + g_free); +} + +static void +bolt_reaper_class_init (BoltReaperClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = bolt_reaper_finalize; + gobject_class->get_property = bolt_reaper_get_property; + gobject_class->set_property = bolt_reaper_set_property; + + props[PROP_TIMEOUT] = + g_param_spec_uint ("timeout", + "Timeout", NULL, + 0, G_MAXINT, REAPER_TIMEOUT, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, + PROP_LAST, + props); + + + signals[PROCESS_DIED] = + g_signal_new ("process-died", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + NULL, + G_TYPE_NONE, + 2, G_TYPE_UINT, G_TYPE_STRING); +} + +static gboolean +bolt_reaper_timeout (gpointer user_data) +{ + BoltReaper *reaper = BOLT_REAPER (user_data); + GHashTableIter iter; + gpointer key, value; + + bolt_debug (LOG_TOPIC ("reaper"), "looking for dead processes"); + + g_hash_table_iter_init (&iter, reaper->pids); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + g_autofree char *name = NULL; + guint pid = GPOINTER_TO_UINT (key); + bolt_debug (LOG_TOPIC ("reaper"), "checking '%u'", pid); + + if (bolt_pid_is_alive ((pid_t) pid)) + continue; + + bolt_info (LOG_TOPIC ("reaper"), + "process '%u' is dead", + pid); + + name = (char *) value; + g_hash_table_iter_steal (&iter); + + g_signal_emit (reaper, + signals[PROCESS_DIED], + 0, + pid, name); + } + + if (g_hash_table_size (reaper->pids) == 0) + { + bolt_debug (LOG_TOPIC ("reaper"), "stopping"); + reaper->timeout_id = 0; + return G_SOURCE_REMOVE; + } + + return G_SOURCE_CONTINUE; +} + +BoltReaper * +bolt_reaper_new (void) +{ + BoltReaper *reaper; + + reaper = g_object_new (BOLT_TYPE_REAPER, NULL); + + return reaper; +} + +void +bolt_reaper_add_pid (BoltReaper *reaper, + guint pid, + const char *name) +{ + gpointer p = GUINT_TO_POINTER (pid); + + g_return_if_fail (BOLT_IS_REAPER (reaper)); + + g_hash_table_insert (reaper->pids, p, g_strdup (name)); + + if (reaper->timeout_id != 0) + return; + + reaper->timeout_id = g_timeout_add (reaper->timeout, + bolt_reaper_timeout, + reaper); + + bolt_info (LOG_TOPIC ("reaper"), "started"); +} + +gboolean +bolt_reaper_del_pid (BoltReaper *reaper, + guint pid) +{ + gpointer p = GUINT_TO_POINTER (pid); + + g_return_val_if_fail (BOLT_IS_REAPER (reaper), FALSE); + + return g_hash_table_remove (reaper->pids, p); +} + +gboolean +bolt_reaper_has_pid (BoltReaper *reaper, + guint pid) +{ + gpointer p = GUINT_TO_POINTER (pid); + + g_return_val_if_fail (BOLT_IS_REAPER (reaper), FALSE); + + return g_hash_table_contains (reaper->pids, p); +} diff -Nru bolt-0.8/boltd/bolt-reaper.h bolt-0.9.1/boltd/bolt-reaper.h --- bolt-0.8/boltd/bolt-reaper.h 1970-01-01 00:00:00.000000000 +0000 +++ bolt-0.9.1/boltd/bolt-reaper.h 2020-12-01 11:26:01.000000000 +0000 @@ -0,0 +1,43 @@ +/* + * Copyright © 2020 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Christian J. Kellner + */ + +#pragma once + +#include + + +G_BEGIN_DECLS + +#define BOLT_TYPE_REAPER bolt_reaper_get_type () +G_DECLARE_FINAL_TYPE (BoltReaper, bolt_reaper, BOLT, REAPER, GObject); + +BoltReaper * bolt_reaper_new (void); + +void bolt_reaper_add_pid (BoltReaper *reaper, + guint pid, + const char *name); + +gboolean bolt_reaper_del_pid (BoltReaper *reaper, + guint pid); + +gboolean bolt_reaper_has_pid (BoltReaper *reaper, + guint pid); + +G_END_DECLS diff -Nru bolt-0.8/boltd/bolt-store.c bolt-0.9.1/boltd/bolt-store.c --- bolt-0.8/boltd/bolt-store.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/boltd/bolt-store.c 2020-12-01 11:26:01.000000000 +0000 @@ -34,6 +34,16 @@ /* ************************************ */ /* BoltStore */ +static void bolt_store_initable_iface_init (GInitableIface *iface); + + +static gboolean bolt_store_initialize (GInitable *initable, + GCancellable *cancellable, + GError **error); + +static gboolean bolt_store_init_store (DIR *root, + GError **error); + struct _BoltStore { GObject object; @@ -43,6 +53,8 @@ GFile *devices; GFile *keys; GFile *times; + + guint version; }; @@ -50,6 +62,7 @@ PROP_STORE_0, PROP_ROOT, + PROP_VERSION, PROP_STORE_LAST }; @@ -66,9 +79,11 @@ static guint signals[SIGNAL_LAST] = {0}; -G_DEFINE_TYPE (BoltStore, - bolt_store, - G_TYPE_OBJECT) +G_DEFINE_TYPE_WITH_CODE (BoltStore, + bolt_store, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, + bolt_store_initable_iface_init)); static void @@ -104,6 +119,10 @@ g_value_set_object (value, store->root); break; + case PROP_VERSION: + g_value_set_uint (value, store->version); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } @@ -165,6 +184,13 @@ G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME); + store_props[PROP_VERSION] = + g_param_spec_uint ("version", + NULL, NULL, + 0, G_MAXUINT, 0, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (gobject_class, PROP_STORE_LAST, store_props); @@ -190,6 +216,44 @@ 1, G_TYPE_STRING); } +static void +bolt_store_initable_iface_init (GInitableIface *iface) +{ + iface->init = bolt_store_initialize; +} + +static gboolean +bolt_store_initialize (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GError) err = NULL; + g_autoptr(DIR) root = NULL; + g_autofree char *path = NULL; + BoltStore *store = BOLT_STORE (initable); + gboolean ok; + + path = g_file_get_path (store->root); + root = bolt_opendir (path, error); + + if (root == NULL) + return FALSE; + + ok = bolt_store_init_store (root, error); + if (!ok) + return FALSE; + + ok = bolt_read_uint_at (dirfd (root), + "version", + &store->version, + &err); + + if (!ok && !bolt_err_notfound (err)) + return bolt_error_propagate (error, &err); + + return TRUE; +} + /* internal methods */ #define DOMAIN_GROUP "domain" #define DEVICE_GROUP "device" @@ -197,22 +261,61 @@ #define CFG_FILE "boltd.conf" +static gboolean +bolt_store_init_store (DIR *root, + GError **error) +{ + gboolean empty; + gboolean ok; + + /* initialize an empty store with the basic layout, + * which currently is just a 'version' field, since + * all other directories are created on-demand */ + + ok = bolt_dir_is_empty (root, &empty, error); + if (!ok) + return FALSE; + + bolt_debug (LOG_TOPIC ("store"), "needs init: %s", + bolt_yesno (empty)); + + if (!empty) + return TRUE; + + bolt_info (LOG_TOPIC ("store"), "initializing"); + + /* store is empty, create the 'version' file */ + ok = bolt_write_uint_at (dirfd (root), + "version", + BOLT_STORE_VERSION, + error); + return ok; +} + /* public methods */ BoltStore * -bolt_store_new (const char *path) +bolt_store_new (const char *path, GError **error) { g_autoptr(GFile) root = NULL; BoltStore *store; root = g_file_new_for_path (path); - store = g_object_new (BOLT_TYPE_STORE, - "root", root, - NULL); - + store = g_initable_new (BOLT_TYPE_STORE, + NULL, error, + "root", root, + NULL); return store; } +guint +bolt_store_get_version (BoltStore *store) +{ + g_return_val_if_fail (BOLT_IS_STORE (store), 0); + + return store->version; +} + GKeyFile * bolt_store_config_load (BoltStore *store, GError **error) @@ -453,6 +556,10 @@ g_return_val_if_fail (domain != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + ok = bolt_domain_can_delete (domain, error); + if (!ok) + return FALSE; + uid = bolt_domain_get_uid (domain); path = g_file_get_child (store->domains, uid); @@ -487,9 +594,10 @@ gboolean ok; guint64 ctime; guint64 atime; - gint64 stime; + guint64 stime; + guint64 gen; gsize len; - guint keystate = 0; + guint keystate; g_return_val_if_fail (BOLT_IS_STORE (store), FALSE); g_return_val_if_fail (BOLT_IS_DEVICE (device), FALSE); @@ -514,16 +622,27 @@ G_KEY_FILE_KEEP_COMMENTS, &err); - if (!ok && bolt_err_exists (err)) - bolt_warn_err (err, LOG_TOPIC ("store"), LOG_DEV_UID (uid), - "could not load previously stored device"); + if (!ok) + { + if (!bolt_err_notfound (err)) + bolt_warn_err (err, LOG_TOPIC ("store"), LOG_DEV_UID (uid), + "could not load previously stored device"); + g_clear_error (&err); + } g_key_file_set_string (kf, DEVICE_GROUP, "name", bolt_device_get_name (device)); g_key_file_set_string (kf, DEVICE_GROUP, "vendor", bolt_device_get_vendor (device)); + gen = bolt_device_get_generation (device); + if (gen) + g_key_file_set_uint64 (kf, DEVICE_GROUP, "generation", gen); + type = bolt_device_get_device_type (device); g_key_file_set_string (kf, DEVICE_GROUP, "type", bolt_device_type_to_string (type)); + if (policy == BOLT_POLICY_DEFAULT) + policy = bolt_device_get_policy (device); + if (policy != BOLT_POLICY_DEFAULT) { const char *str = bolt_policy_to_string (policy); @@ -537,8 +656,8 @@ stime = bolt_device_get_storetime (device); - if (stime < 1) - stime = (gint64) bolt_now_in_seconds (); + if (stime == 0) + stime = bolt_now_in_seconds (); g_key_file_set_uint64 (kf, USER_GROUP, "storetime", stime); @@ -547,10 +666,9 @@ if (!data) return FALSE; + keystate = bolt_device_get_keystate (device); if (key) { - g_clear_error (&err); - ok = bolt_store_put_key (store, uid, key, &err); if (!ok) @@ -612,6 +730,7 @@ guint64 stime; guint64 atime = 0; guint64 ctime = 0; + guint gen; gsize len; g_return_val_if_fail (BOLT_IS_STORE (store), NULL); @@ -639,6 +758,11 @@ polstr = g_key_file_get_string (kf, USER_GROUP, "policy", NULL); label = g_key_file_get_string (kf, USER_GROUP, "label", NULL); + gen = g_key_file_get_int64 (kf, DEVICE_GROUP, "generation", &err); + if (err != NULL && !bolt_err_notfound (err)) + bolt_warn_err (err, LOG_TOPIC ("store"), "invalid generation"); + g_clear_error (&err); + type = bolt_enum_from_string (BOLT_TYPE_DEVICE_TYPE, typestr, &err); if (type == BOLT_DEVICE_UNKNOWN_TYPE) { @@ -682,7 +806,6 @@ stime = g_file_info_get_attribute_uint64 (info, "time::changed"); } - key = bolt_store_have_key (store, uid); if (name == NULL || vendor == NULL) @@ -702,6 +825,7 @@ "uid", uid, "name", name, "vendor", vendor, + "generation", gen, "type", type, "status", BOLT_STATUS_DISCONNECTED, "store", store, @@ -1096,3 +1220,105 @@ return journal; } + +gboolean +bolt_store_del_journal (BoltStore *store, + const char *type, + const char *name, + GError **error) +{ + g_autoptr(GError) err = NULL; + g_autoptr(GFile) root = NULL; + g_autoptr(GFile) journal = NULL; + gboolean ok; + + g_return_val_if_fail (store != NULL, FALSE); + g_return_val_if_fail (type != NULL, FALSE); + g_return_val_if_fail (name != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + root = g_file_get_child (store->root, type); + journal = g_file_get_child (root, name); + + ok = g_file_delete (journal, NULL, &err); + + if (!ok && !bolt_err_notfound (err)) + { + bolt_error_propagate (error, &err); + return FALSE; + } + + return TRUE; +} + +gboolean +bolt_store_has_journal (BoltStore *store, + const char *type, + const char *name) +{ + g_autoptr(GFile) root = NULL; + g_autoptr(GFile) journal = NULL; + + + g_return_val_if_fail (store != NULL, FALSE); + g_return_val_if_fail (type != NULL, FALSE); + g_return_val_if_fail (name != NULL, FALSE); + + root = g_file_get_child (store->root, type); + journal = g_file_get_child (root, name); + + return g_file_query_exists (journal, NULL); +} + +gboolean +bolt_store_upgrade (BoltStore *store, + gboolean *upgrade, + GError **error) +{ + g_autoptr(DIR) root = NULL; + g_autofree char *path = NULL; + gboolean need_upgrade; + gboolean ok; + + g_return_val_if_fail (BOLT_IS_STORE (store), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + need_upgrade = store->version != BOLT_STORE_VERSION; + + if (upgrade) + *upgrade = need_upgrade; + + if (!need_upgrade) + return TRUE; + + path = g_file_get_path (store->root); + root = bolt_opendir (path, error); + + if (!root) + return FALSE; + + ok = bolt_write_int_at (dirfd (root), + ".version-upgrade", + BOLT_STORE_VERSION, + error); + if (!ok) + return FALSE; + + ok = bolt_renameat (dirfd (root), + ".version-upgrade", + dirfd (root), + "version", + error); + + if (!ok) + { + bolt_unlink_at (dirfd (root), ".version-upgrade", 0, NULL); + return FALSE; + } + + store->version = BOLT_STORE_VERSION; + g_object_notify_by_pspec (G_OBJECT (store), + store_props[PROP_VERSION]); + + return ok; +} diff -Nru bolt-0.8/boltd/bolt-store.h bolt-0.9.1/boltd/bolt-store.h --- bolt-0.8/boltd/bolt-store.h 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/boltd/bolt-store.h 2020-12-01 11:26:01.000000000 +0000 @@ -30,11 +30,16 @@ G_BEGIN_DECLS +#define BOLT_STORE_VERSION 1 + /* BoltStore - database for devices, keys */ #define BOLT_TYPE_STORE bolt_store_get_type () G_DECLARE_FINAL_TYPE (BoltStore, bolt_store, BOLT, STORE, GObject); -BoltStore * bolt_store_new (const char *path); +BoltStore * bolt_store_new (const char *path, + GError **error); + +guint bolt_store_get_version (BoltStore *store); GKeyFile * bolt_store_config_load (BoltStore *store, GError **error); @@ -130,4 +135,18 @@ const char *name, GError **error); +gboolean bolt_store_del_journal (BoltStore *store, + const char *type, + const char *name, + GError **error); + +gboolean bolt_store_has_journal (BoltStore *store, + const char *type, + const char *name); + +/* store upgrades */ +gboolean bolt_store_upgrade (BoltStore *store, + gboolean *upgrade, + GError **error); + G_END_DECLS diff -Nru bolt-0.8/boltd/bolt-sysfs.c bolt-0.9.1/boltd/bolt-sysfs.c --- bolt-0.8/boltd/bolt-sysfs.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/boltd/bolt-sysfs.c 2020-12-01 11:26:01.000000000 +0000 @@ -30,8 +30,57 @@ #include #include +#include #include +typedef struct udev_device udev_device; +G_DEFINE_AUTOPTR_CLEANUP_FUNC (udev_device, udev_device_unref); + +void +bolt_ident_clear (BoltIdent *id) +{ + if (id->udev == NULL) + return; + + udev_device_unref (id->udev); + memset (id, 0, sizeof (BoltIdent)); +} + +static const char * +sysfs_get_sysattr_value (struct udev_device *dev, + const char *attr, + GError **error) +{ + const char *val; + + g_return_val_if_fail (dev != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + val = udev_device_get_sysattr_value (dev, attr); + + if (val == NULL) + { + int code = errno; + const char *path = udev_device_get_syspath (dev); + + g_set_error (error, BOLT_ERROR, BOLT_ERROR_UDEV, + "could not get '%s' for %s: %s", + attr, path, g_strerror (code)); + } + + return val; +} + +const char * +bolt_sysfs_device_get_unique_id (struct udev_device *dev, + GError **error) +{ + g_return_val_if_fail (dev != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + return sysfs_get_sysattr_value (dev, BOLT_SYSFS_UNIQUE_ID, error); +} + gint64 bolt_sysfs_device_get_time (struct udev_device *udev, BoltStatTime st) @@ -150,10 +199,155 @@ return s; } +static const char * +read_sysattr_name (struct udev_device *udev, + const char *attr, + GError **error) +{ + g_autofree char *s = NULL; + const char *v; + + s = g_strdup_printf ("%s_name", attr); + v = udev_device_get_sysattr_value (udev, s); + + if (v != NULL) + return v; + + return sysfs_get_sysattr_value (udev, attr, error); +} + +gboolean +bolt_sysfs_device_ident (struct udev_device *udev, + BoltIdent *id, + GError **error) +{ + const char *name; + const char *vendor; + + g_return_val_if_fail (udev != NULL, FALSE); + g_return_val_if_fail (id != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + vendor = read_sysattr_name (udev, "vendor", error); + if (vendor == NULL) + return FALSE; + + name = read_sysattr_name (udev, "device", error); + if (name == NULL) + return FALSE; + + id->udev = udev_device_ref (udev); + id->name = name; + id->vendor = vendor; + + return TRUE; +} + +gboolean +bolt_sysfs_host_ident (struct udev_device *dev, + BoltIdent *id, + GError **error) +{ + g_autoptr(udev_device) dmi = NULL; + struct udev *udev; + const char *name; + const char *vendor; + const char *attr; + gboolean ok; + + g_return_val_if_fail (dev != NULL, FALSE); + g_return_val_if_fail (id != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + /* First check if the host controller has normal device ident, + * which should be present for all controller that have a DROM. */ + ok = bolt_sysfs_device_ident (dev, id, NULL); + if (ok) + return TRUE; + + /* On embedded thunderbolt controllers without DROM, we + * fall back to using the SMBIOS/DMI system information */ + + udev = udev_device_get_udev (dev); + dmi = udev_device_new_from_syspath (udev, BOLT_SYSFS_DMI_ID); + + if (dmi == NULL) + { + int code = errno; + g_set_error (error, BOLT_ERROR, BOLT_ERROR_UDEV, + "could not open dmi/id device: %s", + g_strerror (code)); + return FALSE; + } + + attr = BOLT_SYSFS_DMI_SYS_VENDOR; + vendor = sysfs_get_sysattr_value (dmi, attr, error); + + if (vendor == NULL) + return FALSE; + + /* Almost all systems are using the product_name attribute + * for the human readable string, with exception of Lenovo, + * that instead uses the product_version, so we special + * case that. */ + attr = BOLT_SYSFS_DMI_PRODUCT_NAME; + + if (!g_ascii_strcasecmp (vendor, "lenovo")) + { + attr = BOLT_SYSFS_DMI_PRODUCT_VERSION; + vendor = "Lenovo"; + } + + name = sysfs_get_sysattr_value (dmi, attr, error); + + if (name == NULL) + return FALSE; + + id->udev = g_steal_pointer (&dmi); + id->name = name; + id->vendor = vendor; + + return TRUE; +} + +static int +bolt_syfs_count_tb_devices (struct udev *udev, + struct udev_device *parent, + GError **error) +{ + struct udev_enumerate *e; + struct udev_list_entry *l, *devices; + int r, count = 0; + + e = udev_enumerate_new (udev); + + udev_enumerate_add_match_subsystem (e, "thunderbolt"); + udev_enumerate_add_match_property (e, "DEVTYPE", "thunderbolt_device"); + + if (parent) + udev_enumerate_add_match_parent (e, parent); + + r = udev_enumerate_scan_devices (e); + if (r < 0) + { + g_set_error (error, BOLT_ERROR, BOLT_ERROR_UDEV, + "failed to scan udev: %s", + g_strerror (-r)); + return r; + } + + devices = udev_enumerate_get_list_entry (e); + udev_list_entry_foreach (l, devices) + count++; + + udev_enumerate_unref (e); + + return count; +} int -bolt_sysfs_count_domains (struct udev *udev, - GError **error) +bolt_sysfs_count_hosts (struct udev *udev, + GError **error) { struct udev_enumerate *e; struct udev_list_entry *l, *devices; @@ -175,13 +369,64 @@ devices = udev_enumerate_get_list_entry (e); udev_list_entry_foreach (l, devices) - count++; + { + struct udev_device *udevice = NULL; + const char *syspath; + int n; + + syspath = udev_list_entry_get_name (l); + udevice = udev_device_new_from_syspath (udev, syspath); + + if (udevice == NULL) + continue; + + n = bolt_syfs_count_tb_devices (udev, udevice, NULL); + if (n > 0) + count++; + + udev_device_unref (udevice); + } udev_enumerate_unref (e); return count; } +gboolean +bolt_sysfs_nhi_id_for_domain (struct udev_device *udev, + guint32 *id, + GError **error) +{ + struct udev_device *parent; + const char *str; + gboolean ok; + + ok = bolt_sysfs_device_is_domain (udev, error); + if (!ok) + return FALSE; + + parent = udev_device_get_parent (udev); + + if (parent == NULL) + { + g_set_error (error, BOLT_ERROR, BOLT_ERROR_UDEV, + "failed to get parent for domain: %s", + g_strerror (errno)); + return FALSE; + } + + str = udev_device_get_sysattr_value (parent, "device"); + if (str == NULL) + { + g_set_error (error, BOLT_ERROR, BOLT_ERROR_UDEV, + "failed to get PCI id for NHI device: %s", + g_strerror (errno)); + return FALSE; + } + + return bolt_str_parse_as_uint32 (str, id, error); +} + static gint sysfs_get_sysattr_value_as_int (struct udev_device *udev, const char *attr) @@ -219,6 +464,30 @@ return strlen (str); } +void +bolt_sysfs_read_link_speed (struct udev_device *udev, + BoltLinkSpeed *speed) +{ + struct + { + const char *name; + guint32 *item; + } entries[] = { + {BOLT_SYSFS_RX_LANES, &speed->rx.lanes}, + {BOLT_SYSFS_RX_SPEED, &speed->rx.speed}, + {BOLT_SYSFS_TX_LANES, &speed->tx.lanes}, + {BOLT_SYSFS_TX_SPEED, &speed->tx.speed}, + }; + + for (unsigned i = 0; i < G_N_ELEMENTS (entries); i++) + { + const char *name = entries[i].name; + int res = sysfs_get_sysattr_value_as_int (udev, name); + + *entries[i].item = res > 0 ? res : 0; + } +} + gboolean bolt_sysfs_info_for_device (struct udev_device *udev, gboolean full, @@ -227,6 +496,7 @@ { struct udev_device *parent; int auth; + int gen; g_return_val_if_fail (udev != NULL, FALSE); g_return_val_if_fail (info != NULL, FALSE); @@ -235,6 +505,7 @@ info->ctim = -1; info->full = FALSE; info->parent = NULL; + info->generation = 0; auth = sysfs_get_sysattr_value_as_int (udev, "authorized"); info->authorized = auth; @@ -263,6 +534,12 @@ if (parent != NULL) info->parent = udev_device_get_sysattr_value (parent, "unique_id"); + gen = sysfs_get_sysattr_value_as_int (udev, BOLT_SYSFS_GENERATION); + if (gen > 0) + info->generation = gen; + + bolt_sysfs_read_link_speed (udev, &info->linkspeed); + return TRUE; } @@ -332,3 +609,47 @@ return TRUE; } + +/* NHI PCI id related */ +static struct +{ + guint32 pci_id; + gboolean stable; /* Does the UUID change on reboot */ +} nhi_table[] = { + {0x157d, TRUE}, // WIN_RIDGE_2C_NHI + {0x15bf, TRUE}, // ALPINE_RIDGE_LP_NHI + {0x15d2, TRUE}, // ALPINE_RIDGE_C_4C_NHI + {0x15d9, TRUE}, // ALPINE_RIDGE_C_2C_NHI + {0x15dc, TRUE}, // ALPINE_RIDGE_LP_USBONLY_NHI + {0x15dd, TRUE}, // ALPINE_RIDGE_USBONLY_NH + {0x15de, TRUE}, // ALPINE_RIDGE_C_USBONLY_NHI + {0x15e8, TRUE}, // TITAN_RIDGE_2C_NHI + {0x15eb, TRUE}, // TITAN_RIDGE_4C_NHI + {0x8a0d, FALSE}, // ICL_NHI1 + {0x8a17, FALSE}, // ICL_NHI0 + {0x9a1b, FALSE}, // TGL_NHI0 + {0x9a1d, FALSE}, // TGL_NHI1 +}; + +gboolean +bolt_nhi_uuid_is_stable (guint32 pci_id, + gboolean *stability, + GError **error) +{ + g_return_val_if_fail (stability != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + for (gsize i = 0; i < G_N_ELEMENTS (nhi_table); i++) + { + if (pci_id == nhi_table[i].pci_id) + { + *stability = nhi_table[i].stable; + return TRUE; + } + } + + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "unknown NHI PCI id '0x%04x'", pci_id); + + return FALSE; +} diff -Nru bolt-0.8/boltd/bolt-sysfs.h bolt-0.9.1/boltd/bolt-sysfs.h --- bolt-0.8/boltd/bolt-sysfs.h 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/boltd/bolt-sysfs.h 2020-12-01 11:26:01.000000000 +0000 @@ -22,6 +22,8 @@ #include "bolt-enums.h" +#include "bolt-wire.h" + #include struct udev; @@ -29,6 +31,25 @@ G_BEGIN_DECLS +/* Device identification */ +typedef struct _BoltIdent BoltIdent; +struct _BoltIdent +{ + struct udev_device *udev; + + const char *name; + const char *vendor; +}; + +#define BOLT_IDENT_INIT {NULL, NULL, NULL} + +void bolt_ident_clear (BoltIdent *id); + +G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (BoltIdent, bolt_ident_clear); + +const char * bolt_sysfs_device_get_unique_id (struct udev_device *dev, + GError **error); + typedef enum BoltStatTime { BOLT_ST_ATIME, BOLT_ST_CTIME, @@ -47,8 +68,21 @@ BoltSecurity bolt_sysfs_security_for_device (struct udev_device *udev, GError **error); -int bolt_sysfs_count_domains (struct udev *udev, - GError **error); +gboolean bolt_sysfs_device_ident (struct udev_device *udev, + BoltIdent *id, + GError **error); + +gboolean bolt_sysfs_host_ident (struct udev_device *udev, + BoltIdent *id, + GError **error); + +int bolt_sysfs_count_hosts (struct udev *udev, + GError **error); + +gboolean bolt_sysfs_nhi_id_for_domain (struct udev_device *udev, + guint32 *id, + GError **error); + typedef struct _BoltDevInfo { @@ -62,6 +96,10 @@ gint64 ctim; const char *syspath; const char *parent; /* the uid */ + guint generation; + + /* link speed, may be 0 if unknown */ + BoltLinkSpeed linkspeed; } BoltDevInfo; @@ -70,6 +108,9 @@ BoltDevInfo *info, GError **error); +void bolt_sysfs_read_link_speed (struct udev_device *udev, + BoltLinkSpeed *speed); + gboolean bolt_sysfs_read_boot_acl (struct udev_device *udev, GStrv *out, GError **error); @@ -82,4 +123,9 @@ gboolean *out, GError **error); +/* NHI id related functions*/ +gboolean bolt_nhi_uuid_is_stable (guint32 pci_id, + gboolean *stability, + GError **error); + G_END_DECLS diff -Nru bolt-0.8/boltd/bolt-udev.c bolt-0.9.1/boltd/bolt-udev.c --- bolt-0.8/boltd/bolt-udev.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/boltd/bolt-udev.c 2020-12-01 11:26:01.000000000 +0000 @@ -407,12 +407,12 @@ /* thunderbolt specific helpers */ int -bolt_udev_count_domains (BoltUdev *udev, - GError **error) +bolt_udev_count_hosts (BoltUdev *udev, + GError **error) { g_return_val_if_fail (BOLT_IS_UDEV (udev), -1); - return bolt_sysfs_count_domains (udev->udev, error); + return bolt_sysfs_count_hosts (udev->udev, error); } gboolean diff -Nru bolt-0.8/boltd/bolt-udev.h bolt-0.9.1/boltd/bolt-udev.h --- bolt-0.8/boltd/bolt-udev.h 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/boltd/bolt-udev.h 2020-12-01 11:26:01.000000000 +0000 @@ -45,8 +45,8 @@ GError **error); /* thunderbolt specific helpers */ -int bolt_udev_count_domains (BoltUdev *udev, - GError **error); +int bolt_udev_count_hosts (BoltUdev *udev, + GError **error); gboolean bolt_udev_detect_force_power (BoltUdev *udev, char **path, diff -Nru bolt-0.8/boltd/bolt-watchdog.c bolt-0.9.1/boltd/bolt-watchdog.c --- bolt-0.8/boltd/bolt-watchdog.c 1970-01-01 00:00:00.000000000 +0000 +++ bolt-0.9.1/boltd/bolt-watchdog.c 2020-12-01 11:26:01.000000000 +0000 @@ -0,0 +1,238 @@ +/* + * Copyright © 2019 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Christian J. Kellner + */ + +#include "config.h" + +#include "bolt-watchdog.h" + +#include "bolt-log.h" +#include "bolt-str.h" +#include "bolt-time.h" +#include "bolt-unix.h" + +#include + +/* prototypes */ +static void watchdog_initable_iface_init (GInitableIface *iface); +static gboolean bolt_watchdog_initialize (GInitable *initable, + GCancellable *cancellable, + GError **error); + +static gboolean bolt_watchdog_on_pulse (gpointer user_data); +/* */ +struct _BoltWatchdog +{ + GObject object; + + /* */ + guint64 timeout; /* the actual timeout (usec) */ + guint64 pulse; /* the calculated pulse (sec) */ + guint pulse_id; /* source id for the pulse */ + +}; + +enum { + PROP_0, + + PROP_TIMEOUT, + PROP_PULSE, + + PROP_LAST +}; + +static GParamSpec *props[PROP_LAST] = { NULL, }; + + +G_DEFINE_TYPE_WITH_CODE (BoltWatchdog, + bolt_watchdog, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, + watchdog_initable_iface_init)); + +static void +bolt_watchdog_finalize (GObject *object) +{ + BoltWatchdog *dog = BOLT_WATCHDOG (object); + + g_clear_handle_id (&dog->pulse_id, g_source_remove); + + G_OBJECT_CLASS (bolt_watchdog_parent_class)->finalize (object); +} + + +static void +bolt_watchdog_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + BoltWatchdog *dog = BOLT_WATCHDOG (object); + + switch (prop_id) + { + case PROP_TIMEOUT: + g_value_set_uint64 (value, dog->timeout); + break; + + case PROP_PULSE: + g_value_set_uint (value, dog->pulse); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +bolt_watchdog_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + BoltWatchdog *dog = BOLT_WATCHDOG (object); + + switch (prop_id) + { + case PROP_TIMEOUT: + dog->timeout = g_value_get_uint64 (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +bolt_watchdog_init (BoltWatchdog *dog) +{ +} + +static void +bolt_watchdog_class_init (BoltWatchdogClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->get_property = bolt_watchdog_get_property; + gobject_class->set_property = bolt_watchdog_set_property; + gobject_class->finalize = bolt_watchdog_finalize; + + props[PROP_TIMEOUT] = + g_param_spec_uint64 ("timeout", + "Timeout", NULL, + 0, G_MAXUINT64, 0, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS); + + props[PROP_PULSE] = + g_param_spec_uint ("pulse", + "Pulse", NULL, + 0, G_MAXUINT - 1, 0, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, + PROP_LAST, + props); +} + +static void +watchdog_initable_iface_init (GInitableIface *iface) +{ + iface->init = bolt_watchdog_initialize; +} + +static gboolean +bolt_watchdog_initialize (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + BoltWatchdog *dog = BOLT_WATCHDOG (initable); + guint64 quot, rem; + guint pulse; + guint tid; + int r; + + r = bolt_sd_watchdog_enabled (&dog->timeout, error); + + if (r < 0) + return FALSE; + else if (r == 0) + return TRUE; + + quot = dog->timeout / G_USEC_PER_SEC; + rem = dog->timeout % G_USEC_PER_SEC; + + if (quot < 2 || quot >= G_MAXUINT) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + "invalid timeout: %" G_GUINT64_FORMAT, dog->timeout); + return FALSE; + } + + if (rem != 0) + bolt_warn (LOG_TOPIC ("watchdog"), + "sub-second precision timeout: " + "%" G_GUINT64_FORMAT ". Rounding down.", rem); + + /* we send a pulse/ping right in the middle of the timeout + * period to ensure we never miss one */ + pulse = quot / 2; + + tid = g_timeout_add_seconds (pulse, bolt_watchdog_on_pulse, dog); + dog->pulse_id = tid; + dog->pulse = pulse; + + /* we have an active timeout enabled */ + bolt_info (LOG_TOPIC ("watchdog"), "enabled [pulse: %lus]", pulse); + + return TRUE; +} + +/* internal methods */ +static gboolean +bolt_watchdog_on_pulse (gpointer user_data) +{ + g_autoptr(GError) err = NULL; + gboolean ok; + gboolean sent; + + ok = bolt_sd_notify_literal ("WATCHDOG=1", &sent, &err); + + if (!ok) + bolt_warn_err (err, LOG_TOPIC ("watchdog"), "failed to send ping"); + else + bolt_debug (LOG_TOPIC ("watchdog"), "ping [sent: %s]", + bolt_yesno (sent)); + + return G_SOURCE_CONTINUE; +} + +/* public methods */ +BoltWatchdog * +bolt_watchdog_new (GError **error) +{ + BoltWatchdog *watchdog; + + watchdog = g_initable_new (BOLT_TYPE_WATCHDOG, + NULL, error, + NULL); + + return watchdog; +} diff -Nru bolt-0.8/boltd/bolt-watchdog.h bolt-0.9.1/boltd/bolt-watchdog.h --- bolt-0.8/boltd/bolt-watchdog.h 1970-01-01 00:00:00.000000000 +0000 +++ bolt-0.9.1/boltd/bolt-watchdog.h 2020-12-01 11:26:01.000000000 +0000 @@ -0,0 +1,34 @@ +/* + * Copyright © 2019 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Christian J. Kellner + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +/* BoltWatchdog - Idle and status tracking */ + +#define BOLT_TYPE_WATCHDOG bolt_watchdog_get_type () +G_DECLARE_FINAL_TYPE (BoltWatchdog, bolt_watchdog, BOLT, WATCHDOG, GObject); + +BoltWatchdog * bolt_watchdog_new (GError **error); + +G_END_DECLS diff -Nru bolt-0.8/cli/bolt-client.c bolt-0.9.1/cli/bolt-client.c --- bolt-0.8/cli/bolt-client.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/cli/bolt-client.c 2020-12-01 11:26:01.000000000 +0000 @@ -288,8 +288,8 @@ GAsyncResult *res, gpointer user_data) { + g_autoptr(GTask) task = user_data; GError *error = NULL; - GTask *task = user_data; GObject *obj; obj = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, &error); @@ -302,7 +302,6 @@ } g_task_return_pointer (task, obj, g_object_unref); - g_object_unref (task); } static void @@ -321,6 +320,7 @@ g_prefix_error (&error, "could not connect to D-Bus: "); /* error ownership gets transferred to the task */ g_task_return_error (task, error); + g_object_unref (task); return; } diff -Nru bolt-0.8/cli/bolt-device.c bolt-0.9.1/cli/bolt-device.c --- bolt-0.8/cli/bolt-device.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/cli/bolt-device.c 2020-12-01 11:26:01.000000000 +0000 @@ -25,6 +25,7 @@ #include "bolt-enums.h" #include "bolt-error.h" #include "bolt-names.h" +#include "bolt-wire.h" #include @@ -40,6 +41,7 @@ PROP_UID, PROP_NAME, PROP_VENDOR, + PROP_GEN, PROP_TYPE, PROP_STATUS, PROP_AUTHFLAGS, @@ -48,6 +50,7 @@ PROP_DOMAIN, PROP_CONNTIME, PROP_AUTHTIME, + PROP_LINKSPEED, PROP_STORED, PROP_POLICY, @@ -75,7 +78,7 @@ props[PROP_UID] = g_param_spec_string ("uid", "Uid", - "The unique identifer.", + "The unique identifier.", "unknown", G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); @@ -93,6 +96,12 @@ "unknown", G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + props[PROP_GEN] = + g_param_spec_uint ("generation", "Generation", + "The generation of the controller chip.", + 0, G_MAXUINT, 0, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS); props[PROP_TYPE] = g_param_spec_enum ("type", "Type", @@ -154,6 +163,13 @@ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + props[PROP_LINKSPEED] = + g_param_spec_boxed ("linkspeed", "LinkSpeed", + "The speed to the parent", + BOLT_TYPE_LINK_SPEED, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS); + props[PROP_STORED] = g_param_spec_boolean ("stored", "Stored", "Is the device recorded in the database?", @@ -166,7 +182,7 @@ "What to do when the device is connected?", BOLT_TYPE_POLICY, BOLT_POLICY_DEFAULT, - G_PARAM_READABLE | + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); props[PROP_KEY] = @@ -210,7 +226,8 @@ GCancellable *cancel, GError **error) { - BoltDevice *dev; + g_autoptr(BoltDevice) dev = NULL; + gboolean ok; g_return_val_if_fail (G_IS_DBUS_CONNECTION (bus), NULL); g_return_val_if_fail (path != NULL, NULL); @@ -226,7 +243,20 @@ "g-interface-name", BOLT_DBUS_DEVICE_INTERFACE, NULL); - return dev; + if (dev == NULL) + return NULL; + + ok = bolt_proxy_set_wireconv (BOLT_PROXY (dev), + props[PROP_LINKSPEED], + "linkspeed", + bolt_link_speed_to_wire, + bolt_link_speed_from_wire, + error); + + if (!ok) + return NULL; + + return g_steal_pointer (&dev); } gboolean @@ -349,6 +379,18 @@ return str; } +guint +bolt_device_get_generation (BoltDevice *dev) +{ + guint32 val = 0; + + g_return_val_if_fail (BOLT_IS_DEVICE (dev), 0); + + val = bolt_proxy_get_uint32_by_pspec (dev, props[PROP_GEN]); + + return (guint) val; +} + BoltDeviceType bolt_device_get_device_type (BoltDevice *dev) { @@ -452,6 +494,27 @@ return val; } +void +bolt_device_get_linkspeed (BoltDevice *dev, + BoltLinkSpeed *speed) +{ + g_auto(GValue) value = G_VALUE_INIT; + BoltLinkSpeed *ls; + gboolean ok; + + g_return_if_fail (BOLT_IS_DEVICE (dev)); + g_return_if_fail (speed != NULL); + + ok = bolt_proxy_get_dbus_property (BOLT_PROXY (dev), + props[PROP_LINKSPEED], + &value); + if (!ok) + return; + + ls = g_value_get_boxed (&value); + *speed = *ls; +} + gboolean bolt_device_is_stored (BoltDevice *dev) { diff -Nru bolt-0.8/cli/bolt-device.h bolt-0.9.1/cli/bolt-device.h --- bolt-0.8/cli/bolt-device.h 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/cli/bolt-device.h 2020-12-01 11:26:01.000000000 +0000 @@ -22,6 +22,7 @@ #include "bolt-enums.h" #include "bolt-proxy.h" +#include "bolt-wire.h" G_BEGIN_DECLS @@ -55,6 +56,8 @@ const char * bolt_device_get_vendor (BoltDevice *dev); +guint bolt_device_get_generation (BoltDevice *dev); + BoltDeviceType bolt_device_get_device_type (BoltDevice *dev); gboolean bolt_device_is_host (BoltDevice *dev); @@ -73,6 +76,9 @@ guint64 bolt_device_get_authtime (BoltDevice *dev); +void bolt_device_get_linkspeed (BoltDevice *dev, + BoltLinkSpeed *speed); + gboolean bolt_device_is_stored (BoltDevice *dev); BoltPolicy bolt_device_get_policy (BoltDevice *dev); diff -Nru bolt-0.8/cli/bolt-domain.c bolt-0.9.1/cli/bolt-domain.c --- bolt-0.8/cli/bolt-domain.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/cli/bolt-domain.c 2020-12-01 11:26:01.000000000 +0000 @@ -91,7 +91,7 @@ g_param_spec_boxed ("bootacl", "BootACL", "Pre-boot access control list (uuids).", G_TYPE_STRV, - G_PARAM_READABLE | + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); props[PROP_IOMMU] = diff -Nru bolt-0.8/cli/bolt-proxy.c bolt-0.9.1/cli/bolt-proxy.c --- bolt-0.8/cli/bolt-proxy.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/cli/bolt-proxy.c 2020-12-01 11:26:01.000000000 +0000 @@ -298,6 +298,66 @@ } /* public methods */ +gboolean +bolt_proxy_set_wireconv (BoltProxy *proxy, + GParamSpec *param_spec, + const char *custom_id, + BoltConvToWire to_wire, + BoltConvFromWire from_wire, + GError **error) +{ + GDBusInterfaceInfo *info; + GDBusPropertyInfo *pi; + BoltProxyClass *klass; + const char *nick; + BoltWireConv *conv; + + klass = BOLT_PROXY_GET_CLASS (proxy); + + nick = g_param_spec_get_nick (param_spec); + conv = g_hash_table_lookup (klass->priv->wire_convs, nick); + + if (conv) + return TRUE; + + info = g_dbus_proxy_get_interface_info (G_DBUS_PROXY (proxy)); + + if (info == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "could not find dbus interface info"); + return FALSE; + } + + pi = g_dbus_interface_info_lookup_property (info, nick); + if (pi == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "could not find dbus property info"); + return FALSE; + } + + conv = bolt_wire_conv_custom (G_VARIANT_TYPE (pi->signature), + param_spec, + custom_id, + to_wire, + from_wire); + + if (conv == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "could create conversion helper"); + return FALSE; + } + + /* nick is valid as long as spec is valid and + * a reference to spec is held by conv */ + g_hash_table_insert (klass->priv->wire_convs, + (gpointer) nick, conv); + + return TRUE; +} + void bolt_proxy_property_getter (GObject *object, guint prop_id, diff -Nru bolt-0.8/cli/bolt-proxy.h bolt-0.9.1/cli/bolt-proxy.h --- bolt-0.8/cli/bolt-proxy.h 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/cli/bolt-proxy.h 2020-12-01 11:26:01.000000000 +0000 @@ -21,6 +21,7 @@ #pragma once #include +#include G_BEGIN_DECLS @@ -55,6 +56,13 @@ gpointer padding[10]; }; +gboolean bolt_proxy_set_wireconv (BoltProxy *proxy, + GParamSpec *param_spec, + const char *custom_id, + BoltConvToWire to_wire, + BoltConvFromWire from_wire, + GError **error); + void bolt_proxy_property_getter (GObject *object, guint prop_id, GValue *value, diff -Nru bolt-0.8/cli/boltctl-config.c bolt-0.9.1/cli/boltctl-config.c --- bolt-0.8/cli/boltctl-config.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/cli/boltctl-config.c 2020-12-01 11:26:01.000000000 +0000 @@ -346,7 +346,7 @@ res = property_set (target, prop, value, &err); if (res == EXIT_FAILURE) - g_printerr ("%s\n", err->message); + report_error (NULL, err); return res; } diff -Nru bolt-0.8/cli/boltctl-monitor.c bolt-0.9.1/cli/boltctl-monitor.c --- bolt-0.8/cli/boltctl-monitor.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/cli/boltctl-monitor.c 2020-12-01 11:26:01.000000000 +0000 @@ -104,6 +104,8 @@ g_value_init (&prop_val, G_PARAM_SPEC_VALUE_TYPE (pspec)); g_value_init (&str_val, G_TYPE_STRING); + g_print ("%" G_GINT64_FORMAT " ", g_get_real_time ()); + g_object_get_property (G_OBJECT (dev), prop_name, &prop_val); g_print ("[%s] %30s | %10s -> ", uid, dev_name, prop_name); @@ -127,8 +129,8 @@ GDBusConnection *bus; BoltDevice *dev; GPtrArray *devices = user_data; - - g_print (" DeviceAdded: %s\n", opath); + const char *name; + const char *uid; bus = g_dbus_proxy_get_connection (G_DBUS_PROXY (cli)); dev = bolt_device_new_for_object_path (bus, opath, NULL, &err); @@ -139,6 +141,12 @@ return; } + uid = bolt_device_get_uid (dev); + name = bolt_device_get_name (dev); + + g_print ("%" G_GINT64_FORMAT " ", g_get_real_time ()); + g_print ("[%s] %30s | DeviceAdded @ %s\n", uid, name, opath); + g_ptr_array_add (devices, dev); g_signal_connect (dev, "notify", G_CALLBACK (handle_device_changed), @@ -152,8 +160,8 @@ { GPtrArray *devices = user_data; BoltDevice *device = NULL; - - g_print (" DeviceRemoved: %s\n", opath); + const char *name; + const char *uid; for (guint i = 0; i < devices->len; i++) { @@ -173,6 +181,12 @@ return; } + uid = bolt_device_get_uid (device); + name = bolt_device_get_name (device); + + g_print ("%" G_GINT64_FORMAT " ", g_get_real_time ()); + g_print ("[%s] %30s | DeviceRemoved @ %s\n", uid, name, opath); + g_signal_handlers_block_by_func (device, handle_device_changed, devices); g_ptr_array_remove_fast (devices, device); } @@ -184,6 +198,8 @@ { gboolean probing = bolt_client_is_probing (client); + g_print ("%" G_GINT64_FORMAT " ", g_get_real_time ()); + if (probing) g_print ("Probing started\n"); else diff -Nru bolt-0.8/cli/boltctl.c bolt-0.9.1/cli/boltctl.c --- bolt-0.8/cli/boltctl.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/cli/boltctl.c 2020-12-01 11:26:01.000000000 +0000 @@ -103,6 +103,31 @@ return usage_error (error); } +int +report_error (const char *prefix, GError *error) +{ + g_printerr ("%s:", g_get_application_name ()); + g_printerr ("%s error: %s", bolt_color (ANSI_RED), bolt_color (ANSI_NORMAL)); + + if (prefix) + g_printerr ("%s", prefix); + + if (error) + { + if (g_dbus_error_is_remote_error (error)) + g_dbus_error_strip_remote_error (error); + + if (prefix) + g_printerr (": "); + + g_printerr ("%s", error->message); + } + + g_printerr ("\n"); + + return EXIT_FAILURE; +} + /* device related commands */ static gboolean @@ -129,6 +154,31 @@ return ts > 0; } +static gboolean +format_generation (guint generation, char *buffer, size_t n) +{ + switch (generation) + { + case 1: + case 2: + case 3: + g_snprintf (buffer, n, "Thunderbolt %u", generation); + return TRUE; + + case 4: + g_snprintf (buffer, n, "USB4"); + return TRUE; + + case 0: + g_snprintf (buffer, n, "Unknown"); + return FALSE; + + default: + g_snprintf (buffer, n, "%u", generation); + return TRUE; + } +} + void print_device (BoltDevice *dev, gboolean verbose) { @@ -154,11 +204,13 @@ gboolean stored; gboolean pcie; char buf[256]; + guint gen; path = g_dbus_proxy_get_object_path (G_DBUS_PROXY (dev)); uid = bolt_device_get_uid (dev); name = bolt_device_get_name (dev); vendor = bolt_device_get_vendor (dev); + gen = bolt_device_get_generation (dev); type = bolt_device_get_device_type (dev); status = bolt_device_get_status (dev); aflags = bolt_device_get_authflags (dev); @@ -236,12 +288,15 @@ g_print (" %s uuid: %s\n", tree_branch, format_uid (uid)); if (verbose) g_print (" %s dbus path: %s\n", tree_branch, path); + if (format_generation (gen, buf, sizeof (buf)) || verbose) + g_print (" %s generation: %s\n", tree_branch, buf); g_print (" %s status: %s\n", tree_branch, status_text); if (bolt_status_is_connected (status)) { g_autofree char *flags = NULL; const char *domain; + BoltLinkSpeed speed = {0, }; domain = bolt_device_get_domain (dev); g_print (" %s %s domain: %s\n", @@ -261,6 +316,25 @@ syspath); } + bolt_device_get_linkspeed (dev, &speed); + if (speed.rx.lanes && speed.rx.speed) + { + g_print (" %s %s rx speed: %u Gb/s = %u lanes * %u Gb/s\n", + bolt_glyph (TREE_VERTICAL), + tree_branch, + speed.rx.lanes * speed.rx.speed, + speed.rx.lanes, speed.rx.speed); + } + + if (speed.tx.lanes && speed.tx.speed) + { + g_print (" %s %s tx speed: %u Gb/s = %u lanes * %u Gb/s\n", + bolt_glyph (TREE_VERTICAL), + tree_branch, + speed.tx.lanes * speed.tx.speed, + speed.tx.lanes, speed.tx.speed); + } + flags = bolt_flags_to_string (BOLT_TYPE_AUTH_FLAGS, aflags, NULL); g_print (" %s %s authflags: %s\n", bolt_glyph (TREE_VERTICAL), @@ -432,10 +506,7 @@ client = bolt_client_new (&error); if (!client) - { - g_error ("Could not create client: %s", error->message); - return EXIT_FAILURE; - } + return report_error ("could not create client", error); cmd = subcommands_find (subcommands, cmdname, &error); diff -Nru bolt-0.8/cli/boltctl.h bolt-0.9.1/cli/boltctl.h --- bolt-0.8/cli/boltctl.h 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/cli/boltctl.h 2020-12-01 11:26:01.000000000 +0000 @@ -53,6 +53,8 @@ int usage_error (GError *error); int usage_error_need_arg (const char *arg); int usage_error_too_many_args (void); +int report_error (const char *prefix, + GError *error); void print_device (BoltDevice *dev, gboolean verbose); diff -Nru bolt-0.8/common/bolt-dbus.c bolt-0.9.1/common/bolt-dbus.c --- bolt-0.8/common/bolt-dbus.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/common/bolt-dbus.c 2020-12-01 11:26:01.000000000 +0000 @@ -21,6 +21,7 @@ #include "config.h" #include "bolt-dbus.h" +#include "bolt-error.h" #include "bolt-dbus-resource.h" #include "bolt-str.h" @@ -101,3 +102,39 @@ return info; } + +gboolean +bolt_dbus_get_sender_pid (GDBusMethodInvocation *invocation, + guint *pid, + GError **error) +{ + g_autoptr(GVariant) res = NULL; + g_autoptr(GError) err = NULL; + GDBusConnection *con; + const char *sender; + + con = g_dbus_method_invocation_get_connection (invocation); + sender = g_dbus_method_invocation_get_sender (invocation); + + res = g_dbus_connection_call_sync (con, + "org.freedesktop.DBus", + "/", + "org.freedesktop.DBus", + "GetConnectionUnixProcessID", + g_variant_new ("(s)", sender), + G_VARIANT_TYPE ("(u)"), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, + &err); + if (res == NULL) + { + g_set_error (error, BOLT_ERROR, BOLT_ERROR_FAILED, + "could not get pid of caller: %s", + err->message); + return FALSE; + } + + g_variant_get (res, "(u)", pid); + + return TRUE; +} diff -Nru bolt-0.8/common/bolt-dbus.h bolt-0.9.1/common/bolt-dbus.h --- bolt-0.8/common/bolt-dbus.h 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/common/bolt-dbus.h 2020-12-01 11:26:01.000000000 +0000 @@ -33,4 +33,7 @@ const char *interface_name, GError **error); +gboolean bolt_dbus_get_sender_pid (GDBusMethodInvocation *invocation, + guint *pid, + GError **error); G_END_DECLS diff -Nru bolt-0.8/common/bolt-error.h bolt-0.9.1/common/bolt-error.h --- bolt-0.8/common/bolt-error.h 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/common/bolt-error.h 2020-12-01 11:26:01.000000000 +0000 @@ -36,7 +36,7 @@ * * Error codes used inside Bolt. */ -enum { +typedef enum { BOLT_ERROR_FAILED = 0, BOLT_ERROR_UDEV, BOLT_ERROR_NOKEY, diff -Nru bolt-0.8/common/bolt-glue.c bolt-0.9.1/common/bolt-glue.c --- bolt-0.8/common/bolt-glue.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/common/bolt-glue.c 2020-12-01 11:26:01.000000000 +0000 @@ -25,6 +25,7 @@ #include "bolt-enums.h" #include "bolt-str.h" +#include GParamSpec * bolt_param_spec_override (GObjectClass *klass, @@ -229,20 +230,12 @@ typedef enum { BOLT_WIRE_CONV_NATIVE, + BOLT_WIRE_CONV_CUSTOM, BOLT_WIRE_CONV_ENUM_AS_STRING, BOLT_WIRE_CONV_FLAGS_AS_STRING, BOLT_WIRE_CONV_OBJECT_AS_STRING, } BoltWireConvType; -typedef GVariant * (*conv_to_wire) (BoltWireConv *conv, - const GValue *value, - GError **error); - -typedef gboolean (*conv_from_str) (BoltWireConv *conv, - GVariant *wire, - GValue *value, - GError **error); - struct _BoltWireConv { /* book-keeping */ @@ -254,9 +247,11 @@ /* */ BoltWireConvType conv_type; - conv_to_wire to_wire; - conv_from_str from_wire; + BoltConvToWire to_wire; + BoltConvFromWire from_wire; + /* */ + char *custom_id; }; /* helper */ @@ -304,8 +299,6 @@ gboolean ok; gint v; - wire_conv_init_value_if_needed (conv, value); - es = G_PARAM_SPEC_ENUM (conv->prop_spec); str = g_variant_get_string (wire, NULL); @@ -326,7 +319,7 @@ GError **error) { GParamSpecFlags *fs; - const char *str; + char *str; guint uv; fs = G_PARAM_SPEC_FLAGS (conv->prop_spec); @@ -337,7 +330,7 @@ if (str == NULL) return NULL; - return g_variant_new_string (str); + return g_variant_new_take_string (str); } static gboolean @@ -351,8 +344,6 @@ const char *str; guint v; - wire_conv_init_value_if_needed (conv, value); - fs = G_PARAM_SPEC_FLAGS (conv->prop_spec); str = g_variant_get_string (wire, NULL); @@ -456,8 +447,6 @@ { const char *str = g_variant_get_string (wire, NULL); - wire_conv_init_value_if_needed (conv, value); - if (str && *str == '\0') str = NULL; @@ -488,6 +477,8 @@ { g_variant_type_free (conv->wire_type); g_param_spec_unref (conv->prop_spec); + g_clear_pointer (&conv->custom_id, g_free); + g_free (conv); } } @@ -536,6 +527,11 @@ case BOLT_WIRE_CONV_OBJECT_AS_STRING: return "object-as-string"; + + case BOLT_WIRE_CONV_CUSTOM: + if (conv->custom_id) + return conv->custom_id; + return "custom"; } return "*unknown*"; @@ -548,13 +544,14 @@ BoltWireConv *conv; gboolean as_str; - g_return_val_if_fail (wire_type != NULL, FALSE); - g_return_val_if_fail (prop_spec != NULL, FALSE); + g_return_val_if_fail (wire_type != NULL, NULL); + g_return_val_if_fail (prop_spec != NULL, NULL); conv = g_new (BoltWireConv, 1); conv->ref_count = 1; conv->wire_type = g_variant_type_copy (wire_type); conv->prop_spec = g_param_spec_ref (prop_spec); + conv->custom_id = NULL; as_str = g_variant_type_equal (wire_type, G_VARIANT_TYPE_STRING); @@ -592,6 +589,35 @@ return conv; } +BoltWireConv * +bolt_wire_conv_custom (const GVariantType *wire_type, + GParamSpec *prop_spec, + const char *custom_id, + BoltConvToWire to_wire, + BoltConvFromWire from_wire) +{ + BoltWireConv *conv; + + g_return_val_if_fail (wire_type != NULL, NULL); + g_return_val_if_fail (prop_spec != NULL, NULL); + + g_return_val_if_fail (wire_type != NULL, NULL); + g_return_val_if_fail (prop_spec != NULL, NULL); + + conv = g_new (BoltWireConv, 1); + conv->ref_count = 1; + + conv->conv_type = BOLT_WIRE_CONV_CUSTOM; + conv->wire_type = g_variant_type_copy (wire_type); + conv->prop_spec = g_param_spec_ref (prop_spec); + conv->custom_id = g_strdup (custom_id); + + conv->to_wire = to_wire; + conv->from_wire = from_wire; + + return conv; +} + GVariant * bolt_wire_conv_to_wire (BoltWireConv *conv, const GValue *value, @@ -627,6 +653,8 @@ g_return_val_if_fail (value != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + wire_conv_init_value_if_needed (conv, value); + ok = conv->from_wire (conv, wire, value, error); return ok; diff -Nru bolt-0.8/common/bolt-glue.h bolt-0.9.1/common/bolt-glue.h --- bolt-0.8/common/bolt-glue.h 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/common/bolt-glue.h 2020-12-01 11:26:01.000000000 +0000 @@ -20,7 +20,7 @@ #pragma once -#include +#include G_BEGIN_DECLS @@ -43,6 +43,15 @@ /* wire protocol variant/value conversions */ typedef struct _BoltWireConv BoltWireConv; +typedef GVariant * (*BoltConvToWire) (BoltWireConv *conv, + const GValue *value, + GError **error); + +typedef gboolean (*BoltConvFromWire) (BoltWireConv *conv, + GVariant *wire, + GValue *value, + GError **error); + BoltWireConv * bolt_wire_conv_ref (BoltWireConv *conv); void bolt_wire_conv_unref (BoltWireConv *conv); @@ -58,6 +67,12 @@ BoltWireConv * bolt_wire_conv_for (const GVariantType *wire_type, GParamSpec *prop_spec); +BoltWireConv * bolt_wire_conv_custom (const GVariantType *wire_type, + GParamSpec *prop_spec, + const char *custom_id, + BoltConvToWire to_wire, + BoltConvFromWire from_wire); + GVariant * bolt_wire_conv_to_wire (BoltWireConv *conv, const GValue *value, GError **error); @@ -70,7 +85,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (BoltWireConv, bolt_wire_conv_unref) #if !GLIB_CHECK_VERSION (2, 57, 1) -G_DEFINE_AUTOPTR_CLEANUP_FUNC(GParamSpec, g_param_spec_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (GParamSpec, g_param_spec_unref) #endif G_END_DECLS diff -Nru bolt-0.8/common/bolt-io.c bolt-0.9.1/common/bolt-io.c --- bolt-0.8/common/bolt-io.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/common/bolt-io.c 2020-12-01 11:26:01.000000000 +0000 @@ -51,6 +51,11 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (FILE, fclose); +/* standard open flags for overwriting a file, i.e. close on + * exec (3), open in write only mode, truncate before writing, + * and create it if it does not yet exist */ +#define BOLT_O_OVERWRITE (O_CLOEXEC | O_WRONLY | O_TRUNC | O_CREAT) + int bolt_open (const char *path, int flags, int mode, GError **error) { @@ -319,6 +324,34 @@ gboolean +bolt_mkdirat (int dirfd, + const char *name, + mode_t mode, + GError **error) +{ + int r = 0; + + g_return_val_if_fail (name != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + r = mkdirat (dirfd, name, mode); + + if (r < 0) + { + g_set_error (error, + G_IO_ERROR, + g_io_error_from_errno (errno), + "failed to create directory '%s': %s", + name, + g_strerror (errno)); + return FALSE; + } + + return TRUE; +} + + +gboolean bolt_rmdir (const char *name, GError **error) { @@ -396,6 +429,35 @@ return TRUE; } +gboolean +bolt_write_file_at (int dirfd, + const char *name, + const char *data, + gssize len, + GError **error) +{ + int fd; + gboolean ok; + + g_return_val_if_fail (name != NULL, FALSE); + g_return_val_if_fail (data != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + fd = bolt_openat (dirfd, name, BOLT_O_OVERWRITE, 0666, error); + + if (fd < 0) + return FALSE; + + ok = bolt_write_all (fd, data, len, error); + + if (!ok) + (void) close (fd); + else + ok = bolt_close (fd, error); + + return ok; +} + char * bolt_read_value_at (int dirfd, const char *name, @@ -500,6 +562,25 @@ } gboolean +bolt_write_int_at (int dirfd, + const char *name, + gint val, + GError **error) +{ + char buf[256] = {0, }; + gsize n; + + g_return_val_if_fail (name != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + n = g_snprintf (buf, sizeof (buf), "%d", val); + + g_assert (n < sizeof (buf)); + + return bolt_write_file_at (dirfd, name, buf, n, error); +} + +gboolean bolt_read_int_at (int dirfd, const char *name, gint *val, @@ -519,6 +600,44 @@ } gboolean +bolt_write_uint_at (int dirfd, + const char *name, + guint val, + GError **error) +{ + char buf[256] = {0, }; + gsize n; + + g_return_val_if_fail (name != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + n = g_snprintf (buf, sizeof (buf), "%u", val); + + g_assert (n < sizeof (buf)); + + return bolt_write_file_at (dirfd, name, buf, n, error); +} + +gboolean +bolt_read_uint_at (int dirfd, + const char *name, + guint *val, + GError **error) +{ + g_autofree char *str = NULL; + + g_return_val_if_fail (name != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + str = bolt_read_value_at (dirfd, name, error); + + if (str == NULL) + return FALSE; + + return bolt_str_parse_as_uint (str, val, error); +} + +gboolean bolt_verify_uid (int dirfd, const char *want, GError **error) @@ -536,6 +655,8 @@ if (have == NULL) { + /* make clang's static analyzer happy */ + g_return_val_if_fail (err != NULL, FALSE); g_set_error (error, BOLT_ERROR, BOLT_ERROR_FAILED, "unique id verification failed: %s", err->message); @@ -561,26 +682,11 @@ gssize n, GError **error) { - int fd = -1; - gboolean ok; - g_return_val_if_fail (fn != NULL, FALSE); g_return_val_if_fail (data != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - fd = bolt_open (fn, O_CLOEXEC | O_WRONLY | O_TRUNC | O_CREAT, 0666, error); - - if (fd < 0) - return FALSE; - - ok = bolt_write_all (fd, data, n, error); - - if (ok) - ok = bolt_close (fd, error); - else - (void) close (fd); - - return ok; + return bolt_write_file_at (AT_FDCWD, fn, data, n, error); } gboolean @@ -789,6 +895,34 @@ return FALSE; } +gboolean +bolt_renameat (int from_dir, + const char *from, + int to_dir, + const char *to, + GError **error) +{ + int code; + int r; + + g_return_val_if_fail (from != NULL, FALSE); + g_return_val_if_fail (to != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + r = renameat (from_dir, from, to_dir, to); + + if (r == 0) + return TRUE; + + code = errno; + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (code), + "could not rename '%s' to '%s': %s", + from, to, g_strerror (code)); + + return FALSE; +} + #if !HAVE_FN_COPY_FILE_RANGE static loff_t copy_file_range (int fd_in, @@ -846,6 +980,50 @@ return len == 0; } +gboolean +bolt_dir_is_empty (DIR *dir, + gboolean *empty, + GError **error) +{ + struct dirent *de = NULL; + long n; + + g_return_val_if_fail (dir != NULL, FALSE); + g_return_val_if_fail (empty != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + *empty = TRUE; + + n = telldir (dir); + if (n == -1) + { + int code = errno; + + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (code), + "telldir(3) failed: %s", + g_strerror (code)); + + return FALSE; + } + + rewinddir (dir); + + while ((de = readdir (dir)) != NULL) + { + + if (!g_strcmp0 (de->d_name, ".") || + !g_strcmp0 (de->d_name, "..")) + continue; + + *empty = FALSE; + break; + } + + seekdir (dir, n); + + return TRUE; +} /* auto cleanup helpers */ void diff -Nru bolt-0.8/common/bolt-io.h bolt-0.9.1/common/bolt-io.h --- bolt-0.8/common/bolt-io.h 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/common/bolt-io.h 2020-12-01 11:26:01.000000000 +0000 @@ -70,6 +70,11 @@ gboolean bolt_closedir (DIR *d, GError **error); +gboolean bolt_mkdirat (int dirfd, + const char *name, + mode_t mode, + GError **error); + gboolean bolt_rmdir (const char *name, GError **error); @@ -87,6 +92,12 @@ int flag, GError **error); +gboolean bolt_write_file_at (int dirfd, + const char *name, + const char *data, + gssize len, + GError **error); + char * bolt_read_value_at (int dirfd, const char *name, GError **error); @@ -96,11 +107,26 @@ char value, GError **error); +gboolean bolt_write_int_at (int dirfd, + const char *name, + gint val, + GError **error); + gboolean bolt_read_int_at (int dirfd, const char *name, gint *val, GError **error); +gboolean bolt_write_uint_at (int dirfd, + const char *name, + guint val, + GError **error); + +gboolean bolt_read_uint_at (int dirfd, + const char *name, + guint *val, + GError **error); + gboolean bolt_verify_uid (int dirfd, const char *uid, GError **error); @@ -141,11 +167,21 @@ const char *to, GError **error); +gboolean bolt_renameat (int from_dir, + const char *from, + int to_dir, + const char *to, + GError **error); + gboolean bolt_copy_bytes (int fd_from, int fd_to, size_t len, GError **error); +gboolean bolt_dir_is_empty (DIR *dir, + gboolean *empty, + GError **error); + /* auto cleanup for I/O handles */ void bolt_cleanup_close_intpr (int *fd); #define bolt_autoclose bolt_cleanup (bolt_cleanup_close_intpr) diff -Nru bolt-0.8/common/bolt-macros.h bolt-0.9.1/common/bolt-macros.h --- bolt-0.8/common/bolt-macros.h 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/common/bolt-macros.h 2020-12-01 11:26:01.000000000 +0000 @@ -54,4 +54,15 @@ #define bolt_cleanup(x) __attribute__((cleanup (x))) +#if defined(__SANITIZE_ADDRESS__) +# define HAVE_ASAN 1 +#elif defined(__has_feature) +# if __has_feature (address_sanitizer) +# define HAVE_ASAN 1 +# endif +#endif +#ifndef HAVE_ASAN +# define HAVE_ASAN 0 +#endif + G_END_DECLS diff -Nru bolt-0.8/common/bolt-names.h bolt-0.9.1/common/bolt-names.h --- bolt-0.8/common/bolt-names.h 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/common/bolt-names.h 2020-12-01 11:26:01.000000000 +0000 @@ -64,13 +64,29 @@ #define BOLT_DBUS_POWER_INTERFACE "org.freedesktop.bolt1.Power" /* sysfs */ +#define BOLT_SYSFS_UNIQUE_ID "unique_id" + #define BOLT_SYSFS_IOMMU "iommu_dma_protection" +#define BOLT_SYSFS_GENERATION "generation" + +#define BOLT_SYSFS_RX_LANES "rx_lanes" +#define BOLT_SYSFS_RX_SPEED "rx_speed" +#define BOLT_SYSFS_TX_LANES "tx_lanes" +#define BOLT_SYSFS_TX_SPEED "tx_speed" + +#define BOLT_SYSFS_DMI_ID "/sys/class/dmi/id" +#define BOLT_SYSFS_DMI_SYS_VENDOR "sys_vendor" +#define BOLT_SYSFS_DMI_PRODUCT_NAME "product_name" +#define BOLT_SYSFS_DMI_PRODUCT_VERSION "product_version" /* environment variables */ #define BOLT_ENV_DBPATH "BOLT_DBPATH" #define BOLT_ENV_RUNTIME_DIRECTORY "RUNTIME_DIRECTORY" #define BOLT_ENV_STATE_DIRECTORY "STATE_DIRECTORY" +#define BOLT_SD_WATCHDOG_USEC "WATCHDOG_USEC" +#define BOLT_SD_NOTIFY_SOCKET "NOTIFY_SOCKET" + /* other well known names */ #define INTEL_WMI_THUNDERBOLT_GUID "86CCFD48-205E-4A77-9C48-2021CBEDE341" diff -Nru bolt-0.8/common/bolt-rnd.c bolt-0.9.1/common/bolt-rnd.c --- bolt-0.8/common/bolt-rnd.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/common/bolt-rnd.c 2020-12-01 11:26:01.000000000 +0000 @@ -99,7 +99,7 @@ (void) close (rndfd); - /* NB: accroding to the man page random(4), "when calling + /* NB: according to the man page random(4), "when calling read(2) for the device /dev/urandom, reads of up to 256 bytes will return as many bytes as are requested and will not be interrupted by a signal handler". diff -Nru bolt-0.8/common/bolt-str.c bolt-0.9.1/common/bolt-str.c --- bolt-0.8/common/bolt-str.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/common/bolt-str.c 2020-12-01 11:26:01.000000000 +0000 @@ -80,6 +80,18 @@ return (GStrv) g_ptr_array_free (a, FALSE); } +GStrv +bolt_strv_make_n (guint size, const char *init) +{ + GPtrArray *a; + + a = g_ptr_array_sized_new (size + 1); + for (guint i = 0; i < size; i++) + g_ptr_array_add (a, g_strdup (init)); + + return bolt_strv_from_ptr_array (&a); +} + gsize bolt_strv_length (char * const *strv) { @@ -94,6 +106,15 @@ return l; } +guint +bolt_gstrv_length0 (const GStrv strv) +{ + if (strv == NULL) + return 0; + + return g_strv_length ((char **) strv); +} + char ** bolt_strv_contains (GStrv haystack, const char *needle) { @@ -216,6 +237,39 @@ } } +gboolean +bolt_uuidv_check (const GStrv uuidv, + gboolean empty_ok, + GError **error) +{ + if (bolt_strv_isempty (uuidv)) + { + if (empty_ok) + return TRUE; + + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + "provided uuid array is empty"); + return FALSE; + } + + for (char * const *p = uuidv; *p; p++) + { + const char *uuid = *p; + + if (bolt_strzero (uuid) && empty_ok) + continue; + + if (!g_uuid_string_is_valid (uuid)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + "entry '%s' is not a valid UUID", uuid); + return FALSE; + } + } + + return TRUE; +} + char * bolt_strdup_validate (const char *string) { @@ -297,6 +351,32 @@ } gboolean +bolt_str_parse_as_uint (const char *str, + guint *ret, + GError **error) +{ + guint64 val; + gboolean ok; + + ok = bolt_str_parse_as_uint64 (str, &val, error); + if (!ok) + return FALSE; + + if (val > G_MAXUINT) + { + errno = ERANGE; + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + "parsing '%s' overflows uint", str); + return FALSE; + } + + if (ret) + *ret = (guint) val; + + return TRUE; +} + +gboolean bolt_str_parse_as_uint64 (const char *str, guint64 *ret, GError **error) @@ -330,6 +410,32 @@ return TRUE; } + +gboolean +bolt_str_parse_as_uint32 (const char *str, + guint32 *ret, + GError **error) +{ + gboolean ok; + guint64 val; + + ok = bolt_str_parse_as_uint64 (str, &val, error); + if (!ok) + return FALSE; + + if (val > G_MAXUINT32) + { + errno = ERANGE; + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + "parsing '%s' overflows uint32", str); + return FALSE; + } + + if (ret) + *ret = (guint32) val; + + return TRUE; +} gboolean bolt_str_parse_as_boolean (const char *str, diff -Nru bolt-0.8/common/bolt-str.h bolt-0.9.1/common/bolt-str.h --- bolt-0.8/common/bolt-str.h 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/common/bolt-str.h 2020-12-01 11:26:01.000000000 +0000 @@ -34,7 +34,10 @@ #define bolt_strcaseeq(s1, s2) (g_ascii_strcasecmp ((s1), (s2)) == 0) GStrv bolt_strv_from_ptr_array (GPtrArray **array); +GStrv bolt_strv_make_n (guint size, + const char *init); gsize bolt_strv_length (char * const *strv); +guint bolt_gstrv_length0 (const GStrv strv); char ** bolt_strv_contains (GStrv haystack, const char *needle); gboolean bolt_strv_equal (const GStrv a, @@ -48,6 +51,10 @@ #define bolt_strv_isempty(strv) ((strv) == NULL || *(strv) == NULL) +gboolean bolt_uuidv_check (const GStrv uuidv, + gboolean empty_ok, + GError **error); + #define bolt_strzero(str) (str == NULL || *str == '\0') #define bolt_yesno(val) val ? "yes" : "no" @@ -61,10 +68,18 @@ gint *ret, GError **error); +gboolean bolt_str_parse_as_uint (const char *str, + guint *ret, + GError **error); + gboolean bolt_str_parse_as_uint64 (const char *str, guint64 *ret, GError **error); +gboolean bolt_str_parse_as_uint32 (const char *str, + guint32 *ret, + GError **error); + gboolean bolt_str_parse_as_boolean (const char *str, gboolean *ret, GError **error); diff -Nru bolt-0.8/common/bolt-time.h bolt-0.9.1/common/bolt-time.h --- bolt-0.8/common/bolt-time.h 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/common/bolt-time.h 2020-12-01 11:26:01.000000000 +0000 @@ -24,6 +24,9 @@ G_BEGIN_DECLS +#define BOLT_MSEC_PER_SEC 1000LL /* number of milli-seconds (ms) in a second (s) */ +#define BOLT_USEC_PER_MSEC 1000LL /* number of micro-seconds (µs) in a ms */ + char * bolt_epoch_format (guint64 seconds, const char *format); diff -Nru bolt-0.8/common/bolt-unix.c bolt-0.9.1/common/bolt-unix.c --- bolt-0.8/common/bolt-unix.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/common/bolt-unix.c 2020-12-01 11:26:01.000000000 +0000 @@ -24,6 +24,8 @@ #include "bolt-error.h" #include "bolt-io.h" +#include "bolt-names.h" +#include "bolt-str.h" #include @@ -66,7 +68,7 @@ if (sent) *sent = FALSE; - env = g_getenv ("NOTIFY_SOCKET"); + env = g_getenv (BOLT_SD_NOTIFY_SOCKET); if (env == NULL) return TRUE; @@ -76,7 +78,7 @@ if (len > sizeof (sau.sun_path) - 1) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, - "unix domain socket to long: %s", env); + "unix domain socket path too long: %s", env); return FALSE; } @@ -134,3 +136,33 @@ return TRUE; } + +int +bolt_sd_watchdog_enabled (guint64 *timeout, + GError **error) +{ + const char *str; + guint64 val = 0; + gboolean ok; + + str = g_getenv (BOLT_SD_WATCHDOG_USEC); + + if (str == NULL) + return 0; + + ok = bolt_str_parse_as_uint64 (str, &val, error); + + if (ok && (val == 0 || val == (guint64) - 1)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + "invalid value '%" G_GUINT64_FORMAT "'", val); + ok = FALSE; + } + + if (!ok) + g_prefix_error (error, "failed to parse WATCHDOG_USEC: "); + else if (timeout) + *timeout = val; + + return ok ? 1 : -1; +} diff -Nru bolt-0.8/common/bolt-unix.h bolt-0.9.1/common/bolt-unix.h --- bolt-0.8/common/bolt-unix.h 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/common/bolt-unix.h 2020-12-01 11:26:01.000000000 +0000 @@ -30,4 +30,8 @@ gboolean bolt_sd_notify_literal (const char *state, gboolean *sent, GError **error); + +int bolt_sd_watchdog_enabled (guint64 *timeout, + GError **error); + G_END_DECLS diff -Nru bolt-0.8/common/bolt-wire.c bolt-0.9.1/common/bolt-wire.c --- bolt-0.8/common/bolt-wire.c 1970-01-01 00:00:00.000000000 +0000 +++ bolt-0.9.1/common/bolt-wire.c 2020-12-01 11:26:01.000000000 +0000 @@ -0,0 +1,110 @@ +/* + * Copyright © 2020 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Christian J. Kellner + */ + +#include "config.h" + +#include "bolt-wire.h" + +#include "bolt-error.h" + +#include + +G_DEFINE_BOXED_TYPE (BoltLinkSpeed, bolt_link_speed, bolt_link_speed_copy, g_free); + +BoltLinkSpeed * +bolt_link_speed_copy (const BoltLinkSpeed *other) +{ + BoltLinkSpeed *copy = g_new (BoltLinkSpeed, 1); + + *copy = *other; + return copy; +} + +gboolean +bolt_link_speed_equal (const BoltLinkSpeed *a, + const BoltLinkSpeed *b) +{ + return + a->rx.speed == b->rx.speed && + a->rx.lanes == b->rx.lanes && + a->tx.speed == b->tx.speed && + a->tx.lanes == b->tx.lanes; +} + +GVariant * +bolt_link_speed_to_wire (BoltWireConv *conv, + const GValue *value, + GError **error) +{ + GVariantBuilder builder; + BoltLinkSpeed *link; + + + link = g_value_get_boxed (value); + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{su}")); + + g_variant_builder_add (&builder, "{su}", "rx.speed", link->rx.speed); + g_variant_builder_add (&builder, "{su}", "rx.lanes", link->rx.lanes); + g_variant_builder_add (&builder, "{su}", "tx.speed", link->tx.speed); + g_variant_builder_add (&builder, "{su}", "tx.lanes", link->tx.lanes); + + return g_variant_builder_end (&builder); +} + +gboolean +bolt_link_speed_from_wire (BoltWireConv *conv, + GVariant *wire, + GValue *value, + GError **error) +{ + BoltLinkSpeed link; + gboolean ok; + + struct + { + const char *name; + guint32 *target; + } entries[] = { + {"rx.speed", &link.rx.speed}, + {"rx.lanes", &link.rx.lanes}, + {"tx.speed", &link.tx.speed}, + {"tx.lanes", &link.tx.lanes}, + }; + + for (unsigned i = 0; i < G_N_ELEMENTS (entries); i++) + { + const char *name = entries[i].name; + guint32 *target = entries[i].target; + + ok = g_variant_lookup (wire, name, "u", target); + if (!ok) + { + g_set_error (error, BOLT_ERROR, BOLT_ERROR_FAILED, + "Missing entry in LinkSpeed dict: %s", + name); + return FALSE; + } + } + + g_value_set_boxed (value, &link); + + return ok; +} diff -Nru bolt-0.8/common/bolt-wire.h bolt-0.9.1/common/bolt-wire.h --- bolt-0.8/common/bolt-wire.h 1970-01-01 00:00:00.000000000 +0000 +++ bolt-0.9.1/common/bolt-wire.h 2020-12-01 11:26:01.000000000 +0000 @@ -0,0 +1,66 @@ +/* + * Copyright © 2020 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Christian J. Kellner + */ + +#pragma once + +#include + +#include + +G_BEGIN_DECLS + +typedef struct BoltLinkSpeed_ BoltLinkSpeed; + +struct BoltLinkSpeed_ +{ + struct + { + struct + { + guint32 speed; + guint32 lanes; + } rx; + + struct + { + guint32 speed; + guint32 lanes; + } tx; + }; +}; + +GType bolt_link_speed_get_type (void); +#define BOLT_TYPE_LINK_SPEED (bolt_link_speed_get_type ()) + +BoltLinkSpeed * bolt_link_speed_copy (const BoltLinkSpeed *ls); + +gboolean bolt_link_speed_equal (const BoltLinkSpeed *a, + const BoltLinkSpeed *b); + +GVariant * bolt_link_speed_to_wire (BoltWireConv *conv, + const GValue *value, + GError **error); + +gboolean bolt_link_speed_from_wire (BoltWireConv *conv, + GVariant *wire, + GValue *value, + GError **error); + +G_END_DECLS diff -Nru bolt-0.8/config.h.in bolt-0.9.1/config.h.in --- bolt-0.8/config.h.in 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/config.h.in 2020-12-01 11:26:01.000000000 +0000 @@ -51,3 +51,7 @@ /* use structure logging f */ #define G_LOG_USE_STRUCTURED 1 + +/* glib version checking */ +#mesondefine GLIB_VERSION_MIN_REQUIRED +#mesondefine GLIB_VERSION_MAX_ALLOWED diff -Nru bolt-0.8/contrib/Dockerfile-alpine bolt-0.9.1/contrib/Dockerfile-alpine --- bolt-0.8/contrib/Dockerfile-alpine 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/contrib/Dockerfile-alpine 2020-12-01 11:26:01.000000000 +0000 @@ -4,5 +4,5 @@ ENV LANGUAGE en_US:en ENV LC_ALL en_US.UTF-8 RUN apk add --no-cache bash dbus dbus-dev eudev-dev gcc glib-dev libc-dev meson ninja polkit-dev udev -RUN mkdir /src +RUN mkdir /src /build WORKDIR /src diff -Nru bolt-0.8/contrib/Dockerfile-arch bolt-0.9.1/contrib/Dockerfile-arch --- bolt-0.8/contrib/Dockerfile-arch 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/contrib/Dockerfile-arch 2020-12-01 11:26:01.000000000 +0000 @@ -20,7 +20,7 @@ umockdev \ valgrind -RUN pip3 install python-dbusmock +RUN pip3 install python-dbusmock pylint==2.4.1 -RUN mkdir /src +RUN mkdir /src /build WORKDIR /src diff -Nru bolt-0.8/contrib/Dockerfile-coverity bolt-0.9.1/contrib/Dockerfile-coverity --- bolt-0.8/contrib/Dockerfile-coverity 1970-01-01 00:00:00.000000000 +0000 +++ bolt-0.9.1/contrib/Dockerfile-coverity 2020-12-01 11:26:01.000000000 +0000 @@ -0,0 +1,47 @@ +## -*- mode: dockerfile -*- +FROM fedora:33 + +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 +RUN dnf --enablerepo=updates-testing -y update +RUN dnf --enablerepo=updates-testing -y install \ + clang-analyzer \ + codespell \ + gcc \ + git \ + glib2-devel \ + gtk-doc \ + lcov \ + libgudev-devel \ + meson \ + polkit-devel \ + python3 \ + python3-dbus \ + python3-dbusmock \ + python3-gobject \ + rpm-build \ + redhat-rpm-config \ + systemd-devel \ + umockdev-devel \ + uncrustify \ + wget + +ARG TOKEN +ARG PROJECT +ARG ORG + +ENV HOME "/root" + +WORKDIR "$HOME" +RUN wget https://scan.coverity.com/download/linux64 \ + --post-data "token=${TOKEN}&project=${ORG}%2F${PROJECT}" \ + -O coverity_tool.tgz && \ + tar zxf coverity_tool.tgz \ + && rm coverity_tool.tgz && \ + mv cov-analysis-linux64-* cov-analysis-linux64 + +ENV PATH "$PATH:$HOME/cov-analysis-linux64/bin" + +RUN mkdir /src /build +WORKDIR /src diff -Nru bolt-0.8/contrib/Dockerfile-debian bolt-0.9.1/contrib/Dockerfile-debian --- bolt-0.8/contrib/Dockerfile-debian 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/contrib/Dockerfile-debian 2020-12-01 11:26:01.000000000 +0000 @@ -20,5 +20,5 @@ systemd \ && rm -rf /var/lib/apt/lists/* -RUN mkdir /src +RUN mkdir /src /build WORKDIR /src diff -Nru bolt-0.8/contrib/Dockerfile-fedora bolt-0.9.1/contrib/Dockerfile-fedora --- bolt-0.8/contrib/Dockerfile-fedora 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/contrib/Dockerfile-fedora 2020-12-01 11:26:01.000000000 +0000 @@ -1,11 +1,14 @@ ## -*- mode: dockerfile -*- -FROM fedora:29 +FROM fedora:32 ENV LANG en_US.UTF-8 ENV LANGUAGE en_US:en ENV LC_ALL en_US.UTF-8 RUN dnf --enablerepo=updates-testing -y update RUN dnf --enablerepo=updates-testing -y install \ + clang-analyzer \ + codespell \ gcc \ + git \ glib2-devel \ gtk-doc \ lcov \ @@ -19,7 +22,8 @@ rpm-build \ redhat-rpm-config \ systemd-devel \ - umockdev-devel + umockdev-devel \ + uncrustify -RUN mkdir /src +RUN mkdir /src /build WORKDIR /src diff -Nru bolt-0.8/contrib/bolt-mock bolt-0.9.1/contrib/bolt-mock --- bolt-0.8/contrib/bolt-mock 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/contrib/bolt-mock 2020-12-01 11:26:01.000000000 +0000 @@ -43,7 +43,7 @@ from gi.repository import UMockdev except ImportError as e: - sys.stderr.write('Missing depdendencies: %s\n' % str(e)) + sys.stderr.write('Missing dependencies: %s\n' % str(e)) sys.exit(1) DBUS_NAME = 'org.freedesktop.bolt' @@ -143,6 +143,7 @@ 'boot', 'device', 'device_name', + 'generation', 'key', 'unique_id', 'vendor', @@ -229,15 +230,19 @@ self.domains[sysname] = domain return domain - def host_add(self, domain, uid, name, vendor): + def host_add(self, domain, uid, name, vendor, generation): sysname = "%d-0" % domain.index + attributes = ['device_name', name, + 'device', '0x23', + 'vendor_name', vendor, + 'vendor', '0x23', + 'authorized', '1', + 'unique_id', uid] + if generation is not None: + attributes += ['generation', str(generation)] + syspath = self.testbed.add_device('thunderbolt', sysname, domain.syspath, - ['device_name', name, - 'device', '0x23', - 'vendor_name', vendor, - 'vendor', '0x23', - 'authorized', '1', - 'unique_id', uid], + attributes, ['DEVTYPE', 'thunderbolt_device']) host = Device(uid, sysname, syspath) host.is_host = True @@ -245,7 +250,7 @@ host.domain = domain return host - def device_add(self, parent, uid, name, vendor, authorized, key, boot): + def device_add(self, parent, uid, name, vendor, authorized, key, boot, gen): domain = parent.domain serial = domain.gen_serial() sysname = "%d-%d" % (domain.index, serial) @@ -266,6 +271,9 @@ if boot is not None: props += ['boot', str(boot)] + if gen is not None: + props += ['generation', str(gen)] + syspath = self.testbed.add_device('thunderbolt', sysname, parent.syspath, props, @@ -429,7 +437,7 @@ self.handler(obj, argv, self.parser) def __init__(self, handler): - self.hanlder = handler + self.handler = handler self.name = handler.__name__ self.desc = handler.__doc__ or self.name self.parser = argparse.ArgumentParser(prog=self.name, description=self.desc, add_help=False) @@ -449,7 +457,7 @@ elif hasattr(args, 'func'): args.func(obj, args, context) else: - self.hanlder(obj, args, context) + self.handler(obj, args, context) def subcommand(self, handler): if self.subparsers is None: @@ -588,6 +596,7 @@ @arg('--uuid', type=str, default=None) @arg('--iommu', type=str, choices=[None, '0', '1'], default=None) @arg('--bootacl', type=str, default=None) + @arg('--generation', type=int, default=3) @arg('--vendor', type=str, default='GNOME.org') @arg('--name', type=str, default='Laptop') @controller.subcommand @@ -596,6 +605,7 @@ security = args.security bootacl = args.bootacl iommu = args.iommu + gen = args.generation uid = args.uuid or str(uuid.uuid4()) print(uid) @@ -605,7 +615,7 @@ name = args.name vendor = args.vendor - host = self.sysfs.host_add(domain, uid, name, vendor) + host = self.sysfs.host_add(domain, uid, name, vendor, gen) host.show() @arg('id', type=str, help='name or uuid') @@ -631,6 +641,7 @@ @arg('--authorized', type=int, choices=[0, 1], default=0) @arg('--uuid', type=str, default=None) @arg('--vendor', type=str, default='GNOME.org') + @arg('--generation', type=int, default=3) @arg('name', type=str) @arg('parent', type=str) @device.subcommand @@ -649,7 +660,8 @@ args.vendor, args.authorized, args.key, - args.boot) + args.boot, + args.generation) device.show() diff -Nru bolt-0.8/contrib/bolt.spec.in bolt-0.9.1/contrib/bolt.spec.in --- bolt-0.8/contrib/bolt.spec.in 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/contrib/bolt.spec.in 2020-12-01 11:26:01.000000000 +0000 @@ -27,8 +27,8 @@ %endif %description -bolt is a system daemon to manage thunderbolt 3 devices via a D-BUS -API. Thunderbolt 3 features different security modes that require +bolt is a system daemon to manage Thunderbolt devices via a D-BUS +API. Thunderbolt 3 introduced different security modes that require devices to be authorized before they can be used. The D-Bus API can be used to list devices, enroll them (authorize and store them in the local database) and forget them again (remove previously enrolled diff -Nru bolt-0.8/contrib/cov-model.c bolt-0.9.1/contrib/cov-model.c --- bolt-0.8/contrib/cov-model.c 1970-01-01 00:00:00.000000000 +0000 +++ bolt-0.9.1/contrib/cov-model.c 2020-12-01 11:26:01.000000000 +0000 @@ -0,0 +1,30 @@ +/* GLib types. */ +typedef size_t gsize; +typedef char gchar; +typedef unsigned char guchar; +typedef int gint; +typedef unsigned long gulong; +typedef unsigned int guint32; +typedef void * gpointer; +typedef unsigned int gboolean; + + +void +g_assertion_message_expr (const char *domain, + const char *file, + int line, + const char *func, + const char *expr) +{ + __coverity_panic__ (); +} + +#define g_critical(...) __coverity_panic__ (); + +/* Treat it as a memory sink to hide one-time allocation leaks. */ +void + (g_once_init_leave) (volatile void *location, + gsize result) +{ + __coverity_escape__ (result); +} diff -Nru bolt-0.8/contrib/coverity.sh bolt-0.9.1/contrib/coverity.sh --- bolt-0.8/contrib/coverity.sh 1970-01-01 00:00:00.000000000 +0000 +++ bolt-0.9.1/contrib/coverity.sh 2020-12-01 11:26:01.000000000 +0000 @@ -0,0 +1,35 @@ +#!/bin/bash +set -e +set -x + +GIT_DESC=$(git describe) + +env + +# prepare +export LC_ALL=C.UTF-8 +export PYTHONPATH="/usr/share/glib-2.0" + +rm -rf /build/* + +# info +ls -la /build +whoami + +# actual building +export CC=clang CXX=clang +meson -Dcoverity=true /build +cov-build --dir /build/cov-int ninja -C /build +pushd /build +tar -caf coverity.xz cov-int +popd + +if [[ -v COVERITY_TOKEN && -v COVERITY_EMAIL ]]; then + + curl --form "token=${COVERITY_TOKEN}" \ + --form "email=${COVERITY_EMAIL}" \ + --form "file=@/build/coverity.xz" \ + --form "version=main" \ + --form "description=${GIT_DESC}" \ + https://scan.coverity.com/builds?project=gicmo%2Fbolt +fi diff -Nru bolt-0.8/contrib/docker-build.sh bolt-0.9.1/contrib/docker-build.sh --- bolt-0.8/contrib/docker-build.sh 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/contrib/docker-build.sh 2020-12-01 11:26:01.000000000 +0000 @@ -2,15 +2,42 @@ set -e set -x +# prepare export LC_ALL=C.UTF-8 export PYTHONPATH="/usr/share/glib-2.0" -rm -rf /build -mkdir /build -meson -Db_coverage=true . /build +rm -rf /build/* + +# info +ls -la /build +whoami + +# actual building +meson -Db_coverage=true -Dtests-speed=slow . /build ninja -C /build meson test -C /build --verbose if [[ -x "$(command -v lcov)" ]]; then ninja -C /build coverage fi + +if [[ -x "$(command -v scan-build)" ]]; then + ninja -C /build scan-build + + if [[ -n "$(ls -A /build/meson-logs/scanbuild/)" ]]; then + echo "Scan build log found, assuming defects exist" + exit 1 + fi +fi + +if [[ -x "$(command -v lcov)" ]]; then + scripts/uncrustify.sh --check +fi + +if [[ -x $(command -v pylint) ]]; then + pylint tests/test-integration +fi + +if [[ -x $(command -v codespell) ]]; then + codespell -S .git -S build +fi diff -Nru bolt-0.8/data/bolt.service.in bolt-0.9.1/data/bolt.service.in --- bolt-0.8/data/bolt.service.in 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/data/bolt.service.in 2020-12-01 11:26:01.000000000 +0000 @@ -10,6 +10,7 @@ #Environment="G_MESSAGES_DEBUG=all" Restart=on-failure NotifyAccess=main +WatchdogSec=3min MemoryDenyWriteExecute=yes PrivateTmp=yes diff -Nru bolt-0.8/data/org.freedesktop.bolt.conf bolt-0.9.1/data/org.freedesktop.bolt.conf --- bolt-0.8/data/org.freedesktop.bolt.conf 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/data/org.freedesktop.bolt.conf 2020-12-01 11:26:01.000000000 +0000 @@ -26,6 +26,9 @@ send_interface="org.freedesktop.bolt1.Manager" /> + + The policy to use during enrollment when "default" was - sepcified. + specified. @@ -56,6 +56,14 @@ + + + The maximum generation of any of Thunderbolt controller + associated with the device. It will contain 4 for USB4, + 3 for Thunderbolt 3. + + + @@ -158,7 +166,7 @@ - Remove the device and any associated inforamtion, + Remove the device and any associated information, such as the policy and its key, from the store. @@ -234,7 +242,7 @@ - The minimum amout of time the controller will be powered. + The minimum amount of time the controller will be powered. This timeout will get reset with every new activity on the thundrbolt bus while the controller is being force powered. @@ -321,6 +329,14 @@ + + + The generation of the Thunderbolt controller associated + with the device. It will contain 4 for USB4, 3 for + Thunderbolt 3. + + + The current status of the device. @@ -358,7 +374,7 @@ When a device is connected, it always is connected via a thunderbolt domain, directly or via a chain of - other devices. This is the indentifier of the domain. + other devices. This is the identifier of the domain. @@ -368,7 +384,7 @@ - + The authorization policy of the device. @@ -408,6 +424,17 @@ + + + Information about the link speed, i.e. with how + many lanes the device is connected to its parent + and what rate (in Gb/s) the lanes have. Separate + information is reported for transmission (tx) and + reception (rx). All lanes in one direction have + the same rate. + + + @@ -462,7 +489,7 @@ - + The boot access control list. @@ -473,7 +500,7 @@ The input output memory management unit (IOMMU) can used on recent hardware in combination with recent kernel versions to protect against attacks, - like direct memory access (DMA) attacs, at the + like direct memory access (DMA) attacks, at the hardware level. If this property is true than IOMMU hardware protection is active. diff -Nru bolt-0.8/debian/bolt-tests.install bolt-0.9.1/debian/bolt-tests.install --- bolt-0.8/debian/bolt-tests.install 2019-09-23 10:52:29.000000000 +0000 +++ bolt-0.9.1/debian/bolt-tests.install 2021-10-05 06:46:37.000000000 +0000 @@ -1 +1 @@ -usr/lib/bolt/installed-tests +usr/libexec/installed-tests diff -Nru bolt-0.8/debian/bolt.install bolt-0.9.1/debian/bolt.install --- bolt-0.8/debian/bolt.install 2019-09-23 10:52:29.000000000 +0000 +++ bolt-0.9.1/debian/bolt.install 2021-10-05 06:46:37.000000000 +0000 @@ -1,6 +1,6 @@ lib/systemd lib/udev usr/bin -usr/lib/bolt/boltd +usr/libexec/boltd usr/share var/lib/boltd diff -Nru bolt-0.8/debian/changelog bolt-0.9.1/debian/changelog --- bolt-0.8/debian/changelog 2020-09-10 08:53:51.000000000 +0000 +++ bolt-0.9.1/debian/changelog 2021-10-05 06:46:37.000000000 +0000 @@ -1,3 +1,32 @@ +bolt (0.9.1-2ubuntu0~20.04.1) focal; urgency=medium + + * debian/patches/git_umockdev_update.patch: + - Get along with existing mock /sys/{bus,class} directories + (Closes: #995248) + + -- Sebastien Bacher Tue, 05 Oct 2021 08:46:37 +0200 + +bolt (0.9.1-1) unstable; urgency=medium + + * New bugfix version + + -- Sebastien Bacher Fri, 15 Jan 2021 16:17:09 +0100 + +bolt (0.9-1) unstable; urgency=medium + + * New upstream version + * debian/control: + - updated the glib requirement to 2.56 + * debian/patches/gitlab_oldkernel_tests.patch: + - removed, the change is in the new version + + [ Laurent Bigonville ] + * Bump to debhelper version 12 + * Move the daemon to /usr/libexec now that's allowed in the policy + * debian/control: Add the proper dependencies for the bolt-tests package + + -- Sebastien Bacher Tue, 16 Jun 2020 16:01:37 +0200 + bolt (0.8-4ubuntu1) focal; urgency=medium * debian/patches/ignore-wakeup-device-uevents.patch: diff -Nru bolt-0.8/debian/compat bolt-0.9.1/debian/compat --- bolt-0.8/debian/compat 2019-09-23 10:52:29.000000000 +0000 +++ bolt-0.9.1/debian/compat 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -11 diff -Nru bolt-0.8/debian/control bolt-0.9.1/debian/control --- bolt-0.8/debian/control 2019-09-23 10:52:29.000000000 +0000 +++ bolt-0.9.1/debian/control 2021-10-05 06:46:37.000000000 +0000 @@ -3,15 +3,15 @@ Priority: optional Maintainer: Debian freedesktop.org maintainers Uploaders: Jeremy Bicha -Build-Depends: debhelper (>= 11), - meson (>= 0.44.0), - asciidoc-base , +Build-Depends: asciidoc-base , + debhelper-compat (= 12), docbook-xml , - libglib2.0-dev (>= 2.50), + libglib2.0-dev (>= 2.56), + libpolkit-gobject-1-dev, libudev-dev, libumockdev-dev, - libpolkit-gobject-1-dev, libxml2-utils , + meson (>= 0.44.0), python3-dbus , python3-dbusmock , python3-gi , @@ -19,7 +19,7 @@ udev, umockdev , xmlto , - xsltproc , + xsltproc Standards-Version: 4.2.1 Vcs-Browser: https://salsa.debian.org/freedesktop-team/bolt Vcs-Git: https://salsa.debian.org/freedesktop-team/bolt.git @@ -27,7 +27,7 @@ Package: bolt Architecture: linux-any -Depends: ${shlibs:Depends}, ${misc:Depends} +Depends: ${misc:Depends}, ${shlibs:Depends} Description: system daemon to manage thunderbolt 3 devices Thunderbolt 3 features different security modes that require devices to be authorized before they can be used. The D-Bus API can be @@ -41,7 +41,15 @@ Package: bolt-tests Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends} +Depends: gir1.2-glib-2.0, + gir1.2-umockdev-1.0, + python3, + python3-dbus, + python3-dbusmock, + python3-gi, + umockdev, + ${misc:Depends}, + ${shlibs:Depends} Description: system daemon to manage thunderbolt 3 devices - installed tests Thunderbolt 3 features different security modes that require devices to be authorized before they can be used. The D-Bus API can be diff -Nru bolt-0.8/debian/copyright bolt-0.9.1/debian/copyright --- bolt-0.8/debian/copyright 2019-09-23 10:52:29.000000000 +0000 +++ bolt-0.9.1/debian/copyright 2021-10-05 06:46:37.000000000 +0000 @@ -26,5 +26,3 @@ . On Debian systems, the complete text of the GNU Lesser General Public License can be found in "/usr/share/common-licenses/LGPL-2.1". - - diff -Nru bolt-0.8/debian/patches/git_umockdev_update.patch bolt-0.9.1/debian/patches/git_umockdev_update.patch --- bolt-0.8/debian/patches/git_umockdev_update.patch 1970-01-01 00:00:00.000000000 +0000 +++ bolt-0.9.1/debian/patches/git_umockdev_update.patch 2021-10-05 06:46:37.000000000 +0000 @@ -0,0 +1,43 @@ +From 130e09d1c7ff02c09e4ad1c9c36e9940b68e58d8 Mon Sep 17 00:00:00 2001 +From: Martin Pitt +Date: Thu, 16 Sep 2021 10:10:03 +0200 +Subject: [PATCH] tests/mock: Get along with existing mock /sys/{bus,class} + directories + +This was fixed in umockdev 0.16.3, but now causes warning messages due +to the existing directories. This at least breaks Debian/Ubuntu's +autopkgtests. +--- + tests/mock-sysfs.c | 8 +++++--- + 1 file changed, 5 insertions(+), 3 deletions(-) + +diff --git a/tests/mock-sysfs.c b/tests/mock-sysfs.c +index 6d0df1d..9d08430 100644 +--- a/tests/mock-sysfs.c ++++ b/tests/mock-sysfs.c +@@ -175,19 +175,21 @@ mock_sysfs_init (MockSysfs *ms) + ms->devices = g_hash_table_new (g_str_hash, g_str_equal); + + /* udev_enumerate_scan_devices() will return -ENOENT, if +- * sys/bus or sys/class directories can not be found ++ * sys/bus or sys/class directories can not be found; ++ * fixed in https://github.com/martinpitt/umockdev/commit/5e8296014346 ++ * but work with older versions as well + */ + sys = umockdev_testbed_get_sys_dir (ms->bed); + + bus = g_build_filename (sys, "bus", NULL); + r = g_mkdir (bus, 0744); + +- if (r < 0) ++ if (r < 0 && errno != EEXIST) + g_warning ("could not create %s", bus); + + cls = g_build_filename (sys, "class", NULL); + r = g_mkdir (cls, 0744); +- if (r < 0) ++ if (r < 0 && errno != EEXIST) + g_warning ("could not create %s", bus); + } + +-- diff -Nru bolt-0.8/debian/patches/gitlab_oldkernel_tests.patch bolt-0.9.1/debian/patches/gitlab_oldkernel_tests.patch --- bolt-0.8/debian/patches/gitlab_oldkernel_tests.patch 2019-09-23 10:52:29.000000000 +0000 +++ bolt-0.9.1/debian/patches/gitlab_oldkernel_tests.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,691 +0,0 @@ -Index: bolt-0.8/meson.build -=================================================================== ---- bolt-0.8.orig/meson.build -+++ bolt-0.8/meson.build -@@ -215,7 +215,7 @@ common_sources = [ - 'common/bolt-str.c', - 'common/bolt-term.c', - 'common/bolt-time.c', -- 'common/bolt-unix.c' -+ 'common/bolt-unix.c', - ] - - common_enums = gnome.mkenums_simple('bolt-enum-types', -@@ -408,6 +408,7 @@ tests = [ - ['test-logging', [libdaemon]], - ['test-store', [libdaemon]], - ['test-journal', [libdaemon]], -+ ['test-self'] - ] - - if mockdev.found() -Index: bolt-0.8/tests/bolt-test.c -=================================================================== ---- bolt-0.8.orig/tests/bolt-test.c -+++ bolt-0.8/tests/bolt-test.c -@@ -23,6 +23,7 @@ - #include "bolt-test.h" - - #include "bolt-fs.h" -+#include - - BoltTmpDir - bolt_tmp_dir_make (const char *pattern, -@@ -63,3 +64,154 @@ bolt_tmp_dir_destroy (BoltTmpDir dir) - - g_free (dir); - } -+ -+/* Version parsing, checking */ -+static gboolean -+parse_one (const char *str, -+ BoltVersion *version, -+ int *index, -+ GError **error) -+{ -+ gboolean ok; -+ gint64 v; -+ int i = *index; -+ -+ ok = g_ascii_string_to_signed (str, -+ 10, /* base */ -+ 0, G_MAXINT, -+ &v, -+ error); -+ -+ if (!ok) -+ return FALSE; -+ -+ version->triplet[i] = (int) v; -+ *index = i + 1; -+ -+ return TRUE; -+} -+ -+gboolean -+bolt_version_parse (const char *str, -+ BoltVersion *version, -+ GError **error) -+{ -+ g_autofree char *tmp = NULL; -+ gboolean ok = FALSE; -+ char *data = NULL; -+ char *delim = NULL; -+ int index = 0; -+ -+ g_return_val_if_fail (str != NULL, FALSE); -+ g_return_val_if_fail (version != NULL, FALSE); -+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE); -+ -+ bolt_version_clear (version); -+ -+ /* so we manipulate the string */ -+ tmp = data = g_strdup (str); -+ -+ delim = strchr (data, '-'); -+ -+ if (delim) -+ { -+ *delim = '\0'; -+ version->suffix = g_strdup (delim + 1); -+ } -+ -+ while (index < 2 && (delim = strchr (data, '.')) != NULL) -+ { -+ *delim = '\0'; -+ ok = parse_one (data, version, &index, error); -+ -+ if (!ok) -+ return FALSE; -+ -+ data = delim + 1; -+ } -+ -+ return parse_one (data, version, &index, error); -+} -+ -+void -+bolt_version_clear (BoltVersion *version) -+{ -+ version->major = -1; -+ version->minor = -1; -+ version->patch = -1; -+ -+ g_clear_pointer (&version->suffix, g_free); -+} -+ -+/* strcmp semantics */ -+int -+bolt_version_compare (BoltVersion *a, -+ BoltVersion *b) -+{ -+ g_return_val_if_fail (a != NULL, -1); -+ g_return_val_if_fail (b != NULL, 1); -+ -+ for (int i = 0; i < 3; i++) -+ { -+ int ac = a->triplet[i]; -+ int bc = b->triplet[i]; -+ -+ if (ac < bc) -+ return -1; -+ else if (ac > bc) -+ return 1; -+ } -+ -+ return 0; /* all components equal */ -+} -+ -+gboolean -+bolt_version_check (BoltVersion *base, -+ int major, -+ int minor, -+ int patch) -+{ -+ BoltVersion ref = BOLT_VERSION_INIT (major, minor, patch); -+ -+ g_return_val_if_fail (base != NULL, FALSE); -+ -+ return bolt_version_compare (base, &ref) >= 0; -+} -+ -+gboolean -+bolt_check_kernel_version (int major, int minor) -+{ -+ g_autoptr(GError) err = NULL; -+ g_autofree char *data = NULL; -+ g_auto(BoltVersion) ver = BOLT_VERSION_INIT (1, 0, 0); -+ gboolean ok; -+ gsize length; -+ -+ ok = g_file_get_contents ("/proc/sys/kernel/osrelease", -+ &data, -+ &length, -+ &err); -+ -+ if (!ok) -+ { -+ g_message ("Could not read kernel version: %s", err->message); -+ return FALSE; -+ } -+ -+ while (length > 1 && data[length - 1] == '\n') -+ data[--length] = '\0'; -+ -+ ok = bolt_version_parse (data, &ver, &err); -+ if (!ok) -+ { -+ g_message ("Could not parse kernel version (%s): %s", -+ data, err->message); -+ return FALSE; -+ } -+ -+ g_debug ("Read kernel version: %d.%d.%d (%s)", -+ ver.major, ver.minor, ver.patch, -+ (ver.suffix ? : "")); -+ -+ return bolt_version_check (&ver, major, minor, -1); -+} -Index: bolt-0.8/tests/bolt-test.h -=================================================================== ---- bolt-0.8.orig/tests/bolt-test.h -+++ bolt-0.8/tests/bolt-test.h -@@ -22,6 +22,8 @@ - - #include - -+G_BEGIN_DECLS -+ - typedef char *BoltTmpDir; - - BoltTmpDir bolt_tmp_dir_make (const char *pattern, -@@ -56,4 +58,58 @@ G_DEFINE_AUTO_CLEANUP_FREE_FUNC (BoltTmp - va__, sa__[il__], "!=", sb__[il__]); \ - } \ - } G_STMT_END -+ -+#define skip_test_if(condition, message) G_STMT_START { \ -+ if (condition) \ -+ { \ -+ g_test_skip (message); \ -+ return; \ -+ } \ -+ } G_STMT_END -+ -+#define skip_test_unless(condition, message) skip_test_if(!(condition), message) -+ -+/* Version parsing, checking */ -+ -+typedef struct BoltVersion_ BoltVersion; -+struct BoltVersion_ -+{ -+ union -+ { -+ struct -+ { -+ int major; -+ int minor; -+ int patch; -+ }; -+ -+ int triplet[3]; -+ }; -+ -+ char *suffix; -+}; -+ -+#define BOLT_VERSION_INIT(ma, mi, pa) {.major = (ma), .minor = (mi), .patch = (pa), .suffix = NULL} -+ -+gboolean bolt_version_parse (const char *str, -+ BoltVersion *version, -+ GError **error); -+ -+void bolt_version_clear (BoltVersion *version); -+ -+int bolt_version_compare (BoltVersion *a, -+ BoltVersion *b); -+ -+gboolean bolt_version_check (BoltVersion *version, -+ int major, -+ int minor, -+ int patch); -+ -+G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (BoltVersion, bolt_version_clear); -+ -+gboolean bolt_check_kernel_version (int major, -+ int minor); -+ -+G_END_DECLS -+ - /* *INDENT-ON* */ -Index: bolt-0.8/tests/mock-sysfs.c -=================================================================== ---- bolt-0.8.orig/tests/mock-sysfs.c -+++ bolt-0.8/tests/mock-sysfs.c -@@ -21,6 +21,7 @@ - #include "config.h" - - #include "bolt-io.h" -+#include "bolt-fs.h" - #include "bolt-names.h" - #include "bolt-str.h" - -@@ -28,10 +29,12 @@ - - #include - -+#include - #include - - #include - #include -+#include - - typedef struct _MockDevice MockDevice; - struct _MockDevice -@@ -835,3 +838,62 @@ mock_sysfs_device_remove (MockSysfs *ms - - return TRUE; - } -+ -+gboolean -+mock_sysfs_set_osrelease (MockSysfs *ms, -+ const char *version) -+{ -+ g_autoptr(GError) err = NULL; -+ g_autoptr(GFile) target = NULL; -+ g_autofree char *data = NULL; -+ g_autofree char *path = NULL; -+ const char *root; -+ gboolean ok; -+ gsize n; -+ int r; -+ -+ root = umockdev_testbed_get_root_dir (ms->bed); -+ target = g_file_new_build_filename (root, "proc/sys/kernel/osrelease", NULL); -+ -+ ok = bolt_fs_make_parent_dirs (target, &err); -+ if (!ok) -+ { -+ g_debug ("Failed to make parent dirs: %s", err->message); -+ return FALSE; -+ } -+ -+ if (version != NULL) -+ data = g_strdup_printf ("%s\n", version); -+ else -+ data = g_strdup ("\n"); -+ -+ n = strlen (data); -+ -+ /* make sure we can write the file, if it fails, -+ * we will indirectly catch it in the write */ -+ path = g_file_get_path (target); -+ chmod (path, 0644); -+ -+ ok = g_file_replace_contents (target, -+ data, n, -+ NULL, -+ FALSE, -+ 0, -+ NULL, -+ NULL, -+ &err); -+ -+ if (!ok) -+ g_debug ("Failed to set osrelease: %s", err->message); -+ -+ /* make the file non-readable if version == NULL, -+ * so to simulate read errors */ -+ if (version == NULL) -+ { -+ r = chmod (path, 0000); -+ if (r != 0) -+ g_debug ("Failed to set mode: %s", g_strerror (errno)); -+ } -+ -+ return ok; -+} -Index: bolt-0.8/tests/mock-sysfs.h -=================================================================== ---- bolt-0.8.orig/tests/mock-sysfs.h -+++ bolt-0.8/tests/mock-sysfs.h -@@ -102,4 +102,7 @@ const char * mock_sysfs_device_get_p - gboolean mock_sysfs_device_remove (MockSysfs *ms, - const char *id); - -+gboolean mock_sysfs_set_osrelease (MockSysfs *ms, -+ const char *version); -+ - G_END_DECLS -Index: bolt-0.8/tests/test-common.c -=================================================================== ---- bolt-0.8.orig/tests/test-common.c -+++ bolt-0.8/tests/test-common.c -@@ -981,6 +981,11 @@ test_io_copy_bytes (TestIO *tt, gconstpo - int from = -1; - int to = -1; - -+ /* bolt_copy_bytes uses copy_file_range(2) internally, which -+ * was added in linux 4.5. */ -+ skip_test_unless (bolt_check_kernel_version (4, 5) || g_test_thorough (), -+ "linux kernel < 4.5, copy_file_range syscall missing"); -+ - chk = g_checksum_new (G_CHECKSUM_SHA256); - - source = g_build_filename (tt->path, "copy_bytes_source", NULL); -@@ -1736,11 +1741,8 @@ test_strv_rotate_left (TestRng *tt, gcon - static void - test_term_fancy (TestRng *tt, gconstpointer user_data) - { -- if (bolt_is_fancy_terminal () == 0) -- { -- g_test_skip ("Terminal is not fancy"); -- return; -- } -+ skip_test_unless (bolt_is_fancy_terminal (), -+ "Terminal is not fancy"); - - g_assert_cmpstr (bolt_color (ANSI_NORMAL), !=, ""); - g_assert_cmpstr (bolt_glyph (WARNING_SIGN), !=, ""); -@@ -1749,11 +1751,8 @@ test_term_fancy (TestRng *tt, gconstpoin - static void - test_term_plain (TestRng *tt, gconstpointer user_data) - { -- if (bolt_is_fancy_terminal () != 0) -- { -- g_test_skip ("Terminal is too fancy"); -- return; -- } -+ skip_test_if (bolt_is_fancy_terminal (), -+ "Terminal is too fancy"); - - g_assert_cmpstr (bolt_color (ANSI_NORMAL), ==, ""); - g_assert_cmpstr (bolt_glyph (WARNING_SIGN), !=, ""); -Index: bolt-0.8/tests/test-journal.c -=================================================================== ---- bolt-0.8.orig/tests/test-journal.c -+++ bolt-0.8/tests/test-journal.c -@@ -24,6 +24,7 @@ - - #include "bolt-dbus.h" - #include "bolt-fs.h" -+#include "bolt-test.h" - - #include - #include -@@ -301,6 +302,11 @@ test_journal_diff (TestJournal *tt, gcon - }; - guint k; - -+ /* bolt_journal_put_diff uses bolt_copy_bytes, which in trun uses -+ * copy_file_range(2) internally, which was added in linux 4.5. */ -+ skip_test_unless (bolt_check_kernel_version (4, 5) || g_test_thorough (), -+ "linux kernel < 4.5, copy_file_range syscall missing"); -+ - j = bolt_journal_new (tt->root, "diff", &err); - - /* the first and the last elements are added "manually" -Index: bolt-0.8/tests/test-self.c -=================================================================== ---- /dev/null -+++ bolt-0.8/tests/test-self.c -@@ -0,0 +1,209 @@ -+/* -+ * Copyright © 2018 Red Hat, Inc -+ * -+ * This program is free software; you can redistribute it and/or -+ * modify it under the terms of the GNU Lesser General Public -+ * License as published by the Free Software Foundation; either -+ * version 2.1 of the License, or (at your option) any later version. -+ * -+ * This library is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -+ * Lesser General Public License for more details. -+ * -+ * You should have received a copy of the GNU Lesser General Public -+ * License along with this library. If not, see . -+ * -+ * Authors: -+ * Christian J. Kellner -+ */ -+ -+#include "config.h" -+ -+#include -+ -+#include -+ -+typedef int TestDummy; -+ -+static void -+test_version_parse (TestDummy *tt, gconstpointer user_data) -+{ -+ struct VersionTest -+ { -+ const char *str; -+ -+ /* result */ -+ gboolean ok; -+ -+ /* data */ -+ int major; -+ int minor; -+ int patch; -+ -+ const char *suffix; -+ } ftt[] = { -+ {"", FALSE, -1, -1, -1, NULL}, -+ {"parsererror", FALSE, -1, -1, -1, NULL}, -+ {"parser.err.or", FALSE, -1, -1, -1, NULL}, -+ {"1.0.0.43", FALSE, 1, 0, -1, NULL}, -+ {"1", TRUE, 1, -1, -1, NULL}, -+ {"1.0", TRUE, 1, 0, -1, NULL}, -+ {"1.0.0", TRUE, 1, 0, 0, NULL}, -+ {"1-100", TRUE, 1, -1, -1, "100"}, -+ {"1-100.fc", TRUE, 1, -1, -1, "100.fc"}, -+ {"1.0-100.fc", TRUE, 1, 0, -1, "100.fc"}, -+ {"1.0.0-100.fc", TRUE, 1, 0, 0, "100.fc"}, -+ {"5.2.11-200.fc30.x86_64", TRUE, 5, 2, 11, "200.fc30.x86_64"}, -+ {"4.4.0-161-generic", TRUE, 4, 4, 0, "161-generic"} -+ }; -+ -+ for (guint i = 0; i < G_N_ELEMENTS (ftt); i++) -+ { -+ g_autoptr(GError) err = NULL; -+ g_auto(BoltVersion) v = { .major = 42, .minor = 42, .patch = 42, .suffix = NULL}; -+ gboolean ok; -+ -+ ok = bolt_version_parse (ftt[i].str, &v, &err); -+ -+ if (ftt[i].ok) -+ { -+ g_assert_no_error (err); -+ g_assert_true (ok); -+ } -+ else -+ { -+ g_assert_nonnull (err); -+ g_assert_false (ok); -+ } -+ -+ g_assert_cmpint (v.major, ==, ftt[i].major); -+ g_assert_cmpint (v.minor, ==, ftt[i].minor); -+ g_assert_cmpint (v.patch, ==, ftt[i].patch); -+ -+ if (ftt[i].suffix) -+ g_assert_cmpstr (v.suffix, ==, ftt[i].suffix); -+ else -+ g_assert_null (v.suffix); -+ } -+} -+ -+static void -+test_version_compare (TestDummy *tt, gconstpointer user_data) -+{ -+ struct VersionTest -+ { -+ BoltVersion a; -+ BoltVersion b; -+ -+ int res; -+ } ftt[] = { -+ /* x.-.- */ -+ { BOLT_VERSION_INIT ( 1, -1, -1), BOLT_VERSION_INIT ( 0, -1, -1), 1}, -+ { BOLT_VERSION_INIT ( 1, -1, -1), BOLT_VERSION_INIT ( 1, -1, -1), 0}, -+ { BOLT_VERSION_INIT ( 1, -1, -1), BOLT_VERSION_INIT (42, -1, -1), -1}, -+ -+ { BOLT_VERSION_INIT ( 1, 5, -1), BOLT_VERSION_INIT ( 1, -1, -1), 1}, -+ { BOLT_VERSION_INIT ( 1, 5, -1), BOLT_VERSION_INIT ( 5, 1, -1), -1}, -+ -+ /* x.y.- */ -+ { BOLT_VERSION_INIT ( 1, 5, -1), BOLT_VERSION_INIT ( 0, 5, -1), 1}, -+ { BOLT_VERSION_INIT ( 1, 5, -1), BOLT_VERSION_INIT ( 1, 0, -1), 1}, -+ { BOLT_VERSION_INIT ( 1, 5, -1), BOLT_VERSION_INIT ( 2, 0, -1), -1}, -+ -+ /* x.y.z */ -+ { BOLT_VERSION_INIT ( 1, 2, 3), BOLT_VERSION_INIT ( 1, 0, 0), 1}, -+ { BOLT_VERSION_INIT ( 1, 2, 3), BOLT_VERSION_INIT ( 1, 2, 2), 1}, -+ { BOLT_VERSION_INIT ( 1, 2, 3), BOLT_VERSION_INIT ( 1, 2, 3), 0}, -+ { BOLT_VERSION_INIT ( 1, 2, 3), BOLT_VERSION_INIT ( 1, 2, 4), -1}, -+ { BOLT_VERSION_INIT ( 1, 2, 3), BOLT_VERSION_INIT ( 2, 0, 0), -1}, -+ { BOLT_VERSION_INIT ( 1, 2, 3), BOLT_VERSION_INIT ( 2, 0, -1), -1}, -+ { BOLT_VERSION_INIT ( 1, 2, 3), BOLT_VERSION_INIT ( 2, -1, -1), -1}, -+ }; -+ -+ for (guint i = 0; i < G_N_ELEMENTS (ftt); i++) -+ { -+ int res = bolt_version_compare (&(ftt[i].a), &(ftt[i].b)); -+ g_assert_cmpint (res, ==, ftt[i].res); -+ -+ res = bolt_version_compare (&(ftt[i].b), &(ftt[i].a)); -+ g_assert_cmpint (res, ==, -1 * ftt[i].res); -+ } -+} -+ -+static void -+test_version_check (TestDummy *tt, gconstpointer user_data) -+{ -+ struct VersionTest -+ { -+ BoltVersion version; -+ -+ int major; -+ int minor; -+ int patch; -+ -+ gboolean res; -+ } ftt[] = { -+ { BOLT_VERSION_INIT (1, 2, 3), 1, -1, -1, TRUE}, -+ { BOLT_VERSION_INIT (1, 2, 3), 1, 0, -1, TRUE}, -+ { BOLT_VERSION_INIT (1, 2, 3), 1, 0, 0, TRUE}, -+ { BOLT_VERSION_INIT (1, 2, 3), 1, 2, 0, TRUE}, -+ { BOLT_VERSION_INIT (1, 2, 3), 1, 2, 3, TRUE}, -+ -+ { BOLT_VERSION_INIT (1, 2, 3), 1, 2, 4, FALSE}, -+ { BOLT_VERSION_INIT (1, 2, 3), 1, 3, 2, FALSE}, -+ { BOLT_VERSION_INIT (1, 2, 3), 2, 0, 0, FALSE}, -+ { BOLT_VERSION_INIT (1, 2, 3), 2, 3, 0, FALSE}, -+ { BOLT_VERSION_INIT (1, 2, 3), 2, 3, 4, FALSE}, -+ { BOLT_VERSION_INIT (1, 2, 3), 2, -1, -1, FALSE}, -+ { BOLT_VERSION_INIT (1, 2, 3), 2, 0, -1, FALSE}, -+ -+ { BOLT_VERSION_INIT (2, -1, -1), 2, -1, -1, TRUE}, -+ { BOLT_VERSION_INIT (2, -1, -1), 1, 9, 9, TRUE}, -+ { BOLT_VERSION_INIT (2, -1, -1), 2, 0, 0, FALSE}, -+ -+ }; -+ -+ for (guint i = 0; i < G_N_ELEMENTS (ftt); i++) -+ { -+ gboolean res; -+ -+ res = bolt_version_check (&(ftt[i].version), -+ ftt[i].major, -+ ftt[i].minor, -+ ftt[i].patch); -+ -+ g_assert_true (res == ftt[i].res); -+ } -+} -+ -+int -+main (int argc, char **argv) -+{ -+ setlocale (LC_ALL, ""); -+ -+ g_test_init (&argc, &argv, NULL); -+ -+ g_test_add ("/self/version/parse", -+ TestDummy, -+ NULL, -+ NULL, -+ test_version_parse, -+ NULL); -+ -+ g_test_add ("/self/version/compare", -+ TestDummy, -+ NULL, -+ NULL, -+ test_version_compare, -+ NULL); -+ -+ g_test_add ("/self/version/check", -+ TestDummy, -+ NULL, -+ NULL, -+ test_version_check, -+ NULL); -+ -+ return g_test_run (); -+} -Index: bolt-0.8/tests/test-sysfs.c -=================================================================== ---- bolt-0.8.orig/tests/test-sysfs.c -+++ bolt-0.8/tests/test-sysfs.c -@@ -1101,6 +1101,31 @@ test_bootacl_allocate (TestBootacl *tt, - } - } - -+static void -+test_check_kernel_version (TestSysfs *tt, gconstpointer user) -+{ -+ gboolean ok; -+ -+ /* simulate read errors */ -+ ok = mock_sysfs_set_osrelease (tt->sysfs, NULL); -+ g_assert_true (ok); -+ g_assert_false (bolt_check_kernel_version (1, 0)); -+ -+ /* short kernel version */ -+ ok = mock_sysfs_set_osrelease (tt->sysfs, "1.0"); -+ g_assert_true (ok); -+ g_assert_true (bolt_check_kernel_version (1, 0)); -+ g_assert_false (bolt_check_kernel_version (1, 1)); -+ g_assert_false (bolt_check_kernel_version (2, 0)); -+ -+ /* more realistic kernel version */ -+ ok = mock_sysfs_set_osrelease (tt->sysfs, "1.0.0-111.fc1"); -+ g_assert_true (ok); -+ g_assert_true (bolt_check_kernel_version (1, 0)); -+ g_assert_false (bolt_check_kernel_version (1, 1)); -+ g_assert_false (bolt_check_kernel_version (2, 0)); -+} -+ - int - main (int argc, char **argv) - { -@@ -1181,5 +1206,12 @@ main (int argc, char **argv) - test_bootacl_allocate, - test_bootacl_tear_down); - -+ g_test_add ("/self/check-kernel-version", -+ TestSysfs, -+ NULL, -+ test_sysfs_setup, -+ test_check_kernel_version, -+ test_sysfs_tear_down); -+ - return g_test_run (); - } diff -Nru bolt-0.8/debian/patches/ignore-wakeup-device-uevents.patch bolt-0.9.1/debian/patches/ignore-wakeup-device-uevents.patch --- bolt-0.8/debian/patches/ignore-wakeup-device-uevents.patch 2020-09-10 08:53:51.000000000 +0000 +++ bolt-0.9.1/debian/patches/ignore-wakeup-device-uevents.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,46 +0,0 @@ -Description: manager: ignore wakeup device uevents for probing -The probing detection code should ignore wakeup device uevents because -these virtual devices can be added (and removed) without and -correspondence to any physical thunderbolt device (un-)plug events. -Author: Christian Kellner -Origin: backport, https://gitlab.freedesktop.org/bolt/bolt/-/merge_requests/209.patch -Bug: https://gitlab.freedesktop.org/bolt/bolt/-/issues/156 -Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/bolt/+bug/1892657 -Applied-Upstream: https://gitlab.freedesktop.org/bolt/bolt/-/commit/70bdb25c -Last-Update: 2020-09-10 ---- -This patch header follows DEP-3: http://dep.debian.net/deps/dep3/ -Index: bolt-0.8/boltd/bolt-manager.c -=================================================================== ---- bolt-0.8.orig/boltd/bolt-manager.c -+++ bolt-0.8/boltd/bolt-manager.c -@@ -2029,6 +2029,16 @@ device_is_thunderbolt_root (struct udev_ - } - - static gboolean -+device_is_wakeup (struct udev_device *dev) -+{ -+ const char *subsys; -+ -+ subsys = udev_device_get_subsystem (dev); -+ -+ return bolt_streq (subsys, "wakeup"); -+} -+ -+static gboolean - probing_add_root (BoltManager *mgr, - struct udev_device *dev) - { -@@ -2065,6 +2075,12 @@ manager_probing_device_added (BoltManage - if (syspath == NULL) - return; - -+ /* ignore events for wakeup devices which get removed -+ * and added at random time without any connection to -+ * any physical thunderbolt device */ -+ if (device_is_wakeup (dev)) -+ return; -+ - roots = mgr->probing_roots; - for (guint i = 0; i < roots->len; i++) - { diff -Nru bolt-0.8/debian/patches/series bolt-0.9.1/debian/patches/series --- bolt-0.8/debian/patches/series 2020-09-10 08:53:51.000000000 +0000 +++ bolt-0.9.1/debian/patches/series 2021-10-05 06:46:37.000000000 +0000 @@ -1,2 +1 @@ -gitlab_oldkernel_tests.patch -ignore-wakeup-device-uevents.patch +git_umockdev_update.patch diff -Nru bolt-0.8/debian/rules bolt-0.9.1/debian/rules --- bolt-0.8/debian/rules 2019-09-23 10:52:29.000000000 +0000 +++ bolt-0.9.1/debian/rules 2021-10-05 06:46:37.000000000 +0000 @@ -8,7 +8,6 @@ override_dh_auto_configure: dh_auto_configure -- \ - --libexecdir=/usr/lib/bolt \ -Dman=true \ -Dprivileged-group=sudo \ -Dinstall-tests=true diff -Nru bolt-0.8/debian/tests/control bolt-0.9.1/debian/tests/control --- bolt-0.8/debian/tests/control 2019-09-23 10:52:29.000000000 +0000 +++ bolt-0.9.1/debian/tests/control 2021-10-05 06:46:37.000000000 +0000 @@ -1,3 +1,9 @@ Tests: installed-tests -Depends: @, dpkg-dev, umockdev, python3-dbus, python3-dbusmock, python3-gi, gir1.2-umockdev-1.0 +Depends: dpkg-dev, + gir1.2-umockdev-1.0, + python3-dbus, + python3-dbusmock, + python3-gi, + umockdev, + @ Restrictions: allow-stderr diff -Nru bolt-0.8/debian/tests/installed-tests bolt-0.9.1/debian/tests/installed-tests --- bolt-0.8/debian/tests/installed-tests 2019-09-23 10:52:29.000000000 +0000 +++ bolt-0.9.1/debian/tests/installed-tests 2021-10-05 06:46:37.000000000 +0000 @@ -15,7 +15,7 @@ echo "Environment:" env | LC_ALL=C sort -u -for tests in /usr/lib/bolt/installed-tests/bolt/test-*; +for tests in /usr/libexec/installed-tests/bolt/test-*; do umockdev-wrapper "$tests" done diff -Nru bolt-0.8/debian/upstream/metadata bolt-0.9.1/debian/upstream/metadata --- bolt-0.8/debian/upstream/metadata 1970-01-01 00:00:00.000000000 +0000 +++ bolt-0.9.1/debian/upstream/metadata 2021-10-05 06:46:37.000000000 +0000 @@ -0,0 +1,4 @@ +Bug-Database: https://gitlab.freedesktop.org/bolt/bolt/issues +Bug-Submit: https://gitlab.freedesktop.org/bolt/bolt/issues/new +Repository: https://gitlab.freedesktop.org/bolt/bolt.git +Repository-Browse: https://gitlab.freedesktop.org/bolt/bolt diff -Nru bolt-0.8/docs/boltctl.1.txt bolt-0.9.1/docs/boltctl.1.txt --- bolt-0.8/docs/boltctl.1.txt 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/docs/boltctl.1.txt 2020-12-01 11:26:01.000000000 +0000 @@ -3,7 +3,7 @@ NAME ---- -boltctl - control the thunderbolt device manger +boltctl - control the thunderbolt device manager SYNOPSIS -------- @@ -39,7 +39,7 @@ *-U | --uuid {'full' | 'short' | 'alias' | N}*:: Control how UUIDs are printed. Since they are somewhat sensitive data - it is not advisable to share them publically in full length. Instead + it is not advisable to share them publicly in full length. Instead 'short' or 'alias' can and should be used when sharing the output of 'boltctl'. 'full';; @@ -47,11 +47,11 @@ 'short';; Truncate all UUIDs so only the first 13 characters are printed. 'alias';; - All UUIDs are replaced by a random string that is dervied from the + All UUIDs are replaced by a random string that is derived from the UUID, therefore the devices can be uniquely identified without revealing the original UUID. N;; - If a integer 'N' is specified, all UUIDs are truncted to only show up + If a integer 'N' is specified, all UUIDs are truncated to only show up to 'N'. COMMANDS @@ -78,12 +78,12 @@ format is 3 columns: permission, name, description. Permission indicates if the property is only readable or can also be written. -config 'KEY' ['VALLUE'] +config 'KEY' ['VALUE'] ~~~~~~~~~~~~~~~~~~~~~~~ Get or set, if 'VALUE' is specified, a global property. -config '.KEY' 'TARGET' ['VALLUE'] +config '.KEY' 'TARGET' ['VALUE'] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Get or set, if 'VALUE' is specified, a domain or device property, diff -Nru bolt-0.8/docs/boltd.8.txt bolt-0.9.1/docs/boltd.8.txt --- bolt-0.8/docs/boltd.8.txt 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/docs/boltd.8.txt 2020-12-01 11:26:01.000000000 +0000 @@ -71,7 +71,7 @@ 'IOMMU' support: if the hardware and firmware support using the input–output memory management unit (IOMMU) to restrict direct memory access to certain safe regions, boltd will detect that feature and change its -behavior: As long as iommu support is active, as inidcated by the +behavior: As long as iommu support is active, as indicated by the iommu_dma_protection sysfs attribute of the domain controller, new devices will be automatically enrolled with the 'iommu' policy and existing devices with 'iommu' (or 'auto') policy will be automatically authorized @@ -96,7 +96,7 @@ *--journal*:: Force logging to the journal. -*-v, --verbosee*:: +*-v, --verbose*:: Print debug output. diff -Nru bolt-0.8/meson.build bolt-0.9.1/meson.build --- bolt-0.8/meson.build 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/meson.build 2020-12-01 11:26:01.000000000 +0000 @@ -1,6 +1,6 @@ project('bolt', 'c', - version: '0.8', + version: '0.9.1', license : 'LGPL-2.1+', meson_version: '>= 0.44.0', default_options: ['warning_level=1', @@ -71,7 +71,12 @@ gnome = import('gnome') -glib = dependency('glib-2.0', version: '>= 2.50.0') +# version requirements +glib_min = [2, 56] +glib_req = '>= @0@.@1@.0'.format(glib_min[0], glib_min[1]) + +# dependency objects +glib = dependency('glib-2.0', version: glib_req) gio = dependency('gio-2.0') libudev = dependency('libudev') unix = dependency('gio-unix-2.0') @@ -153,6 +158,10 @@ conf.set('IS_COVERITY_BUILD', get_option('coverity')) +glib_chk = 'GLIB_VERSION_@0@_@1@'.format(glib_min[0], glib_min[1]) +conf.set('GLIB_VERSION_MIN_REQUIRED', glib_chk) +conf.set('GLIB_VERSION_MAX_ALLOWED', glib_chk) + config_h = configure_file( input: 'config.h.in', output: 'config.h', @@ -215,7 +224,8 @@ 'common/bolt-str.c', 'common/bolt-term.c', 'common/bolt-time.c', - 'common/bolt-unix.c' + 'common/bolt-unix.c', + 'common/bolt-wire.c', ] common_enums = gnome.mkenums_simple('bolt-enum-types', @@ -254,15 +264,18 @@ 'boltd/bolt-config.c', 'boltd/bolt-domain.c', 'boltd/bolt-exported.c', + 'boltd/bolt-guard.c', 'boltd/bolt-journal.c', 'boltd/bolt-manager.c', 'boltd/bolt-power.c', 'boltd/bolt-device.c', 'boltd/bolt-key.c', 'boltd/bolt-log.c', + 'boltd/bolt-reaper.c', 'boltd/bolt-store.c', 'boltd/bolt-sysfs.c', - 'boltd/bolt-udev.c' + 'boltd/bolt-udev.c', + 'boltd/bolt-watchdog.c' ]) install_data(['data/org.freedesktop.bolt.xml'], @@ -408,6 +421,11 @@ ['test-logging', [libdaemon]], ['test-store', [libdaemon]], ['test-journal', [libdaemon]], + ['test-watchdog', [libdaemon]], + ['test-self'], + ['test-guard', [libdaemon]], + ['test-reaper', [libdaemon]], + ['test-wire', [libdaemon]], ] if mockdev.found() @@ -446,7 +464,11 @@ test_env.prepend('LD_PRELOAD', 'libumockdev-preload.so.0') endif - test(test_name, test_exec, env: test_env, timeout: 120) + test(test_name, + test_exec, + args: ['-m', get_option('tests-speed')], + env: test_env, + timeout: 120) endforeach test_it = find_program(join_paths(srcdir, 'tests', 'test-integration')) @@ -543,6 +565,7 @@ 'build manpage: @0@'.format(build_man), 'install tests: @0@'.format(install_tests), 'profiling (gprof): @0@'.format(profiling), + 'tests speed: @0@'.format(get_option('tests-speed')), '' ] message('\n '.join(msg)) diff -Nru bolt-0.8/meson_options.txt bolt-0.9.1/meson_options.txt --- bolt-0.8/meson_options.txt 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/meson_options.txt 2020-12-01 11:26:01.000000000 +0000 @@ -6,3 +6,4 @@ option('privileged-group', type: 'string', value: 'wheel', description: 'Name of privileged group') option('profiling', type: 'boolean', value: 'false', description: 'Build with profiling support') option('systemd', type: 'boolean', value: 'true', description: 'DEPRECATED') +option('tests-speed', type: 'combo', choices: ['quick', 'slow'], value: 'quick', description : 'Which tests to run') diff -Nru bolt-0.8/scripts/uncrustify.cfg bolt-0.9.1/scripts/uncrustify.cfg --- bolt-0.8/scripts/uncrustify.cfg 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/scripts/uncrustify.cfg 2020-12-01 11:26:01.000000000 +0000 @@ -63,7 +63,7 @@ align_keep_tabs False align_with_tabs False align_on_tabstop False -align_number_left True +align_number_right False align_func_params True align_var_def_span 0 align_var_def_amp_style 1 diff -Nru bolt-0.8/scripts/uncrustify.sh bolt-0.9.1/scripts/uncrustify.sh --- bolt-0.8/scripts/uncrustify.sh 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/scripts/uncrustify.sh 2020-12-01 11:26:01.000000000 +0000 @@ -1,7 +1,20 @@ -#!/bin/sh +#!/bin/bash SRCROOT=`git rev-parse --show-toplevel` CFG="$SRCROOT/scripts/uncrustify.cfg" echo "srcroot: $SRCROOT" + +case "$1" in + -c|--check) + OPTS="--check" + ;; + + *) + OPTS="--replace --no-backup" + ;; +esac + pushd "$SRCROOT" -uncrustify -c "$CFG" --no-backup `git ls-tree --name-only -r HEAD | grep \\\.[ch]$ | grep -v gvdb | grep -v build/` +uncrustify -c "$CFG" $OPTS `git ls-tree --name-only -r HEAD | grep \\\.[ch]$ | grep -v gvdb | grep -v build/` +RES=$? popd +exit $RES diff -Nru bolt-0.8/tests/bolt-test.c bolt-0.9.1/tests/bolt-test.c --- bolt-0.8/tests/bolt-test.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/tests/bolt-test.c 2020-12-01 11:26:01.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright © 2018 Red Hat, Inc + * Copyright © 2018-2019 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -23,6 +23,20 @@ #include "bolt-test.h" #include "bolt-fs.h" +#include "bolt-io.h" +#include "bolt-macros.h" +#include "bolt-names.h" +#include "bolt-str.h" + +#include + +#include +#include +#include +#include +#include +#include +#include BoltTmpDir bolt_tmp_dir_make (const char *pattern, @@ -63,3 +77,394 @@ g_free (dir); } + +/* Notification Socket */ + +struct NotifySocket +{ + BoltTmpDir tmpdir; + char *socket_path; + guint socket_watch; + int socket_fd; + + /* */ + guint counter; + GQueue messages; +}; + +union ctrlmsg +{ + struct cmsghdr hdr; + guint8 buf[CMSG_SPACE (sizeof (struct ucred))]; +}; + + +NotifySocket * +notify_socket_new (void) +{ + g_autoptr(GError) err = NULL; + bolt_autoclose int fd = -1; + NotifySocket *ns = NULL; + static const int one = 1; + struct sockaddr_un sau = {AF_UNIX, {'\0', }}; + size_t socklen; + int r; + + ns = g_new0 (NotifySocket, 1); + + ns->tmpdir = bolt_tmp_dir_make ("bolt.unix.XXXXXX", &err); + g_assert_no_error (err); + g_assert_nonnull (ns->tmpdir); + + fd = socket (AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + assert (fd > -1); /* plain assert for coverity */ + + ns->socket_path = g_build_filename (ns->tmpdir, "notify_socket", NULL); + + strncpy (sau.sun_path, ns->socket_path, sizeof (sau.sun_path) - 1); + + socklen = + offsetof (struct sockaddr_un, sun_path) + + strlen (sau.sun_path) + + 1; + + r = bind (fd, &sau, socklen); + g_assert_cmpint (r, >, -1); + + r = setsockopt (fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof (one)); + g_assert_cmpint (r, >, -1); + + g_queue_init (&ns->messages); + ns->socket_fd = bolt_steal (&fd, -1); + + g_debug ("notification socket at '%s'", sau.sun_path); + return ns; +} + +void +notify_socket_free (NotifySocket *ns) +{ + g_autoptr(GError) err = NULL; + + g_clear_handle_id (&ns->socket_watch, g_source_remove); + + if (ns->socket_fd > -1) + { + bolt_close (ns->socket_fd, &err); + g_assert_no_error (err); + ns->socket_fd = -1; + } + + g_clear_pointer (&ns->tmpdir, bolt_tmp_dir_destroy); + g_queue_free_full (&ns->messages, g_free); + g_clear_pointer (&ns->socket_path, g_free); + g_free (ns); +} + +char * +notify_socket_revmsg (NotifySocket *ns, gboolean queue) +{ + char data[4096]; + char *msg; + struct iovec iov = { + .iov_base = data, + .iov_len = sizeof (data) - 1, + }; + union ctrlmsg crtl = {}; + struct msghdr hdr = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = &crtl, + .msg_controllen = sizeof (crtl), + }; + struct ucred *ucred = NULL; + ssize_t r; + + /* MSG_TRUNC: return the real size */ + r = recvmsg (ns->socket_fd, &hdr, MSG_DONTWAIT | MSG_CMSG_CLOEXEC | MSG_TRUNC); + + if (r < 0) + { + if (errno == EINTR || errno == EAGAIN) + return NULL; + + g_critical ("i/o error reading from notify socket: %m"); + return NULL; + } + + if (hdr.msg_flags & MSG_TRUNC || ((size_t) r > sizeof (data) - 1)) + { + g_warning ("notification message truncated"); + return NULL; + } + + g_assert_cmpint (r, <, sizeof (data)); + + data[r] = '\0'; + + ns->counter++; + msg = g_strdup (data); + + for (struct cmsghdr *c = CMSG_FIRSTHDR (&hdr); + c != NULL; + c = CMSG_NXTHDR (&hdr, c)) + { + if (c->cmsg_level != SOL_SOCKET) + continue; + if (c->cmsg_type == SCM_CREDENTIALS && + c->cmsg_len == CMSG_LEN (sizeof (struct ucred))) + ucred = (struct ucred *) (void *) CMSG_DATA (c); + } + + if (queue) + g_queue_push_tail (&ns->messages, msg); + + g_debug ("got message: '%s' [%s]", msg, bolt_yesno (queue)); + if (ucred != NULL) + g_debug (" ucred, pid: %i, uid: %li, gid: %li", + (int) ucred->pid, (long) ucred->uid, (long) ucred->gid); + + return msg; +} + +static gboolean +got_notification (gpointer user_data) +{ + NotifySocket *ns = (NotifySocket *) user_data; + + notify_socket_revmsg (ns, TRUE); + + return TRUE; +} + +void +notify_socket_enable_watch (NotifySocket *ns) +{ + g_autoptr(GSource) source = NULL; + + g_assert_nonnull (ns); + g_assert_cmpuint (ns->socket_fd, >, -1); + + source = g_unix_fd_source_new (ns->socket_fd, G_IO_IN); + g_assert_nonnull (source); + + g_source_set_callback (source, got_notification, ns, NULL); + ns->socket_watch = g_source_attach (source, NULL); +} + +void +notify_socket_set_environment (NotifySocket *ns) +{ + g_setenv (BOLT_SD_NOTIFY_SOCKET, ns->socket_path, TRUE); +} + +void +notify_socket_make_pollfd (NotifySocket *ns, + GPollFD *fd) +{ + memset (fd, 0, sizeof (GPollFD)); + fd->fd = ns->socket_fd; + fd->events = G_IO_IN | G_IO_HUP | G_IO_ERR; +} + +/* Version parsing, checking */ +static gboolean +parse_one (const char *str, + BoltVersion *version, + int *index, + GError **error) +{ + gboolean ok; + gint64 v; + int i = *index; + + ok = g_ascii_string_to_signed (str, + 10, /* base */ + 0, G_MAXINT, + &v, + error); + + if (!ok) + return FALSE; + + version->triplet[i] = (int) v; + *index = i + 1; + + return TRUE; +} + +gboolean +bolt_version_parse (const char *str, + BoltVersion *version, + GError **error) +{ + g_autofree char *tmp = NULL; + gboolean ok = FALSE; + char *data = NULL; + char *delim = NULL; + int index = 0; + + g_return_val_if_fail (str != NULL, FALSE); + g_return_val_if_fail (version != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + bolt_version_clear (version); + + /* so we manipulate the string */ + tmp = data = g_strdup (str); + (void) tmp; /* 'dead' store for g_autofree */ + + delim = strchr (data, '-'); + + if (delim) + { + *delim = '\0'; + version->suffix = g_strdup (delim + 1); + } + + while (index < 2 && (delim = strchr (data, '.')) != NULL) + { + *delim = '\0'; + ok = parse_one (data, version, &index, error); + + if (!ok) + return FALSE; + + data = delim + 1; + } + + return parse_one (data, version, &index, error); +} + +void +bolt_version_clear (BoltVersion *version) +{ + version->major = -1; + version->minor = -1; + version->patch = -1; + + g_clear_pointer (&version->suffix, g_free); +} + +/* strcmp semantics */ +int +bolt_version_compare (BoltVersion *a, + BoltVersion *b) +{ + g_return_val_if_fail (a != NULL, -1); + g_return_val_if_fail (b != NULL, 1); + + for (int i = 0; i < 3; i++) + { + int ac = a->triplet[i]; + int bc = b->triplet[i]; + + if (ac < bc) + return -1; + else if (ac > bc) + return 1; + } + + return 0; /* all components equal */ +} + +gboolean +bolt_version_check (BoltVersion *base, + int major, + int minor, + int patch) +{ + BoltVersion ref = BOLT_VERSION_INIT (major, minor, patch); + + g_return_val_if_fail (base != NULL, FALSE); + + return bolt_version_compare (base, &ref) >= 0; +} + +gboolean +bolt_check_kernel_version (int major, int minor) +{ + g_autoptr(GError) err = NULL; + g_autofree char *data = NULL; + g_auto(BoltVersion) ver = BOLT_VERSION_INIT (1, 0, 0); + gboolean ok; + gsize length; + + ok = g_file_get_contents ("/proc/sys/kernel/osrelease", + &data, + &length, + &err); + + if (!ok) + { + g_message ("Could not read kernel version: %s", err->message); + return FALSE; + } + + while (length > 1 && data[length - 1] == '\n') + data[--length] = '\0'; + + ok = bolt_version_parse (data, &ver, &err); + if (!ok) + { + g_message ("Could not parse kernel version (%s): %s", + data, err->message); + return FALSE; + } + + g_debug ("Read kernel version: %d.%d.%d (%s)", + ver.major, ver.minor, ver.patch, + (ver.suffix ? : "")); + + return bolt_version_check (&ver, major, minor, -1); +} + +typedef struct MainLoopCtx +{ + GMainLoop *loop; + gboolean timeout; +} MainLoopCtx; + +static gboolean +on_main_loop_timeout (gpointer user_data) +{ + MainLoopCtx *ctx = user_data; + + ctx->timeout = TRUE; + g_main_loop_quit (ctx->loop); + + return G_SOURCE_REMOVE; +} + +gboolean +bolt_test_run_main_loop (GMainLoop *loop, + guint timeout_seconds, + gboolean exit_on_timeout, + GError **error) +{ + MainLoopCtx ctx = { .loop = loop, .timeout = FALSE }; + guint tid; + + tid = g_timeout_add_seconds (timeout_seconds, + on_main_loop_timeout, + &ctx); + + if (ctx.timeout) + { + const char *message = "Operation timed out"; + g_set_error (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, + "%s", message); + + if (exit_on_timeout) + { + g_warning ("test error: %s", message); + g_assert_not_reached (); + } + + return FALSE; + } + + g_source_remove (tid); + + return TRUE; +} diff -Nru bolt-0.8/tests/bolt-test.h bolt-0.9.1/tests/bolt-test.h --- bolt-0.8/tests/bolt-test.h 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/tests/bolt-test.h 2020-12-01 11:26:01.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright © 2018 Red Hat, Inc + * Copyright © 2018-2019 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -22,6 +22,8 @@ #include +G_BEGIN_DECLS + typedef char *BoltTmpDir; BoltTmpDir bolt_tmp_dir_make (const char *pattern, @@ -56,4 +58,76 @@ va__, sa__[il__], "!=", sb__[il__]); \ } \ } G_STMT_END + +#define skip_test_if(condition, message) G_STMT_START { \ + if (condition) \ + { \ + g_test_skip (message); \ + return; \ + } \ + } G_STMT_END + +#define skip_test_unless(condition, message) skip_test_if(!(condition), message) + /* *INDENT-ON* */ + +/* Notification Socket */ +typedef struct NotifySocket NotifySocket; + +NotifySocket * notify_socket_new (void); +void notify_socket_free (NotifySocket *ns); + +char * notify_socket_revmsg (NotifySocket *ns, + gboolean queue); +void notify_socket_enable_watch (NotifySocket *ns); +void notify_socket_set_environment (NotifySocket *ns); +void notify_socket_make_pollfd (NotifySocket *ns, + GPollFD *fd); + +/* Version parsing, checking */ + +typedef struct BoltVersion_ BoltVersion; +struct BoltVersion_ +{ + union + { + struct + { + int major; + int minor; + int patch; + }; + + int triplet[3]; + }; + + char *suffix; +}; + +#define BOLT_VERSION_INIT(ma, mi, pa) {.major = (ma), .minor = (mi), .patch = (pa), .suffix = NULL} + +gboolean bolt_version_parse (const char *str, + BoltVersion *version, + GError **error); + +void bolt_version_clear (BoltVersion *version); + +int bolt_version_compare (BoltVersion *a, + BoltVersion *b); + +gboolean bolt_version_check (BoltVersion *version, + int major, + int minor, + int patch); + +G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (BoltVersion, bolt_version_clear); + +gboolean bolt_check_kernel_version (int major, + int minor); + +gboolean bolt_test_run_main_loop (GMainLoop *loop, + guint timeout_seconds, + gboolean exit_on_timeout, + GError **error); + +G_END_DECLS diff -Nru bolt-0.8/tests/mock-sysfs.c bolt-0.9.1/tests/mock-sysfs.c --- bolt-0.8/tests/mock-sysfs.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/tests/mock-sysfs.c 2020-12-01 11:26:01.000000000 +0000 @@ -21,6 +21,7 @@ #include "config.h" #include "bolt-io.h" +#include "bolt-fs.h" #include "bolt-names.h" #include "bolt-str.h" @@ -28,10 +29,12 @@ #include +#include #include #include #include +#include typedef struct _MockDevice MockDevice; struct _MockDevice @@ -55,9 +58,11 @@ g_clear_pointer (&device->devices, g_hash_table_unref); g_free (device->path); + g_free (device->idstr); g_free (device); } + typedef struct _MockDomain MockDomain; struct _MockDomain @@ -69,6 +74,10 @@ gint serial; + guint32 nhi_id; + char *nhi_idstr; + char *nhi_path; + MockDevice *host; }; @@ -79,6 +88,9 @@ mock_device_destroy (domain->host); + g_free (domain->nhi_idstr); + g_free (domain->nhi_path); + g_free (domain->idstr); g_free (domain->path); g_free (domain); @@ -86,13 +98,14 @@ /* prototypes */ -static MockDevice * mock_sysfs_device_plug (MockSysfs *ms, - MockDomain *domain, - char *parent, - MockDevId *id, - guint authorized, - const char *key, - gint boot); +static MockDevice * mock_sysfs_device_plug (MockSysfs *ms, + MockDomain *domain, + char *parent, + MockDevId *id, + guint authorized, + const char *key, + gint boot, + BoltLinkSpeed *link); static void mock_sysfs_device_unplug (MockSysfs *ms, MockDevice *dev); @@ -109,6 +122,7 @@ char *force_power; GHashTable *domains; GHashTable *devices; + char *dmi; }; @@ -132,10 +146,17 @@ { MockSysfs *ms = MOCK_SYSFS (object); - g_clear_object (&ms->bed); + if (ms->dmi) + mock_sysfs_dmi_id_remove (ms); + + if (ms->force_power) + mock_sysfs_force_power_remove (ms); + g_clear_pointer (&ms->domains, g_hash_table_unref); g_clear_pointer (&ms->devices, g_hash_table_unref); + g_clear_object (&ms->bed); + G_OBJECT_CLASS (mock_sysfs_parent_class)->finalize (object); } @@ -214,20 +235,25 @@ #define CONST_STRV(...) (char **) (const char *[]){ __VA_ARGS__} static MockDevice * -mock_sysfs_device_plug (MockSysfs *ms, - MockDomain *domain, - char *parent, - MockDevId *id, - guint authorized, - const char *key, - gint boot) +mock_sysfs_device_plug (MockSysfs *ms, + MockDomain *domain, + char *parent, + MockDevId *id, + guint authorized, + const char *key, + gint boot, + BoltLinkSpeed *link) { g_autofree char *idstr = NULL; g_autofree char *vendor_id = NULL; g_autofree char *device_id = NULL; g_autofree char *authstr = NULL; g_autofree char *bootstr = NULL; - const char *props[17] = {NULL, }; + g_autofree char *rx_speed = NULL; + g_autofree char *tx_speed = NULL; + g_autofree char *rx_lanes = NULL; + g_autofree char *tx_lanes = NULL; + const char *props[25] = {NULL, }; MockDevice *device; guint serial; char *path; @@ -236,21 +262,43 @@ serial = (domain->serial)++; idstr = g_strdup_printf ("%u-%u", domain->id, serial); - vendor_id = g_strdup_printf ("%d", id->vendor_id); - device_id = g_strdup_printf ("%d", id->device_id); authstr = g_strdup_printf ("%u", authorized); i = 0; - props[i++] = "vendor"; - props[i++] = vendor_id; - props[i++] = "vendor_name"; - props[i++] = id->vendor_name; - props[i++] = "device"; - props[i++] = device_id; - props[i++] = "device_name"; - props[i++] = id->device_name; - props[i++] = "unique_id"; - props[i++] = id->unique_id; + + if (id->vendor_id) + { + vendor_id = g_strdup_printf ("%d", id->vendor_id); + props[i++] = "vendor"; + props[i++] = vendor_id; + } + + if (id->vendor_name) + { + props[i++] = "vendor_name"; + props[i++] = id->vendor_name; + } + + if (id->device_id) + { + device_id = g_strdup_printf ("%d", id->device_id); + + props[i++] = "device"; + props[i++] = device_id; + } + + if (id->device_name) + { + props[i++] = "device_name"; + props[i++] = id->device_name; + } + + if (id->unique_id) + { + props[i++] = "unique_id"; + props[i++] = id->unique_id; + } + props[i++] = "authorized"; props[i++] = authstr; @@ -267,6 +315,23 @@ props[i++] = bootstr; } + if (link) + { + rx_speed = g_strdup_printf ("%u Gb/s\n", link->rx.speed); + tx_speed = g_strdup_printf ("%u Gb/s\n", link->tx.speed); + rx_lanes = g_strdup_printf ("%u\n", link->rx.lanes); + tx_lanes = g_strdup_printf ("%u\n", link->tx.lanes); + + props[i++] = "rx_speed"; + props[i++] = rx_speed; + props[i++] = "tx_speed"; + props[i++] = tx_speed; + props[i++] = "rx_lanes"; + props[i++] = rx_lanes; + props[i++] = "tx_lanes"; + props[i++] = tx_lanes; + } + props[i++] = NULL; g_assert (sizeof (props) >= i); @@ -362,6 +427,7 @@ umockdev_testbed_uevent (ms->bed, ms->force_power, "remove"); umockdev_testbed_remove_device (ms->bed, ms->force_power); + g_clear_pointer (&ms->force_power, g_free); return TRUE; } @@ -437,6 +503,57 @@ return bolt_streq (data, "1"); } + +/* dmi */ +const char * +mock_sysfs_dmi_id_add (MockSysfs *ms, + const char *sys_vendor, + const char *product_name, + const char *product_version) +{ + const char *props[25] = {NULL, }; + guint i = 0; + char *path; + + g_return_val_if_fail (MOCK_IS_SYSFS (ms), NULL); + g_return_val_if_fail (ms->dmi == NULL, NULL); + + props[i++] = BOLT_SYSFS_DMI_SYS_VENDOR; + props[i++] = sys_vendor; + + props[i++] = BOLT_SYSFS_DMI_PRODUCT_NAME; + props[i++] = product_name; + + props[i++] = BOLT_SYSFS_DMI_PRODUCT_VERSION; + props[i++] = product_version; + + props[i++] = NULL; + g_assert (sizeof (props) >= i); + + path = umockdev_testbed_add_devicev (ms->bed, "dmi", "id", + NULL, + (char **) props, + NULL); + + + ms->dmi = path; + + return path; +} + +gboolean +mock_sysfs_dmi_id_remove (MockSysfs *ms) +{ + g_return_val_if_fail (MOCK_IS_SYSFS (ms), FALSE); + g_return_val_if_fail (ms->dmi != NULL, FALSE); + + umockdev_testbed_uevent (ms->bed, ms->dmi, "remove"); + umockdev_testbed_remove_device (ms->bed, ms->dmi); + g_clear_pointer (&ms->dmi, g_free); + + return TRUE; +} + /* public methods: domain */ const char * @@ -445,11 +562,15 @@ ...) { g_autofree char *acl = NULL; + g_autofree char *nhi_pciid = NULL; + g_autofree char *nhi_idstr = NULL; + g_autofree char *nhi_path = NULL; const char *props[7] = {NULL, }; const char *secstr; const char *key; MockDomain *domain; va_list args; + guint32 nhi = 0x15d2; char *idstr = NULL; char *path; guint id; @@ -483,6 +604,10 @@ props[i++] = BOLT_SYSFS_IOMMU; props[i++] = iommu; } + else if (bolt_streq (key, "nhi")) + { + nhi = va_arg (args, int); + } } g_assert (i < 7); @@ -492,18 +617,40 @@ g_assert (sizeof (props) >= i); + /* native host interface (NHI) */ + nhi_pciid = g_strdup_printf ("0x%04x", nhi); + nhi_idstr = g_strdup_printf ("0000:00:01.%u", id); + + nhi_path = umockdev_testbed_add_devicev (ms->bed, "pci", nhi_idstr, + NULL, /* parent: NHI has none */ + CONST_STRV ("class", "0x088000", + "vendor", "0x8086", /* Intel */ + "device", nhi_pciid, + NULL), + CONST_STRV ("DRIVER", "thunderbolt", NULL)); + + g_debug ("M [A] %s (0x%04x) @ %s", nhi_idstr, nhi, nhi_path); + + /* add the domain */ path = umockdev_testbed_add_devicev (ms->bed, "thunderbolt", idstr, - NULL, /* parent: domain has none */ + nhi_path, /* parent */ (char **) props, CONST_STRV ("DEVTYPE", "thunderbolt_domain", NULL)); if (path == NULL) - return path; + { + umockdev_testbed_remove_device (ms->bed, nhi_path); + return path; + } g_debug ("M [A] %s (%s) @ %s", idstr, secstr, path); domain = g_new0 (MockDomain, 1); + domain->nhi_id = nhi; + domain->nhi_idstr = g_steal_pointer (&nhi_idstr); + domain->nhi_path = g_steal_pointer (&nhi_path); + domain->id = id; domain->idstr = idstr; domain->path = path; @@ -555,6 +702,11 @@ umockdev_testbed_uevent (ms->bed, domain->path, "remove"); umockdev_testbed_remove_device (ms->bed, domain->path); + g_debug ("M [R] %s @ %s", domain->nhi_idstr, domain->nhi_path); + + umockdev_testbed_uevent (ms->bed, domain->nhi_path, "remove"); + umockdev_testbed_remove_device (ms->bed, domain->nhi_path); + g_hash_table_remove (ms->domains, id); return TRUE; } @@ -691,20 +843,65 @@ domain->path, id, 1, - NULL, /* no key for the host */ - -1); /* no boot file either */ + NULL, /* no key for the host */ + -1, /* no boot file either */ + NULL); /* no link settings */ domain->host = device; return device->idstr; } +void +mock_sysfs_host_remove (MockSysfs *ms, + const char *host) +{ + GHashTableIter iter; + gpointer k, v; + MockDevice *dev; + MockDomain *domain = NULL; + + g_return_if_fail (MOCK_IS_SYSFS (ms)); + g_return_if_fail (host != NULL); + + dev = g_hash_table_lookup (ms->devices, host); + + if (dev == NULL) + { + g_error ("Device not found for %s", host); + return; + } + + g_hash_table_iter_init (&iter, ms->domains); + while (g_hash_table_iter_next (&iter, &k, &v)) + { + MockDomain *d = v; + if (d->host == dev) + { + domain = d; + break; + } + } + + if (!domain) + { + g_error ("domain not found for host: %s", host); + return; + } + + mock_sysfs_device_unplug (ms, dev); + mock_device_destroy (dev); + domain->host = NULL; +} + + const char * -mock_sysfs_device_add (MockSysfs *ms, - const char *parent, - MockDevId *id, - guint authorized, - const char *key, - gint boot) +mock_sysfs_device_add (MockSysfs *ms, + const char *parent, + MockDevId *id, + guint authorized, + const char *key, + gint boot, + BoltLinkSpeed *speed) { MockDevice *pdev; MockDomain *domain = NULL; @@ -747,7 +944,8 @@ id, 1, key, - boot); + boot, + speed); g_hash_table_insert (pdev->devices, device->idstr, device); @@ -835,3 +1033,65 @@ return TRUE; } + +gboolean +mock_sysfs_set_osrelease (MockSysfs *ms, + const char *version) +{ + g_autoptr(GError) err = NULL; + g_autoptr(GFile) target = NULL; + g_autofree char *data = NULL; + g_autofree char *path = NULL; + g_autofree char *root = NULL; + gboolean ok; + gsize n; + int r; + + root = umockdev_testbed_get_root_dir (ms->bed); + target = g_file_new_build_filename (root, "proc/sys/kernel/osrelease", NULL); + + ok = bolt_fs_make_parent_dirs (target, &err); + if (!ok) + { + g_debug ("Failed to make parent dirs: %s", err->message); + return FALSE; + } + + if (version != NULL) + data = g_strdup_printf ("%s\n", version); + else + data = g_strdup ("\n"); + + n = strlen (data); + + /* make sure we can write the file, if it fails, + * we will indirectly catch it in the write */ + path = g_file_get_path (target); + r = chmod (path, 0644); + + if (r != 0) + g_debug ("Failed to set mode: %s", g_strerror (errno)); + + ok = g_file_replace_contents (target, + data, n, + NULL, + FALSE, + 0, + NULL, + NULL, + &err); + + if (!ok) + g_debug ("Failed to set osrelease: %s", err->message); + + /* make the file non-readable if version == NULL, + * so to simulate read errors */ + if (version == NULL) + { + r = chmod (path, 0000); + if (r != 0) + g_debug ("Failed to set mode: %s", g_strerror (errno)); + } + + return ok; +} diff -Nru bolt-0.8/tests/mock-sysfs.h bolt-0.9.1/tests/mock-sysfs.h --- bolt-0.8/tests/mock-sysfs.h 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/tests/mock-sysfs.h 2020-12-01 11:26:01.000000000 +0000 @@ -24,6 +24,7 @@ #include #include "bolt-enums.h" +#include "bolt-wire.h" G_BEGIN_DECLS @@ -58,6 +59,13 @@ gboolean mock_sysfs_force_power_enabled (MockSysfs *ms); +const char * mock_sysfs_dmi_id_add (MockSysfs *ms, + const char *sys_vendor, + const char *product_name, + const char *product_version); + +gboolean mock_sysfs_dmi_id_remove (MockSysfs *ms); + const char * mock_sysfs_domain_add (MockSysfs *ms, BoltSecurity security, ...) G_GNUC_NULL_TERMINATED; @@ -86,12 +94,16 @@ const char *domain, MockDevId *id); -const char * mock_sysfs_device_add (MockSysfs *ms, - const char *parent, - MockDevId *id, - guint authorized, - const char *key, - gint boot); +void mock_sysfs_host_remove (MockSysfs *ms, + const char *host); + +const char * mock_sysfs_device_add (MockSysfs *ms, + const char *parent, + MockDevId *id, + guint authorized, + const char *key, + gint boot, + BoltLinkSpeed *link); const char * mock_sysfs_device_get_syspath (MockSysfs *ms, const char *id); @@ -102,4 +114,8 @@ gboolean mock_sysfs_device_remove (MockSysfs *ms, const char *id); +gboolean mock_sysfs_set_osrelease (MockSysfs *ms, + const char *version); + + G_END_DECLS diff -Nru bolt-0.8/tests/test-common.c bolt-0.9.1/tests/test-common.c --- bolt-0.8/tests/test-common.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/tests/test-common.c 2020-12-01 11:26:01.000000000 +0000 @@ -41,6 +41,7 @@ #include #include +#include #include #include #include @@ -60,6 +61,57 @@ int dummy; } TestDummy; +/* test tables for string to number conversions */ +struct +{ + const char *str; + gint val; + gboolean error; +} str_to_int_table[] = { + {"0", 0, FALSE}, + {"1", 1, FALSE}, + {"-1", -1, FALSE}, +#if __SIZEOF_INT__ == 4 + {"2147483647", 2147483647, FALSE}, /* MAX_INT */ + {"-2147483648", -2147483648, FALSE}, /* MIN_INT */ + {"2147483648", 0, TRUE}, /* MAX_INT + 1 */ + {"-2147483649", 0, TRUE}, /* MIN_INT - 1 */ +#elif __SIZEOF_INT__ == 8 + {"9223372036854775807", 9223372036854775807, FALSE}, /* MAX_INT */ + {"-9223372036854775808", -9223372036854775808, FALSE}, /* MIN_INT */ + {"9223372036854775808", 0, TRUE}, /* MAX_INT + 1 */ + {"-9223372036854775809", 0, TRUE}, /* MIN_INT - 1 */ +#else +#warning __SIZEOF_INT__ not handled +#endif + {"notanint", 0, TRUE}, + {"9223372036854775808", 0, TRUE}, /* overflow */ + {"-9223372036854775809", 0, TRUE}, /* underflow */ +}; + +struct +{ + const char *str; + guint val; + gboolean error; +} str_to_uint_table[] = { + {"0", 0, FALSE}, + {"1", 1, FALSE}, + {"-1", 0, TRUE}, /* negative */ +#if __SIZEOF_INT__ == 4 + {"4294967295", 4294967295, FALSE}, /* MAX_UINT */ + {"4294967296", 0, TRUE}, /* MAX_UINT + 1 */ +#elif __SIZEOF_INT__ == 8 + {"18446744073709551615", 18446744073709551615, FALSE}, /* MAX_INT */ + {"18446744073709551616", 0, TRUE}, /* MAX_INT + 1 */ +#else +#warning __SIZEOF_INT__ not handled +#endif + {"notanint", 0, TRUE}, + {"18446744073709551617", 0, TRUE}, /* overflow */ +}; + + #define TEST_DBUS_GRESOURCE_PATH "/bolt/tests/exported/example.bolt.xml" #define TEST_DBUS_INTERFACE "org.gnome.bolt.Example" @@ -672,6 +724,7 @@ struct stat st; char buffer[256] = {0, }; gboolean ok; + unsigned int uiv; int fd = -1; int iv; int r; @@ -828,6 +881,17 @@ g_assert_false (ok); g_clear_pointer (&err, g_error_free); + /* read_uint_at */ + ok = bolt_read_uint_at (dirfd (root), "NONEXISTENT", &uiv, &err); + g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); + g_assert_false (ok); + g_clear_pointer (&err, g_error_free); + + ok = bolt_read_uint_at (dirfd (root), "readonly", &uiv, &err); + g_assert_error (err, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); + g_assert_false (ok); + g_clear_pointer (&err, g_error_free); + /* pipe error checking */ fd = bolt_mkfifo (tt->path, 0600, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_EXISTS); @@ -879,6 +943,34 @@ } static void +test_io_mkdirat (TestIO *tt, gconstpointer user_data) +{ + g_autoptr(GError) error = NULL; + g_autoptr(DIR) d = NULL; + struct stat st; + gboolean ok; + + d = bolt_opendir (tt->path, &error); + + g_assert_no_error (error); + g_assert_nonnull (d); + + ok = bolt_mkdirat (dirfd (d), "directory", 0666, &error); + g_assert_no_error (error); + g_assert_true (ok); + + ok = bolt_fstatat (dirfd (d), "directory", &st, 0, &error); + g_assert_no_error (error); + g_assert_true (ok); + + g_assert_true (S_ISDIR (st.st_mode)); + + ok = bolt_mkdirat (dirfd (d), "directory", 0666, &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_EXISTS); + g_assert_false (ok); +} + +static void test_io_verify (TestIO *tt, gconstpointer user_data) { g_autoptr(GError) error = NULL; @@ -933,12 +1025,185 @@ } static void +test_io_write_file_at (TestIO *tt, gconstpointer user_data) +{ + g_autoptr(GError) error = NULL; + g_autoptr(DIR) dir = NULL; + g_autofree char *path = NULL; + g_autofree char *data = NULL; + static const char *ref = "The world is everything that is the case."; + gboolean ok; + gsize len; + + dir = bolt_opendir (tt->path, &error); + g_assert_no_error (error); + g_assert_nonnull (dir); + + ok = bolt_write_file_at (dirfd (dir), "test.txt", ref, -1, &error); + + g_assert_no_error (error); + g_assert_true (ok); + + path = g_build_filename (tt->path, "test.txt", NULL); + ok = g_file_get_contents (path, &data, &len, &error); + g_assert_no_error (error); + g_assert_true (ok); + g_assert_cmpuint (strlen (ref), ==, len); + g_assert_cmpstr (ref, ==, data); + + g_clear_pointer (&data, g_free); + ok = bolt_file_write_all (path, ref, 5, &error); + + g_assert_no_error (error); + g_assert_true (ok); + + ok = g_file_get_contents (path, &data, &len, &error); + g_assert_no_error (error); + g_assert_true (ok); + g_assert_cmpuint (len, ==, 5); + g_assert_true (strncmp (data, ref, 5) == 0); +} + +static void +test_io_write_int_at (TestIO *tt, gconstpointer user_data) +{ + g_autoptr(GError) error = NULL; + g_autoptr(DIR) dir = NULL; + gboolean ok; + int tests[] = {0, 1, 42, G_MAXINT}; + + dir = bolt_opendir (tt->path, &error); + g_assert_no_error (error); + g_assert_nonnull (dir); + + for (gsize i = 0; i < G_N_ELEMENTS (tests); i++) + { + g_autoptr(GError) err = NULL; + int ref = tests[i]; + int val; + + ok = bolt_write_int_at (dirfd (dir), "int.txt", ref, &err); + g_assert_true (ok); + + ok = bolt_read_int_at (dirfd (dir), "int.txt", &val, &err); + g_assert_no_error (err); + g_assert_true (ok); + g_assert_cmpint (val, ==, ref); + } +} + +static void +test_io_read_int_at (TestIO *tt, gconstpointer user_data) +{ + g_autoptr(GError) error = NULL; + g_autoptr(DIR) dir = NULL; + gboolean ok; + + dir = bolt_opendir (tt->path, &error); + g_assert_no_error (error); + g_assert_nonnull (dir); + + for (gsize i = 0; i < G_N_ELEMENTS (str_to_int_table); i++) + { + g_autoptr(GError) err = NULL; + const char *txt = str_to_int_table[i].str; + gboolean expect_error = str_to_int_table[i].error; + gint val = str_to_int_table[i].val; + gint v; + + ok = bolt_write_file_at (dirfd (dir), "int.txt", txt, -1, &err); + g_assert_true (ok); + + ok = bolt_read_int_at (dirfd (dir), "int.txt", &v, &err); + if (expect_error) + { + g_assert_nonnull (err); + g_assert_false (ok); + } + else + { + g_assert_no_error (err); + g_assert_cmpint (val, ==, v); + g_assert_true (ok); + } + } +} + +static void +test_io_write_uint_at (TestIO *tt, gconstpointer user_data) +{ + g_autoptr(GError) error = NULL; + g_autoptr(DIR) dir = NULL; + gboolean ok; + unsigned int tests[] = {42, 0, 1, G_MAXUINT}; + + dir = bolt_opendir (tt->path, &error); + g_assert_no_error (error); + g_assert_nonnull (dir); + + for (gsize i = 0; i < G_N_ELEMENTS (tests); i++) + { + g_autoptr(GError) err = NULL; + unsigned int ref = tests[i]; + unsigned int val; + + ok = bolt_write_uint_at (dirfd (dir), "uint.txt", ref, &err); + g_assert_true (ok); + + ok = bolt_read_uint_at (dirfd (dir), "uint.txt", &val, &err); + g_assert_no_error (err); + g_assert_true (ok); + g_assert_cmpint (val, ==, ref); + } +} + +static void +test_io_read_uint_at (TestIO *tt, gconstpointer user_data) +{ + g_autoptr(GError) error = NULL; + g_autoptr(DIR) dir = NULL; + gboolean ok; + + dir = bolt_opendir (tt->path, &error); + g_assert_no_error (error); + g_assert_nonnull (dir); + + for (gsize i = 0; i < G_N_ELEMENTS (str_to_uint_table); i++) + { + g_autoptr(GError) err = NULL; + const char *txt = str_to_uint_table[i].str; + gboolean expect_error = str_to_uint_table[i].error; + guint val = str_to_uint_table[i].val; + guint v; + + if (g_test_verbose ()) + g_test_message ("bolt_read_uint: '%s'", txt); + + ok = bolt_write_file_at (dirfd (dir), "uint.txt", txt, -1, &err); + g_assert_true (ok); + + ok = bolt_read_uint_at (dirfd (dir), "uint.txt", &v, &err); + if (expect_error) + { + g_assert_nonnull (err); + g_assert_false (ok); + } + else + { + g_assert_no_error (err); + g_assert_cmpuint (val, ==, v); + g_assert_true (ok); + } + } +} + +static void test_io_file_write_all (TestIO *tt, gconstpointer user_data) { g_autoptr(GError) error = NULL; g_autofree char *path = NULL; g_autofree char *data = NULL; - static const char *ref = "Die Welt is alles was der Fall ist!"; + static const char *ref = "The world is everything that is the case."; gboolean ok; gsize len; @@ -968,6 +1233,67 @@ } static void +test_io_renameat (TestIO *tt, gconstpointer user_data) +{ + g_autoptr(GError) err = NULL; + g_autoptr(DIR) root = NULL; + g_autoptr(DIR) subdir = NULL; + struct stat st; + gboolean ok; + + root = bolt_opendir (tt->path, &err); + g_assert_no_error (err); + g_assert_nonnull (root); + + ok = bolt_write_file_at (dirfd (root), "a", "a", -1, &err); + g_assert_no_error (err); + g_assert_true (ok); + + ok = bolt_mkdirat (dirfd (root), "subdir", 0777, &err); + g_assert_no_error (err); + g_assert_true (ok); + + subdir = bolt_opendir_at (dirfd (root), "subdir", O_RDONLY, &err); + g_assert_no_error (err); + g_assert_nonnull (subdir); + + ok = bolt_renameat (dirfd (root), "a", + dirfd (subdir), "b", + &err); + + g_assert_no_error (err); + g_assert_true (ok); + + ok = bolt_fstatat (dirfd (subdir), "b", &st, 0, &err); + g_assert_no_error (err); + g_assert_true (ok); + + g_assert_true (S_ISREG (st.st_mode)); + + ok = bolt_renameat (dirfd (subdir), "b", + dirfd (subdir), "c", + &err); + + g_assert_no_error (err); + g_assert_true (ok); + + memset (&st, 0, sizeof (st)); + ok = bolt_fstatat (dirfd (subdir), "c", &st, 0, &err); + g_assert_no_error (err); + g_assert_true (ok); + + g_assert_true (S_ISREG (st.st_mode)); + + /* error reporting: file not found */ + ok = bolt_renameat (dirfd (subdir), "b", + dirfd (subdir), "c", + &err); + + g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); + g_assert_false (ok); +} + +static void test_io_copy_bytes (TestIO *tt, gconstpointer user_data) { g_autoptr(GError) error = NULL; @@ -981,6 +1307,11 @@ int from = -1; int to = -1; + /* bolt_copy_bytes uses copy_file_range(2) internally, which + * was added in linux 4.5. */ + skip_test_unless (bolt_check_kernel_version (4, 5) || g_test_thorough (), + "linux kernel < 4.5, copy_file_range syscall missing"); + chk = g_checksum_new (G_CHECKSUM_SHA256); source = g_build_filename (tt->path, "copy_bytes_source", NULL); @@ -1052,6 +1383,72 @@ } static void +test_io_dir_is_empty (TestIO *tt, gconstpointer user_data) +{ + g_autoptr(GError) err = NULL; + g_autoptr(DIR) root = NULL; + struct dirent *de = NULL; + gboolean empty; + gboolean ok; + long pos; + + root = bolt_opendir (tt->path, &err); + g_assert_no_error (err); + g_assert_nonnull (root); + + empty = FALSE; + ok = bolt_dir_is_empty (root, &empty, &err); + g_assert_no_error (err); + g_assert_true (ok); + g_assert_true (empty); + + ok = bolt_write_file_at (dirfd (root), "a", "a", -1, &err); + g_assert_no_error (err); + g_assert_true (ok); + + empty = TRUE; + ok = bolt_dir_is_empty (root, &empty, &err); + g_assert_no_error (err); + g_assert_true (ok); + g_assert_false (empty); + + /* check that we don't mess with the dir pointer offset, + * by creating a second file, iterating exactly once + * and then making sure we are at the same position + * after the call to bolt_dir_is_empty */ + + ok = bolt_write_file_at (dirfd (root), "b", "b", -1, &err); + g_assert_no_error (err); + g_assert_true (ok); + + empty = TRUE; + ok = bolt_dir_is_empty (root, &empty, &err); + g_assert_no_error (err); + g_assert_true (ok); + g_assert_false (empty); + + while ((de = readdir (root)) != NULL) + { + + if (!g_strcmp0 (de->d_name, ".") || + !g_strcmp0 (de->d_name, "..")) + continue; + + break; + } + + pos = telldir (root); + + empty = TRUE; + ok = bolt_dir_is_empty (root, &empty, &err); + g_assert_no_error (err); + g_assert_true (ok); + g_assert_false (empty); + + g_assert_cmpint (telldir (root), ==, pos); +} + +static void test_autoclose (TestIO *tt, gconstpointer user_data) { g_autoptr(GError) err = NULL; @@ -1127,7 +1524,7 @@ if (geteuid () == 0) /* if we run as root, maybe inside a container, we will - * be able o do that anyway so skip it in that case */ + * be able to do that anyway so skip it in that case */ return; /* check error checking */ @@ -1287,6 +1684,11 @@ size_t n; bolt_get_random_data (buf, sizeof (buf) - 1); + buf[0] = 'b'; /* make sure we never have an empty string */ + buf[1] = 'o'; + buf[2] = 'l'; + buf[3] = 'l'; + d1 = g_strdup (buf); d2 = g_strdup (buf); @@ -1317,43 +1719,19 @@ static void test_str_parse_int (TestRng *tt, gconstpointer user_data) { - struct - { - const char *str; - gint val; - gboolean error; - } table[] = { - {"0", 0, FALSE}, - {"1", 1, FALSE}, - {"-1", -1, FALSE}, -#if __SIZEOF_INT__ == 4 - {"2147483647", 2147483647, FALSE}, /* MAX_INT */ - {"-2147483648", -2147483648, FALSE}, /* MIN_INT */ - {"2147483648", 0, TRUE}, /* MAX_INT + 1 */ - {"-2147483649", 0, TRUE}, /* MIN_INT - 1 */ -#elif __SIZEOF_INT__ == 8 - {"9223372036854775807", 9223372036854775807, FALSE}, /* MAX_INT */ - {"-9223372036854775808", -9223372036854775808, FALSE}, /* MIN_INT */ - {"9223372036854775808", 0, TRUE}, /* MAX_INT + 1 */ - {"-9223372036854775809", 0, TRUE}, /* MIN_INT - 1 */ -#else - #warning __SIZEOF_INT__ not handled -#endif - {"notanint", 0, TRUE}, - {"9223372036854775808", 0, TRUE}, /* overflow */ - {"-9223372036854775809", 0, TRUE}, /* underflow */ - }; - - for (gsize i = 0; i < G_N_ELEMENTS (table); i++) + for (gsize i = 0; i < G_N_ELEMENTS (str_to_int_table); i++) { g_autoptr(GError) error = NULL; + const char *txt = str_to_int_table[i].str; + gboolean expect_error = str_to_int_table[i].error; + gint val = str_to_int_table[i].val; gboolean ok; gint v; errno = 0; - ok = bolt_str_parse_as_int (table[i].str, &v, &error); + ok = bolt_str_parse_as_int (txt, &v, &error); - if (table[i].error) + if (expect_error) { int err = errno; @@ -1363,7 +1741,43 @@ } else { - g_assert_cmpuint (table[i].val, ==, v); + g_assert_cmpint (val, ==, v); + g_assert_true (ok); + } + } +} + +static void +test_str_parse_uint (TestRng *tt, gconstpointer user_data) +{ + for (gsize i = 0; i < G_N_ELEMENTS (str_to_uint_table); i++) + { + g_autoptr(GError) error = NULL; + const char *txt = str_to_uint_table[i].str; + gboolean expect_error = str_to_uint_table[i].error; + guint val = str_to_uint_table[i].val; + gboolean ok; + guint v; + + errno = 0; + ok = bolt_str_parse_as_uint (txt, &v, &error); + + if (g_test_verbose ()) + g_test_message ("parsing '%s', expecting: %s", txt, + (expect_error ? "error" : "success")); + + if (expect_error) + { + int err = errno; + + if (g_test_verbose ()) + g_assert_nonnull (error); + g_assert_false (ok); + g_assert_cmpint (err, !=, 0); + } + else + { + g_assert_cmpuint (val, ==, v); g_assert_true (ok); } } @@ -1394,8 +1808,50 @@ ok = bolt_str_parse_as_uint64 (table[i].str, &v, &err); if (table[i].error) { + int e = errno; + + g_assert_nonnull (err); + g_assert_false (ok); + g_assert_cmpint (e, !=, 0); + } + else + { + g_assert_cmpuint (table[i].val, ==, v); + g_assert_true (ok); + } + } +} + +static void +test_str_parse_uint32 (TestRng *tt, gconstpointer user_data) +{ + struct + { + const char *str; + guint32 val; + gboolean error; + } table[] = { + {"0", 0, FALSE}, + {"1", 1, FALSE}, + {"0xffffffff", 0xffffffff, FALSE}, /* G_MAXUINT64 */ + {"notauint64", 0, TRUE}, + {"4294967296", 0, TRUE}, /* overflow (G_MAXUINT32 + 1) */ + }; + + for (gsize i = 0; i < G_N_ELEMENTS (table); i++) + { + g_autoptr(GError) err = NULL; + gboolean ok; + guint32 v; + + ok = bolt_str_parse_as_uint32 (table[i].str, &v, &err); + if (table[i].error) + { + int e = errno; + g_assert_nonnull (err); g_assert_false (ok); + g_assert_cmpint (e, !=, 0); } else { @@ -1467,6 +1923,25 @@ g_assert_cmpstr (target, ==, "Hallo Welt"); } +static void +test_strv_make_n (TestRng *tt, gconstpointer user_data) +{ + g_auto(GStrv) empty = NULL; + g_auto(GStrv) full = NULL; + const char *check[] = {"voll", "voll", NULL}; + + empty = bolt_strv_make_n (0, "nichts"); + g_assert_nonnull (empty); + g_assert_null (*empty); + + full = bolt_strv_make_n (2, "voll"); + g_assert_nonnull (full); + g_assert_nonnull (*full); + g_assert_cmpuint (bolt_gstrv_length0 (full), ==, 2); + bolt_assert_strv_equal (full, (GStrv) check, -1); +} + + #define MAKE_GSTRV(...) (GStrv) (const char *[]){ __VA_ARGS__} static void @@ -1486,6 +1961,7 @@ for (gsize i = 0; i < G_N_ELEMENTS (table); i++) { g_assert_cmpuint (bolt_strv_length (table[i].strv), ==, table[i].l); + g_assert_cmpuint (bolt_gstrv_length0 (table[i].strv), ==, table[i].l); if (table[i].l == 0) g_assert_true (bolt_strv_isempty (table[i].strv)); @@ -1500,8 +1976,8 @@ const GStrv strv = MAKE_GSTRV ("a", "b", "c", "d", NULL); - g_assert_false (bolt_strv_contains (NULL, "nonexistant")); - g_assert_false (bolt_strv_contains (strv, "nonexistant")); + g_assert_false (bolt_strv_contains (NULL, "nonexistent")); + g_assert_false (bolt_strv_contains (strv, "nonexistent")); for (char **iter = strv; *iter; iter++) { @@ -1733,14 +2209,66 @@ g_assert_true (target == &a[4]); } +#ifndef MAKE_GSTRV +#define MAKE_GSTRV(...) (GStrv) (const char *[]){ __VA_ARGS__} +#endif + static void -test_term_fancy (TestRng *tt, gconstpointer user_data) +test_uuidv_check (TestRng *tt, gconstpointer user_data) { - if (bolt_is_fancy_terminal () == 0) + g_autoptr(GError) err = NULL; + char *empty[] = {NULL}; + const char *empty_entries[] = {"884c6edd-7118-4b21-b186-b02d396ecca0", "", NULL}; + const char *valid[] = {"884c6edd-7118-4b21-b186-b02d396ecca0", NULL}; + const GStrv invalid[] = { + MAKE_GSTRV ("\n", NULL), + MAKE_GSTRV ("884c6eddx7118x4b21xb186-b02d396ecca0", NULL), + MAKE_GSTRV ("884c6edd-4b21-b186-b02d396ecca0", NULL), + MAKE_GSTRV ("884c6edd-7118-4b21-b186-b02d396ecca0", "a", NULL), + }; + gboolean ok; + + ok = bolt_uuidv_check (NULL, TRUE, &err); + g_assert_true (ok); + + ok = bolt_uuidv_check (empty, TRUE, &err); + g_assert_true (ok); + + ok = bolt_uuidv_check ((GStrv) empty_entries, TRUE, &err); + g_assert_true (ok); + + ok = bolt_uuidv_check (NULL, FALSE, &err); + g_assert_error (err, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); + g_assert_false (ok); + g_clear_error (&err); + + ok = bolt_uuidv_check (empty, FALSE, &err); + g_assert_error (err, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); + g_assert_false (ok); + g_clear_error (&err); + + ok = bolt_uuidv_check ((GStrv) empty_entries, FALSE, &err); + g_assert_error (err, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); + g_assert_false (ok); + g_clear_error (&err); + + ok = bolt_uuidv_check ((GStrv) valid, TRUE, &err); + g_assert_true (ok); + + for (gsize i = 0; i < G_N_ELEMENTS (invalid); i++) { - g_test_skip ("Terminal is not fancy"); - return; + ok = bolt_uuidv_check (invalid[i], FALSE, &err); + g_assert_error (err, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); + g_assert_false (ok); + g_clear_error (&err); } +} + +static void +test_term_fancy (TestRng *tt, gconstpointer user_data) +{ + skip_test_unless (bolt_is_fancy_terminal (), + "Terminal is not fancy"); g_assert_cmpstr (bolt_color (ANSI_NORMAL), !=, ""); g_assert_cmpstr (bolt_glyph (WARNING_SIGN), !=, ""); @@ -1749,11 +2277,8 @@ static void test_term_plain (TestRng *tt, gconstpointer user_data) { - if (bolt_is_fancy_terminal () != 0) - { - g_test_skip ("Terminal is too fancy"); - return; - } + skip_test_if (bolt_is_fancy_terminal (), + "Terminal is too fancy"); g_assert_cmpstr (bolt_color (ANSI_NORMAL), ==, ""); g_assert_cmpstr (bolt_glyph (WARNING_SIGN), !=, ""); @@ -1812,17 +2337,22 @@ g_assert_true (l[i].prev->next == &l[i]); } - c = 0; - bolt_nhlist_iter_init (&iter, l); - while ((k = bolt_nhlist_iter_next (&iter))) + /* start in the middle */ + for (guint i = 0; i < 10; i++) { - BoltList *p = bolt_nhlist_iter_node (&iter); - g_assert_true (k == n + (c % 10)); - g_assert_true (k == p); - c++; - } + c = 0; + bolt_nhlist_iter_init (&iter, &n[i]); + while ((k = bolt_nhlist_iter_next (&iter))) + { + BoltList *p = bolt_nhlist_iter_node (&iter); + g_assert_true (k == n + ((c + i) % 10)); + g_assert_true (k == p); + c++; + } - g_assert_cmpuint (c, ==, 10); + g_assert_cmpuint (c, ==, 10); + g_debug ("start[%u] %p: count: %u", i, &n[i], c); + } } static void @@ -1931,6 +2461,13 @@ test_io_errors, test_io_tear_down); + g_test_add ("/common/io/mkdirat", + TestIO, + NULL, + test_io_setup, + test_io_mkdirat, + test_io_tear_down); + g_test_add ("/common/io/verify", TestIO, NULL, @@ -1938,6 +2475,41 @@ test_io_verify, test_io_tear_down); + g_test_add ("/common/io/write_file_at", + TestIO, + NULL, + test_io_setup, + test_io_write_file_at, + test_io_tear_down); + + g_test_add ("/common/io/write_int_at", + TestIO, + NULL, + test_io_setup, + test_io_write_int_at, + test_io_tear_down); + + g_test_add ("/common/io/read_int_at", + TestIO, + NULL, + test_io_setup, + test_io_read_int_at, + test_io_tear_down); + + g_test_add ("/common/io/write_uint_at", + TestIO, + NULL, + test_io_setup, + test_io_write_uint_at, + test_io_tear_down); + + g_test_add ("/common/io/read_uint_at", + TestIO, + NULL, + test_io_setup, + test_io_read_uint_at, + test_io_tear_down); + g_test_add ("/common/io/file_write_all", TestIO, NULL, @@ -1945,6 +2517,13 @@ test_io_file_write_all, test_io_tear_down); + g_test_add ("/common/io/renameat", + TestIO, + NULL, + test_io_setup, + test_io_renameat, + test_io_tear_down); + g_test_add ("/common/io/copy_bytes", TestIO, NULL, @@ -1952,6 +2531,13 @@ test_io_copy_bytes, test_io_tear_down); + g_test_add ("/common/io/dir_is_empty", + TestIO, + NULL, + test_io_setup, + test_io_dir_is_empty, + test_io_tear_down); + g_test_add ("/common/io/autoclose", TestIO, NULL, @@ -1994,6 +2580,13 @@ test_str_parse_int, NULL); + g_test_add ("/common/str/parse/uint", + TestRng, + NULL, + NULL, + test_str_parse_uint, + NULL); + g_test_add ("/common/str/parse/uint64", TestRng, NULL, @@ -2001,6 +2594,13 @@ test_str_parse_uint64, NULL); + g_test_add ("/common/str/parse/uint32", + TestRng, + NULL, + NULL, + test_str_parse_uint32, + NULL); + g_test_add ("/common/str/parse/boolean", TestRng, NULL, @@ -2015,6 +2615,13 @@ test_str_set, NULL); + g_test_add ("/common/strv/make_n", + TestRng, + NULL, + NULL, + test_strv_make_n, + NULL); + g_test_add ("/common/strv/equal", TestRng, NULL, @@ -2029,6 +2636,8 @@ test_strv_length, NULL); + + g_test_add ("/common/strv/contains", TestRng, NULL, @@ -2057,6 +2666,13 @@ test_strv_rotate_left, NULL); + g_test_add ("/common/uuidv/check", + TestRng, + NULL, + NULL, + test_uuidv_check, + NULL); + g_test_add ("/common/term/fancy", TestRng, NULL, diff -Nru bolt-0.8/tests/test-device.c bolt-0.9.1/tests/test-device.c --- bolt-0.8/tests/test-device.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/tests/test-device.c 2020-12-01 11:26:01.000000000 +0000 @@ -44,15 +44,19 @@ g_autoptr(BoltKey) key = NULL; g_autoptr(GError) err = NULL; char uid[] = "fbc83890-e9bf-45e5-a777-b3728490989c"; + BoltDeviceType devtype = BOLT_DEVICE_PERIPHERAL; BoltKeyState keystate; BoltSecurity sl; + guint gen; gboolean ok; dev = g_object_new (BOLT_TYPE_DEVICE, "uid", uid, "name", "Laptop", "vendor", "GNOME.org", + "type", BOLT_DEVICE_HOST, "status", BOLT_STATUS_DISCONNECTED, + "generation", 3, NULL); g_assert_nonnull (dev); @@ -61,12 +65,19 @@ "store", &store, "domain", &dom, "security", &sl, + "generation", &gen, + "type", &devtype, NULL); g_assert_null (store); g_assert_null (dom); + g_assert_true (dom == bolt_device_get_domain (dev)); g_assert_cmpint (sl, ==, BOLT_SECURITY_UNKNOWN); + g_assert_cmpint (gen, ==, 3); + g_assert_cmpint (gen, ==, bolt_device_get_generation (dev)); + g_assert_cmpint (devtype, ==, BOLT_DEVICE_HOST); + g_assert_true (bolt_device_is_host (dev)); keystate = bolt_device_get_keystate (dev); g_assert_cmpint (keystate, ==, BOLT_KEY_MISSING); diff -Nru bolt-0.8/tests/test-glue.c bolt-0.9.1/tests/test-glue.c --- bolt-0.8/tests/test-glue.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/tests/test-glue.c 2020-12-01 11:26:01.000000000 +0000 @@ -22,6 +22,8 @@ #include "bolt-glue.h" +#include "bolt-error.h" +#include "bolt-wire.h" #include "test-enums.h" #include "bolt-test-resources.h" @@ -460,6 +462,7 @@ test_wire_conv_enum (TestGlue *tt, gconstpointer data) { g_autoptr(BoltWireConv) conv = NULL; + g_autoptr(GParamSpec) spec = NULL; g_autoptr(GError) err = NULL; g_autoptr(GVariant) bogus = NULL; g_autoptr(GVariant) var = NULL; @@ -467,7 +470,6 @@ const GVariantType *wire_type; const GParamSpec *prop_spec; gboolean ok; - GParamSpec *spec; spec = g_param_spec_enum ("test", "Test", "Test Enumeration", @@ -546,6 +548,7 @@ test_wire_conv_flags (TestGlue *tt, gconstpointer data) { g_autoptr(BoltWireConv) conv = NULL; + g_autoptr(GParamSpec) spec = NULL; g_autoptr(GError) err = NULL; g_autoptr(GVariant) bogus = NULL; g_autoptr(GVariant) var = NULL; @@ -553,7 +556,6 @@ const GVariantType *wire_type; const GParamSpec *prop_spec; gboolean ok; - GParamSpec *spec; spec = g_param_spec_flags ("test", "Test", "Test Flags", @@ -630,6 +632,7 @@ test_wire_conv_object (TestGlue *tt, gconstpointer data) { g_autoptr(BoltWireConv) conv = NULL; + g_autoptr(GParamSpec) spec = NULL; g_autoptr(GError) err = NULL; g_autoptr(GVariant) bogus = NULL; g_autoptr(GVariant) var = NULL; @@ -637,7 +640,6 @@ const GVariantType *wire_type; const GParamSpec *prop_spec; gboolean ok; - GParamSpec *spec; spec = g_param_spec_object ("obj", "Obj", "Object Test", @@ -672,6 +674,7 @@ ==, ""); + g_clear_pointer (&var, g_variant_unref); /* to the wire, value holding a valid object */ g_value_reset (&val); g_value_set_object (&val, tt->bg); @@ -698,11 +701,11 @@ g_autoptr(BoltWireConv) conv = NULL; g_autoptr(GError) err = NULL; g_autoptr(GVariant) var = NULL; + g_autoptr(GParamSpec) spec = NULL; g_auto(GValue) val = G_VALUE_INIT; const GVariantType *wire_type; const GParamSpec *prop_spec; gboolean ok; - GParamSpec *spec; spec = g_param_spec_uint64 ("uint", "Uint", "Unsigned Integer", @@ -763,6 +766,61 @@ 42U); } +static void +test_wire_conv_custom (TestGlue *tt, gconstpointer data) +{ + g_autoptr(BoltWireConv) conv = NULL; + g_autoptr(GParamSpec) spec = NULL; + g_autoptr(GError) err = NULL; + g_autoptr(GVariant) var = NULL; + g_auto(GValue) val = G_VALUE_INIT; + gboolean ok; + BoltLinkSpeed *check; + BoltLinkSpeed attr = + {.rx.speed = 10, + .rx.lanes = 1, + .tx.speed = 20, + .tx.lanes = 2}; + + spec = g_param_spec_boxed ("link-speed", "LinkSpeed", + "Link Speed Info", + BOLT_TYPE_LINK_SPEED, + G_PARAM_STATIC_STRINGS); + + conv = bolt_wire_conv_custom (G_VARIANT_TYPE ("a{su}"), spec, + "link speed to dict", + bolt_link_speed_to_wire, + bolt_link_speed_from_wire); + + g_assert_nonnull (conv); + + g_assert_false (bolt_wire_conv_is_native (conv)); + g_assert_nonnull (bolt_wire_conv_describe (conv)); + + g_value_init (&val, BOLT_TYPE_LINK_SPEED); + g_value_set_boxed (&val, &attr); + + /* to the wire */ + var = bolt_wire_conv_to_wire (conv, &val, &err); + g_assert_no_error (err); + g_assert_nonnull (var); + + /* from the wire, value is unset */ + g_value_unset (&val); + g_assert_true (G_VALUE_TYPE (&val) == 0); + + ok = bolt_wire_conv_from_wire (conv, var, &val, &err); + g_assert_no_error (err); + g_assert_true (ok); + + check = g_value_get_boxed (&val); + + g_assert_cmpuint (attr.rx.speed, ==, check->rx.speed); + g_assert_cmpuint (attr.rx.lanes, ==, check->rx.lanes); + g_assert_cmpuint (attr.tx.speed, ==, check->tx.speed); + g_assert_cmpuint (attr.tx.lanes, ==, check->tx.lanes); +} + int main (int argc, char **argv) { @@ -861,6 +919,13 @@ test_wire_conv_simple, NULL); + g_test_add ("/common/wire_conv/custom", + TestGlue, + NULL, + NULL, + test_wire_conv_custom, + NULL); + return g_test_run (); } diff -Nru bolt-0.8/tests/test-guard.c bolt-0.9.1/tests/test-guard.c --- bolt-0.8/tests/test-guard.c 1970-01-01 00:00:00.000000000 +0000 +++ bolt-0.9.1/tests/test-guard.c 2020-12-01 11:26:01.000000000 +0000 @@ -0,0 +1,362 @@ +/* + * Copyright © 2020 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Christian J. Kellner + */ + +#include "config.h" + +#include "bolt-guard.h" + +#include "bolt-dbus.h" +#include "bolt-fs.h" +#include "bolt-test.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +typedef struct +{ + char *rundir; +} TestGuard; + +static void +test_guard_setup (TestGuard *tt, gconstpointer data) +{ + g_autoptr(GError) err = NULL; + + tt->rundir = g_strdup (g_getenv ("BOLT_RUNDIR")); + if (tt->rundir == NULL) + tt->rundir = g_dir_make_tmp ("bolt.guard.XXXXXX", &err); + + g_assert_no_error (err); + g_assert_nonnull (tt->rundir); + + g_debug ("rundir at '%s'", tt->rundir); +} + +static void +test_guard_tear_down (TestGuard *tt, gconstpointer user) +{ + g_autoptr(GError) err = NULL; + gboolean ok; + + ok = bolt_fs_cleanup_dir (tt->rundir, &err); + g_assert_no_error (err); + g_assert_true (ok); + + g_clear_pointer (&tt->rundir, g_free); +} + +static void +on_release_true (BoltGuard *guard, gboolean *released) +{ + g_assert_cmpstr (bolt_guard_get_id (guard), ==, "guard-1"); + g_assert_cmpstr (bolt_guard_get_who (guard), ==, "Richard III"); + g_assert_cmpuint (bolt_guard_get_pid (guard), ==, getpid ()); + + *released = TRUE; +} + +static void +test_guard_basic (TestGuard *tt, gconstpointer user) +{ + g_autoptr(BoltGuard) guard = NULL; + g_autoptr(GError) err = NULL; + g_autoptr(GFile) f = NULL; + const char *id = "guard-1"; + const char *who = "Richard III"; + gboolean released = FALSE; + gboolean ok; + pid_t pid = getpid (); + + guard = g_object_new (BOLT_TYPE_GUARD, + "id", id, + "who", who, + "pid", pid, + NULL); + + g_assert_nonnull (guard); + + g_assert_cmpstr (id, ==, bolt_guard_get_id (guard)); + g_assert_cmpstr (who, ==, bolt_guard_get_who (guard)); + g_assert_cmpuint ((guint) pid, ==, bolt_guard_get_pid (guard)); + g_assert_null (bolt_guard_get_path (guard)); + g_assert_null (bolt_guard_get_fifo (guard)); + + f = g_file_new_for_path (tt->rundir); + + ok = bolt_guard_save (guard, f, &err); + g_assert_no_error (err); + g_assert_true (ok); + + g_assert_nonnull (bolt_guard_get_path (guard)); + + g_signal_connect (guard, "released", + (GCallback) on_release_true, + &released); + + g_assert_false (released); + /* release the guard */ + g_clear_object (&guard); + g_assert_true (released); +} + +static gboolean +on_cb_close_fd (gpointer user_data) +{ + int *fd = user_data; + int r; + + g_debug ("closing fd"); + r = close (*fd); + + g_assert_cmpint (r, >, -1); + *fd = -1; + return FALSE; +} + +static void +quit_mainloop (BoltGuard *guard, GMainLoop *loop) +{ + g_debug ("Stopping loop"); + g_main_loop_quit (loop); +} + +static void +test_guard_recover_active (TestGuard *tt, gconstpointer user) +{ + g_autoptr(GPtrArray) guards = NULL; + g_autoptr(BoltGuard) guard = NULL; + g_autoptr(GMainLoop) loop = NULL; + g_autoptr(GError) err = NULL; + g_autoptr(GFile) f = NULL; + g_autofree char *fifo = NULL; + const char *id = "guard-1"; + const char *who = "Richard III"; + BoltGuard *g = NULL; + gboolean released = FALSE; + gboolean ok; + gpointer data; + int fd; + + guard = g_object_new (BOLT_TYPE_GUARD, + "id", id, + "who", who, + "pid", getpid (), + NULL); + + g_assert_nonnull (guard); + + f = g_file_new_for_path (tt->rundir); + + ok = bolt_guard_save (guard, f, &err); + g_assert_no_error (err); + g_assert_true (ok); + + g_assert_nonnull (bolt_guard_get_path (guard)); + + g_signal_connect (guard, "released", + (GCallback) on_release_true, + &released); + + g_assert_false (released); + + fd = bolt_guard_monitor (guard, &err); + g_assert_no_error (err); + g_assert_cmpint (fd, >, -1); + g_object_unref (guard); /* monitor adds a references */ + + /* memorize the fifo so we can check it exists + * after we release the guard */ + fifo = g_strdup (bolt_guard_get_fifo (guard)); + g_assert_nonnull (fifo); + ok = g_file_test (fifo, G_FILE_TEST_EXISTS); + g_assert_true (ok); + + /* release the kraken */ + g_clear_object (&guard); + g_assert_true (released); + + ok = g_file_test (fifo, G_FILE_TEST_EXISTS); + g_assert_true (ok); + + /* recover the guard */ + guards = bolt_guard_recover (tt->rundir, &err); + g_assert_no_error (err); + g_assert_cmpuint (guards->len, ==, 1); + g = g_ptr_array_index (guards, 0); + + g_assert_cmpstr (bolt_guard_get_id (g), ==, id); + g_assert_cmpstr (bolt_guard_get_who (g), ==, who); + g_assert_cmpuint (bolt_guard_get_pid (g), ==, getpid ()); + + released = FALSE; + g_signal_connect (g, "released", + (GCallback) on_release_true, + &released); + + loop = g_main_loop_new (NULL, FALSE); + + g_signal_connect (g, "released", + (GCallback) quit_mainloop, + loop); + + /* schedule a closing of the fifo */ + g_idle_add (on_cb_close_fd, (gpointer) & fd); + + /* fail if we don't have anything after n seconds */ + bolt_test_run_main_loop (loop, 5, TRUE, NULL); + + /* free the array without calling its destroy function */ + data = g_ptr_array_free (guards, FALSE); + g_free (data); + guards = NULL; +} + +static void +test_guard_recover_dead (TestGuard *tt, gconstpointer user) +{ + g_autoptr(GPtrArray) guards = NULL; + g_autoptr(BoltGuard) guard = NULL; + g_autoptr(GMainLoop) loop = NULL; + g_autoptr(GError) err = NULL; + g_autoptr(GFile) f = NULL; + g_autofree char *fifo = NULL; + const char *id = "guard-1"; + const char *who = "Richard III"; + BoltGuard *g = NULL; + gboolean released = FALSE; + gboolean ok; + gpointer data; + int fd; + + guard = g_object_new (BOLT_TYPE_GUARD, + "id", id, + "who", who, + "pid", getpid (), + NULL); + + g_assert_nonnull (guard); + + f = g_file_new_for_path (tt->rundir); + + ok = bolt_guard_save (guard, f, &err); + g_assert_no_error (err); + g_assert_true (ok); + + g_assert_nonnull (bolt_guard_get_path (guard)); + + g_signal_connect (guard, "released", + (GCallback) on_release_true, + &released); + + g_assert_false (released); + + fd = bolt_guard_monitor (guard, &err); + g_assert_no_error (err); + assert (fd > -1); /* plain assert for coverity */ + g_object_unref (guard); /* monitor adds a references */ + + /* memorize the fifo so we can check it exists + * after we release the guard */ + fifo = g_strdup (bolt_guard_get_fifo (guard)); + g_assert_nonnull (fifo); + ok = g_file_test (fifo, G_FILE_TEST_EXISTS); + g_assert_true (ok); + + /* release the kraken */ + g_clear_object (&guard); + g_assert_true (released); + + ok = g_file_test (fifo, G_FILE_TEST_EXISTS); + g_assert_true (ok); + + /* simulate that the client meanwhile closed the FIFO */ + (void) close (fd); + + /* recover the guard */ + guards = bolt_guard_recover (tt->rundir, &err); + g_assert_no_error (err); + g_assert_cmpuint (guards->len, ==, 1); + g = g_ptr_array_index (guards, 0); + + g_assert_cmpstr (bolt_guard_get_id (g), ==, id); + g_assert_cmpstr (bolt_guard_get_who (g), ==, who); + g_assert_cmpuint (bolt_guard_get_pid (g), ==, getpid ()); + + released = FALSE; + g_signal_connect (g, "released", + (GCallback) on_release_true, + &released); + + loop = g_main_loop_new (NULL, FALSE); + + g_signal_connect (g, "released", + (GCallback) quit_mainloop, + loop); + + /* fail if we don't have anything after n seconds */ + bolt_test_run_main_loop (loop, 5, TRUE, NULL); + + /* free the array without calling its destroy function */ + data = g_ptr_array_free (guards, FALSE); + g_free (data); + guards = NULL; +} + +int +main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + + g_test_init (&argc, &argv, NULL); + + bolt_dbus_ensure_resources (); + + g_test_add ("/guard/basic", + TestGuard, + NULL, + test_guard_setup, + test_guard_basic, + test_guard_tear_down); + + g_test_add ("/guard/recover/active", + TestGuard, + NULL, + test_guard_setup, + test_guard_recover_active, + test_guard_tear_down); + + g_test_add ("/guard/recover/dead", + TestGuard, + NULL, + test_guard_setup, + test_guard_recover_dead, + test_guard_tear_down); + + return g_test_run (); +} diff -Nru bolt-0.8/tests/test-integration bolt-0.9.1/tests/test-integration --- bolt-0.8/tests/test-integration 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/tests/test-integration 2020-12-01 11:26:01.000000000 +0000 @@ -19,13 +19,17 @@ # License along with this library. If not, see . # Authors: # Christian J. Kellner +# +# pylint: disable=too-many-lines, too-many-statements, too-many-public-methods +# pylint: disable=too-many-instance-attributes -from __future__ import print_function import binascii +import errno import gc import os import shutil +import socket import sys import subprocess import unittest @@ -34,6 +38,7 @@ import time from collections import namedtuple +from contextlib import contextmanager from functools import reduce from itertools import chain @@ -49,15 +54,10 @@ import configparser except ImportError as e: - sys.stderr.write('Skipping integration test due to missing depdendencies: %s\n' % str(e)) + sys.stderr.write('Skipping integration test due to missing dependencies: %s\n' % str(e)) sys.exit(1) -try: - from subprocess import DEVNULL -except ImportError: - DEVNULL = open(os.devnull, 'wb') - DBUS_NAME = 'org.freedesktop.bolt' DBUS_PATH = '/org/freedesktop/bolt' DBUS_IFACE_PREFIX = 'org.freedesktop.bolt1.' @@ -86,31 +86,40 @@ return lut[topic] -class Signal(object): +def can_override_dac(): + """Check if we have CAP_DAC_OVERRIDE""" + with tempfile.TemporaryDirectory() as tmp: + path = os.path.join(tmp, "access-check") + + with open(path, "w") as f: + f.write("") + + mode = os.stat(path).st_mode + os.chmod(path, 0) + can_write = os.access(path, os.W_OK) + os.chmod(path, mode & 0o7777) + + return can_write + + +class Signal: def __init__(self, name): self.name = name self.callbacks = set() - self.notify = None self._bridge = None def connect(self, callback): self.callbacks.add(callback) - if self.notify is not None: - self.notify(self, 'connect', len(self.callbacks)) if len(self.callbacks) == 1: self._bridge_build() def disconnect(self, callback): self.callbacks.remove(callback) - if self.notify is not None: - self.notify(self, 'disconnect', len(self.callbacks)) - if len(self.callbacks) == 0: + if not self.callbacks: self._bridge_destory() def disconnect_all(self): self.callbacks = set() - if self.notify is not None: - self.notify(self, 'disconnect', 0) self._bridge_destory() def emit(self, *args, **kwargs): @@ -146,7 +155,7 @@ if 'filter' in self._bridge: res, args, kwargs = self._bridge['filter'](args, kwargs) if not res: - return + return False return self.emit(*args, **kwargs) def __call__(self, *args, **kwargs): @@ -167,7 +176,7 @@ def install(l): if l is None: - return + return l if l in methods: print('WARNING: signal "%s" will overwrite method' % l, file=sys.stderr) @@ -187,7 +196,7 @@ def getter(self): return get_signal(self) - def setter(self, value): + def setter(self, _value): return get_signal(self) p = property(getter, setter) @@ -200,14 +209,12 @@ return klass -@Signal.enable -class Recorder(object): +class Recorder: Event = namedtuple('Event', ['what', 'name', 'details', 'time']) - signals = ['event'] - def __init__(self, target): self.recording = True + self.event = Signal('event') self.events = [] self.target = target self.target.g_properties_changed += self._on_props_changed @@ -215,7 +222,7 @@ def close(self): if not self.recording: - return + return None self.target.g_properties_changed -= self._on_props_changed self.target.g_signal -= self._on_signal self.recording = False @@ -230,25 +237,25 @@ def _on_props_changed(self, props): now = time.time() for k, v in props.items(): - e = self.Event('property', k, v, now) - self._add_event(e) + event = self.Event('property', k, v, now) + self._add_event(event) - def _on_signal(self, proxy, sender, signal, params): + def _on_signal(self, _proxy, _sender, signal, params): now = time.time() - e = self.Event('signal', signal, None, now) - self._add_event(e) + event = self.Event('signal', signal, params, now) + self._add_event(event) def _add_event(self, event): self.events.append(event) self.event.emit(event) @staticmethod - def event_match(e, target): - if e.what != target.what or e.name != target.name: + def event_match(event, target): + if event.what != target.what or event.name != target.name: return False if target.details is None: return True - return e.details == target.details + return event.details == target.details @staticmethod def events_list_has_event(events, target): @@ -259,28 +266,33 @@ target = Recorder.Event(what, name, details, None) return list(filter(lambda x: Recorder.event_match(x, target), events)) - def events_filter(self, e): - return self.events_list_has_event(self.events, e) + def events_filter(self, event): + return self.events_list_has_event(self.events, event) - def have_event(self, e): - return len(self.events_filter(e)) > 0 + def have_event(self, event): + return len(self.events_filter(event)) > 0 def wait_for_events(self, lst, timeout=None): loop = GLib.MainLoop() def got_event(event): - for idx, e in enumerate(lst): - if self.event_match(event, e): + for idx, current in enumerate(lst): + if self.event_match(event, current): del lst[idx] break - if len(lst) == 0: + if not lst: loop.quit() def got_timeout(): - print('WARNING: timeout reached! Waiting list: ', - str(lst), file=sys.stderr) + print('WARNING: timeout reached! Want: %s, Have: %s' % + (str(lst), str(self.events)), file=sys.stderr) loop.quit() + # check if we did receive the events already + lst = list(filter(lambda x: not self.have_event(x), lst)) + if not lst: + return True + self.event += got_event timeout = timeout or get_timeout() GLib.timeout_add(timeout*1000, got_timeout) @@ -289,14 +301,17 @@ return len(lst) == 0 def wait_for_event(self, what, name, details=None): - t = self.Event(what, name, details, None) - if self.have_event(t): - return True - return self.wait_for_events([t]) + event = self.Event(what, name, details, None) + return self.wait_for_events([event]) + + def wait_for_props(self, **kwargs): + events = [self.Event('property', name, details, None) for name, details in kwargs.items()] + res = self.wait_for_events(events) + assert res @Signal.enable -class ProxyWrapper(object): +class ProxyWrapper: signals = ['g_properties_changed', 'g_signal'] def __init__(self, bus, iname, path): @@ -308,7 +323,7 @@ iname, None) - def props_changed(args, kwargs): + def props_changed(args, _kwargs): return True, [args[1].unpack()], {} self.g_properties_changed.bridge(self._proxy, @@ -329,8 +344,7 @@ value = self._proxy.get_cached_property(name) if value is not None: return value.unpack() - else: - return value + return value return getattr(self._proxy, name) @@ -360,11 +374,25 @@ NOKEY = 1 << 2 BOOT = 1 << 3 + HOST = 'host' + PERIPHERAL = 'peripheral' + def __init__(self, bus, path): super(BoltDevice, self).__init__(bus, DBUS_IFACE_DEVICE, path) + def _set_property(self, name, value): + if isinstance(value, str): + value = GLib.Variant("s", value) + target = GLib.Variant("(ssv)", (DBUS_IFACE_DEVICE, name, value)) + res = self._proxy.call_sync('org.freedesktop.DBus.Properties.Set', + target, + 0, + -1, + None) + return res is not None + @property def is_connected(self): return self.status > self.DISCONNECTED @@ -404,6 +432,10 @@ return reduce(lambda r, x: r | mapping.get(x, 0), keys, 0) @property + def device_type(self): + return getattr(self, 'type') + + @property def key(self): res = getattr(self, 'Key') mapping = {'missing': self.KEY_MISSING, @@ -415,24 +447,34 @@ @property def label(self): res = getattr(self, 'Label') - if res is not None and len(res) < 1: + if not res: return None return res @label.setter def label(self, value): - if isinstance(value, str): - value = GLib.Variant("s", value) + return self._set_property('Label', value) - res = self._proxy.call_sync('org.freedesktop.DBus.Properties.Set', - GLib.Variant("(ssv)", - (DBUS_IFACE_DEVICE, - "Label", - value)), - 0, - -1, - None) - return res is not None + @property + def policy(self): + res = getattr(self, 'Policy') + return res if res else None + + @policy.setter + def policy(self, value): + return self._set_property('Policy', value) + + @property + def linkspeed(self): + res = getattr(self, 'LinkSpeed') + if not res: + return None + return res + + @staticmethod + def gen_object_path(object_id): + base = os.path.join(DBUS_PATH, "devices") + return BoltClient.gen_object_path(base, object_id) class BoltDomain(ProxyWrapper): @@ -452,6 +494,26 @@ DBUS_IFACE_DOMAIN, path) + @property + def bootacl(self): + res = getattr(self, 'BootACL') + if not res: + return None + return res + + @bootacl.setter + def bootacl(self, value): + value = GLib.Variant("as", value) + + self._proxy.call_sync('org.freedesktop.DBus.Properties.Set', + GLib.Variant("(ssv)", + (DBUS_IFACE_DOMAIN, + "BootACL", + value)), + 0, + -1, + None) + @Signal.enable class BoltClient(ProxyWrapper): @@ -468,12 +530,12 @@ DBUS_PATH) self._proxy.connect('g-signal', self._on_dbus_signal) - def _on_dbus_signal(self, proxy, sender, signal, params): + def _on_dbus_signal(self, _proxy, _sender, signal, params): bus = self._proxy.get_connection() if signal == 'DeviceAdded': self.device_added.emit(BoltDevice(bus, params[0])) return True - elif signal == 'DeviceRemoved': + if signal == 'DeviceRemoved': self.device_removed.emit(params[0]) return True return False @@ -481,7 +543,7 @@ @property def auth_mode(self): res = getattr(self, 'AuthMode') - if res is not None and len(res) < 1: + if not res: return None return res @@ -527,6 +589,10 @@ bus = self._proxy.get_connection() return BoltDevice(bus, object_path) + def list_peripherals(self): + devs = self.list_devices() + return list(filter(lambda d: d.device_type == BoltDevice.PERIPHERAL, devs)) + def enroll(self, uid, policy=POLICY_DEFAULT, flags=""): object_path = self.EnrollDevice("(sss)", uid, policy, flags) if object_path is None: @@ -538,10 +604,23 @@ self.ForgetDevice("(s)", uid) return True + @staticmethod + def gen_object_path(base, object_id): + oid = None + if object_id: + oid = object_id.replace('-', '_') + if base and oid: + return os.path.join('/', base, oid) + if base: + return os.path.join('/', base) + if oid: + return os.path.join('/', oid) + return '/' + # Mock Device Tree @Signal.enable -class Device(object): +class Device: subsystem = "unknown" udev_attrs = [] udev_props = [] @@ -581,6 +660,7 @@ found = c.first(predicate) if found: return found + return None @property def parent(self): @@ -619,14 +699,30 @@ c.disconnect(bed) bed.uevent(self.syspath, "remove") bed.remove_device(self.syspath) - self.authorized = 0 - if hasattr(self, 'key') and self.key is not None: - self.key = "\n" self.root.device_disconnected(self) self.syspath = None self.testbed = None +class TbNativeHostInterface(Device): + subsystem = "pci" + udev_attrs = ['class', + 'device', + 'vendor'] + + udev_props = ['DRIVER'] + + driver = "thunderbolt" + + def __init__(self, domain, pci_id=0x15d2): + assert domain + name = "pci0000:00:0d.%d" % domain.index + super().__init__(name, [domain]) + setattr(self, 'class', '0x088000') + self.vendor = '0x8086' + self.device = '0x%04x' % pci_id + + class TbDevice(Device): subsystem = "thunderbolt" devtype = "thunderbolt_device" @@ -634,14 +730,19 @@ udev_attrs = ['authorized', 'device', 'device_name', + 'generation', 'key', 'unique_id', 'vendor', - 'vendor_name'] + 'vendor_name', + 'rx_lanes', + 'rx_speed', + 'tx_lanes', + 'tx_speed'] udev_props = ['DEVTYPE'] - def __init__(self, name, authorized=0, vendor=None, uid=None, children=None, key='\n'): + def __init__(self, name, authorized=0, vendor=None, uid=None, children=None, key='\n', gen=None): super(TbDevice, self).__init__(name, children or []) self.unique_id = uid or str(uuid.uuid4()) self.device_name = 'Thunderbolt ' + name @@ -650,8 +751,20 @@ self.vendor = self._make_id(self.vendor_name) self.authorized = authorized self.key = key + self.generation = gen + self.rx_lanes = None + self.rx_speed = None + self.tx_lanes = None + self.tx_speed = None + + def disconnect(self, bed): + super().disconnect(bed) + self.authorized = 0 + if self.key is not None: + self.key = "\n" - def _make_id(self, name): + @staticmethod + def _make_id(name): return '0x%X' % binascii.crc32(name.encode('utf-8')) @property @@ -670,10 +783,11 @@ def bolt_status(self): if self.syspath is None: return BoltDevice.DISCONNECTED - elif self.authorized == 0: + if self.authorized == 0: return BoltDevice.CONNECTED - elif self.authorized in [1, 2]: + if self.authorized in [1, 2]: return BoltDevice.AUTHORIZED + return BoltDevice.UNKNOWN @property def bolt_authflags(self): @@ -701,6 +815,28 @@ def is_unauthorized(d): return isinstance(d, TbDevice) and d.authorized == 0 + @property + def bus_path(self): + return BoltDevice.gen_object_path(self.unique_id) + + @property + def linkspeed(self): + return self.rx_lanes, self.rx_speed, self.tx_lanes, self.tx_speed + + @linkspeed.setter + def linkspeed(self, ls): + self.rx_lanes = str(ls["rx.lanes"]) + self.rx_speed = "%u.0 Gb/s" % ls["rx.speed"] + self.tx_lanes = str(ls["tx.lanes"]) + self.tx_speed = "%u.0 Gb/s" % ls["tx.speed"] + + if self.syspath: + self.testbed.set_attribute(self.syspath, "rx_lanes", self.rx_lanes) + self.testbed.set_attribute(self.syspath, "rx_speed", self.rx_speed) + self.testbed.set_attribute(self.syspath, "tx_lanes", self.tx_lanes) + self.testbed.set_attribute(self.syspath, "tx_speed", self.tx_speed) + self.testbed.uevent(self.syspath, 'change') + def reload_auth(self): authorized = self.authorized key = self.key @@ -725,10 +861,10 @@ class TbHost(TbDevice): - def __init__(self, children): - super(TbHost, self).__init__('Laptop', + def __init__(self, children, name='Laptop', gen=None): + super(TbHost, self).__init__(name, authorized=1, - uid='3b7d4bad-4fdf-44ff-8730-ffffdeadbabe', + gen=gen, children=children) def connect(self, bed): @@ -753,16 +889,34 @@ assert host assert isinstance(host, TbHost) name = 'domain%d' % index + if host.unique_id is None: + host.unique_id = '3b7d4bad-4fdf-44ff-8730-ffffdeadbab%d' % index super(TbDomain, self).__init__(name, children=[host]) self.security = security self.boot_acl = ','*(acl-1) if isinstance(acl, int) else acl self.iommu_dma_protection = iommu + self.index = index + + self.nhi = TbNativeHostInterface(self) + + def connect(self, bed): + self.nhi.connect(bed) + super().connect(bed) + + def disconnect(self, bed): + if self.syspath: # prevent recursion via nhi.disconnect() + super().disconnect(bed) + self.nhi.disconnect(bed) @property def unique_id(self): - if len(self.children) < 1: + return self.host and self.host.unique_id + + @property + def host(self): + if not self.children: return None - return self.children[0].unique_id + return self.children[0] @property def devices(self): @@ -793,95 +947,7 @@ return self.first(finder) -class TreeChecker(object): - - def __init__(self, client, tree): - self.client = client - self.tree = tree - self.remote_devices = {} - devices = client.list_devices() - [self._register_device(d) for d in devices] - client.device_added += self._on_device_added - client.device_removed += self._on_device_removed - tree.device_connected += self._on_udev_connected - tree.device_disconnected += self._on_udev_disconnected - self.actions = {} - self.loop = None - - # signal handler for local (udev) devices - def _on_udev_connected(self, dev): - if not isinstance(dev, TbDevice): - return - - uid = dev.unique_id - self.actions[uid] = 'connected' - - def _on_udev_disconnected(self, dev): - if not isinstance(dev, TbDevice): - return - uid = dev.unique_id - if uid in self.actions: - del self.actions[uid] - else: - self.actions[uid] = 'disconnected' - - # signal handler for remote devices - def _on_device_added(self, dev): - self._register_device(dev) - self._check_action(dev.uid, 'connected') - - def _on_device_removed(self, object_path): - dev = self.remote_devices.get(object_path, None) - if dev is None: - return - self._check_action(dev.uid, 'disconnected') - self._unregister_device(dev) - - # book keeping of remote devices - def _register_device(self, dev): - self.remote_devices[dev.object_path] = dev - return dev - - def _unregister_device(self, dev): - del self.remote_devices[dev.object_path] - - def _check_action(self, uid, action): - if self.actions.get(uid, None) != action: - return False - del self.actions[uid] - self._check_sync() - return True - - def _check_sync(self): - keep_going = len(self.actions) > 0 - if not keep_going: - self._stop_loop() - return keep_going - - def _stop_loop(self): - if self.loop is None: - return - self.loop.quit() - self.loop = None - - def _on_timeout(self): - print('WARNING, timeout reached', file=sys.stderr) - self._stop_loop() - - def sync(self, timeout=None): - timeout = timeout or get_timeout() - GLib.timeout_add(timeout*1000, self._on_timeout) - self.loop = GLib.MainLoop() - self.loop.run() - - def close(self): - self.client.device_added.disconnect_all() - self.client.device_removed.disconnect_all() - self.tree.device_connected.disconnect_all() - self.tree.device_disconnected.disconnect_all() - - -class Parameterize(object): +class Parameterize: # class is used for namespacing @staticmethod @@ -910,16 +976,79 @@ return decorator +# Systemd integration tests +class SdNotify: + def __init__(self, tmpdir): + assert(tmpdir is not None) + soflags = socket.SOCK_DGRAM | socket.SOCK_CLOEXEC | socket.SOCK_NONBLOCK + self.sock = socket.socket(socket.AF_UNIX, soflags) + self.path = os.path.join(tmpdir, 'bolt_notify_socket') + self.sock.bind(self.path) + self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_PASSCRED, 1) + self.msgs = [] + + def read_msg(self): + rflags = socket.MSG_DONTWAIT | socket.MSG_CMSG_CLOEXEC | socket.MSG_TRUNC + try: + msg, _ac, _flags, _addr = self.sock.recvmsg(4096, 0, rflags) + except OSError as e: + if e.errno != errno.EINTR and e.errno != errno.EAGAIN: + raise e + msg = None + + if msg is None: + return None + msg = msg.decode('utf-8') + self.msgs.append(msg) + return msg + + def fetch_msgs(self): + count = 0 + while self.read_msg(): + count += 1 + return count + + def wait_for(self, event, timeout=None): + timeout = timeout or get_timeout() + timeout_count = timeout * 10 + timeout_sleep = 0.1 + + while timeout_count > 0: + + count = self.fetch_msgs() + if count: + matches = [msg for msg in self.msgs if msg.startswith(event)] + if matches: + return matches + + time.sleep(timeout_sleep) + timeout_count -= 1 + + # debug print + for msg in self.msgs: + print(msg) + + raise TimeoutError("Timeout waiting for '%s'" % event) + + def close(self): + self.path = None + if self.sock is None: + return + self.sock.shutdown(socket.SHUT_WR) + self.sock.close() + self.sock = None + + # Test Suite @Parameterize.enable class BoltTest(dbusmock.DBusTestCase): @staticmethod - def path_from_service_file(sf): + def path_from_service_file(_sf): with open(SERVICE_FILE) as f: - for line in f: - if not line.startswith('Exec='): - continue - return line.split('=', 1)[1].strip() + for line in f: + if not line.startswith('Exec='): + continue + return line.split('=', 1)[1].strip() return None @classmethod @@ -957,6 +1086,11 @@ # a well known key that can be used in testing cls.key = 'b68bce095a13ac39e9254a88b189a38f240487aa6f78f803390a0cdeceb774d8' + # monkey patch Gio.IOErrorEnum to have a.quark() method + # so it behaves like Gio.DBusError + io_error_quark = GLib.quark_from_static_string('g-io-error-quark') + setattr(Gio.IOErrorEnum, 'quark', lambda _self: io_error_quark) + @classmethod def tearDownClass(cls): cls.test_bus.down() @@ -976,6 +1110,7 @@ self.daemon = None self.polkitd = None self.valgrind = False + self.sdnotify = None def tearDown(self): shutil.rmtree(self.dbpath) @@ -983,6 +1118,9 @@ del self.testbed self.daemon_stop() self.polkitd_stop() + if self.sdnotify is not None: + self.sdnotify.close() + self.sdnotify = None # Gross work-around for some flaky behavior where tests will fail # with some obscure error in umockdev, but only if all the tests # executed in one go. Maybe some cleanup issue. Needs further @@ -1001,13 +1139,16 @@ return proxy.Get('(ss)', interface, name) # daemon helper - def daemon_start(self): + def daemon_start(self, sdnotify=False): timeout = get_timeout('daemon_start') # seconds env = os.environ.copy() env['G_DEBUG'] = 'fatal-criticals' env['UMOCKDEV_DIR'] = self.testbed.get_root_dir() env['STATE_DIRECTORY'] = self.dbpath env['RUNTIME_DIRECTORY'] = self.rundir + if sdnotify: + self.sdnotify = SdNotify(self.rundir) + env['NOTIFY_SOCKET'] = self.sdnotify.path argv = [self.paths['daemon'], '-v'] valgrind = os.getenv('VALGRIND') if valgrind is not None: @@ -1052,7 +1193,7 @@ def polkitd_start(self): self._polkitd, self._polkitd_obj = self.spawn_server_template( - 'polkitd', {}, stdout=DEVNULL) + 'polkitd', {}, stdout=subprocess.DEVNULL) self.polkitd = dbus.Interface(self._polkitd_obj, dbusmock.MOCK_IFACE) def polkitd_stop(self): @@ -1097,7 +1238,8 @@ return stdout, stderr, ret # mock tree stuff - def default_mock_tree(self, acl=None): + @staticmethod + def default_mock_tree(acl=None): # default mock tree mt = TbDomain(host=TbHost([ TbDevice('Cable1'), @@ -1106,12 +1248,25 @@ ]), acl=acl) return mt - def simple_mock_tree(self): + @staticmethod + def simple_mock_tree(): mt = TbDomain(host=TbHost([ TbDevice('Dock') ])) return mt + def assertGError(self, have, want): + if hasattr(have, 'exception'): + have = have.exception + + domain = GLib.quark_to_string(want.quark()) + code = int(want) + if have.domain == domain and have.code == code: + return + + msg = "want: [%s (%d)], have [%s]" % (want, code, have) + raise self.failureException(msg) + def assertDeviceEqual(self, local, remote): self.assertTrue(local and remote) self.assertEqual(local.unique_id, remote.uid) @@ -1129,13 +1284,25 @@ self.assertEqual(local.bolt_status, remote.status) self.assertEqual(local.bolt_authflags, remote.authflags) + + if local.generation is not None: + self.assertEqual(local.generation, remote.generation) + else: + self.assertEqual(0, remote.generation) + return True - def add_domain_host(self, domain=0, security='secure', uid=None, bootacl=15, iommu=None): + def add_domain_host(self, domain=0, security='secure', uid=None, bootacl=15, iommu=None, nhi=0x15d2): if uid is None: uid = str(uuid.uuid4()) + nhi = self.testbed.add_device('pci', '0000:00:0d.%d' % domain, None, + ['class', '0x088000', + 'vendor', '0x8086', + 'device', '0x%04x' % nhi], + ['DRIVER', 'thunderbolt']) + props = ['security', security] if isinstance(bootacl, int): @@ -1147,7 +1314,7 @@ if iommu is not None: props += ['iommu_dma_protection', str(iommu) + '\n'] - dc = self.testbed.add_device('thunderbolt', 'domain%d' % domain, None, + dc = self.testbed.add_device('thunderbolt', 'domain%d' % domain, nhi, props, ['DEVTYPE', 'thunderbolt_domain']) @@ -1166,6 +1333,9 @@ self.testbed.remove_device(host) self.testbed.uevent(domain, "remove") self.testbed.remove_device(domain) + nhi = os.path.dirname(domain) + self.testbed.uevent(nhi, "remove") + self.testbed.remove_device(nhi) def add_device(self, parent, devid, name, vendor, domain=0, authorized=1, key='', boot=None): uid = str(uuid.uuid4()) @@ -1196,7 +1366,7 @@ self.assertEqual(len(x), 1) return x[0] - def store_device(self, dev, policy='auto', key=None): + def store_put_device(self, dev, policy='auto', key=None): df = configparser.ConfigParser() df.optionxform = lambda option: option @@ -1204,7 +1374,7 @@ df['device'] = { 'name': dev.device_name, 'vendor': dev.vendor_name, - 'type': 'peripheral' + 'type': 'host' if isinstance(dev, TbHost) else 'peripheral' } df['user'] = { @@ -1212,6 +1382,9 @@ 'policy': policy } + if dev.generation: + df['device']['generation'] = dev.generation + path = os.path.join(self.dbpath, 'devices', uid) with open(path, 'w') as f: df.write(f) @@ -1228,8 +1401,77 @@ with open(path, 'w') as f: f.write(key) + def store_get_device(self, uid): + path = os.path.join(self.dbpath, 'devices', uid) + df = configparser.ConfigParser() + df.read(path) + + device = df['device'] + name = device['name'] + vendor = device['vendor'] + generation = None + if 'generation' in device: + generation = int(device['generation']) + + dtype = device['type'] + if dtype == 'host': + dev = TbHost([], gen=generation) + elif dtype == 'peripheral': + dev = TbDevice(name, vendor=vendor, uid=uid, gen=generation) + else: + raise ValueError('Unknown device type') + return dev + + @contextmanager + def store_deny_device(self, uid): + path = os.path.join(self.dbpath, 'devices', uid) + fd = os.open(path, os.O_RDWR) + mode = os.fstat(fd).st_mode + try: + print('store: denying access to %s' % uid, file=sys.stderr) + os.fchmod(fd, 0) + yield fd + finally: + print('store: restoring access to %s' % uid, file=sys.stderr) + os.fchmod(fd, mode & 0o7777) + os.close(fd) + + def store_has_domain(self, uid): + path = os.path.join(self.dbpath, 'domains', uid) + return os.path.exists(path) + + def store_create_domain(self, *, uid=None, acl=None): + if not uid: + uid = str(uuid.uuid4()) + if acl: + bootacl = ",".join(acl) + else: + bootacl = "" + + entry = ( + "[domain]\n" + f"bootacl={bootacl}\n" + ) + path = os.path.join(self.dbpath, 'domains', uid) + with open(path, 'w') as f: + f.write(entry) + return uid + + def store_create_journal(self, name, uid, entries): + ts = 123456 + data = ["%s %s %016iX" % (e["uid"], e["op"], ts) for e in entries] + journal = os.path.join(self.dbpath, name) + os.makedirs(journal, exist_ok=True) + path = os.path.join(journal, uid) + with open(path, 'w') as f: + f.write("\n".join(data)) + # the actual tests def test_basic(self): + def make_events(name, devices): + paths = [GLib.Variant("(o)", (dev.bus_path, )) for dev in devices] + return [Recorder.Event('signal', name, path, None) for path in paths] + self.daemon_start() version = self.client.version assert version is not None @@ -1238,11 +1480,15 @@ policy = self.client.default_policy self.assertIn(policy, [self.client.POLICY_AUTO, self.client.POLICY_MANUAL]) - # connect all device + + # connect all device and make sure we get the proper events tree = self.default_mock_tree() - chk = TreeChecker(self.client, tree) - tree.connect_tree(self.testbed) - chk.sync() # check for the signals + + with self.client.record() as tape: + events = make_events('DeviceAdded', tree.devices) + tree.connect_tree(self.testbed) + res = tape.wait_for_events(events) + self.assertTrue(res) devices = self.client.list_devices() self.assertEqual(len(devices), len(tree.devices)) @@ -1250,17 +1496,24 @@ local = tree.find(False, unique_id=remote.uid) self.assertDeviceEqual(local, remote) - # disconnect all devices again - tree.disconnect(self.testbed) - chk.sync() # check for the signals - chk.close() + # disconnect all devices again (don't check the host, i.e. peripherals only) + with self.client.record() as tape: + events = make_events('DeviceRemoved', tree.peripherals) + tree.disconnect(self.testbed) + res = tape.wait_for_events(events) + self.assertTrue(res) - devices = self.client.list_devices() + devices = self.client.list_peripherals() self.assertEqual(len(devices), 0) + + # host device should be stored + remote = self.client.device_by_uid(tree.host.unique_id) + self.assertEqual(remote.stored, True) + self.assertEqual(remote.policy, BoltClient.POLICY_MANUAL) self.daemon_stop() def test_auth_mode(self): - dc, host = self.add_domain_host(security='secure') + _, host = self.add_domain_host(security='secure') self.daemon_start() self.polkitd_start() @@ -1276,14 +1529,11 @@ # disable the authorization with client.record() as tape: client.auth_mode = 'disabled' - res = tape.wait_for_event('property', - 'AuthMode', - 'disabled') - self.assertTrue(res) + tape.wait_for_props(AuthMode='disabled') self.assertEqual(client.auth_mode, 'disabled') - d1, d1_uid = self.add_device(host, 1, "Dock", "GNOME.org", authorized=0, key='', boot='0') - d2, d2_uid = self.add_device(host, 2, "Dock2", "GNOME.org", authorized=0, key=None, boot='0') + _, d1_uid = self.add_device(host, 1, "Dock", "GNOME.org", authorized=0, key='', boot='0') + _, d2_uid = self.add_device(host, 2, "Dock2", "GNOME.org", authorized=0, key=None, boot='0') devices = self.client.list_devices() self.assertEqual(len(devices), 3) @@ -1303,27 +1553,19 @@ for remote, uid in remotes: with self.assertRaises(GLib.GError) as cm: client.enroll(uid) - err = cm.exception - self.assertEqual(err.domain, GLib.quark_to_string(Gio.DBusError.quark())) - self.assertEqual(err.code, int(Gio.DBusError.ACCESS_DENIED)) + self.assertGError(cm, Gio.DBusError.ACCESS_DENIED) # enable the authorization again now with client.record() as tape: client.auth_mode = 'enabled' - res = tape.wait_for_event('property', - 'AuthMode', - 'enabled') - self.assertTrue(res) + tape.wait_for_props(AuthMode='enabled') self.assertEqual(client.auth_mode, 'enabled') policy = BoltClient.POLICY_DEFAULT for remote, uid in remotes: with remote.record() as tape: client.enroll(uid, policy) - res = tape.wait_for_event('property', - 'Stored', - True) - self.assertTrue(res) + tape.wait_for_props(Stored=True) self.assertEqual(remote.policy, client.default_policy) now = int(time.time()) self.assertEqual(remote.stored, True) @@ -1347,7 +1589,7 @@ self.assertEqual(self.client.security_level, 'unknown') with self.client.record() as tape: d, dom, host = make_domain(0, 'user') - tape.wait_for_event('property', 'SecurityLevel', 'user') + tape.wait_for_props(SecurityLevel='user') self.assertEqual(self.client.security_level, 'user') self.daemon_stop() self.remove_domain_host(dom, host) @@ -1371,7 +1613,7 @@ self.client.domain_by_id("") with self.assertRaises(GLib.GError): - self.client.domain_by_id("nonexistant") + self.client.domain_by_id("nonexistent") for name, domain, host in domains: remote = self.client.domain_by_id(name) @@ -1379,10 +1621,7 @@ with remote.record() as tape: self.remove_domain_host(domain, host) - res = tape.wait_for_event('property', - 'SysfsPath', - '') - self.assertTrue(res) + tape.wait_for_props(SysfsPath='') for d in range(4): remote = self.client.domain_by_id(make_uid(d)) @@ -1390,10 +1629,7 @@ with remote.record() as tape: name, domain, host = make_domain(d, 'secure') - res = tape.wait_for_event('property', - 'SysfsPath', - domain) - self.assertTrue(res) + tape.wait_for_props(SysfsPath=domain) remotes = self.client.list_domains() self.assertEqual(len(remotes), 4) @@ -1415,13 +1651,13 @@ with self.client.record() as tape: security = 'secure' - dom, host = self.add_domain_host(0, security=security, uid=uid) - tape.wait_for_event('property', 'SecurityLevel', security) + self.add_domain_host(0, security=security, uid=uid) + tape.wait_for_props(SecurityLevel=security) self.assertEqual(self.client.security_level, security) self.daemon_stop() - def test_domain_bootacl_init(self): + def test_domain_bootacl(self): ssd1 = TbDevice('SSD1',) cable1 = TbDevice('Cable1', children=[ssd1]) @@ -1436,10 +1672,10 @@ tree.connect_tree(self.testbed) - self.store_device(cable1, key=None) - self.store_device(ssd1, key='known') - self.store_device(cable2, key='known') - self.store_device(ssd2, key='known') + self.store_put_device(cable1, key=None) + self.store_put_device(ssd1, key='known') + self.store_put_device(cable2, key='known') + self.store_put_device(ssd2, key='known') domain_id = host.unique_id @@ -1448,13 +1684,112 @@ client = self.client domain = client.domain_by_id(domain_id) - bootacl = domain.BootACL + bootacl = domain.bootacl print('domain [%s] bootacl: %s' % (domain.uid, bootacl)) + self.assertNotIn(host.unique_id, bootacl) + devs = tree.peripherals for d in devs: self.assertIn(d.unique_id, bootacl) + # try to set the bootacl, without PolKit allowing it + with self.assertRaises(GLib.GError) as cm: + domain.bootacl = [''] + self.assertGError(cm, Gio.DBusError.ACCESS_DENIED) + self.polkitd.SetAllowed(['org.freedesktop.bolt.manage']) + + # try to set an invalid bootacl + with self.assertRaises(GLib.GError) as cm: + domain.bootacl = [''] + self.assertGError(cm, Gio.IOErrorEnum.INVALID_ARGUMENT) + + # try to set an invalid bootacl, 2nd time + with self.assertRaises(GLib.GError) as cm: + domain.bootacl = None + self.assertGError(cm, Gio.IOErrorEnum.INVALID_ARGUMENT) + + # remove all entries + with domain.record() as tape: + domain.bootacl = ['']*len(bootacl) + tape.wait_for_props(BootACL=None) + bootacl = domain.bootacl + print('domain [%s] bootacl: %s' % (domain.uid, bootacl)) + self.assertTrue(all([x == '' for x in bootacl])) + + bootacl_new = ['']*len(bootacl) + uuids = ["884c6edd-7118-4b21-b186-b02d396ecca0", + "58796843-4a8b-4578-921a-acc654984bfb", + "0cc8556f-c73b-41c0-990e-f1e317360626"] + + for i, u in enumerate(uuids): + bootacl_new[i] = u + + with domain.record() as tape: + domain.bootacl = bootacl_new + tape.wait_for_props(BootACL=None) + bootacl = domain.bootacl + print('domain [%s] bootacl: %s' % (domain.uid, bootacl)) + for entry in uuids: + self.assertIn(entry, uuids) + + self.daemon_stop() + + def test_domain_integrated_tbt(self): + # On integrated TBT (like ICL/TGL) the UUID of the controller is not + # stable, i.e. it changes between reboots. We therefore must not be + # saving the domain + def make_events(name, devices): + paths = [GLib.Variant("(o)", (dev.object_path, )) for dev in devices] + return [Recorder.Event('signal', name, path, None) for path in paths] + + ice_lake = { + "security": 'none', + "bootacl":None, + "iommu": "1", + "nhi": 0x8a17, # ice lake + } + + dom, host = self.add_domain_host(**ice_lake) + + self.daemon_start() + client = self.client + + domains = client.list_domains() + for domain in domains: + self.assertFalse(self.store_has_domain(domain.uid)) + + with self.client.record() as tape: + events = make_events('DomainRemoved', domains) + + self.remove_domain_host(dom, host) + + res = tape.wait_for_events(events) + self.assertTrue(res) + + self.daemon_stop() + + def test_domain_cleanup_stale(self): + stale = [] + for _ in range(4): + uid = self.store_create_domain() + stale += [uid] + + uid = self.store_create_domain() + acls = [{"uid": str(uuid.uuid4), "op": "+"}] + self.store_create_journal("bootacl", uid, acls) + + self.daemon_start() + client = self.client + + domains = client.list_domains() + have = [] + for domain in domains: + self.assertNotIn(domain.uid, stale) + have += [domain.uid] + + self.assertIn(uid, have) + self.daemon_stop() def test_signals_on_start(self): @@ -1475,8 +1810,9 @@ def test_basic_device_name(self): # prepare the basic setup - dc, host = self.add_domain_host() + _, host = self.add_domain_host() + # pylint: disable=bad-whitespace devs = [ # name vendor label notes ['GNOME.org Cable', 'GNOME.org', 'GNOME.org Cable' ''], # duplicated vendor name @@ -1486,10 +1822,11 @@ ['HP TB3 Dock', 'HP Inc.', 'HP TB3 Dock' ''], # cleanup company names ['TB Gadget', 'Apple, Inc.', 'Apple TB Gadget' ''], # cleanup company names ] + # pylint: enable=bad-whitespace devs = [{'name': d[0], 'vendor': d[1], 'label': d[2], 'id': i+1} for i, d in enumerate(devs)] - for i, d in enumerate(devs): + for d in devs: did, name, vendor = d['id'], d['name'], d['vendor'] path, uid = self.add_device(host, did, name, vendor) d['path'] = path @@ -1522,7 +1859,7 @@ self.client.device_by_uid("") with self.assertRaises(GLib.GError): - self.client.device_by_uid("nonexistant") + self.client.device_by_uid("nonexistent") tree = self.default_mock_tree() tree.connect_tree(self.testbed) @@ -1600,9 +1937,7 @@ remote = self.client.device_by_uid(d.unique_id) with self.assertRaises(GLib.GError) as cm: remote.authorize() - err = cm.exception - self.assertEqual(err.domain, GLib.quark_to_string(Gio.DBusError.quark())) - self.assertEqual(err.code, int(Gio.DBusError.ACCESS_DENIED)) + self.assertGError(cm, Gio.DBusError.ACCESS_DENIED) self.polkitd.SetAllowed(['org.freedesktop.bolt.authorize']) before = int(time.time()) @@ -1611,8 +1946,7 @@ tape = remote.record() remote.authorize() d.reload_auth() # will emit the uevent, so the daemon can update - res = tape.wait_for_event('property', 'Status', 'authorized') - self.assertTrue(res) + tape.wait_for_props(Status='authorized') self.assertDeviceEqual(d, remote) # make sure AuthorizeTime is correct now = int(time.time()) @@ -1636,14 +1970,9 @@ remote = self.client.device_by_uid(d.unique_id) with remote.record() as tape: client.enroll(d.unique_id, 'manual') - res = tape.wait_for_event('property', - 'Stored', - True) + tape.wait_for_props(Stored=True) d.disconnect(self.testbed) - res = tape.wait_for_event('property', - 'Status', - 'disconnected') - self.assertTrue(res) + tape.wait_for_props(Status='disconnected') err = None with self.assertRaises(GLib.GError) as cm: remote.authorize() @@ -1669,14 +1998,14 @@ ])) tree.connect_tree(self.testbed) - self.store_device(cable1, key=None) - self.store_device(ssd1, key='known') - self.store_device(cable2, key='known') - self.store_device(ssd2, key='known') - self.store_device(cable3, key='known', policy='manual') - self.store_device(dock1, key='known') - self.store_device(cable4, key='known', policy='iommu') - self.store_device(dock2, key=None, policy='iommu') + self.store_put_device(cable1, key=None) + self.store_put_device(ssd1, key='known') + self.store_put_device(cable2, key='known') + self.store_put_device(ssd2, key='known') + self.store_put_device(cable3, key='known', policy='manual') + self.store_put_device(dock1, key='known') + self.store_put_device(cable4, key='known', policy='iommu') + self.store_put_device(dock2, key=None, policy='iommu') self.daemon_start() @@ -1689,8 +2018,7 @@ tape = remote_ssd2.record() with remote_ssd2.record() as tape: if remote_ssd2.status != BoltDevice.AUTHORIZED: - res = tape.wait_for_event('property', 'Status', 'authorized') - self.assertTrue(res) + tape.wait_for_props(Status='authorized') self.assertEqual(remote_ssd2.status, BoltDevice.AUTHORIZED) @@ -1724,14 +2052,12 @@ tape_ssd1 = remote_ssd1.record() with remote_c1.record() as tape: cable1.authorize('1') - res = tape.wait_for_event('property', 'Status', 'authorized') - self.assertTrue(res) + tape.wait_for_props(Status='authorized') self.assertEqual(remote_c1.status, BoltDevice.AUTHORIZED) - res = tape_ssd1.wait_for_event('property', 'Status', 'authorized') + tape_ssd1.wait_for_props(Status='authorized') events = tape_ssd1.close() self.assertTrue(Recorder.events_list_contains(events, 'property', 'Status', 'authorizing')) - self.assertTrue(res) self.assertEqual(remote_ssd1.status, BoltDevice.AUTHORIZED) self.daemon_stop() @@ -1749,10 +2075,10 @@ ])) tree.connect_tree(self.testbed) - self.store_device(cable1, key=None, policy='iommu') - self.store_device(ssd1, key='known', policy='iommu') - self.store_device(cable2, key='known', policy='iommu') - self.store_device(ssd2, key='known', policy='auto') + self.store_put_device(cable1, key=None, policy='iommu') + self.store_put_device(ssd1, key='known', policy='iommu') + self.store_put_device(cable2, key='known', policy='iommu') + self.store_put_device(ssd2, key='known', policy='auto') self.daemon_start() @@ -1765,8 +2091,7 @@ tape = remote_ssd2.record() with remote_ssd2.record() as tape: if remote_ssd2.status != BoltDevice.AUTHORIZED: - res = tape.wait_for_event('property', 'Status', 'authorized') - self.assertTrue(res) + tape.wait_for_props(Status='authorized') self.assertEqual(remote_ssd2.status, BoltDevice.AUTHORIZED) @@ -1781,7 +2106,7 @@ self.daemon_stop() def device_import_test(self, security, devs, iommu): - dc, host = self.add_domain_host(security=security, iommu=iommu) + _, host = self.add_domain_host(security=security, iommu=iommu) for i, d in enumerate(devs): did = i + 1 @@ -1848,11 +2173,13 @@ ] if not iommu: + # pylint: disable=bad-whitespace devs += [ # check we are not auto-importing un-authorized devices {'authorized': 0, 'key': None, 'boot': 0, 'policy': None}, {'authorized': 0, 'key': '', 'boot': 0, 'policy': None}, ] + # pylint: enable=bad-whitespace self.device_import_test('user', devs, iommu) @@ -1861,6 +2188,7 @@ # like test_device_auto_import but in SECURE mode key = self.key + # pylint: disable=bad-whitespace devs = [ # no boot flag, not importing {'authorized': 1, 'key': None, 'boot': 0, 'policy': None}, @@ -1878,6 +2206,7 @@ {'authorized': 0, 'key': None, 'boot': 0, 'policy': None}, {'authorized': 0, 'key': '', 'boot': 0, 'policy': None}, ] + # pylint: enable=bad-whitespace self.device_import_test('secure', devs, iommu) @@ -1924,10 +2253,7 @@ with remote.record() as tape: if remote.status != BoltDevice.AUTHORIZED: - res = tape.wait_for_event('property', - 'Stored', - True) - self.assertTrue(res) + tape.wait_for_props(Stored=True) self.assertEqual(remote.status, BoltDevice.AUTHORIZED) self.assertEqual(remote.stored, True) @@ -1943,7 +2269,7 @@ client = self.client domain = client.domain_by_id(tree.unique_id) self.assertIsNotNone(domain) - self.assertIsNotNone(domain.BootACL) + self.assertIsNotNone(domain.bootacl) to_enroll = tree.collect(TbDevice.is_unauthorized) @@ -1955,9 +2281,7 @@ for d in to_enroll: with self.assertRaises(GLib.GError) as cm: client.enroll(d.unique_id) - err = cm.exception - self.assertEqual(err.domain, GLib.quark_to_string(Gio.DBusError.quark())) - self.assertEqual(err.code, int(Gio.DBusError.ACCESS_DENIED)) + self.assertGError(cm, Gio.DBusError.ACCESS_DENIED) self.polkitd.SetAllowed(['org.freedesktop.bolt.enroll']) @@ -1973,10 +2297,7 @@ policy = BoltClient.POLICY_AUTO with domain.record() as tape: remote = client.enroll(d.unique_id, policy) - res = tape.wait_for_event('property', - 'BootACL', - None) - self.assertTrue(res) + tape.wait_for_props(BootACL=None) else: policy = BoltClient.POLICY_MANUAL remote = client.enroll(d.unique_id, policy) @@ -2003,26 +2324,23 @@ self.assertTrue(remote.AuthorizeTime <= now) if policy == BoltClient.POLICY_AUTO: - self.assertIn(d.unique_id, domain.BootACL) + self.assertIn(d.unique_id, domain.bootacl) else: - self.assertNotIn(d.unique_id, domain.BootACL) + self.assertNotIn(d.unique_id, domain.bootacl) policies[d.unique_id] = policy # we disconnect the tree, but since the devices are connected # the daemon should have them in its database now tree.disconnect(self.testbed) - devices = self.client.list_devices() - - # the host itself is not stored in the daemon - expected_number = len(tree.devices) - 1 - devices = self.client.list_devices() + expected_number = len(tree.peripherals) + devices = self.client.list_peripherals() tries = 0 while expected_number != len(devices) and tries < 3: time.sleep(.2) tries += 1 - devices = self.client.list_devices() - self.assertEqual(len(devices), expected_number) + devices = self.client.list_peripherals() + self.assertEqual(len(devices), expected_number) tree.connect(self.testbed) # we connect the domain again tree.children[0].connect(self.testbed) # and the host too @@ -2044,19 +2362,16 @@ status = 'authorized' else: status = 'connected' - res = tape.wait_for_event('property', - 'Status', - status) + tape.wait_for_props(Status=status) local.reload_auth() # will emit the uevent, so the daemon can update - self.assertTrue(res) self.assertDeviceEqual(local, remote) def test_enroll_authorized(self): key = self.key - dc, host = self.add_domain_host() - d1, d1_uid = self.add_device(host, 1, "Dock", "GNOME.org", authorized=2, key=key, boot='0') - d2, d2_uid = self.add_device(host, 2, "Dock2", "GNOME.org", authorized=1, key=None, boot='0') + _, host = self.add_domain_host() + _, d1_uid = self.add_device(host, 1, "Dock", "GNOME.org", authorized=2, key=key, boot='0') + _, d2_uid = self.add_device(host, 2, "Dock2", "GNOME.org", authorized=1, key=None, boot='0') self.daemon_start() self.polkitd_start() @@ -2081,10 +2396,7 @@ with remote.record() as tape: client.enroll(uid, policy) - res = tape.wait_for_event('property', - 'Stored', - True) - self.assertTrue(res) + tape.wait_for_props(Stored=True) self.assertEqual(remote.policy, client.default_policy) now = int(time.time()) self.assertEqual(remote.stored, True) @@ -2095,7 +2407,7 @@ self.daemon_stop() def test_enroll_iommu(self): - dc, host = self.add_domain_host(security='secure', iommu='1') + _, host = self.add_domain_host(security='secure', iommu='1') self.daemon_start() self.polkitd_start() @@ -2108,14 +2420,11 @@ # disable the authorization globally with client.record() as tape: client.auth_mode = 'disabled' - res = tape.wait_for_event('property', - 'AuthMode', - 'disabled') - self.assertTrue(res) + tape.wait_for_props(AuthMode='disabled') self.assertEqual(client.auth_mode, 'disabled') - d1, d1_uid = self.add_device(host, 1, "Dock", "GNOME.org", authorized=0, key='', boot='0') - d2, d2_uid = self.add_device(host, 2, "Dock2", "GNOME.org", authorized=0, key=None, boot='0') + _, d1_uid = self.add_device(host, 1, "Dock", "GNOME.org", authorized=0, key='', boot='0') + _, d2_uid = self.add_device(host, 2, "Dock2", "GNOME.org", authorized=0, key=None, boot='0') devices = self.client.list_devices() self.assertEqual(len(devices), 3) @@ -2135,10 +2444,7 @@ # enable the authorization again now with client.record() as tape: client.auth_mode = 'enabled' - res = tape.wait_for_event('property', - 'AuthMode', - 'enabled') - self.assertTrue(res) + tape.wait_for_props(AuthMode='enabled') self.assertEqual(client.auth_mode, 'enabled') # with iommu enabled, the policy should be adjusted to 'IOMMU' @@ -2146,10 +2452,7 @@ for remote, uid in remotes: with remote.record() as tape: client.enroll(uid, policy) - res = tape.wait_for_event('property', - 'Stored', - True) - self.assertTrue(res) + tape.wait_for_props(Stored=True) self.assertEqual(remote.policy, client.POLICY_IOMMU) now = int(time.time()) self.assertEqual(remote.stored, True) @@ -2170,8 +2473,8 @@ ])) tree.connect_tree(self.testbed) - self.store_device(dock) - self.store_device(ssd2) + self.store_put_device(dock) + self.store_put_device(ssd2) self.daemon_start() self.polkitd_start() @@ -2190,20 +2493,17 @@ with remote_dock.record() as tape: remote_dock.authorize() - res = tape.wait_for_event('property', 'Key', 'new') - self.assertTrue(res) + tape.wait_for_props(Key='new') self.assertEqual(remote_dock.key, BoltDevice.KEY_NEW) with remote_ssd1.record() as tape: remote_ssd1.authorize() - res = tape.wait_for_event('property', 'Status', 'authorized') - self.assertTrue(res) + tape.wait_for_props(Status='authorized') self.assertEqual(remote_ssd1.key, BoltDevice.KEY_MISSING) with remote_ssd2.record() as tape: remote_ssd2.authorize() - res = tape.wait_for_event('property', 'Status', 'authorized') - self.assertTrue(res) + tape.wait_for_props(Status='authorized') self.assertEqual(remote_ssd2.key, BoltDevice.KEY_MISSING) def test_device_forget(self): @@ -2226,21 +2526,19 @@ self.assertEqual(remote.policy, policy) tree.disconnect(self.testbed) - expected_number = len(tree.devices) - 1 # host is not stored - devices = self.client.list_devices() + expected_number = len(tree.peripherals) + devices = self.client.list_peripherals() tries = 0 while expected_number != len(devices) and tries < 3: time.sleep(.2) tries += 1 - devices = self.client.list_devices() + devices = self.client.list_peripherals() self.assertEqual(len(devices), expected_number) for remote in devices: with self.assertRaises(GLib.GError) as cm: - client.forget(d.unique_id) - err = cm.exception - self.assertEqual(err.domain, GLib.quark_to_string(Gio.DBusError.quark())) - self.assertEqual(err.code, int(Gio.DBusError.ACCESS_DENIED)) + client.forget(remote.uid) + self.assertGError(cm, Gio.DBusError.ACCESS_DENIED) self.polkitd.SetAllowed(['org.freedesktop.bolt.manage']) @@ -2253,16 +2551,13 @@ # now we actually forget the device for remote in devices: - self.assertIn(remote.uid, domain.BootACL) + self.assertIn(remote.uid, domain.bootacl) with domain.record() as tape: client.forget(remote.uid) - res = tape.wait_for_event('property', - 'BootACL', - None) - self.assertTrue(res) - self.assertNotIn(remote.uid, domain.BootACL) + tape.wait_for_props(BootACL=None) + self.assertNotIn(remote.uid, domain.bootacl) - devices = self.client.list_devices() + devices = self.client.list_peripherals() self.assertEqual(len(devices), 0) def test_device_label(self): @@ -2289,32 +2584,233 @@ with self.assertRaises(GLib.GError) as cm: remote.label = 'not authorized' - err = cm.exception - self.assertEqual(err.domain, GLib.quark_to_string(Gio.DBusError.quark())) - self.assertEqual(err.code, int(Gio.DBusError.ACCESS_DENIED)) + self.assertGError(cm, Gio.DBusError.ACCESS_DENIED) self.polkitd.SetAllowed(['org.freedesktop.bolt.manage']) for val in ['', ' ', ' ']: with self.assertRaises(GLib.GError) as cm: remote.label = val - err = cm.exception - self.assertEqual(err.domain, GLib.quark_to_string(Gio.DBusError.quark())) - self.assertEqual(err.code, int(Gio.DBusError.INVALID_ARGS)) + self.assertGError(cm, Gio.DBusError.INVALID_ARGS) self.assertEqual(remote.label, "%s %s" % (local.vendor_name, local.device_name)) + # store update failure check is done in test_device_store_failures + val = 'A valid label' with remote.record() as tape: remote.label = val - res = tape.wait_for_event('property', - 'Label', - val) - self.assertTrue(res) + tape.wait_for_props(Label=val) self.assertEqual(remote.label, val) self.daemon_stop() + @unittest.skipIf(can_override_dac(), "have DAC override") + def test_device_store_failures(self): + self.daemon_start() + tree = self.simple_mock_tree() + self.polkitd_start() + tree.connect_tree(self.testbed) + + client = self.client + self.polkitd.SetAllowed(['org.freedesktop.bolt.enroll', + 'org.freedesktop.bolt.manage']) + + local = tree.collect(TbDevice.is_unauthorized)[0] + policy = BoltClient.POLICY_AUTO + + remote = client.enroll(local.unique_id, policy) + local.reload_auth() + + label = remote.label + + # check for store errors and verify the label did not change + with self.assertRaises(GLib.GError) as cm, \ + self.store_deny_device(local.unique_id): + remote.label = 'denied' + self.assertGError(cm, Gio.IOErrorEnum.PERMISSION_DENIED) + self.assertEqual(remote.label, label) + + # check store update failures + with self.assertRaises(GLib.GError) as cm, \ + self.store_deny_device(local.unique_id): + remote.policy = 'iommu' + self.assertGError(cm, Gio.IOErrorEnum.PERMISSION_DENIED) + self.assertEqual(remote.policy, policy) + + self.daemon_stop() + + def test_device_policy(self): + dock = TbDevice('Dock', gen=3) + tree = TbDomain(security=TbDomain.SECURITY_SECURE, + acl=16, + host=TbHost([dock])) + tree.connect_tree(self.testbed) + + # multiple domain controller for bootacl checks + ssd = TbDevice('SSD', gen=4) + dom2 = TbDomain(security=TbDomain.SECURITY_SECURE, + index=1, acl=16, + host=TbHost([ssd], name="Laptop1")) + dom2.connect_tree(self.testbed) + + self.daemon_start() + self.polkitd_start() + + client = self.client + remote = client.device_by_uid(dock.unique_id) + + # we are not allowed to manage the device + with self.assertRaises(GLib.GError) as cm: + remote.policy = 'iommu' + self.assertGError(cm, Gio.DBusError.ACCESS_DENIED) + + self.polkitd.SetAllowed(['org.freedesktop.bolt.manage', + 'org.freedesktop.bolt.enroll']) + + # device is not stored + with self.assertRaises(GLib.GError) as cm: + remote.policy = 'iommu' + self.assertGError(cm, Gio.DBusError.INVALID_ARGS) + + # enroll the device with manual policy + policy = BoltClient.POLICY_MANUAL + with remote.record() as tape: + remote = client.enroll(dock.unique_id, policy) + dock.reload_auth() + tape.wait_for_props(Stored=True) + self.assertEqual(remote.policy, BoltClient.POLICY_MANUAL) + + # policy is invalid + with self.assertRaises(GLib.GError) as cm: + remote.policy = 'foobar' + self.assertGError(cm, Gio.DBusError.INVALID_ARGS) + + # store update failure check is done in test_device_store_failures + + # finally a valid update + with remote.record() as tape: + remote.policy = 'iommu' + tape.wait_for_props(Policy='iommu') + self.assertEqual(remote.policy, BoltClient.POLICY_IOMMU) + + # enroll the ssd with manual policy + remote = client.device_by_uid(ssd.unique_id) + policy = BoltClient.POLICY_MANUAL + with remote.record() as tape: + remote = client.enroll(ssd.unique_id, policy) + ssd.reload_auth() + tape.wait_for_props(Stored=True) + self.assertEqual(remote.policy, BoltClient.POLICY_MANUAL) + + # policy -> auto, check that it got added to the bootacl + with remote.record() as tape: + remote.policy = 'auto' + tape.wait_for_props(Policy='auto') + self.assertEqual(remote.policy, BoltClient.POLICY_AUTO) + + for dom in [tree, dom2]: + domain = self.client.domain_by_id(dom.unique_id) + self.assertIn(ssd.unique_id, domain.bootacl) + + # policy -> manual, check it got removed from the bootacl + with remote.record() as tape: + remote.policy = 'auto' + tape.wait_for_props(Policy='auto') + self.assertEqual(remote.policy, BoltClient.POLICY_AUTO) + + for dom in [tree, dom2]: + domain = self.client.domain_by_id(dom.unique_id) + self.assertIn(ssd.unique_id, domain.bootacl) + + def test_device_generation(self): + dock = TbDevice('Dock', gen=3) + ssd1 = TbDevice('SSD1', gen=1) + ssd2 = TbDevice('SSD2', gen=2) + ssd2 = TbDevice('Ethernet', gen=None) + tree = TbDomain(security=TbDomain.SECURITY_SECURE, + host=TbHost([ + dock, + ssd1, + ssd2 + ], gen=4)) + + tree.connect_tree(self.testbed) + self.daemon_start() + + for d in tree.devices: + remote = self.client.device_by_uid(d.unique_id) + self.assertDeviceEqual(d, remote) + print("local: %d, remote: %d" % (d.generation or 0, remote.generation)) + + self.assertEqual(self.client.generation, 4) + + def test_device_generation_update(self): + dock = TbDevice('Dock', gen=0) + host = TbHost([dock], gen=0) + tree = TbDomain(security=TbDomain.SECURITY_SECURE, host=host) + + self.store_put_device(host) + self.store_put_device(dock) + self.daemon_start() + + self.assertEqual(self.client.generation, 0) + + remote = self.client.device_by_uid(host.unique_id) + self.assertEqual(remote.device_type, BoltDevice.HOST) + + with self.client.record() as tape: + host.generation = 4 + dock.generation = 3 + + tree.connect_tree(self.testbed) + + tape.wait_for_props(Generation=4) + + self.daemon_stop() + + # ensure that the updates are written to the store + stored = self.store_get_device(host.unique_id) + self.assertEqual(stored.generation, 4) + + stored = self.store_get_device(dock.unique_id) + self.assertEqual(stored.generation, 3) + + def test_device_linkspeed(self): + dock = TbDevice('Dock', gen=1) + host = TbHost([dock], gen=0) + tree = TbDomain(security=TbDomain.SECURITY_SECURE, host=host) + + linkspeed = {'rx.speed': 20, 'rx.lanes': 1, 'tx.speed': 10, 'tx.lanes': 2} + dock.linkspeed = linkspeed + + tree.connect_tree(self.testbed) + + self.daemon_start() + remote = self.client.device_by_uid(dock.unique_id) + + self.assertEqual(remote.linkspeed, linkspeed) + + with remote.record() as tape: + linkspeed = {'rx.speed': 20, 'rx.lanes': 2, 'tx.speed': 10, 'tx.lanes': 1} + dock.linkspeed = linkspeed + tape.wait_for_props(LinkSpeed=linkspeed) + + self.daemon_stop() + + def test_sdnotify(self): + self.add_domain_host(security='secure', iommu='1') + + self.daemon_start(sdnotify=True) + self.polkitd_start() + + assert(self.sdnotify) + + msgs = self.sdnotify.wait_for('STATUS') + self.assertTrue(msgs is not None) + + self.daemon_stop() + def test_boltctl(self): ssd1 = TbDevice('SSD1',) cable1 = TbDevice('Cable1', children=[ssd1]) @@ -2447,10 +2943,7 @@ with self.client.record() as tape: _, _, res = self.boltctl('config', 'auth-mode', 'disabled') self.assertEqual(res, 0) - res = tape.wait_for_event('property', - 'AuthMode', - 'disabled') - self.assertTrue(res) + tape.wait_for_props(AuthMode='disabled') self.assertEqual(self.client.auth_mode, 'disabled') # boltctl config: set device name @@ -2459,10 +2952,7 @@ with remote.record() as tape: _, _, res = self.boltctl('config', 'device.label', target, 'Nobody') self.assertEqual(res, 0) - res = tape.wait_for_event('property', - 'Label', - 'Nobody') - self.assertTrue(res) + tape.wait_for_props(Label='Nobody') self.assertEqual(remote.label, 'Nobody') # all done @@ -2478,7 +2968,7 @@ yield machine, human -if __name__ == '__main__': +def main(): if len(sys.argv) == 2 and sys.argv[1] == "list-tests": for machine, human in list_tests(): print("%s %s" % (machine, human), end="\n") @@ -2489,3 +2979,7 @@ os.execvp(wrapped[0], wrapped) unittest.main(verbosity=2) + + +if __name__ == '__main__': + main() diff -Nru bolt-0.8/tests/test-journal.c bolt-0.9.1/tests/test-journal.c --- bolt-0.8/tests/test-journal.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/tests/test-journal.c 2020-12-01 11:26:01.000000000 +0000 @@ -24,6 +24,7 @@ #include "bolt-dbus.h" #include "bolt-fs.h" +#include "bolt-test.h" #include #include @@ -301,6 +302,11 @@ }; guint k; + /* bolt_journal_put_diff uses bolt_copy_bytes, which in turn uses + * copy_file_range(2) internally, which was added in linux 4.5. */ + skip_test_unless (bolt_check_kernel_version (4, 5) || g_test_thorough (), + "linux kernel < 4.5, copy_file_range syscall missing"); + j = bolt_journal_new (tt->root, "diff", &err); /* the first and the last elements are added "manually" @@ -353,6 +359,46 @@ } static void +test_journal_diff_fresh (TestJournal *tt, gconstpointer user_data) +{ + g_autoptr(BoltJournal) j = NULL; + g_autoptr(GError) err = NULL; + g_autoptr(GPtrArray) arr = NULL; + g_autoptr(GHashTable) diff = NULL; + gboolean ok; + static BoltJournalItem items[] = { + {(char *) "aaaa", BOLT_JOURNAL_ADDED, 0}, + {(char *) "bbbb", BOLT_JOURNAL_REMOVED, 0}, + }; + guint k; + + /* bolt_journal_put_diff uses bolt_copy_bytes, which in turn uses + * copy_file_range(2) internally, which was added in linux 4.5. */ + skip_test_unless (bolt_check_kernel_version (4, 5) || g_test_thorough (), + "linux kernel < 4.5, copy_file_range syscall missing"); + + j = bolt_journal_new (tt->root, "diff_fresh", &err); + + g_assert_true (bolt_journal_is_fresh (j)); + + diff = g_hash_table_new (g_str_hash, g_str_equal); + + for (k = 1; k < G_N_ELEMENTS (items); k++) + { + BoltJournalItem *i = items + k; + int op = (i->op == BOLT_JOURNAL_ADDED) ? '+' : '-'; + + g_hash_table_insert (diff, i->id, GINT_TO_POINTER (op)); + } + + ok = bolt_journal_put_diff (j, diff, &err); + g_assert_no_error (err); + g_assert_true (ok); + + g_assert_false (bolt_journal_is_fresh (j)); +} + +static void test_journal_invalid_file (TestJournal *tt, gconstpointer user_data) { if (g_test_subprocess ()) @@ -468,6 +514,13 @@ test_journal_diff, test_journal_tear_down); + g_test_add ("/journal/diff/fresh", + TestJournal, + NULL, + test_journal_setup, + test_journal_diff_fresh, + test_journal_tear_down); + g_test_add ("/journal/invalid_file", TestJournal, NULL, diff -Nru bolt-0.8/tests/test-power.c bolt-0.9.1/tests/test-power.c --- bolt-0.8/tests/test-power.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/tests/test-power.c 2020-12-01 11:26:01.000000000 +0000 @@ -24,6 +24,7 @@ #include "bolt-dbus.h" #include "bolt-fs.h" +#include "bolt-macros.h" #include "bolt-str.h" #include "mock-sysfs.h" @@ -113,8 +114,7 @@ g_autoptr(GError) err = NULL; g_autoptr(BoltUdev) udev = NULL; g_autoptr(GFile) statedir = NULL; - g_autoptr(BoltPowerGuard) guard = NULL; - g_autoptr(BoltPower) guard_power = NULL; + g_autoptr(BoltGuard) guard = NULL; g_autofree char *guard_id = NULL; g_autofree char *guard_who = NULL; g_autofree char *guard_path = NULL; @@ -194,7 +194,6 @@ g_object_get (guard, "id", &guard_id, "who", &guard_who, - "power", &guard_power, "path", &guard_path, "pid", &guard_pid, "fifo", &guard_fifo, @@ -202,16 +201,15 @@ g_assert_nonnull (guard_id); g_assert_nonnull (guard_who); - g_assert_nonnull (guard_power); g_assert_nonnull (guard_path); g_assert_cmpstr (guard_id, ==, "1"); g_assert_cmpstr (guard_who, ==, "boltd"); g_assert_cmpuint (guard_pid, ==, getpid ()); - g_assert_cmpstr (guard_id, ==, bolt_power_guard_get_id (guard)); - g_assert_cmpstr (guard_who, ==, bolt_power_guard_get_who (guard)); - g_assert_cmpuint (guard_pid, ==, bolt_power_guard_get_pid (guard)); + g_assert_cmpstr (guard_id, ==, bolt_guard_get_id (guard)); + g_assert_cmpstr (guard_who, ==, bolt_guard_get_who (guard)); + g_assert_cmpuint (guard_pid, ==, bolt_guard_get_pid (guard)); /* set of OFF */ g_clear_object (&guard); @@ -232,7 +230,7 @@ { g_autoptr(BoltPower) power = NULL; g_autoptr(GError) err = NULL; - g_autoptr(BoltPowerGuard) guard = NULL; + g_autoptr(BoltGuard) guard = NULL; GPtrArray *guards = NULL; BoltPowerState state; gboolean supported; @@ -267,7 +265,7 @@ /* add one and remove it, nothing should change */ for (guint i = 0; i < 5; i++) { - g_autoptr(BoltPowerGuard) g = NULL; + g_autoptr(BoltGuard) g = NULL; state = bolt_power_get_state (power); g_assert (state == BOLT_FORCE_POWER_ON); @@ -302,7 +300,7 @@ guards = g_ptr_array_new_with_free_func (g_object_unref); for (guint i = 0; i < 5; i++) { - BoltPowerGuard *g = bolt_power_acquire (power, &err); + BoltGuard *g = bolt_power_acquire (power, &err); g_ptr_array_add (guards, g); } @@ -345,7 +343,7 @@ { g_autoptr(BoltPower) power = NULL; g_autoptr(GError) err = NULL; - g_autoptr(BoltPowerGuard) guard = NULL; + g_autoptr(BoltGuard) guard = NULL; g_autoptr(GMainLoop) loop = NULL; BoltPowerState state; gboolean supported; @@ -410,7 +408,7 @@ test_power_recover_state (TestPower *tt, gconstpointer user) { g_autoptr(BoltPower) power = NULL; - g_autoptr(BoltPowerGuard) guard = NULL; + g_autoptr(BoltGuard) guard = NULL; g_autoptr(GError) err = NULL; BoltPowerState state; GFile *guarddir; @@ -465,73 +463,22 @@ g_unsetenv ("BOLT_RUNDIR"); } - -static void -test_power_recover_guards_ok (TestPower *tt, gconstpointer user) -{ - g_autoptr(BoltPower) power = NULL; - g_autoptr(BoltPowerGuard) guard = NULL; - g_autoptr(GError) err = NULL; - BoltPowerState state; - const char *fp; - pid_t pid; - int r; - - fp = mock_sysfs_force_power_add (tt->sysfs); - g_assert_nonnull (fp); - - pid = fork (); - g_assert_cmpint (pid, !=, -1); - - if (pid == 0) - { - /* child */ - power = make_bolt_power_timeout (tt, 10); - - g_assert_no_error (err); - g_assert_nonnull (power); - - state = bolt_power_get_state (power); - g_assert_cmpint (state, ==, BOLT_FORCE_POWER_UNSET); - - /* we pretend the guard is for the parent */ - pid = getppid (); - guard = bolt_power_acquire_full (power, "test", pid, &err); - g_assert_no_error (err); - g_assert_nonnull (guard); - - state = bolt_power_get_state (power); - g_assert_cmpint (state, ==, BOLT_FORCE_POWER_ON); - - exit (0); - } - - /* parent */ - pid = waitpid (pid, &r, 0); - g_assert_cmpint (pid, >, 0); - g_assert_cmpint (r, ==, 0); - - /* now lets recover the guard */ - power = make_bolt_power_timeout (tt, 10); - - g_assert_no_error (err); - g_assert_nonnull (power); - - state = bolt_power_get_state (power); - g_assert_cmpint (state, ==, BOLT_FORCE_POWER_ON); -} - static void test_power_recover_guards_fail (TestPower *tt, gconstpointer user) { g_autoptr(BoltPower) power = NULL; - g_autoptr(BoltPowerGuard) guard = NULL; + g_autoptr(BoltGuard) guard = NULL; g_autoptr(GError) err = NULL; BoltPowerState state; const char *fp; pid_t pid; int r; +#if HAVE_ASAN + g_test_skip ("Test does not work with ASAN yet."); + return; +#endif + fp = mock_sysfs_force_power_add (tt->sysfs); g_assert_nonnull (fp); @@ -594,7 +541,7 @@ { g_autoptr(BoltPower) power = NULL; g_autoptr(GError) err = NULL; - g_autoptr(BoltPowerGuard) guard = NULL; + g_autoptr(BoltGuard) guard = NULL; g_autoptr(GMainLoop) loop = NULL; BoltPowerState state; gboolean on; @@ -618,7 +565,7 @@ state = bolt_power_get_state (power); g_assert (state == BOLT_FORCE_POWER_ON); - fd = bolt_power_guard_monitor (guard, &err); + fd = bolt_guard_monitor (guard, &err); g_assert_no_error (err); g_assert_cmpint (fd, >, -1); @@ -763,13 +710,6 @@ test_power_recover_state, test_power_tear_down); - g_test_add ("/power/guards/recover/ok", - TestPower, - NULL, - test_power_setup, - test_power_recover_guards_ok, - test_power_tear_down); - g_test_add ("/power/guards/recover/fail", TestPower, NULL, diff -Nru bolt-0.8/tests/test-reaper.c bolt-0.9.1/tests/test-reaper.c --- bolt-0.8/tests/test-reaper.c 1970-01-01 00:00:00.000000000 +0000 +++ bolt-0.9.1/tests/test-reaper.c 2020-12-01 11:26:01.000000000 +0000 @@ -0,0 +1,161 @@ +/* + * Copyright © 2020 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Christian J. Kellner + */ + +#include "config.h" + +#include "bolt-reaper.h" + +#include "bolt-dbus.h" +#include "bolt-unix.h" + +#include +#include +#include +#include + + +typedef struct +{ + int dummy; +} TestReaper; + +static gboolean +warn_quit_loop (gpointer user_data) +{ + GMainLoop *loop = user_data; + + g_warning ("timeout reached"); + g_main_loop_quit (loop); + + return G_SOURCE_REMOVE; +} + +static void +process_died (GObject *gobject, + guint pid, + const char *name, + gpointer user_data) +{ + + GMainLoop *loop = user_data; + + g_debug ("%u (%s) died", pid, name); + + if (g_main_loop_is_running (loop)) + g_main_loop_quit (loop); +} + +/* */ + +static void +test_reaper_object (TestReaper *tt, gconstpointer user) +{ + g_autoptr(BoltReaper) reaper = NULL; + guint timeout; + gboolean found; + + reaper = g_object_new (BOLT_TYPE_REAPER, NULL); + + g_object_get (reaper, "timeout", &timeout, NULL); + g_assert_cmpuint (timeout, >, 0); + + g_clear_object (&reaper); + + reaper = g_object_new (BOLT_TYPE_REAPER, "timeout", 10, NULL); + g_object_get (reaper, "timeout", &timeout, NULL); + g_assert_cmpuint (timeout, ==, 10); + + bolt_reaper_add_pid (reaper, 23, NULL); + + found = bolt_reaper_has_pid (reaper, 23); + g_assert_true (found); + + found = bolt_reaper_del_pid (reaper, 23); + g_assert_true (found); + + found = bolt_reaper_del_pid (reaper, 23); + g_assert_false (found); +} + +static void +test_reaper_basic (TestReaper *tt, gconstpointer user) +{ + g_autoptr(BoltReaper) reaper = NULL; + g_autoptr(GMainLoop) loop = NULL; + guint tid; + pid_t pid; + int r; + + pid = fork (); + g_assert_cmpint (pid, !=, -1); + + if (pid == 0) + /* child, do nothing but exit */ + exit (0); + + g_assert_true (bolt_pid_is_alive (pid)); + + pid = waitpid (pid, &r, 0); + g_assert_cmpint (pid, >, 0); + g_assert_cmpint (r, ==, 0); + + loop = g_main_loop_new (NULL, FALSE); + reaper = g_object_new (BOLT_TYPE_REAPER, + "timeout", 500, + NULL); + g_assert_nonnull (reaper); + + bolt_reaper_add_pid (reaper, (pid_t) pid, "foo"); + + g_signal_connect (reaper, "process-died", + G_CALLBACK (process_died), + loop); + + tid = g_timeout_add_seconds (5, warn_quit_loop, loop); + g_main_loop_run (loop); + g_clear_handle_id (&tid, g_source_remove); + +} + +int +main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + + g_test_init (&argc, &argv, NULL); + + bolt_dbus_ensure_resources (); + + g_test_add ("/reaper/object", + TestReaper, + NULL, + NULL, + test_reaper_object, + NULL); + + g_test_add ("/reaper/basic", + TestReaper, + NULL, + NULL, + test_reaper_basic, + NULL); + + return g_test_run (); +} diff -Nru bolt-0.8/tests/test-self.c bolt-0.9.1/tests/test-self.c --- bolt-0.8/tests/test-self.c 1970-01-01 00:00:00.000000000 +0000 +++ bolt-0.9.1/tests/test-self.c 2020-12-01 11:26:01.000000000 +0000 @@ -0,0 +1,209 @@ +/* + * Copyright © 2018 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Christian J. Kellner + */ + +#include "config.h" + +#include + +#include + +typedef int TestDummy; + +static void +test_version_parse (TestDummy *tt, gconstpointer user_data) +{ + struct VersionTest + { + const char *str; + + /* result */ + gboolean ok; + + /* data */ + int major; + int minor; + int patch; + + const char *suffix; + } ftt[] = { + {"", FALSE, -1, -1, -1, NULL}, + {"parsererror", FALSE, -1, -1, -1, NULL}, + {"parser.err.or", FALSE, -1, -1, -1, NULL}, + {"1.0.0.43", FALSE, 1, 0, -1, NULL}, + {"1", TRUE, 1, -1, -1, NULL}, + {"1.0", TRUE, 1, 0, -1, NULL}, + {"1.0.0", TRUE, 1, 0, 0, NULL}, + {"1-100", TRUE, 1, -1, -1, "100"}, + {"1-100.fc", TRUE, 1, -1, -1, "100.fc"}, + {"1.0-100.fc", TRUE, 1, 0, -1, "100.fc"}, + {"1.0.0-100.fc", TRUE, 1, 0, 0, "100.fc"}, + {"5.2.11-200.fc30.x86_64", TRUE, 5, 2, 11, "200.fc30.x86_64"}, + {"4.4.0-161-generic", TRUE, 4, 4, 0, "161-generic"} + }; + + for (guint i = 0; i < G_N_ELEMENTS (ftt); i++) + { + g_autoptr(GError) err = NULL; + g_auto(BoltVersion) v = { .major = 42, .minor = 42, .patch = 42, .suffix = NULL}; + gboolean ok; + + ok = bolt_version_parse (ftt[i].str, &v, &err); + + if (ftt[i].ok) + { + g_assert_no_error (err); + g_assert_true (ok); + } + else + { + g_assert_nonnull (err); + g_assert_false (ok); + } + + g_assert_cmpint (v.major, ==, ftt[i].major); + g_assert_cmpint (v.minor, ==, ftt[i].minor); + g_assert_cmpint (v.patch, ==, ftt[i].patch); + + if (ftt[i].suffix) + g_assert_cmpstr (v.suffix, ==, ftt[i].suffix); + else + g_assert_null (v.suffix); + } +} + +static void +test_version_compare (TestDummy *tt, gconstpointer user_data) +{ + struct VersionTest + { + BoltVersion a; + BoltVersion b; + + int res; + } ftt[] = { + /* x.-.- */ + { BOLT_VERSION_INIT ( 1, -1, -1), BOLT_VERSION_INIT ( 0, -1, -1), 1}, + { BOLT_VERSION_INIT ( 1, -1, -1), BOLT_VERSION_INIT ( 1, -1, -1), 0}, + { BOLT_VERSION_INIT ( 1, -1, -1), BOLT_VERSION_INIT (42, -1, -1), -1}, + + { BOLT_VERSION_INIT ( 1, 5, -1), BOLT_VERSION_INIT ( 1, -1, -1), 1}, + { BOLT_VERSION_INIT ( 1, 5, -1), BOLT_VERSION_INIT ( 5, 1, -1), -1}, + + /* x.y.- */ + { BOLT_VERSION_INIT ( 1, 5, -1), BOLT_VERSION_INIT ( 0, 5, -1), 1}, + { BOLT_VERSION_INIT ( 1, 5, -1), BOLT_VERSION_INIT ( 1, 0, -1), 1}, + { BOLT_VERSION_INIT ( 1, 5, -1), BOLT_VERSION_INIT ( 2, 0, -1), -1}, + + /* x.y.z */ + { BOLT_VERSION_INIT ( 1, 2, 3), BOLT_VERSION_INIT ( 1, 0, 0), 1}, + { BOLT_VERSION_INIT ( 1, 2, 3), BOLT_VERSION_INIT ( 1, 2, 2), 1}, + { BOLT_VERSION_INIT ( 1, 2, 3), BOLT_VERSION_INIT ( 1, 2, 3), 0}, + { BOLT_VERSION_INIT ( 1, 2, 3), BOLT_VERSION_INIT ( 1, 2, 4), -1}, + { BOLT_VERSION_INIT ( 1, 2, 3), BOLT_VERSION_INIT ( 2, 0, 0), -1}, + { BOLT_VERSION_INIT ( 1, 2, 3), BOLT_VERSION_INIT ( 2, 0, -1), -1}, + { BOLT_VERSION_INIT ( 1, 2, 3), BOLT_VERSION_INIT ( 2, -1, -1), -1}, + }; + + for (guint i = 0; i < G_N_ELEMENTS (ftt); i++) + { + int res = bolt_version_compare (&(ftt[i].a), &(ftt[i].b)); + g_assert_cmpint (res, ==, ftt[i].res); + + res = bolt_version_compare (&(ftt[i].b), &(ftt[i].a)); + g_assert_cmpint (res, ==, -1 * ftt[i].res); + } +} + +static void +test_version_check (TestDummy *tt, gconstpointer user_data) +{ + struct VersionTest + { + BoltVersion version; + + int major; + int minor; + int patch; + + gboolean res; + } ftt[] = { + { BOLT_VERSION_INIT (1, 2, 3), 1, -1, -1, TRUE}, + { BOLT_VERSION_INIT (1, 2, 3), 1, 0, -1, TRUE}, + { BOLT_VERSION_INIT (1, 2, 3), 1, 0, 0, TRUE}, + { BOLT_VERSION_INIT (1, 2, 3), 1, 2, 0, TRUE}, + { BOLT_VERSION_INIT (1, 2, 3), 1, 2, 3, TRUE}, + + { BOLT_VERSION_INIT (1, 2, 3), 1, 2, 4, FALSE}, + { BOLT_VERSION_INIT (1, 2, 3), 1, 3, 2, FALSE}, + { BOLT_VERSION_INIT (1, 2, 3), 2, 0, 0, FALSE}, + { BOLT_VERSION_INIT (1, 2, 3), 2, 3, 0, FALSE}, + { BOLT_VERSION_INIT (1, 2, 3), 2, 3, 4, FALSE}, + { BOLT_VERSION_INIT (1, 2, 3), 2, -1, -1, FALSE}, + { BOLT_VERSION_INIT (1, 2, 3), 2, 0, -1, FALSE}, + + { BOLT_VERSION_INIT (2, -1, -1), 2, -1, -1, TRUE}, + { BOLT_VERSION_INIT (2, -1, -1), 1, 9, 9, TRUE}, + { BOLT_VERSION_INIT (2, -1, -1), 2, 0, 0, FALSE}, + + }; + + for (guint i = 0; i < G_N_ELEMENTS (ftt); i++) + { + gboolean res; + + res = bolt_version_check (&(ftt[i].version), + ftt[i].major, + ftt[i].minor, + ftt[i].patch); + + g_assert_true (res == ftt[i].res); + } +} + +int +main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + + g_test_init (&argc, &argv, NULL); + + g_test_add ("/self/version/parse", + TestDummy, + NULL, + NULL, + test_version_parse, + NULL); + + g_test_add ("/self/version/compare", + TestDummy, + NULL, + NULL, + test_version_compare, + NULL); + + g_test_add ("/self/version/check", + TestDummy, + NULL, + NULL, + test_version_check, + NULL); + + return g_test_run (); +} diff -Nru bolt-0.8/tests/test-store.c bolt-0.9.1/tests/test-store.c --- bolt-0.8/tests/test-store.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/tests/test-store.c 2020-12-01 11:26:01.000000000 +0000 @@ -66,11 +66,11 @@ } - tt->store = bolt_store_new (tt->path); + tt->store = bolt_store_new (tt->path, &error); if (tt->store == NULL) { - g_critical ("Could not create store at %s", - tt->path); + g_critical ("Could not create store at %s: %s", + tt->path, error->message); return; } @@ -105,12 +105,19 @@ g_autofree char *path = NULL; char uid[] = "fbc83890-e9bf-45e5-a777-b3728490989c"; BoltKeyState keystate; + guint version; gboolean ok; g_object_get (tt->store, "root", &root, NULL); path = g_file_get_path (root); g_assert_cmpstr (tt->path, ==, path); + g_object_get (tt->store, "version", &version, NULL); + g_assert_cmpuint (version, ==, BOLT_STORE_VERSION); + + version = bolt_store_get_version (tt->store); + g_assert_cmpuint (version, ==, BOLT_STORE_VERSION); + dev = g_object_new (BOLT_TYPE_DEVICE, "uid", uid, "name", "Laptop", @@ -138,6 +145,7 @@ g_assert_cmpstr (bolt_device_get_uid (stored), ==, bolt_device_get_uid (dev)); g_assert_cmpstr (bolt_device_get_name (stored), ==, bolt_device_get_name (dev)); g_assert_cmpstr (bolt_device_get_vendor (stored), ==, bolt_device_get_vendor (dev)); + g_assert_cmpuint (bolt_device_get_generation (stored), ==, 0); g_assert_cmpuint (bolt_device_get_policy (stored), ==, BOLT_POLICY_AUTO); g_assert_cmpuint (bolt_device_get_stored (stored), ==, TRUE); @@ -151,6 +159,7 @@ "uid", uid, "name", "Laptop", "vendor", "GNOME.org", + "generation", 4, "status", BOLT_STATUS_DISCONNECTED, NULL); @@ -176,6 +185,7 @@ g_assert_cmpstr (bolt_device_get_name (stored), ==, bolt_device_get_name (dev)); g_assert_cmpstr (bolt_device_get_vendor (stored), ==, bolt_device_get_vendor (dev)); + g_assert_cmpuint (bolt_device_get_generation (stored), ==, 4); g_assert_cmpuint (bolt_device_get_policy (stored), ==, BOLT_POLICY_MANUAL); g_assert_cmpuint (bolt_device_get_stored (stored), ==, TRUE); g_assert_cmpuint (bolt_device_get_keystate (stored), ==, 1); @@ -239,6 +249,69 @@ } static void +test_store_update (TestStore *tt, gconstpointer user_data) +{ + g_autoptr(BoltDevice) dev = NULL; + g_autoptr(BoltKey) key = NULL; + g_autoptr(GError) err = NULL; + char uid[] = "fbc83890-e9bf-45e5-a777-b3728490989c"; + BoltKeyState keystate; + BoltPolicy policy; + gboolean ok; + guint64 storetime; + + policy = BOLT_POLICY_IOMMU; + dev = g_object_new (BOLT_TYPE_DEVICE, + "uid", uid, + "name", "Laptop", + "vendor", "GNOME.org", + "status", BOLT_STATUS_DISCONNECTED, + "generation", 1, + NULL); + + key = bolt_key_new (NULL); + g_assert_nonnull (key); + + ok = bolt_store_put_device (tt->store, + dev, + policy, + key, + &err); + + g_assert_no_error (err); + g_assert_true (ok); + + keystate = bolt_device_get_keystate (dev); + g_assert_cmpuint (keystate, ==, BOLT_KEY_NEW); + g_assert_cmpuint (bolt_device_get_policy (dev), ==, policy); + + storetime = bolt_device_get_storetime (dev); + + g_object_set (G_OBJECT (dev), + "generation", 3, + "label", "My Laptop", + NULL); + + /* update the device. generation and label should + * change, but the rest should stay the same, esp. + * keystate and also storetime should not change. + * Also, BOLT_POLICY_DEFAULT should be ignored */ + ok = bolt_store_put_device (tt->store, + dev, + BOLT_POLICY_DEFAULT, + NULL, + &err); + + g_assert_no_error (err); + g_assert_true (ok); + + keystate = bolt_device_get_keystate (dev); + g_assert_cmpuint (keystate, ==, BOLT_KEY_NEW); + g_assert_cmpuint (bolt_device_get_policy (dev), ==, policy); + g_assert_cmpuint (bolt_device_get_storetime (dev), ==, storetime); +} + +static void test_store_config (TestStore *tt, gconstpointer user_data) { g_autoptr(GKeyFile) kf = NULL; @@ -345,6 +418,8 @@ g_assert_no_error (err); g_assert_nonnull (loaded); + g_clear_object (&loaded); + /* corrupt the key */ p = g_file_get_path (f); r = truncate (p, 32); @@ -595,6 +670,7 @@ g_autoptr(BoltDomain) d1 = NULL; g_autoptr(BoltDomain) s1 = NULL; const char *uid = "884c6edd-7118-4b21-b186-b02d396ecca0"; + gboolean exists; gboolean ok; GStrv bootacl = NULL; const char *acl[16] = { @@ -655,6 +731,13 @@ g_assert_no_error (err); g_assert_true (ok); + exists = bolt_store_has_journal (tt->store, "bootacl", uid); + g_assert_true (exists); + + ok = bolt_domain_can_delete (d1, &err); + g_assert_no_error (err); + g_assert_true (ok); + /* update: get again after update */ g_clear_object (&s1); s1 = bolt_store_get_domain (tt->store, uid, &err); @@ -665,13 +748,158 @@ bootacl = bolt_domain_get_bootacl (s1); bolt_assert_strv_equal ((GStrv) acl, bootacl, 0); + g_clear_object (&s1); /* delete */ - g_assert_true (bolt_domain_is_stored (s1)); - ok = bolt_store_del_domain (tt->store, s1, &err); + g_assert_true (bolt_domain_is_stored (d1)); + ok = bolt_store_del_domain (tt->store, d1, &err); + g_assert_no_error (err); + g_assert_true (ok); + g_assert_false (bolt_domain_is_stored (d1)); + + exists = bolt_store_has_journal (tt->store, "bootacl", uid); + g_assert_false (exists); + + /* store again, modify the bootacl, i.e. write to the journal */ + ok = bolt_store_put_domain (tt->store, d1, &err); + g_assert_no_error (err); + g_assert_true (ok); + g_assert_true (bolt_domain_is_stored (d1)); + + ok = bolt_domain_bootacl_del (d1, + "884c6edd-7118-4b21-b186-b02d396ecca1", + &err); g_assert_no_error (err); g_assert_true (ok); - g_assert_false (bolt_domain_is_stored (s1)); + + bootacl = bolt_domain_get_bootacl (d1); + acl[0] = ""; + bolt_assert_strv_equal ((GStrv) acl, bootacl, 0); + + /* journal should exist and non-empty */ + exists = bolt_store_has_journal (tt->store, "bootacl", uid); + g_assert_true (exists); + + /* non-empty journal should prevent deletion */ + ok = bolt_domain_can_delete (d1, &err); + g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_EMPTY); + g_assert_false (ok); + g_clear_error (&err); + + ok = bolt_store_del_domain (tt->store, d1, &err); + g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_EMPTY); + g_assert_false (ok); +} + +static void +test_store_journal (TestStore *tt, gconstpointer user_data) +{ + g_autoptr(GError) err = NULL; + g_autoptr(BoltJournal) journal = NULL; + gboolean exists; + gboolean ok; + + /* delete an non-existing one */ + ok = bolt_store_del_journal (tt->store, "acl", "log", &err); + g_assert_no_error (err); + g_assert_true (ok); + + /* create a non-existing one */ + journal = bolt_store_open_journal (tt->store, "acl", "log", &err); + g_assert_no_error (err); + g_assert_nonnull (journal); + + exists = bolt_store_has_journal (tt->store, "acl", "log"); + g_assert_true (exists); + + /* re-open the existing one */ + g_clear_object (&journal); + journal = bolt_store_open_journal (tt->store, "acl", "log", &err); + g_assert_no_error (err); + g_assert_nonnull (journal); + + /* delete the journal */ + g_clear_object (&journal); + ok = bolt_store_del_journal (tt->store, "acl", "log", &err); + g_assert_no_error (err); + g_assert_true (ok); + + exists = bolt_store_has_journal (tt->store, "acl", "log"); + g_assert_false (exists); + +} + +static void +test_store_upgrade (TestStore *tt, gconstpointer user_data) +{ + g_autoptr(BoltDevice) dev = NULL; + g_autoptr(GError) err = NULL; + g_autoptr(DIR) root = NULL; + char uid[] = "fbc83890-e9bf-45e5-a777-b3728490989c"; + guint version; + gboolean up; + gboolean ok; + + /* simulate a version 0 store, i.e. has some entries, + * but no 'version' file */ + + root = bolt_opendir (tt->path, &err); + g_assert_no_error (err); + g_assert_nonnull (root); + + version = bolt_store_get_version (tt->store); + g_assert_cmpuint (version, ==, BOLT_STORE_VERSION); + + dev = g_object_new (BOLT_TYPE_DEVICE, + "uid", uid, + "name", "Laptop", + "vendor", "GNOME.org", + "status", BOLT_STATUS_DISCONNECTED, + NULL); + + ok = bolt_store_put_device (tt->store, dev, BOLT_POLICY_AUTO, NULL, &err); + g_assert_no_error (err); + g_assert_true (ok); + + /* close the store, delete 'version' */ + g_clear_object (&tt->store); + + ok = bolt_unlink_at (dirfd (root), "version", 0, &err); + g_assert_no_error (err); + g_assert_true (ok); + + /* re-create the store object */ + tt->store = bolt_store_new (tt->path, &err); + g_assert_no_error (err); + g_assert_nonnull (tt->store); + + version = bolt_store_get_version (tt->store); + g_assert_cmpuint (version, ==, 0); + + /* no upgrade the store */ + ok = bolt_store_upgrade (tt->store, &up, &err); + g_assert_no_error (err); + g_assert_true (ok); + g_assert_true (up); + + /* assert the upgrade changed the version */ + version = bolt_store_get_version (tt->store); + g_assert_cmpuint (version, ==, BOLT_STORE_VERSION); + + /* upgrade again, check it did not do anything */ + ok = bolt_store_upgrade (tt->store, &up, &err); + g_assert_no_error (err); + g_assert_true (ok); + g_assert_false (up); + + version = bolt_store_get_version (tt->store); + g_assert_cmpuint (version, ==, BOLT_STORE_VERSION); + + /* ensure 'upgrade' argument is optional */ + ok = bolt_store_upgrade (tt->store, NULL, &err); + g_assert_no_error (err); + g_assert_true (ok); + } int @@ -698,6 +926,13 @@ test_store_basic, test_store_tear_down); + g_test_add ("/daemon/store/update", + TestStore, + NULL, + test_store_setup, + test_store_update, + test_store_tear_down); + g_test_add ("/daemon/store/config", TestStore, NULL, @@ -726,5 +961,19 @@ test_store_domain, test_store_tear_down); + g_test_add ("/daemon/store/journal", + TestStore, + NULL, + test_store_setup, + test_store_journal, + test_store_tear_down); + + g_test_add ("/daemon/store/upgrade", + TestStore, + NULL, + test_store_setup, + test_store_upgrade, + test_store_tear_down); + return g_test_run (); } diff -Nru bolt-0.8/tests/test-sysfs.c bolt-0.9.1/tests/test-sysfs.c --- bolt-0.8/tests/test-sysfs.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/tests/test-sysfs.c 2020-12-01 11:26:01.000000000 +0000 @@ -21,6 +21,7 @@ #include "config.h" #include "bolt-dbus.h" +#include "bolt-error.h" #include "bolt-macros.h" #include "bolt-store.h" #include "bolt-str.h" @@ -37,6 +38,7 @@ #include #include +#include typedef struct udev_device udev_device; G_DEFINE_AUTOPTR_CLEANUP_FUNC (udev_device, udev_device_unref); @@ -72,6 +74,274 @@ } static void +test_sysfs_device_get_unique_id (TestSysfs *tt, gconstpointer user) +{ + g_autoptr(udev_device) udevice = NULL; + g_autoptr(GError) err = NULL; + const char *domain; + const char *host; + const char *syspath; + const char *dev; + const char *uid; + MockDevId id = { + .vendor_id = 0x42, + .vendor_name = "GNOME.org", + .device_id = 0x42, + .device_name = "Laptop", + .unique_id = "884c6edd-7118-4b21-b186-b02d396ecca0", + }; + + domain = mock_sysfs_domain_add (tt->sysfs, BOLT_SECURITY_SECURE, NULL); + g_assert_nonnull (domain); + + host = mock_sysfs_host_add (tt->sysfs, domain, &id); + g_assert_nonnull (host); + + syspath = mock_sysfs_device_get_syspath (tt->sysfs, host); + udevice = udev_device_new_from_syspath (tt->udev, syspath); + g_assert_nonnull (udevice); + + uid = bolt_sysfs_device_get_unique_id (udevice, &err); + g_assert_no_error (err); + g_assert_cmpstr (uid, ==, id.unique_id); + + /* error case */ + memset (&id, 0, sizeof (MockDevId)); + dev = mock_sysfs_device_add (tt->sysfs, host, &id, 0, NULL, -1, NULL); + g_assert_nonnull (dev); + + g_clear_pointer (&udevice, udev_device_unref); + syspath = mock_sysfs_device_get_syspath (tt->sysfs, dev); + udevice = udev_device_new_from_syspath (tt->udev, syspath); + g_assert_nonnull (udevice); + + uid = bolt_sysfs_device_get_unique_id (udevice, &err); + g_assert_error (err, BOLT_ERROR, BOLT_ERROR_UDEV); + g_assert_null (uid); +} + +static void +check_ident (TestSysfs *tt, const char *host) +{ + g_auto(BoltIdent) ident = BOLT_IDENT_INIT; + MockDevId ref = { + .vendor_id = 0x42, + .vendor_name = "GNOME.org", + .device_id = 0x42, + .device_name = "Laptop", + .unique_id = "884c6edd-7118-4b21-b186-b02d396ecca0", + }; + MockDevId id = ref; + struct + { + const char *name; + const char **source; + gint *fallback; + const char **target; + } fields[] = { + {"name", &id.device_name, &id.device_id, &ident.name }, + {"vendor", &id.vendor_name, &id.vendor_id, &ident.vendor} + }; + + for (gsize i = 0; i < G_N_ELEMENTS (fields); i++) + { + g_autoptr(udev_device) udevice = NULL; + g_autoptr(GError) err = NULL; + const char *syspath; + const char *dev; + gboolean ok; + + /* reset fields */ + id = ref; + + /* set the target to NULL */ + *(fields[i].source) = NULL; + + dev = mock_sysfs_device_add (tt->sysfs, host, &id, 0, NULL, -1, NULL); + g_assert_nonnull (dev); + + syspath = mock_sysfs_device_get_syspath (tt->sysfs, dev); + udevice = udev_device_new_from_syspath (tt->udev, syspath); + g_assert_nonnull (udevice); + + ok = bolt_sysfs_device_ident (udevice, &ident, &err); + + g_assert_no_error (err); + g_assert_true (ok); + + g_assert_nonnull (*(fields[i].target)); + + mock_sysfs_device_remove (tt->sysfs, dev); + g_clear_pointer (&udevice, udev_device_unref); + + /* remove the fallback, re-plug the device */ + *(fields[i].fallback) = 0; + dev = mock_sysfs_device_add (tt->sysfs, host, &id, 0, NULL, -1, NULL); + g_assert_nonnull (dev); + + syspath = mock_sysfs_device_get_syspath (tt->sysfs, dev); + udevice = udev_device_new_from_syspath (tt->udev, syspath); + g_assert_nonnull (udevice); + + bolt_ident_clear (&ident); + g_assert_null (*(fields[i].target)); + + /* fallback is removed */ + ok = bolt_sysfs_device_ident (udevice, &ident, &err); + g_assert_error (err, BOLT_ERROR, BOLT_ERROR_UDEV); + g_assert_false (ok); + g_assert_null (ident.udev); + + bolt_ident_clear (&ident); + mock_sysfs_device_remove (tt->sysfs, dev); + } +} + +static void +test_sysfs_device_ident (TestSysfs *tt, gconstpointer user) +{ + g_autoptr(udev_device) udevice = NULL; + g_autoptr(GError) err = NULL; + g_auto(BoltIdent) ident = BOLT_IDENT_INIT; + const char *domain; + const char *host; + const char *syspath; + gboolean ok; + MockDevId id = { + .vendor_id = 0x42, + .vendor_name = "GNOME.org", + .device_id = 0x42, + .device_name = "Laptop", + .unique_id = "884c6edd-7118-4b21-b186-b02d396ecca0", + }; + + domain = mock_sysfs_domain_add (tt->sysfs, BOLT_SECURITY_SECURE, NULL); + g_assert_nonnull (domain); + + host = mock_sysfs_host_add (tt->sysfs, domain, &id); + g_assert_nonnull (host); + + syspath = mock_sysfs_device_get_syspath (tt->sysfs, host); + udevice = udev_device_new_from_syspath (tt->udev, syspath); + g_assert_nonnull (udevice); + + ok = bolt_sysfs_device_ident (udevice, &ident, &err); + g_assert_no_error (err); + g_assert_true (ok); + + g_assert_true (ident.udev == udevice); + g_assert_cmpstr (id.device_name, ==, ident.name); + g_assert_cmpstr (id.vendor_name, ==, ident.vendor); + + g_clear_pointer (&udevice, udev_device_unref); + + /* clear function */ + bolt_ident_clear (&ident); + g_assert_null (ident.udev); + g_assert_null (ident.name); + g_assert_null (ident.vendor); + + /* table based individual field checks */ + check_ident (tt, host); +} + +static void +test_sysfs_host_ident (TestSysfs *tt, gconstpointer user) +{ + g_autoptr(udev_device) udevice = NULL; + g_autoptr(GError) err = NULL; + g_auto(BoltIdent) ident = BOLT_IDENT_INIT; + const char *domain; + const char *host; + const char *syspath; + gboolean ok; + MockDevId id = { + .vendor_id = 0x42, + .vendor_name = "GNOME.org", + .device_id = 0x42, + .device_name = "Laptop", + .unique_id = "884c6edd-7118-4b21-b186-b02d396ecca0", + }; + MockDevId icl = { + .vendor_id = 0, + .vendor_name = NULL, + .device_id = 0, + .device_name = NULL, + .unique_id = id.unique_id, + }; + struct + { + const char *sys_vendor; + const char *product_version; + const char *product_name; + + /* test */ + const char *vendor; + const char *name; + } dmi_ids[] = { + {"Dell Inc.", "", "XPS 13", "Dell Inc.", "XPS 13"}, + {"LENOVO", "Thinkpad", "ABCD", "Lenovo", "Thinkpad"} + }; + + domain = mock_sysfs_domain_add (tt->sysfs, BOLT_SECURITY_SECURE, NULL); + g_assert_nonnull (domain); + + host = mock_sysfs_host_add (tt->sysfs, domain, &id); + g_assert_nonnull (host); + + syspath = mock_sysfs_device_get_syspath (tt->sysfs, host); + udevice = udev_device_new_from_syspath (tt->udev, syspath); + g_assert_nonnull (udevice); + + ok = bolt_sysfs_host_ident (udevice, &ident, &err); + g_assert_no_error (err); + g_assert_true (ok); + + g_assert_true (ident.udev == udevice); + g_assert_cmpstr (id.device_name, ==, ident.name); + g_assert_cmpstr (id.vendor_name, ==, ident.vendor); + + g_clear_pointer (&udevice, udev_device_unref); + bolt_ident_clear (&ident); + + /* empty host identification and no dmi info, should fail*/ + mock_sysfs_host_remove (tt->sysfs, host); + host = mock_sysfs_host_add (tt->sysfs, domain, &icl); + g_assert_nonnull (host); + + syspath = mock_sysfs_device_get_syspath (tt->sysfs, host); + udevice = udev_device_new_from_syspath (tt->udev, syspath); + g_assert_nonnull (udevice); + + ok = bolt_sysfs_host_ident (udevice, &ident, &err); + g_assert_error (err, BOLT_ERROR, BOLT_ERROR_UDEV); + g_assert_false (ok); + g_clear_error (&err); + + /* fill in and check various dmi information */ + for (gsize i = 0; i < G_N_ELEMENTS (dmi_ids); i++) + { + const char *dmi; + + dmi = mock_sysfs_dmi_id_add (tt->sysfs, + dmi_ids[i].sys_vendor, + dmi_ids[i].product_name, + dmi_ids[i].product_version); + g_assert_nonnull (dmi); + + ok = bolt_sysfs_host_ident (udevice, &ident, &err); + g_assert_no_error (err); + g_assert_true (ok); + + g_assert_cmpstr (dmi_ids[i].name, ==, ident.name); + g_assert_cmpstr (dmi_ids[i].vendor, ==, ident.vendor); + + bolt_ident_clear (&ident); + mock_sysfs_dmi_id_remove (tt->sysfs); + } +} + +static void test_sysfs_domain_for_device (TestSysfs *tt, gconstpointer user) { g_autoptr(udev_device) udevice = NULL; @@ -107,7 +377,8 @@ &dockid, 0, NULL, - 0); + 0, + NULL); g_assert_nonnull (dock); @@ -145,6 +416,133 @@ } static void +test_sysfs_info_for_device (TestSysfs *tt, gconstpointer user) +{ + g_autoptr(udev_device) udevice = NULL; + g_autoptr(GError) err = NULL; + const char *domain; + const char *host; + const char *dock; + const char *syspath; + BoltDevInfo info; + gboolean ok; + MockDevId hostid = { + .vendor_id = 0x42, + .vendor_name = "GNOME.org", + .device_id = 0x42, + .device_name = "Laptop", + .unique_id = "884c6edd-7118-4b21-b186-b02d396ecca0", + }; + MockDevId dockid = { + .vendor_id = 0x42, + .vendor_name = "GNOME.org", + .device_id = 0x42, + .device_name = "Thunderbolt Dock", + .unique_id = "884c6edd-7118-4b21-b186-b02d396ecca1", + }; + BoltLinkSpeed ls = { + .rx.speed = 10, + .rx.lanes = 1, + .tx.speed = 20, + .tx.lanes = 2 + }; + + domain = mock_sysfs_domain_add (tt->sysfs, BOLT_SECURITY_SECURE, NULL); + g_assert_nonnull (domain); + + host = mock_sysfs_host_add (tt->sysfs, domain, &hostid); + g_assert_nonnull (host); + + dock = mock_sysfs_device_add (tt->sysfs, + host, + &dockid, + 0, + NULL, + 0, + &ls); + + + syspath = mock_sysfs_device_get_syspath (tt->sysfs, dock); + udevice = udev_device_new_from_syspath (tt->udev, syspath); + g_assert_nonnull (udevice); + + ok = bolt_sysfs_info_for_device (udevice, TRUE, &info, &err); + g_assert_no_error (err); + g_assert_true (ok); + + g_assert_true (info.full); + g_assert_cmpstr (info.parent, ==, hostid.unique_id); + g_assert_cmpstr (info.syspath, ==, syspath); + + g_assert_cmpuint (info.linkspeed.rx.speed, ==, ls.rx.speed); + g_assert_cmpuint (info.linkspeed.rx.lanes, ==, ls.rx.lanes); + g_assert_cmpuint (info.linkspeed.tx.speed, ==, ls.tx.speed); + g_assert_cmpuint (info.linkspeed.tx.lanes, ==, ls.tx.lanes); +} + +static void +test_sysfs_nhi_id_for_domain (TestSysfs *tt, gconstpointer user) +{ + guint32 nhi[] = {0x15d2, }; + + for (gsize i = 0; i < G_N_ELEMENTS (nhi); i++) + { + g_autoptr(GError) err = NULL; + g_autoptr(udev_device) udev = NULL; + const char *domain; + const char *syspath; + gboolean ok; + guint32 want = nhi[i]; + guint32 have; + + domain = mock_sysfs_domain_add (tt->sysfs, + BOLT_SECURITY_SECURE, + "nhi", want, + NULL); + g_assert_nonnull (domain); + + syspath = mock_sysfs_domain_get_syspath (tt->sysfs, domain); + g_assert_nonnull (syspath); + + udev = udev_device_new_from_syspath (tt->udev, syspath); + g_assert_nonnull (udev); + + ok = bolt_sysfs_nhi_id_for_domain (udev, &have, &err); + g_assert_no_error (err); + g_assert_true (ok); + g_assert_cmpuint (have, ==, want); + } +} + +static void +test_nhi_uuid_is_stable (TestSysfs *tt, gconstpointer user) +{ + g_autoptr(GError) err = NULL; + gboolean stable; + gboolean ok; + + /* Titan Ridge */ + ok = bolt_nhi_uuid_is_stable (0x15e8, &stable, &err); + g_assert_no_error (err); + g_assert_true (ok); + + g_assert_true (stable); + + /* Ice Lake integrated TBT */ + ok = bolt_nhi_uuid_is_stable (0x8a0d, &stable, &err); + g_assert_no_error (err); + g_assert_true (ok); + + g_assert_false (stable); + + /* missing id */ + + ok = bolt_nhi_uuid_is_stable (0x000d, &stable, &err); + g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); + g_assert_false (ok); +} + +static void test_sysfs_read_iommu (TestSysfs *tt, gconstpointer user) { const char *domain; @@ -239,7 +637,6 @@ test_sysfs_domains (TestSysfs *tt, gconstpointer user) { g_autoptr(GError) err = NULL; - const char *uid = "884c6edd-7118-4b21-b186-b02d396ecca0"; const char *ids[5]; BoltSecurity sl[5] = {BOLT_SECURITY_NONE, BOLT_SECURITY_DPONLY, @@ -251,7 +648,7 @@ BoltDomain *iter; int n; - n = bolt_sysfs_count_domains (tt->udev, &err); + n = bolt_sysfs_count_hosts (tt->udev, &err); g_assert_no_error (err); g_assert_cmpint (n, ==, 0); @@ -261,14 +658,29 @@ g_autoptr(udev_device) udevice = NULL; g_autoptr(BoltDomain) dom = NULL; /* the list will own reference */ const char *syspath; + char uid[37] = {0, }; + MockDevId hostid = { + .vendor_id = 0x42, + .vendor_name = "GNOME.org", + .device_id = 0x42, + .device_name = "Laptop", + .unique_id = (const char *) &uid, + }; + + g_snprintf (uid, sizeof (uid), "884c6edd-7118-4b21-b186-b02d396ecc%02x", + (unsigned int) i); ids[i] = mock_sysfs_domain_add (tt->sysfs, sl[i], NULL); syspath = mock_sysfs_domain_get_syspath (tt->sysfs, ids[i]); udevice = udev_device_new_from_syspath (tt->udev, syspath); + mock_sysfs_host_add (tt->sysfs, ids[i], &hostid); + g_assert_nonnull (udevice); + g_debug ("uid: %s", uid); + dom = bolt_domain_new_for_udev (udevice, uid, &err); g_assert_no_error (err); g_assert_nonnull (dom); @@ -287,7 +699,7 @@ ==, G_N_ELEMENTS (sl)); - g_assert_cmpint (bolt_sysfs_count_domains (tt->udev, NULL), + g_assert_cmpint (bolt_sysfs_count_hosts (tt->udev, NULL), ==, (int) G_N_ELEMENTS (sl)); @@ -349,7 +761,6 @@ { g_autoptr(udev_device) udevice = NULL; g_autoptr(BoltDomain) domain = NULL; - g_autoptr(GError) err = NULL; const char *uid = "884c6edd-7118-4b21-b186-b02d396ecca0"; const char *id; const char *syspath; @@ -977,7 +1388,10 @@ guint k; dir = bolt_tmp_dir_make ("bolt.sysfs.XXXXXX", NULL); - store = bolt_store_new (dir); + store = bolt_store_new (dir, &err); + + g_assert_no_error (err); + g_assert_nonnull (store); ok = bolt_store_put_domain (store, dom, &err); g_assert_no_error (err); @@ -1101,6 +1515,31 @@ } } +static void +test_check_kernel_version (TestSysfs *tt, gconstpointer user) +{ + gboolean ok; + + /* simulate read errors */ + ok = mock_sysfs_set_osrelease (tt->sysfs, NULL); + g_assert_true (ok); + g_assert_false (bolt_check_kernel_version (1, 0)); + + /* short kernel version */ + ok = mock_sysfs_set_osrelease (tt->sysfs, "1.0"); + g_assert_true (ok); + g_assert_true (bolt_check_kernel_version (1, 0)); + g_assert_false (bolt_check_kernel_version (1, 1)); + g_assert_false (bolt_check_kernel_version (2, 0)); + + /* more realistic kernel version */ + ok = mock_sysfs_set_osrelease (tt->sysfs, "1.0.0-111.fc1"); + g_assert_true (ok); + g_assert_true (bolt_check_kernel_version (1, 0)); + g_assert_false (bolt_check_kernel_version (1, 1)); + g_assert_false (bolt_check_kernel_version (2, 0)); +} + int main (int argc, char **argv) { @@ -1111,6 +1550,27 @@ bolt_dbus_ensure_resources (); + g_test_add ("/sysfs/device_get_unique_id", + TestSysfs, + NULL, + test_sysfs_setup, + test_sysfs_device_get_unique_id, + test_sysfs_tear_down); + + g_test_add ("/sysfs/device_ident", + TestSysfs, + NULL, + test_sysfs_setup, + test_sysfs_device_ident, + test_sysfs_tear_down); + + g_test_add ("/sysfs/host_ident", + TestSysfs, + NULL, + test_sysfs_setup, + test_sysfs_host_ident, + test_sysfs_tear_down); + g_test_add ("/sysfs/domain_for_device", TestSysfs, NULL, @@ -1118,6 +1578,27 @@ test_sysfs_domain_for_device, test_sysfs_tear_down); + g_test_add ("/sysfs/info_for_device", + TestSysfs, + NULL, + test_sysfs_setup, + test_sysfs_info_for_device, + test_sysfs_tear_down); + + g_test_add ("/sysfs/domain_get_nhi_id", + TestSysfs, + NULL, + test_sysfs_setup, + test_sysfs_nhi_id_for_domain, + test_sysfs_tear_down); + + g_test_add ("/sysfs/nhi_uuid_is_stable", + TestSysfs, + NULL, + test_sysfs_setup, + test_nhi_uuid_is_stable, + test_sysfs_tear_down); + g_test_add ("/sysfs/read_iommu", TestSysfs, NULL, @@ -1181,5 +1662,12 @@ test_bootacl_allocate, test_bootacl_tear_down); + g_test_add ("/self/check-kernel-version", + TestSysfs, + NULL, + test_sysfs_setup, + test_check_kernel_version, + test_sysfs_tear_down); + return g_test_run (); } diff -Nru bolt-0.8/tests/test-udev.c bolt-0.9.1/tests/test-udev.c --- bolt-0.8/tests/test-udev.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/tests/test-udev.c 2020-12-01 11:26:01.000000000 +0000 @@ -205,6 +205,7 @@ /* cleanup */ uevent_clear (&ev); + udev_device_unref (dev); } static void diff -Nru bolt-0.8/tests/test-unix.c bolt-0.9.1/tests/test-unix.c --- bolt-0.8/tests/test-unix.c 2019-06-13 22:04:55.000000000 +0000 +++ bolt-0.9.1/tests/test-unix.c 2020-12-01 11:26:01.000000000 +0000 @@ -21,23 +21,20 @@ #include "config.h" #include "bolt-unix.h" - #include "bolt-io.h" #include "bolt-macros.h" +#include "bolt-names.h" #include "bolt-str.h" #include "bolt-test.h" -#include #include -#include #include #include #include -#include #include -#include -#include /* unlinkat, fork */ +#include /* waitpid */ +#include /* fork */ typedef struct TestDummy { @@ -74,159 +71,19 @@ typedef struct TestNotify { - BoltTmpDir tmpdir; - char *socket_path; - guint socket_watch; - int socket_fd; - - /* */ - guint counter; - GQueue messages; + NotifySocket *ns; } TestNotify; -union ctrlmsg -{ - struct cmsghdr hdr; - guint8 buf[CMSG_SPACE (sizeof (struct ucred))]; -}; - -static char * -test_notify_revmsg (TestNotify *tt, gboolean queue) -{ - char data[4096]; - char *msg; - struct iovec iov = { - .iov_base = data, - .iov_len = sizeof (data) - 1, - }; - union ctrlmsg crtl = {}; - struct msghdr hdr = { - .msg_iov = &iov, - .msg_iovlen = 1, - .msg_control = &crtl, - .msg_controllen = sizeof (crtl), - }; - struct ucred *ucred = NULL; - ssize_t r; - - /* MSG_TRUNC: return the real size */ - r = recvmsg (tt->socket_fd, &hdr, MSG_DONTWAIT | MSG_CMSG_CLOEXEC | MSG_TRUNC); - - if (r < 0) - { - if (errno == EINTR || errno == EAGAIN) - return NULL; - - g_critical ("i/o error reading from notify socket: %m"); - return NULL; - } - - if (hdr.msg_flags & MSG_TRUNC || ((size_t) r > sizeof (data) - 1)) - { - g_warning ("notification message truncated"); - return NULL; - } - - g_assert_cmpint (r, <, sizeof (data)); - - data[r] = '\0'; - - tt->counter++; - msg = g_strdup (data); - - for (struct cmsghdr *c = CMSG_FIRSTHDR (&hdr); - c != NULL; - c = CMSG_NXTHDR (&hdr, c)) - { - if (c->cmsg_level != SOL_SOCKET) - continue; - if (c->cmsg_type == SCM_CREDENTIALS && - c->cmsg_len == CMSG_LEN (sizeof (struct ucred))) - ucred = (struct ucred *) (void *) CMSG_DATA (c); - } - - if (queue) - g_queue_push_tail (&tt->messages, msg); - - g_debug ("got message: '%s' [%s]", msg, bolt_yesno (queue)); - if (ucred != NULL) - g_debug (" ucred, pid: %i, uid: %li, gid: %li", - (int) ucred->pid, (long) ucred->uid, (long) ucred->gid); - - return msg; -} - -static gboolean -got_notification (gpointer user_data) -{ - TestNotify *tt = (TestNotify *) user_data; - - test_notify_revmsg (tt, TRUE); - - return TRUE; -} - static void test_notify_setup (TestNotify *tt, gconstpointer data) { - g_autoptr(GError) err = NULL; - g_autoptr(GSource) source = NULL; - bolt_autoclose int fd = -1; - static const int one = 1; - struct sockaddr_un sau = {AF_UNIX, {'\0', }}; - size_t socklen; - int r; - - tt->tmpdir = bolt_tmp_dir_make ("bolt.unix.XXXXXX", &err); - g_assert_no_error (err); - g_assert_nonnull (tt->tmpdir); - - fd = socket (AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); - g_assert_cmpint (fd, >, -1); - - tt->socket_path = g_build_filename (tt->tmpdir, "notify_socket", NULL); - - strncpy (sau.sun_path, tt->socket_path, sizeof (sau.sun_path) - 1); - - socklen = - offsetof (struct sockaddr_un, sun_path) - + strlen (sau.sun_path) - + 1; - - r = bind (fd, &sau, socklen); - g_assert_cmpint (r, >, -1); - - r = setsockopt (fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof (one)); - g_assert_cmpint (r, >, -1); - - source = g_unix_fd_source_new (fd, G_IO_IN); - g_assert_nonnull (source); - - g_source_set_callback (source, got_notification, tt, NULL); - tt->socket_watch = g_source_attach (source, NULL); - - tt->socket_fd = bolt_steal (&fd, -1); - g_queue_init (&tt->messages); - - g_debug ("notification socket at '%s'", sau.sun_path); + tt->ns = notify_socket_new (); } static void test_notify_teardown (TestNotify *tt, gconstpointer user) { - g_autoptr(GError) err = NULL; - - g_clear_handle_id (&tt->socket_watch, g_source_remove); - - if (tt->socket_fd > -1) - { - bolt_close (tt->socket_fd, &err); - g_assert_no_error (err); - tt->socket_fd = -1; - } - - g_clear_pointer (&tt->tmpdir, bolt_tmp_dir_destroy); - g_queue_free_full (&tt->messages, g_free); + g_clear_pointer (&tt->ns, notify_socket_free); } static void @@ -249,7 +106,7 @@ g_assert_false (sent); /* invalid/unsupported destinations */ - g_setenv ("NOTIFY_SOCKET", "INVALID SOCKET", TRUE); + g_setenv (BOLT_SD_NOTIFY_SOCKET, "INVALID SOCKET", TRUE); ok = bolt_sd_notify_literal (ref, &sent, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); g_assert_false (ok); @@ -261,7 +118,7 @@ g_assert_cmpuint (l, <, 1024); verylong = g_strnfill (l, 'a'); - g_setenv ("NOTIFY_SOCKET", verylong, TRUE); + g_setenv (BOLT_SD_NOTIFY_SOCKET, verylong, TRUE); ok = bolt_sd_notify_literal (ref, &sent, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); g_assert_false (ok); @@ -269,7 +126,7 @@ g_clear_error (&err); /* peer does not exist */ - g_setenv ("NOTIFY_SOCKET", "@NONEXISTANTABSTRACT", TRUE); + g_setenv (BOLT_SD_NOTIFY_SOCKET, "@NONEXISTANTABSTRACT", TRUE); ok = bolt_sd_notify_literal (ref, &sent, &err); g_assert_error (err, G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED); g_assert_false (ok); @@ -277,19 +134,56 @@ g_clear_error (&err); /* finally the VALID socket */ - g_setenv ("NOTIFY_SOCKET", tt->socket_path, TRUE); + notify_socket_set_environment (tt->ns); ok = bolt_sd_notify_literal (ref, &sent, &err); g_assert_no_error (err); g_assert_true (ok); g_assert_true (sent); - msg = test_notify_revmsg (tt, FALSE); + msg = notify_socket_revmsg (tt->ns, FALSE); g_assert_nonnull (msg); g_assert_cmpstr (msg, ==, ref); g_free (msg); } +static void +test_sd_watchdog_enabled (TestDummy *tt, gconstpointer user_data) +{ + g_autoptr(GError) err = NULL; + g_autofree char *tmp = NULL; + guint64 val; + int r; + + /* no env variable */ + g_assert_null (g_getenv (BOLT_SD_WATCHDOG_USEC)); + + r = bolt_sd_watchdog_enabled (&val, &err); + g_assert_no_error (err); + g_assert_cmpint (r, ==, 0); + + /* empty env variable [error] */ + g_setenv (BOLT_SD_WATCHDOG_USEC, "", TRUE); + r = bolt_sd_watchdog_enabled (&val, &err); + g_assert_nonnull (err); + g_assert_cmpint (r, <, 0); + g_clear_error (&err); + + /* invalid env variable [error] */ + g_setenv (BOLT_SD_WATCHDOG_USEC, "NOT-A-NUMBER", TRUE); + r = bolt_sd_watchdog_enabled (&val, &err); + g_assert_nonnull (err); + g_assert_cmpint (r, <, 0); + g_clear_error (&err); + + /* valid number, finally */ + tmp = g_strdup_printf ("%d", 42 * G_USEC_PER_SEC); + g_setenv (BOLT_SD_WATCHDOG_USEC, tmp, TRUE); + r = bolt_sd_watchdog_enabled (&val, &err); + g_assert_no_error (err); + g_assert_cmpint (r, >, 0); +} + int main (int argc, char **argv) { @@ -311,5 +205,12 @@ test_sd_notify, test_notify_teardown); + g_test_add ("/common/unix/sd_watchdog_enabled", + TestDummy, + NULL, + NULL, + test_sd_watchdog_enabled, + NULL); + return g_test_run (); } diff -Nru bolt-0.8/tests/test-watchdog.c bolt-0.9.1/tests/test-watchdog.c --- bolt-0.8/tests/test-watchdog.c 1970-01-01 00:00:00.000000000 +0000 +++ bolt-0.9.1/tests/test-watchdog.c 2020-12-01 11:26:01.000000000 +0000 @@ -0,0 +1,246 @@ +/* + * Copyright © 2019 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Christian J. Kellner + */ + +#include "config.h" + +#include "bolt-watchdog.h" + +#include "bolt-names.h" +#include "bolt-test.h" +#include "bolt-time.h" + +#include + +typedef struct TestWatchdog +{ + NotifySocket *ns; + + /* */ + guint64 timeout; /* in usec */ + char *timestr; + + /* */ + GArray *pulses; + + /* */ + GCancellable *cancel; +} TestWatchdog; + +static void +test_notify_setup (TestWatchdog *tt, gconstpointer data) +{ + tt->ns = notify_socket_new (); + g_assert_nonnull (tt->ns); + + tt->timeout = 6 * G_USEC_PER_SEC; + tt->timestr = g_strdup_printf ("%" G_GUINT64_FORMAT, tt->timeout); + + tt->pulses = g_array_new (FALSE, FALSE, sizeof (guint64)); + tt->cancel = g_cancellable_new (); +} + +static void +test_notify_teardown (TestWatchdog *tt, gconstpointer user) +{ + g_clear_pointer (&tt->ns, notify_socket_free); + g_clear_pointer (&tt->timestr, g_free); + g_clear_pointer (&tt->pulses, g_array_unref); + g_clear_object (&tt->cancel); +} + + +static void +test_watchdog_basic (TestWatchdog *tt, gconstpointer user_data) +{ + g_autoptr(GError) err = NULL; + g_autoptr(BoltWatchdog) dog = NULL; + guint64 timeout = 0; + guint pulse; + + /* watchdog env not set */ + dog = bolt_watchdog_new (&err); + g_assert_no_error (err); + g_assert_nonnull (dog); + + g_object_get (dog, + "timeout", &timeout, + "pulse", &pulse, + NULL); + + g_assert_cmpuint (timeout, ==, 0); + g_assert_cmpuint (pulse, ==, 0); + + /* invalid watchdog env */ + g_setenv (BOLT_SD_WATCHDOG_USEC, "INVALID", TRUE); + + g_clear_object (&dog); + + dog = bolt_watchdog_new (&err); + g_assert_error (err, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); + g_assert_null (dog); + g_clear_error (&err); + g_clear_object (&dog); + + /* now with some actual valid socket, watchdog */ + notify_socket_set_environment (tt->ns); + g_setenv (BOLT_SD_WATCHDOG_USEC, tt->timestr, TRUE); + + g_clear_object (&dog); + dog = bolt_watchdog_new (&err); + g_assert_no_error (err); + g_assert_nonnull (dog); + + g_object_get (dog, "timeout", &timeout, NULL); + g_assert_cmpuint (timeout, ==, tt->timeout); + + g_object_get (dog, "pulse", &pulse, NULL); + g_assert_cmpuint (pulse, ==, timeout / 2 / G_USEC_PER_SEC); +} + +static gpointer +test_watchdog_receiver (gpointer data) +{ + TestWatchdog *tt = data; + GPollFD fds[2]; + gboolean ok; + + notify_socket_make_pollfd (tt->ns, &fds[0]); + + ok = g_cancellable_make_pollfd (tt->cancel, &fds[1]); + g_assert_true (ok); + + while (!g_cancellable_is_cancelled (tt->cancel)) + { + const char *msg; + guint64 now; + gint r; + + r = g_poll (fds, G_N_ELEMENTS (fds), -1); + g_assert_cmpint (r, >, -1); + + if (!fds[0].revents) + continue; + + msg = notify_socket_revmsg (tt->ns, TRUE); + + if (!g_str_has_prefix (msg, "WATCHDOG")) + continue; + + now = bolt_now_in_seconds (); + g_array_append_val (tt->pulses, now); + + g_debug ("%" G_GUINT64_FORMAT ": pulse received", now); + + fds[0].revents = fds[1].revents = 0; + } + + return NULL; +} + +static gboolean +on_timeout_warn_quit_loop (gpointer user_data) +{ + GMainLoop *loop = user_data; + + g_main_loop_quit (loop); + + return G_SOURCE_REMOVE; +} + +static void +test_watchdog_timeout (TestWatchdog *tt, gconstpointer user_data) +{ + g_autoptr(GError) err = NULL; + g_autoptr(BoltWatchdog) dog = NULL; + g_autoptr(GMainLoop) loop = NULL; + GThread *thread; + guint64 timeout = 0; + guint pulse; + guint n = 10; + + skip_test_unless (g_test_slow (), "slow tests disabled"); + + notify_socket_set_environment (tt->ns); + g_setenv (BOLT_SD_WATCHDOG_USEC, tt->timestr, TRUE); + + dog = bolt_watchdog_new (&err); + g_assert_no_error (err); + g_assert_nonnull (dog); + + g_object_get (dog, "timeout", &timeout, NULL); + g_assert_cmpuint (timeout, ==, tt->timeout); + + g_object_get (dog, "pulse", &pulse, NULL); + g_assert_cmpuint (pulse, ==, timeout / 2 / G_USEC_PER_SEC); + + thread = g_thread_new ("NotifySocket", test_watchdog_receiver, tt); + + loop = g_main_loop_new (NULL, FALSE); + + /* we wait for 11 pulses */ + g_timeout_add_seconds (pulse * n + 1, on_timeout_warn_quit_loop, loop); + + /* now we wait */ + g_main_loop_run (loop); + + /* stop the background thread */ + g_cancellable_cancel (tt->cancel); + + /* we got here because the timeout kicked in */ + g_assert_cmpuint (tt->pulses->len, >=, n); + + for (guint i = 1; i < n; i++) + { + guint64 before = g_array_index (tt->pulses, guint64, i - 1); + guint64 after = g_array_index (tt->pulses, guint64, i); + gint64 diff = after - before; + + g_debug ("%u - %u: %" G_GINT64_FORMAT "s", i, i - 1, diff); + g_assert_cmpint (diff, <, tt->timeout / G_USEC_PER_SEC); + + } + + (void) g_thread_join (thread); +} + + +int +main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + + g_test_init (&argc, &argv, NULL); + + g_test_add ("/boltd/watchdog/basic", + TestWatchdog, + NULL, + test_notify_setup, + test_watchdog_basic, + test_notify_teardown); + + g_test_add ("/boltd/watchdog/timeout", + TestWatchdog, + NULL, + test_notify_setup, + test_watchdog_timeout, + test_notify_teardown); + + return g_test_run (); +} diff -Nru bolt-0.8/tests/test-wire.c bolt-0.9.1/tests/test-wire.c --- bolt-0.8/tests/test-wire.c 1970-01-01 00:00:00.000000000 +0000 +++ bolt-0.9.1/tests/test-wire.c 2020-12-01 11:26:01.000000000 +0000 @@ -0,0 +1,80 @@ +/* + * Copyright © 2019 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Christian J. Kellner + */ + +#include "config.h" + +#include "bolt-wire.h" + +#include "bolt-error.h" +#include "test-enums.h" +#include "bolt-test-resources.h" + +#include +#include +#include + +#include + +typedef struct TestWire +{ + int dummy; +} TestWire; + +static void +test_linkspeed_basic (TestWire *tt, gconstpointer data) +{ + BoltLinkSpeed a = { + .rx.speed = 10, + .rx.lanes = 1, + .tx.speed = 20, + .tx.lanes = 2 + }; + BoltLinkSpeed b = { + .rx.speed = 20, + .rx.lanes = 2, + .tx.speed = 10, + .tx.lanes = 1 + }; + g_autofree BoltLinkSpeed *c = NULL; + + g_assert_false (bolt_link_speed_equal (&a, &b)); + b = a; + g_assert_true (bolt_link_speed_equal (&a, &b)); + + c = bolt_link_speed_copy (&a); + g_assert_true (bolt_link_speed_equal (&a, c)); +} + +int +main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + + g_test_init (&argc, &argv, NULL); + + g_test_add ("/common/linkseed/basic", + TestWire, + NULL, + NULL, + test_linkspeed_basic, + NULL); + + return g_test_run (); +}