1
//! Testing support functions, to more easily make a bunch of directories and
2
//! links.
3
//!
4
//! This module is only built when compiling tests.
5

            
6
use std::{
7
    fs::{self, File},
8
    io::Write,
9
    path::{Path, PathBuf},
10
};
11

            
12
#[cfg(target_family = "unix")]
13
use std::os::unix::{self, fs::PermissionsExt};
14

            
15
use crate::Mistrust;
16

            
17
/// A temporary directory with convenience functions to build items inside it.
18
#[derive(Debug)]
19
pub(crate) struct Dir {
20
    /// The temporary directory
21
    toplevel: tempfile::TempDir,
22
    /// Canonicalized path to the temporary directory
23
    canonical_root: PathBuf,
24
}
25

            
26
/// When creating a link, are we creating a directory link or a file link?
27
///
28
/// (These are the same on Unix, and different on windows.)
29
#[cfg(target_family = "unix")]
30
#[derive(Copy, Clone, Debug)]
31
pub(crate) enum LinkType {
32
    Dir,
33
    File,
34
}
35

            
36
impl Dir {
37
    /// Make a new temporary directory
38
46
    pub(crate) fn new() -> Self {
39
46
        let toplevel = tempfile::TempDir::new().expect("Can't get tempfile");
40
46
        let canonical_root = toplevel.path().canonicalize().expect("Can't canonicalize");
41
46

            
42
46
        Dir {
43
46
            toplevel,
44
46
            canonical_root,
45
46
        }
46
46
    }
47

            
48
    /// Return the canonical path of the directory's root.
49
46
    pub(crate) fn canonical_root(&self) -> &Path {
50
46
        self.canonical_root.as_path()
51
46
    }
52

            
53
    /// Return the path to the temporary directory's root relative to our working directory.
54
2
    pub(crate) fn relative_root(&self) -> PathBuf {
55
2
        let mut cwd = std::env::current_dir().expect("no cwd");
56
2
        let mut relative = PathBuf::new();
57
        // TODO(nickm): I am reasonably confident that this will not work
58
        // correctly on windows.
59
14
        while !self.toplevel.path().starts_with(&cwd) {
60
12
            assert!(cwd.pop());
61
12
            relative.push("..");
62
        }
63
2
        relative.join(
64
2
            self.toplevel
65
2
                .path()
66
2
                .strip_prefix(cwd)
67
2
                .expect("error computing common ancestor"),
68
2
        )
69
2
    }
70

            
71
    /// Return the path of `p` within this temporary directory.
72
    ///
73
    /// Requires that `p` is a relative path.
74
380
    pub(crate) fn path(&self, p: impl AsRef<Path>) -> PathBuf {
75
380
        let p = p.as_ref();
76
380
        assert!(p.is_relative());
77
380
        self.canonical_root.join(p)
78
380
    }
79

            
80
    /// Make a  directory at `p` within this temporary directory, creating
81
    /// parent directories as needed.
82
    ///
83
    /// Requires that `p` is a relative path.
84
92
    pub(crate) fn dir(&self, p: impl AsRef<Path>) {
85
92
        fs::create_dir_all(self.path(p)).expect("Can't create directory.");
86
92
    }
87

            
88
    /// Make a small file at `p` within this temporary directory, creating
89
    /// parent directories as needed.
90
    ///
91
    /// Requires that `p` is a relative path.
92
28
    pub(crate) fn file(&self, p: impl AsRef<Path>) {
93
28
        self.dir(p.as_ref().parent().expect("Tempdir had no parent"));
94
28
        let mut f = File::create(self.path(p)).expect("Can't create file");
95
28
        f.write_all(&b"This space is intentionally left blank"[..])
96
28
            .expect("Can't write");
97
28
    }
98

            
99
    /// Make a relative link from "original" to "link" within this temporary
100
    /// directory, where `original` is relative
101
    /// to the directory containing `link`, and `link` is relative to the temporary directory.
102
    #[cfg(target_family = "unix")]
103
18
    pub(crate) fn link_rel(
104
18
        &self,
105
18
        link_type: LinkType,
106
18
        original: impl AsRef<Path>,
107
18
        link: impl AsRef<Path>,
108
18
    ) {
109
18
        {
110
18
            let _ = link_type;
111
18
            unix::fs::symlink(original.as_ref(), self.path(link)).expect("Can't symlink");
112
18
        }
113
18

            
114
18
        // Windows does support symlinks but it requires elevated privileges. For more information,
115
18
        // please have a look at:
116
18
        // https://docs.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/create-symbolic-links
117
18
    }
118

            
119
    /// As `link_rel`, but create an absolute link.  `original` is now relative
120
    /// to the temporary directory.
121
    #[cfg(target_family = "unix")]
122
2
    pub(crate) fn link_abs(
123
2
        &self,
124
2
        link_type: LinkType,
125
2
        original: impl AsRef<Path>,
126
2
        link: impl AsRef<Path>,
127
2
    ) {
128
2
        self.link_rel(link_type, self.path(original), link);
129
2
    }
130

            
131
    /// Change the unix permissions of a file.
132
    ///
133
    /// Requires that `p` is a relative path.
134
    ///
135
    /// Does nothing on windows.
136
124
    pub(crate) fn chmod(&self, p: impl AsRef<Path>, mode: u32) {
137
124
        #[cfg(target_family = "unix")]
138
124
        {
139
124
            let perm = fs::Permissions::from_mode(mode);
140
124
            fs::set_permissions(self.path(p), perm).expect("can't chmod");
141
124
        }
142
124
        #[cfg(not(target_family = "unix"))]
143
124
        {
144
124
            let (_, _) = (p, mode);
145
124
        }
146
124
    }
147
}
148

            
149
/// A utility type to represent the different operations available for a MistrustBuilder.
150
#[derive(Debug)]
151
pub(crate) enum MistrustOp<'a> {
152
    IgnorePrefix(&'a Path),
153
    DangerouslyTrustEveryone(),
154
    TrustNoGroupId(),
155

            
156
    #[cfg(target_family = "unix")]
157
    TrustAdminOnly(),
158

            
159
    #[cfg(target_family = "unix")]
160
    TrustGroup(u32),
161
}
162

            
163
/// A convenience function to construct a Mistrust type using a set of given operations.
164
22
pub(crate) fn mistrust_build(ops: &[MistrustOp]) -> Mistrust {
165
22
    ops.iter()
166
47
        .fold(&mut Mistrust::builder(), |m, op| {
167
36
            match op {
168
20
                MistrustOp::IgnorePrefix(prefix) => m.ignore_prefix(prefix),
169

            
170
2
                MistrustOp::DangerouslyTrustEveryone() => m.dangerously_trust_everyone(),
171

            
172
                MistrustOp::TrustNoGroupId() => {
173
                    // We call `m.trust_no_group_id()` on platforms where it is available.
174
                    // Otherwise, we simply return `m` unmodified here.
175
                    #[cfg(all(
176
                        target_family = "unix",
177
                        not(target_os = "ios"),
178
                        not(target_os = "android"),
179
                        not(target_os = "tvos")
180
                    ))]
181
10
                    return m.trust_no_group_id();
182

            
183
                    #[cfg(not(all(
184
                        target_family = "unix",
185
                        not(target_os = "ios"),
186
                        not(target_os = "android"),
187
                        not(target_os = "tvos")
188
                    )))]
189
                    return m;
190
                }
191

            
192
                #[cfg(target_family = "unix")]
193
                MistrustOp::TrustAdminOnly() => {
194
                    #[cfg(all(
195
                        target_family = "unix",
196
                        not(target_os = "ios"),
197
                        not(target_os = "android")
198
                    ))]
199
                    return m.trust_admin_only();
200
                    #[cfg(not(all(
201
                        target_family = "unix",
202
                        not(target_os = "ios"),
203
                        not(target_os = "android")
204
                    )))]
205
                    return m;
206
                }
207

            
208
                #[cfg(target_family = "unix")]
209
4
                MistrustOp::TrustGroup(gid) => {
210
4
                    #[cfg(all(
211
4
                        target_family = "unix",
212
4
                        not(target_os = "ios"),
213
4
                        not(target_os = "android")
214
4
                    ))]
215
4
                    return m.trust_group(*gid);
216
                    #[cfg(not(all(
217
                        target_family = "unix",
218
                        not(target_os = "ios"),
219
                        not(target_os = "android")
220
                    )))]
221
                    return m;
222
                }
223
            }
224
47
        })
225
22
        .build()
226
22
        .expect("Unable to build Mistrust object")
227
22
}