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#[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#[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#[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)]
335pub 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)]
363pub 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)]
385pub 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)]
406pub 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)]
429pub 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)]
453pub 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#[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#[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#[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#[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#[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#[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}