New wcs2zstring to explicitly convert to zero-terminated strings

wcs2string converts a wide string to a narrow one.  The result is
null-terminated and may also contain interior null-characters.
std::string allows this.

Rust's null-terminated string, CString, does not like interior null-characters.
This means we will need to use Vec<u8> or OsString for the places where we
use interior null-characters.
On the other hand, we want to use CString for places that require a
null-terminator, because other Rust types don't guarantee the null-terminator.

Turns out there is basically no overlap between the two use cases, so make
it two functions. Their equivalents in Rust will have the same name, so
we'll only need to adjust the type when porting.
This commit is contained in:
Johannes Altmanninger 2023-04-01 13:50:30 +02:00
parent 3b15e995e7
commit 998cb7f1cd
16 changed files with 61 additions and 46 deletions

View File

@ -101,6 +101,7 @@ include_cpp! {
generate!("re::regex_result_ffi")
generate!("re::try_compile_ffi")
generate!("wcs2string")
generate!("wcs2zstring")
generate!("str2wcstring")
generate!("signal_handle")

View File

@ -7,7 +7,7 @@ use std::{
use cxx::let_cxx_string;
use crate::{
ffi::{str2wcstring, wcs2string},
ffi::{str2wcstring, wcs2zstring},
wchar::{wstr, WString},
wchar_ffi::{WCharFromFFI, WCharToFFI},
};
@ -19,7 +19,7 @@ pub fn wrealpath(pathname: &wstr) -> Option<WString> {
return None;
}
let mut narrow_path: Vec<u8> = wcs2string(&pathname.to_ffi()).from_ffi();
let mut narrow_path: Vec<u8> = wcs2zstring(&pathname.to_ffi()).from_ffi();
// Strip trailing slashes. This is treats "/a//" as equivalent to "/a" if /a is a non-directory.
while narrow_path.len() > 1 && narrow_path[narrow_path.len() - 1] == b'/' {

View File

@ -400,6 +400,15 @@ std::string wcs2string(const wchar_t *in, size_t len) {
return result;
}
std::string wcs2zstring(const wcstring &input) { return wcs2zstring(input.data(), input.size()); }
std::string wcs2zstring(const wchar_t *in, size_t len) {
if (len == 0) return std::string{};
std::string result;
wcs2string_appending(in, len, &result);
return result;
}
void wcs2string_appending(const wchar_t *in, size_t len, std::string *receiver) {
assert(receiver && "Null receiver");
receiver->reserve(receiver->size() + len);

View File

@ -306,6 +306,10 @@ wcstring str2wcstring(const char *in, size_t len);
std::string wcs2string(const wcstring &input);
std::string wcs2string(const wchar_t *in, size_t len);
/// Same as wcs2string. Meant to be used when we need a zero-terminated string to feed legacy APIs.
std::string wcs2zstring(const wcstring &input);
std::string wcs2zstring(const wchar_t *in, size_t len);
/// Like wcs2string, but appends to \p receiver instead of returning a new string.
void wcs2string_appending(const wchar_t *in, size_t len, std::string *receiver);

View File

@ -220,7 +220,7 @@ static void setup_user(env_stack_t &vars) {
// If we have a $USER, we try to get the passwd entry for the name.
// If that has the same UID that we use, we assume the data is correct.
if (!user_var.missing_or_empty()) {
std::string unam_narrow = wcs2string(user_var->as_string());
std::string unam_narrow = wcs2zstring(user_var->as_string());
int retval = getpwnam_r(unam_narrow.c_str(), &userinfo, buf, sizeof(buf), &result);
if (!retval && result) {
if (result->pw_uid == uid) {
@ -730,9 +730,9 @@ std::shared_ptr<owning_null_terminated_array_t> env_scoped_impl_t::create_export
std::vector<std::string> export_list;
export_list.reserve(vals.size());
for (const auto &kv : vals) {
std::string str = wcs2string(kv.first);
std::string str = wcs2zstring(kv.first);
str.push_back('=');
str.append(wcs2string(kv.second.as_string()));
str.append(wcs2zstring(kv.second.as_string()));
export_list.push_back(std::move(str));
}
return std::make_shared<owning_null_terminated_array_t>(std::move(export_list));

View File

@ -137,11 +137,11 @@ static void handle_timezone(const wchar_t *env_var_name, const environment_t &va
const auto var = vars.get(env_var_name, ENV_DEFAULT);
FLOGF(env_dispatch, L"handle_timezone() current timezone var: |%ls| => |%ls|", env_var_name,
!var ? L"MISSING" : var->as_string().c_str());
std::string name = wcs2string(env_var_name);
std::string name = wcs2zstring(env_var_name);
if (var.missing_or_empty()) {
unsetenv_lock(name.c_str());
} else {
const std::string value = wcs2string(var->as_string());
const std::string value = wcs2zstring(var->as_string());
setenv_lock(name.c_str(), value.c_str(), 1);
}
tzset();
@ -164,7 +164,7 @@ static void guess_emoji_width(const environment_t &vars) {
double version = 0;
if (auto version_var = vars.get(L"TERM_PROGRAM_VERSION")) {
std::string narrow_version = wcs2string(version_var->as_string());
std::string narrow_version = wcs2zstring(version_var->as_string());
version = strtod(narrow_version.c_str(), nullptr);
}
@ -465,7 +465,7 @@ static void initialize_curses_using_fallbacks(const environment_t &vars) {
}
int err_ret = 0;
std::string term = wcs2string(fallback);
std::string term = wcs2zstring(fallback);
bool success = (setupterm(&term[0], STDOUT_FILENO, &err_ret) == OK);
if (is_interactive_session()) {
@ -566,13 +566,13 @@ static bool does_term_support_setting_title(const environment_t &vars) {
/// Initialize the curses subsystem.
static void init_curses(const environment_t &vars) {
for (const auto &var_name : curses_variables) {
std::string name = wcs2string(var_name);
std::string name = wcs2zstring(var_name);
const auto var = vars.get(var_name, ENV_EXPORT);
if (var.missing_or_empty()) {
FLOGF(term_support, L"curses var %s missing or empty", name.c_str());
unsetenv_lock(name.c_str());
} else {
std::string value = wcs2string(var->as_string());
std::string value = wcs2zstring(var->as_string());
FLOGF(term_support, L"curses var %s='%s'", name.c_str(), value.c_str());
setenv_lock(name.c_str(), value.c_str(), 1);
}
@ -618,12 +618,12 @@ static void init_locale(const environment_t &vars) {
for (const auto &var_name : locale_variables) {
const auto var = vars.get(var_name, ENV_EXPORT);
std::string name = wcs2string(var_name);
std::string name = wcs2zstring(var_name);
if (var.missing_or_empty()) {
FLOGF(env_locale, L"locale var %s missing or empty", name.c_str());
unsetenv_lock(name.c_str());
} else {
const std::string value = wcs2string(var->as_string());
const std::string value = wcs2zstring(var->as_string());
FLOGF(env_locale, L"locale var %s='%s'", name.c_str(), value.c_str());
setenv_lock(name.c_str(), value.c_str(), 1);
}

View File

@ -369,7 +369,7 @@ void env_universal_t::load_from_fd(int fd, callback_data_list_t &callbacks) {
}
bool env_universal_t::load_from_path(const wcstring &path, callback_data_list_t &callbacks) {
return load_from_path(wcs2string(path), callbacks);
return load_from_path(wcs2zstring(path), callbacks);
}
bool env_universal_t::load_from_path(const std::string &path, callback_data_list_t &callbacks) {
@ -449,7 +449,7 @@ void env_universal_t::initialize_at_path(callback_data_list_t &callbacks, wcstri
if (path.empty()) return;
assert(!initialized() && "Already initialized");
vars_path_ = std::move(path);
narrow_vars_path_ = wcs2string(vars_path_);
narrow_vars_path_ = wcs2zstring(vars_path_);
if (load_from_path(narrow_vars_path_, callbacks)) {
// Successfully loaded from our normal path.
@ -475,7 +475,7 @@ autoclose_fd_t env_universal_t::open_temporary_file(const wcstring &directory, w
autoclose_fd_t result;
std::string narrow_str;
for (size_t attempt = 0; attempt < 10 && !result.valid(); attempt++) {
narrow_str = wcs2string(tmp_name_template);
narrow_str = wcs2zstring(tmp_name_template);
result.reset(fish_mkstemp_cloexec(&narrow_str[0]));
saved_errno = errno;
}
@ -1127,7 +1127,7 @@ static wcstring default_named_pipe_path() {
static autoclose_fd_t make_fifo(const wchar_t *test_path, const wchar_t *suffix) {
wcstring vars_path = test_path ? wcstring(test_path) : default_named_pipe_path();
vars_path.append(suffix);
const std::string narrow_path = wcs2string(vars_path);
const std::string narrow_path = wcs2zstring(vars_path);
int mkfifo_status = mkfifo(narrow_path.c_str(), 0600);
if (mkfifo_status == -1 && errno != EEXIST) {

View File

@ -7,6 +7,7 @@
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include "trace.rs.h"
#ifdef HAVE_SIGINFO_H
#include <siginfo.h>
@ -197,7 +198,7 @@ bool is_thompson_shell_script(const char *path) {
// Construct envp.
auto export_vars = vars.export_arr();
const char **envp = export_vars->get();
std::string actual_cmd = wcs2string(p->actual_cmd);
std::string actual_cmd = wcs2zstring(p->actual_cmd);
// Ensure the terminal modes are what they were before we changed them.
restore_term_mode();
@ -525,7 +526,7 @@ static launch_result_t exec_external_command(parser_t &parser, const std::shared
const char *const *argv = argv_array.get();
const char *const *envv = export_arr->get();
std::string actual_cmd_str = wcs2string(p->actual_cmd);
std::string actual_cmd_str = wcs2zstring(p->actual_cmd);
const char *actual_cmd = actual_cmd_str.c_str();
filename_ref_t file = parser.libdata().current_filename;

View File

@ -822,7 +822,7 @@ static void expand_home_directory(wcstring &input, const environment_t &vars) {
tail_idx = 1;
} else {
// Some other user's home directory.
std::string name_cstr = wcs2string(username);
std::string name_cstr = wcs2zstring(username);
struct passwd userinfo;
struct passwd *result;
char buf[8192];

View File

@ -235,7 +235,7 @@ int open_cloexec(const char *path, int flags, mode_t mode) {
}
int wopen_cloexec(const wcstring &pathname, int flags, mode_t mode) {
return open_cloexec(wcs2string(pathname), flags, mode);
return open_cloexec(wcs2zstring(pathname), flags, mode);
}
void exec_close(int fd) {

View File

@ -865,10 +865,10 @@ static std::string html_colorize(const wcstring &text,
}
}
html.append(L"</span></code></pre>");
return wcs2string(html);
return wcs2zstring(html);
}
static std::string no_colorize(const wcstring &text) { return wcs2string(text); }
static std::string no_colorize(const wcstring &text) { return wcs2zstring(text); }
int main(int argc, char *argv[]) {
program_name = L"fish_indent";

View File

@ -1521,7 +1521,7 @@ void test_dir_iter() {
char t1[] = "/tmp/fish_test_dir_iter.XXXXXX";
const std::string basepathn = mkdtemp(t1);
const wcstring basepath = str2wcstring(basepathn);
auto makepath = [&](const wcstring &s) { return wcs2string(basepath + L"/" + s); };
auto makepath = [&](const wcstring &s) { return wcs2zstring(basepath + L"/" + s); };
const wcstring dirname = L"dir";
const wcstring regname = L"reg";
@ -2995,7 +2995,7 @@ struct autoload_tester_t {
wcstring cmd = vformat_string(fmt, va);
va_end(va);
int status = system(wcs2string(cmd).c_str());
int status = system(wcs2zstring(cmd).c_str());
do_test(status == 0);
}
@ -3606,7 +3606,7 @@ static void test_autosuggest_suggest_special() {
perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/has_loop/", L"loopy/loop/", vars,
__LINE__);
if (!pushd(wcs2string(wd).c_str())) return;
if (!pushd(wcs2zstring(wd).c_str())) return;
perform_one_autosuggestion_cd_test(L"cd 0", L"foobar/", vars, __LINE__);
perform_one_autosuggestion_cd_test(L"cd \"0", L"foobar/", vars, __LINE__);
perform_one_autosuggestion_cd_test(L"cd '0", L"foobar/", vars, __LINE__);
@ -4022,7 +4022,7 @@ static void test_universal_ok_to_save() {
say(L"Testing universal Ok to save");
if (system("mkdir -p test/fish_uvars_test/")) err(L"mkdir failed");
constexpr const char contents[] = "# VERSION: 99999.99\n";
FILE *fp = fopen(wcs2string(UVARS_TEST_PATH).c_str(), "w");
FILE *fp = fopen(wcs2zstring(UVARS_TEST_PATH).c_str(), "w");
assert(fp && "Failed to open UVARS_TEST_PATH for writing");
fwrite(contents, const_strlen(contents), 1, fp);
fclose(fp);
@ -4509,7 +4509,7 @@ void history_tests_t::test_history_path_detection() {
// Place one valid file in the directory.
wcstring filename = L"testfile";
std::string path = wcs2string(tmpdir + filename);
std::string path = wcs2zstring(tmpdir + filename);
FILE *f = fopen(path.c_str(), "w");
if (!f) {
err(L"Failed to open test file from history path detection");

View File

@ -791,7 +791,7 @@ bool history_impl_t::rewrite_to_temporary_file(int existing_fd, int dst_fd) cons
// Returns the fd of an opened temporary file, or an invalid fd on failure.
static autoclose_fd_t create_temporary_file(const wcstring &name_template, wcstring *out_path) {
for (int attempt = 0; attempt < 10; attempt++) {
std::string narrow_str = wcs2string(name_template);
std::string narrow_str = wcs2zstring(name_template);
autoclose_fd_t out_fd{fish_mkstemp_cloexec(&narrow_str[0])};
if (out_fd.valid()) {
*out_path = str2wcstring(narrow_str);

View File

@ -4,7 +4,7 @@ std::vector<std::string> wide_string_list_to_narrow(const wcstring_list_t &strs)
std::vector<std::string> res;
res.reserve(strs.size());
for (const wcstring &s : strs) {
res.push_back(wcs2string(s));
res.push_back(wcs2zstring(s));
}
return res;
}

View File

@ -35,7 +35,7 @@ static get_path_result_t path_get_path_core(const wcstring &cmd, const wcstring_
/// Test if the given path can be executed.
/// \return 0 on success, an errno value on failure.
auto test_path = [](const wcstring &path) -> int {
std::string narrow = wcs2string(path);
std::string narrow = wcs2zstring(path);
struct stat buff;
if (access(narrow.c_str(), X_OK) != 0 || stat(narrow.c_str(), &buff) != 0) {
return errno;
@ -108,7 +108,7 @@ static bool path_is_executable(const std::string &path) {
/// \return whether the given path is on a remote filesystem.
static dir_remoteness_t path_remoteness(const wcstring &path) {
std::string narrow = wcs2string(path);
std::string narrow = wcs2zstring(path);
#if defined(__linux__)
struct statfs buf {};
if (statfs(narrow.c_str(), &buf) < 0) {
@ -149,7 +149,7 @@ wcstring_list_t path_get_paths(const wcstring &cmd, const environment_t &vars) {
// If the command has a slash, it must be an absolute or relative path and thus we don't bother
// looking for matching commands in the PATH var.
if (cmd.find(L'/') != wcstring::npos) {
std::string narrow = wcs2string(cmd);
std::string narrow = wcs2zstring(cmd);
if (path_is_executable(narrow)) paths.push_back(cmd);
return paths;
}
@ -161,7 +161,7 @@ wcstring_list_t path_get_paths(const wcstring &cmd, const environment_t &vars) {
for (auto path : pathsv) {
if (path.empty()) continue;
append_path_component(path, cmd);
std::string narrow = wcs2string(path);
std::string narrow = wcs2zstring(path);
if (path_is_executable(narrow)) paths.push_back(path);
}

View File

@ -56,7 +56,7 @@ wcstring wgetcwd() {
}
DIR *wopendir(const wcstring &name) {
const cstring tmp = wcs2string(name);
const cstring tmp = wcs2zstring(name);
return opendir(tmp.c_str());
}
@ -146,7 +146,7 @@ void dir_iter_t::entry_t::do_stat() const {
if (this->dirfd_ < 0) {
return;
}
std::string narrow = wcs2string(this->name);
std::string narrow = wcs2zstring(this->name);
struct stat s {};
if (fstatat(this->dirfd_, narrow.c_str(), &s, 0) == 0) {
this->stat_ = s;
@ -238,22 +238,22 @@ const dir_iter_t::entry_t *dir_iter_t::next() {
}
int wstat(const wcstring &file_name, struct stat *buf) {
const cstring tmp = wcs2string(file_name);
const cstring tmp = wcs2zstring(file_name);
return stat(tmp.c_str(), buf);
}
int lwstat(const wcstring &file_name, struct stat *buf) {
const cstring tmp = wcs2string(file_name);
const cstring tmp = wcs2zstring(file_name);
return lstat(tmp.c_str(), buf);
}
int waccess(const wcstring &file_name, int mode) {
const cstring tmp = wcs2string(file_name);
const cstring tmp = wcs2zstring(file_name);
return access(tmp.c_str(), mode);
}
int wunlink(const wcstring &file_name) {
const cstring tmp = wcs2string(file_name);
const cstring tmp = wcs2zstring(file_name);
return unlink(tmp.c_str());
}
@ -292,7 +292,7 @@ maybe_t<wcstring> wreadlink(const wcstring &file_name) {
}
ssize_t bufsize = buf.st_size + 1;
char target_buf[bufsize];
const std::string tmp = wcs2string(file_name);
const std::string tmp = wcs2zstring(file_name);
ssize_t nbytes = readlink(tmp.c_str(), target_buf, bufsize);
if (nbytes == -1) {
wperror(L"readlink");
@ -314,7 +314,7 @@ maybe_t<wcstring> wrealpath(const wcstring &pathname) {
if (pathname.empty()) return none();
cstring real_path;
cstring narrow_path = wcs2string(pathname);
cstring narrow_path = wcs2zstring(pathname);
// Strip trailing slashes. This is treats "/a//" as equivalent to "/a" if /a is a non-directory.
while (narrow_path.size() > 1 && narrow_path.at(narrow_path.size() - 1) == '/') {
@ -510,7 +510,7 @@ const wcstring &wgettext(const wchar_t *in) {
auto wmap = wgettext_map.acquire();
wcstring &val = (*wmap)[key];
if (val.empty()) {
cstring mbs_in = wcs2string(key);
cstring mbs_in = wcs2zstring(key);
char *out = fish_gettext(mbs_in.c_str());
val = format_string(L"%s", out);
}
@ -524,13 +524,13 @@ const wcstring &wgettext(const wchar_t *in) {
const wchar_t *wgettext_ptr(const wchar_t *in) { return wgettext(in).c_str(); }
int wmkdir(const wcstring &name, int mode) {
cstring name_narrow = wcs2string(name);
cstring name_narrow = wcs2zstring(name);
return mkdir(name_narrow.c_str(), mode);
}
int wrename(const wcstring &old, const wcstring &newv) {
cstring old_narrow = wcs2string(old);
cstring new_narrow = wcs2string(newv);
cstring old_narrow = wcs2zstring(old);
cstring new_narrow = wcs2zstring(newv);
return rename(old_narrow.c_str(), new_narrow.c_str());
}