Wrong mapping of C struct to Rust

前端 未结 1 616
借酒劲吻你
借酒劲吻你 2021-01-20 04:10

For educational purpose I try to access the FILE struct in Rust:

unsafe {
    let passwd = libc::fopen(\"/etc/passwd\".to_ptr(), &(\'r\' as          


        
相关标签:
1条回答
  • 2021-01-20 05:06

    First, your implementation of ToPtr invites unsound code. Reproduced here:

    // code in italics is wrong
    impl ToPtr for str {
        fn to_ptr(&self) -> *const i8 {
            CString::new(self).unwrap().as_ptr()
        }
    }
    

    This allocates a new CString and returns a pointer to its contents, but the CString is dropped when to_ptr returns, so this is a dangling pointer. Any dereference of this pointer is undefined behavior. The documentation has a big warning about this, but it's still a very common mistake.

    One correct way to make a *const c_char from a string literal is b"string here\0".as_ptr() as *const c_char. The string is null terminated and there is no dangling pointer because string literals live for the entire program. If you have a non-constant string to be converted, you must keep the CString alive while it is being used, like this:

    let s = "foo";
    let cs = CString::new(s).unwrap(); // don't call .as_ptr(), so the CString stays alive
    unsafe { some_c_function(cs.as_ptr()); }
    // CString is dropped here, after we're done with it
    

    Aside: an editor "suggested" (I'm new to Stack Overflow, but it seems more polite to comment rather than try to rewrite my answer) that the above code could be written like this:

    let s = "foo";
    unsafe {
        // due to temporary drop rules, the CString will be dropped at the end of the statement (the `;`)
        some_c_function(CString::new(s).unwrap().as_ptr());
    }
    

    While this is technically correct (the best kind of correct), the "temporary drop rules" involved are subtle -- this works because as_ptr takes a reference to the CString (actually a &CStr, because the compiler changes the method chain to CString::new(s).unwrap().deref().as_ptr()) instead of consuming it, and because we have only one C function to call. I don't like to rely on anything that's subtle or non-obvious when writing unsafe code.


    With that out of the way, I fixed that unsoundness in your code (your calls all use string literals so I just used my first strategy above). I get this output on OSX:

    0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0
    0, 0, 8, 1, 0, 0, 0, 0, 0, 0, 0
    0, 0, 8, 2, 0, 0, 0, 0, 0, 0, 0
    0, 0, 4, 3, 0, 0, 0, 0, 0, 0, 0
    

    So, this matches your results, right? I also wrote the following C program:

    #include <stdio.h>
    #include <unistd.h>
    
    int main() {
        struct __sFILE *fp1 = fdopen(STDIN_FILENO, "r");
        struct __sFILE *fp2 = fdopen(STDOUT_FILENO, "w");
        struct __sFILE *fp3 = fdopen(STDERR_FILENO, "w");
        struct __sFILE *passwd = fopen("/etc/passwd", "r");
    
        printf("%i %i %i %i\n", fp1->_flags, fp2->_flags, fp3->_flags, passwd->_flags);
    }
    

    And got the output:

    4 8 8 4
    

    Which seems to vindicate the Rust results. There is a comment at the top of /usr/include/stdio.h that says:

    /*
     * The following always hold:
     *
     *  if (_flags&(__SLBF|__SWR)) == (__SLBF|__SWR),
     *      _lbfsize is -_bf._size, else _lbfsize is 0
     *  if _flags&__SRD, _w is 0
     *  if _flags&__SWR, _r is 0
     */
    

    And looking up those constants:

    #define __SLBF  0x0001      /* line buffered */
    #define __SRD   0x0004      /* OK to read */
    #define __SWR   0x0008      /* OK to write */
    

    This seems to match the output we get: 4 for file opened in read mode, 8 for write. So what is the problem here?

    0 讨论(0)
提交回复
热议问题