Skip to content
38 changes: 38 additions & 0 deletions src/shims/unix/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,44 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
interp_ok(Scalar::from_u32(this.get_current_tid()))
}

fn uname(&mut self, uname: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();
this.assert_target_os_is_unix("uname");

let uname_ptr = this.read_pointer(uname)?;
if this.ptr_is_null(uname_ptr)? {
return this.set_last_error_and_return_i32(LibcError("EFAULT"));
}

let uname = this.deref_pointer_as(uname, this.libc_ty_layout("utsname"))?;
let arch = this.machine.tcx.sess.target.arch.desc_symbol();
// Values required by POSIX.
let values = [
("sysname", "Miri"),
("nodename", "Miri"),
("release", env!("CARGO_PKG_VERSION")),
("version", ""),
Copy link
Member

Choose a reason for hiding this comment

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

What does "version" usually contain and why is it empty here?

Copy link
Author

Choose a reason for hiding this comment

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

I'm not 100% sure, but I think it's very much OS dependant. Posix defines it as (https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_utsname.h.html):

Current version level of this release.

Some examples:

  • Arch Linux: #1 SMP PREEMPT_DYNAMIC Thu, 18 Dec 2025 18:00:18 +0000
  • macOS: Darwin Kernel Version 24.6.0: Wed Oct 15 21:12:15 PDT 2025; root:xnu-11417.140.69.703.14~1/RELEASE_ARM64_T6041

I'm open to suggestion on what to put here for Miri, but realistically I don't think this info is useful in general (outside from maybe logging it).

Copy link
Member

Choose a reason for hiding this comment

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

I don't really understand the distinction between "release" and "version"... but it seems like typically, both contain a version number. So maybe "version" should be "Miri 0.1" or so?

("machine", arch.as_str()),
];
for (name, value) in values {
let field = this.project_field_named(&uname, name)?;
let size = field.layout().layout.size().bytes();
let (written, _) = this.write_c_str(value.as_bytes(), field.ptr(), size)?;
assert!(written); // All values should fit.
}
// The following fields are not defined on all OS/libc implementations,
// so only write them if they are defined.
let optional_values = [("domainname", "(none)")];
for (name, value) in optional_values {
if let Some(field) = this.try_project_field_named(&uname, name)? {
let size = field.layout().layout.size().bytes();
let (written, _) = this.write_c_str(value.as_bytes(), field.ptr(), size)?;
assert!(written); // All values should fit.
}
}
interp_ok(Scalar::from_i32(0))
}

/// The Apple-specific `int pthread_threadid_np(pthread_t thread, uint64_t *thread_id)`, which
/// allows querying the ID for arbitrary threads, identified by their pthread_t.
///
Expand Down
10 changes: 10 additions & 0 deletions src/shims/unix/foreign_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let result = this.getpid()?;
this.write_scalar(result, dest)?;
}
"uname" => {
Copy link
Member

Choose a reason for hiding this comment

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

Apparently that's not universally the symbol name then, given what you had to do for freebsd? Please add a check similar to what we do for flock and other shims to ensure this arm is only taken on OSes where it makes sense.

Copy link
Author

Choose a reason for hiding this comment

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

I think is the correct name for the function, but the implementation for FreeBSD within libc then calls __xuname. I'm not exactly sure what would happen if we remove it from the list here, but I think it would cause errors to be honest.

Copy link
Author

Choose a reason for hiding this comment

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

I tried running it with uname here and with --target x86_64-unknown-freebsd and it does work, then I'll have to figure what the correct names are

Copy link
Member

Choose a reason for hiding this comment

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

This match arm here is just dead code for freebsd target. So just add a check to only allow this arm on the other Unixes.

let [uname] = this.check_shim_sig(
shim_sig!(extern "C" fn(*mut _) -> i32),
link_name,
abi,
args,
)?;
let result = this.uname(uname)?;
this.write_scalar(result, dest)?;
}
"sysconf" => {
let [val] = this.check_shim_sig(
shim_sig!(extern "C" fn(i32) -> isize),
Expand Down
17 changes: 17 additions & 0 deletions src/shims/unix/freebsd/foreign_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,23 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let errno_place = this.last_error_place()?;
this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?;
}
"__xuname" => {
// FreeBSD uses __xuname under the hood to implement uname, see:
// https://github.com/freebsd/freebsd-src/blob/3542d60fb8042474f66fbf2d779ed8c5a80d0f78/sys/sys/utsname.h#L64
// https://github.com/freebsd/freebsd-src/blob/3542d60fb8042474f66fbf2d779ed8c5a80d0f78/lib/libc/gen/uname.c#L44
let [size, uname] = this.check_shim_sig(
shim_sig!(extern "C" fn(i32, *mut _) -> i32),
link_name,
abi,
args,
)?;
// The size determines the length of each field in the utsname
// structure. We don't really care about this, all we do check
// that the argument is an i32.
let _size = this.read_scalar(size)?.to_i32()?;
let result = this.uname(uname)?;
this.write_scalar(result, dest)?;
}

// Incomplete shims that we "stub out" just to get pre-main initialization code to work.
// These shims are enabled only when the caller is in the standard library.
Expand Down
38 changes: 38 additions & 0 deletions tests/pass-dep/libc/libc-uname.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//@ignore-target: windows # No libc

use std::ffi::CStr;
use std::{io, ptr};

fn main() {
test_ok();
test_null_ptr();
}

fn test_ok() {
// SAFETY: all zeros for `utsname` is valid.
let mut uname: libc::utsname = unsafe { std::mem::zeroed() };
let result = unsafe { libc::uname(&mut uname) };
if result != 0 {
panic!("failed to call uname");
}

assert_eq!(unsafe { CStr::from_ptr(&uname.sysname as *const _) }, c"Miri");
assert_eq!(unsafe { CStr::from_ptr(&uname.nodename as *const _) }, c"Miri");
assert_eq!(
unsafe { CStr::from_ptr(&uname.release as *const _) }.to_str().unwrap(),
env!("CARGO_PKG_VERSION")
);
assert_eq!(unsafe { CStr::from_ptr(&uname.version as *const _) }, c"");
assert_eq!(
unsafe { CStr::from_ptr(&uname.machine as *const _) }.to_str().unwrap(),
std::env::consts::ARCH
);
#[cfg(any(target_os = "linux", target_os = "android"))]
assert_eq!(unsafe { CStr::from_ptr(&uname.domainname as *const _) }, c"(none)");
}

fn test_null_ptr() {
let result = unsafe { libc::uname(ptr::null_mut()) };
assert_eq!(result, -1);
assert_eq!(io::Error::last_os_error().raw_os_error(), Some(libc::EFAULT));
}