Skip to main content

libcontainer/syscall/
test.rs

1use std::any::Any;
2use std::cell::{Ref, RefCell, RefMut};
3use std::collections::HashMap;
4use std::ffi::{OsStr, OsString};
5use std::os::fd::{AsRawFd, BorrowedFd, RawFd};
6use std::os::unix::io::OwnedFd;
7use std::path::{Path, PathBuf};
8use std::sync::Arc;
9
10use caps::{CapSet, CapsHashSet};
11use nix::mount::{MntFlags, MsFlags};
12use nix::sched::CloneFlags;
13use nix::sys::stat::{Mode, SFlag};
14use nix::unistd::{Gid, Uid};
15use oci_spec::runtime::PosixRlimit;
16
17use super::super::config::PersonalityDomain;
18use super::{Result, Syscall, linux};
19
20#[derive(Clone, PartialEq, Eq, Debug)]
21pub struct MountArgs {
22    pub source: Option<PathBuf>,
23    pub target: PathBuf,
24    pub fstype: Option<String>,
25    pub flags: MsFlags,
26    pub data: Option<String>,
27}
28
29#[derive(Clone, PartialEq, Eq, Debug)]
30pub struct MountFromFdArgs {
31    pub fd: i32,
32    pub target: PathBuf,
33}
34
35#[derive(Clone, PartialEq, Eq, Debug)]
36pub struct MoveMountArgs {
37    pub from_dirfd: i32,
38    pub from_path: Option<OsString>,
39    pub to_dirfd: i32,
40    pub to_path: Option<OsString>,
41    pub flags: u32,
42}
43
44#[derive(Clone, PartialEq, Eq, Debug)]
45pub struct FsopenArgs {
46    pub fsname: Option<String>,
47    pub flags: u32,
48}
49
50#[derive(Clone, PartialEq, Eq, Debug)]
51pub struct MknodArgs {
52    pub path: PathBuf,
53    pub kind: SFlag,
54    pub perm: Mode,
55    pub dev: u64,
56}
57
58#[derive(Clone, PartialEq, Eq, Debug)]
59pub struct ChownArgs {
60    pub path: PathBuf,
61    pub owner: Option<Uid>,
62    pub group: Option<Gid>,
63}
64
65#[derive(Clone, PartialEq, Eq, Debug)]
66pub struct IoPriorityArgs {
67    pub class: i64,
68    pub priority: i64,
69}
70
71#[derive(Clone, PartialEq, Eq, Debug)]
72pub struct MemPolicyArgs {
73    pub mode: i32,
74    pub nodemask: Vec<libc::c_ulong>, // Store the nodemask vector for testing
75    pub maxnode: u64,
76}
77
78#[derive(Clone, PartialEq, Eq, Debug)]
79pub struct UMount2Args {
80    pub target: PathBuf,
81    pub flags: MntFlags,
82}
83
84#[derive(Default)]
85struct Mock {
86    values: Vec<Box<dyn Any>>,
87    ret_err: Option<fn() -> Result<()>>,
88    ret_err_times: usize,
89}
90
91#[derive(PartialEq, Eq, Hash, Copy, Clone)]
92pub enum ArgName {
93    Namespace,
94    Unshare,
95    Mount,
96    MountFromFd,
97    Symlink,
98    Mknod,
99    Chown,
100    Hostname,
101    Domainname,
102    Groups,
103    Capability,
104    IoPriority,
105    MemPolicy,
106    UMount2,
107    MoveMount,
108    Fsopen,
109}
110
111impl ArgName {
112    fn iterator() -> impl Iterator<Item = ArgName> {
113        [
114            ArgName::Namespace,
115            ArgName::Unshare,
116            ArgName::Mount,
117            ArgName::MountFromFd,
118            ArgName::Symlink,
119            ArgName::Mknod,
120            ArgName::Chown,
121            ArgName::Hostname,
122            ArgName::Domainname,
123            ArgName::Groups,
124            ArgName::Capability,
125            ArgName::IoPriority,
126            ArgName::MemPolicy,
127            ArgName::MoveMount,
128        ]
129        .iter()
130        .copied()
131    }
132}
133
134struct MockCalls {
135    args: HashMap<ArgName, RefCell<Mock>>,
136}
137
138impl Default for MockCalls {
139    fn default() -> Self {
140        let mut m = MockCalls {
141            args: HashMap::new(),
142        };
143
144        for name in ArgName::iterator() {
145            m.args.insert(name, RefCell::new(Mock::default()));
146        }
147
148        m
149    }
150}
151
152impl MockCalls {
153    fn act(&self, name: ArgName, value: Box<dyn Any>) -> Result<()> {
154        if self.args.get(&name).unwrap().borrow().ret_err_times > 0 {
155            self.args.get(&name).unwrap().borrow_mut().ret_err_times -= 1;
156            if let Some(e) = &self.args.get(&name).unwrap().borrow().ret_err {
157                return e();
158            }
159        }
160
161        self.args
162            .get(&name)
163            .unwrap()
164            .borrow_mut()
165            .values
166            .push(value);
167        Ok(())
168    }
169
170    fn fetch(&self, name: ArgName) -> Ref<'_, Mock> {
171        self.args.get(&name).unwrap().borrow()
172    }
173
174    fn fetch_mut(&self, name: ArgName) -> RefMut<'_, Mock> {
175        self.args.get(&name).unwrap().borrow_mut()
176    }
177}
178
179#[derive(Default)]
180pub struct TestHelperSyscall {
181    mock_id: RefCell<MockId>,
182    mocks: MockCalls,
183}
184
185pub struct MockId {
186    uid: Uid,
187    gid: Gid,
188    euid: Uid,
189    egid: Gid,
190}
191
192impl Default for MockId {
193    fn default() -> Self {
194        Self {
195            uid: nix::unistd::getuid(),
196            gid: nix::unistd::getgid(),
197            euid: nix::unistd::geteuid(),
198            egid: nix::unistd::getegid(),
199        }
200    }
201}
202
203impl Syscall for TestHelperSyscall {
204    fn as_any(&self) -> &dyn Any {
205        self
206    }
207
208    fn pivot_rootfs(&self, _path: &Path) -> Result<()> {
209        unimplemented!()
210    }
211
212    fn set_ns(&self, rawfd: i32, nstype: CloneFlags) -> Result<()> {
213        self.mocks
214            .act(ArgName::Namespace, Box::new((rawfd, nstype)))
215    }
216
217    fn set_id(&self, _uid: Uid, _gid: Gid) -> Result<()> {
218        self.mock_id.borrow_mut().uid = _uid;
219        self.mock_id.borrow_mut().gid = _gid;
220        self.mock_id.borrow_mut().euid = _uid;
221        self.mock_id.borrow_mut().egid = _gid;
222        Ok(())
223    }
224
225    fn unshare(&self, flags: CloneFlags) -> Result<()> {
226        self.mocks.act(ArgName::Unshare, Box::new(flags))
227    }
228
229    fn set_capability(&self, cset: CapSet, value: &CapsHashSet) -> Result<()> {
230        self.mocks
231            .act(ArgName::Capability, Box::new((cset, value.clone())))
232    }
233
234    fn set_hostname(&self, hostname: &str) -> Result<()> {
235        self.mocks
236            .act(ArgName::Hostname, Box::new(hostname.to_owned()))
237    }
238
239    fn set_domainname(&self, domainname: &str) -> Result<()> {
240        self.mocks
241            .act(ArgName::Domainname, Box::new(domainname.to_owned()))
242    }
243
244    fn set_rlimit(&self, _rlimit: &PosixRlimit) -> Result<()> {
245        todo!()
246    }
247
248    fn get_pwuid(&self, _: u32) -> Option<Arc<OsStr>> {
249        Some(OsString::from("youki").into())
250    }
251
252    fn chroot(&self, _: &Path) -> Result<()> {
253        todo!()
254    }
255
256    fn mount(
257        &self,
258        source: Option<&Path>,
259        target: &Path,
260        fstype: Option<&str>,
261        flags: MsFlags,
262        data: Option<&str>,
263    ) -> Result<()> {
264        // For tests: resolve /proc/self/fd/<n> to the real path before recording.
265        let target_owned = if target.starts_with(Path::new("/proc/self/fd")) {
266            std::fs::read_link(target).unwrap_or_else(|_| target.to_owned())
267        } else {
268            target.to_owned()
269        };
270
271        self.mocks.act(
272            ArgName::Mount,
273            Box::new(MountArgs {
274                source: source.map(|x| x.to_owned()),
275                target: target_owned,
276                fstype: fstype.map(|x| x.to_owned()),
277                flags,
278                data: data.map(|x| x.to_owned()),
279            }),
280        )
281    }
282
283    fn mount_from_fd(&self, source_fd: &OwnedFd, target: &Path) -> Result<()> {
284        self.mocks.act(
285            ArgName::MountFromFd,
286            Box::new(MountFromFdArgs {
287                fd: source_fd.as_raw_fd(),
288                target: target.to_owned(),
289            }),
290        )
291    }
292
293    fn move_mount(
294        &self,
295        from_dirfd: BorrowedFd<'_>,
296        from_path: Option<&str>,
297        to_dirfd: BorrowedFd<'_>,
298        to_path: Option<&str>,
299        flags: u32,
300    ) -> Result<()> {
301        let rec = MoveMountArgs {
302            from_dirfd: from_dirfd.as_raw_fd(),
303            from_path: from_path.map(OsString::from),
304            to_dirfd: to_dirfd.as_raw_fd(),
305            to_path: to_path.map(OsString::from),
306            flags,
307        };
308        self.mocks.act(ArgName::MoveMount, Box::new(rec))
309    }
310
311    fn fsopen(&self, _: Option<&str>, _: u32) -> Result<OwnedFd> {
312        todo!()
313    }
314
315    fn fsconfig(
316        &self,
317        _: BorrowedFd<'_>,
318        _: u32,
319        _: Option<&str>,
320        _: Option<&str>,
321        _: libc::c_int,
322    ) -> Result<()> {
323        todo!()
324    }
325
326    fn fsmount(&self, _: BorrowedFd<'_>, _: u32, _: Option<u64>) -> Result<OwnedFd> {
327        todo!()
328    }
329
330    fn open_tree(&self, _: RawFd, _: Option<&str>, _: u32) -> Result<OwnedFd> {
331        todo!()
332    }
333
334    fn symlink(&self, original: &Path, link: &Path) -> Result<()> {
335        self.mocks.act(
336            ArgName::Symlink,
337            Box::new((original.to_path_buf(), link.to_path_buf())),
338        )
339    }
340
341    fn mknod(&self, path: &Path, kind: SFlag, perm: Mode, dev: u64) -> Result<()> {
342        self.mocks.act(
343            ArgName::Mknod,
344            Box::new(MknodArgs {
345                path: path.to_path_buf(),
346                kind,
347                perm,
348                dev,
349            }),
350        )
351    }
352    fn chown(&self, path: &Path, owner: Option<Uid>, group: Option<Gid>) -> Result<()> {
353        self.mocks.act(
354            ArgName::Chown,
355            Box::new(ChownArgs {
356                path: path.to_path_buf(),
357                owner,
358                group,
359            }),
360        )
361    }
362
363    fn set_groups(&self, groups: &[Gid]) -> Result<()> {
364        self.mocks.act(ArgName::Groups, Box::new(groups.to_vec()))
365    }
366
367    fn close_range(&self, _: i32) -> Result<()> {
368        todo!()
369    }
370
371    fn mount_setattr(
372        &self,
373        _: BorrowedFd<'_>,
374        _: &Path,
375        _: u32,
376        _: &linux::MountAttr,
377        _: libc::size_t,
378    ) -> Result<()> {
379        todo!()
380    }
381
382    fn set_io_priority(&self, class: i64, priority: i64) -> Result<()> {
383        self.mocks.act(
384            ArgName::IoPriority,
385            Box::new(IoPriorityArgs { class, priority }),
386        )
387    }
388
389    fn set_mempolicy(&self, mode: i32, nodemask: &[libc::c_ulong], maxnode: u64) -> Result<()> {
390        self.mocks.act(
391            ArgName::MemPolicy,
392            Box::new(MemPolicyArgs {
393                mode,
394                nodemask: nodemask.to_vec(),
395                maxnode,
396            }),
397        )
398    }
399
400    fn umount2(&self, target: &Path, flags: MntFlags) -> Result<()> {
401        self.mocks.act(
402            ArgName::UMount2,
403            Box::new(UMount2Args {
404                target: target.to_owned(),
405                flags,
406            }),
407        )
408    }
409
410    fn get_uid(&self) -> Uid {
411        self.mock_id.borrow().uid
412    }
413
414    fn get_gid(&self) -> Gid {
415        self.mock_id.borrow().gid
416    }
417
418    fn get_euid(&self) -> Uid {
419        self.mock_id.borrow().euid
420    }
421
422    fn get_egid(&self) -> Gid {
423        self.mock_id.borrow().egid
424    }
425
426    fn personality(&self, _: PersonalityDomain) -> Result<()> {
427        todo!()
428    }
429}
430
431impl TestHelperSyscall {
432    pub fn set_ret_err(&self, name: ArgName, err: fn() -> Result<()>) {
433        self.mocks.fetch_mut(name).ret_err = Some(err);
434        self.set_ret_err_times(name, 1);
435    }
436
437    pub fn set_ret_err_times(&self, name: ArgName, times: usize) {
438        self.mocks.fetch_mut(name).ret_err_times = times;
439    }
440
441    pub fn get_setns_args(&self) -> Vec<(i32, CloneFlags)> {
442        self.mocks
443            .fetch(ArgName::Namespace)
444            .values
445            .iter()
446            .map(|x| *x.downcast_ref::<(i32, CloneFlags)>().unwrap())
447            .collect::<Vec<(i32, CloneFlags)>>()
448    }
449
450    pub fn get_unshare_args(&self) -> Vec<CloneFlags> {
451        self.mocks
452            .fetch(ArgName::Unshare)
453            .values
454            .iter()
455            .map(|x| *x.downcast_ref::<CloneFlags>().unwrap())
456            .collect::<Vec<CloneFlags>>()
457    }
458
459    pub fn get_set_capability_args(&self) -> Vec<(CapSet, CapsHashSet)> {
460        self.mocks
461            .fetch(ArgName::Capability)
462            .values
463            .iter()
464            .map(|x| x.downcast_ref::<(CapSet, CapsHashSet)>().unwrap().clone())
465            .collect::<Vec<(CapSet, CapsHashSet)>>()
466    }
467
468    pub fn get_mount_args(&self) -> Vec<MountArgs> {
469        self.mocks
470            .fetch(ArgName::Mount)
471            .values
472            .iter()
473            .map(|x| x.downcast_ref::<MountArgs>().unwrap().clone())
474            .collect::<Vec<MountArgs>>()
475    }
476
477    pub fn get_mount_from_fd_args(&self) -> Vec<MountFromFdArgs> {
478        self.mocks
479            .fetch(ArgName::MountFromFd)
480            .values
481            .iter()
482            .map(|x| x.downcast_ref::<MountFromFdArgs>().unwrap().clone())
483            .collect::<Vec<MountFromFdArgs>>()
484    }
485
486    pub fn get_symlink_args(&self) -> Vec<(PathBuf, PathBuf)> {
487        self.mocks
488            .fetch(ArgName::Symlink)
489            .values
490            .iter()
491            .map(|x| x.downcast_ref::<(PathBuf, PathBuf)>().unwrap().clone())
492            .collect::<Vec<(PathBuf, PathBuf)>>()
493    }
494
495    pub fn get_mknod_args(&self) -> Vec<MknodArgs> {
496        self.mocks
497            .fetch(ArgName::Mknod)
498            .values
499            .iter()
500            .map(|x| x.downcast_ref::<MknodArgs>().unwrap().clone())
501            .collect::<Vec<MknodArgs>>()
502    }
503
504    pub fn get_chown_args(&self) -> Vec<ChownArgs> {
505        self.mocks
506            .fetch(ArgName::Chown)
507            .values
508            .iter()
509            .map(|x| x.downcast_ref::<ChownArgs>().unwrap().clone())
510            .collect::<Vec<ChownArgs>>()
511    }
512
513    pub fn get_hostname_args(&self) -> Vec<String> {
514        self.mocks
515            .fetch(ArgName::Hostname)
516            .values
517            .iter()
518            .map(|x| x.downcast_ref::<String>().unwrap().clone())
519            .collect::<Vec<String>>()
520    }
521
522    pub fn get_domainname_args(&self) -> Vec<String> {
523        self.mocks
524            .fetch(ArgName::Domainname)
525            .values
526            .iter()
527            .map(|x| x.downcast_ref::<String>().unwrap().clone())
528            .collect::<Vec<String>>()
529    }
530
531    pub fn get_groups_args(&self) -> Vec<Gid> {
532        self.mocks
533            .fetch(ArgName::Groups)
534            .values
535            .iter()
536            .flat_map(|x| x.downcast_ref::<Vec<Gid>>().unwrap().clone())
537            .collect::<Vec<Gid>>()
538    }
539
540    pub fn get_io_priority_args(&self) -> Vec<IoPriorityArgs> {
541        self.mocks
542            .fetch(ArgName::IoPriority)
543            .values
544            .iter()
545            .map(|x| x.downcast_ref::<IoPriorityArgs>().unwrap().clone())
546            .collect::<Vec<IoPriorityArgs>>()
547    }
548
549    pub fn get_mempolicy_args(&self) -> Vec<MemPolicyArgs> {
550        self.mocks
551            .fetch(ArgName::MemPolicy)
552            .values
553            .iter()
554            .map(|x| x.downcast_ref::<MemPolicyArgs>().unwrap().clone())
555            .collect::<Vec<MemPolicyArgs>>()
556    }
557
558    pub fn get_umount_args(&self) -> Vec<UMount2Args> {
559        self.mocks
560            .fetch(ArgName::UMount2)
561            .values
562            .iter()
563            .map(|x| x.downcast_ref::<UMount2Args>().unwrap().clone())
564            .collect::<Vec<UMount2Args>>()
565    }
566}