From 6faa2f986695ae3731a91fec10292e37ebe59102 Mon Sep 17 00:00:00 2001 From: Aaron Gyes Date: Sat, 10 Sep 2016 14:38:28 -0700 Subject: [PATCH] Tighten up empty string checks. Fixes various spots throughout fish where broken strtoi checks were converting empty strings to zero. Zero is not a valid pid and this was causing breakage as well when input. Nix fish_wcstoi - wcstoimax does the same thing. Improve comments and some general cleanup. --- src/builtin.cpp | 123 ++++++++--------- src/builtin_jobs.cpp | 12 +- src/expand.cpp | 10 +- src/highlight.cpp | 8 +- src/parse_execution.cpp | 6 +- src/signal.cpp | 29 ++-- src/wutil.cpp | 288 +++++++++++++++++++++------------------- src/wutil.h | 35 +---- 8 files changed, 237 insertions(+), 274 deletions(-) diff --git a/src/builtin.cpp b/src/builtin.cpp index 56bdaa890..da7b9b82e 100644 --- a/src/builtin.cpp +++ b/src/builtin.cpp @@ -20,7 +20,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -88,7 +90,8 @@ bool builtin_data_t::operator<(const builtin_data_t *other) const { /// int builtin_count_args(const wchar_t *const *argv) { int argc; - for (argc = 1; argv[argc] != NULL; argc++); + for (argc = 1; argv[argc] != NULL; argc++) + ; assert(argv[argc] == NULL); return argc; @@ -396,7 +399,7 @@ static int builtin_bind_add(const wchar_t *seq, const wchar_t *const *cmds, size /// if specified, _all_ key bindings will be erased /// @param mode /// if specified, only bindings from that mode will be erased. If not given -/// and @c all is @c false, @c DEFAULT_BIND_MODE will be used. +/// and all is false, @c DEFAULT_BIND_MODE will be used. /// @param use_terminfo /// Whether to look use terminfo -k name /// @@ -410,23 +413,19 @@ static int builtin_bind_erase(wchar_t **seq, int all, const wchar_t *mode, int u input_mapping_erase(it->seq, it->mode); } } - - return 0; + return STATUS_BUILTIN_OK; } - int res = 0; - if (mode == NULL) mode = DEFAULT_BIND_MODE; - while (*seq) { if (use_terminfo) { wcstring seq2; if (get_terminfo_sequence(*seq++, &seq2, streams)) { - input_mapping_erase(seq2, mode); + input_mapping_erase(seq2, mode != NULL ? mode : DEFAULT_BIND_MODE); } else { - res = 1; + res = STATUS_BUILTIN_ERROR; } } else { - input_mapping_erase(*seq++, mode); + input_mapping_erase(*seq++, mode != NULL ? mode : DEFAULT_BIND_MODE); } } @@ -440,12 +439,12 @@ static int builtin_bind(parser_t &parser, io_streams_t &streams, wchar_t **argv) int argc = builtin_count_args(argv); int mode = BIND_INSERT; int res = STATUS_BUILTIN_OK; - int all = 0; + bool all = false; const wchar_t *bind_mode = DEFAULT_BIND_MODE; bool bind_mode_given = false; const wchar_t *sets_bind_mode = DEFAULT_BIND_MODE; bool sets_bind_mode_given = false; - int use_terminfo = 0; + bool use_terminfo = false; w.woptind = 0; @@ -475,7 +474,7 @@ static int builtin_bind(parser_t &parser, io_streams_t &streams, wchar_t **argv) return STATUS_BUILTIN_ERROR; } case 'a': { - all = 1; + all = true; break; } case 'e': { @@ -487,7 +486,7 @@ static int builtin_bind(parser_t &parser, io_streams_t &streams, wchar_t **argv) return STATUS_BUILTIN_OK; } case 'k': { - use_terminfo = 1; + use_terminfo = true; break; } case 'K': { @@ -942,7 +941,7 @@ static wcstring functions_def(const wcstring &name) { case EVENT_EXIT: { if (next->param1.pid > 0) append_format(out, L" --on-process-exit %d", next->param1.pid); - else + else if (next->param1.pid < 0) append_format(out, L" --on-job-exit %d", -next->param1.pid); break; } @@ -1262,7 +1261,7 @@ static bool builtin_echo_parse_numeric_sequence(const wchar_t *str, size_t *cons bool success = false; unsigned int start = 0; // the first character of the numeric part of the sequence - unsigned int base = 0, max_digits = 0; + unsigned short base = 0, max_digits = 0; if (builtin_echo_digit(str[0], 8) != UINT_MAX) { // Octal escape base = 8; @@ -1281,7 +1280,7 @@ static bool builtin_echo_parse_numeric_sequence(const wchar_t *str, size_t *cons if (base != 0) { unsigned int idx; - unsigned char val = 0; // resulting character + unsigned char val = '\0'; // resulting character for (idx = start; idx < start + max_digits; idx++) { unsigned int digit = builtin_echo_digit(str[idx], base); if (digit == UINT_MAX) break; @@ -1572,7 +1571,6 @@ int builtin_function(parser_t &parser, io_streams_t &streams, const wcstring_lis } case 'j': case 'p': { - pid_t pid; wchar_t *end; event_t e(EVENT_ANY); @@ -1599,18 +1597,17 @@ int builtin_function(parser_t &parser, io_streams_t &streams, const wcstring_lis append_format(*out_err, _(L"%ls: Cannot find calling job for event handler"), argv[0]); - res = 1; + res = STATUS_BUILTIN_ERROR; } else { e.type = EVENT_JOB_ID; e.param1.job_id = job_id; } } else { - errno = 0; - pid = fish_wcstoi(w.woptarg, &end, 10); - if (errno || !end || *end) { - append_format(*out_err, _(L"%ls: Invalid process id %ls"), argv[0], + pid_t pid = wcstoimax(w.woptarg, &end, 10); + if (pid < 1 || !(*w.woptarg != L'\0' && *end == L'\0')) { + append_format(*out_err, _(L"%ls: Invalid process id '%ls'"), argv[0], w.woptarg); - res = 1; + res = STATUS_BUILTIN_ERROR; break; } @@ -1662,7 +1659,7 @@ int builtin_function(parser_t &parser, io_streams_t &streams, const wcstring_lis } case '?': { builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); - res = 1; + res = STATUS_BUILTIN_ERROR; break; } } @@ -1777,7 +1774,7 @@ int builtin_function(parser_t &parser, io_streams_t &streams, const wcstring_lis /// The random builtin generates random numbers. static int builtin_random(parser_t &parser, io_streams_t &streams, wchar_t **argv) { - static int seeded = 0; + static bool seeded = false; static struct drand48_data seed_buffer; int argc = builtin_count_args(argv); @@ -1815,7 +1812,7 @@ static int builtin_random(parser_t &parser, io_streams_t &streams, wchar_t **arg case 0: { long res; if (!seeded) { - seeded = 1; + seeded = true; srand48_r(time(0), &seed_buffer); } lrand48_r(&seed_buffer, &res); @@ -1823,19 +1820,16 @@ static int builtin_random(parser_t &parser, io_streams_t &streams, wchar_t **arg break; } case 1: { - long foo; wchar_t *end = 0; - - errno = 0; - foo = wcstol(argv[w.woptind], &end, 10); - if (errno || *end) { + long seedval = wcstol(argv[w.woptind], &end, 10); + if (!(*argv[w.woptind] != L'\0' && *end == L'\0')) { streams.err.append_format(_(L"%ls: Seed value '%ls' is not a valid number\n"), argv[0], argv[w.woptind]); return STATUS_BUILTIN_ERROR; } - seeded = 1; - srand48_r(foo, &seed_buffer); + seeded = true; + srand48_r(seedval, &seed_buffer); break; } default: { @@ -1861,8 +1855,8 @@ static int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv) const wchar_t *mode_name = READ_MODE_NAME; int nchars = 0; wchar_t *end; - int shell = 0; - int array = 0; + bool shell = false; + bool array = false; bool split_null = false; while (1) { @@ -1932,9 +1926,9 @@ static int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv) break; } case L'n': { - errno = 0; - nchars = fish_wcstoi(w.woptarg, &end, 10); - if (errno || *end != 0) { + nchars = wcstoimax(w.woptarg, &end, 10); + if (!(*w.woptarg != L'\0' && *end == L'\0')) { + // MUST be an error switch (errno) { case ERANGE: { streams.err.append_format(_(L"%ls: Argument '%ls' is out of range\n"), @@ -1953,11 +1947,11 @@ static int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv) break; } case 's': { - shell = 1; + shell = true; break; } case 'a': { - array = 1; + array = true; break; } case L'z': { @@ -2353,9 +2347,8 @@ static int builtin_exit(parser_t &parser, io_streams_t &streams, wchar_t **argv) } case 2: { wchar_t *end; - errno = 0; ec = wcstol(argv[1], &end, 10); - if (errno || *end != 0) { + if (!(*argv[1] != L'\0' && *end == L'\0')) { streams.err.append_format(_(L"%ls: Argument '%ls' must be an integer\n"), argv[0], argv[1]); builtin_print_help(parser, streams, argv[0], streams.err); @@ -2420,9 +2413,8 @@ static int builtin_cd(parser_t &parser, io_streams_t &streams, wchar_t **argv) { res = 1; } else if (wchdir(dir) != 0) { struct stat buffer; - int status; + int status = wstat(dir, &buffer); - status = wstat(dir, &buffer); if (!status && S_ISDIR(buffer.st_mode)) { streams.err.append_format(_(L"%ls: Permission denied: '%ls'\n"), argv[0], dir.c_str()); @@ -2603,14 +2595,12 @@ static int builtin_fg(parser_t &parser, io_streams_t &streams, wchar_t **argv) { // try to locate the job argv[1], since we want to know if this is an ambigous job // specification or if this is an malformed job id. wchar_t *endptr; - int pid; - int found_job = 0; + bool found_job = false; - errno = 0; - pid = fish_wcstoi(argv[1], &endptr, 10); - if (!(*endptr || errno)) { + pid_t pid = wcstoimax(argv[1], &endptr, 10); + if (pid >= 1 || (*argv[1] != L'\0' && *endptr == L'\0')) { j = job_get_from_pid(pid); - if (j) found_job = 1; + if (j) found_job = true; } if (found_job) { @@ -2621,27 +2611,25 @@ static int builtin_fg(parser_t &parser, io_streams_t &streams, wchar_t **argv) { builtin_print_help(parser, streams, argv[0], streams.err); - j = 0; + return STATUS_BUILTIN_ERROR; } else { wchar_t *end; - int pid; - errno = 0; - pid = abs(fish_wcstoi(argv[1], &end, 10)); - - if (*end || errno) { + pid_t pid = wcstoimax(argv[1], &end, 10); + if (pid < 1 || !(*argv[1] != L'\0' && *end == L'\0')) { streams.err.append_format(BUILTIN_ERR_NOT_NUMBER, argv[0], argv[1]); builtin_print_help(parser, streams, argv[0], streams.err); } else { j = job_get_from_pid(pid); if (!j || !job_get_flag(j, JOB_CONSTRUCTED) || job_is_completed(j)) { streams.err.append_format(_(L"%ls: No suitable job: %d\n"), argv[0], pid); - j = 0; - } else if (!job_get_flag(j, JOB_CONTROL)) { + return STATUS_BUILTIN_ERROR; + } + if (!job_get_flag(j, JOB_CONTROL)) { streams.err.append_format(_(L"%ls: Can't put job %d, '%ls' to foreground because " L"it is not under job control\n"), argv[0], pid, j->command_wcstr()); - j = 0; + return STATUS_BUILTIN_ERROR; } } } @@ -2710,13 +2698,9 @@ static int builtin_bg(parser_t &parser, io_streams_t &streams, wchar_t **argv) { } } else { wchar_t *end; - int i; - int pid; - - for (i = 1; argv[i]; i++) { - errno = 0; - pid = fish_wcstoi(argv[i], &end, 10); - if (errno || pid < 0 || *end || !job_get_from_pid(pid)) { + for (int i = 1; argv[i]; i++) { + pid_t pid = wcstoimax(argv[i], &end, 10); + if (pid < 1 || !(*argv[i] != L'\0' && *end == L'\0') || !job_get_from_pid(pid)) { streams.err.append_format(_(L"%ls: '%ls' is not a job\n"), argv[0], argv[i]); return STATUS_BUILTIN_ERROR; } @@ -2789,9 +2773,8 @@ static int builtin_return(parser_t &parser, io_streams_t &streams, wchar_t **arg } case 2: { wchar_t *end; - errno = 0; - status = fish_wcstoi(argv[1], &end, 10); - if (errno || *end != 0) { + status = wcstoimax(argv[1], &end, 10); + if (!(*argv[1] != L'\0' && *end == L'\0')) { streams.err.append_format(_(L"%ls: Argument '%ls' must be an integer\n"), argv[0], argv[1]); builtin_print_help(parser, streams, argv[0], streams.err); diff --git a/src/builtin_jobs.cpp b/src/builtin_jobs.cpp index 72765cb82..2c46d3ae1 100644 --- a/src/builtin_jobs.cpp +++ b/src/builtin_jobs.cpp @@ -1,6 +1,8 @@ // Functions for executing the jobs builtin. #include "config.h" // IWYU pragma: keep +#include +#include #include #ifdef HAVE__PROC_SELF_STAT #include @@ -175,14 +177,10 @@ int builtin_jobs(parser_t &parser, io_streams_t &streams, wchar_t **argv) { } else { if (w.woptind < argc) { - int i; - - for (i = w.woptind; i < argc; i++) { - int pid; + for (int i = w.woptind; i < argc; i++) { wchar_t *end; - errno = 0; - pid = fish_wcstoi(argv[i], &end, 10); - if (errno || *end) { + pid_t pid = wcstoimax(argv[i], &end, 10); + if (!(*argv[i] != L'\0' && *end == L'\0')) { streams.err.append_format(_(L"%ls: '%ls' is not a job\n"), argv[0], argv[i]); return 1; } diff --git a/src/expand.cpp b/src/expand.cpp index 1b80fd334..2dadff21e 100644 --- a/src/expand.cpp +++ b/src/expand.cpp @@ -2,6 +2,8 @@ // IWYU pragma: no_include #include "config.h" +#include +#include #include #include #include @@ -15,7 +17,6 @@ #ifdef HAVE_SYS_SYSCTL_H #include // IWYU pragma: keep #endif -#include #include #ifdef SunOS #include @@ -400,7 +401,7 @@ bool process_iterator_t::next_process(wcstring *out_str, pid_t *out_pid) { if (buf.st_uid != getuid()) continue; // Remember the pid. - pid = fish_wcstoi(name.c_str(), NULL, 10); + pid = wcstoimax(name.c_str(), NULL, 10); // The 'cmdline' file exists, it should contain the commandline. FILE *cmdfile; @@ -487,9 +488,8 @@ static int find_job(const struct find_job_data_t *info) { int jid; wchar_t *end; - errno = 0; - jid = fish_wcstoi(proc, &end, 10); - if (jid > 0 && !errno && !*end) { + jid = wcstoimax(proc, &end, 10); + if (jid >= 1 && (*proc != L'\0' && *end == L'\0')) { j = job_get(jid); if ((j != 0) && (j->command_wcstr() != 0) && (!j->command_is_empty())) { append_completion(&completions, to_string(j->pgid)); diff --git a/src/highlight.cpp b/src/highlight.cpp index 49dd2f065..f40e5e7fb 100644 --- a/src/highlight.cpp +++ b/src/highlight.cpp @@ -5,7 +5,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -875,12 +877,12 @@ void highlighter_t::color_redirection(const parse_node_t &redirection_node) { switch (redirect_type) { case TOK_REDIRECT_FD: { // Target should be an fd. It must be all digits, and must not overflow. - // fish_wcstoi returns INT_MAX on overflow; we could instead check errno to + // wcstoimax returns INT_MAX on overflow; we could instead check errno to // disambiguiate this from a real INT_MAX fd, but instead we just disallow // that. const wchar_t *target_cstr = target.c_str(); - wchar_t *end = NULL; - int fd = fish_wcstoi(target_cstr, &end, 10); + wchar_t *end; + int fd = wcstoimax(target_cstr, &end, 10); // The iswdigit check ensures there's no leading whitespace, the *end check // ensures the entire string was consumed, and the numeric checks ensure the diff --git a/src/parse_execution.cpp b/src/parse_execution.cpp index 5ee3a12d0..062908542 100644 --- a/src/parse_execution.cpp +++ b/src/parse_execution.cpp @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include #include @@ -1000,8 +1002,8 @@ bool parse_execution_context_t::determine_io_chain(const parse_node_t &statement } else { wchar_t *end = NULL; errno = 0; - int old_fd = fish_wcstoi(target.c_str(), &end, 10); - if (old_fd < 0 || errno || *end) { + int old_fd = wcstoimax(target.c_str(), &end, 10); + if (old_fd < 0 || errno || *end != L'\0') { errored = report_error(redirect_node, _(L"Requested redirection to '%ls', which " L"is not a valid file descriptor"), diff --git a/src/signal.cpp b/src/signal.cpp index 213b2ffb7..91337a77e 100644 --- a/src/signal.cpp +++ b/src/signal.cpp @@ -1,6 +1,8 @@ // The library for various signal related issues. #include "config.h" // IWYU pragma: keep +#include +#include #include #include #include @@ -145,31 +147,28 @@ static const struct lookup_entry lookup[] = { /// Test if \c name is a string describing the signal named \c canonical. static int match_signal_name(const wchar_t *canonical, const wchar_t *name) { - if (wcsncasecmp(name, L"sig", 3) == 0) name += 3; + if (wcsncasecmp(name, L"sig", 3) == 0) { + name += 3; + } return wcscasecmp(canonical + 3, name) == 0; } int wcs2sig(const wchar_t *str) { - int i; wchar_t *end = 0; - for (i = 0; lookup[i].desc; i++) { + for (int i = 0; lookup[i].desc; i++) { if (match_signal_name(lookup[i].name, str)) { return lookup[i].signal; } } - errno = 0; - int res = fish_wcstoi(str, &end, 10); - if (!errno && res >= 0 && !*end) return res; - return -1; + int res = wcstoimax(str, &end, 10); + return (*str != L'\0' && *end == L'\0') ? res : -1; } const wchar_t *sig2wcs(int sig) { - int i; - - for (i = 0; lookup[i].desc; i++) { + for (int i = 0; lookup[i].desc; i++) { if (lookup[i].signal == sig) { return lookup[i].name; } @@ -179,9 +178,7 @@ const wchar_t *sig2wcs(int sig) { } const wchar_t *signal_get_desc(int sig) { - int i; - - for (i = 0; lookup[i].desc; i++) { + for (int i = 0; lookup[i].desc; i++) { if (lookup[i].signal == sig) { return _(lookup[i].desc); } @@ -229,21 +226,19 @@ static void handle_int(int sig, siginfo_t *info, void *context) { default_handler(sig, info, context); } -/// sigchld handler. Does notification and calls the handler in proc.c. +/// sigchld handler. Does notification and calls the handler in proc.cpp. static void handle_chld(int sig, siginfo_t *info, void *context) { job_handle_signal(sig, info, context); default_handler(sig, info, context); } void signal_reset_handlers() { - int i; - struct sigaction act; sigemptyset(&act.sa_mask); act.sa_flags = 0; act.sa_handler = SIG_DFL; - for (i = 0; lookup[i].desc; i++) { + for (int i = 0; lookup[i].desc; i++) { sigaction(lookup[i].signal, &act, 0); } } diff --git a/src/wutil.cpp b/src/wutil.cpp index d76b2ad8c..c7b826fd4 100644 --- a/src/wutil.cpp +++ b/src/wutil.cpp @@ -23,128 +23,23 @@ #include "fallback.h" // IWYU pragma: keep #include "wutil.h" // IWYU pragma: keep -typedef std::string cstring; - const file_id_t kInvalidFileID = {(dev_t)-1LL, (ino_t)-1LL, (uint64_t)-1LL, -1, -1, -1, -1}; #ifndef PATH_MAX #ifdef MAXPATHLEN #define PATH_MAX MAXPATHLEN #else -/// Fallback length of MAXPATHLEN. Hopefully a sane value. -#define PATH_MAX 4096 +#define PATH_MAX 4096 // Fallback length of MAXPATHLEN. Hopefully a sane value. #endif #endif -/// Lock to protect wgettext. -static pthread_mutex_t wgettext_lock; - /// Map used as cache by wgettext. typedef std::map wgettext_map_t; +/// Lock to protect wgettext. +static pthread_mutex_t wgettext_lock; static wgettext_map_t wgettext_map; -bool wreaddir_resolving(DIR *dir, const std::wstring &dir_path, std::wstring &out_name, - bool *out_is_dir) { - struct dirent *d = readdir(dir); - if (!d) return false; - - out_name = str2wcstring(d->d_name); - if (out_is_dir) { - // The caller cares if this is a directory, so check. - bool is_dir = false; - - // We may be able to skip stat, if the readdir can tell us the file type directly. - bool check_with_stat = true; -#ifdef HAVE_STRUCT_DIRENT_D_TYPE - if (d->d_type == DT_DIR) { - // Known directory. - is_dir = true; - check_with_stat = false; - } else if (d->d_type == DT_LNK || d->d_type == DT_UNKNOWN) { - // We want to treat symlinks to directories as directories. Use stat to resolve it. - check_with_stat = true; - } else { - // Regular file. - is_dir = false; - check_with_stat = false; - } -#endif // HAVE_STRUCT_DIRENT_D_TYPE - if (check_with_stat) { - // We couldn't determine the file type from the dirent; check by stat'ing it. - cstring fullpath = wcs2string(dir_path); - fullpath.push_back('/'); - fullpath.append(d->d_name); - struct stat buf; - if (stat(fullpath.c_str(), &buf) != 0) { - is_dir = false; - } else { - is_dir = !!(S_ISDIR(buf.st_mode)); - } - } - *out_is_dir = is_dir; - } - return true; -} - -bool wreaddir(DIR *dir, std::wstring &out_name) { - struct dirent *d = readdir(dir); - if (!d) return false; - - out_name = str2wcstring(d->d_name); - return true; -} - -bool wreaddir_for_dirs(DIR *dir, wcstring *out_name) { - struct dirent *result = NULL; - while (result == NULL) { - struct dirent *d = readdir(dir); - if (!d) break; - -#if HAVE_STRUCT_DIRENT_D_TYPE - switch (d->d_type) { - case DT_DIR: - case DT_LNK: - case DT_UNKNOWN: { - // These may be directories. - result = d; - break; - } - default: { - // Nothing else can. - break; - } - } -#else - // We can't determine if it's a directory or not, so just return it. - result = d; -#endif - } - if (result && out_name) { - *out_name = str2wcstring(result->d_name); - } - return result != NULL; -} - -const wcstring wgetcwd() { - wcstring retval; - - char *res = getcwd(NULL, 0); - if (res) { - retval = str2wcstring(res); - free(res); - } else { - debug(0, _(L"getcwd() failed with errno %d/%s"), errno, strerror(errno)); - retval = wcstring(); - } - - return retval; -} - -int wchdir(const wcstring &dir) { - cstring tmp = wcs2string(dir); - return chdir(tmp.c_str()); -} - +/// Wide character version of fopen(). This sets CLO_EXEC. FILE *wfopen(const wcstring &path, const char *mode) { int permissions = 0, options = 0; size_t idx = 0; @@ -194,7 +89,7 @@ bool set_cloexec(int fd) { static int wopen_internal(const wcstring &pathname, int flags, mode_t mode, bool cloexec) { ASSERT_IS_NOT_FORKED_CHILD(); - cstring tmp = wcs2string(pathname); + std::string tmp = wcs2string(pathname); int fd; #ifdef O_CLOEXEC @@ -214,35 +109,152 @@ static int wopen_internal(const wcstring &pathname, int flags, mode_t mode, bool return fd; } +/// Wide character version of open() that also sets the close-on-exec flag (atomically when +/// possible). int wopen_cloexec(const wcstring &pathname, int flags, mode_t mode) { return wopen_internal(pathname, flags, mode, true); } + +bool wreaddir_resolving(DIR *dir, const wcstring &dir_path, wcstring &out_name, bool *out_is_dir) { + struct dirent *d = readdir(dir); if (!d) return false; + + out_name = str2wcstring(d->d_name); + if (out_is_dir) { + // The caller cares if this is a directory, so check. + bool is_dir = false; + + // We may be able to skip stat, if the readdir can tell us the file type directly. + bool check_with_stat = true; +#ifdef HAVE_STRUCT_DIRENT_D_TYPE + if (d->d_type == DT_DIR) { + // Known directory. + is_dir = true; + check_with_stat = false; + } else if (d->d_type == DT_LNK || d->d_type == DT_UNKNOWN) { + // We want to treat symlinks to directories as directories. Use stat to resolve it. + check_with_stat = true; + } else { + // Regular file. + is_dir = false; + check_with_stat = false; + } +#endif // HAVE_STRUCT_DIRENT_D_TYPE + if (check_with_stat) { + // We couldn't determine the file type from the dirent; check by stat'ing it. + std::string fullpath = wcs2string(dir_path); + fullpath.push_back('/'); + fullpath.append(d->d_name); + struct stat buf; + if (stat(fullpath.c_str(), &buf) != 0) { + is_dir = false; + } else { + is_dir = !!(S_ISDIR(buf.st_mode)); + } + } + *out_is_dir = is_dir; + } + return true; +} + +/// Wide-character version of readdir() +bool wreaddir(DIR *dir, wcstring &out_name) { + struct dirent *d = readdir(dir); + if (!d) return false; + + out_name = str2wcstring(d->d_name); + return true; +} + +/// Like wreaddir, but skip items that are known to not be directories. If this requires a stat +/// (i.e. the file is a symlink), then return it. Note that this does not guarantee that everything +/// returned is a directory, it's just an optimization for cases where we would check for +/// directories anyways. +bool wreaddir_for_dirs(DIR *dir, wcstring *out_name) { + struct dirent *result = NULL; + while (result == NULL) { + struct dirent *d = readdir(dir); + if (!d) break; + +#if HAVE_STRUCT_DIRENT_D_TYPE + switch (d->d_type) { + case DT_DIR: + case DT_LNK: + case DT_UNKNOWN: { + // These may be directories. + result = d; + break; + } + default: { + // Nothing else can. + break; + } + } +#else + // We can't determine if it's a directory or not, so just return it. + result = d; +#endif + } + if (result && out_name) { + *out_name = str2wcstring(result->d_name); + } + return result != NULL; +} + +/// Wide character version of getcwd(). +const wcstring wgetcwd() { + wcstring retval; + + char *res = getcwd(NULL, 0); + if (res) { + retval = str2wcstring(res); + free(res); + } else { + debug(0, _(L"getcwd() failed with errno %d/%s"), errno, strerror(errno)); + retval = wcstring(); + } + + return retval; +} + +/// Wide character version of chdir(). +int wchdir(const wcstring &dir) { + std::string tmp = wcs2string(dir); + return chdir(tmp.c_str()); +} + +/// Wide character version of opendir(). Note that opendir() is guaranteed to set close-on-exec by +/// POSIX (hooray). DIR *wopendir(const wcstring &name) { - const cstring tmp = wcs2string(name); + const std::string tmp = wcs2string(name); return opendir(tmp.c_str()); } +/// Wide character version of stat(). int wstat(const wcstring &file_name, struct stat *buf) { - const cstring tmp = wcs2string(file_name); + const std::string tmp = wcs2string(file_name); return stat(tmp.c_str(), buf); } +/// Wide character version of lstat(). int lwstat(const wcstring &file_name, struct stat *buf) { - const cstring tmp = wcs2string(file_name); + const std::string tmp = wcs2string(file_name); return lstat(tmp.c_str(), buf); } +/// Wide character version of access(). int waccess(const wcstring &file_name, int mode) { - const cstring tmp = wcs2string(file_name); + const std::string tmp = wcs2string(file_name); return access(tmp.c_str(), mode); } +/// Wide character version of unlink(). int wunlink(const wcstring &file_name) { - const cstring tmp = wcs2string(file_name); + const std::string tmp = wcs2string(file_name); return unlink(tmp.c_str()); } +/// Wide character version of perror(). void wperror(const wchar_t *s) { int e = errno; if (s[0] != L'\0') { @@ -251,6 +263,7 @@ void wperror(const wchar_t *s) { fwprintf(stderr, L"%s\n", strerror(e)); } +/// Mark an fd as nonblocking; returns errno or 0 on success. int make_fd_nonblocking(int fd) { int flags = fcntl(fd, F_GETFL, 0); int err = 0; @@ -261,6 +274,7 @@ int make_fd_nonblocking(int fd) { return err == -1 ? errno : 0; } +/// Mark an fd as blocking; returns errno or 0 on success. int make_fd_blocking(int fd) { int flags = fcntl(fd, F_GETFL, 0); int err = 0; @@ -275,9 +289,11 @@ static inline void safe_append(char *buffer, const char *s, size_t buffsize) { strncat(buffer, s, buffsize - strlen(buffer) - 1); } -// In general, strerror is not async-safe, and therefore we cannot use it directly. So instead we -// have to grub through sys_nerr and sys_errlist directly On GNU toolchain, this will produce a -// deprecation warning from the linker (!!), which appears impossible to suppress! +/// Async-safe version of strerror(). +/// In general, strerror is not async-safe, and therefore we cannot use it directly. So instead we +/// have to grub through sys_nerr and sys_errlist directly On GNU toolchain, this will produce a +/// deprecation warning from the linker (!!), which appears impossible to suppress! +/// XXX Use strerror_r instead! const char *safe_strerror(int err) { #if defined(__UCLIBC__) // uClibc does not have sys_errlist, however, its strerror is believed to be async-safe. @@ -313,6 +329,7 @@ const char *safe_strerror(int err) { return buff; } +/// Async-safe version of perror(). void safe_perror(const char *message) { // Note we cannot use strerror, because on Linux it uses gettext, which is not safe. int err = errno; @@ -333,8 +350,11 @@ void safe_perror(const char *message) { #ifdef HAVE_REALPATH_NULL +/// Wide character version of realpath function. Just like the GNU version of realpath, wrealpath +/// will accept 0 as the value for the second argument, in which case the result will be allocated +/// using malloc, and must be free'd by the user. wchar_t *wrealpath(const wcstring &pathname, wchar_t *resolved_path) { - cstring narrow_path = wcs2string(pathname); + std::string narrow_path = wcs2string(pathname); char *narrow_res = realpath(narrow_path.c_str(), NULL); if (!narrow_res) return NULL; @@ -362,7 +382,7 @@ wchar_t *wrealpath(const wcstring &pathname, wchar_t *resolved_path) { #else wchar_t *wrealpath(const wcstring &pathname, wchar_t *resolved_path) { - cstring tmp = wcs2string(pathname); + std::string tmp = wcs2string(pathname); char narrow_buff[PATH_MAX]; char *narrow_res = realpath(tmp.c_str(), narrow_buff); wchar_t *res; @@ -381,6 +401,7 @@ wchar_t *wrealpath(const wcstring &pathname, wchar_t *resolved_path) { #endif +/// Wide character version of dirname(). wcstring wdirname(const wcstring &path) { char *tmp = wcs2str(path.c_str()); char *narrow_res = dirname(tmp); @@ -389,6 +410,7 @@ wcstring wdirname(const wcstring &path) { return result; } +/// Wide character version of basename(). wcstring wbasename(const wcstring &path) { char *tmp = wcs2str(path.c_str()); char *narrow_res = basename(tmp); @@ -411,6 +433,10 @@ static void wgettext_init_if_necessary() { pthread_once(&once, wgettext_really_init); } +/// Wide character wrapper around the gettext function. For historic reasons, unlike the real +/// gettext function, wgettext takes care of setting the correct domain, etc. using the textdomain +/// and bindtextdomain functions. This should probably be moved out of wgettext, so that wgettext +/// will be nothing more than a wrapper around gettext, like all other functions in this file. const wcstring &wgettext(const wchar_t *in) { // Preserve errno across this since this is often used in printing error messages. int err = errno; @@ -420,7 +446,7 @@ const wcstring &wgettext(const wchar_t *in) { scoped_lock locker(wgettext_lock); wcstring &val = wgettext_map[key]; if (val.empty()) { - cstring mbs_in = wcs2string(key); + std::string mbs_in = wcs2string(key); char *out = fish_gettext(mbs_in.c_str()); val = format_string(L"%s", out); } @@ -431,29 +457,19 @@ const wcstring &wgettext(const wchar_t *in) { return val; } +/// Wide character version of mkdir. int wmkdir(const wcstring &name, int mode) { - cstring name_narrow = wcs2string(name); + std::string name_narrow = wcs2string(name); return mkdir(name_narrow.c_str(), mode); } +/// Wide character version of rename. int wrename(const wcstring &old, const wcstring &newv) { - cstring old_narrow = wcs2string(old); - cstring new_narrow = wcs2string(newv); + std::string old_narrow = wcs2string(old); + std::string new_narrow = wcs2string(newv); return rename(old_narrow.c_str(), new_narrow.c_str()); } -int fish_wcstoi(const wchar_t *str, wchar_t **endptr, int base) { - long ret = wcstol(str, endptr, base); - if (ret > INT_MAX) { - ret = INT_MAX; - errno = ERANGE; - } else if (ret < INT_MIN) { - ret = INT_MIN; - errno = ERANGE; - } - return (int)ret; -} - file_id_t file_id_t::file_id_from_stat(const struct stat *buf) { assert(buf != NULL); diff --git a/src/wutil.h b/src/wutil.h index ba3854cbf..2c72716be 100644 --- a/src/wutil.h +++ b/src/wutil.h @@ -10,88 +10,55 @@ #include "common.h" -/// Wide character version of fopen(). This sets CLO_EXEC. FILE *wfopen(const wcstring &path, const char *mode); -/// Sets CLO_EXEC on a given fd. bool set_cloexec(int fd); -/// Wide character version of open() that also sets the close-on-exec flag (atomically when -/// possible). int wopen_cloexec(const wcstring &pathname, int flags, mode_t mode = 0); -/// Mark an fd as nonblocking; returns errno or 0 on success. int make_fd_nonblocking(int fd); -/// Mark an fd as blocking; returns errno or 0 on success. int make_fd_blocking(int fd); -/// Wide character version of opendir(). Note that opendir() is guaranteed to set close-on-exec by -/// POSIX (hooray). DIR *wopendir(const wcstring &name); -/// Wide character version of stat(). int wstat(const wcstring &file_name, struct stat *buf); -/// Wide character version of lstat(). int lwstat(const wcstring &file_name, struct stat *buf); -/// Wide character version of access(). int waccess(const wcstring &pathname, int mode); -/// Wide character version of unlink(). int wunlink(const wcstring &pathname); -/// Wide character version of perror(). void wperror(const wchar_t *s); -/// Async-safe version of perror(). void safe_perror(const char *message); -/// Async-safe version of strerror(). const char *safe_strerror(int err); -/// Wide character version of getcwd(). const wcstring wgetcwd(); -/// Wide character version of chdir(). int wchdir(const wcstring &dir); -/// Wide character version of realpath function. Just like the GNU version of realpath, wrealpath -/// will accept 0 as the value for the second argument, in which case the result will be allocated -/// using malloc, and must be free'd by the user. wchar_t *wrealpath(const wcstring &pathname, wchar_t *resolved_path); -/// Wide character version of readdir(). bool wreaddir(DIR *dir, std::wstring &out_name); + bool wreaddir_resolving(DIR *dir, const std::wstring &dir_path, std::wstring &out_name, bool *out_is_dir); -/// Like wreaddir, but skip items that are known to not be directories. If this requires a stat -/// (i.e. the file is a symlink), then return it. Note that this does not guarantee that everything -/// returned is a directory, it's just an optimization for cases where we would check for -/// directories anyways. bool wreaddir_for_dirs(DIR *dir, wcstring *out_name); -/// Wide character version of dirname(). std::wstring wdirname(const std::wstring &path); -/// Wide character version of basename(). std::wstring wbasename(const std::wstring &path); -/// Wide character wrapper around the gettext function. For historic reasons, unlike the real -/// gettext function, wgettext takes care of setting the correct domain, etc. using the textdomain -/// and bindtextdomain functions. This should probably be moved out of wgettext, so that wgettext -/// will be nothing more than a wrapper around gettext, like all other functions in this file. const wcstring &wgettext(const wchar_t *in); -/// Wide character version of mkdir. int wmkdir(const wcstring &dir, int mode); -/// Wide character version of rename. int wrename(const wcstring &oldName, const wcstring &newName); -/// Like wcstol(), but fails on a value outside the range of an int. int fish_wcstoi(const wchar_t *str, wchar_t **endptr, int base); /// Class for representing a file's inode. We use this to detect and avoid symlink loops, among