rsmount/mount/
mount_source_enum.rs

1// Copyright (c) 2023 Nick Piaddo
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4// From dependency library
5
6// From standard library
7use std::fmt;
8use std::str::FromStr;
9
10// From this library
11use crate::core::device::BlockDevice;
12use crate::core::device::MountPoint;
13use crate::core::device::Pseudo;
14use crate::core::device::SmbFs;
15use crate::core::device::SshFs;
16use crate::core::device::Tag;
17use crate::core::device::NFS;
18use crate::core::errors::ParserError;
19
20/// Source of a device to mount.
21///
22/// A source can take any of the following forms:
23/// - a block device path (e.g. `/dev/sda1`),
24/// - a network ID:
25///     - Samba: `smb://ip-address-or-hostname/shared-dir`,
26///     - NFS: `hostname:/shared-dir`  (e.g. knuth.cwi.nl:/dir)
27///     - SSHFS: `[user@]ip-address-or-hostname:[/shared-dir]` elements in brackets are optional (e.g.
28///       tux@192.168.0.1:/share)
29/// - a tag:
30///     - `UUID=uuid` (file system UUID),
31///     - `LABEL=label` (human readable file system identifier),
32///     - `PARTLABEL=label` (human readable partition identifier),
33///     - `PARTUUID=uuid` (partition UUID),
34///     - `ID=id` (hardware block device ID as generated by `udevd`).
35/// - `none` for pseudo-filesystems.
36///
37/// (For more information, see the subsection titled [Indicating the device and
38/// filesystem](https://www.man7.org/linux/man-pages/man8/mount.8.html) of the `mount`
39/// syscall)
40///
41/// # Examples
42///
43/// ```
44/// # use pretty_assertions::assert_eq;
45/// use rsmount::device::BlockDevice;
46/// use rsmount::device::MountPoint;
47/// use rsmount::device::NFS;
48/// use rsmount::device::Pseudo;
49/// use rsmount::device::SmbFs;
50/// use rsmount::device::SshFs;
51/// use rsmount::device::Tag;
52/// use rsmount::mount::MountSource;
53///
54/// fn main() -> rsmount::Result<()> {
55///    let samba_share: SmbFs = "smb://samba.server.internal/shared".parse()?;
56///     let source = MountSource::from(samba_share);
57///    assert!(source.is_samba_share());
58///
59///    let sshfs_share: SshFs = "tux@sshfs.server.internal:/shared".parse()?;
60///     let source = MountSource::from(sshfs_share);
61///    assert!(source.is_sshfs_share());
62///
63///    let block_device: BlockDevice = "/dev/vda".parse()?;
64///     let source = MountSource::from(block_device);
65///    assert!(source.is_block_device());
66///
67///    let mount_point: MountPoint = "/boot".parse()?;
68///     let source = MountSource::from(mount_point);
69///    assert!(source.is_mount_point());
70///
71///    let tag: Tag = "UUID=dd476616-1ce4-415e-9dbd-8c2fa8f42f0f".parse()?;
72///     let source = MountSource::from(tag);
73///    assert!(source.is_tag());
74///    assert!(source.is_tag_uuid());
75///
76///    let none: Pseudo = "none".parse()?;
77///     let source = MountSource::from(none);
78///    assert!(source.is_pseudo_fs());
79///
80///    Ok(())
81/// }
82/// ```
83#[derive(Debug, Eq, PartialEq)]
84#[non_exhaustive]
85pub enum MountSource {
86    BlockDevice(BlockDevice),
87    MountPoint(MountPoint),
88    NFS(NFS),
89    SmbFs(SmbFs),
90    SshFs(SshFs),
91    Tag(Tag),
92    PseudoFs(Pseudo),
93}
94
95impl MountSource {
96    /// Returns `true` if this `MountSource` is a block device.
97    pub fn is_block_device(&self) -> bool {
98        matches!(self, Self::BlockDevice(_))
99    }
100
101    /// Returns `true` if this `MountSource` is a mount point.
102    pub fn is_mount_point(&self) -> bool {
103        matches!(self, Self::MountPoint(_))
104    }
105
106    /// Returns `true` if this `MountSource` is an NFS share address.
107    pub fn is_nfs_share(&self) -> bool {
108        matches!(self, Self::NFS(_))
109    }
110
111    /// Returns `true` if this `MountSource` is a SmbFs share address.
112    pub fn is_samba_share(&self) -> bool {
113        matches!(self, Self::SmbFs(_))
114    }
115
116    /// Returns `true` if this `MountSource` is an SSHFS address.
117    pub fn is_sshfs_share(&self) -> bool {
118        matches!(self, Self::SshFs(_))
119    }
120
121    /// Returns `true` if this `MountSource` is a tag (e.g `UUID=uuid`, `LABEL=label`,
122    /// `PARTUUID=uuid`, etc.).
123    pub fn is_tag(&self) -> bool {
124        matches!(self, Self::Tag(_))
125    }
126
127    /// Returns `true` if this `MountSource` is a pseudo-filesystem.
128    pub fn is_pseudo_fs(&self) -> bool {
129        matches!(self, Self::PseudoFs(_))
130    }
131
132    /// Returns `true` if this `MountSource` is a `LABEL=label` tag.
133    pub fn is_tag_label(&self) -> bool {
134        matches!(self, Self::Tag(t) if t.is_label())
135    }
136
137    /// Returns `true` if this `MountSource` is a `PARTLABEL=label` tag.
138    pub fn is_tag_partition_label(&self) -> bool {
139        matches!(self, Self::Tag(t) if t.is_partition_label())
140    }
141
142    /// Returns `true` if this `MountSource` is a `UUID=uuid` tag.
143    pub fn is_tag_uuid(&self) -> bool {
144        matches!(self, Self::Tag(t) if t.is_uuid())
145    }
146
147    /// Returns `true` if this `MountSource` is a `PARTUUID=uuid` tag.
148    pub fn is_tag_partition_uuid(&self) -> bool {
149        matches!(self, Self::Tag(t) if t.is_partition_uuid())
150    }
151
152    /// Returns `true` if this `MountSource` is an `ID=id` tag.
153    pub fn is_tag_id(&self) -> bool {
154        matches!(self, Self::Tag(t) if t.is_id())
155    }
156}
157
158impl AsRef<MountSource> for MountSource {
159    #[inline]
160    fn as_ref(&self) -> &MountSource {
161        self
162    }
163}
164
165impl TryFrom<&str> for MountSource {
166    type Error = ParserError;
167
168    fn try_from(s: &str) -> Result<Self, Self::Error> {
169        // Parse string into matching type...
170        Tag::from_str(s)
171            .map(Self::from)
172            .or_else(|_| Pseudo::from_str(s).map(Self::from))
173            .or_else(|_| SmbFs::from_str(s).map(Self::from))
174            .or_else(|_| SshFs::from_str(s).map(Self::from))
175            .or_else(|_| NFS::from_str(s).map(Self::from))
176            .or_else(|_| MountPoint::from_str(s).map(Self::from))
177            // ...if all else fails, assume `s` is a block device.
178            .or_else(|_| BlockDevice::from_str(s).map(Self::from))
179    }
180}
181
182impl TryFrom<String> for MountSource {
183    type Error = ParserError;
184
185    #[inline]
186    fn try_from(s: String) -> Result<Self, Self::Error> {
187        Self::try_from(s.as_str())
188    }
189}
190
191impl TryFrom<&String> for MountSource {
192    type Error = ParserError;
193
194    #[inline]
195    fn try_from(s: &String) -> Result<Self, Self::Error> {
196        Self::try_from(s.as_str())
197    }
198}
199
200impl FromStr for MountSource {
201    type Err = ParserError;
202
203    fn from_str(s: &str) -> Result<Self, Self::Err> {
204        Self::try_from(s)
205    }
206}
207
208impl fmt::Display for MountSource {
209    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210        let output = match self {
211            Self::BlockDevice(device) => device.to_string(),
212            Self::MountPoint(mount_point) => mount_point.to_string(),
213            Self::NFS(share) => share.to_string(),
214            Self::SmbFs(share) => share.to_string(),
215            Self::SshFs(share) => share.to_string(),
216            Self::Tag(tag) => tag.to_string(),
217            Self::PseudoFs(fs) => fs.to_string(),
218        };
219
220        write!(f, "{}", output)
221    }
222}
223
224impl From<BlockDevice> for MountSource {
225    #[inline]
226    fn from(device: BlockDevice) -> MountSource {
227        MountSource::BlockDevice(device)
228    }
229}
230
231impl From<MountPoint> for MountSource {
232    #[inline]
233    fn from(mount_point: MountPoint) -> MountSource {
234        MountSource::MountPoint(mount_point)
235    }
236}
237
238impl From<NFS> for MountSource {
239    #[inline]
240    fn from(share: NFS) -> MountSource {
241        MountSource::NFS(share)
242    }
243}
244
245impl From<SmbFs> for MountSource {
246    #[inline]
247    fn from(share: SmbFs) -> MountSource {
248        MountSource::SmbFs(share)
249    }
250}
251
252impl From<SshFs> for MountSource {
253    #[inline]
254    fn from(share: SshFs) -> MountSource {
255        MountSource::SshFs(share)
256    }
257}
258
259impl From<Tag> for MountSource {
260    #[inline]
261    fn from(share: Tag) -> MountSource {
262        MountSource::Tag(share)
263    }
264}
265
266impl From<Pseudo> for MountSource {
267    #[inline]
268    fn from(fs: Pseudo) -> MountSource {
269        MountSource::PseudoFs(fs)
270    }
271}
272
273#[cfg(test)]
274#[allow(unused_imports)]
275mod tests {
276    use super::*;
277    use pretty_assertions::{assert_eq, assert_ne};
278
279    #[test]
280    #[should_panic(expected = "expected a device path instead of")]
281    fn mount_source_does_not_parse_an_empty_string_as_a_block_device() {
282        let source = "";
283        let _ = MountSource::try_from(source).unwrap();
284    }
285
286    #[test]
287    fn mount_source_parses_a_block_device() -> crate::Result<()> {
288        let source = "/dev/vda";
289        let actual: MountSource = source.parse()?;
290
291        assert!(actual.is_block_device());
292
293        Ok(())
294    }
295
296    #[test]
297    fn mount_source_parses_a_mount_point() -> crate::Result<()> {
298        let source = "/boot";
299        let actual: MountSource = source.parse()?;
300
301        assert!(actual.is_mount_point());
302
303        Ok(())
304    }
305
306    #[test]
307    fn mount_source_parses_a_nfs_share_address_as_an_sshfs_share() -> crate::Result<()> {
308        let source = "localhost:/share";
309        let actual: MountSource = source.parse()?;
310
311        assert!(actual.is_sshfs_share());
312
313        Ok(())
314    }
315
316    #[test]
317    fn mount_source_parses_a_samba_share_address() -> crate::Result<()> {
318        let source = "smb://localhost/share";
319        let actual: MountSource = source.parse()?;
320
321        assert!(actual.is_samba_share());
322
323        Ok(())
324    }
325
326    #[test]
327    fn mount_source_parses_a_sshfs_share_address() -> crate::Result<()> {
328        let source = "user@localhost:/share";
329        let actual: MountSource = source.parse()?;
330
331        assert!(actual.is_sshfs_share());
332
333        Ok(())
334    }
335
336    #[test]
337    fn mount_source_parses_a_uuid_tag() -> crate::Result<()> {
338        let source = "UUID=dd476616-1ce4-415e-9dbd-8c2fa8f42f0f";
339        let actual: MountSource = source.parse()?;
340
341        assert!(actual.is_tag_uuid());
342
343        Ok(())
344    }
345
346    #[test]
347    fn mount_source_parses_a_pseudo_fs() -> crate::Result<()> {
348        let source = "none";
349        let actual: MountSource = source.parse()?;
350
351        assert!(actual.is_pseudo_fs());
352
353        Ok(())
354    }
355}