@@ -36,6 +36,27 @@ fn ffi_guard<T>(name: &str, default: T, body: impl FnOnce() -> T) -> T {
3636 }
3737}
3838
39+ /// Validate a required C string FFI argument and convert it to `&str`.
40+ ///
41+ /// Logs an error and returns `None` if `ptr` is null or the bytes are not UTF-8.
42+ /// Callers translate `None` into the function's failure sentinel.
43+ ///
44+ /// # Safety
45+ /// If `ptr` is non-null, it must point to a valid, null-terminated C string.
46+ unsafe fn cstr_arg < ' a > ( fn_name : & str , arg_name : & str , ptr : * const c_char ) -> Option < & ' a str > {
47+ if ptr. is_null ( ) {
48+ log:: error!( "{}(): Bad argument: {} cannot be NULL" , fn_name, arg_name) ;
49+ return None ;
50+ }
51+ match unsafe { CStr :: from_ptr ( ptr) } . to_str ( ) {
52+ Ok ( s) => Some ( s) ,
53+ Err ( e) => {
54+ log:: error!( "{}(): Bad argument: {}: {}" , fn_name, arg_name, e) ;
55+ None
56+ }
57+ }
58+ }
59+
3960/// Install or replace the log callback.
4061///
4162/// The first call installs the global logger; subsequent calls atomically swap
@@ -72,18 +93,10 @@ pub unsafe extern "C" fn lch_log_init(
7293#[ unsafe( no_mangle) ]
7394pub unsafe extern "C" fn lch_init ( work_dir : * const c_char ) -> * mut config:: Config {
7495 ffi_guard ( "lch_init" , std:: ptr:: null_mut ( ) , || {
75- if work_dir. is_null ( ) {
76- log:: error!( "lch_init(): Bad argument: work directory cannot be NULL" ) ;
96+ let Some ( work_dir) = ( unsafe { cstr_arg ( "lch_init" , "work_dir" , work_dir) } ) else {
7797 return std:: ptr:: null_mut ( ) ;
78- }
79-
80- let path = match unsafe { CStr :: from_ptr ( work_dir) } . to_str ( ) {
81- Ok ( path) => PathBuf :: from ( path) ,
82- Err ( e) => {
83- log:: error!( "lch_init(): Bad argument: {e}" ) ;
84- return std:: ptr:: null_mut ( ) ;
85- }
8698 } ;
99+ let path = PathBuf :: from ( work_dir) ;
87100
88101 log:: debug!( "lch_init(work_dir={})" , path. display( ) ) ;
89102
@@ -298,43 +311,24 @@ pub unsafe extern "C" fn lch_patch_inject(
298311 return FAILURE ;
299312 }
300313
301- if name. is_null ( ) || value. is_null ( ) || sql_type. is_null ( ) {
302- log:: error!(
303- "lch_patch_inject(): Bad argument: name, value, and sql_type cannot be NULL"
304- ) ;
305- return FAILURE ;
306- }
307-
308314 if out_buf. is_null ( ) || out_len. is_null ( ) {
309315 log:: error!( "lch_patch_inject(): Bad argument: out_buf and out_len cannot be NULL" ) ;
310316 return FAILURE ;
311317 }
312318
313- let config = unsafe { & * config } ;
314- let data = unsafe { std:: slice:: from_raw_parts ( in_buf, in_len) } ;
315-
316- let name = match unsafe { CStr :: from_ptr ( name) } . to_str ( ) {
317- Ok ( s) => s,
318- Err ( e) => {
319- log:: error!( "lch_patch_inject(): Bad argument: name: {e}" ) ;
320- return FAILURE ;
321- }
319+ let Some ( name) = ( unsafe { cstr_arg ( "lch_patch_inject" , "name" , name) } ) else {
320+ return FAILURE ;
322321 } ;
323- let value = match unsafe { CStr :: from_ptr ( value) } . to_str ( ) {
324- Ok ( s) => s,
325- Err ( e) => {
326- log:: error!( "lch_patch_inject(): Bad argument: value: {e}" ) ;
327- return FAILURE ;
328- }
322+ let Some ( value) = ( unsafe { cstr_arg ( "lch_patch_inject" , "value" , value) } ) else {
323+ return FAILURE ;
329324 } ;
330- let sql_type = match unsafe { CStr :: from_ptr ( sql_type) } . to_str ( ) {
331- Ok ( s) => s,
332- Err ( e) => {
333- log:: error!( "lch_patch_inject(): Bad argument: sql_type: {e}" ) ;
334- return FAILURE ;
335- }
325+ let Some ( sql_type) = ( unsafe { cstr_arg ( "lch_patch_inject" , "sql_type" , sql_type) } ) else {
326+ return FAILURE ;
336327 } ;
337328
329+ let config = unsafe { & * config } ;
330+ let data = unsafe { std:: slice:: from_raw_parts ( in_buf, in_len) } ;
331+
338332 let mut patch = match wire:: decode_patch ( data) {
339333 Ok ( patch) => patch,
340334 Err ( e) => {
0 commit comments