Skip to content

fs::openat2 is unavailable when building with --cfg=rustix_no_linux_raw (e.g., in AOSP) - #1713

Open
tylersaunders wants to merge 1 commit into
bytecodealliance:mainfrom
tylersaunders:openat2-android
Open

tylersaunders wants to merge 1 commit into
bytecodealliance:mainfrom
tylersaunders:openat2-android

Conversation

@tylersaunders

Copy link
Copy Markdown

Hello, while moving some cpp code over to rust inside of AOSP, I encounted openat2 not being useable on android linux due to some configuration, and a minor issue with the open_how struct not being available outside of linux_raw_sys

rustix::fs::openat2 is currently gated on #[cfg(linux_raw_dep)] in src/fs/mod.rs, src/backend/libc/fs/syscalls.rs, and tests/fs/main.rs, making it unavailable on Linux and Android when building with --cfg=rustix_no_linux_raw (such as in AOSP, which builds rustix with the libc backend and without linux-raw-sys).

Background Context

In #1478 (fda7cbe6), --cfg=rustix_no_linux_raw was introduced so projects like AOSP and Mesa3D can build rustix on Linux and Android using the libc backend without depending on linux-raw-sys.

In that change, openat2 was switched from #[cfg(linux_kernel)] to #[cfg(linux_raw_dep)] in:

  • src/fs/mod.rs (mod openat2; and pub use openat2::openat2;)
  • src/backend/libc/fs/syscalls.rs (pub(crate) fn openat2)

However, all other openat2 support in the libc backend is already independent of linux-raw-sys and gated on #[cfg(linux_kernel)]:

  • rustix::fs::ResolveFlags in src/backend/libc/fs/types.rs is gated on #[cfg(linux_kernel)] (and its rustdoc links to [openat2]: crate::fs::openat2, which is broken when not(linux_raw_dep)).
  • use crate::fs::ResolveFlags and const SYS_OPENAT2 = 437 in src/backend/libc/fs/syscalls.rs are already gated on #[cfg(linux_kernel)].

The only dependency on linux-raw-sys inside src/backend/libc/fs/syscalls.rs::openat2 is use linux_raw_sys::general::open_how; for the 24-byte kernel ABI struct (struct open_how { flags: u64, mode: u64, resolve: u64 }). Note that libc::open_how cannot be used directly because it is not defined for target_os = "android" in libc and is marked #[non_exhaustive] on target_os = "linux".

Proposed Solution (see attached commit)

  1. In src/backend/libc/fs/syscalls.rs::openat2, define #[repr(C)] struct open_how { flags: u64, mode: u64, resolve: u64 } when #[cfg(not(linux_raw_dep))] (retaining use linux_raw_sys::general::open_how; when #[cfg(linux_raw_dep)]).
  2. Change the cfg gate on openat2 in src/fs/mod.rs, src/backend/libc/fs/syscalls.rs, and tests/fs/main.rs from #[cfg(linux_raw_dep)] back to #[cfg(linux_kernel)].

I've tested & verified the behavior of this patch in one of our cpp -> rust migrations inside of AOSP. Ideally I'd like to land this upstream rather than maintaining our own AOSP patch downstream.

`rustix::fs::openat2` and its libc backend implementation were gated on
`#[cfg(linux_raw_dep)]` in commit fda7cbe ("rustix: on Linux, support
a build without linux-raw-sys (bytecodealliance#1478)") because
`backend::fs::syscalls::openat2` imported
`linux_raw_sys::general::open_how`. Meanwhile, the supporting
`ResolveFlags` bitflags and `SYS_OPENAT2` constant in the libc backend
remained gated on `#[cfg(linux_kernel)]`.

Because `libc::open_how` is not defined for `target_os = "android"` and
is marked `#[non_exhaustive]` on `target_os = "linux"`, define
`#[repr(C)] struct open_how` locally in `backend::fs::syscalls::openat2`
when `#[cfg(not(linux_raw_dep))]` and gate `openat2` on
`#[cfg(linux_kernel)]`. This makes `rustix::fs::openat2` usable on
Android and Linux when building with `--cfg=rustix_no_linux_raw`.
flags: u64,
mode: u64,
resolve: u64,
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

libc (now?) has an open_how declaration. Should we use that instead of declaring this type manually?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It has been there since v0.2.108 in fact.

@tylersaunders tylersaunders Oct 27, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Unfortunately we can't use libc::open_how here for two reasons (I'd love to be wrong here, but this is my read after looking into this):

  1. Not exposed on target_os = "android": In libc, open_how (along with the RESOLVE_* constants) is defined in src/unix/linux_like/linux/mod.rs (#[cfg(target_os = "linux")]) rather than the shared linux_like or android modules, so it is absent on *-linux-android targets (compare x86_64-unknown-linux-gnu docs https://docs.rs/libc/latest/libc/struct.open_how.html vs. aarch64-linux-android docs https://docs.rs/libc/latest/aarch64-linux-android/libc/?search=open_how).

This is the same reason ResolveFlags and SYS_OPENAT2 in rustix's libc backend are defined inline under #[cfg(linux_kernel)] rather than imported from libc.

  1. #[non_exhaustive]: Even on target_os = "linux", libc::open_how is marked #[non_exhaustive], which disallows constructing it via the open_how { flags, mode, resolve } struct literal (E0639) and would require unsafe {core::mem::zeroed() } + field assignment.

Since the kernel ABI for openat2(2) explicitly versions struct open_how by passing size_of::<open_how>() (24 bytes for v0) as the size syscall argument, defining the 3-field #[repr(C)] struct open_how locally when not(linux_raw_dep) works uniformly across both target_os = "linux" and target_os = "android" while keeping the existing struct literal initialization intact.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It seems that Android does declare open_how in linux/openat2.h, so an issue should be opened in the libc crate to expose it on Android targets.

Also, open_how should probably be non-exhaustive, per openat2(2):

Any future extensions to openat2() will be implemented as new fields appended to the above structure, with a zero value in a new field resulting in the kernel behaving as though that extension field was not present. Therefore, the caller must zero-fill this structure on initialization.

Context: rust-lang/libc#2477

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Issue opened: rust-lang/libc#5673.

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.

3 participants