1 // Copyright 2017 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 //! Small system utility modules for usage by other modules.
6
7 #[cfg(target_os = "android")]
8 mod android;
9 #[cfg(target_os = "android")]
10 use android as target_os;
11 #[cfg(target_os = "linux")]
12 #[allow(clippy::module_inception)]
13 mod linux;
14 #[cfg(target_os = "linux")]
15 use linux as target_os;
16 use log::warn;
17 #[macro_use]
18 pub mod ioctl;
19 #[macro_use]
20 pub mod syslog;
21 mod acpi_event;
22 mod capabilities;
23 mod descriptor;
24 mod event;
25 mod file;
26 mod file_traits;
27 mod get_filesystem_type;
28 mod mmap;
29 mod net;
30 mod netlink;
31 mod notifiers;
32 pub mod platform_timer_resolution;
33 mod poll;
34 mod priority;
35 pub mod process;
36 mod sched;
37 mod shm;
38 pub mod signal;
39 mod signalfd;
40 mod terminal;
41 mod timer;
42 pub mod vsock;
43 mod write_zeroes;
44
45 use std::fs::remove_file;
46 use std::fs::File;
47 use std::fs::OpenOptions;
48 use std::mem;
49 use std::mem::MaybeUninit;
50 use std::ops::Deref;
51 use std::os::unix::io::FromRawFd;
52 use std::os::unix::io::RawFd;
53 use std::os::unix::net::UnixDatagram;
54 use std::os::unix::net::UnixListener;
55 use std::os::unix::process::ExitStatusExt;
56 use std::path::Path;
57 use std::path::PathBuf;
58 use std::process::ExitStatus;
59 use std::ptr;
60 use std::time::Duration;
61
62 pub use acpi_event::*;
63 pub use capabilities::drop_capabilities;
64 pub use descriptor::*;
65 pub use event::EventExt;
66 pub(crate) use event::PlatformEvent;
67 pub use file::find_next_data;
68 pub use file::FileDataIterator;
69 pub(crate) use file_traits::lib::*;
70 pub use get_filesystem_type::*;
71 pub use ioctl::*;
72 use libc::c_int;
73 use libc::c_long;
74 use libc::fcntl;
75 use libc::pipe2;
76 use libc::syscall;
77 use libc::waitpid;
78 use libc::SYS_getpid;
79 use libc::SYS_getppid;
80 use libc::SYS_gettid;
81 use libc::EINVAL;
82 use libc::O_CLOEXEC;
83 use libc::SIGKILL;
84 use libc::WNOHANG;
85 pub use mmap::*;
86 pub(in crate::sys) use net::sendmsg_nosignal as sendmsg;
87 pub(in crate::sys) use net::sockaddr_un;
88 pub(in crate::sys) use net::sockaddrv4_to_lib_c;
89 pub(in crate::sys) use net::sockaddrv6_to_lib_c;
90 pub use netlink::*;
91 use once_cell::sync::OnceCell;
92 pub use poll::EventContext;
93 pub use priority::*;
94 pub use sched::*;
95 pub use shm::MemfdSeals;
96 pub use shm::SharedMemoryLinux;
97 pub use signal::*;
98 pub use signalfd::Error as SignalFdError;
99 pub use signalfd::*;
100 pub use terminal::*;
101 pub use timer::*;
102 pub(crate) use write_zeroes::file_punch_hole;
103 pub(crate) use write_zeroes::file_write_zeroes_at;
104
105 use crate::descriptor::FromRawDescriptor;
106 use crate::descriptor::SafeDescriptor;
107 pub use crate::errno::Error;
108 pub use crate::errno::Result;
109 pub use crate::errno::*;
110 use crate::number_of_logical_cores;
111 use crate::round_up_to_page_size;
112 pub use crate::sys::unix::descriptor::*;
113 use crate::syscall;
114 use crate::AsRawDescriptor;
115 use crate::Pid;
116
117 /// Re-export libc types that are part of the API.
118 pub type Uid = libc::uid_t;
119 pub type Gid = libc::gid_t;
120 pub type Mode = libc::mode_t;
121
122 /// This bypasses `libc`'s caching `getpid(2)` wrapper which can be invalid if a raw clone was used
123 /// elsewhere.
124 #[inline(always)]
getpid() -> Pid125 pub fn getpid() -> Pid {
126 // SAFETY:
127 // Safe because this syscall can never fail and we give it a valid syscall number.
128 unsafe { syscall(SYS_getpid as c_long) as Pid }
129 }
130
131 /// Safe wrapper for the geppid Linux systemcall.
132 #[inline(always)]
getppid() -> Pid133 pub fn getppid() -> Pid {
134 // SAFETY:
135 // Safe because this syscall can never fail and we give it a valid syscall number.
136 unsafe { syscall(SYS_getppid as c_long) as Pid }
137 }
138
139 /// Safe wrapper for the gettid Linux systemcall.
gettid() -> Pid140 pub fn gettid() -> Pid {
141 // SAFETY:
142 // Calling the gettid() sycall is always safe.
143 unsafe { syscall(SYS_gettid as c_long) as Pid }
144 }
145
146 /// Safe wrapper for `geteuid(2)`.
147 #[inline(always)]
geteuid() -> Uid148 pub fn geteuid() -> Uid {
149 // SAFETY:
150 // trivially safe
151 unsafe { libc::geteuid() }
152 }
153
154 /// Safe wrapper for `getegid(2)`.
155 #[inline(always)]
getegid() -> Gid156 pub fn getegid() -> Gid {
157 // SAFETY:
158 // trivially safe
159 unsafe { libc::getegid() }
160 }
161
162 /// The operation to perform with `flock`.
163 pub enum FlockOperation {
164 LockShared,
165 LockExclusive,
166 Unlock,
167 }
168
169 /// Safe wrapper for flock(2) with the operation `op` and optionally `nonblocking`. The lock will be
170 /// dropped automatically when `file` is dropped.
171 #[inline(always)]
flock<F: AsRawDescriptor>(file: &F, op: FlockOperation, nonblocking: bool) -> Result<()>172 pub fn flock<F: AsRawDescriptor>(file: &F, op: FlockOperation, nonblocking: bool) -> Result<()> {
173 let mut operation = match op {
174 FlockOperation::LockShared => libc::LOCK_SH,
175 FlockOperation::LockExclusive => libc::LOCK_EX,
176 FlockOperation::Unlock => libc::LOCK_UN,
177 };
178
179 if nonblocking {
180 operation |= libc::LOCK_NB;
181 }
182
183 // SAFETY:
184 // Safe since we pass in a valid fd and flock operation, and check the return value.
185 syscall!(unsafe { libc::flock(file.as_raw_descriptor(), operation) }).map(|_| ())
186 }
187
188 /// The operation to perform with `fallocate`.
189 pub enum FallocateMode {
190 PunchHole,
191 ZeroRange,
192 Allocate,
193 }
194
195 impl From<FallocateMode> for i32 {
from(value: FallocateMode) -> Self196 fn from(value: FallocateMode) -> Self {
197 match value {
198 FallocateMode::Allocate => libc::FALLOC_FL_KEEP_SIZE,
199 FallocateMode::PunchHole => libc::FALLOC_FL_PUNCH_HOLE | libc::FALLOC_FL_KEEP_SIZE,
200 FallocateMode::ZeroRange => libc::FALLOC_FL_ZERO_RANGE | libc::FALLOC_FL_KEEP_SIZE,
201 }
202 }
203 }
204
205 impl From<FallocateMode> for u32 {
from(value: FallocateMode) -> Self206 fn from(value: FallocateMode) -> Self {
207 Into::<i32>::into(value) as u32
208 }
209 }
210
211 /// Safe wrapper for `fallocate()`.
fallocate<F: AsRawDescriptor>( file: &F, mode: FallocateMode, offset: u64, len: u64, ) -> Result<()>212 pub fn fallocate<F: AsRawDescriptor>(
213 file: &F,
214 mode: FallocateMode,
215 offset: u64,
216 len: u64,
217 ) -> Result<()> {
218 let offset = if offset > libc::off64_t::max_value() as u64 {
219 return Err(Error::new(libc::EINVAL));
220 } else {
221 offset as libc::off64_t
222 };
223
224 let len = if len > libc::off64_t::max_value() as u64 {
225 return Err(Error::new(libc::EINVAL));
226 } else {
227 len as libc::off64_t
228 };
229
230 // SAFETY:
231 // Safe since we pass in a valid fd and fallocate mode, validate offset and len,
232 // and check the return value.
233 syscall!(unsafe { libc::fallocate64(file.as_raw_descriptor(), mode.into(), offset, len) })
234 .map(|_| ())
235 }
236
237 /// Safe wrapper for `fstat()`.
fstat<F: AsRawDescriptor>(f: &F) -> Result<libc::stat64>238 pub fn fstat<F: AsRawDescriptor>(f: &F) -> Result<libc::stat64> {
239 let mut st = MaybeUninit::<libc::stat64>::zeroed();
240
241 // SAFETY:
242 // Safe because the kernel will only write data in `st` and we check the return
243 // value.
244 syscall!(unsafe { libc::fstat64(f.as_raw_descriptor(), st.as_mut_ptr()) })?;
245
246 // SAFETY:
247 // Safe because the kernel guarantees that the struct is now fully initialized.
248 Ok(unsafe { st.assume_init() })
249 }
250
251 /// Checks whether a file is a block device fie or not.
is_block_file<F: AsRawDescriptor>(file: &F) -> Result<bool>252 pub fn is_block_file<F: AsRawDescriptor>(file: &F) -> Result<bool> {
253 let stat = fstat(file)?;
254 Ok((stat.st_mode & libc::S_IFMT) == libc::S_IFBLK)
255 }
256
257 const BLOCK_IO_TYPE: u32 = 0x12;
258 ioctl_io_nr!(BLKDISCARD, BLOCK_IO_TYPE, 119);
259
260 /// Discards the given range of a block file.
discard_block<F: AsRawDescriptor>(file: &F, offset: u64, len: u64) -> Result<()>261 pub fn discard_block<F: AsRawDescriptor>(file: &F, offset: u64, len: u64) -> Result<()> {
262 let range: [u64; 2] = [offset, len];
263 // SAFETY:
264 // Safe because
265 // - we check the return value.
266 // - ioctl(BLKDISCARD) does not hold the descriptor after the call.
267 // - ioctl(BLKDISCARD) does not break the file descriptor.
268 // - ioctl(BLKDISCARD) does not modify the given range.
269 syscall!(unsafe { libc::ioctl(file.as_raw_descriptor(), BLKDISCARD(), &range) }).map(|_| ())
270 }
271
272 /// A trait used to abstract types that provide a process id that can be operated on.
273 pub trait AsRawPid {
as_raw_pid(&self) -> Pid274 fn as_raw_pid(&self) -> Pid;
275 }
276
277 impl AsRawPid for Pid {
as_raw_pid(&self) -> Pid278 fn as_raw_pid(&self) -> Pid {
279 *self
280 }
281 }
282
283 impl AsRawPid for std::process::Child {
as_raw_pid(&self) -> Pid284 fn as_raw_pid(&self) -> Pid {
285 self.id() as Pid
286 }
287 }
288
289 /// A safe wrapper around waitpid.
290 ///
291 /// On success if a process was reaped, it will be returned as the first value.
292 /// The second returned value is the ExitStatus from the libc::waitpid() call.
293 ///
294 /// Note: this can block if libc::WNOHANG is not set and EINTR is not handled internally.
wait_for_pid<A: AsRawPid>(pid: A, options: c_int) -> Result<(Option<Pid>, ExitStatus)>295 pub fn wait_for_pid<A: AsRawPid>(pid: A, options: c_int) -> Result<(Option<Pid>, ExitStatus)> {
296 let pid = pid.as_raw_pid();
297 let mut status: c_int = 1;
298 // SAFETY:
299 // Safe because status is owned and the error is checked.
300 let ret = unsafe { libc::waitpid(pid, &mut status, options) };
301 if ret < 0 {
302 return errno_result();
303 }
304 Ok((
305 if ret == 0 { None } else { Some(ret) },
306 ExitStatus::from_raw(status),
307 ))
308 }
309
310 /// Reaps a child process that has terminated.
311 ///
312 /// Returns `Ok(pid)` where `pid` is the process that was reaped or `Ok(0)` if none of the children
313 /// have terminated. An `Error` is with `errno == ECHILD` if there are no children left to reap.
314 ///
315 /// # Examples
316 ///
317 /// Reaps all child processes until there are no terminated children to reap.
318 ///
319 /// ```
320 /// fn reap_children() {
321 /// loop {
322 /// match base::linux::reap_child() {
323 /// Ok(0) => println!("no children ready to reap"),
324 /// Ok(pid) => {
325 /// println!("reaped {}", pid);
326 /// continue
327 /// },
328 /// Err(e) if e.errno() == libc::ECHILD => println!("no children left"),
329 /// Err(e) => println!("error reaping children: {}", e),
330 /// }
331 /// break
332 /// }
333 /// }
334 /// ```
reap_child() -> Result<Pid>335 pub fn reap_child() -> Result<Pid> {
336 // SAFETY:
337 // Safe because we pass in no memory, prevent blocking with WNOHANG, and check for error.
338 let ret = unsafe { waitpid(-1, ptr::null_mut(), WNOHANG) };
339 if ret == -1 {
340 errno_result()
341 } else {
342 Ok(ret)
343 }
344 }
345
346 /// Kill all processes in the current process group.
347 ///
348 /// On success, this kills all processes in the current process group, including the current
349 /// process, meaning this will not return. This is equivalent to a call to `kill(0, SIGKILL)`.
kill_process_group() -> Result<()>350 pub fn kill_process_group() -> Result<()> {
351 // SAFETY: Safe because pid is 'self group' and return value doesn't matter.
352 unsafe { kill(0, SIGKILL) }?;
353 // Kill succeeded, so this process never reaches here.
354 unreachable!();
355 }
356
357 /// Spawns a pipe pair where the first pipe is the read end and the second pipe is the write end.
358 ///
359 /// The `O_CLOEXEC` flag will be set during pipe creation.
pipe() -> Result<(File, File)>360 pub fn pipe() -> Result<(File, File)> {
361 let mut pipe_fds = [-1; 2];
362 // SAFETY:
363 // Safe because pipe2 will only write 2 element array of i32 to the given pointer, and we check
364 // for error.
365 let ret = unsafe { pipe2(&mut pipe_fds[0], O_CLOEXEC) };
366 if ret == -1 {
367 errno_result()
368 } else {
369 // SAFETY:
370 // Safe because both fds must be valid for pipe2 to have returned sucessfully and we have
371 // exclusive ownership of them.
372 Ok(unsafe {
373 (
374 File::from_raw_fd(pipe_fds[0]),
375 File::from_raw_fd(pipe_fds[1]),
376 )
377 })
378 }
379 }
380
381 /// Sets the pipe signified with fd to `size`.
382 ///
383 /// Returns the new size of the pipe or an error if the OS fails to set the pipe size.
set_pipe_size(fd: RawFd, size: usize) -> Result<usize>384 pub fn set_pipe_size(fd: RawFd, size: usize) -> Result<usize> {
385 // SAFETY:
386 // Safe because fcntl with the `F_SETPIPE_SZ` arg doesn't touch memory.
387 syscall!(unsafe { fcntl(fd, libc::F_SETPIPE_SZ, size as c_int) }).map(|ret| ret as usize)
388 }
389
390 /// Test-only function used to create a pipe that is full. The pipe is created, has its size set to
391 /// the minimum and then has that much data written to it. Use `new_pipe_full` to test handling of
392 /// blocking `write` calls in unit tests.
new_pipe_full() -> Result<(File, File)>393 pub fn new_pipe_full() -> Result<(File, File)> {
394 use std::io::Write;
395
396 let (rx, mut tx) = pipe()?;
397 // The smallest allowed size of a pipe is the system page size on linux.
398 let page_size = set_pipe_size(tx.as_raw_descriptor(), round_up_to_page_size(1))?;
399
400 // Fill the pipe with page_size zeros so the next write call will block.
401 let buf = vec![0u8; page_size];
402 tx.write_all(&buf)?;
403
404 Ok((rx, tx))
405 }
406
407 /// Used to attempt to clean up a named pipe after it is no longer used.
408 pub struct UnlinkUnixDatagram(pub UnixDatagram);
409 impl AsRef<UnixDatagram> for UnlinkUnixDatagram {
as_ref(&self) -> &UnixDatagram410 fn as_ref(&self) -> &UnixDatagram {
411 &self.0
412 }
413 }
414 impl Drop for UnlinkUnixDatagram {
drop(&mut self)415 fn drop(&mut self) {
416 if let Ok(addr) = self.0.local_addr() {
417 if let Some(path) = addr.as_pathname() {
418 if let Err(e) = remove_file(path) {
419 warn!("failed to remove control socket file: {}", e);
420 }
421 }
422 }
423 }
424 }
425
426 /// Used to attempt to clean up a named pipe after it is no longer used.
427 pub struct UnlinkUnixListener(pub UnixListener);
428
429 impl AsRef<UnixListener> for UnlinkUnixListener {
as_ref(&self) -> &UnixListener430 fn as_ref(&self) -> &UnixListener {
431 &self.0
432 }
433 }
434
435 impl Deref for UnlinkUnixListener {
436 type Target = UnixListener;
437
deref(&self) -> &UnixListener438 fn deref(&self) -> &UnixListener {
439 &self.0
440 }
441 }
442
443 impl Drop for UnlinkUnixListener {
drop(&mut self)444 fn drop(&mut self) {
445 if let Ok(addr) = self.0.local_addr() {
446 if let Some(path) = addr.as_pathname() {
447 if let Err(e) = remove_file(path) {
448 warn!("failed to remove control socket file: {}", e);
449 }
450 }
451 }
452 }
453 }
454
455 /// Verifies that |raw_descriptor| is actually owned by this process and duplicates it
456 /// to ensure that we have a unique handle to it.
validate_raw_descriptor(raw_descriptor: RawDescriptor) -> Result<RawDescriptor>457 pub fn validate_raw_descriptor(raw_descriptor: RawDescriptor) -> Result<RawDescriptor> {
458 validate_raw_fd(raw_descriptor)
459 }
460
461 /// Verifies that |raw_fd| is actually owned by this process and duplicates it to ensure that
462 /// we have a unique handle to it.
validate_raw_fd(raw_fd: RawFd) -> Result<RawFd>463 pub fn validate_raw_fd(raw_fd: RawFd) -> Result<RawFd> {
464 // Checking that close-on-exec isn't set helps filter out FDs that were opened by
465 // crosvm as all crosvm FDs are close on exec.
466 // SAFETY:
467 // Safe because this doesn't modify any memory and we check the return value.
468 let flags = unsafe { libc::fcntl(raw_fd, libc::F_GETFD) };
469 if flags < 0 || (flags & libc::FD_CLOEXEC) != 0 {
470 return Err(Error::new(libc::EBADF));
471 }
472
473 // SAFETY:
474 // Duplicate the fd to ensure that we don't accidentally close an fd previously
475 // opened by another subsystem. Safe because this doesn't modify any memory and
476 // we check the return value.
477 let dup_fd = unsafe { libc::fcntl(raw_fd, libc::F_DUPFD_CLOEXEC, 0) };
478 if dup_fd < 0 {
479 return Err(Error::last());
480 }
481 Ok(dup_fd as RawFd)
482 }
483
484 /// Utility function that returns true if the given FD is readable without blocking.
485 ///
486 /// On an error, such as an invalid or incompatible FD, this will return false, which can not be
487 /// distinguished from a non-ready to read FD.
poll_in<F: AsRawDescriptor>(fd: &F) -> bool488 pub fn poll_in<F: AsRawDescriptor>(fd: &F) -> bool {
489 let mut fds = libc::pollfd {
490 fd: fd.as_raw_descriptor(),
491 events: libc::POLLIN,
492 revents: 0,
493 };
494 // SAFETY:
495 // Safe because we give a valid pointer to a list (of 1) FD and check the return value.
496 let ret = unsafe { libc::poll(&mut fds, 1, 0) };
497 // An error probably indicates an invalid FD, or an FD that can't be polled. Returning false in
498 // that case is probably correct as such an FD is unlikely to be readable, although there are
499 // probably corner cases in which that is wrong.
500 if ret == -1 {
501 return false;
502 }
503 fds.revents & libc::POLLIN != 0
504 }
505
506 /// Return the maximum Duration that can be used with libc::timespec.
max_timeout() -> Duration507 pub fn max_timeout() -> Duration {
508 Duration::new(libc::time_t::max_value() as u64, 999999999)
509 }
510
511 /// If the given path is of the form /proc/self/fd/N for some N, returns `Ok(Some(N))`. Otherwise
512 /// returns `Ok(None)`.
safe_descriptor_from_path<P: AsRef<Path>>(path: P) -> Result<Option<SafeDescriptor>>513 pub fn safe_descriptor_from_path<P: AsRef<Path>>(path: P) -> Result<Option<SafeDescriptor>> {
514 let path = path.as_ref();
515 if path.parent() == Some(Path::new("/proc/self/fd")) {
516 let raw_descriptor = path
517 .file_name()
518 .and_then(|fd_osstr| fd_osstr.to_str())
519 .and_then(|fd_str| fd_str.parse::<RawFd>().ok())
520 .ok_or_else(|| Error::new(EINVAL))?;
521 let validated_fd = validate_raw_fd(raw_descriptor)?;
522 Ok(Some(
523 // SAFETY:
524 // Safe because nothing else has access to validated_fd after this call.
525 unsafe { SafeDescriptor::from_raw_descriptor(validated_fd) },
526 ))
527 } else {
528 Ok(None)
529 }
530 }
531
532 /// Open the file with the given path, or if it is of the form `/proc/self/fd/N` then just use the
533 /// file descriptor.
534 ///
535 /// Note that this will not work properly if the same `/proc/self/fd/N` path is used twice in
536 /// different places, as the metadata (including the offset) will be shared between both file
537 /// descriptors.
open_file_or_duplicate<P: AsRef<Path>>(path: P, options: &OpenOptions) -> Result<File>538 pub fn open_file_or_duplicate<P: AsRef<Path>>(path: P, options: &OpenOptions) -> Result<File> {
539 let path = path.as_ref();
540 // Special case '/proc/self/fd/*' paths. The FD is already open, just use it.
541 Ok(if let Some(fd) = safe_descriptor_from_path(path)? {
542 fd.into()
543 } else {
544 options.open(path)?
545 })
546 }
547
548 /// Get the max number of open files allowed by the environment.
max_open_files() -> Result<u64>549 pub fn max_open_files() -> Result<u64> {
550 let mut buf = mem::MaybeUninit::<libc::rlimit64>::zeroed();
551
552 // SAFETY:
553 // Safe because this will only modify `buf` and we check the return value.
554 let res = unsafe { libc::prlimit64(0, libc::RLIMIT_NOFILE, ptr::null(), buf.as_mut_ptr()) };
555 if res == 0 {
556 // SAFETY:
557 // Safe because the kernel guarantees that the struct is fully initialized.
558 let limit = unsafe { buf.assume_init() };
559 Ok(limit.rlim_max)
560 } else {
561 errno_result()
562 }
563 }
564
565 /// Moves the requested PID/TID to a particular cgroup
move_to_cgroup(cgroup_path: PathBuf, id_to_write: Pid, cgroup_file: &str) -> Result<()>566 pub fn move_to_cgroup(cgroup_path: PathBuf, id_to_write: Pid, cgroup_file: &str) -> Result<()> {
567 use std::io::Write;
568
569 let gpu_cgroup_file = cgroup_path.join(cgroup_file);
570 let mut f = File::create(gpu_cgroup_file)?;
571 f.write_all(id_to_write.to_string().as_bytes())?;
572 Ok(())
573 }
574
move_task_to_cgroup(cgroup_path: PathBuf, thread_id: Pid) -> Result<()>575 pub fn move_task_to_cgroup(cgroup_path: PathBuf, thread_id: Pid) -> Result<()> {
576 move_to_cgroup(cgroup_path, thread_id, "tasks")
577 }
578
move_proc_to_cgroup(cgroup_path: PathBuf, process_id: Pid) -> Result<()>579 pub fn move_proc_to_cgroup(cgroup_path: PathBuf, process_id: Pid) -> Result<()> {
580 move_to_cgroup(cgroup_path, process_id, "cgroup.procs")
581 }
582
583 /// Queries the property of a specified CPU sysfs node.
parse_sysfs_cpu_info_vec(cpu_id: usize, property: &str) -> Result<Vec<u32>>584 fn parse_sysfs_cpu_info_vec(cpu_id: usize, property: &str) -> Result<Vec<u32>> {
585 let path = format!("/sys/devices/system/cpu/cpu{cpu_id}/{property}");
586 let res: Result<Vec<_>> = std::fs::read_to_string(path)?
587 .split_whitespace()
588 .map(|x| x.parse().map_err(|_| Error::new(libc::EINVAL)))
589 .collect();
590 res
591 }
592
593 /// Returns a list of supported frequencies in kHz for a given logical core.
logical_core_frequencies_khz(cpu_id: usize) -> Result<Vec<u32>>594 pub fn logical_core_frequencies_khz(cpu_id: usize) -> Result<Vec<u32>> {
595 parse_sysfs_cpu_info_vec(cpu_id, "cpufreq/scaling_available_frequencies")
596 }
597
parse_sysfs_cpu_info(cpu_id: usize, property: &str) -> Result<u32>598 fn parse_sysfs_cpu_info(cpu_id: usize, property: &str) -> Result<u32> {
599 let path = format!("/sys/devices/system/cpu/cpu{cpu_id}/{property}");
600 std::fs::read_to_string(path)?
601 .trim()
602 .parse()
603 .map_err(|_| Error::new(libc::EINVAL))
604 }
605
606 /// Returns the capacity (measure of performance) of a given logical core.
logical_core_capacity(cpu_id: usize) -> Result<u32>607 pub fn logical_core_capacity(cpu_id: usize) -> Result<u32> {
608 static CPU_MAX_FREQS: OnceCell<Vec<u32>> = OnceCell::new();
609
610 let cpu_capacity = parse_sysfs_cpu_info(cpu_id, "cpu_capacity")?;
611
612 // Collect and cache the maximum frequencies of all cores. We need to know
613 // the largest maximum frequency between all cores to reverse normalization,
614 // so collect all the values once on the first call to this function.
615 let cpu_max_freqs = CPU_MAX_FREQS.get_or_try_init(|| {
616 (0..number_of_logical_cores()?)
617 .map(logical_core_max_freq_khz)
618 .collect()
619 });
620
621 if let Ok(cpu_max_freqs) = cpu_max_freqs {
622 let largest_max_freq = cpu_max_freqs.iter().max().ok_or(Error::new(EINVAL))?;
623 let cpu_max_freq = cpu_max_freqs.get(cpu_id).ok_or(Error::new(EINVAL))?;
624 (cpu_capacity * largest_max_freq)
625 .checked_div(*cpu_max_freq)
626 .ok_or(Error::new(EINVAL))
627 } else {
628 // cpu-freq is not enabled. Fall back to using the normalized capacity.
629 Ok(cpu_capacity)
630 }
631 }
632
633 /// Returns the cluster ID of a given logical core.
logical_core_cluster_id(cpu_id: usize) -> Result<u32>634 pub fn logical_core_cluster_id(cpu_id: usize) -> Result<u32> {
635 parse_sysfs_cpu_info(cpu_id, "topology/physical_package_id")
636 }
637
638 /// Returns the maximum frequency (in kHz) of a given logical core.
logical_core_max_freq_khz(cpu_id: usize) -> Result<u32>639 fn logical_core_max_freq_khz(cpu_id: usize) -> Result<u32> {
640 parse_sysfs_cpu_info(cpu_id, "cpufreq/cpuinfo_max_freq")
641 }
642
643 #[repr(C)]
644 pub struct sched_attr {
645 pub size: u32,
646
647 pub sched_policy: u32,
648 pub sched_flags: u64,
649 pub sched_nice: i32,
650
651 pub sched_priority: u32,
652
653 pub sched_runtime: u64,
654 pub sched_deadline: u64,
655 pub sched_period: u64,
656
657 pub sched_util_min: u32,
658 pub sched_util_max: u32,
659 }
660
661 impl sched_attr {
default() -> Self662 pub fn default() -> Self {
663 Self {
664 size: std::mem::size_of::<sched_attr>() as u32,
665 sched_policy: 0,
666 sched_flags: 0,
667 sched_nice: 0,
668 sched_priority: 0,
669 sched_runtime: 0,
670 sched_deadline: 0,
671 sched_period: 0,
672 sched_util_min: 0,
673 sched_util_max: 0,
674 }
675 }
676 }
677
sched_setattr(pid: Pid, attr: &mut sched_attr, flags: u32) -> Result<()>678 pub fn sched_setattr(pid: Pid, attr: &mut sched_attr, flags: u32) -> Result<()> {
679 // SAFETY: Safe becuase all the args are valid and the return valud is checked.
680 let ret = unsafe {
681 libc::syscall(
682 libc::SYS_sched_setattr,
683 pid as usize,
684 attr as *mut sched_attr as usize,
685 flags as usize,
686 )
687 };
688
689 if ret < 0 {
690 return Err(Error::last());
691 }
692 Ok(())
693 }
694
695 #[cfg(test)]
696 mod tests {
697 use std::io::Write;
698 use std::os::fd::AsRawFd;
699
700 use super::*;
701 use crate::unix::add_fd_flags;
702
703 #[test]
pipe_size_and_fill()704 fn pipe_size_and_fill() {
705 let (_rx, mut tx) = new_pipe_full().expect("Failed to pipe");
706
707 // To check that setting the size worked, set the descriptor to non blocking and check that
708 // write returns an error.
709 add_fd_flags(tx.as_raw_fd(), libc::O_NONBLOCK).expect("Failed to set tx non blocking");
710 tx.write(&[0u8; 8])
711 .expect_err("Write after fill didn't fail");
712 }
713 }
714