1 // The manager binary (btmanagerd) is a fairly barebone bin file that depends on the manager_service
2 // library which implements most of the logic. The code is separated in this way so that we can
3 // apply certain linker flags (which is applied to the library but not the binary).
4 // Please keep main.rs logic light and write the heavy logic in the manager_service library instead.
5 
6 use clap::{App, Arg};
7 use dbus::channel::MatchingReceiver;
8 use dbus::message::MatchRule;
9 use dbus_crossroads::Crossroads;
10 use dbus_projection::DisconnectWatcher;
11 use dbus_tokio::connection;
12 use log::LevelFilter;
13 use manager_service::bluetooth_manager::BluetoothManager;
14 use manager_service::powerd_suspend_manager::PowerdSuspendManager;
15 use manager_service::{bluetooth_experimental_dbus, iface_bluetooth_manager};
16 use manager_service::{bluetooth_manager_dbus, config_util, state_machine};
17 use std::sync::{Arc, Mutex};
18 use syslog::{BasicLogger, Facility, Formatter3164};
19 
20 #[tokio::main]
main() -> Result<(), Box<dyn std::error::Error>>21 pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
22     let matches = App::new("Bluetooth Manager")
23         .arg(Arg::with_name("systemd").long("systemd").help("If btadapterd uses systemd init"))
24         .arg(Arg::with_name("debug").long("debug").short("d").help("Enables debug level logs"))
25         .arg(
26             Arg::with_name("log-output")
27                 .long("log-output")
28                 .takes_value(true)
29                 .possible_values(&["syslog", "stderr"])
30                 .default_value("syslog")
31                 .help("Select log output"),
32         )
33         .get_matches();
34 
35     let is_debug = matches.is_present("debug");
36     let is_systemd = matches.is_present("systemd");
37 
38     let level_filter = if is_debug { LevelFilter::Debug } else { LevelFilter::Info };
39 
40     let log_output = matches.value_of("log-output").unwrap_or("syslog");
41 
42     if log_output == "stderr" {
43         env_logger::Builder::new().filter(None, level_filter).init();
44     } else {
45         // syslog is the default log output.
46         let formatter = Formatter3164 {
47             facility: Facility::LOG_USER,
48             hostname: None,
49             process: "btmanagerd".into(),
50             pid: 0,
51         };
52 
53         let logger = syslog::unix(formatter).expect("could not connect to syslog");
54         let _ = log::set_boxed_logger(Box::new(BasicLogger::new(logger)))
55             .map(|()| log::set_max_level(config_util::get_log_level().unwrap_or(level_filter)));
56     }
57 
58     // Initialize config util
59     config_util::fix_config_file_format();
60 
61     // Connect to the D-Bus system bus (this is blocking, unfortunately).
62     let (resource, conn) = connection::new_system_sync()?;
63 
64     // There are multiple signal handlers. We need to set signal match mode to true to allow signal
65     // handlers process the signals independently. Otherwise only the first signal handler will get
66     // the chance to handle the same signal in case the match rule overlaps.
67     conn.set_signal_match_mode(true);
68 
69     // Determine whether to use upstart or systemd
70     let invoker = if is_systemd {
71         state_machine::Invoker::SystemdInvoker
72     } else {
73         state_machine::Invoker::UpstartInvoker
74     };
75 
76     let context = state_machine::create_new_state_machine_context(invoker);
77     let proxy = context.get_proxy();
78 
79     // The resource is a task that should be spawned onto a tokio compatible
80     // reactor ASAP. If the resource ever finishes, you lost connection to D-Bus.
81     tokio::spawn(async {
82         let err = resource.await;
83         panic!("Lost connection to D-Bus: {}", err);
84     });
85 
86     // Let's request a name on the bus, so that clients can find us.
87     conn.request_name("org.chromium.bluetooth.Manager", false, true, false).await?;
88     log::debug!("D-Bus name: {}", conn.unique_name());
89 
90     // Create a new crossroads instance.
91     // The instance is configured so that introspection and properties interfaces
92     // are added by default on object path additions.
93     let cr = Arc::new(Mutex::new(Crossroads::new()));
94 
95     // Enable async support for the crossroads instance.
96     cr.lock().unwrap().set_async_support(Some((
97         conn.clone(),
98         Box::new(|x| {
99             tokio::spawn(x);
100         }),
101     )));
102 
103     // Object manager is necessary for clients (to inform them when Bluetooth is
104     // available). Create it at root (/) so subsequent additions generate
105     // InterfaceAdded and InterfaceRemoved signals.
106     cr.lock().unwrap().set_object_manager_support(Some(conn.clone()));
107     let om = cr.lock().unwrap().object_manager();
108     cr.lock().unwrap().insert("/", &[om], {});
109 
110     let bluetooth_manager = Arc::new(Mutex::new(Box::new(BluetoothManager::new(proxy))));
111 
112     // Set up the disconnect watcher to monitor client disconnects.
113     let disconnect_watcher = Arc::new(Mutex::new(DisconnectWatcher::new()));
114     disconnect_watcher.lock().unwrap().setup_watch(conn.clone()).await;
115 
116     // We add the Crossroads instance to the connection so that incoming method calls will be
117     // handled.
118     // This must be done before exporting interfaces so that clients that rely on InterfacesAdded
119     // signal can rely on us being ready to handle methods on those exported interfaces.
120     let cr_clone = cr.clone();
121     conn.start_receive(
122         MatchRule::new_method_call(),
123         Box::new(move |msg, conn| {
124             cr_clone.lock().unwrap().handle_message(msg, conn).unwrap();
125             true
126         }),
127     );
128 
129     // Let's add the "/org/chromium/bluetooth/Manager" path, which implements
130     // the org.chromium.bluetooth.Manager interface, to the crossroads instance.
131     let iface = bluetooth_manager_dbus::export_bluetooth_manager_dbus_intf(
132         conn.clone(),
133         &mut cr.lock().unwrap(),
134         disconnect_watcher.clone(),
135     );
136 
137     // Let's add the "/org/chromium/bluetooth/Experimental" path, which implements
138     // the org.chromium.bluetooth.Experimental interface, to the crossroads instance
139     let iface_exp = bluetooth_experimental_dbus::export_bluetooth_experimental_dbus_intf(
140         conn.clone(),
141         &mut cr.lock().unwrap(),
142         disconnect_watcher.clone(),
143     );
144 
145     // Create mixin object for Manager + Experimental interfaces.
146     let mixin = Box::new(iface_bluetooth_manager::BluetoothManagerMixin {
147         manager: bluetooth_manager.clone(),
148         experimental: bluetooth_manager.clone(),
149     });
150 
151     cr.lock().unwrap().insert("/org/chromium/bluetooth/Manager", &[iface, iface_exp], mixin);
152 
153     let mut powerd_suspend_manager = PowerdSuspendManager::new(conn.clone(), cr);
154 
155     bluetooth_manager
156         .lock()
157         .unwrap()
158         .set_suspend_manager_context(powerd_suspend_manager.get_suspend_manager_context());
159 
160     tokio::spawn(async move {
161         powerd_suspend_manager.init().await;
162         powerd_suspend_manager.mainloop().await;
163     });
164 
165     tokio::spawn(async move {
166         state_machine::mainloop(context, bluetooth_manager).await;
167     });
168 
169     std::future::pending::<()>().await;
170 
171     // Run forever.
172     unreachable!()
173 }
174