1
0
Fork 0
mirror of https://github.com/containers/youki synced 2024-05-10 17:46:15 +02:00
youki/tests/contest/runtimetest/src/utils.rs
Toru Komatsu 464344923f
Name the test tools `contest` (#2486)
* Name the test tools `contest`

Signed-off-by: utam0k <k0ma@utam0k.jp>

* Address the feedbacks

Signed-off-by: utam0k <k0ma@utam0k.jp>

* Fix a build error

Signed-off-by: utam0k <k0ma@utam0k.jp>

* Fix a workflow

Signed-off-by: utam0k <k0ma@utam0k.jp>

* Address the feedbacks

Signed-off-by: utam0k <k0ma@utam0k.jp>

---------

Signed-off-by: utam0k <k0ma@utam0k.jp>
2024-01-12 14:28:47 +05:30

472 lines
16 KiB
Rust

use nix::sys::stat::stat;
use nix::sys::stat::SFlag;
use std::fs;
use std::fs::metadata;
use std::fs::symlink_metadata;
use std::os::unix::prelude::MetadataExt;
use std::path::PathBuf;
use std::process::Command;
fn test_file_read_access(path: &str) -> Result<(), std::io::Error> {
let _ = std::fs::OpenOptions::new()
.create(false)
.read(true)
.open(path)?;
Ok(())
}
fn test_dir_read_access(path: &str) -> Result<(), std::io::Error> {
let _ = std::fs::read_dir(path)?;
Ok(())
}
fn is_file_like(mode: u32) -> bool {
// for this please refer
// https://stackoverflow.com/questions/40163270/what-is-s-isreg-and-what-does-it-do
// https://linux.die.net/man/2/stat
mode & SFlag::S_IFREG.bits() != 0 || mode & SFlag::S_IFCHR.bits() != 0
}
fn is_dir(mode: u32) -> bool {
mode & SFlag::S_IFDIR.bits() != 0
}
pub fn test_read_access(path: &str) -> Result<(), std::io::Error> {
let fstat = stat(path)?;
let mode = fstat.st_mode;
if is_file_like(mode) {
// we have a file or a char/block device
return test_file_read_access(path);
} else if is_dir(mode) {
return test_dir_read_access(path);
}
Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("cannot test read access for {path:?}, has mode {mode:x}"),
))
}
fn test_file_write_access(path: &str) -> Result<(), std::io::Error> {
let _ = std::fs::OpenOptions::new().write(true).open(path)?;
Ok(())
}
fn test_dir_write_access(path: &str) -> Result<(), std::io::Error> {
let _ = std::fs::OpenOptions::new()
.create(true)
.write(true)
.open(PathBuf::from(path).join("test.txt"))?;
Ok(())
}
pub fn test_write_access(path: &str) -> Result<(), std::io::Error> {
let fstat = stat(path)?;
let mode = fstat.st_mode;
if is_file_like(mode) {
// we have a file or a char/block device
return test_file_write_access(path);
} else if is_dir(mode) {
return test_dir_write_access(path);
}
Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("cannot test write access for {path:?}, has mode {mode:x}"),
))
}
pub fn test_file_executable(path: &str) -> Result<(), std::io::Error> {
let fstat = stat(path)?;
let mode = fstat.st_mode;
if is_file_like(mode) {
Command::new(path).output()?;
return Ok(());
}
Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("{path:?} is directory, so cannot execute"),
))
}
pub fn test_dir_update_access_time(path: &str) -> Result<(), std::io::Error> {
println!("test_dir_update_access_time path: {path:?}");
let metadata = fs::metadata(PathBuf::from(path))?;
let rest = metadata.accessed();
let first_access_time = rest.unwrap();
println!("{path:?} dir first access time is {first_access_time:?}");
// execute ls command to update access time
Command::new("ls")
.arg(path)
.output()
.expect("execute ls command error");
// second get access time
let metadata = fs::metadata(PathBuf::from(path))?;
let rest = metadata.accessed();
let second_access_time = rest.unwrap();
println!("{path:?} dir second access time is {second_access_time:?}");
if first_access_time == second_access_time {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("cannot update access time for path {path:?}"),
));
}
Ok(())
}
pub fn test_dir_not_update_access_time(path: &str) -> Result<(), std::io::Error> {
println!("test_dir_not_update_access_time path: {path:?}");
let metadata = fs::metadata(PathBuf::from(path))?;
let rest = metadata.accessed();
let first_access_time = rest.unwrap();
println!("{path:?} dir first access time is {first_access_time:?}");
// execute ls command to update access time
Command::new("ls")
.arg(path)
.output()
.expect("execute ls command error");
// second get access time
let metadata = fs::metadata(PathBuf::from(path))?;
let rest = metadata.accessed();
let second_access_time = rest.unwrap();
println!("{path:?} dir second access time is {second_access_time:?}");
if first_access_time != second_access_time {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("cannot update access time for path {path:?}"),
));
}
Ok(())
}
pub fn test_device_access(path: &str) -> Result<(), std::io::Error> {
println!("test_device_access path: {path:?}");
let _ = std::fs::OpenOptions::new()
.create(true)
.write(true)
.open(PathBuf::from(path).join("null"))?;
Ok(())
}
pub fn test_device_unaccess(path: &str) -> Result<(), std::io::Error> {
println!("test_device_unaccess path: {path:?}");
let _ = std::fs::OpenOptions::new()
.create(true)
.write(true)
.open(PathBuf::from(path).join("null"))?;
Ok(())
}
// https://man7.org/linux/man-pages/man2/mount_setattr.2.html
// When a file is accessed via this mount, update the
// file's last access time (atime) only if the current
// value of atime is less than or equal to the file's
// last modification time (mtime) or last status
// change time (ctime).
// case:
// 1. create test.txt file, get one atime
// 2. cat a.txt, get two atime; check atime whether update, conditions are met atime less than or equal mtime or ctime
// 3. cat a.txt, get three atime, check now two atime whether equal three atime
pub fn test_mount_releatime_option(path: &str) -> Result<(), std::io::Error> {
let test_file_path = PathBuf::from(path).join("test.txt");
Command::new("touch")
.arg(test_file_path.to_str().unwrap())
.output()?;
let one_metadata = fs::metadata(test_file_path.clone())?;
println!(
"{:?} file one metadata atime is {:?},mtime is {:?},current time is{:?}",
test_file_path,
one_metadata.atime(),
one_metadata.mtime(),
std::time::SystemTime::now()
);
std::thread::sleep(std::time::Duration::from_millis(1000));
// execute cat command to update access time
Command::new("cat")
.arg(test_file_path.to_str().unwrap())
.output()
.expect("execute cat command error");
let two_metadata = fs::metadata(test_file_path.clone())?;
println!(
"{:?} file two metadata atime is {:?},mtime is {:?},current time is{:?}",
test_file_path,
two_metadata.atime(),
two_metadata.mtime(),
std::time::SystemTime::now()
);
if one_metadata.atime() == two_metadata.atime() {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!(
"not update access time for file {:?}",
test_file_path.to_str()
),
));
}
// execute cat command to update access time
std::thread::sleep(std::time::Duration::from_millis(1000));
Command::new("cat")
.arg(test_file_path.to_str().unwrap())
.output()
.expect("execute cat command error");
let three_metadata = fs::metadata(test_file_path.clone())?;
println!(
"{:?} file three metadata atime is {:?}",
test_file_path,
three_metadata.atime()
);
if two_metadata.atime() != three_metadata.atime() {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("update access time for file {:?}", test_file_path.to_str()),
));
}
Ok(())
}
// case: because filesystem having relatime option
// 1. create test.txt file, get one atime
// 2. cat a.txt, get two atime; check atime whether update
// 3. cat a.txt, get three atime, check now two atime whether equal three atime
pub fn test_mount_noreleatime_option(path: &str) -> Result<(), std::io::Error> {
let test_file_path = PathBuf::from(path).join("noreleatime.txt");
Command::new("touch")
.arg(test_file_path.to_str().unwrap())
.output()?;
let one_metadata = fs::metadata(test_file_path.clone())?;
println!(
"{:?} file one atime is {:?},mtime is {:?}, current time is {:?}",
test_file_path,
one_metadata.atime(),
one_metadata.mtime(),
std::time::SystemTime::now()
);
std::thread::sleep(std::time::Duration::from_millis(1000));
// execute cat command to update access time
Command::new("cat")
.arg(test_file_path.to_str().unwrap())
.output()
.expect("execute cat command error");
let two_metadata = fs::metadata(test_file_path.clone())?;
println!(
"{:?} file two atime is {:?},mtime is {:?},current time is {:?}",
test_file_path,
two_metadata.atime(),
two_metadata.mtime(),
std::time::SystemTime::now()
);
if one_metadata.atime() == two_metadata.atime() {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!(
"not update access time for file {:?}",
test_file_path.to_str()
),
));
}
// execute cat command to update access time
std::thread::sleep(std::time::Duration::from_millis(1000));
Command::new("cat")
.arg(test_file_path.to_str().unwrap())
.output()
.expect("execute cat command error");
let three_metadata = fs::metadata(test_file_path.clone())?;
println!(
"{:?} file three atime is {:?},mtime is {:?},current time is {:?}",
test_file_path,
three_metadata.atime(),
three_metadata.mtime(),
std::time::SystemTime::now()
);
if two_metadata.atime() != three_metadata.atime() {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("update access time for file {:?}", test_file_path.to_str()),
));
}
Ok(())
}
// Do not update access times for (all types of) files on this mount.
// case:
// 1. touch rnoatime.txt file, get atime
// 2. cat rnoatime.txt, check atime whether update, if update return error, else return Ok
pub fn test_mount_rnoatime_option(path: &str) -> Result<(), std::io::Error> {
let test_file_path = PathBuf::from(path).join("rnoatime.txt");
Command::new("touch")
.arg(test_file_path.to_str().unwrap())
.output()?;
let one_metadata = fs::metadata(test_file_path.clone())?;
println!(
"{:?} file one atime is {:?},mtime is {:?}, current time is {:?}",
test_file_path,
one_metadata.atime(),
one_metadata.mtime(),
std::time::SystemTime::now()
);
std::thread::sleep(std::time::Duration::from_millis(1000));
// execute cat command to update access time
Command::new("cat")
.arg(test_file_path.to_str().unwrap())
.output()
.expect("execute cat command error");
let two_metadata = fs::metadata(test_file_path.clone())?;
println!(
"{:?} file two atime is {:?},mtime is {:?},current time is {:?}",
test_file_path,
two_metadata.atime(),
two_metadata.mtime(),
std::time::SystemTime::now()
);
if one_metadata.atime() != two_metadata.atime() {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!(
"update access time for file {:?}, expected not update",
test_file_path.to_str()
),
));
}
Ok(())
}
// Always update the last access time (atime) when files are accessed on this mount.
pub fn test_mount_rstrictatime_option(path: &str) -> Result<(), std::io::Error> {
let test_file_path = PathBuf::from(path).join("rstrictatime.txt");
Command::new("touch")
.arg(test_file_path.to_str().unwrap())
.output()?;
let one_metadata = fs::metadata(test_file_path.clone())?;
println!(
"{:?} file one atime is {:?},mtime is {:?}, current time is {:?}",
test_file_path,
one_metadata.atime(),
one_metadata.mtime(),
std::time::SystemTime::now()
);
std::thread::sleep(std::time::Duration::from_millis(1000));
// execute cat command to update access time
Command::new("cat")
.arg(test_file_path.to_str().unwrap())
.output()
.expect("execute cat command error");
let two_metadata = fs::metadata(test_file_path.clone())?;
println!(
"{:?} file two atime is {:?},mtime is {:?},current time is {:?}",
test_file_path,
two_metadata.atime(),
two_metadata.mtime(),
std::time::SystemTime::now()
);
if one_metadata.atime() == two_metadata.atime() {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!(
"not update access time for file {:?}",
test_file_path.to_str()
),
));
}
// execute cat command to update access time
std::thread::sleep(std::time::Duration::from_millis(1000));
Command::new("cat")
.arg(test_file_path.to_str().unwrap())
.output()
.expect("execute cat command error");
let three_metadata = fs::metadata(test_file_path.clone())?;
println!(
"{:?} file three atime is {:?},mtime is {:?},current time is {:?}",
test_file_path,
two_metadata.atime(),
two_metadata.mtime(),
std::time::SystemTime::now()
);
if two_metadata.atime() == three_metadata.atime() {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("update access time for file {:?}", test_file_path.to_str()),
));
}
Ok(())
}
pub fn test_mount_rnosymfollow_option(path: &str) -> Result<(), std::io::Error> {
let path = format!("{}/{}", path, "link");
let metadata = match symlink_metadata(path.clone()) {
Ok(metadata) => metadata,
Err(e) => {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("get file symlink_metadata err {path:?}, {e}"),
));
}
};
// check symbolic is followed
if metadata.file_type().is_symlink() && metadata.mode() & 0o777 == 0o777 {
Ok(())
} else {
Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("get file symlink_metadata err {path:?}"),
))
}
}
pub fn test_mount_rsymfollow_option(path: &str) -> Result<(), std::io::Error> {
let path = format!("{}/{}", path, "link");
let metadata = match symlink_metadata(path.clone()) {
Ok(metadata) => metadata,
Err(e) => {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("get file symlink_metadata err {path:?}, {e}"),
));
}
};
// check symbolic is followed
if metadata.file_type().is_symlink() && metadata.mode() & 0o777 == 0o777 {
Ok(())
} else {
Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("get file symlink_metadata err {path:?}"),
))
}
}
pub fn test_mount_rsuid_option(path: &str) -> Result<(), std::io::Error> {
let path = PathBuf::from(path).join("file");
let metadata = match metadata(path.clone()) {
Ok(metadata) => metadata,
Err(e) => {
return Err(std::io::Error::new(std::io::ErrorKind::Other, e));
}
};
// check suid and sgid
let suid = metadata.mode() & 0o4000 == 0o4000;
let sgid = metadata.mode() & 0o2000 == 0o2000;
println!("suid: {suid:?},sgid: {sgid:?}");
if suid && sgid {
return Ok(());
}
Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("rsuid error {path:?}"),
))
}