ippusb_bridge: Defer IPP-over-USB setup when screen is locked

ippusb_bridge is started by udev when a printer supporting IPP-over-USB
is connected to the USB port. Unfortunately, it fails when access to
USB is blocked by usbguard. It happens when the printer is connected
after user locked the screen. The goal of this CL is to detect this
kind of situation and defer the configuration of IPP-over-USB
connection till access to USB ports is restored.
Now, when access to USB is denied, ippusb_bridge waits for the signal
propagated on DBus informing that usbguard is deactivated. Then it
tries to repeat configuration of IPP-over-USB connection.

BUG=b:331433231
TEST=manually on octopus and a printer HP ENVY Inspire 7900

Change-Id: Id73d6433d694d579ed7f9a51bbf7c7ad19ef7f78
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/5528312
Tested-by: Piotr Pawliczek <pawliczek@chromium.org>
Reviewed-by: Paul Moy <pmoy@chromium.org>
Commit-Queue: Piotr Pawliczek <pawliczek@chromium.org>
Reviewed-by: Allen Webb <allenwebb@google.com>
diff --git a/ippusb_bridge/Cargo.toml b/ippusb_bridge/Cargo.toml
index d27bb38..bf72401 100644
--- a/ippusb_bridge/Cargo.toml
+++ b/ippusb_bridge/Cargo.toml
@@ -7,6 +7,7 @@
 [dependencies]
 ascii = "1.0.0"
 chunked_transfer = "1"
+dbus = "0.9"
 getopts = "0.2.18"
 httparse = "1.3.4"
 libc = "0.2.44"
diff --git a/ippusb_bridge/minijail/ippusb-bridge.conf b/ippusb_bridge/minijail/ippusb-bridge.conf
index ebe3e76..30eed77 100644
--- a/ippusb_bridge/minijail/ippusb-bridge.conf
+++ b/ippusb_bridge/minijail/ippusb-bridge.conf
@@ -24,3 +24,4 @@
 bind-mount = /run/udev
 bind-mount = /run/ippusb,/run/ippusb,1
 mount = var,/var,tmpfs,MS_NOSUID|MS_NODEV|MS_NOEXEC
+bind-mount = /run/dbus
diff --git a/ippusb_bridge/src/main.rs b/ippusb_bridge/src/main.rs
index e68e97c..1fb2db5 100644
--- a/ippusb_bridge/src/main.rs
+++ b/ippusb_bridge/src/main.rs
@@ -18,6 +18,7 @@
 use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
 use std::time::Duration;
 
+use dbus::blocking::Connection;
 use libchromeos::deprecated::{EventFd, PollContext, PollToken};
 use libchromeos::signal::register_signal_handler;
 use libchromeos::syslog;
@@ -29,11 +30,13 @@
 use crate::http::handle_request;
 use crate::listeners::{Accept, ScopedUnixListener};
 use crate::usb_connector::{UnplugDetector, UsbConnector};
+use crate::usb_connector::Error::ReadConfigDescriptor;
 
 #[derive(Debug)]
 pub enum Error {
     CreateSocket(io::Error),
     CreateUsbConnector(usb_connector::Error),
+    DBus(dbus::Error),
     EventFd(io::Error),
     ParseArgs(arguments::Error),
     PollEvents(nix::Error),
@@ -50,6 +53,7 @@
         match self {
             CreateSocket(err) => write!(f, "Failed to create socket: {}", err),
             CreateUsbConnector(err) => write!(f, "Failed to create USB connector: {}", err),
+            DBus(err) => write!(f, "DBus error: {}", err),
             EventFd(err) => write!(f, "Failed to create/duplicate EventFd: {}", err),
             ParseArgs(err) => write!(f, "Failed to parse arguments: {}", err),
             PollEvents(err) => write!(f, "Failed to poll for events: {}", err),
@@ -223,8 +227,36 @@
         Box::new(TcpListener::bind(host).map_err(Error::CreateSocket)?)
     };
 
-    let usb =
-        UsbConnector::new(args.verbose_log, args.bus_device).map_err(Error::CreateUsbConnector)?;
+    // Start up a connection to the system bus.
+    // We need to listen to a signal sent when access to USB is restored. It
+    // will be needed if access to USB is blocked by usbguard.
+    let mut dbus_rule =  dbus::message::MatchRule::new();
+    dbus_rule.path = Some("/com/ubuntu/Upstart/jobs/usbguard_2don_2dunlock".into());
+    dbus_rule.interface = Some("com.ubuntu.Upstart0_6.Job".into());
+    dbus_rule.member = Some("InstanceRemoved".into());
+    let dbus_conn = Connection::new_system().map_err(Error::DBus)?;
+    dbus_conn.add_match(dbus_rule, |_: (), _, _msg : &dbus::Message| {
+        info!("Received signal that access to USB was restored.");
+        true
+    }).map_err(Error::DBus)?;
+    // This is run to make sure there are no cached signals from DBus.
+    while dbus_conn.process(Duration::ZERO).map_err(Error::DBus)? {};
+
+    // Try to create UsbConnector in loop until it is created successfully or
+    // an unexpected error occurs. ReadConfigDescriptor error means that access
+    // to USB is blocked by usbguard (e.g.: the screen is locked).
+    let usb : UsbConnector;
+    loop {
+        match UsbConnector::new(args.verbose_log, args.bus_device) {
+            Ok(obj) => { usb = obj; break }
+            Err(ReadConfigDescriptor(..)) => {},
+            Err(err) => return Err(Error::CreateUsbConnector(err))
+        };
+        info!("Failed to create USB connector. Waiting for the signal from DBus.");
+        dbus_conn.process(Duration::MAX).map_err(Error::DBus)?;
+    }
+    info!("USB connector created successfully.");
+
     let unplug_shutdown_fd = shutdown_fd.try_clone().map_err(Error::EventFd)?;
     let _unplug = UnplugDetector::new(
         usb.device(),