1use std::{
4 fs::{Permissions, create_dir_all, set_permissions, write},
5 os::{linux::fs::MetadataExt, unix::fs::PermissionsExt},
6 path::PathBuf,
7 process::{Child, Command},
8 thread,
9 time,
10};
11
12use change_user_run::get_command;
13use log::debug;
14
15#[derive(Debug, thiserror::Error)]
17pub enum Error {
18 #[error("Unable to apply permissions to {path}:\n{source}")]
20 ApplyPermissions {
21 path: PathBuf,
23
24 source: std::io::Error,
26 },
27
28 #[error("Unable to create directory {dir}:\n{source}")]
30 CreateDirectory {
31 dir: PathBuf,
33
34 source: std::io::Error,
36 },
37
38 #[error("Unable to start socket for io.systemd.Credentials:\n{0}")]
40 CredentialsSocket(#[source] std::io::Error),
41
42 #[error("I/O error while {context}:\n{source}")]
44 Io {
45 context: &'static str,
47
48 source: std::io::Error,
50 },
51
52 #[error("I/O error at {path} while {context}:\n{source}")]
54 IoPath {
55 path: PathBuf,
57
58 context: &'static str,
60
61 source: std::io::Error,
63 },
64
65 #[error("Timeout of {timeout}ms reached while {context}")]
67 Timeout {
68 timeout: u64,
70
71 context: String,
73 },
74}
75
76pub(crate) fn write_machine_id() -> Result<(), Error> {
89 debug!("Write dummy /etc/machine-id, required for systemd-creds");
90 let machine_id = PathBuf::from("/etc/machine-id");
91 write(&machine_id, "d3b07384d113edec49eaa6238ad5ff00").map_err(|source| Error::IoPath {
92 path: machine_id.to_path_buf(),
93 context: "writing machine-id",
94 source,
95 })?;
96
97 let metadata = machine_id.metadata().map_err(|source| Error::IoPath {
98 path: machine_id,
99 context: "getting metadata of file",
100 source,
101 })?;
102 debug!(
103 "/etc/machine-id\nmode: {}\nuid: {}\ngid: {}",
104 metadata.permissions().mode(),
105 metadata.st_uid(),
106 metadata.st_gid()
107 );
108 Ok(())
109}
110
111#[derive(Debug)]
116pub struct BackgroundProcess {
117 child: Child,
118 command: String,
119}
120
121impl BackgroundProcess {
122 pub fn kill(&mut self) -> Result<(), Error> {
128 self.child.kill().map_err(|source| Error::Io {
129 context: "killing process",
130 source,
131 })
132 }
133}
134
135impl Drop for BackgroundProcess {
136 fn drop(&mut self) {
138 if let Err(error) = self.kill() {
139 log::debug!(
140 "Unable to kill background process of command {}:\n{error}",
141 self.command
142 )
143 }
144 }
145}
146
147pub fn start_credentials_socket() -> Result<BackgroundProcess, crate::Error> {
160 write_machine_id()?;
161 let systemd_run_path = PathBuf::from("/run/systemd");
162 let socket_path = PathBuf::from("/run/systemd/io.systemd.Credentials");
163 create_dir_all(&systemd_run_path).map_err(|source| Error::CreateDirectory {
164 dir: systemd_run_path,
165 source,
166 })?;
167
168 let command = "systemd-socket-activate";
170 let systemd_socket_activate = get_command(command)?;
171 let mut command = Command::new(systemd_socket_activate);
172 let command = command.args([
173 "--listen",
174 "/run/systemd/io.systemd.Credentials",
175 "--accept",
176 "--fdname=varlink",
177 "systemd-creds",
178 ]);
179 let child = command.spawn().map_err(Error::CredentialsSocket)?;
180
181 let timeout = 10000;
183 let step = 100;
184 let mut elapsed = 0;
185 let mut permissions_set = false;
186 while elapsed < timeout {
187 if socket_path.exists() {
188 debug!("Found {socket_path:?}");
189 set_permissions(socket_path.as_path(), Permissions::from_mode(0o666)).map_err(
190 |source| Error::ApplyPermissions {
191 path: socket_path.to_path_buf(),
192 source,
193 },
194 )?;
195 permissions_set = true;
196 break;
197 } else {
198 thread::sleep(time::Duration::from_millis(step));
199 elapsed += step;
200 }
201 }
202 if !permissions_set {
203 return Err(Error::Timeout {
204 timeout,
205 context: format!("waiting for {socket_path:?}"),
206 }
207 .into());
208 }
209
210 Ok(BackgroundProcess {
211 child,
212 command: format!("{command:?}"),
213 })
214}