rsmount 0.2.2

Safe Rust wrapper around the `util-linux/libmount` C library
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
// Copyright (c) 2023 Nick Piaddo
// SPDX-License-Identifier: Apache-2.0 OR MIT

// From dependency library

// From standard library
use std::fmt;
use std::fs::File;
use std::mem::MaybeUninit;
use std::path::Path;

// From this library
use crate::core::cache::Cache;
use crate::core::device::Source;
use crate::core::errors::SwapsEntryError;
use crate::ffi_utils;

/// A line in `/proc/swaps`.
///
/// For example:
///
/// ```text
/// /dev/sda2                               partition       1048572         0               -2
/// ```
#[derive(Debug, PartialEq)]
#[repr(transparent)]
pub struct SwapsEntry {
    pub(crate) inner: *mut libmount::libmnt_fs,
}

impl Drop for SwapsEntry {
    fn drop(&mut self) {
        log::debug!("SwapsEntry::drop deallocating `SwapsEntry` instance");

        unsafe { libmount::mnt_unref_fs(self.inner) }
    }
}

impl SwapsEntry {
    #[doc(hidden)]
    /// Increments the inner value's reference counter.
    pub(crate) fn incr_ref_counter(&mut self) {
        unsafe { libmount::mnt_ref_fs(self.inner) }
    }

    #[doc(hidden)]
    #[allow(dead_code)]
    /// Decrements the inner value's reference counter.
    pub(crate) fn decr_ref_counter(&mut self) {
        unsafe { libmount::mnt_unref_fs(self.inner) }
    }

    #[doc(hidden)]
    /// Borrows an instance.
    pub(crate) fn borrow_ptr(ptr: *mut libmount::libmnt_fs) -> SwapsEntry {
        let mut entry = Self { inner: ptr };
        // We are virtually ceding ownership of this table entry which will be automatically
        // deallocated once it is out of scope, incrementing its reference counter protects it from
        // being freed prematurely.
        entry.incr_ref_counter();

        entry
    }

    #[doc(hidden)]
    /// Wraps a raw `libmount::mnt_fs` pointer in a safe instance.
    #[inline]
    pub(crate) fn from_ptr(ptr: *mut libmount::libmnt_fs) -> SwapsEntry {
        Self { inner: ptr }
    }

    #[doc(hidden)]
    /// Wraps a boxed raw `libmount::mnt_fs` pointer in a safe reference.
    pub(crate) unsafe fn ref_from_boxed_ptr<'a>(
        ptr: Box<*mut libmount::libmnt_fs>,
    ) -> (*mut *mut libmount::libmnt_fs, &'a SwapsEntry) {
        let raw_ptr = Box::into_raw(ptr);
        let entry_ref = unsafe { &*(raw_ptr as *const _ as *const SwapsEntry) };

        (raw_ptr, entry_ref)
    }

    #[doc(hidden)]
    #[allow(dead_code)]
    /// Wraps a boxed raw `libmount::mnt_fs` pointer in a safe mutable reference.
    pub(crate) unsafe fn mut_from_boxed_ptr<'a>(
        ptr: Box<*mut libmount::libmnt_fs>,
    ) -> (*mut *mut libmount::libmnt_fs, &'a mut SwapsEntry) {
        let raw_ptr = Box::into_raw(ptr);
        let entry_ref = unsafe { &mut *(raw_ptr as *mut SwapsEntry) };

        (raw_ptr, entry_ref)
    }

    #[doc(hidden)]
    #[allow(dead_code)]
    /// Creates a new instance.
    pub(crate) fn new() -> Result<SwapsEntry, SwapsEntryError> {
        log::debug!("SwapsEntry::new creating a new `SwapsEntry` instance");

        let mut inner = MaybeUninit::<*mut libmount::libmnt_fs>::zeroed();
        unsafe {
            inner.write(libmount::mnt_new_fs());
        }

        match unsafe { inner.assume_init() } {
            inner if inner.is_null() => {
                let err_msg = "failed to create a new `SwapsEntry` instance".to_owned();
                log::debug!(
                    "SwapsEntry::new {err_msg}. libmount::mnt_new_fs returned a NULL pointer"
                );

                Err(SwapsEntryError::Creation(err_msg))
            }
            inner => {
                log::debug!("SwapsEntry::new created a new `SwapsEntry` instance");
                let entry = Self { inner };

                Ok(entry)
            }
        }
    }

    //---- BEGIN getters

    /// Returns the entry's source path which can be
    /// - a directory for bind mounts (in `/etc/fstab` or `/etc/mtab` only)
    /// - a path to a block device for standard mounts.
    pub fn source_path(&self) -> Option<&Path> {
        log::debug!(concat!(
            stringify!($entry_type),
            "::source_path getting the mount's source path"
        ));

        let mut ptr = MaybeUninit::<*const libc::c_char>::zeroed();

        unsafe {
            ptr.write(libmount::mnt_fs_get_srcpath(self.inner));
        }

        match unsafe { ptr.assume_init() } {
            ptr if ptr.is_null() => {
                log::debug!(concat!(stringify!($entry_type), "::source_path failed to get the mount's source path. libmount::mnt_fs_get_srcpath returned a NULL pointer"));

                None
            }

            ptr => {
                let path = ffi_utils::const_c_char_array_to_path(ptr);
                log::debug!(
                    concat!(stringify!($entry_type), "::source_path value: {:?}"),
                    path
                );

                Some(path)
            }
        }
    }

    /// Returns the type of swap partition.
    pub fn swap_type(&self) -> Option<&str> {
        log::debug!("SwapsEntry::swap_type getting swap type");

        let mut ptr = MaybeUninit::<*const libc::c_char>::zeroed();

        unsafe {
            ptr.write(libmount::mnt_fs_get_swaptype(self.inner));
        }

        match unsafe { ptr.assume_init() } {
            ptr if ptr.is_null() => {
                log::debug!("SwapsEntry::swap_type failed to get swap type. libmount::mnt_fs_get_swaptype returned a NULL pointer");

                None
            }

            ptr => {
                let swap_type = ffi_utils::const_char_array_to_str_ref(ptr);
                log::debug!("SwapsEntry::swap_type value: {:?}", swap_type);

                swap_type.ok()
            }
        }
    }

    /// Returns the total size of the swap partition (in kibibytes).
    pub fn size(&self) -> usize {
        let size = unsafe { libmount::mnt_fs_get_size(self.inner) as usize };
        log::debug!("SwapsEntry::size value: {:?}", size);

        size
    }

    /// Returns the size of the swap space used (in kibibytes).
    pub fn size_used(&self) -> usize {
        let size = unsafe { libmount::mnt_fs_get_usedsize(self.inner) as usize };
        log::debug!("SwapsEntry::size_used size: {:?}", size);

        size
    }

    /// Returns the priority number of the swap partition.
    pub fn priority(&self) -> i32 {
        let priority = unsafe { libmount::mnt_fs_get_priority(self.inner) };
        log::debug!("SwapsEntry::priority value: {:?}", priority);

        priority
    }

    //---- END getters

    //---- BEGIN setters

    /// Sets the file stream to print debug messages to.
    pub fn print_debug_to(&mut self, stream: &mut File) -> Result<(), SwapsEntryError> {
        log::debug!("SwapsEntry::print_debug_to setting file stream to print debug messages to");

        if ffi_utils::is_open_write_only(stream)? || ffi_utils::is_open_read_write(stream)? {
            let file_stream = ffi_utils::write_only_c_file_stream_from(stream)?;

            let result = unsafe { libmount::mnt_fs_print_debug(self.inner, file_stream as *mut _) };
            match result {
                0 => {
                    log::debug!(
                        "SwapsEntry::print_debug_to set file stream to print debug messages to"
                    );

                    Ok(())
                }
                code => {
                    let err_msg = "failed to set file stream to print debug messages to".to_owned();
                    log::debug!( "SwapsEntry::print_debug_to {err_msg}. libmount::mnt_fs_print_debug returned error code: {code:?}");

                    Err(SwapsEntryError::Action(err_msg))
                }
            }
        } else {
            let err_msg = "missing write permission for given stream".to_owned();
            log::debug!("SwapsEntry::print_debug_to {err_msg}");

            Err(SwapsEntryError::Permission(err_msg))
        }
    }

    /// Sets the priority of the swap device; swap priority takes a value between `-1` and `32767`.
    ///
    /// Higher numbers indicate higher priority (for more information see the [`swapon` command's
    /// manpage](https://manpages.org/swapon/8)).
    pub fn set_priority(&mut self, priority: i32) -> Result<(), SwapsEntryError> {
        log::debug!(
            "SwapsEntry::set_priority setting swap priority to: {:?}",
            priority
        );

        let result = unsafe { libmount::mnt_fs_set_priority(self.inner, priority) };
        match result {
            0 => {
                log::debug!(
                    "SwapsEntry::set_priority set swap priority to: {:?}",
                    priority
                );

                Ok(())
            }
            code => {
                let err_msg = format!("failed to set swap priority to: {:?}", priority);
                log::debug!("SwapsEntry::set_priority {}. libmount::mnt_fs_set_priority returned error code: {:?}", err_msg, code);

                Err(SwapsEntryError::Config(err_msg))
            }
        }
    }

    #[doc(hidden)]
    #[allow(dead_code)]
    /// Sets the source of the device to mount.
    ///
    /// A `source` can take any of the following forms:
    /// - block device path (e.g. `/dev/sda1`),
    /// - network ID:
    ///     - Samba: `smb://ip-address-or-hostname/shared-dir`,
    ///     - NFS: `hostname:/shared-dir`  (e.g. knuth.cwi.nl:/dir)
    ///     - SSHFS: `user@ip-address-or-hostname:/shared-dir`  (e.g. tux@192.168.0.1:/home/tux)
    /// - label:
    ///     - `UUID=uuid`,
    ///     - `LABEL=label`,
    ///     - `PARTLABEL=label`,
    ///     - `PARTUUID=uuid`,
    ///     - `ID=id`.
    pub(crate) fn set_mount_source<T>(&mut self, source: T) -> Result<(), SwapsEntryError>
    where
        T: AsRef<str>,
    {
        let source = source.as_ref();
        log::debug!(
            "SwapsEntry::set_mount_source setting the source of a device to mount: {:?}",
            source
        );

        let source_cstr = ffi_utils::as_ref_str_to_c_string(source)?;

        let result = unsafe { libmount::mnt_fs_set_source(self.inner, source_cstr.as_ptr()) };
        match result {
            0 => {
                log::debug!(
                    "SwapsEntry::set_mount_source set the source of a device to mount: {:?}",
                    source
                );

                Ok(())
            }
            code => {
                let err_msg = format!(
                    "failed to set the source of a device to mount: {:?}",
                    source
                );
                log::debug!( "SwapsEntry::set_mount_source {err_msg}. libmount::mnt_fs_set_source returned error code: {code:?}");

                Err(SwapsEntryError::Config(err_msg))
            }
        }
    }

    #[doc(hidden)]
    #[allow(dead_code)]
    /// Sets a device's mount point.
    pub(crate) fn set_mount_target<T>(&mut self, path: T) -> Result<(), SwapsEntryError>
    where
        T: AsRef<Path>,
    {
        let path = path.as_ref();
        log::debug!(
            "SwapsEntry::set_mount_target setting device mount point to: {:?}",
            path
        );

        let path_cstr = ffi_utils::as_ref_path_to_c_string(path)?;

        let result = unsafe { libmount::mnt_fs_set_target(self.inner, path_cstr.as_ptr()) };
        match result {
            0 => {
                log::debug!(
                    "SwapsEntry::set_mount_target set device mount point to: {:?}",
                    path
                );

                Ok(())
            }
            code => {
                let err_msg = format!("failed to set a device's mount point to: {:?}", path);
                log::debug!( "SwapsEntry::set_mount_target {err_msg}. libmount::mnt_fs_set_target returned error code: {code:?}");

                Err(SwapsEntryError::Config(err_msg))
            }
        }
    }

    //---- END setters

    //---- BEGIN mutators

    /// Allocates a new `SwapsEntry`, and a copies all the source's fields to the new instance except
    /// any private user data.
    pub fn copy(&self) -> Result<SwapsEntry, SwapsEntryError> {
        log::debug!("SwapsEntry::copy copying `SwapsEntry`");

        let mut ptr = MaybeUninit::<*mut libmount::libmnt_fs>::zeroed();

        unsafe {
            ptr.write(libmount::mnt_copy_fs(std::ptr::null_mut(), self.inner));
        }

        match unsafe { ptr.assume_init() } {
            ptr if ptr.is_null() => {
                let err_msg = "failed to copy `SwapsEntry`".to_owned();
                log::debug!(
                    "SwapsEntry::copy {err_msg}. libmount::mnt_copy_fs returned a NULL pointer"
                );

                Err(SwapsEntryError::Action(err_msg))
            }
            ptr => {
                log::debug!("SwapsEntry::copy copied `SwapsEntry`");
                let entry = Self::from_ptr(ptr);

                Ok(entry)
            }
        }
    }

    //---- END mutators

    //---- BEGIN predicates

    /// Returns `true` if data is read directly from the kernel (e.g `/proc/mounts`).
    pub fn is_from_kernel(&self) -> bool {
        let state = unsafe { libmount::mnt_fs_is_kernel(self.inner) == 1 };
        log::debug!("SwapsEntry::is_from_kernel value: {:?}", state);

        state
    }

    /// Returns `true` if the file system of this `SwapsEntry` is a network file system.
    pub fn is_net_fs(&self) -> bool {
        let state = unsafe { libmount::mnt_fs_is_netfs(self.inner) == 1 };
        log::debug!("SwapsEntry::is_net_fs value: {:?}", state);

        state
    }

    /// Returns `true` if the file system of this `SwapsEntry` is a pseudo file system type (`proc`, `cgroups`).
    pub fn is_pseudo_fs(&self) -> bool {
        let state = unsafe { libmount::mnt_fs_is_pseudofs(self.inner) == 1 };
        log::debug!("SwapsEntry::is_pseudo_fs value: {:?}", state);

        state
    }

    #[cfg(mount = "v2_39")]
    /// Returns `true` if the file system of this `SwapsEntry` is a regular file system (neither a network nor a pseudo file system).
    pub fn is_regular_fs(&self) -> bool {
        let state = unsafe { libmount::mnt_fs_is_regularfs(self.inner) == 1 };
        log::debug!("SwapsEntry::is_regular_fs value: {:?}", state);

        state
    }

    /// Returns `true` if this `SwapsEntry` represents a swap partition.
    pub fn is_swap(&self) -> bool {
        let state = unsafe { libmount::mnt_fs_is_swaparea(self.inner) == 1 };
        log::debug!("SwapsEntry::is_swap value: {:?}", state);

        state
    }

    /// Returns `true` if the `source` parameter matches the `source` field in this `SwapsEntry`.
    ///
    /// Using the provided `cache`, this method will perform the following comparisons in sequence:
    /// - `source` vs the value of the `source` field in this `SwapsEntry`
    ///
    /// - the resolved value of the `source` parameter vs the value of the `source` field in this
    ///   `SwapsEntry`
    /// - the resolved value of the `source` parameter vs the resolved value of the `source` field
    ///   in this `SwapsEntry`
    /// - the resolved value of the `source` parameter vs the evaluated tag of the `source` field
    ///   in this `SwapsEntry`
    ///
    /// *Resolving* the `source` parameter means searching and returning the absolute path to
    /// the device it represents. The same for *evaluating* a tag.
    pub fn is_source(&self, source: &Source, cache: &Cache) -> bool {
        let source_cstr = ffi_utils::as_ref_str_to_c_string(source.to_string()).ok();

        if let Some(source_cstr) = source_cstr {
            let state = unsafe {
                libmount::mnt_fs_match_source(self.inner, source_cstr.as_ptr(), cache.inner) == 1
            };
            log::debug!(
                "SwapsEntry::is_source is {:?} the source of this entry? {:?}",
                source,
                state
            );

            state
        } else {
            log::debug!("SwapsEntry::is_source failed to convert source to `CString`");

            false
        }
    }

    /// Returns `true` if the `source` parameter matches exactly the `source` field in this
    /// `SwapsEntry`
    ///
    /// **Note:** redundant forward slashes are ignored when comparing values.
    pub fn is_exact_source(&self, source: &Source) -> bool {
        let source_cstr = ffi_utils::as_ref_str_to_c_string(source.to_string()).ok();

        if let Some(source_cstr) = source_cstr {
            let state =
                unsafe { libmount::mnt_fs_streq_srcpath(self.inner, source_cstr.as_ptr()) == 1 };
            log::debug!(
                "SwapsEntry::is_exact_source is {:?} the exact source of this entry? {:?}",
                source,
                state
            );

            state
        } else {
            log::debug!("SwapsEntry::is_exact_source failed to convert source to `CString`");

            false
        }
    }

    //---- END predicates
}

impl fmt::Display for SwapsEntry {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // Formatting from Linux Kernel
        // linux/mm/swapfile.c
        // 2702:           seq_puts(swap, "Filename\t\t\t\tType\t\tSize\t\tUsed\t\tPriority\n");
        let mut output: Vec<String> = vec![];
        if let Some(path) = self.source_path() {
            let source_path = format!("{}\t\t", path.display());
            output.push(source_path.to_string());
        }

        if let Some(swap_type) = self.swap_type() {
            output.push(swap_type.to_string());
        }

        let size = self.size();
        output.push(size.to_string());

        let size_used = self.size_used();
        output.push(size_used.to_string());

        let priority = self.priority();
        output.push(priority.to_string());

        write!(f, "{}", output.join("\t\t"))
    }
}