-
Notifications
You must be signed in to change notification settings - Fork 1
Custom syscalls and io_uring subsystem calls #52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
@Avgor46 wow, thanks. It will take some time for me to review, stay tuned. |
|
|
||
| impl Drop for ConnectCall { | ||
| fn drop(&mut self) { | ||
| unsafe { File::from_raw_fd(self.sockfd as i32) }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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?
Molter73
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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<_, _>, _>>() |
| 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a huge deal but do we need to update the edition? Is this a requirement from one of the new dependencies added?
| 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| // 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. |
| } else { | ||
| default | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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());| } | ||
|
|
||
| impl ChmodCall { | ||
| pub fn new(chmod_args: &HashMap<String, String>) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[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?
| #[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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we missing a tight loop check here?
| 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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?
| ring.submission() | ||
| .push(&self.openat) | ||
| .expect("submission queue is full"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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); | |
| } |
| 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar comment about not panicking here, though these might be a bit harder.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see this same error handling is done on other io_uring operations, we should change those as well of we change them here.
| # 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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.