How do I write a CString into *mut c_char pointer?

I'm exposing some rust code with a c interface. If any method return an error, the consumer can call get_last_error and pass in an empty char buffer which gets populated with an full error description. This seems like a better idea than returning a pointer and relying on the consumer to free it later. This way the caller allocates the memory.

I'm aware that I have to use a CString, but how should I copy it into err_string while ensuring that I don't copy in more than err_string_len characters?

#[no_mangle]
pub extern "C" fn get_last_error(err_string: *mut c_char, err_string_len: i32) {
  // need to copy a CString into err_string...
}
#[no_mangle]
pub extern "C" fn get_last_error(err_string: *mut c_char, err_string_len: i32) {
    let cs = CString::new("").unwrap();
    
    assert!(err_string_len >= 0);
    let len = std::cmp::min(cs.as_bytes_with_nul().len(), err_string_len as usize);
    unsafe {
        std::ptr::copy_nonoverlapping(cs.as_ptr(), err_string, len);
    }
}
1 Like

By the way, in your provided code example, if a string is longer than err_string_len, this won't provide a NUL terminator, which can cause subtle bugs. This may or may not be what the thread author wanted, however.

From my experience with C language, strncpy and strlcpy (you are essentially doing strncpy here, but without zero-fill) are pretty much always wrong, using those identifiers in a program pretty much always indicates bugs.

If you can, I would prefer to allocate a buffer (with malloc or provide a function to free that buffer), and allow the caller to deallocate it. Use naming conventions to point out that the function allocates (yes, I'm suggesting Hungarian notation, but honestly C is so primitive that you may as well need it). The design with err_string_len is very error-prone. With malloc you will have at worst a memory leak, which can be found with tools like Valgrind.

3 Likes

Thanks, noted!

As I'm writing the language specific wrappers for this API I think you're suggestion is a good idea. I see that this is the recommendation here too.

@mbrubeck I think you meant to use cmp::min :wink:

In this case, given that it is an error message, it could be acceptable to truncate te string with a err_string.add(err_string_len - 1).write(b'\0') at the end of the function (and an assertion that the len is > 0).

That being said, out pointers are only useful if they can provide a way to return a dynamically sized element while avoiding an allocation. So indeed, if the chars are being copied from a CString, you'd better be returning a(n owning) pointer to the CString chars.

And of course, if you have the "return value" slot already used by what your function returns when it does not fail, you can still use an out-pointer: *mut *mut c_char

use ::std::{ffi::CString, ops::Not};

#[no_mangle] pub unsafe extern "C"
fn swap_bytes (
    x: *mut u8,
    y: *mut u8,
    error: *mut *mut c_char,
) -> c_int // status
{
    if x.is_null() || y.is_null() {
        if error.is_null().not() {
            let err_msg = CString::new("Got NULL pointer").unwrap();
            error.write(err_msg.into_raw());
        }
        -1
    } else {
        ::core::ptr::swap(x, y);
        0
    }
}

#[no_mangle] pub unsafe extern "C"
fn free_error_string (error: *mut c_char)
{
    if error.is_null().not() {
        drop(CString::from_raw(error));
    }
}
1 Like

Yes, thanks! Fixed.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.