Externing C function with memcpy

Hi,
I have a very basic question. I have C library which has one void function and copying a string to input char *. The function is as follows;
libreadName.c

void readName(char* input){
	memcpy(input, "Temp String", 64);
}

I call this function in another C program as follows;
main.c

int main(){
	char myArr[64];
	readName(myArr);
	printf("%s \n", myArr);

	return 0;
}

I try to call function readName from Rust program. I tried something following

extern "C" {fn readName(input: *mut c_char); 
fn main() {
    let mut c_string = CString::new(" ").expect("CString::new failed");
	let raw = c_string.into_raw();
		
	unsafe {
	    readName(raw);
	    c_string = CString::from_raw(raw);
	    
	}
	println!("{:?}", c_string);
}

When I try to run this I have the following output.

"Temp String"
*** Error in `target/debug/rust_string_c': free(): invalid next size (normal): 0x000055f1f719f130 ***
======= Backtrace: =========
/lib64/libc.so.6(+0x81299)[0x7fe15027f299]
target/debug/rust_string_c(+0x1e50b)[0x55f1f66b250b]
target/debug/rust_string_c(_ZN3std4sync4once4Once10call_inner17h222a34e71c71c7d0E+0x26a)[0x55f1f669d01a]
target/debug/rust_string_c(_ZN3std2rt19lang_start_internal17h52e73755f77c7dd9E+0x460)[0x55f1f66b2c90]
target/debug/rust_string_c(+0xa930)[0x55f1f669e930]
target/debug/rust_string_c(+0xb65c)[0x55f1f669f65c]
/lib64/libc.so.6(__libc_start_main+0xf5)[0x7fe150220555]
target/debug/rust_string_c(+0xa440)[0x55f1f669e440]
======= Memory map: ========


What am I missing? Can you guys help me out.

2 things:

  • readName copies 64 bytes from an 11-character long string (12 with \0)
  • The allocated CString is only 1 byte long (a single space character). You can do CString:new(vec![' '; 11]) to make sure it allocates enough space for "Temp String" (CString::new adds the null character for you)
2 Likes

I think this also not work. I change the c_string = CStrıng::new("Temp String") it still gives the same core dumped output. By the way reaching memcpy method of C from Rust is something manageable right ?

Did you change the number of bytes memcpy copies?

Calling C code that calls memcpy should be fine, yes.

1 Like

When I change the number of bytes in memcpy from 64 to 11 it works. However, what I want to make is copy any string up to 64 bytes wıth calling memcpy of C from Rust. Just like calling readName from another C program (main.c).

Whatever string you're using, you need to make sure to only copy as many bytes as there are in it (i.e. 11 for "Temp String"), and no more. There may be some function that copies everything up until the first null byte, but I am not aware of it.

2 Likes

strncpy (or friends). Heed the warnings and bugs.

3 Likes

So I made some experiments on it. First changing, readName() method as follows;

void readName(char* input){
	char src[64] = "Temp String";
	memcpy(input, src, 64);
}

This gives the same error message. Then I update the function as follows;

void readName(char* input){
	char src[64] = "Temp String";
	//memcpy(input, src, 64);
	strncpy(input, src, 64);
}

Again same error. Finally, I have tried something like this;

void readName(char* input){
	char src[64] = "Temp String";
	strcpy(input, src);
}

And this is working without errors.So, I am assuming rust compiler is checking whether the full memory is used or not right ? Or is there anything I am missing ? By the way, is my rust code right ? I am new to Rust programming so maybe the problem was my rust program.

What's your Rust code look like at this point?

It is never changed. I am still using the same Rust code.

If you want to copy up to 64 bytes to c_string you must allocate enough to be able to do so. CStrıng::new("Temp String") is not going to do that for the same reason CString::new(" ") did not: Temp String is not 64 bytes long. Almost (C does not allocate, creating CString does) direct translation of C code into rust will be

use std::mem::MaybeUninit;
use std::os::raw::c_char;
use std::ffi::CStr;
extern "C" {fn readName(input: *mut MaybeUninit<c_char>); }
fn main() {
    let mut my_arr: [MaybeUninit<c_char>; 64] = [MaybeUninit::uninit(); 64];
    unsafe { readName(my_arr.as_mut_ptr()); }
    let c_string = unsafe { CStr::from_ptr(my_arr.as_ptr().cast()).to_owned() };
    println!("{:?} ", c_string);
}

(Note change in readName signature: it does not care whether input is initialized, so I assumed it fine to accept pointer to MaybeUninit<c_char>; since MaybeUninit is transparent this change is fine.) Here readName is assumed to write NUL-terminated string into some part of my_arr, making that specific part initialized and CStr::from_ptr is assumed to not read past NUL.

1 Like
    let mut c_string = CString::new(" ").expect("CString::new failed");
	let raw = c_string.into_raw();
		
	unsafe {
	    readName(raw);
	    c_string = CString::from_raw(raw);
	}

Coming to think about that, is not this code unsound if readName does not write string with exactly the same amount of bytes that were passed to CString::new and so the issue cannot be fixed by passing "1111{63 1s}1" to it? dealloc documentation states that you must supply it with the same layout as you did when allocating and if string length changes it will no longer be the case for layout.size.

Actually, it really is the case: I see there is explicit mention of this in from_raw documentation:

It should be noted that the length isn’t just “recomputed,” but that the recomputed length must match the original length from the CString::into_raw call. This means the CString::into_raw/from_raw methods should not be used when passing the string to C functions that can modify the string’s length.

1 Like

What actually I am looking is the way you suggested. This works just fine for my case. About your question, I saw that the CString should be used in order to pass a string in to C functions where CStr should be used in order to hold the Strings that come from C function so maybe it was the case. However, your approach seems reasonable in my opinion. So, thank you for your time.