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
416
417
418
419
420
421
422
423
// SPDX-License-Identifier: MIT

//! Rust wrapper library for the [user-space debugfs](https://git.sr.ht/~nabijaczleweli/febug) ABI.
//!
//! Full documentation at
//! [libfebug.rs(3)](https://srhtcdn.githack.com/~nabijaczleweli/febug/blob/man/Debian/libfebug.rs.3.html),
//! and of the rest of febug at [index(0)](https://srhtcdn.githack.com/~nabijaczleweli/febug/blob/man/Debian/index.0.html).
//!
//! Set `$FEBUG_DONT` at build-time to turn everything into a no-op,
//! set `$FEBUG_SIGNUM` at build-time to override the default signal (`SIGUSR2`),
//! set `$FEBUG_SOCKET` at build-time to override the default location (`[/var]/run/febug.sock`).
//!
//! Set `$FEBUG_DONT` at run-time to not connect (and, hence, make everything a no-op),
//! set `$FEBUG_SOCKET` at run-time to set an alternate location.
//!
//! See [`examples/string-sorts.rs`](https://git.sr.ht/~nabijaczleweli/febug/tree/trunk/item/examples/string-sorts.rs)
//! for a usage example.


extern crate libc;
extern crate once_cell;


#[cfg(not(febug_dont))]
use libc::{getenv, close, c_char, c_uint};
#[cfg(all(not(febug_dont), target_os="netbsd"))]
use libc::{recv, ssize_t};
use libc::{strerror, c_void, c_int};
#[cfg(not(febug_dont))]
use std::os::unix::io::FromRawFd;
use std::sync::atomic::AtomicI32;
#[cfg(not(febug_dont))]
use std::sync::atomic::Ordering;
use std::collections::BTreeMap;
use std::marker::PhantomData;
#[cfg(not(febug_dont))]
use std::io::{Cursor, Write};
#[cfg(not(febug_dont))]
use std::{slice, cmp, ptr};
#[cfg(not(febug_dont))]
use std::mem::MaybeUninit;
use once_cell::sync::Lazy;
use std::sync::Mutex;
use std::any::TypeId;
use std::{fmt, mem};
use std::ffi::CStr;
use std::fs::File;

// Borrowed from https://github.com/rust-random/getrandom/blob/2b03b0e0b8a65ec5272b867311d3004cea73f381/src/util_libc.rs
#[cfg(any(target_os = "netbsd", target_os = "openbsd"))]
use libc::__errno as errno_location;
#[cfg(any(target_os = "linux"))]
use libc::__errno_location as errno_location;
#[cfg(any(target_os = "freebsd"))]
use libc::__error as errno_location;



/// [`febug-abi(5)`](https://srhtcdn.githack.com/~nabijaczleweli/febug/blob/man/Debian/febug-abi.5.html)
pub mod abi {
    use libc::c_char;
    use std::mem;

    /// `febug_message`
    #[repr(packed)]
    pub struct FebugMessage {
        pub variable_id: u64,
        pub variable_type: u64,
        pub signal: u8,
        pub name: [c_char; 4096 - 8 - 8 - 1],
    }
    const _FEBUG_MESSAGE_ASSERT: [(); mem::size_of::<FebugMessage>() - 4096] = [];

    /// `stop_febug_message`
    #[repr(packed)]
    pub struct StopFebugMessage {
        pub variable_id: u64,
    }
    const _STOP_FEBUG_MESSAGE_ASSERT: [(); mem::size_of::<StopFebugMessage>() - 8] = [];

    /// `attn_febug_message`
    #[repr(packed)]
    pub struct AttnFebugMessage {
        pub variable_id: u64,
        pub variable_type: u64,
    }
    const _ATTN_FEBUG_MESSAGE_ASSERT: [(); mem::size_of::<AttnFebugMessage>() - 16] = [];
}


/// FD for the connection to [`febug(8)`](https://srhtcdn.githack.com/~nabijaczleweli/febug/blob/man/Debian/febug.8.html), or
/// -1 if none.
///
/// c_int is universally i32 (on platforms that we support, anyway)
pub static GLOBAL_CONTROLLED_SOCKET: AtomicI32 = AtomicI32::new(-1);
const _ILP32_ASSERT: [(); mem::size_of::<AtomicI32>() - mem::size_of::<c_int>()] = [];


struct Strerror;
impl fmt::Display for Strerror {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(unsafe { CStr::from_ptr(strerror(*errno_location())) }.to_str().unwrap_or("<strerror not UTF-8?>"))
    }
}


/// Call `start_raw()` with the default socket location for this platform, overridable with `FEBUG_SOCKET`
pub fn start() {
    start_raw(include!(concat!(env!("OUT_DIR"), "/febug_socket.rs")))
}

/// Disabled
#[cfg(febug_dont)]
pub fn start_raw(_: &[u8]) {}
/// If `$FEBUG_DONT` isn't set, dial
/// [`febug(8)`](https://srhtcdn.githack.com/~nabijaczleweli/febug/blob/man/Debian/febug.8.html) at the specified path,
/// or `$FEBUG_SOCKET`
#[cfg(not(febug_dont))]
pub fn start_raw(mut path: &[u8]) {
    if unsafe { getenv(b"FEBUG_DONT\0".as_ptr() as *const c_char) } != ptr::null_mut() {
        return;
    }

    let sock = unsafe { libc::socket(libc::AF_UNIX, libc::SOCK_SEQPACKET, 0) };
    if sock == -1 {
        eprintln!("febug::start_raw: socket: {}", Strerror);
        return;
    }

    let fs = unsafe { getenv(b"FEBUG_SOCKET\0".as_ptr() as *const _) };
    if fs != ptr::null_mut() {
        path = unsafe { CStr::from_ptr(fs) }.to_bytes();
    }
    let path = unsafe { slice::from_raw_parts(path.as_ptr() as *const c_char, path.len()) };

    let mut addr: libc::sockaddr_un = unsafe { MaybeUninit::zeroed().assume_init() };
    addr.sun_family = libc::AF_UNIX as libc::sa_family_t;

    let path_strlen = cmp::min(addr.sun_path.len() - 1, path.len());
    addr.sun_path[0..path_strlen].copy_from_slice(&path[0..path_strlen]);
    if unsafe { libc::connect(sock, &addr as *const libc::sockaddr_un as *const libc::sockaddr, mem::size_of_val(&addr) as u32) } == -1 {
        eprintln!("febug::start_raw: connect: {}", Strerror);
        unsafe { close(sock) };
        return;
    }

        #[cfg(any(target_os="linux", target_os="openbsd"))]
    {
        // Handled automatically with SO_PASSCRED, also the manual variant didn't work for some reason
        // Only way is getsockopt(SO_PEERCRED)
    }
        #[cfg(target_os="netbsd")]
    {
        // Correct way is automatically via LOCAL_CREDS
        // However, the message /must/ be sent after the peer sets it; use a sync message from the server for this,
        // otherwise we sent the first febug_message too quickly sometimes
        let mut sync_msg = abi::AttnFebugMessage {
            variable_id: 0,
            variable_type: 0,
        };
        if unsafe { recv(sock, &mut sync_msg as *mut _ as *mut c_void, mem::size_of_val(&sync_msg), 0) } != mem::size_of_val(&sync_msg) as ssize_t {
            eprintln!("febug::start_raw: recv: {}", Strerror);
            unsafe { close(sock) };
            return;
        }
    }
        #[cfg(not(any(target_os="linux", target_os="openbsd", target_os="netbsd")))]
    {
        // From FreeBSD 12.1-RELEASE-p7 /usr/include/socket.h:
        //
        // Credentials structure, used to verify the identity of a peer
        // process that has sent us a message. This is allocated by the
        // peer process but filled in by the kernel. This prevents the
        // peer from lying about its identity. (Note that cmcred_groups[0]
        // is the effective GID.)
        //

        let mut cmsgbuf = [0u8; mem::align_of::<libc::cmsghdr>() * 2 + mem::size_of::<libc::cmsgcred>() * 2];
        let cmsgbuf_len = unsafe { libc::CMSG_SPACE(mem::size_of::<libc::cmsgcred>() as c_uint) as usize }; // This is a macro in C. Not so here. Alas!
        assert!(cmsgbuf.len() >= cmsgbuf_len, "{} >= {}", cmsgbuf.len(), cmsgbuf_len);

        let mut msg = libc::msghdr {
            msg_name: ptr::null_mut(),
            msg_namelen: 0,
            msg_iov: ptr::null_mut(),
            msg_iovlen: 0,
            msg_control: cmsgbuf.as_mut_ptr() as *mut _,
            msg_controllen: cmsgbuf.len() as c_uint,
            msg_flags: 0,
        };

        let cmsg = unsafe { libc::CMSG_FIRSTHDR(&msg) };
        unsafe { (*cmsg).cmsg_level = libc::SOL_SOCKET };
        unsafe { (*cmsg).cmsg_type = libc::SCM_CREDS };
        unsafe { (*cmsg).cmsg_len = libc::CMSG_LEN(mem::size_of::<libc::cmsgcred>() as c_uint) };
        msg.msg_controllen = unsafe { (*cmsg).cmsg_len }; // total size of all control blocks

        if unsafe { libc::sendmsg(sock, &msg, 0) } == -1 {
            eprintln!("febug::start_raw: sendmsg: {}", Strerror);
            unsafe { close(sock) };
            return;
        }
    }

    GLOBAL_CONTROLLED_SOCKET.store(sock, Ordering::Relaxed);
}

/// call `debug_handler()` with `FEBUG_SIGNUM` (`SIGUSR2` by default)
pub fn install_handler() -> bool {
    install_handler_signal(include!(concat!(env!("OUT_DIR"), "/febug_signum.rs")) as u8)
}

/// Disabled
#[cfg(febug_dont)]
pub fn install_handler_signal(_: u8) -> bool {
    false
}

/// Install `debug_handler()` as a handler for the specified signal
///
/// Returns `true` if became installed
#[cfg(not(febug_dont))]
pub fn install_handler_signal(signal: u8) -> bool {
    if GLOBAL_CONTROLLED_SOCKET.load(Ordering::Relaxed) != -1 {
        let mut act: libc::sigaction = unsafe { MaybeUninit::zeroed().assume_init() };
        act.sa_sigaction = debug_handler as usize;
        if unsafe { libc::sigaction(signal as c_int, &act, ptr::null_mut()) } == -1 {
            eprintln!("febug::install_handler: sigaction: {}", Strerror);
            false
        } else {
            true
        }
    } else {
        false
    }
}

/// Disabled
#[cfg(febug_dont)]
pub fn end() {}
/// Hang up on [`febug(8)`](https://srhtcdn.githack.com/~nabijaczleweli/febug/blob/man/Debian/febug.8.html)
#[cfg(not(febug_dont))]
pub fn end() {
    let sock = GLOBAL_CONTROLLED_SOCKET.load(Ordering::Relaxed);
    if sock != -1 {
        unsafe { close(sock) };
        GLOBAL_CONTROLLED_SOCKET.store(-1, Ordering::Relaxed);
    }
}


/// Helper trait for constructing `Wrapper<T>`s
pub trait Wrappable {
    fn wrap_type(&self, tp: u64, name: fmt::Arguments) -> Wrapper<Self>;
    fn wrap_type_signal(&self, tp: u64, signal: u8, name: fmt::Arguments) -> Wrapper<Self>;
}
impl<T: ?Sized> Wrappable for T {
    fn wrap_type(&self, tp: u64, name: fmt::Arguments) -> Wrapper<T> {
        Wrapper::new(tp, self, name)
    }

    fn wrap_type_signal(&self, tp: u64, signal: u8, name: fmt::Arguments) -> Wrapper<T> {
        Wrapper::new_signal(tp, self, signal, name)
    }
}

/// Helper trait for constructing `Wrapper<T>`s with type equal to `TypeId::of::<Self>()`
pub trait StaticWrappable: 'static {
    fn wrap(&self, name: fmt::Arguments) -> Wrapper<Self>;
    fn wrap_signal(&self, signal: u8, name: fmt::Arguments) -> Wrapper<Self>;
}
impl<T: ?Sized + 'static> StaticWrappable for T {
    fn wrap(&self, name: fmt::Arguments) -> Wrapper<T> {
        Wrapper::new(unsafe { mem::transmute::<_, TypeIdButStronger>(TypeId::of::<T>()).t }, self, name)
    }

    fn wrap_signal(&self, signal: u8, name: fmt::Arguments) -> Wrapper<T> {
        Wrapper::new_signal(unsafe { mem::transmute::<_, TypeIdButStronger>(TypeId::of::<T>()).t }, self, signal, name)
    }
}


struct TypeIdButStronger {
    t: u64,
}
const _TYPE_ID_SIZE_ASSERT: [(); mem::size_of::<TypeId>() - mem::size_of::<TypeIdButStronger>()] = [];
const _TYPE_ID_ALIGN_ASSERT: [(); mem::align_of::<TypeId>() - mem::align_of::<TypeIdButStronger>()] = [];

/// Create this to register a variable to be debugged
pub struct Wrapper<T: ?Sized> {
    #[cfg_attr(febug_dont, allow(dead_code))]
    id: u64,
    tee: PhantomData<T>,
}

impl<T: ?Sized> Wrapper<T> {
    /// Call `new_signal()` with `FEBUG_SIGNUM` (`SIGUSR2` by default)
    pub fn new(tp: u64, data: &T, name: fmt::Arguments) -> Wrapper<T> {
        Wrapper::new_signal(tp, data, include!(concat!(env!("OUT_DIR"), "/febug_signum.rs")) as u8, name)
    }

    /// Register the specified variable of the specified type with the specified name to be notified (if not `SIGKILL`) on the
    /// specified signal to format it
    pub fn new_signal(tp: u64, data: &T, signal: u8, name: fmt::Arguments) -> Wrapper<T> {
        let id = data as *const T as *const c_void as usize as u64;

        #[cfg(febug_dont)]
        let _ = (tp, signal, name);
        #[cfg(not(febug_dont))]
        {

            let s = GLOBAL_CONTROLLED_SOCKET.load(Ordering::Relaxed);
            if s != -1 {
                let mut msg = abi::FebugMessage {
                    variable_id: id,
                    variable_type: tp,
                    signal: signal,
                    name: unsafe { MaybeUninit::zeroed().assume_init() },
                };
                let _ = Cursor::new(unsafe { slice::from_raw_parts_mut(msg.name.as_mut_ptr() as *mut u8, msg.name.len()) }).write_fmt(name);
                unsafe { libc::send(s, &msg as *const _ as *const c_void, mem::size_of_val(&msg), 0) };
            }
        }

        Wrapper {
            id: id,
            tee: PhantomData,
        }
    }
}

impl<T: ?Sized> Drop for Wrapper<T> {
    /// Disabled
    #[cfg(febug_dont)]
    fn drop(&mut self) {}
    /// Deregister the variable
    #[cfg(not(febug_dont))]
    fn drop(&mut self) {
        let s = GLOBAL_CONTROLLED_SOCKET.load(Ordering::Relaxed);
        if s != -1 {
            let msg = abi::StopFebugMessage { variable_id: self.id };
            unsafe { libc::send(s, &msg as *const _ as *const c_void, mem::size_of_val(&msg), 0) };
        }
    }
}


/// Register formatters for your variable types here;
///
/// type -> fn(return pipe, ID)
pub static FORMATTERS: Lazy<Mutex<BTreeMap<TypeId, fn(&mut File, usize)>>> = Lazy::new(|| Mutex::new(BTreeMap::new()));


/// Disabled
#[cfg(febug_dont)]
pub extern "C" fn debug_handler(_: c_int) {}
/// Register this as a signal handler or call this from an event loop to format variables to be inspected
#[cfg(not(febug_dont))]
pub extern "C" fn debug_handler(_: c_int) {
    let s = GLOBAL_CONTROLLED_SOCKET.load(Ordering::Relaxed);
    if s == -1 {
        return;
    }

    let mut afmsg = abi::AttnFebugMessage {
        variable_id: 0,
        variable_type: 0,
    };
    let mut buf_i = libc::iovec {
        iov_base: &mut afmsg as *mut _ as *mut c_void,
        iov_len: mem::size_of_val(&afmsg),
    };

    let mut retpipe: c_int = -1;
    let mut cmsgbuf = [0u8; mem::align_of::<libc::cmsghdr>() * 2 + mem::size_of::<c_int>() * 8];
    let cmsgbuf_len = unsafe { libc::CMSG_SPACE(mem::size_of_val(&retpipe) as c_uint) as usize }; // This is a macro in C. Not so here. Alas!
    assert!(cmsgbuf.len() >= cmsgbuf_len, "{} >= {}", cmsgbuf.len(), cmsgbuf_len);

    let mut msg = libc::msghdr {
        msg_name: ptr::null_mut(),
        msg_namelen: 0,
        msg_iov: &mut buf_i,
        msg_iovlen: 1,
        msg_control: cmsgbuf.as_mut_ptr() as *mut _,
        #[cfg(target_os="linux")]
        msg_controllen: cmsgbuf_len,
        #[cfg(not(target_os="linux"))]
        msg_controllen: cmsgbuf_len as c_uint,
        msg_flags: 0,
    };

    if unsafe { libc::recvmsg(s, &mut msg, 0) } == -1 {
        eprintln!("febug::debug_handler: recvmsg: {}", Strerror);
        return;
    }

    let cmsg = unsafe { libc::CMSG_FIRSTHDR(&msg) };
    if cmsg != ptr::null_mut() && unsafe { (*cmsg).cmsg_type } == libc::SCM_RIGHTS {
        unsafe { ptr::copy_nonoverlapping(libc::CMSG_DATA(cmsg), &mut retpipe as *mut _ as *mut u8, mem::size_of_val(&retpipe)) };
        let mut retpipe = unsafe { File::from_raw_fd(retpipe) };

        let tp = afmsg.variable_type;
        let ti = unsafe { mem::transmute::<_, TypeId>(TypeIdButStronger { t: tp }) };
        let id = afmsg.variable_id;
        match FORMATTERS.lock() {
            Ok(fmts) => {
                match fmts.get(&ti) {
                    Some(fmt) => fmt(&mut retpipe, afmsg.variable_id as usize),
                    None => {
                        let _ = writeln!(retpipe, "Unknown variable type {} with ID {}", tp, id);
                    }
                }
            }
            Err(e) => {
                let _ = writeln!(retpipe, "Can't see variable {} with ID {}: poisoned: {}!", tp, id, e);
            }
        };

        // closed by drop
    } else {
        eprintln!("febug::debug_handler: cmsg: no fd");
    }
}