Skip to content

Commit e2bdc3f

Browse files
committed
dbus: rauc: system_conf: write runtime RAUC config with poll section
This configures RAUC to poll for updates on our behalf. We do not use the information yet or enable automatic installation but those are next steps. We also need to trigger RAUC to re-read the file.
1 parent bd41a30 commit e2bdc3f

File tree

3 files changed

+155
-15
lines changed

3 files changed

+155
-15
lines changed

src/dbus/rauc.rs

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818
use std::collections::HashMap;
1919

2020
use anyhow::Result;
21-
use async_std::channel::Receiver;
2221
use async_std::stream::StreamExt;
2322
use async_std::sync::Arc;
23+
use futures_util::FutureExt;
2424
use log::warn;
2525
use serde::{Deserialize, Serialize};
2626

@@ -31,6 +31,9 @@ use crate::watched_tasks::WatchedTasksBuilder;
3131
mod update_channels;
3232
pub use update_channels::{Channel, Channels};
3333

34+
mod system_conf;
35+
use system_conf::update_system_conf;
36+
3437
#[cfg(feature = "demo_mode")]
3538
mod demo_mode;
3639

@@ -119,7 +122,6 @@ pub struct Rauc {
119122
pub channels: Arc<Topic<Channels>>,
120123
pub reload: Arc<Topic<bool>>,
121124
pub should_reboot: Arc<Topic<bool>>,
122-
#[allow(dead_code)]
123125
pub enable_polling: Arc<Topic<bool>>,
124126
}
125127

@@ -183,13 +185,26 @@ fn would_reboot_into_other_slot(slot_status: &SlotStatus, primary: Option<String
183185
}
184186

185187
async fn channel_list_update_task(
186-
mut reload_stream: Receiver<bool>,
188+
reload: Arc<Topic<bool>>,
189+
enable_polling: Arc<Topic<bool>>,
187190
channels: Arc<Topic<Channels>>,
188191
) -> Result<()> {
189-
while let Some(reload) = reload_stream.next().await {
190-
if !reload {
191-
continue;
192-
}
192+
let (reload_stream, _) = reload.subscribe_unbounded();
193+
let (mut enable_polling_stream, _) = enable_polling.subscribe_unbounded();
194+
195+
let mut enable_polling = enable_polling_stream.next().await.unwrap_or(false);
196+
197+
'reload_loop: loop {
198+
futures::select! {
199+
reload = reload_stream.recv().fuse() => {
200+
if !(reload?) {
201+
continue 'reload_loop
202+
}
203+
}
204+
enable_polling_new = enable_polling_stream.recv().fuse() => {
205+
enable_polling = enable_polling_new?;
206+
}
207+
};
193208

194209
// Read the list of available update channels
195210
let new_channels = match Channels::from_directory(CHANNELS_DIR) {
@@ -200,10 +215,10 @@ async fn channel_list_update_task(
200215
}
201216
};
202217

218+
update_system_conf(new_channels.primary(), enable_polling)?;
219+
203220
channels.set(new_channels);
204221
}
205-
206-
Ok(())
207222
}
208223

209224
impl Rauc {
@@ -242,10 +257,13 @@ impl Rauc {
242257
inst.last_error.set("".to_string());
243258

244259
// Reload the channel list on request
245-
let (reload_stream, _) = inst.reload.clone().subscribe_unbounded();
246260
wtb.spawn_task(
247261
"rauc-channel-list-update",
248-
channel_list_update_task(reload_stream, inst.channels.clone()),
262+
channel_list_update_task(
263+
inst.reload.clone(),
264+
inst.enable_polling.clone(),
265+
inst.channels.clone(),
266+
),
249267
)?;
250268

251269
Ok(inst)
@@ -444,11 +462,14 @@ impl Rauc {
444462
Ok(())
445463
})?;
446464

447-
// Reload the channel list on request
448-
let (reload_stream, _) = inst.reload.clone().subscribe_unbounded();
465+
// Reload the channel list when required
449466
wtb.spawn_task(
450467
"rauc-channel-list-update",
451-
channel_list_update_task(reload_stream, inst.channels.clone()),
468+
channel_list_update_task(
469+
inst.reload.clone(),
470+
inst.enable_polling.clone(),
471+
inst.channels.clone(),
472+
),
452473
)?;
453474

454475
Ok(inst)

src/dbus/rauc/system_conf.rs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
use std::fmt::Write;
2+
use std::fs::{create_dir_all, read_to_string, remove_file, rename, write};
3+
use std::io::{Error, ErrorKind};
4+
use std::path::Path;
5+
6+
use super::Channel;
7+
8+
use log::info;
9+
10+
#[cfg(feature = "demo_mode")]
11+
mod imports {
12+
pub(super) const STATIC_CONF_PATH: &str = "demo_files/usr/lib/rauc/system.conf";
13+
pub(super) const DYNAMIC_CONF_PATH: &str = "demo_files/run/rauc/system.conf";
14+
}
15+
16+
#[cfg(not(feature = "demo_mode"))]
17+
mod imports {
18+
pub(super) const STATIC_CONF_PATH: &str = "/usr/lib/rauc/system.conf";
19+
pub(super) const DYNAMIC_CONF_PATH: &str = "/run/rauc/system.conf";
20+
}
21+
22+
use imports::*;
23+
24+
const MAGIC_LINE: &str = "\n# <tacd-poll-section>\n";
25+
26+
fn poll_section(
27+
primary_channel: Option<&Channel>,
28+
polling: bool,
29+
) -> Result<Option<String>, std::fmt::Error> {
30+
// If no primary channel is configured or if polling is not enabled,
31+
// then we do not need a `[poll]` section at all.
32+
let primary_channel = match (primary_channel, polling) {
33+
(Some(pc), true) => pc,
34+
_ => return Ok(None),
35+
};
36+
37+
let mut section = String::new();
38+
39+
writeln!(&mut section)?;
40+
writeln!(&mut section, "[poll]")?;
41+
writeln!(&mut section, "source={}", primary_channel.url)?;
42+
43+
if let Some(interval) = primary_channel.polling_interval {
44+
writeln!(&mut section, "interval-sec={}", interval.as_secs())?;
45+
}
46+
47+
writeln!(&mut section, "candidate-criteria=different-version")?;
48+
49+
Ok(Some(section))
50+
}
51+
52+
pub fn update_system_conf(
53+
primary_channel: Option<&Channel>,
54+
enable_polling: bool,
55+
) -> std::io::Result<bool> {
56+
let dynamic_conf = {
57+
match poll_section(primary_channel, enable_polling) {
58+
Ok(Some(ps)) => {
59+
// We use the config in /etc as a template ...
60+
let static_conf = read_to_string(STATIC_CONF_PATH)?;
61+
62+
// ... and replace the line `# <tacd-poll-section>` with our
63+
// generated `[poll]` section.
64+
let dc = static_conf.replacen(MAGIC_LINE, &ps, 1);
65+
66+
// The user may have decided not to include a `# <tacd-poll-section>`
67+
// line. In that case we do not need a dynamic config at all.
68+
if dc == static_conf {
69+
info!(
70+
"Rauc config {} did not contain magic line '{}'. Will not generate poll section.",
71+
STATIC_CONF_PATH, MAGIC_LINE
72+
);
73+
74+
None
75+
} else {
76+
Some(dc)
77+
}
78+
}
79+
_ => None,
80+
}
81+
};
82+
83+
/* Do we need a dynamic config in /run/rauc?
84+
*
85+
* If so, then is it actually different from what we already have?
86+
* If not, then there is no need to restart the daemon.
87+
* If it is, we write the new one and signal the need for a daemon
88+
* restart.
89+
*
90+
* If we do not need dynamic config, then try to delete the previous one.
91+
* If there was none, then the deamon does not have to be restarted.
92+
* If there was a dynamic config before, then we need to restart the
93+
* daemon.
94+
*/
95+
match dynamic_conf {
96+
Some(new) => match read_to_string(DYNAMIC_CONF_PATH) {
97+
Ok(old) if old == new => Ok(false),
98+
Err(err) if err.kind() != ErrorKind::NotFound => Err(err),
99+
Ok(_) | Err(_) => {
100+
let dynamic_conf_dir = Path::new(DYNAMIC_CONF_PATH)
101+
.parent()
102+
.ok_or_else(|| Error::other("Invalid dynamic config path"))?;
103+
104+
let tmp_path = dynamic_conf_dir.join("system.conf.tacd-tmp");
105+
106+
create_dir_all(dynamic_conf_dir)?;
107+
108+
write(&tmp_path, &new)?;
109+
rename(&tmp_path, DYNAMIC_CONF_PATH)?;
110+
111+
Ok(true)
112+
}
113+
},
114+
None => match remove_file(DYNAMIC_CONF_PATH) {
115+
Ok(_) => Ok(true),
116+
Err(err) if err.kind() == ErrorKind::NotFound => Ok(false),
117+
Err(err) => Err(err),
118+
},
119+
}
120+
}

src/dbus/rauc/update_channels.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,6 @@ impl Channels {
174174
self.0
175175
}
176176

177-
#[cfg(not(feature = "demo_mode"))]
178177
pub(super) fn primary(&self) -> Option<&Channel> {
179178
self.0.iter().find(|ch| ch.primary)
180179
}

0 commit comments

Comments
 (0)