Skip to main content

xenclient/tx/
mod.rs

1pub mod channel;
2pub mod fs9p;
3pub mod pci;
4pub mod vbd;
5pub mod vif;
6
7use crate::{
8    devalloc::DeviceIdAllocator,
9    error::{Error, Result},
10};
11use std::{collections::HashMap, sync::Arc};
12use tokio::sync::Mutex;
13use xenplatform::domain::{PlatformDomainConfig, PlatformDomainInfo};
14use xenstore::{
15    XsPermission, XsdClient, XsdInterface, XsdTransaction, XS_PERM_NONE, XS_PERM_READ,
16    XS_PERM_READ_WRITE,
17};
18
19pub struct XenTransaction {
20    frontend_domid: u32,
21    frontend_dom_path: String,
22    backend_domid: u32,
23    backend_dom_path: String,
24    blkalloc: Arc<Mutex<DeviceIdAllocator>>,
25    devalloc: Arc<Mutex<DeviceIdAllocator>>,
26    tx: XsdTransaction,
27    abort: bool,
28}
29
30impl XenTransaction {
31    pub async fn new(store: &XsdClient, frontend_domid: u32, backend_domid: u32) -> Result<Self> {
32        let frontend_dom_path = store.get_domain_path(frontend_domid).await?;
33        let backend_dom_path = store.get_domain_path(backend_domid).await?;
34        let tx = store.transaction().await?;
35
36        let devalloc = XenTransaction::load_id_allocator(&tx, "devid", &frontend_dom_path).await?;
37        let blkalloc = XenTransaction::load_id_allocator(&tx, "blkid", &frontend_dom_path).await?;
38
39        Ok(XenTransaction {
40            frontend_domid,
41            frontend_dom_path,
42            backend_domid,
43            backend_dom_path,
44            tx,
45            devalloc: Arc::new(Mutex::new(devalloc)),
46            blkalloc: Arc::new(Mutex::new(blkalloc)),
47            abort: true,
48        })
49    }
50
51    async fn load_id_allocator(
52        tx: &XsdTransaction,
53        allocator_type: &str,
54        frontend_dom_path: &str,
55    ) -> Result<DeviceIdAllocator> {
56        let state = tx
57            .read(format!(
58                "{}/{}-alloc-state",
59                frontend_dom_path, allocator_type
60            ))
61            .await?;
62        let allocator = state
63            .and_then(|state| DeviceIdAllocator::deserialize(&state))
64            .unwrap_or_else(DeviceIdAllocator::new);
65        Ok(allocator)
66    }
67
68    pub async fn assign_next_devid(&self) -> Result<u64> {
69        self.devalloc
70            .lock()
71            .await
72            .allocate()
73            .ok_or(Error::DevIdExhausted)
74            .map(|x| x as u64)
75    }
76
77    pub async fn assign_next_blkidx(&self) -> Result<u32> {
78        self.blkalloc
79            .lock()
80            .await
81            .allocate()
82            .ok_or(Error::DevIdExhausted)
83    }
84
85    pub async fn release_devid(&self, devid: u64) -> Result<()> {
86        self.devalloc.lock().await.release(devid as u32);
87        Ok(())
88    }
89
90    pub async fn release_blkid(&self, blkid: u32) -> Result<()> {
91        self.blkalloc.lock().await.release(blkid);
92        Ok(())
93    }
94
95    pub async fn write(
96        &self,
97        key: impl AsRef<str>,
98        value: impl AsRef<str>,
99        perms: Option<&[XsPermission]>,
100    ) -> Result<()> {
101        let path = format!("{}/{}", self.frontend_dom_path, key.as_ref());
102        if let Some(perms) = perms {
103            self.tx.mknod(&path, perms).await?;
104        }
105
106        // empty string is written by mknod, if perms is set we can skip it.
107        if perms.is_none() || perms.is_some() && !value.as_ref().is_empty() {
108            self.tx.write_string(path, value.as_ref()).await?;
109        }
110        Ok(())
111    }
112
113    pub async fn add_domain_declaration(
114        &self,
115        name: Option<impl AsRef<str>>,
116        platform: &PlatformDomainConfig,
117        created: &PlatformDomainInfo,
118    ) -> Result<()> {
119        let vm_path = format!("/vm/{}", platform.uuid);
120        let ro_perm = &[
121            XsPermission {
122                id: 0,
123                perms: XS_PERM_NONE,
124            },
125            XsPermission {
126                id: self.frontend_domid,
127                perms: XS_PERM_READ,
128            },
129        ];
130
131        let no_perm = &[XsPermission {
132            id: 0,
133            perms: XS_PERM_NONE,
134        }];
135
136        let rw_perm = &[XsPermission {
137            id: self.frontend_domid,
138            perms: XS_PERM_READ_WRITE,
139        }];
140
141        self.tx.rm(&self.frontend_dom_path).await?;
142        self.tx.mknod(&self.frontend_dom_path, ro_perm).await?;
143
144        self.tx.rm(&vm_path).await?;
145        self.tx.mknod(&vm_path, no_perm).await?;
146        self.tx
147            .write_string(format!("{}/uuid", vm_path), &platform.uuid.to_string())
148            .await?;
149
150        self.write("vm", &vm_path, None).await?;
151        self.write("cpu", "", Some(ro_perm)).await?;
152        self.write("memory", "", Some(ro_perm)).await?;
153        self.write("control", "", Some(ro_perm)).await?;
154        self.write("control/shutdown", "", Some(rw_perm)).await?;
155        self.write("control/feature-poweroff", "", Some(rw_perm))
156            .await?;
157        self.write("control/feature-reboot", "", Some(rw_perm))
158            .await?;
159        self.write("control/feature-suspend", "", Some(rw_perm))
160            .await?;
161        self.write("control/sysrq", "", Some(rw_perm)).await?;
162        self.write("data", "", Some(rw_perm)).await?;
163        self.write("drivers", "", Some(rw_perm)).await?;
164        self.write("feature", "", Some(rw_perm)).await?;
165        self.write("attr", "", Some(rw_perm)).await?;
166        self.write("error", "", Some(rw_perm)).await?;
167        self.write("uuid", platform.uuid.to_string(), Some(ro_perm))
168            .await?;
169        if let Some(name) = name {
170            self.write("name", name.as_ref(), Some(ro_perm)).await?;
171        }
172        self.write(
173            "memory/static-max",
174            (platform.resources.max_memory_mb * 1024).to_string(),
175            None,
176        )
177        .await?;
178        self.write(
179            "memory/target",
180            (platform.resources.assigned_memory_mb * 1024).to_string(),
181            None,
182        )
183        .await?;
184        self.write("memory/videoram", "0", None).await?;
185        self.write("domid", self.frontend_domid.to_string(), None)
186            .await?;
187        self.write("type", "PV", None).await?;
188        self.write("store/port", created.store_evtchn.to_string(), None)
189            .await?;
190        self.write("store/ring-ref", created.store_mfn.to_string(), None)
191            .await?;
192        for i in 0..platform.resources.max_vcpus {
193            let path = format!("{}/cpu/{}", self.frontend_dom_path, i);
194            self.tx.mkdir(&path).await?;
195            self.tx.set_perms(&path, ro_perm).await?;
196            let path = format!("{}/cpu/{}/availability", self.frontend_dom_path, i);
197            self.tx
198                .write_string(
199                    &path,
200                    if i < platform.resources.assigned_vcpus {
201                        "online"
202                    } else {
203                        "offline"
204                    },
205                )
206                .await?;
207            self.tx.set_perms(&path, ro_perm).await?;
208        }
209        Ok(())
210    }
211
212    pub async fn add_device(&self, id: u64, device: DeviceDescription) -> Result<()> {
213        let frontend_path = if let Some(ref special_frontend_path) = device.special_frontend_path {
214            format!("{}/{}", self.frontend_dom_path, special_frontend_path)
215        } else {
216            format!(
217                "{}/device/{}/{}",
218                self.frontend_dom_path, device.frontend_type, id
219            )
220        };
221        let backend_path = format!(
222            "{}/backend/{}/{}/{}",
223            self.backend_dom_path, device.backend_type, self.frontend_domid, id
224        );
225
226        let frontend_perms = &[
227            XsPermission {
228                id: self.frontend_domid,
229                perms: XS_PERM_READ_WRITE,
230            },
231            XsPermission {
232                id: self.backend_domid,
233                perms: XS_PERM_READ,
234            },
235        ];
236
237        let backend_perms = &[
238            XsPermission {
239                id: self.backend_domid,
240                perms: XS_PERM_READ_WRITE,
241            },
242            XsPermission {
243                id: self.frontend_domid,
244                perms: XS_PERM_READ,
245            },
246        ];
247
248        self.tx.mknod(&frontend_path, frontend_perms).await?;
249        self.tx.mknod(&backend_path, backend_perms).await?;
250
251        for (key, value) in &device.backend_items {
252            let path = format!("{}/{}", backend_path, key);
253            self.tx.write_string(&path, value).await?;
254        }
255
256        self.tx
257            .write_string(format!("{}/frontend", backend_path), &frontend_path)
258            .await?;
259        self.tx
260            .write_string(
261                format!("{}/frontend-id", backend_path),
262                &self.frontend_domid.to_string(),
263            )
264            .await?;
265        for (key, value) in &device.frontend_items {
266            let path = format!("{}/{}", frontend_path, key);
267            self.tx.write_string(&path, value).await?;
268            if device.special_frontend_path.is_none() {
269                self.tx.set_perms(&path, frontend_perms).await?;
270            }
271        }
272        self.tx
273            .write_string(format!("{}/backend", frontend_path), &backend_path)
274            .await?;
275        self.tx
276            .write_string(
277                format!("{}/backend-id", frontend_path),
278                &self.backend_domid.to_string(),
279            )
280            .await?;
281        Ok(())
282    }
283
284    pub async fn add_rw_path(&self, key: impl AsRef<str>) -> Result<()> {
285        let rw_perm = &[XsPermission {
286            id: self.frontend_domid,
287            perms: XS_PERM_READ_WRITE,
288        }];
289
290        self.tx
291            .mknod(
292                &format!("{}/{}", self.frontend_dom_path, key.as_ref()),
293                rw_perm,
294            )
295            .await?;
296        Ok(())
297    }
298
299    async fn before_commit(&self) -> Result<()> {
300        let devid_allocator_state = self.devalloc.lock().await.serialize();
301        let blkid_allocator_state = self.blkalloc.lock().await.serialize();
302        self.tx
303            .write(
304                format!("{}/devid-alloc-state", self.frontend_dom_path),
305                devid_allocator_state,
306            )
307            .await?;
308        self.tx
309            .write(
310                format!("{}/blkid-alloc-state", self.frontend_dom_path),
311                blkid_allocator_state,
312            )
313            .await?;
314        Ok(())
315    }
316
317    pub async fn maybe_commit(mut self) -> Result<bool> {
318        self.abort = false;
319        self.before_commit().await?;
320        Ok(self.tx.maybe_commit().await?)
321    }
322
323    pub async fn commit(mut self) -> Result<()> {
324        self.abort = false;
325        self.before_commit().await?;
326        self.tx.commit().await?;
327        Ok(())
328    }
329}
330
331impl Drop for XenTransaction {
332    fn drop(&mut self) {
333        if !self.abort {
334            return;
335        }
336        let tx = self.tx.clone();
337        tokio::task::spawn(async move {
338            let _ = tx.abort().await;
339        });
340    }
341}
342
343pub struct DeviceDescription {
344    frontend_type: String,
345    backend_type: String,
346    special_frontend_path: Option<String>,
347    frontend_items: HashMap<String, String>,
348    backend_items: HashMap<String, String>,
349}
350
351impl DeviceDescription {
352    pub fn new(frontend_type: impl AsRef<str>, backend_type: impl AsRef<str>) -> Self {
353        Self {
354            frontend_type: frontend_type.as_ref().to_string(),
355            backend_type: backend_type.as_ref().to_string(),
356            special_frontend_path: None,
357            frontend_items: HashMap::new(),
358            backend_items: HashMap::new(),
359        }
360    }
361
362    pub fn special_frontend_path(&mut self, path: impl AsRef<str>) -> &mut Self {
363        self.special_frontend_path = Some(path.as_ref().to_string());
364        self
365    }
366
367    pub fn add_frontend_item(&mut self, key: impl AsRef<str>, value: impl ToString) -> &mut Self {
368        self.frontend_items
369            .insert(key.as_ref().to_string(), value.to_string());
370        self
371    }
372
373    pub fn add_backend_item(&mut self, key: impl AsRef<str>, value: impl ToString) -> &mut Self {
374        self.backend_items
375            .insert(key.as_ref().to_string(), value.to_string());
376        self
377    }
378
379    pub fn add_frontend_bool(&mut self, key: impl AsRef<str>, value: bool) -> &mut Self {
380        self.add_frontend_item(key, if value { "1" } else { "0" })
381    }
382
383    pub fn add_backend_bool(&mut self, key: impl AsRef<str>, value: bool) -> &mut Self {
384        self.add_backend_item(key, if value { "1" } else { "0" })
385    }
386
387    pub fn done(self) -> Self {
388        self
389    }
390}
391
392#[derive(Clone, Debug)]
393pub struct DeviceResult {
394    pub id: u64,
395}
396
397#[derive(Clone, Debug)]
398pub struct BlockDeviceResult {
399    pub id: u64,
400    pub idx: u32,
401}
402
403#[async_trait::async_trait]
404pub trait DeviceConfig {
405    type Result;
406
407    async fn add_to_transaction(&self, tx: &XenTransaction) -> Result<Self::Result>;
408}
409
410#[derive(Clone, Debug)]
411pub struct BlockDeviceRef {
412    pub path: String,
413    pub major: u32,
414    pub minor: u32,
415}
416
417impl BlockDeviceRef {
418    pub fn new(path: impl AsRef<str>, major: u32, minor: u32) -> Self {
419        Self {
420            path: path.as_ref().to_string(),
421            major,
422            minor,
423        }
424    }
425}