systemd_run/
mount.rs

1#[allow(dead_code)]
2enum Priv {
3    Bind {
4        src: String,
5        rw: bool,
6        ignore_nonexist: bool,
7        recursive: bool,
8    },
9    Tmpfs {
10        rw: bool,
11        opts: Vec<String>,
12    },
13    Normal {
14        src: String,
15        rw: bool,
16        ignore_nonexist: bool,
17        opts: Vec<String>,
18    },
19}
20
21/// The description of a mount.
22pub struct Mount(Priv);
23
24impl Mount {
25    /// Create a new bind mount.
26    ///
27    /// Read `BindPaths` and `BindReadOnlyPaths` in
28    /// [systemd.exec(5)](man:systemd.exec(5)) for details.
29    ///
30    /// This setting is not available if the feature `systemd_233` is
31    /// disabled.
32    #[cfg(feature = "systemd_233")]
33    pub fn bind<T: AsRef<str>>(src: T) -> Self {
34        Self(Priv::Bind {
35            src: src.as_ref().to_owned(),
36            rw: false,
37            recursive: false,
38            ignore_nonexist: false,
39        })
40    }
41
42    /// Create a tmpfs mount.
43    ///
44    /// Read `TemporaryFileSystem` in [systemd.exec(5)](man:systemd.exec(5))
45    /// for details.
46    ///
47    /// This setting is not available if the feature `systemd_238` is
48    /// disabled.
49    #[cfg(feature = "systemd_238")]
50    pub fn tmpfs() -> Self {
51        Self(Priv::Tmpfs {
52            rw: false,
53            opts: vec![],
54        })
55    }
56
57    /// Create a normal mount.
58    ///
59    /// Read `MountImages` in [systemd.exec(5)](man:systemd.exec(5)) for
60    /// details.
61    ///
62    /// This setting is not available if the feature `systemd_247` is
63    /// disabled.
64    #[cfg(feature = "systemd_247")]
65    pub fn normal<T: AsRef<str>>(src: T) -> Self {
66        Self(Priv::Normal {
67            src: src.as_ref().to_owned(),
68            rw: false,
69            ignore_nonexist: false,
70            opts: vec![],
71        })
72    }
73
74    /// Make the [Mount] writable.
75    pub fn writable(self) -> Self {
76        match self {
77            Self(Priv::Bind {
78                src,
79                recursive,
80                ignore_nonexist,
81                ..
82            }) => Self(Priv::Bind {
83                src,
84                recursive,
85                ignore_nonexist,
86                rw: true,
87            }),
88            Self(Priv::Tmpfs { opts, .. }) => Self(Priv::Tmpfs { opts, rw: true }),
89            Self(Priv::Normal {
90                opts,
91                src,
92                ignore_nonexist,
93                ..
94            }) => Self(Priv::Normal {
95                opts,
96                src,
97                ignore_nonexist,
98                rw: true,
99            }),
100        }
101    }
102
103    /// Make the [Mount] recursive.  It only makes a difference for a bind
104    /// mount.
105    pub fn recursive(self) -> Self {
106        match self {
107            Self(Priv::Bind { src, rw, .. }) => Self(Priv::Bind {
108                src,
109                rw,
110                recursive: true,
111                ignore_nonexist: true,
112            }),
113            _ => self,
114        }
115    }
116
117    /// Append a mount option.  If the option contains a comma (`,`), or
118    /// it is `ro`, `rw`, or empty, or it's applied for a bind mount,
119    /// [None] will be returned.  But the option will not be validated
120    /// further.
121    ///
122    /// For `rw`, use [Self::writable] instead.
123    pub fn opt<T: AsRef<str>>(self, option: T) -> Option<Self> {
124        let o = option.as_ref();
125        if o.contains(',') {
126            return None;
127        }
128        match o {
129            "" | "ro" | "rw" => return None,
130            _ => {}
131        }
132        match self {
133            Self(Priv::Bind { .. }) => None,
134            Self(Priv::Tmpfs { rw, mut opts }) => {
135                opts.push(o.to_owned());
136                Some(Self(Priv::Tmpfs { rw, opts }))
137            }
138            Self(Priv::Normal {
139                src,
140                rw,
141                mut opts,
142                ignore_nonexist,
143            }) => {
144                opts.push(o.to_owned());
145                Some(Self(Priv::Normal {
146                    src,
147                    rw,
148                    opts,
149                    ignore_nonexist,
150                }))
151            }
152        }
153    }
154
155    /// Ignore the mount if the source does not exist.  This does not make
156    /// any difference for tmpfs mounts.
157    pub fn ignore_nonexist(self) -> Self {
158        match self {
159            Self(Priv::Tmpfs { .. }) => self,
160            Self(Priv::Normal { src, rw, opts, .. }) => Self(Priv::Normal {
161                src,
162                rw,
163                opts,
164                ignore_nonexist: true,
165            }),
166            Self(Priv::Bind {
167                src, rw, recursive, ..
168            }) => Self(Priv::Bind {
169                src,
170                rw,
171                recursive,
172                ignore_nonexist: true,
173            }),
174        }
175    }
176}
177
178fn escape(s: &str) -> String {
179    s.replace('\\', "\\\\")
180        .replace(':', "\\:")
181        .replace(' ', "\\ ")
182}
183
184pub enum MarshaledMount {
185    /// src, dest, ignore_nonexist, flags (MS_REC or 0)
186    Bind(String, String, bool, u64),
187    /// src, dest, ignore_nonexist, flags (MS_REC or 0)
188    BindReadOnly(String, String, bool, u64),
189    /// src, dest, ignore_nonexist, `[(GPT label, flags)]`
190    /// Currently only `root` used as `GPT label`
191    Normal(String, String, bool, Vec<(&'static str, String)>),
192    /// dest, flags (joined with comma)
193    Tmpfs(String, String),
194}
195
196pub fn marshal<T: AsRef<str>>(mount_point: T, mount: Mount) -> MarshaledMount {
197    use MarshaledMount::*;
198    let mp = escape(mount_point.as_ref());
199
200    match mount {
201        Mount(Priv::Bind {
202            src,
203            rw,
204            ignore_nonexist,
205            recursive,
206        }) => {
207            let src = escape(&src);
208            let flags: u64 = match recursive {
209                true => 16384, // MS_REC
210                false => 0,
211            };
212            match rw {
213                true => Bind(src, mp, ignore_nonexist, flags),
214                false => BindReadOnly(src, mp, ignore_nonexist, flags),
215            }
216        }
217        Mount(Priv::Normal {
218            src,
219            rw,
220            ignore_nonexist,
221            mut opts,
222        }) => {
223            let src = escape(&src);
224            if !rw {
225                opts.push("ro".into());
226            }
227            let opts = opts.into_iter().map(|x| ("root", x)).collect();
228            Normal(src, mp, ignore_nonexist, opts)
229        }
230        Mount(Priv::Tmpfs { rw, mut opts }) => {
231            if !rw {
232                opts.push("ro".into());
233            }
234            Tmpfs(mp, opts.join(","))
235        }
236    }
237}