Custom syscalls and io_uring subsystem calls#52
Conversation
|
@Avgor46 wow, thanks. It will take some time for me to review, stay tuned. |
src/worker/syscalls/connect.rs
Outdated
|
|
||
| impl Drop for ConnectCall { | ||
| fn drop(&mut self) { | ||
| unsafe { File::from_raw_fd(self.sockfd as i32) }; |
There was a problem hiding this comment.
@Avgor46 can you clarify why it's done this way? I assume it will make the file descriptor go away when leaving the scope, but why not just close it explicitly?
There was a problem hiding this comment.
replaced all from_raw_fd to raw close syscall
Molter73
left a comment
There was a problem hiding this comment.
Thanks for this PR, it is quite extensive!
I'm leaving a few comments, I've gone through about half the syscalls. Still need to go through the io_uring stuff and the toml files, hopefully I can do that tomorrow morning.
src/lib.rs
Outdated
| let s = String::deserialize(deserializer)?; | ||
| let mut map = HashMap::new(); | ||
| for arg in s.split(',').filter(|x| !x.is_empty()) { | ||
| let parts = arg.split_once('='); | ||
| let Some((key, value)) = parts else { | ||
| return Err(serde::de::Error::custom( | ||
| "invalid syscall arguments format", | ||
| )); | ||
| }; | ||
| map.insert(key.to_string(), value.to_string()); | ||
| } | ||
| Ok(map) |
There was a problem hiding this comment.
This might make the code a bit too dense, but how about doing this all in a single functional operation?
| let s = String::deserialize(deserializer)?; | |
| let mut map = HashMap::new(); | |
| for arg in s.split(',').filter(|x| !x.is_empty()) { | |
| let parts = arg.split_once('='); | |
| let Some((key, value)) = parts else { | |
| return Err(serde::de::Error::custom( | |
| "invalid syscall arguments format", | |
| )); | |
| }; | |
| map.insert(key.to_string(), value.to_string()); | |
| } | |
| Ok(map) | |
| String::deserialize(deserializer)? | |
| .split(',') | |
| .filter(|x| !x.is_empty()) | |
| .map(|arg| match arg.split_once('=') { | |
| Some((key, value)) => Ok((key.to_string(), value.to_string())), | |
| None => Err(serde::de::Error::custom( | |
| "invalid syscall arguments format", | |
| )), | |
| }) | |
| .collect::<Result<HashMap<_, _>, _>>() |
src/lib.rs
Outdated
| let parts = arg.split_once('='); | ||
| let Some((key, value)) = parts else { | ||
| return Err(serde::de::Error::custom( | ||
| "invalid syscall arguments format", |
There was a problem hiding this comment.
We might want to consider adding the offending argument to this string to help debug the error.
| name = "berserker" | ||
| version = "0.1.0" | ||
| edition = "2021" | ||
| edition = "2024" |
There was a problem hiding this comment.
Not a huge deal but do we need to update the edition? Is this a requirement from one of the new dependencies added?
There was a problem hiding this comment.
I used 'let chains' feature, which is available in the 2024 edition
There was a problem hiding this comment.
Oh cool, I missed that, thanks!
src/worker/syscalls/mod.rs
Outdated
| let rng = thread_rng(); | ||
| let mut rng_iter = rng.sample_iter(exp); | ||
|
|
||
| let syscall = Sysno::from(*syscall_nr); |
There was a problem hiding this comment.
[nit] This same operation is being done in line 87, we can probably move this line above that and re-use syscall, since it seems Sysno is represented as an i32, so it shouldn't be a big deal to copy it.
|
|
||
| impl SysCaller for ListenCall { | ||
| fn init(&mut self) -> Result<usize, Errno> { | ||
| // Create socket directly instead of calling socket_call.call() |
There was a problem hiding this comment.
| // Create socket directly instead of calling socket_call.call() | |
| // Create socket directly instead of calling socket_call.call() | |
| // since that would immediately close the fd. |
src/worker/syscalls/mod.rs
Outdated
| } else { | ||
| default | ||
| } | ||
| } |
There was a problem hiding this comment.
This might be a bit of overkill, but do we maybe want to have a wrapper type around HashMap<String, String> and make this the get method for that type? Something like this:
struct SyscallArgs(HashMap<String, String>);
impl SyscallArgs {
fn get<T: FromStr>(&self, name: &str, default: T) -> T {
if let Some(arg) = self.0.get(name) {
arg.parse().unwrap_or(default)
} else {
default
}
}
}It would make it clearer at call sites IMO, going from:
pub fn new(chmod_args: &HashMap<String, String>) -> Self {
let pathname =
get_argument(chmod_args, "pathname", CString::new("/tmp").unwrap());
// ...To something like:
pub fn new(chmod_args: &SyscallArgs) -> Self {
let pathname =
chmod_args.get("pathname", CString::new("/tmp").unwrap());
src/worker/syscalls/chmod.rs
Outdated
| } | ||
|
|
||
| impl ChmodCall { | ||
| pub fn new(chmod_args: &HashMap<String, String>) -> Self { |
There was a problem hiding this comment.
[nit] I find it a bit redundant to have <syscall>_args on every new method when we are already in the syscall caller implementation block, any reason to not name the argument args directly?
src/worker/syscalls/ioctl.rs
Outdated
| #[derive(Debug)] | ||
| pub struct IoctlCall { | ||
| pub fd: usize, | ||
| pub op: usize, | ||
| pub argp: usize, | ||
| } | ||
|
|
||
| impl IoctlCall { | ||
| pub fn new(_: &HashMap<String, String>) -> Self { | ||
| let fd = 0; // Will be initialized in init() | ||
| let op = 0; // Default value, can be overridden if needed | ||
| let argp = 0; // Default value, can be overridden if needed | ||
|
|
||
| Self { fd, op, argp } | ||
| } | ||
| } |
There was a problem hiding this comment.
[nit]
| #[derive(Debug)] | |
| pub struct IoctlCall { | |
| pub fd: usize, | |
| pub op: usize, | |
| pub argp: usize, | |
| } | |
| impl IoctlCall { | |
| pub fn new(_: &HashMap<String, String>) -> Self { | |
| let fd = 0; // Will be initialized in init() | |
| let op = 0; // Default value, can be overridden if needed | |
| let argp = 0; // Default value, can be overridden if needed | |
| Self { fd, op, argp } | |
| } | |
| } | |
| #[derive(Debug, Default)] | |
| pub struct IoctlCall { | |
| pub fd: usize, | |
| pub op: usize, | |
| pub argp: usize, | |
| } | |
| impl IoctlCall { | |
| pub fn new(_: &HashMap<String, String>) -> Self { | |
| // Zero initialize all fields, fd will be initialized in `Syscaller::init`. | |
| // All other fields can be overridden as needed | |
| Default::default() | |
| } | |
| } |
| } | ||
|
|
||
| impl SysCaller for MountCall { | ||
| fn call(&self) -> Result<usize, Errno> { |
There was a problem hiding this comment.
I know mount is probably only used with bogus inputs but, just in case it is not, do we want to umount if we successfully mounted the volume?
| ), | ||
| } | ||
|
|
||
| // Otherwise calculate waiting time |
There was a problem hiding this comment.
Are we missing a tight loop check here?
src/worker/io_uring/mod.rs
Outdated
| fn get_argument<T: FromStr>( | ||
| args: &HashMap<String, String>, | ||
| name: &str, | ||
| default: T, | ||
| ) -> T { | ||
| if let Some(arg) = args.get(name) { | ||
| arg.parse().unwrap_or(default) | ||
| } else { | ||
| default | ||
| } | ||
| } |
There was a problem hiding this comment.
Similar comment about the SyscallArgs wrapper type here, maybe a IOUringArgs type? Or maybe an ArgsMap that we can use for both syscalls and io_uring? WDYT?
There was a problem hiding this comment.
replaced SyscallArgs and IOUringArgs with ArgsMap
src/worker/io_uring/openat.rs
Outdated
| ring.submission() | ||
| .push(&self.openat) | ||
| .expect("submission queue is full"); |
There was a problem hiding this comment.
Maybe we can be a bit more graceful here, unless we actually want to fully crash, but we probably don't.
| ring.submission() | |
| .push(&self.openat) | |
| .expect("submission queue is full"); | |
| if ring.submission().push(&self.openat).is_err() { | |
| return Err(Errno::ENOSPC); | |
| } |
src/worker/io_uring/openat.rs
Outdated
| ring.submit_and_wait(1).expect("submission failed"); | ||
|
|
||
| let cqe = ring.completion().next().expect("completion queue is empty"); |
There was a problem hiding this comment.
Similar comment about not panicking here, though these might be a bit harder.
There was a problem hiding this comment.
I see this same error handling is done on other io_uring operations, we should change those as well of we change them here.
There was a problem hiding this comment.
It was intended to indicate an error with the io_uring subsystem, and there is no reason to continue execution. However, classic error handling with Result has its own advantages. I don't know what the best choice is here. Nevertheless, replaced with Result.
| # How often to invoke a syscall. Parameter of exponential distribution. | ||
| arrival_rate = 10.0 | ||
| # Syscall number to invoke. | ||
| syscall_nr = 162 |
There was a problem hiding this comment.
Not something to do here but, in the interest of being a bit more user friendly, we might want to allow specifying the syscall by name in the future.
|
Did some testing, I think all looks good, thanks @Avgor46. |
Hi!
We have implemented custom, user-parametrized syscall generation. Providing arguments to system calls allows them to complete successfully and makes the workload more “real.” We have also added initial support for generating io_uring subsystem events.