将libc::getcwd的输出转换为字符串

3

我想打印出libc :: getcwd 的结果。 我的问题是,创建getcwd需要一个i8c_char)缓冲区,而String :: from_utf8 需要一个u8缓冲区。我从以下内容开始:

static BUF_BYTES: usize = 4096;

fn main() {
    unsafe {
        let mut buf: Vec<i8> = Vec::with_capacity(BUF_BYTES as usize);
        libc::getcwd(buf.as_mut_ptr(), buf.len());
        let s = String::from_utf8(buf).expect("Found invalid UTF-8");
        println!("result: {}", s);
    }
}

这会导致错误:

14:32 error: mismatched types:
 expected `std::vec::Vec<u8>`,
    found `std::vec::Vec<i8>` [E0308]

感谢评论,我将buf更改为Vec<u8>,并在getcwd调用中将其转换为c_char缓冲区:

    let mut buf: Vec<u8> = Vec::with_capacity(BUF_BYTES as usize);
    libc::getcwd(buf.as_mut_ptr() as *mut c_char, buf.len());

这段代码编译通过了,但是打印字符串时为空(长度为0)。

我发现getcwd返回了NULL(libc :: getcwd(...).is_null()为true),通过外部的crate errno读取最后一个错误(为什么要将其作为一个独立的crate与libc分开呢?)显示getcwd失败,提示“无效参数”。问题的根源似乎在于buf.len()返回0。


1
你可以将buf定义为Vec<u8>,然后调用libc::getcwd(buf.as_mut_ptr() as *mut i8, buf.len())。或者类似的操作。 - isekaijin
3
注意:不能保证getcwd的结果编码为UTF-8,你可能需要了解一下OsString,它专门用于包含这些内容。 - Matthieu M.
3
如果c_char有所更改,最好使用as *mut c_char - Matthieu M.
@MatthieuM。没错,你说得对。我刚看了一下代码片段,并假设i8在所有情况下都是正确的类型,但你指出它不必如此。 - isekaijin
1
@MatthieuM。OsString与C字符串没有固有的关系。例如,在Windows上,OsString是WTF-8,而C字符串保证以某种其他编码存在(因为Win32不知道WTF-8是什么)。 - DK.
显示剩余2条评论
1个回答

3
在大多数情况下,你应该只使用env::current_dir。这会为你处理平台特定的所有问题,例如评论中提到的“其他”编码方式。

C字符串有点糟糕。 getcwd 填充了一个长度为某个值的缓冲区,但并不告诉您它在哪里结束;您必须手动查找终止的 NUL 字节。

extern crate libc;

static BUF_BYTES: usize = 4096;

fn main() {
    let buf = unsafe {
        let mut buf = Vec::with_capacity(BUF_BYTES);
        let res = libc::getcwd(buf.as_mut_ptr() as *mut i8, buf.capacity());
        if res.is_null() {
            panic!("Not long enough");
        }
        let mut len = 0;
        while *buf.as_mut_ptr().offset(len as isize) != 0 { len += 1 }
        buf.set_len(len);
        buf
    };

    let s = String::from_utf8(buf).expect("Found invalid UTF-8");
    println!("result: {}", s);
}

似乎buf.len()返回0。
是的,长度为0,因为没有人告诉向量添加了数据。向量由三个部分组成-指向数据的指针、长度和容量。
容量是可用内存大小,尺寸是使用了多少。当将向量视为存储数据的 blob 时,应使用容量。然后您需要告知向量使用了多少字节,以便 String::from_utf8 知道其结尾在哪里。
请注意,我将 unsafe 的范围更改为仅包括真正不安全的方面和使该代码实际上安全的代码。
实际上,你可以直接复制 Unix-like 系统下 env::current_dir 的实现。它更好地处理了失败情况并使用了正确的类型(路径不是字符串)。当然,更容易的方法是直接调用env::current_dir。^_^

fyi: I ended up with this

extern crate libc;

use std::ffi::CStr;
use std::io;
use std::str;

static BUF_BYTES: usize = 4096;

fn main() {
  let buf = unsafe {
      let mut buf = Vec::with_capacity(BUF_BYTES);
      let ptr = buf.as_mut_ptr() as *mut libc::c_char;
      if libc::getcwd(ptr, buf.capacity()).is_null() {
          panic!(io::Error::last_os_error());
      }
      CStr::from_ptr(ptr).to_bytes()
  };
  println!("result: {}", str::from_utf8(buf).unwrap());
}
这是不安全的,可能会导致崩溃(在最好的情况下)或者静默内存损坏或更糟。
当块结束时,其中的任何变量都将被丢弃。在本例中,unsafe 块创建 buf,获取指向它的指针,使用指针创建 CStr,然后释放 Vec,使指针无效。然后,它返回包含来自块的无效引用的 CStr。
像这样的东西更好:
extern crate libc;

use std::ffi::{CStr, CString};
use std::io;
use std::str;

static BUF_BYTES: usize = 4096;

fn main() {
    let buf = unsafe {
        // Allocate some space to store the result
        let mut buf = Vec::with_capacity(BUF_BYTES);

        // Call the function, panicking if it fails
        let ptr = buf.as_mut_ptr() as *mut libc::c_char;
        if libc::getcwd(ptr, buf.capacity()).is_null() {
            panic!(io::Error::last_os_error());
        }

        // Find the first NUL and inform the vector of that
        let s = CStr::from_ptr(ptr);
        buf.set_len(s.to_bytes().len());

        // Transfer ownership of the Vec to a CString, ensuring there are no interior NULs
        CString::new(buf)
    };

    let s = buf.expect("Not a C string").into_string().expect("Not UTF-8");
    println!("result: {}", s);
}

我想知道为什么这实际上起作用了

很可能是因为在尝试访问内存之前没有更改该内存。在高度多线程的环境中,可能会出现更多问题。

为什么向量可以有两个可变引用?首先是mut buf,然后是ptr = buf.as_mut_ptr()。所有权没有移动,对吧?否则,为什么还能调用buf.capacity()

你实际上并没有两个引用buf拥有该值,然后你得到一个可变指针。指针没有编译器保护,这也是需要unsafe块的原因之一。


非常感谢您抽出时间撰写这篇详尽的回答。我对Rust和操作系统编程都很陌生,所以这些指引对我未来的学习将非常有价值。是的,我可以使用env::current_dir,但我正在尝试尽可能接近Unix编程的方式。 - hansaplast
1
@PhilippKeller 这确实是“Unix编程” -> 请参见env :: current_dir的实现及其内部实现。不要认为因为Rust有一个很好的接口就意味着它在做一些无意义的事情。 - Shepmaster
我正在阅读《Unix环境高级编程》,这是一本关于“C语言下的Unix编程”的指导参考书,其中包含了所有函数定义和内存结构。这就是为什么我现在想要使用空终止字符串和所有这些“障碍”的原因。感谢您提供的提示,实际实现在std::env::current_dir中。 - hansaplast
FYI:我最终得到了这个:https://gist.github.com/philippkeller/6a9d4787c8962d9dd0f975432cd1e0bc - CStr 有一个 from_ptr 函数,可以将以 NUL 结尾的字符串转换为数组。此外,io 有 io::Error::last_os_error,因此不需要使用外部的 errno crate。 - hansaplast
1
@PhilippKeller 看一下我的更新,讨论为什么那是一个坏主意。 - Shepmaster
哦,亲爱的,当然可以!我想知道为什么这个代码实际上起作用了。感谢您提供更正后的代码和注释。我想知道的是:为什么可以有两个可变引用指向向量?首先是 mut buf,然后是 ptr = buf.as_mut_ptr()。所有权没有移动,对吧?否则,为什么可以调用 buf.capacity() - hansaplast

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接