Skip to content
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

System V shared memory APIs (2nd draft) #2314

Open
wants to merge 58 commits into
base: master
Choose a base branch
from

Conversation

IzawGithub
Copy link

@IzawGithub IzawGithub commented Feb 14, 2024

What does this PR do

Checklist:

  • I have read CONTRIBUTING.md
  • I have written necessary tests and rustdoc comments
  • A change log has been added if this PR modifies nix's API

I've seen issue #1718 and the pull request #1989 made by @arpankapoor from a year ago, that added some support for the System V API.

This is a continuation of this PR, where I tried to implement most of the change suggested by @VorpalBlade.

What's missing

I haven't added any unit test yet, as somehow I'm getting a lot of compilation error on the test, about some missing symbol, but I've got a closed source project where I'm using this draft as a "eat your own dog food", with unit test, and it seems to work fine.

Some of the bitflags, 2 in ShmgetFlag and a lot in SemctlCmd do not exist in libc, even though they are documented.
So to merge this, there is a need to create a PR for libc that add those.

What can be better

Permissions: Could maybe be moved elsewhere? I haven't found any struct already implemented that wrap the octal permission system unix use, so I threw this in 5m.

shmat: I'm really not a fan of returning a void ptr. I've tried to wrap it in a Weak<RefCell<T>> but I get a segfault when it gets dropped.
What would be ideal is for the user to give a generic type T that could be wrapped in a non owning smart pointer that could also be null. That's why I though Weak would be the answer, but I'm not good enough to find the problem.
Currently, the user has to cast it in an unsafe block, to be able to use the value stored, which is awkward and error prone.

I'm not too sure about the linux only bitflags compilation. We should test it for android too, but I don't know how to do that.

@SteveLauC
Copy link
Member

I am trying to add support for System V message queue in #2318

@IzawGithub
Copy link
Author

Hello @SteveLauC,

I think this PR is pretty good now. I've added some basic test, with around 30% coverage of the new function.
What's missing is testing shmdt, which I don't really know how I could do that, and all of the different bitflags, which is a bit overwhelming.

I also didn't implement the Semop function because I have no clue at what it's even supposed to do. If anyone got an idea about how to implement it, it would be more than welcome.

I don't understand why BSD isn't able to build successfully, when it should be working fine.

Is there anything left to be done before a review and an eventual merge?

@SteveLauC
Copy link
Member

Hi @IzawGithub, sorry for the late reply, I plan to take a look at this PR this weekend :)

changelog/2313.added.md Outdated Show resolved Hide resolved
src/sys/mod.rs Outdated
@@ -143,6 +143,10 @@ feature! {
#[allow(missing_docs)]
pub mod sysinfo;

#[allow(missing_docs)]
#[cfg(any(bsd, target_os = "linux", not(target_os = "android")))]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this not(target_os = "android") necessary as any(bsd, target_os = "linux") does not include android

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And it is always ok to not support all the platforms, we can add them incrementally

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed the redundant android target. BSD is still not building and I don't really understand why. Something to do with macro? I'm not proficient with them yet.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BSD is still not building and I don't really understand why.

The OpenBSD and NetBSD CI failed because some missing constants in the libc crate, which means we need to add them before merging this PR if we want to have #[cfg(bsd)s supported.

Let me take a closer look at this and probably give it a fix!

src/sys/system_v.rs Outdated Show resolved Hide resolved
src/sys/mod.rs Outdated Show resolved Hide resolved
changelog/2313.added.md Outdated Show resolved Hide resolved
/// # Ok(())
/// # }
/// ```
pub struct Permissions {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the last 9 bits of those flg arguments, Nix already has a type for it,
nix::sys::stat::Mode, so a new type is probably not necessary.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, wasn't aware of this type. This makes it depend on the fs module but I'm assuming it's fine?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes it depend on the fs module but I'm assuming it's fine?

Yeah, this is unfortunate, ideally, this Mode type should be available if #[cfg(any(feature="fs", feature="system_v")], doing this will require some extra work, so I think it is ok to make system_v depend on fs.

@SteveLauC
Copy link
Member

Some thoughts about these APIs:

  1. Module names

    I kinda think we should separate semaphore set and shared memory segments
    into 2 modules, following the names of their C header files, i.e, sys/sem.rs for
    semaphore set and sys/shm.rs for shared memory segments.

  2. For those APIs, do you think it will be better if we do something like:

    Take semaphore set as an example:

    /// A System V semaphore set.
    pub struct SemaphoreSet(libc::c_int);
    
    pub enum SemCmd {
        IPC_STAT(&mut libc::semid_ds),
        IPC_SET(&libc::semid_ds),
    }
    
    impl SemaphoreSet {
        pub fn new(key: key_t, n_sem: usize, semflg: SemFlg) -> Result<Self>;
        pub fn op<O: AsRef<[Sembuf]>>(&self, ops: O) -> Result<()>;
        pub fn op_timeout<O: AsRef<[Sembuf]>>(&self, ops: O, timeout: TimeSepc) -> Result<()>;
        pub fn ctl(&self, sem_num: usize, cmd: SemCmd) -> Result<Option<libc::c_int>>;
    }

    And to remove a semaphore set, I am thinking about making our wrapper type
    non-Copy, and do this:

    #[allow(missing_copy_implementations)]
    pub struct SemaphoreSet(libc::c_int);
    
    impl SemaphoreSet {
        pub fn remove(self) -> Result<(), (Errno, Self)> {
           let res = unsafe {
               libc::semctl(self.0, /*whatever*/, IPC_RMID)
           };
    
           match Errno::result(res) {
               Ok(_) => Ok(()),
               Err(errno) => Err((errno, self))
           }
        }
    }

@IzawGithub
Copy link
Author

Hello,

Thanks for the comprehensive review! Those are all fair issue, I'll work on them this week.

For your last question about wrapping all the functions, this is certainly doable, but I wasn't sure if it was prefered to match libc as closely as possible or not. I can definitely try to make something more Rusty. Worst case scenario, if it's not ok we could always revert to what's currently there.

Also, once the PR is done, should I clean the history with a rebase before a merge or not?

@SteveLauC
Copy link
Member

Also, once the PR is done, should I clean the history with a rebase before a merge or not?

Nix uses squash merge, so it's fine to leave the commit history here:)

@IzawGithub
Copy link
Author

Hello, all of the issue listed above should be fixed, expect for the c_void where I don't really know how to go forward with.

Nice catch on the UB!
To resolve it, I had to make shmget an unsafe function. This is because, if the user is connecting to a shared memory zone that we haven't created ourselves, then there is no way to make sure the types match.
If we're always creating the new memory zone, then we're sure the type match, so I've added a safe create_and_connect for that.

I'll work on the semaphores during the coming week.

src/sys/mod.rs Outdated Show resolved Hide resolved
Cargo.toml Outdated Show resolved Hide resolved
src/sys/system_v/shm.rs Outdated Show resolved Hide resolved
src/sys/system_v/shm.rs Outdated Show resolved Hide resolved
/// # Ok::<(), Errno>(())
/// ```
///
pub fn create_and_connect(key: key_t, mode: Mode) -> Result<Self> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am curious about why is this called connect(), IMHO, connecting to a segment is kinda similar to attaching to it, which make me a little bit confused, thoughts on this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I didn't really put too much though on the name. Maybe create_and_get? We're creating the segment by using shmget. I'm not the best at naming things, so if you've got a better idea, feel free!

src/sys/system_v/shm.rs Outdated Show resolved Hide resolved
src/sys/system_v/shm.rs Outdated Show resolved Hide resolved
src/sys/system_v/shm.rs Outdated Show resolved Hide resolved
src/sys/system_v/shm.rs Outdated Show resolved Hide resolved
src/sys/system_v/shm.rs Outdated Show resolved Hide resolved
@SteveLauC
Copy link
Member

Hi, I just finished another round of review, and realized we can still have UB even with create_and_connect():

use nix::sys::{
    stat::Mode,
    system_v::shm::{Shm, ShmatFlag},
};

#[tokio::main(flavor = "current_thread")]
async fn main() {
    let mode = Mode::S_IRWXU;

    let shm = Shm::<&()>::create_and_connect(65536, mode).unwrap();
    let mem_segment = shm.attach(ShmatFlag::empty()).unwrap();
    println!("reference = {:p}", *mem_segment);
}

In the above code snippet, we create a new shared memory segment and attach to it, then print the value stored there, a Rust reference can never be NULL, i.e, 0, but with the above code, I got:

$ cargo r -q
reference = 0x0

With the current impl, there is no guarantee that the value stored in a segment is valid for T, perhaps we should add create 2 types here:

  1. UnsafeSharedMemory
  2. SharedMemory

A SharedMemory can ONLY be created via unsafe fn UnsafeShardMemory::assume_init(), thoughts?

@IzawGithub
Copy link
Author

Hello, this is harder than I though!

For your example, after a bit of debugging, I think that pointing to 0x0 is because we're trying to store a type that's a ref, when SharedMemory actually expect a non ref.
T being a generic, I would like to restrict it to only owned data, which would resolve the issue.
However, I don't really see a way to do that. I didn't see any "not a pointer" trait, maybe Default? But that's annoying for the end user if their data struct does not implement default.

The rest of the issue are pretty simple fix I'll work on right away.

@SteveLauC
Copy link
Member

Hi, sorry for the late response, been busy with my work and can barely do open source things on work days.


For your example, after a bit of debugging, I think that pointing to 0x0 is because we're trying to store a type that's a ref, when SharedMemory actually expect a non ref. T being a generic, I would like to restrict it to only owned data, which would resolve the issue.

Even if we restrict T to be owned types, it is still not guaranteed that the value stored in that shared memory is valid for T, so the problem still exists.

From my observation, that memory segment will be zero-initialized, 0 is a valid type for std::num::NonZeroU8:

use nix::sys::{
    stat::Mode,
    system_v::shm::{Shm, ShmatFlag},
};

fn main() {
    let mode = Mode::S_IRWXU;

    let shm = Shm::<std::num::NonZeroU8>::create_and_connect(65536, mode).unwrap();
    let mem_segment = shm.attach(ShmatFlag::empty()).unwrap();
    println!("number = {}", *mem_segment);
}
$ cargo r                      
    Finished dev [unoptimized + debuginfo] target(s) in 0.03s
     Running `target/debug/rust`
number = 0

@SteveLauC SteveLauC mentioned this pull request Apr 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants