Преобразование вывода libc::getcwd в строку

Я хотел бы распечатать результат libc::getcwd. Моя проблема в том, что для создания getcwd требуется буфер i8 (c_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> и привел его к буферу c_char в вызове getcwd:

    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() верно), чтение последней ошибки через внешний крейт errno (почему это отдельный крейт для libc?) показывает, что getcwd терпит неудачу с "Недопустимым аргументом". Источник проблемы кажется, что buf.len() возвращает 0.


person hansaplast    schedule 05.07.2016    source источник
comment
Вы можете сделать buf Vec<u8>, а затем вызвать libc::getcwd(buf.as_mut_ptr() as *mut i8, buf.len()). Или что-то вроде того.   -  person pyon    schedule 05.07.2016
comment
Одно примечание: нет гарантии, что результат getcwd будет закодирован в UTF-8, вы можете заглянуть в OsString, который специально предназначен для их содержания.   -  person Matthieu M.    schedule 05.07.2016
comment
@pyon: с таким же успехом можно использовать as *mut c_char на случай, если c_char когда-либо изменится.   -  person Matthieu M.    schedule 05.07.2016
comment
@MatthieuM. Да, ты прав. Я только что посмотрел на фрагмент кода и предположил, что i8 будет правильным типом при любых обстоятельствах, а вы справедливо указываете, что это не обязательно.   -  person pyon    schedule 05.07.2016
comment
@MatthieuM. OsString не имеет нет неотъемлемого отношения к строкам C. Например, в Windows OsString — это WTF-8, в то время как строки C гарантировано находятся в какой-то другой кодировке (поскольку Win32 не знает, что такое WTF-8) .   -  person DK.    schedule 05.07.2016
comment
@DK.: О, мило. Почему я должен предполагать здравомыслие...   -  person Matthieu M.    schedule 05.07.2016
comment
благодаря вашим комментариям я теперь смог скомпилировать, но столкнулся со следующей проблемой: результирующая строка теперь пуста..   -  person hansaplast    schedule 05.07.2016


Ответы (1)


В большинстве случаев вам следует просто использовать 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

Да, длина равна нулю, потому что вектору никто не сказал, что данные добавлены. Векторы состоят из трех частей: указателя на данные, длины и емкости.

Емкость — это объем доступной памяти, размер — сколько используется. При обработке вектора как большого двоичного объекта для хранения данных вы хотите использовать емкость. Затем вам нужно сообщить вектору, сколько из этих байтов было использовано, чтобы String::from_utf8 знал, где конец.

Вы заметите, что я изменил область unsafe, чтобы включить только действительно небезопасные аспекты и код, который делает этот код действительно безопасным.


Фактически, вы можете просто скопировать реализация env::current_dir для Unix-подобных систем. Он гораздо лучше обрабатывает случаи сбоев и использует правильные типы (пути не являются строками). Конечно, еще проще просто позвонить env::current_dir. ^_^


к вашему сведению: я закончил с этим

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.

person Shepmaster    schedule 05.07.2016
comment
большое спасибо, что вы нашли время, чтобы написать этот обширный ответ. Я новичок как в ржавчине, так и в программировании ОС, так что это дает мне несколько советов, которые будут полезны в будущем. Да, я мог бы использовать env::current_dir, но я стараюсь максимально приблизиться к программированию для Unix. - person hansaplast; 05.07.2016
comment
@PhilippKeller Это это программирование для Unix — см. реализация env::current_dir и его внутренняя реализация. Не думайте, что из-за того, что у Rust приятный интерфейс, он делает что-то бессмысленное. - person Shepmaster; 05.07.2016
comment
Я читаю Advanced Programming in the Unix Environment, которая представляет собой справочник по unix-программированию на C со всеми определениями функций и конструкциями памяти. Вот почему я хочу пока остаться со строками с завершающим нулем и всеми этими препятствиями. Спасибо за подсказку, что реальная реализация в std::env::current_dir - person hansaplast; 05.07.2016
comment
к вашему сведению: я закончил с этим: функция from_ptr, которая преобразует строку с завершением NUL в массив. Кроме того, у io есть io::Error::last_os_error, поэтому внешний крейт errno не нужен. - person hansaplast; 06.07.2016
comment
@PhilippKeller смотрите мое обновление, в котором обсуждается, почему это плохая идея. - person Shepmaster; 06.07.2016
comment
о дорогой, конечно! Интересно, почему это на самом деле сработало. Спасибо за исправленный код и комментарии. Я задался вопросом: почему возможно иметь две изменяемые ссылки на вектор? Сначала как mut buf, а затем как ptr = buf.as_mut_ptr(). Собственность не переехала, не так ли? Иначе почему можно звонить buf.capacity()? - person hansaplast; 06.07.2016