Skip to main content

session/
lib.rs

1mod backend;
2mod logging;
3#[cfg(feature = "sessiond")]
4mod sessiond_state;
5mod status;
6
7#[cfg(not(any(feature = "sessiond", feature = "logind", feature = "consolekit")))]
8compile_error!(
9    "libsession requires at least one backend feature: `sessiond`, `logind`, or `consolekit`"
10);
11
12#[cfg(feature = "consolekit")]
13use backend::consolekit;
14#[cfg(feature = "logind")]
15use backend::logind;
16#[cfg(feature = "sessiond")]
17use backend::sessiond;
18use std::{env, ffi, process, ptr, rc, result};
19
20struct Libsession {
21    backend: Box<dyn backend::Backend>,
22    logger: rc::Rc<logging::Logger>,
23}
24
25#[repr(C)]
26pub struct LibsessionContext {
27    _private: [u8; 0],
28}
29
30pub struct LibsessionSession(String);
31
32#[repr(C)]
33pub enum LibsessionStatus {
34    Ok = 0,
35    InvalidArgument = 1,
36    NotFound = 2,
37    InternalError = 3,
38    PermissionDenied = 4,
39    Unavailable = 5,
40    InvalidData = 6,
41    IoError = 7,
42    DbusError = 8,
43}
44
45impl From<LibsessionStatus> for result::Result<(), Error> {
46    fn from(value: LibsessionStatus) -> Self {
47        match value {
48            LibsessionStatus::Ok => Ok(()),
49            LibsessionStatus::InvalidArgument => Err(Error::InvalidArgument),
50            LibsessionStatus::NotFound => Err(Error::NotFound),
51            LibsessionStatus::InternalError => Err(Error::InternalError),
52            LibsessionStatus::PermissionDenied => Err(Error::PermissionDenied),
53            LibsessionStatus::Unavailable => Err(Error::Unavailable),
54            LibsessionStatus::InvalidData => Err(Error::InvalidData),
55            LibsessionStatus::IoError => Err(Error::IoError),
56            LibsessionStatus::DbusError => Err(Error::DbusError),
57        }
58    }
59}
60
61pub type Result<T> = result::Result<T, Error>;
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
64pub enum Error {
65    #[error("invalid argument")]
66    InvalidArgument,
67    #[error("not found")]
68    NotFound,
69    #[error("internal error")]
70    InternalError,
71    #[error("permission denied")]
72    PermissionDenied,
73    #[error("unavailable")]
74    Unavailable,
75    #[error("invalid data")]
76    InvalidData,
77    #[error("I/O error")]
78    IoError,
79    #[error("D-Bus error")]
80    DbusError,
81}
82
83#[repr(C)]
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85pub enum LibsessionUserState {
86    Offline = 0,
87    Lingering = 1,
88    Online = 2,
89    Active = 3,
90    Closing = 4,
91    Unknown = 5,
92}
93
94#[repr(C)]
95#[derive(PartialEq, Eq)]
96pub enum LibsessionSessionState {
97    Online = 0,
98    Active = 1,
99    Closing = 2,
100    Opening = 3,
101    Unkown = 4,
102}
103
104#[unsafe(no_mangle)]
105#[must_use]
106pub extern "C" fn libsession_new() -> *mut LibsessionContext {
107    let logger = rc::Rc::new(logging::Logger::default());
108    if let Ok(backend) = env::var("LIBSESSION_BACKEND") {
109        match backend.as_str() {
110            #[cfg(feature = "sessiond")]
111            "sessiond" => {
112                if let Some(sessiond) = sessiond::Sessiond::try_connect(rc::Rc::clone(&logger)) {
113                    return Box::into_raw(Box::new(Libsession {
114                        backend: Box::new(sessiond),
115                        logger,
116                    }))
117                    .cast::<LibsessionContext>();
118                }
119            }
120            #[cfg(feature = "logind")]
121            "logind" => {
122                if let Some(logind) = logind::Logind::try_connect(rc::Rc::clone(&logger)) {
123                    return Box::into_raw(Box::new(Libsession {
124                        backend: Box::new(logind),
125                        logger,
126                    }))
127                    .cast::<LibsessionContext>();
128                }
129            }
130            #[cfg(feature = "consolekit")]
131            "consolekit" => {
132                if let Some(consolekit) =
133                    consolekit::ConsoleKit::try_connect(rc::Rc::clone(&logger))
134                {
135                    return Box::into_raw(Box::new(Libsession {
136                        backend: Box::new(consolekit),
137                        logger,
138                    }))
139                    .cast::<LibsessionContext>();
140                }
141            }
142            _ => {}
143        }
144    }
145
146    #[cfg(feature = "sessiond")]
147    if let Some(sessiond) = sessiond::Sessiond::try_connect(rc::Rc::clone(&logger)) {
148        return Box::into_raw(Box::new(Libsession {
149            backend: Box::new(sessiond),
150            logger,
151        }))
152        .cast::<LibsessionContext>();
153    }
154    #[cfg(feature = "logind")]
155    if let Some(logind) = logind::Logind::try_connect(rc::Rc::clone(&logger)) {
156        return Box::into_raw(Box::new(Libsession {
157            backend: Box::new(logind),
158            logger,
159        }))
160        .cast::<LibsessionContext>();
161    }
162    #[cfg(feature = "consolekit")]
163    if let Some(consolekit) = consolekit::ConsoleKit::try_connect(rc::Rc::clone(&logger)) {
164        return Box::into_raw(Box::new(Libsession {
165            backend: Box::new(consolekit),
166            logger,
167        }))
168        .cast::<LibsessionContext>();
169    }
170
171    ptr::null_mut()
172}
173
174#[derive(Debug, PartialEq, Eq)]
175#[repr(C)]
176pub enum LibsessionBackend {
177    Sessiond = 0,
178    Logind = 1,
179    ConsoleKit = 2,
180}
181
182#[unsafe(no_mangle)]
183#[must_use]
184pub unsafe extern "C" fn libsession_get_backend(
185    ctx: *mut LibsessionContext,
186    ret_backend: *mut LibsessionBackend,
187) -> LibsessionStatus {
188    if ctx.is_null() {
189        return LibsessionStatus::InvalidArgument;
190    }
191
192    let libsession = unsafe { &mut *ctx.cast::<Libsession>() };
193
194    unsafe { ret_backend.write(libsession.backend.get_backend()) };
195
196    LibsessionStatus::Ok
197}
198
199#[unsafe(no_mangle)]
200pub extern "C" fn libsession_free(ctx: *mut LibsessionContext) {
201    if ctx.is_null() {
202        return;
203    }
204
205    unsafe { drop(Box::from_raw(ctx.cast::<Libsession>())) }
206}
207
208#[unsafe(no_mangle)]
209pub unsafe extern "C" fn libsession_session_list(
210    ctx: *mut LibsessionContext,
211    ret_sessions: *mut *mut *mut LibsessionSession,
212    ret_count: *mut usize,
213) -> LibsessionStatus {
214    if ctx.is_null() || ret_sessions.is_null() || ret_count.is_null() {
215        return LibsessionStatus::InvalidArgument;
216    }
217
218    let libsession = unsafe { &mut *ctx.cast::<Libsession>() };
219
220    match libsession.backend.list_sessions() {
221        Ok(sessions) => {
222            let ids = sessions
223                .into_iter()
224                .map(|session_id| Box::into_raw(Box::new(LibsessionSession(session_id))))
225                .collect::<Vec<_>>()
226                .into_boxed_slice();
227
228            let count = ids.len();
229            let ids = if count == 0 {
230                ptr::null_mut()
231            } else {
232                Box::into_raw(ids).cast::<*mut LibsessionSession>()
233            };
234
235            unsafe {
236                ret_sessions.write(ids);
237                ret_count.write(count);
238            }
239
240            LibsessionStatus::Ok
241        }
242        Err(e) => {
243            libsession.logger.log(LibsessionLogLevel::Error, || {
244                format!("failed to list sessions: {e}")
245            });
246            LibsessionStatus::from(&e)
247        }
248    }
249}
250
251#[unsafe(no_mangle)]
252pub unsafe extern "C" fn libsession_session_list_free(
253    sessions: *mut *mut LibsessionSession,
254    count: usize,
255) {
256    if sessions.is_null() {
257        return;
258    }
259
260    let slice = ptr::slice_from_raw_parts_mut(sessions, count);
261    let ids = unsafe { Box::from_raw(slice) };
262
263    for &id in &ids {
264        unsafe { libsession_session_free(id) };
265    }
266}
267
268/// Retrieves an allocated session ID for a process.
269///
270/// # Safety
271///
272/// Non-null `ctx` must be a live context with exclusive access for this call.
273/// Non-null `ret_session` must point to writable, aligned pointer storage that
274/// does not overlap the context. It is written only on success. Free the
275/// resulting string with `libsession_session_free`.
276#[unsafe(no_mangle)]
277pub unsafe extern "C" fn libsession_pid_get_session(
278    ctx: *mut LibsessionContext,
279    pid: libc::pid_t,
280    ret_session: *mut *mut LibsessionSession,
281) -> LibsessionStatus {
282    if ctx.is_null() || ret_session.is_null() {
283        return LibsessionStatus::InvalidArgument;
284    }
285
286    let libsession = unsafe { &mut *ctx.cast::<Libsession>() };
287
288    let Ok(pid) = pid.try_into() else {
289        return LibsessionStatus::InvalidArgument;
290    };
291    let Some(session_id) = libsession.backend.get_session_id(pid) else {
292        return LibsessionStatus::NotFound;
293    };
294
295    unsafe { ret_session.write(Box::into_raw(Box::new(LibsessionSession(session_id)))) };
296
297    LibsessionStatus::Ok
298}
299
300/// Retrieves an allocated session ID for the current process.
301///
302/// # Safety
303///
304/// The pointer requirements of `libsession_pid_get_session` apply.
305#[unsafe(no_mangle)]
306pub unsafe extern "C" fn libsession_get_session(
307    ctx: *mut LibsessionContext,
308    ret_session: *mut *mut LibsessionSession,
309) -> LibsessionStatus {
310    let pid = process::id();
311    if let Ok(pid) = pid.try_into() {
312        return unsafe { libsession_pid_get_session(ctx, pid, ret_session) };
313    }
314
315    LibsessionStatus::InvalidArgument
316}
317
318/// Frees an owned session ID handle.
319///
320/// # Safety
321///
322/// `id` must be null or a live handle allocated by this library. The caller
323/// must own the handle and ensure it is freed only once, including through
324/// session-list cleanup. No other pointer or reference may access the handle
325/// during this call. The handle and any references into it must not be used
326/// afterward.
327#[unsafe(no_mangle)]
328pub unsafe extern "C" fn libsession_session_free(id: *mut LibsessionSession) {
329    if !id.is_null() {
330        unsafe { drop(Box::from_raw(id)) };
331    }
332}
333
334#[unsafe(no_mangle)]
335/// # Safety
336/// `ctx` must be a valid context and `ret_uid` must point to writable storage.
337pub unsafe extern "C" fn libsession_pid_get_owner_uid(
338    ctx: *mut LibsessionContext,
339    pid: libc::pid_t,
340    ret_uid: *mut libc::uid_t,
341) -> LibsessionStatus {
342    if ctx.is_null() || pid < 0 || ret_uid.is_null() {
343        return LibsessionStatus::InvalidArgument;
344    }
345
346    let libsession = unsafe { &mut *ctx.cast::<Libsession>() };
347
348    match libsession.backend.get_owner_uid(pid.cast_unsigned()) {
349        Ok(owner_uid) => {
350            unsafe { ret_uid.write(owner_uid) };
351            LibsessionStatus::Ok
352        }
353        Err(error) => {
354            libsession.logger.log(LibsessionLogLevel::Error, || {
355                format!("failed to get owner UID for process {pid}: {error}")
356            });
357            LibsessionStatus::from(&error)
358        }
359    }
360}
361
362#[unsafe(no_mangle)]
363/// # Safety
364/// `ctx` must be valid and `ret_session` must point to writable storage.
365pub unsafe extern "C" fn libsession_uid_get_display(
366    ctx: *mut LibsessionContext,
367    uid: libc::uid_t,
368    ret_session: *mut *mut LibsessionSession,
369) -> LibsessionStatus {
370    if ctx.is_null() || ret_session.is_null() {
371        return LibsessionStatus::InvalidArgument;
372    }
373
374    let libsession = unsafe { &mut *ctx.cast::<Libsession>() };
375
376    if let Some(session_id) = libsession.backend.get_display(uid) {
377        unsafe { ret_session.write(Box::into_raw(Box::new(LibsessionSession(session_id)))) };
378        return LibsessionStatus::Ok;
379    }
380
381    LibsessionStatus::NotFound
382}
383
384#[unsafe(no_mangle)]
385/// # Safety
386/// `ctx` must be valid and `ret_state` must point to writable storage.
387pub unsafe extern "C" fn libsession_uid_get_state(
388    ctx: *mut LibsessionContext,
389    uid: libc::uid_t,
390    ret_state: *mut LibsessionUserState,
391) -> LibsessionStatus {
392    if ctx.is_null() || ret_state.is_null() {
393        return LibsessionStatus::InvalidArgument;
394    }
395
396    let libsession = unsafe { &mut *ctx.cast::<Libsession>() };
397    let Some(state) = libsession.backend.get_state(uid) else {
398        return LibsessionStatus::NotFound;
399    };
400    unsafe { ret_state.write(state) };
401
402    LibsessionStatus::Ok
403}
404
405#[unsafe(no_mangle)]
406/// # Safety
407/// `ctx` and `session` must be valid, and `ret_uid` must point to writable storage.
408pub unsafe extern "C" fn libsession_session_get_uid(
409    ctx: *mut LibsessionContext,
410    session: *const LibsessionSession,
411    ret_uid: *mut libc::uid_t,
412) -> LibsessionStatus {
413    if ctx.is_null() || session.is_null() || ret_uid.is_null() {
414        return LibsessionStatus::InvalidArgument;
415    }
416
417    let libsession = unsafe { &mut *ctx.cast::<Libsession>() };
418    let session = unsafe { &*session.cast::<LibsessionSession>() };
419
420    if let Some(owner_uid) = libsession.backend.get_uid(&session.0) {
421        unsafe { ret_uid.write(owner_uid) };
422        return LibsessionStatus::Ok;
423    }
424
425    LibsessionStatus::NotFound
426}
427
428#[unsafe(no_mangle)]
429/// # Safety
430/// `ctx` and `session` must be valid, and `ret_state` must point to writable storage.
431pub unsafe extern "C" fn libsession_session_get_state(
432    ctx: *mut LibsessionContext,
433    session: *const LibsessionSession,
434    ret_state: *mut LibsessionSessionState,
435) -> LibsessionStatus {
436    if ctx.is_null() || session.is_null() || ret_state.is_null() {
437        return LibsessionStatus::InvalidArgument;
438    }
439
440    let libsession = unsafe { &mut *ctx.cast::<Libsession>() };
441    let session = unsafe { &*session.cast::<LibsessionSession>() };
442
443    let state = match libsession.backend.get_session_state(&session.0) {
444        Ok(state) => state,
445        Err(error) => return (&error).into(),
446    };
447    unsafe { ret_state.write(state) };
448
449    LibsessionStatus::Ok
450}
451
452#[unsafe(no_mangle)]
453/// # Safety
454/// `ctx` and `session` must be valid, and `ret_active` must point to writable storage.
455pub unsafe extern "C" fn libsession_session_is_active(
456    ctx: *mut LibsessionContext,
457    session: *const LibsessionSession,
458    ret_active: *mut bool,
459) -> LibsessionStatus {
460    if ctx.is_null() || session.is_null() || ret_active.is_null() {
461        return LibsessionStatus::InvalidArgument;
462    }
463
464    let libsession = unsafe { &mut *ctx.cast::<Libsession>() };
465    let session = unsafe { &*session.cast::<LibsessionSession>() };
466
467    let state = match libsession.backend.get_session_state(&session.0) {
468        Ok(state) => state,
469        Err(error) => return (&error).into(),
470    };
471    unsafe { ret_active.write(state == LibsessionSessionState::Active) };
472
473    LibsessionStatus::Ok
474}
475
476/// Reports whether a session is local.
477///
478/// # Safety
479///
480/// Non-null `ctx` must be a live context with exclusive access for this call.
481/// Non-null `session` must point to a valid NUL-terminated string for this call.
482/// Non-null `ret_local` must point to writable, aligned storage that does not
483/// overlap the context or session string. It is written only on success.
484#[unsafe(no_mangle)]
485pub unsafe extern "C" fn libsession_session_is_local(
486    ctx: *mut LibsessionContext,
487    session: *const LibsessionSession,
488    ret_local: *mut bool,
489) -> LibsessionStatus {
490    if ctx.is_null() || session.is_null() || ret_local.is_null() {
491        return LibsessionStatus::InvalidArgument;
492    }
493
494    let libsession = unsafe { &mut *ctx.cast::<Libsession>() };
495    let session = unsafe { &*session.cast::<LibsessionSession>() };
496
497    let Some(local) = libsession.backend.is_local_session(&session.0) else {
498        return LibsessionStatus::NotFound;
499    };
500    unsafe { ret_local.write(local) };
501
502    LibsessionStatus::Ok
503}
504
505/// Copies a session ID into an allocated, NUL-terminated C string.
506///
507/// Free the returned string with `libsession_session_id_free`.
508///
509/// # Safety
510///
511/// `session` must be null or a live session handle. `ret_id` must point to
512/// writable pointer storage. It is written only on success.
513#[unsafe(no_mangle)]
514pub unsafe extern "C" fn libsession_session_get_id(
515    session: *const LibsessionSession,
516    ret_id: *mut *mut ffi::c_char,
517) -> LibsessionStatus {
518    if session.is_null() || ret_id.is_null() {
519        return LibsessionStatus::InvalidArgument;
520    }
521
522    let session = unsafe { &*session.cast::<LibsessionSession>() };
523
524    let Ok(session_id) = ffi::CString::new(session.0.as_str()) else {
525        return LibsessionStatus::InvalidData;
526    };
527
528    unsafe { ret_id.write(session_id.into_raw()) };
529
530    LibsessionStatus::Ok
531}
532
533/// Frees a string returned by this library, such as an ID from
534/// `libsession_session_get_id`.
535///
536/// # Safety
537///
538/// `string` must be null or a pointer returned by this library that has not
539/// already been freed. The pointer must not be used after this call.
540#[unsafe(no_mangle)]
541pub unsafe extern "C" fn libsession_session_id_free(string: *mut ffi::c_char) {
542    if !string.is_null() {
543        unsafe { drop(ffi::CString::from_raw(string)) };
544    }
545}
546
547#[allow(non_camel_case_types)]
548#[repr(C)]
549pub struct libsession_monitor {
550    _private: [u8; 0],
551}
552
553struct Monitor(Box<dyn backend::BackendMonitor>);
554
555#[unsafe(no_mangle)]
556pub extern "C" fn libsession_monitor_new(ctx: *const LibsessionContext) -> *mut libsession_monitor {
557    if ctx.is_null() {
558        return ptr::null_mut();
559    }
560    let libsession = unsafe { &*ctx.cast::<Libsession>() };
561    if let Some(monitor) = libsession.backend.get_monitor() {
562        return Box::into_raw(Box::new(Monitor(monitor))).cast::<libsession_monitor>();
563    }
564
565    ptr::null_mut()
566}
567
568#[unsafe(no_mangle)]
569pub extern "C" fn libsession_monitor_free(monitor: *mut libsession_monitor) {
570    if monitor.is_null() {
571        return;
572    }
573
574    unsafe { drop(Box::from_raw(monitor.cast::<Monitor>())) }
575}
576
577#[unsafe(no_mangle)]
578pub extern "C" fn libsession_monitor_flush(monitor: *mut libsession_monitor) {
579    if monitor.is_null() {
580        return;
581    }
582    let monitor = unsafe { &mut *monitor.cast::<Monitor>() };
583    monitor.0.flush();
584}
585
586#[unsafe(no_mangle)]
587pub extern "C" fn libsession_monitor_fd(monitor: *mut libsession_monitor) -> ffi::c_int {
588    if monitor.is_null() {
589        return -1;
590    }
591    let monitor = unsafe { &*monitor.cast::<Monitor>() };
592    monitor.0.fd()
593}
594
595pub type LibsessionLogFn = Option<unsafe extern "C" fn(LibsessionLogLevel, *const ffi::c_char)>;
596
597/// Sets the context's logging callback, or disables callbacks with `None`.
598///
599/// # Safety
600///
601/// Non-null `ctx` must be a live context on the thread that created it.
602/// The callback must remain callable until replaced or the context is freed.
603/// It must not unwind, access the context reentrantly, or retain the message
604/// pointer, which is valid only for the duration of the callback.
605#[unsafe(no_mangle)]
606pub unsafe extern "C" fn libsession_set_log_fn(
607    ctx: *mut LibsessionContext,
608    callback: LibsessionLogFn,
609) -> LibsessionStatus {
610    if ctx.is_null() {
611        return LibsessionStatus::InvalidArgument;
612    }
613    let libsession = unsafe { &*ctx.cast::<Libsession>() };
614
615    libsession.logger.set_callback(callback);
616
617    LibsessionStatus::Ok
618}
619
620#[repr(C)]
621#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
622pub enum LibsessionLogLevel {
623    #[default]
624    Off = 0,
625    Error = 1,
626    Warn = 2,
627    Info = 3,
628    Debug = 4,
629    Trace = 5,
630}
631
632/// Sets the context's logging level.
633///
634/// # Safety
635///
636/// Non-null `ctx` must be a live context on the thread that created it and
637/// remain valid for this call.
638#[unsafe(no_mangle)]
639pub unsafe extern "C" fn libsession_set_log_level(
640    ctx: *mut LibsessionContext,
641    log_level: LibsessionLogLevel,
642) -> LibsessionStatus {
643    if ctx.is_null() {
644        return LibsessionStatus::InvalidArgument;
645    }
646    let libsession = unsafe { &*ctx.cast::<Libsession>() };
647
648    libsession.logger.set_level(log_level);
649
650    LibsessionStatus::Ok
651}
652
653/// Retrieves the context's logging level.
654///
655/// # Safety
656///
657/// Non-null `ctx` must be a live context on the thread that created it and
658/// remain valid for this call.
659#[unsafe(no_mangle)]
660pub unsafe extern "C" fn libsession_get_log_level(
661    ctx: *const LibsessionContext,
662) -> LibsessionLogLevel {
663    if ctx.is_null() {
664        return LibsessionLogLevel::Off;
665    }
666    let libsession = unsafe { &*ctx.cast::<Libsession>() };
667    libsession.logger.level()
668}