Skip to main content

session/
lib.rs

1mod backend;
2mod logging;
3
4#[cfg(not(any(feature = "sessiond", feature = "logind", feature = "consolekit")))]
5compile_error!(
6    "libsession requires at least one backend feature: `sessiond`, `logind`, or `consolekit`"
7);
8
9#[cfg(feature = "consolekit")]
10use backend::consolekit;
11#[cfg(feature = "logind")]
12use backend::logind;
13#[cfg(feature = "sessiond")]
14use backend::sessiond;
15use std::{env, ffi, process, ptr, rc};
16
17struct Libsession {
18    backend: Box<dyn backend::Backend>,
19    logger: rc::Rc<logging::Logger>,
20}
21
22#[allow(non_camel_case_types)]
23#[repr(C)]
24pub struct libsession_context {
25    _private: [u8; 0],
26}
27
28#[repr(C)]
29pub enum LibsessionStatus {
30    Ok = 0,
31    Failed = 1,
32}
33
34#[repr(C)]
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum LibsessionUserState {
37    Offline = 0,
38    Lingering = 1,
39    Online = 2,
40    Active = 3,
41    Closing = 4,
42    Unknown = 5,
43}
44
45#[repr(C)]
46#[derive(PartialEq, Eq)]
47pub enum LibsessionSessionState {
48    Online = 0,
49    Active = 1,
50    Closing = 2,
51    Opening = 3,
52    Unkown = 4,
53}
54
55#[unsafe(no_mangle)]
56#[must_use]
57pub extern "C" fn libsession_new() -> *mut libsession_context {
58    let logger = rc::Rc::new(logging::Logger::default());
59    if let Ok(backend) = env::var("LIBSESSION_BACKEND") {
60        match backend.as_str() {
61            #[cfg(feature = "sessiond")]
62            "sessiond" => {
63                if let Some(sessiond) = sessiond::Sessiond::try_connect(rc::Rc::clone(&logger)) {
64                    return Box::into_raw(Box::new(Libsession {
65                        backend: Box::new(sessiond),
66                        logger,
67                    }))
68                    .cast::<libsession_context>();
69                }
70            }
71            #[cfg(feature = "logind")]
72            "logind" => {
73                if let Some(logind) = logind::Logind::try_connect(rc::Rc::clone(&logger)) {
74                    return Box::into_raw(Box::new(Libsession {
75                        backend: Box::new(logind),
76                        logger,
77                    }))
78                    .cast::<libsession_context>();
79                }
80            }
81            #[cfg(feature = "consolekit")]
82            "consolekit" => {
83                if let Some(consolekit) =
84                    consolekit::ConsoleKit::try_connect(rc::Rc::clone(&logger))
85                {
86                    return Box::into_raw(Box::new(Libsession {
87                        backend: Box::new(consolekit),
88                        logger,
89                    }))
90                    .cast::<libsession_context>();
91                }
92            }
93            _ => {}
94        }
95    }
96
97    #[cfg(feature = "sessiond")]
98    if let Some(sessiond) = sessiond::Sessiond::try_connect(rc::Rc::clone(&logger)) {
99        return Box::into_raw(Box::new(Libsession {
100            backend: Box::new(sessiond),
101            logger,
102        }))
103        .cast::<libsession_context>();
104    }
105    #[cfg(feature = "logind")]
106    if let Some(logind) = logind::Logind::try_connect(rc::Rc::clone(&logger)) {
107        return Box::into_raw(Box::new(Libsession {
108            backend: Box::new(logind),
109            logger,
110        }))
111        .cast::<libsession_context>();
112    }
113    #[cfg(feature = "consolekit")]
114    if let Some(consolekit) = consolekit::ConsoleKit::try_connect(rc::Rc::clone(&logger)) {
115        return Box::into_raw(Box::new(Libsession {
116            backend: Box::new(consolekit),
117            logger,
118        }))
119        .cast::<libsession_context>();
120    }
121
122    ptr::null_mut()
123}
124
125#[unsafe(no_mangle)]
126pub extern "C" fn libsession_free(ctx: *mut libsession_context) {
127    if ctx.is_null() {
128        return;
129    }
130
131    unsafe { drop(Box::from_raw(ctx.cast::<Libsession>())) }
132}
133
134/// Retrieves an allocated session ID for a process.
135///
136/// # Safety
137///
138/// Non-null `ctx` must be a live context with exclusive access for this call.
139/// Non-null `ret_session` must point to writable, aligned pointer storage that
140/// does not overlap the context. It is written only on success. Free the
141/// resulting string with `libsession_session_id_free`.
142#[unsafe(no_mangle)]
143pub unsafe extern "C" fn libsession_pid_get_session(
144    ctx: *mut libsession_context,
145    pid: libc::pid_t,
146    ret_session: *mut *mut ffi::c_char,
147) -> LibsessionStatus {
148    if ctx.is_null() || ret_session.is_null() {
149        return LibsessionStatus::Failed;
150    }
151
152    let libsession = unsafe { &mut *ctx.cast::<Libsession>() };
153
154    let Ok(pid) = pid.try_into() else {
155        return LibsessionStatus::Failed;
156    };
157    let Some(session_id) = libsession.backend.get_session_id(pid) else {
158        return LibsessionStatus::Failed;
159    };
160
161    let Ok(session_id) = ffi::CString::new(session_id) else {
162        return LibsessionStatus::Failed;
163    };
164
165    unsafe { ret_session.write(session_id.into_raw()) };
166    LibsessionStatus::Ok
167}
168
169/// Retrieves an allocated session ID for the current process.
170///
171/// # Safety
172///
173/// The pointer requirements of `libsession_pid_get_session` apply.
174#[unsafe(no_mangle)]
175pub unsafe extern "C" fn libsession_get_session(
176    ctx: *mut libsession_context,
177    ret_session: *mut *mut ffi::c_char,
178) -> LibsessionStatus {
179    let pid = process::id();
180    if let Ok(pid) = pid.try_into() {
181        return unsafe { libsession_pid_get_session(ctx, pid, ret_session) };
182    }
183
184    LibsessionStatus::Failed
185}
186
187/// Frees a session ID returned by a session ID or display lookup.
188///
189/// # Safety
190///
191/// `id` must be null or a pointer written on success by `libsession_get_session`,
192/// `libsession_pid_get_session`, or `libsession_uid_get_display` that has not
193/// already been freed. Its
194/// terminating NUL must remain at its original position. The pointer must not
195/// be used after this call.
196#[unsafe(no_mangle)]
197pub unsafe extern "C" fn libsession_session_id_free(id: *mut ffi::c_char) {
198    if !id.is_null() {
199        unsafe { drop(ffi::CString::from_raw(id)) };
200    }
201}
202
203#[unsafe(no_mangle)]
204/// # Safety
205/// `ctx` must be a valid context and `ret_uid` must point to writable storage.
206pub unsafe extern "C" fn libsession_pid_get_owner_uid(
207    ctx: *mut libsession_context,
208    pid: libc::pid_t,
209    ret_uid: *mut libc::uid_t,
210) -> LibsessionStatus {
211    if ctx.is_null() || pid < 0 || ret_uid.is_null() {
212        return LibsessionStatus::Failed;
213    }
214
215    let libsession = unsafe { &mut *ctx.cast::<Libsession>() };
216
217    if let Some(owner_uid) = libsession.backend.get_owner_uid(pid.cast_unsigned()) {
218        unsafe { ret_uid.write(owner_uid) };
219        return LibsessionStatus::Ok;
220    }
221
222    LibsessionStatus::Failed
223}
224
225#[unsafe(no_mangle)]
226/// # Safety
227/// `ctx` must be valid and `ret_session` must point to writable storage.
228pub unsafe extern "C" fn libsession_uid_get_display(
229    ctx: *mut libsession_context,
230    uid: libc::uid_t,
231    ret_session: *mut *mut ffi::c_char,
232) -> LibsessionStatus {
233    if ctx.is_null() || ret_session.is_null() {
234        return LibsessionStatus::Failed;
235    }
236
237    let libsession = unsafe { &mut *ctx.cast::<Libsession>() };
238
239    if let Some(session_id) = libsession.backend.get_display(uid)
240        && let Ok(session_id) = ffi::CString::new(session_id)
241    {
242        unsafe { ret_session.write(session_id.into_raw()) };
243        return LibsessionStatus::Ok;
244    }
245
246    LibsessionStatus::Failed
247}
248
249#[unsafe(no_mangle)]
250/// # Safety
251/// `ctx` must be valid and `ret_state` must point to writable storage.
252pub unsafe extern "C" fn libsession_uid_get_state(
253    ctx: *mut libsession_context,
254    uid: libc::uid_t,
255    ret_state: *mut LibsessionUserState,
256) -> LibsessionStatus {
257    if ctx.is_null() || ret_state.is_null() {
258        return LibsessionStatus::Failed;
259    }
260
261    let libsession = unsafe { &mut *ctx.cast::<Libsession>() };
262    let Some(state) = libsession.backend.get_state(uid) else {
263        return LibsessionStatus::Failed;
264    };
265    unsafe { ret_state.write(state) };
266
267    LibsessionStatus::Ok
268}
269
270#[unsafe(no_mangle)]
271/// # Safety
272/// `ctx` and `session` must be valid, and `ret_uid` must point to writable storage.
273pub unsafe extern "C" fn libsession_session_get_uid(
274    ctx: *mut libsession_context,
275    session: *const ffi::c_char,
276    ret_uid: *mut libc::uid_t,
277) -> LibsessionStatus {
278    if ctx.is_null() || session.is_null() || ret_uid.is_null() {
279        return LibsessionStatus::Failed;
280    }
281
282    let libsession = unsafe { &mut *ctx.cast::<Libsession>() };
283    if let Some(owner_uid) = libsession
284        .backend
285        .get_uid(unsafe { ffi::CStr::from_ptr(session) })
286    {
287        unsafe { ret_uid.write(owner_uid) };
288        return LibsessionStatus::Ok;
289    }
290
291    LibsessionStatus::Failed
292}
293
294#[unsafe(no_mangle)]
295/// # Safety
296/// `ctx` and `session` must be valid, and `ret_state` must point to writable storage.
297pub unsafe extern "C" fn libsession_session_get_state(
298    ctx: *mut libsession_context,
299    session: *const ffi::c_char,
300    ret_state: *mut LibsessionSessionState,
301) -> LibsessionStatus {
302    if ctx.is_null() || session.is_null() || ret_state.is_null() {
303        return LibsessionStatus::Failed;
304    }
305
306    let libsession = unsafe { &mut *ctx.cast::<Libsession>() };
307    let Some(state) = libsession
308        .backend
309        .get_session_state(unsafe { ffi::CStr::from_ptr(session) })
310    else {
311        return LibsessionStatus::Failed;
312    };
313    unsafe { ret_state.write(state) };
314
315    LibsessionStatus::Ok
316}
317
318#[unsafe(no_mangle)]
319/// # Safety
320/// `ctx` and `session` must be valid, and `ret_active` must point to writable storage.
321pub unsafe extern "C" fn libsession_session_is_active(
322    ctx: *mut libsession_context,
323    session: *const ffi::c_char,
324    ret_active: *mut bool,
325) -> LibsessionStatus {
326    if ctx.is_null() || session.is_null() || ret_active.is_null() {
327        return LibsessionStatus::Failed;
328    }
329
330    let libsession = unsafe { &mut *ctx.cast::<Libsession>() };
331    let Some(state) = libsession
332        .backend
333        .get_session_state(unsafe { ffi::CStr::from_ptr(session) })
334    else {
335        return LibsessionStatus::Failed;
336    };
337    unsafe { ret_active.write(state == LibsessionSessionState::Active) };
338
339    LibsessionStatus::Ok
340}
341
342/// Reports whether a session is local.
343///
344/// # Safety
345///
346/// Non-null `ctx` must be a live context with exclusive access for this call.
347/// Non-null `session` must point to a valid NUL-terminated string for this call.
348/// Non-null `ret_local` must point to writable, aligned storage that does not
349/// overlap the context or session string. It is written only on success.
350#[unsafe(no_mangle)]
351pub unsafe extern "C" fn libsession_session_is_local(
352    ctx: *mut libsession_context,
353    session: *const ffi::c_char,
354    ret_local: *mut bool,
355) -> LibsessionStatus {
356    if ctx.is_null() || session.is_null() || ret_local.is_null() {
357        return LibsessionStatus::Failed;
358    }
359
360    let libsession = unsafe { &mut *ctx.cast::<Libsession>() };
361    let Some(local) = libsession
362        .backend
363        .is_local_session(unsafe { ffi::CStr::from_ptr(session) })
364    else {
365        return LibsessionStatus::Failed;
366    };
367    unsafe { ret_local.write(local) };
368
369    LibsessionStatus::Ok
370}
371
372#[allow(non_camel_case_types)]
373#[repr(C)]
374pub struct libsession_monitor {
375    _private: [u8; 0],
376}
377
378struct Monitor(Box<dyn backend::BackendMonitor>);
379
380#[unsafe(no_mangle)]
381pub extern "C" fn libsession_monitor_new(
382    ctx: *const libsession_context,
383) -> *mut libsession_monitor {
384    if ctx.is_null() {
385        return ptr::null_mut();
386    }
387    let libsession = unsafe { &*ctx.cast::<Libsession>() };
388    if let Some(monitor) = libsession.backend.get_monitor() {
389        return Box::into_raw(Box::new(Monitor(monitor))).cast::<libsession_monitor>();
390    }
391
392    ptr::null_mut()
393}
394
395#[unsafe(no_mangle)]
396pub extern "C" fn libsession_monitor_free(monitor: *mut libsession_monitor) {
397    if monitor.is_null() {
398        return;
399    }
400
401    unsafe { drop(Box::from_raw(monitor.cast::<Monitor>())) }
402}
403
404#[unsafe(no_mangle)]
405pub extern "C" fn libsession_monitor_flush(monitor: *mut libsession_monitor) {
406    if monitor.is_null() {
407        return;
408    }
409    let monitor = unsafe { &mut *monitor.cast::<Monitor>() };
410    monitor.0.flush();
411}
412
413#[unsafe(no_mangle)]
414pub extern "C" fn libsession_monitor_fd(monitor: *mut libsession_monitor) -> ffi::c_int {
415    if monitor.is_null() {
416        return -1;
417    }
418    let monitor = unsafe { &*monitor.cast::<Monitor>() };
419    monitor.0.fd()
420}
421
422pub type LibsessionLogFn = Option<unsafe extern "C" fn(LibsessionLogLevel, *const ffi::c_char)>;
423
424/// Sets the context's logging callback, or disables callbacks with `None`.
425///
426/// # Safety
427///
428/// Non-null `ctx` must be a live context on the thread that created it.
429/// The callback must remain callable until replaced or the context is freed.
430/// It must not unwind, access the context reentrantly, or retain the message
431/// pointer, which is valid only for the duration of the callback.
432#[unsafe(no_mangle)]
433pub unsafe extern "C" fn libsession_set_log_fn(
434    ctx: *mut libsession_context,
435    callback: LibsessionLogFn,
436) -> LibsessionStatus {
437    if ctx.is_null() {
438        return LibsessionStatus::Failed;
439    }
440    let libsession = unsafe { &*ctx.cast::<Libsession>() };
441
442    libsession.logger.set_callback(callback);
443
444    LibsessionStatus::Ok
445}
446
447#[repr(C)]
448#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
449pub enum LibsessionLogLevel {
450    #[default]
451    Off = 0,
452    Error = 1,
453    Warn = 2,
454    Info = 3,
455    Debug = 4,
456    Trace = 5,
457}
458
459/// Sets the context's logging level.
460///
461/// # Safety
462///
463/// Non-null `ctx` must be a live context on the thread that created it and
464/// remain valid for this call.
465#[unsafe(no_mangle)]
466pub unsafe extern "C" fn libsession_set_log_level(
467    ctx: *mut libsession_context,
468    log_level: LibsessionLogLevel,
469) -> LibsessionStatus {
470    if ctx.is_null() {
471        return LibsessionStatus::Failed;
472    }
473    let libsession = unsafe { &*ctx.cast::<Libsession>() };
474
475    libsession.logger.set_level(log_level);
476
477    LibsessionStatus::Ok
478}
479
480/// Retrieves the context's logging level.
481///
482/// # Safety
483///
484/// Non-null `ctx` must be a live context on the thread that created it and
485/// remain valid for this call.
486#[unsafe(no_mangle)]
487pub unsafe extern "C" fn libsession_get_log_level(
488    ctx: *const libsession_context,
489) -> LibsessionLogLevel {
490    if ctx.is_null() {
491        return LibsessionLogLevel::Off;
492    }
493    let libsession = unsafe { &*ctx.cast::<Libsession>() };
494    libsession.logger.level()
495}