1use libloading::{Library, Symbol};
8use std::env;
9use std::error::Error as StdError;
10use std::ffi::{c_char, c_double, c_int, c_uint, c_void, CStr};
11use std::fmt;
12use std::fs;
13use std::path::{Path, PathBuf};
14use std::sync::Arc;
15
16pub type RawOop = u64;
17pub type Result<T> = std::result::Result<T, GciError>;
18
19pub const OOP_ILLEGAL: RawOop = 0x01;
20pub const OOP_NIL: RawOop = 0x14;
21pub const OOP_FALSE: RawOop = 0x0C;
22pub const OOP_TRUE: RawOop = 0x10C;
23pub const OOP_ASCII_NUL: RawOop = 0x1C;
24pub const GCI_INVALID_SESSION: RawOop = 0;
25pub const GCI_ENCRYPT_BUF_SIZE: usize = 1024;
26pub const GCI_LOGIN_PW_ENCRYPTED: RawOop = 0x1;
27pub const GCI_LOGIN_IS_GCSTS: RawOop = 0x2;
28pub const GCI_ERR_STR_SIZE: usize = 1024;
29pub const GCI_MAX_ERR_ARGS: usize = 10;
30
31const TAG_SMALLINT: RawOop = 0x2;
32const TAG_SMALLDOUBLE: RawOop = 0x6;
33const TAG_SPECIAL: RawOop = 0x4;
34const SMALLINT_SHIFT: u32 = 3;
35const CHAR_TAG_BYTE: RawOop = 0x1C;
36
37#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
38pub struct Oop(pub RawOop);
39
40impl Oop {
41 pub const ILLEGAL: Self = Self(OOP_ILLEGAL);
42 pub const NIL: Self = Self(OOP_NIL);
43 pub const FALSE: Self = Self(OOP_FALSE);
44 pub const TRUE: Self = Self(OOP_TRUE);
45
46 pub fn from_smallint(value: i64) -> Self {
47 Self(i64_to_smallint(value))
48 }
49
50 pub fn from_bool(value: bool) -> Self {
51 if value {
52 Self::TRUE
53 } else {
54 Self::FALSE
55 }
56 }
57
58 pub fn from_char(value: char) -> Self {
59 Self(char_to_oop(value))
60 }
61
62 pub fn raw(self) -> RawOop {
63 self.0
64 }
65
66 pub fn is_illegal(self) -> bool {
67 self.0 == OOP_ILLEGAL
68 }
69
70 pub fn is_nil(self) -> bool {
71 self.0 == OOP_NIL
72 }
73
74 pub fn is_boolean(self) -> bool {
75 matches!(self.0, OOP_TRUE | OOP_FALSE)
76 }
77
78 pub fn as_bool(self) -> Option<bool> {
79 match self.0 {
80 OOP_TRUE => Some(true),
81 OOP_FALSE => Some(false),
82 _ => None,
83 }
84 }
85
86 pub fn is_smallint(self) -> bool {
87 is_smallint(self.0)
88 }
89
90 pub fn as_smallint(self) -> Option<i64> {
91 self.is_smallint().then(|| smallint_to_i64(self.0))
92 }
93
94 pub fn is_char(self) -> bool {
95 is_char(self.0)
96 }
97
98 pub fn as_char(self) -> Result<Option<char>> {
99 if self.is_char() {
100 char_from_oop(self.0).map(Some)
101 } else {
102 Ok(None)
103 }
104 }
105}
106
107impl From<RawOop> for Oop {
108 fn from(value: RawOop) -> Self {
109 Self(value)
110 }
111}
112
113impl From<Oop> for RawOop {
114 fn from(value: Oop) -> Self {
115 value.0
116 }
117}
118
119#[repr(C)]
120#[derive(Clone, Copy)]
121pub struct GciErrSType {
122 pub category: RawOop,
123 pub context: RawOop,
124 pub exception_obj: RawOop,
125 pub args: [RawOop; GCI_MAX_ERR_ARGS],
126 pub number: c_int,
127 pub arg_count: c_int,
128 pub fatal: u8,
129 pub message: [c_char; GCI_ERR_STR_SIZE + 1],
130 pub reason: [c_char; GCI_ERR_STR_SIZE + 1],
131}
132
133impl Default for GciErrSType {
134 fn default() -> Self {
135 unsafe { std::mem::zeroed() }
138 }
139}
140
141impl GciErrSType {
142 pub fn message_text(&self) -> String {
143 c_char_array_to_string(&self.message)
144 }
145
146 pub fn reason_text(&self) -> String {
147 c_char_array_to_string(&self.reason)
148 }
149
150 pub fn full_message(&self) -> String {
151 let message = self.message_text();
152 let reason = self.reason_text();
153 if reason.is_empty() || reason == message {
154 message
155 } else {
156 format!("{message} [{reason}]")
157 }
158 }
159}
160
161#[derive(Debug)]
162pub enum GciError {
163 LibraryNotFound,
164 LibraryLoad {
165 path: PathBuf,
166 source: libloading::Error,
167 },
168 Symbol {
169 path: PathBuf,
170 symbol: String,
171 source: libloading::Error,
172 },
173 ReadDir {
174 path: PathBuf,
175 source: std::io::Error,
176 },
177 InvalidCharOop(RawOop),
178}
179
180impl fmt::Display for GciError {
181 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182 match self {
183 Self::LibraryNotFound => write!(
184 f,
185 "Cannot find libgcirpc. Pass a library path or set GS_LIB_PATH/GS_LIB/GEMSTONE."
186 ),
187 Self::LibraryLoad { path, source } => {
188 write!(f, "cannot load {}: {source}", path.display())
189 }
190 Self::Symbol {
191 path,
192 symbol,
193 source,
194 } => write!(f, "{symbol} not found in {}: {source}", path.display()),
195 Self::ReadDir { path, source } => {
196 write!(f, "cannot read {}: {source}", path.display())
197 }
198 Self::InvalidCharOop(oop) => write!(f, "invalid GemStone character OOP {oop}"),
199 }
200 }
201}
202
203impl StdError for GciError {
204 fn source(&self) -> Option<&(dyn StdError + 'static)> {
205 match self {
206 Self::LibraryLoad { source, .. } => Some(source),
207 Self::Symbol { source, .. } => Some(source),
208 Self::ReadDir { source, .. } => Some(source),
209 Self::LibraryNotFound | Self::InvalidCharOop(_) => None,
210 }
211 }
212}
213
214#[derive(Clone)]
215pub struct GciLibrary {
216 library: Arc<Library>,
217 path: PathBuf,
218}
219
220impl GciLibrary {
221 pub fn load(lib_path: Option<PathBuf>) -> Result<Self> {
222 let path = resolve_library_path(lib_path)?;
223 unsafe { Self::load_path(path) }
226 }
227
228 pub unsafe fn load_path(path: PathBuf) -> Result<Self> {
235 let library = Library::new(&path).map_err(|source| GciError::LibraryLoad {
236 path: path.clone(),
237 source,
238 })?;
239 Ok(Self {
240 library: Arc::new(library),
241 path,
242 })
243 }
244
245 pub fn path(&self) -> &Path {
246 &self.path
247 }
248
249 pub unsafe fn gci_init(&self) -> Result<c_int> {
255 let init: Symbol<unsafe extern "C" fn() -> c_int> = self.symbol(b"GciInit")?;
256 Ok(init())
257 }
258
259 pub unsafe fn gci_set_net(
266 &self,
267 stone_nrs: &CStr,
268 host_username: &CStr,
269 encrypted_host_password: *const c_char,
270 gem_service: &CStr,
271 ) -> Result<()> {
272 let set_net: Symbol<
273 unsafe extern "C" fn(*const c_char, *const c_char, *const c_char, *const c_char),
274 > = self.symbol(b"GciSetNet")?;
275 set_net(
276 stone_nrs.as_ptr(),
277 host_username.as_ptr(),
278 encrypted_host_password,
279 gem_service.as_ptr(),
280 );
281 Ok(())
282 }
283
284 pub unsafe fn gci_encrypt(
290 &self,
291 password: &CStr,
292 buffer: *mut c_char,
293 buffer_size: c_uint,
294 ) -> Result<*mut c_char> {
295 let encrypt: Symbol<
296 unsafe extern "C" fn(*const c_char, *mut c_char, c_uint) -> *mut c_char,
297 > = self.symbol(b"GciEncrypt")?;
298 Ok(encrypt(password.as_ptr(), buffer, buffer_size))
299 }
300
301 pub unsafe fn gci_login_ex(
308 &self,
309 username: &CStr,
310 password: &CStr,
311 flags: c_uint,
312 halt_on_error: c_int,
313 ) -> Result<c_int> {
314 let login: Symbol<
315 unsafe extern "C" fn(*const c_char, *const c_char, c_uint, c_int) -> c_int,
316 > = self.symbol(b"GciLoginEx")?;
317 Ok(login(
318 username.as_ptr(),
319 password.as_ptr(),
320 flags,
321 halt_on_error,
322 ))
323 }
324
325 pub unsafe fn gci_logout(&self) -> Result<c_int> {
331 let logout: Symbol<unsafe extern "C" fn() -> c_int> = self.symbol(b"GciLogout")?;
332 Ok(logout())
333 }
334
335 pub unsafe fn gci_commit(&self, err: &mut GciErrSType) -> Result<c_int> {
341 self.gci_commit_ptr(err as *mut GciErrSType as *mut c_void)
342 }
343
344 pub unsafe fn gci_commit_ptr(&self, err: *mut c_void) -> Result<c_int> {
351 let commit: Symbol<unsafe extern "C" fn(*mut c_void) -> c_int> =
352 self.symbol(b"GciCommit")?;
353 Ok(commit(err))
354 }
355
356 pub unsafe fn gci_abort(&self, err: &mut GciErrSType) -> Result<c_int> {
362 self.gci_abort_ptr(err as *mut GciErrSType as *mut c_void)
363 }
364
365 pub unsafe fn gci_abort_ptr(&self, err: *mut c_void) -> Result<c_int> {
372 let abort: Symbol<unsafe extern "C" fn(*mut c_void) -> c_int> = self.symbol(b"GciAbort")?;
373 Ok(abort(err))
374 }
375
376 pub unsafe fn gci_err(&self, err: &mut GciErrSType) -> Result<c_int> {
382 self.gci_err_ptr(err as *mut GciErrSType as *mut c_void)
383 }
384
385 pub unsafe fn gci_err_ptr(&self, err: *mut c_void) -> Result<c_int> {
391 let gci_err: Symbol<unsafe extern "C" fn(*mut c_void) -> c_int> = self.symbol(b"GciErr")?;
392 Ok(gci_err(err))
393 }
394
395 pub unsafe fn gci_execute_str(&self, source: &CStr, receiver: RawOop) -> Result<RawOop> {
402 let execute: Symbol<unsafe extern "C" fn(*const c_char, RawOop) -> RawOop> =
403 self.symbol(b"GciExecuteStr")?;
404 Ok(execute(source.as_ptr(), receiver))
405 }
406
407 pub unsafe fn gci_new_string(&self, value: &CStr) -> Result<RawOop> {
414 let new_string: Symbol<unsafe extern "C" fn(*const c_char) -> RawOop> =
415 self.symbol(b"GciNewString")?;
416 Ok(new_string(value.as_ptr()))
417 }
418
419 pub unsafe fn gci_new_symbol(&self, value: &CStr) -> Result<RawOop> {
426 let new_symbol: Symbol<unsafe extern "C" fn(*const c_char) -> RawOop> =
427 self.symbol(b"GciNewSymbol")?;
428 Ok(new_symbol(value.as_ptr()))
429 }
430
431 pub unsafe fn gci_flt_to_oop(&self, value: c_double) -> Result<RawOop> {
437 let flt_to_oop: Symbol<unsafe extern "C" fn(c_double) -> RawOop> =
438 self.symbol(b"GciFltToOop")?;
439 Ok(flt_to_oop(value))
440 }
441
442 pub unsafe fn gci_oop_to_flt(&self, oop: RawOop, value: *mut c_double) -> Result<c_int> {
449 let oop_to_flt: Symbol<unsafe extern "C" fn(RawOop, *mut c_double) -> c_int> =
450 self.symbol(b"GciOopToFlt_")?;
451 Ok(oop_to_flt(oop, value))
452 }
453
454 pub unsafe fn gci_fetch_size(&self, oop: RawOop) -> Result<i64> {
460 let fetch_size: Symbol<unsafe extern "C" fn(RawOop) -> i64> =
461 self.symbol(b"GciFetchSize_")?;
462 Ok(fetch_size(oop))
463 }
464
465 pub unsafe fn gci_fetch_bytes(
472 &self,
473 oop: RawOop,
474 start: i64,
475 buffer: *mut c_char,
476 count: i64,
477 ) -> Result<i64> {
478 let fetch_bytes: Symbol<unsafe extern "C" fn(RawOop, i64, *mut c_char, i64) -> i64> =
479 self.symbol(b"GciFetchBytes_")?;
480 Ok(fetch_bytes(oop, start, buffer, count))
481 }
482
483 pub unsafe fn gci_fetch_class(&self, oop: RawOop) -> Result<RawOop> {
489 let fetch_class: Symbol<unsafe extern "C" fn(RawOop) -> RawOop> =
490 self.symbol(b"GciFetchClass")?;
491 Ok(fetch_class(oop))
492 }
493
494 pub unsafe fn gci_perform(
501 &self,
502 receiver: RawOop,
503 selector: &CStr,
504 args: *const RawOop,
505 argc: c_int,
506 ) -> Result<RawOop> {
507 let perform: Symbol<
508 unsafe extern "C" fn(RawOop, *const c_char, *const RawOop, c_int) -> RawOop,
509 > = self.symbol(b"GciPerform")?;
510 Ok(perform(receiver, selector.as_ptr(), args, argc))
511 }
512
513 pub unsafe fn gci_new_oop(&self, class_oop: RawOop) -> Result<RawOop> {
519 let new_oop: Symbol<unsafe extern "C" fn(RawOop) -> RawOop> = self.symbol(b"GciNewOop")?;
520 Ok(new_oop(class_oop))
521 }
522
523 pub unsafe fn gci_resolve_symbol(&self, name: &CStr, symbol_list: RawOop) -> Result<RawOop> {
530 let resolve: Symbol<unsafe extern "C" fn(*const c_char, RawOop) -> RawOop> =
531 self.symbol(b"GciResolveSymbol")?;
532 Ok(resolve(name.as_ptr(), symbol_list))
533 }
534
535 pub unsafe fn gci_sym_dict_at_put(
542 &self,
543 dict: RawOop,
544 key: &CStr,
545 value: RawOop,
546 ) -> Result<()> {
547 let at_put: Symbol<unsafe extern "C" fn(RawOop, *const c_char, RawOop)> =
548 self.symbol(b"GciSymDictAtPut")?;
549 at_put(dict, key.as_ptr(), value);
550 Ok(())
551 }
552
553 pub unsafe fn gci_sym_dict_at_obj_put(
559 &self,
560 dict: RawOop,
561 key: RawOop,
562 value: RawOop,
563 ) -> Result<()> {
564 let at_put: Symbol<unsafe extern "C" fn(RawOop, RawOop, RawOop)> =
565 self.symbol(b"GciSymDictAtObjPut")?;
566 at_put(dict, key, value);
567 Ok(())
568 }
569
570 pub unsafe fn gci_str_key_value_dict_at_put(
577 &self,
578 dict: RawOop,
579 key: &CStr,
580 value: RawOop,
581 ) -> Result<()> {
582 let at_put: Symbol<unsafe extern "C" fn(RawOop, *const c_char, RawOop)> =
583 self.symbol(b"GciStrKeyValueDictAtPut")?;
584 at_put(dict, key.as_ptr(), value);
585 Ok(())
586 }
587
588 pub unsafe fn gci_str_key_value_dict_at(
595 &self,
596 dict: RawOop,
597 key: &CStr,
598 value: *mut RawOop,
599 ) -> Result<()> {
600 let at: Symbol<unsafe extern "C" fn(RawOop, *const c_char, *mut RawOop)> =
601 self.symbol(b"GciStrKeyValueDictAt")?;
602 at(dict, key.as_ptr(), value);
603 Ok(())
604 }
605
606 pub unsafe fn gci_sym_dict_at(
613 &self,
614 dict: RawOop,
615 key: &CStr,
616 value: *mut RawOop,
617 assoc: *mut RawOop,
618 ) -> Result<()> {
619 let at: Symbol<unsafe extern "C" fn(RawOop, *const c_char, *mut RawOop, *mut RawOop)> =
620 self.symbol(b"GciSymDictAt")?;
621 at(dict, key.as_ptr(), value, assoc);
622 Ok(())
623 }
624
625 pub unsafe fn gci_get_session_id(&self) -> Result<c_int> {
631 let get_session_id: Symbol<unsafe extern "C" fn() -> c_int> =
632 self.symbol(b"GciGetSessionId")?;
633 Ok(get_session_id())
634 }
635
636 pub unsafe fn gci_set_session_id(&self, session_id: c_int) -> Result<()> {
643 let set_session_id: Symbol<unsafe extern "C" fn(c_int)> =
644 self.symbol(b"GciSetSessionId")?;
645 set_session_id(session_id);
646 Ok(())
647 }
648
649 pub unsafe fn gci_needs_commit(&self) -> Result<c_int> {
655 let needs_commit: Symbol<unsafe extern "C" fn() -> c_int> =
656 self.symbol(b"GciNeedsCommit")?;
657 Ok(needs_commit())
658 }
659
660 pub unsafe fn gci_in_transaction(&self) -> Result<c_int> {
666 let in_transaction: Symbol<unsafe extern "C" fn() -> c_int> =
667 self.symbol(b"GciInTransaction")?;
668 Ok(in_transaction())
669 }
670
671 pub unsafe fn call_optional_oop_export(&self, name: &[u8], oop: RawOop) -> Result<bool> {
679 let symbol = self.library.get::<unsafe extern "C" fn(RawOop)>(name);
680 if let Ok(function) = symbol {
681 function(oop);
682 return Ok(true);
683 }
684 Ok(false)
685 }
686
687 fn symbol<T>(&self, name: &[u8]) -> Result<Symbol<'_, T>> {
688 unsafe { self.library.get(name) }.map_err(|source| GciError::Symbol {
689 path: self.path.clone(),
690 symbol: String::from_utf8_lossy(name).to_string(),
691 source,
692 })
693 }
694}
695
696pub fn is_smallint(oop: RawOop) -> bool {
697 (oop & 0x7) == TAG_SMALLINT
698}
699
700pub fn is_smalldouble(oop: RawOop) -> bool {
701 (oop & 0x7) == TAG_SMALLDOUBLE
702}
703
704pub fn smallint_to_i64(oop: RawOop) -> i64 {
705 (oop as i64) >> SMALLINT_SHIFT
706}
707
708pub fn i64_to_smallint(value: i64) -> RawOop {
709 ((value << SMALLINT_SHIFT) as RawOop) | TAG_SMALLINT
710}
711
712pub fn is_char(oop: RawOop) -> bool {
713 (oop & 0xFF) == CHAR_TAG_BYTE && (oop & 0x6) == TAG_SPECIAL
714}
715
716pub fn char_from_oop(oop: RawOop) -> Result<char> {
717 let codepoint = ((oop >> 8) & 0x1F_FFFF) as u32;
718 char::from_u32(codepoint).ok_or(GciError::InvalidCharOop(oop))
719}
720
721pub fn char_to_oop(value: char) -> RawOop {
722 ((value as u32 as RawOop) << 8) | CHAR_TAG_BYTE
723}
724
725pub fn resolve_library_path(lib_path: Option<PathBuf>) -> Result<PathBuf> {
726 if let Some(path) = lib_path {
727 return Ok(path);
728 }
729 if let Ok(path) = env::var("GS_LIB_PATH") {
730 if !path.is_empty() {
731 return Ok(PathBuf::from(path));
732 }
733 }
734 if let Ok(dir) = env::var("GS_LIB") {
735 if !dir.is_empty() {
736 if let Some(path) = find_gcirpc_in_dir(Path::new(&dir))? {
737 return Ok(path);
738 }
739 }
740 }
741 if let Ok(gemstone) = env::var("GEMSTONE") {
742 if !gemstone.is_empty() {
743 let lib_dir = Path::new(&gemstone).join("lib");
744 if let Some(path) = find_gcirpc_in_dir(&lib_dir)? {
745 return Ok(path);
746 }
747 }
748 }
749 Err(GciError::LibraryNotFound)
750}
751
752pub fn find_gcirpc_in_dir(dir: &Path) -> Result<Option<PathBuf>> {
753 if !dir.is_dir() {
754 return Ok(None);
755 }
756 let mut candidates = Vec::new();
757 for entry in fs::read_dir(dir).map_err(|source| GciError::ReadDir {
758 path: dir.to_path_buf(),
759 source,
760 })? {
761 let entry = entry.map_err(|source| GciError::ReadDir {
762 path: dir.to_path_buf(),
763 source,
764 })?;
765 let path = entry.path();
766 let Some(name) = path.file_name().and_then(|value| value.to_str()) else {
767 continue;
768 };
769 if name.starts_with("libgcirpc")
770 && (name.ends_with(".dylib") || name.ends_with(".so") || name.ends_with(".dll"))
771 {
772 candidates.push(path);
773 }
774 }
775 candidates.sort();
776 Ok(candidates.pop())
777}
778
779fn c_char_array_to_string(value: &[c_char]) -> String {
780 let bytes: Vec<u8> = value
781 .iter()
782 .take_while(|byte| **byte != 0)
783 .map(|byte| *byte as u8)
784 .collect();
785 String::from_utf8_lossy(&bytes).into_owned()
786}
787
788#[cfg(test)]
789mod tests {
790 use super::*;
791
792 #[test]
793 fn smallint_helpers_round_trip_signed_values() {
794 for value in [-42, -1, 0, 1, 42] {
795 assert_eq!(smallint_to_i64(i64_to_smallint(value)), value);
796 }
797 }
798
799 #[test]
800 fn oop_helpers_identify_special_values() {
801 assert!(Oop::NIL.is_nil());
802 assert!(Oop::TRUE.is_boolean());
803 assert!(Oop::FALSE.is_boolean());
804 assert_eq!(Oop::TRUE.as_bool(), Some(true));
805 assert_eq!(Oop::FALSE.as_bool(), Some(false));
806 assert!(Oop(i64_to_smallint(7)).is_smallint());
807 assert_eq!(Oop(i64_to_smallint(7)).as_smallint(), Some(7));
808 }
809
810 #[test]
811 fn char_helpers_round_trip_unicode_values() {
812 for value in ['A', '\0', 'λ'] {
813 let oop = Oop::from_char(value);
814 assert!(oop.is_char());
815 assert_eq!(oop.as_char().unwrap(), Some(value));
816 assert_eq!(char_from_oop(char_to_oop(value)).unwrap(), value);
817 }
818 }
819}