file_integrity/src/file_io.rs

Lines

97.30 %

Functions

80.00 %

Regions

92.86 %

Line Count Source (jump to first uncovered line)
1
//! Low-level file I/O operations that don't involve xattrs.
2
3
// Copyright 2023 Zack Weinberg
4
// Licensed under the Apache License, Version 2.0 (the "License");
5
// you may not use this file except in compliance with the License.
6
// You may obtain a copy of the License at
7
// https://www.apache.org/licenses/LICENSE-2.0
8
9
#[cfg(test)]
10
#[path = "test/file_io.rs"]
11
mod test;
12
13
use std::fs::File;
14
use std::io;
15
use std::os::fd::AsRawFd;
16
use std::time::SystemTime;
17
18
use memmap2::{Mmap, MmapOptions};
19
20
/// Succeeds if the file F was opened with O_RDONLY rather than
21
/// O_WRONLY or O_RDWR, fails otherwise.
22 6
pub(crate) fn require_read_only_fd(f: &File) -> io::Result<()> {

Missing instantiations:

file_integrity::file_io::require_read_only_fd
23 6
    // SAFETY: trusts you to supply a valid fd, manual error checking

Missing instantiations:

file_integrity::file_io::require_read_only_fd
24 6
    // required.  Various other safety issues are not relevant when

Missing instantiations:

file_integrity::file_io::require_read_only_fd
25 6
    // the second argument is F_GETFL.

Missing instantiations:

file_integrity::file_io::require_read_only_fd
26 6
    let fl = unsafe { libc::fcntl(f.as_raw_fd(), libc::F_GETFL) };

Missing instantiations:

file_integrity::file_io::require_read_only_fd
27 6
    if fl == -1 {

Missing instantiations:

file_integrity::file_io::require_read_only_fd
28 2
        return Err(io::Error::last_os_error());

Missing instantiations:

file_integrity::file_io::require_read_only_fd
29 4
    }

Missing instantiations:

file_integrity::file_io::require_read_only_fd
30 4
    if (fl & libc::O_ACCMODE) != libc::O_RDONLY {

Missing instantiations:

file_integrity::file_io::require_read_only_fd
31 2
        return Err(io::Error::from_raw_os_error(libc::EINVAL));

Missing instantiations:

file_integrity::file_io::require_read_only_fd
32 2
    }

Missing instantiations:

file_integrity::file_io::require_read_only_fd
33 2
    Ok(())

Missing instantiations:

file_integrity::file_io::require_read_only_fd
34 6
}

Missing instantiations:

file_integrity::file_io::require_read_only_fd
35
36
/// Take an advisory read lock on a file, using flock().  The lock
37
/// will be released when the file is closed (most precisely when the
38
/// OS-level "open file description" is closed).
39
///
40
/// This function waits for the lock to be granted and therefore may
41
/// take a very long time to return.
42
///
43
/// # Bugs
44
///
45
/// Depending on the OS and the file system, this lock may or may not
46
/// block programs that use fcntl() instead of flock() to lock files.
47 3588
fn read_lock(f: &File) -> io::Result<()> {
48 3588
    // SAFETY: trusts you to supply a valid fd, manual error checking required
49 3588
    let status = unsafe { libc::flock(f.as_raw_fd(), libc::LOCK_SH) };
50 3588
    if status != 0 {
51 3
        Err(io::Error::last_os_error())

Missing instantiations:

file_integrity::file_io::read_lock
52
    } else {
53 3585
        Ok(())
54
    }
55 3588
}
56
57
/// Subroutine of checked_file::wrap: Given an open file, look up its
58
/// last modification time, lock it for reading, and create an mmap
59
/// region covering the entire file.
60 3585
pub(crate) fn mtime_and_contents(
61 3585
    file: &File,
62 3585
) -> io::Result<(SystemTime, Mmap)> {
63 1
    let md = file.metadata()?;

Missing instantiations:

file_integrity::file_io::mtime_and_contents
64
65
    // Rust does not allow [u8] to have more than isize::MAX elements.
66
    // It's possible (particularly if usize is only 32 bits) for
67
    // md.len() to be larger than that.  Reject such files now,
68
    // because the OS might allow mappings that are too large for
69
    // isize (but still fit in usize).
70 3584
    let slen: isize = md
71 3584
        .len()
72 3584
        .try_into()
73 3584
        .map_err(|_| io::Error::from_raw_os_error(libc::EFBIG))?;

Missing instantiations:

file_integrity::file_io::mtime_and_contents
file_integrity::file_io::mtime_and_contents::{closure#0}
74
75 1
    read_lock(file)?;

Missing instantiations:

file_integrity::file_io::mtime_and_contents
76
77
    // SAFETY: File mappings are considered unsafe because the data
78
    // in the file _could_ be changed by another process during the
79
    // lifetime of the map, and the kernel _could_ expose that change
80
    // through the map (even for a MAP_PRIVATE mapping), violating
81
    // Rust's expectations that a &[u8] slice is immutable.
82
    //
83
    // We presume that CheckedFiles are intended to be immutable on
84
    // disk (else what's the point of checking them?) so it should be
85
    // OK to trap the unsafety here and present a safe interface to
86
    // users of the crate.  We further mitigate the risk by applying
87
    // an advisory read lock to the file and, at a higher level,
88
    // requiring the File object to be open exclusively for reading.
89
    // We don't, however, attempt to enforce that the file is read-
90
    // only on disk, because that would interfere with use of the
91
    // write_checksum() method, and because the answer to "is the file
92
    // read-only on disk" can, for most files, change at any moment.
93 3582
    let contents = unsafe {
94 3583
        MmapOptions::new()
95 3583
            .len(slen as usize)
96 1
            .map_copy_read_only(file)?

Missing instantiations:

file_integrity::file_io::mtime_and_contents
97
    };
98
99
    // As far as we can tell, it is impossible to make Metadata::modified()
100
    // fail on Unix.  By using map() here, instead of ?, we avoid
101
    // having that show up as a lacuna in branch coverage.
102
    //
103
    // In case we ever get fancier coverage testing that can catch the
104
    // lacuna, this function has been placed at the very bottom of
105
    // this file.  That keeps the lacuna out of the the way of the
106
    // "jump to first uncovered line" feature in llvm-cov output.
107 3582
    md.modified().map(|actual_mtime| (actual_mtime, contents))
108 3585
}