lisy 0.1.0

Linux specific high and middle level system level API library.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
//! `pidfds` are handles to processes which can be polled and used to send signals and other
//! operations, they are much more powerful than numerical PIDs.

use std::ffi::{c_int, c_uint, c_void};
use std::io;
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};

use crate::error::io_assert;
use crate::ns::NsFd;

#[rustfmt::skip]
mod ioctls {
    use std::ffi::c_int;

    use crate::ioctl::{io, iowr};

    use super::CPidFdInfo;

    pub const PIDFS_IOCTL_MAGIC: c_int = 0xFF;

    pub const PIDFD_GET_CGROUP_NAMESPACE            : c_int = io(PIDFS_IOCTL_MAGIC, 1);
    pub const PIDFD_GET_IPC_NAMESPACE               : c_int = io(PIDFS_IOCTL_MAGIC, 2);
    pub const PIDFD_GET_MNT_NAMESPACE               : c_int = io(PIDFS_IOCTL_MAGIC, 3);
    pub const PIDFD_GET_NET_NAMESPACE               : c_int = io(PIDFS_IOCTL_MAGIC, 4);
    pub const PIDFD_GET_PID_NAMESPACE               : c_int = io(PIDFS_IOCTL_MAGIC, 5);
    pub const PIDFD_GET_PID_FOR_CHILDREN_NAMESPACE  : c_int = io(PIDFS_IOCTL_MAGIC, 6);
    pub const PIDFD_GET_TIME_NAMESPACE              : c_int = io(PIDFS_IOCTL_MAGIC, 7);
    pub const PIDFD_GET_TIME_FOR_CHILDREN_NAMESPACE : c_int = io(PIDFS_IOCTL_MAGIC, 8);
    pub const PIDFD_GET_USER_NAMESPACE              : c_int = io(PIDFS_IOCTL_MAGIC, 9);
    pub const PIDFD_GET_UTS_NAMESPACE               : c_int = io(PIDFS_IOCTL_MAGIC, 10);
    pub const PIDFD_GET_INFO                        : c_int = iowr::<CPidFdInfo>(PIDFS_IOCTL_MAGIC, 11);
}

/// A pid file descriptor is a handle to a process.
///
/// Contrary to numerical pids, pidfds cannot be reused while a handle exists. Signals can be sent
/// to the process and they enable waiting for processes via polling mechanisms such as `epoll`.
#[derive(Debug)]
pub struct PidFd {
    fd: OwnedFd,
}

impl AsRawFd for PidFd {
    fn as_raw_fd(&self) -> RawFd {
        self.fd.as_raw_fd()
    }
}

impl IntoRawFd for PidFd {
    fn into_raw_fd(self) -> RawFd {
        self.fd.into_raw_fd()
    }
}

impl FromRawFd for PidFd {
    unsafe fn from_raw_fd(fd: RawFd) -> Self {
        Self {
            fd: unsafe { OwnedFd::from_raw_fd(fd) },
        }
    }
}

impl AsFd for PidFd {
    fn as_fd(&self) -> BorrowedFd<'_> {
        self.fd.as_fd()
    }
}

macro_rules! ns_fd_getters {
    (
        $(
            $(#[$doc:meta])+
            $name:ident($ioctl:expr) -> $nsty:ident;
        )+
    ) => {
        $(
            $(#[$doc])+
            pub fn $name(&self) -> io::Result<NsFd<crate::ns::$nsty>> {
                unsafe {
                    let fd = libc::ioctl(self.as_raw_fd(), $ioctl as u64, 0);
                    io_assert!(fd >= 0);
                    Ok(NsFd::from_raw_fd(fd))
                }
            }
        )+
    };
}

impl PidFd {
    /// Open a pidfd for a process via its process ID.
    pub fn open(pid: libc::pid_t, flags: PidFdFlags) -> io::Result<Self> {
        let fd = unsafe { libc::syscall(libc::SYS_pidfd_open, pid, flags.bits()) };
        io_assert!(fd >= 0);
        Ok(unsafe { Self::from_raw_fd(i32::try_from(fd).unwrap()) })
    }

    /// Get a pid fd to the current process.
    pub fn this(flags: PidFdFlags) -> io::Result<Self> {
        let pid = unsafe { libc::getpid() };
        Self::open(pid, flags)
    }

    /// Get a file descriptor from the process.
    ///
    /// This is *similar* to opening `/proc/pid/fd/FD`, but is actually more equivalent to a
    /// `dup()` operation on that file descriptor.
    pub fn get_fd(&self, fd: RawFd) -> io::Result<OwnedFd> {
        unsafe {
            let fd = libc::syscall(libc::SYS_pidfd_getfd, self.as_raw_fd(), fd, 0);
            io_assert!(fd >= 0);
            Ok(OwnedFd::from_raw_fd(i32::try_from(fd).unwrap()))
        }
    }

    /// Send a signal to the process.
    ///
    /// This works like `kill(2 h)`.
    pub fn send_signal(&self, signal: c_int) -> io::Result<()> {
        let rc = unsafe {
            libc::syscall(
                libc::SYS_pidfd_send_signal,
                self.as_raw_fd(),
                signal,
                std::ptr::null::<c_void>(),
                0,
            )
        };
        io_assert!(rc == 0);
        Ok(())
    }

    /*
    /// Wait on the process.
    pub fn wait(&self, flags: WaitFlags) -> io::Result<WaitResult> {
        unsafe {
            let mut info: libc::siginfo_t = std::mem::zeroed();
            let rc = libc::waitid(libc::P_PIDFD, self.as_raw_fd(), &raw mut info, flags.bits());
            io_assert!(rc == 0);
        }
    }
    */

    ns_fd_getters! {
        /// Get a handle to the process' cgroup namespace.
        cgroup_namespace(ioctls::PIDFD_GET_CGROUP_NAMESPACE) -> CGroup;

        /// Get a handle to the process' IPC namespace.
        ipc_namespace(ioctls::PIDFD_GET_IPC_NAMESPACE) -> Ipc;

        /// Get a handle to the process' mount namespace.
        mount_namespace(ioctls::PIDFD_GET_MNT_NAMESPACE) -> Mnt;

        /// Get a handle to the process' network namespace.
        network_namespace(ioctls::PIDFD_GET_NET_NAMESPACE) -> Net;

        /// Get a handle to the process' *own* PID namespace.
        ///
        /// This is the PID namespace the process lives under, as opposed to the PID namespace new
        /// subprocesses will be spawned under, which can be queried via
        /// [`pid_namespace_for_children`](PidFd::pid_namespace_for_children()).
        pid_namespace(ioctls::PIDFD_GET_PID_NAMESPACE) -> Pid;

        /// Get a handle to the PID namespace this process' children will be spawned in.
        ///
        /// This is the namespace for child processes after the process has used `setns()` or
        /// `unshare()` for the PID namespace, as pid namespaces cannot be entered directly.
        ///
        /// To get the process' *own* PID namespace, use [`pid_namespace`](PidFd::pid_namespace()).
        pid_namespace_for_children(ioctls::PIDFD_GET_PID_FOR_CHILDREN_NAMESPACE) -> Pid;

        /// Get a handle to the process' *own* time namespace.
        ///
        /// This is the time namespace the process lives under, as opposed to the time namespace
        /// new subprocesses will be spawned under, which can be queried via
        /// [`time_namespace_for_children`](PidFd::time_namespace_for_children()).
        time_namespace(ioctls::PIDFD_GET_TIME_NAMESPACE) -> Time;

        /// Get a handle to the time namespace this process' children will be spawned in.
        ///
        /// This is the namespace for child processes after the process has used
        /// `unshare(CLONE_NEWTIME)`, as this does not immediately enter a time namespace.
        ///
        /// To get the process' *own* time namespace, use [`time_namespace`](PidFd::time_namespace()).
        time_namespace_for_children(ioctls::PIDFD_GET_TIME_FOR_CHILDREN_NAMESPACE) -> Time;

        /// Get a handle to the process' user namespace.
        user_namespace(ioctls::PIDFD_GET_USER_NAMESPACE) -> User;

        /// Get a handle to the process' UTS namespace.
        uts_namespace(ioctls::PIDFD_GET_UTS_NAMESPACE) -> Uts;
    }

    /// Query information about the process.
    ///
    /// While the kernel provides bitflags for which information to query, all the current ones are
    /// returned even if not requested. For maximum compatibility, these flags should be included
    /// in the request, so this should be called as `fd.info(Default::default())`.
    pub fn info(&self, flags: GetInfoFlags) -> io::Result<Info> {
        unsafe {
            let mut info = Info {
                raw: CPidFdInfo {
                    mask: flags.bits(),
                    ..std::mem::zeroed()
                },
            };
            let rc = libc::ioctl(
                self.as_raw_fd(),
                ioctls::PIDFD_GET_INFO as u64,
                &raw mut info.raw,
            );
            io_assert!(rc == 0);
            Ok(info)
        }
    }
}

bitflags::bitflags! {
    /// Flags for opening a pid file descriptor.
    #[derive(Clone, Copy, Debug)]
    #[repr(transparent)]
    pub struct PidFdFlags: c_uint {
        /// Make the pidfd non-blocking.
        const NONBLOCK  = libc::PIDFD_NONBLOCK;
    }
}

impl Default for PidFdFlags {
    fn default() -> Self {
        Self::empty()
    }
}

/*
bitflags::bitflags! {
    /// Flags for waiting on a pidfd - `EXITED` by default.
    #[derive(Clone, Copy, Debug)]
    #[repr(transparent)]
    pub struct WaitFlags: c_int {
        /// Return immediately if the child has not exited.
        const NO_HANG   = libc::WNOHANG;

        /// Leave the child in a waitable state.
        const NO_WAIT   = libc::WNOWAIT;

        /// Return if the child has exited.
        const EXITED    = libc::WEXITED;

        /// Return if the child has been stopped by delivery of a signal.
        const STOPPED   = libc::WSTOPPED;

        /// Return if a previously stopped child has been resumed via `SIGCONT`.
        const CONTINUED = libc::WCONTINUED;
    }
}

impl Default for WaitFlags {
    fn default() -> Self {
        Self::EXITED
    }
}

/// The result of waiting on a pidfd.
pub enum WaitResult {
    /// No state change happened and [`WaitFlags::NO_HANG`] was used.
    None,

    /// The child exited.
    Exited(c_int),

    /// The child was killed by a signal.
    ///
    /// The boolean indicates whether a core-dump happened.
    Killed(bool),

    /// A signal stopped the process.
    Stopped(c_int),

    /// A traced child was trapped.
    Trapped,

    /// A stopped child was resumed via `SIGCONT`.
    Continued,
}
*/

bitflags::bitflags! {
    /// Fields to query from a pidfd.
    ///
    /// The default value is `PID | CREDS | CGROUP_ID` as these are returned even if not
    /// requested.
    #[derive(Clone, Copy, Debug)]
    #[repr(transparent)]
    pub struct GetInfoFlags: u64 {
        /// Get the PID. Always returned, even if not requested.
        const PID       = 0x0000_0001;

        /// Get the credential information. Always returned, even if not requested.
        const CREDS     = 0x0000_0002;

        /// Get the cgroup id. Always returned, even if not requested.
        const CGROUP_ID = 0x0000_0004;

        /// Get the exit code (if the process has exited).
        const EXIT      = 0x0000_0008;
    }
}

impl Default for GetInfoFlags {
    fn default() -> Self {
        const { Self::from_bits(Self::PID.bits() | Self::CREDS.bits() | Self::CGROUP_ID.bits()).unwrap() }
    }
}

#[derive(Clone, Debug)]
#[repr(C)]
struct CPidFdInfo {
    mask: u64,
    cgroupid: u64,
    pid: u32,
    tgid: u32,
    ppid: u32,
    ruid: u32,
    rgid: u32,
    euid: u32,
    egid: u32,
    suid: u32,
    sgid: u32,
    fsuid: u32,
    fsgid: u32,
    exit_code: i32,
}

/// Information about a process retrieved via [`PidFd::info`](PidFd::info()).
#[derive(Clone, Debug)]
pub struct Info {
    raw: CPidFdInfo,
}

impl Info {
    fn maybe<T>(&self, flags: GetInfoFlags, value: T) -> Option<T> {
        (self.raw.mask & flags.bits() == flags.bits()).then_some(value)
    }

    /// Get the PID.
    pub fn pid(&self) -> Option<libc::pid_t> {
        self.maybe(GetInfoFlags::PID, self.raw.pid as libc::pid_t)
    }

    /// Get the cgroup ID
    pub fn cgroup_id(&self) -> Option<u64> {
        self.maybe(GetInfoFlags::CGROUP_ID, self.raw.cgroupid)
    }

    /// Get the thread group id.
    pub fn thread_group_id(&self) -> Option<u32> {
        self.maybe(GetInfoFlags::PID, self.raw.tgid)
    }

    /// Get the parent PID.
    pub fn parent_pid(&self) -> Option<libc::pid_t> {
        self.maybe(GetInfoFlags::PID, self.raw.ppid as libc::pid_t)
    }

    /// Get the process' exit code if it has exited.
    ///
    /// From kernel 6.15 onwards this is also available for already-reaped tasks.
    pub fn exit_code(&self) -> Option<i32> {
        self.maybe(GetInfoFlags::EXIT, self.raw.exit_code)
    }

    /// Get the credentials.
    pub fn credentials(&self) -> Option<Credentials> {
        self.maybe(
            GetInfoFlags::CREDS,
            Credentials {
                ruid: self.raw.ruid as libc::uid_t,
                rgid: self.raw.rgid as libc::gid_t,
                euid: self.raw.euid as libc::uid_t,
                egid: self.raw.egid as libc::gid_t,
                suid: self.raw.suid as libc::uid_t,
                sgid: self.raw.sgid as libc::gid_t,
                fsuid: self.raw.fsuid as libc::uid_t,
                fsgid: self.raw.fsgid as libc::gid_t,
            },
        )
    }
}

/// Credentials of a process.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct Credentials {
    /// The real user id.
    pub ruid: libc::uid_t,

    /// The real group id.
    pub rgid: libc::gid_t,

    /// The effective user id.
    pub euid: libc::uid_t,

    /// The effective group id.
    pub egid: libc::gid_t,

    /// The saved user id.
    pub suid: libc::uid_t,

    /// The saved group id.
    pub sgid: libc::gid_t,

    /// The file system user id.
    pub fsuid: libc::uid_t,

    /// The file system group id.
    pub fsgid: libc::gid_t,
}