Skip to content

Commit 0c93071

Browse files
committed
Fixed pthread get/set name for macOS
1 parent e0bd116 commit 0c93071

File tree

6 files changed

+91
-23
lines changed

6 files changed

+91
-23
lines changed

src/shims/unix/freebsd/foreign_items.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
3939
this.read_scalar(thread)?,
4040
this.read_scalar(name)?,
4141
this.read_scalar(len)?,
42+
false,
4243
)?;
4344
}
4445

src/shims/unix/linux/foreign_items.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
8484
this.read_scalar(name)?,
8585
TASK_COMM_LEN,
8686
)?;
87+
let res = if res { Scalar::from_u32(0) } else { this.eval_libc("ERANGE") };
8788
this.write_scalar(res, dest)?;
8889
}
8990
"pthread_getname_np" => {
@@ -93,14 +94,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
9394
// In case of glibc, the length of the output buffer must
9495
// be not shorter than TASK_COMM_LEN.
9596
let len = this.read_scalar(len)?;
96-
let res = if len.to_target_usize(this)? < TASK_COMM_LEN as u64 {
97-
this.eval_libc("ERANGE")
98-
} else {
99-
this.pthread_getname_np(
97+
let res = if len.to_target_usize(this)? >= TASK_COMM_LEN as u64
98+
&& this.pthread_getname_np(
10099
this.read_scalar(thread)?,
101100
this.read_scalar(name)?,
102101
len,
103-
)?
102+
/* truncate*/ false,
103+
)? {
104+
Scalar::from_u32(0)
105+
} else {
106+
this.eval_libc("ERANGE")
104107
};
105108
this.write_scalar(res, dest)?;
106109
}

src/shims/unix/macos/foreign_items.rs

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -164,24 +164,52 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
164164
// Threading
165165
"pthread_setname_np" => {
166166
let [name] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
167+
168+
// The real implementation has logic in two places:
169+
// * in userland at https://github.com/apple-oss-distributions/libpthread/blob/c032e0b076700a0a47db75528a282b8d3a06531a/src/pthread.c#L1178-L1200,
170+
// * in kernel at https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57b9f54c2f6d4a15585/bsd/kern/proc_info.c#L3218-L3227.
171+
//
172+
// The function in libc calls the kernel to validate
173+
// the security policies and the input. If all of the requirements
174+
// are met, then the name is set and 0 is returned. Otherwise, if
175+
// the specified name is lomnger than MAXTHREADNAMESIZE, then
176+
// ENAMETOOLONG is returned.
177+
//
178+
// FIXME: the real implementation maybe returns ESRCH if the thread ID is invalid.
167179
let thread = this.pthread_self()?;
168-
let max_len = this.eval_libc("MAXTHREADNAMESIZE").to_target_usize(this)?;
169-
let res = this.pthread_setname_np(
180+
let res = if this.pthread_setname_np(
170181
thread,
171182
this.read_scalar(name)?,
172-
max_len.try_into().unwrap(),
173-
)?;
183+
this.eval_libc("MAXTHREADNAMESIZE").to_target_usize(this)?.try_into().unwrap(),
184+
)? {
185+
Scalar::from_u32(0)
186+
} else {
187+
this.eval_libc("ENAMETOOLONG")
188+
};
174189
// Contrary to the manpage, `pthread_setname_np` on macOS still
175190
// returns an integer indicating success.
176191
this.write_scalar(res, dest)?;
177192
}
178193
"pthread_getname_np" => {
179194
let [thread, name, len] =
180195
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
181-
let res = this.pthread_getname_np(
196+
197+
// The function's behavior isn't portable between platforms.
198+
// In case of macOS, a truncated name (due to a too small buffer)
199+
// does not lead to an error.
200+
//
201+
// For details, see the implementation at
202+
// https://github.com/apple-oss-distributions/libpthread/blob/c032e0b076700a0a47db75528a282b8d3a06531a/src/pthread.c#L1160-L1175.
203+
// The key part is the strlcpy, which truncates the resulting value,
204+
// but always null terminates (except for zero sized buffers).
205+
//
206+
// FIXME: the real implementation returns ESRCH if the thread ID is invalid.
207+
let res = Scalar::from_u32(0);
208+
this.pthread_getname_np(
182209
this.read_scalar(thread)?,
183210
this.read_scalar(name)?,
184211
this.read_scalar(len)?,
212+
/* truncate */ true,
185213
)?;
186214
this.write_scalar(res, dest)?;
187215
}

src/shims/unix/solarish/foreign_items.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,20 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
3131
this.read_scalar(name)?,
3232
max_len,
3333
)?;
34+
let res = if res { Scalar::from_u32(0) } else { this.eval_libc("ERANGE") };
3435
this.write_scalar(res, dest)?;
3536
}
3637
"pthread_getname_np" => {
3738
let [thread, name, len] =
3839
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
40+
// https://github.com/illumos/illumos-gate/blob/c56822be04b6c157c8b6f2281e47214c3b86f657/usr/src/lib/libc/port/threads/thr.c#L2449-L2480
3941
let res = this.pthread_getname_np(
4042
this.read_scalar(thread)?,
4143
this.read_scalar(name)?,
4244
this.read_scalar(len)?,
45+
/* truncate */ false,
4346
)?;
47+
let res = if res { Scalar::from_u32(0) } else { this.eval_libc("ERANGE") };
4448
this.write_scalar(res, dest)?;
4549
}
4650

src/shims/unix/thread.rs

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,38 +63,41 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
6363
interp_ok(Scalar::from_uint(thread_id.to_u32(), this.libc_ty_layout("pthread_t").size))
6464
}
6565

66-
/// Set the name of the current thread. `max_name_len` is the maximal length of the name
67-
/// including the null terminator.
66+
/// Set the name of the specified thread. If the name including the null terminator
67+
/// is longer than `name_max_len`, then `false` is returned.
6868
fn pthread_setname_np(
6969
&mut self,
7070
thread: Scalar,
7171
name: Scalar,
72-
max_name_len: usize,
73-
) -> InterpResult<'tcx, Scalar> {
72+
name_max_len: usize,
73+
) -> InterpResult<'tcx, bool> {
7474
let this = self.eval_context_mut();
7575

7676
let thread = thread.to_int(this.libc_ty_layout("pthread_t").size)?;
7777
let thread = ThreadId::try_from(thread).unwrap();
7878
let name = name.to_pointer(this)?;
79-
8079
let name = this.read_c_str(name)?.to_owned();
8180

8281
// Comparing with `>=` to account for null terminator.
83-
if name.len() >= max_name_len {
84-
return interp_ok(this.eval_libc("ERANGE"));
82+
if name.len() >= name_max_len {
83+
return interp_ok(false);
8584
}
8685

8786
this.set_thread_name(thread, name);
8887

89-
interp_ok(Scalar::from_u32(0))
88+
interp_ok(true)
9089
}
9190

91+
/// Get the name of the specified thread. If the thread name doesn't fit
92+
/// the buffer, then if `truncate` is set the truncated name is written out,
93+
/// otherwise `false` is returned.
9294
fn pthread_getname_np(
9395
&mut self,
9496
thread: Scalar,
9597
name_out: Scalar,
9698
len: Scalar,
97-
) -> InterpResult<'tcx, Scalar> {
99+
truncate: bool,
100+
) -> InterpResult<'tcx, bool> {
98101
let this = self.eval_context_mut();
99102

100103
let thread = thread.to_int(this.libc_ty_layout("pthread_t").size)?;
@@ -104,9 +107,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
104107

105108
// FIXME: we should use the program name if the thread name is not set
106109
let name = this.get_thread_name(thread).unwrap_or(b"<unnamed>").to_owned();
107-
let (success, _written) = this.write_c_str(&name, name_out, len)?;
110+
let name = match truncate {
111+
true => &name[..name.len().min(len.try_into().unwrap_or(usize::MAX).saturating_sub(1))],
112+
false => &name,
113+
};
114+
115+
let (success, _written) = this.write_c_str(name, name_out, len)?;
108116

109-
interp_ok(if success { Scalar::from_u32(0) } else { this.eval_libc("ERANGE") })
117+
interp_ok(success)
110118
}
111119

112120
fn sched_yield(&mut self) -> InterpResult<'tcx, ()> {

tests/pass-dep/libc/pthread-threadname.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,26 @@ fn main() {
7474
// large enough for the thread name.
7575
#[cfg(target_os = "linux")]
7676
assert_eq!(get_thread_name(&mut buf[..15]), libc::ERANGE);
77+
78+
// Solaris compatible implementations return an error,
79+
// if the buffer is shorter than the thread name.
80+
#[cfg(any(target_os = "illumos", target_os = "solaris"))]
81+
assert_eq!(get_thread_name(&mut buf[..4]), libc::ERANGE);
82+
83+
// For libc implementation for macOS it's not an error
84+
// for a buffer being too short for the thread name.
85+
#[cfg(target_os = "macos")]
86+
{
87+
// Ensure that a zero sized buffer returns no error.
88+
assert_eq!(get_thread_name(&mut buf[..0]), 0);
89+
90+
// Ensure that a shorter tnan required buffer still returns no error,
91+
// and gives a prefix of the thread name.
92+
assert_eq!(get_thread_name(&mut buf[..4]), 0);
93+
let cstr = CStr::from_bytes_until_nul(&buf).unwrap();
94+
assert_eq!(cstr.to_bytes_with_nul().len(), 4);
95+
assert!(short_name.as_bytes().starts_with(cstr.to_bytes()));
96+
}
7797
})
7898
.unwrap()
7999
.join()
@@ -105,8 +125,12 @@ fn main() {
105125

106126
// But with a too long name it should fail (except on FreeBSD where the
107127
// function has no return, hence cannot indicate failure).
108-
#[cfg(not(target_os = "freebsd"))]
109-
assert_ne!(set_thread_name(&CString::new(long_name).unwrap()), 0);
128+
// On macOS, the error code is different.
129+
#[cfg(not(any(target_os = "freebsd", target_os = "macos")))]
130+
assert_eq!(set_thread_name(&CString::new(long_name).unwrap()), libc::ERANGE);
131+
132+
#[cfg(target_os = "macos")]
133+
assert_eq!(set_thread_name(&CString::new(long_name).unwrap()), libc::ENAMETOOLONG);
110134
})
111135
.unwrap()
112136
.join()

0 commit comments

Comments
 (0)