Revert "Tighten up empty string checks."

The use of wcstoimax causes certain out-of-range values
to be silently truncated (e.g. when converted to a pid),
and is incompatible with FreeBSD (see #626)

This reverts commit 6faa2f9866.
This commit is contained in:
ridiculousfish 2016-10-01 17:21:40 -07:00
parent b05c09429d
commit d17f2585fa
8 changed files with 275 additions and 235 deletions

View file

@ -20,9 +20,7 @@
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <inttypes.h>
#include <limits.h> #include <limits.h>
#include <stddef.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -394,7 +392,7 @@ static int builtin_bind_add(const wchar_t *seq, const wchar_t *const *cmds, size
/// if specified, _all_ key bindings will be erased /// if specified, _all_ key bindings will be erased
/// @param mode /// @param mode
/// if specified, only bindings from that mode will be erased. If not given /// if specified, only bindings from that mode will be erased. If not given
/// and all is false, @c DEFAULT_BIND_MODE will be used. /// and @c all is @c false, @c DEFAULT_BIND_MODE will be used.
/// @param use_terminfo /// @param use_terminfo
/// Whether to look use terminfo -k name /// Whether to look use terminfo -k name
/// ///
@ -408,19 +406,23 @@ static int builtin_bind_erase(wchar_t **seq, int all, const wchar_t *mode, int u
input_mapping_erase(it->seq, it->mode); input_mapping_erase(it->seq, it->mode);
} }
} }
return STATUS_BUILTIN_OK;
return 0;
} }
int res = 0; int res = 0;
if (mode == NULL) mode = DEFAULT_BIND_MODE;
while (*seq) { while (*seq) {
if (use_terminfo) { if (use_terminfo) {
wcstring seq2; wcstring seq2;
if (get_terminfo_sequence(*seq++, &seq2, streams)) { if (get_terminfo_sequence(*seq++, &seq2, streams)) {
input_mapping_erase(seq2, mode != NULL ? mode : DEFAULT_BIND_MODE); input_mapping_erase(seq2, mode);
} else { } else {
res = STATUS_BUILTIN_ERROR; res = 1;
} }
} else { } else {
input_mapping_erase(*seq++, mode != NULL ? mode : DEFAULT_BIND_MODE); input_mapping_erase(*seq++, mode);
} }
} }
@ -434,12 +436,12 @@ static int builtin_bind(parser_t &parser, io_streams_t &streams, wchar_t **argv)
int argc = builtin_count_args(argv); int argc = builtin_count_args(argv);
int mode = BIND_INSERT; int mode = BIND_INSERT;
int res = STATUS_BUILTIN_OK; int res = STATUS_BUILTIN_OK;
bool all = false; int all = 0;
const wchar_t *bind_mode = DEFAULT_BIND_MODE; const wchar_t *bind_mode = DEFAULT_BIND_MODE;
bool bind_mode_given = false; bool bind_mode_given = false;
const wchar_t *sets_bind_mode = DEFAULT_BIND_MODE; const wchar_t *sets_bind_mode = DEFAULT_BIND_MODE;
bool sets_bind_mode_given = false; bool sets_bind_mode_given = false;
bool use_terminfo = false; int use_terminfo = 0;
w.woptind = 0; w.woptind = 0;
@ -468,7 +470,7 @@ static int builtin_bind(parser_t &parser, io_streams_t &streams, wchar_t **argv)
return STATUS_BUILTIN_ERROR; return STATUS_BUILTIN_ERROR;
} }
case 'a': { case 'a': {
all = true; all = 1;
break; break;
} }
case 'e': { case 'e': {
@ -480,7 +482,7 @@ static int builtin_bind(parser_t &parser, io_streams_t &streams, wchar_t **argv)
return STATUS_BUILTIN_OK; return STATUS_BUILTIN_OK;
} }
case 'k': { case 'k': {
use_terminfo = true; use_terminfo = 1;
break; break;
} }
case 'K': { case 'K': {
@ -931,7 +933,7 @@ static wcstring functions_def(const wcstring &name) {
case EVENT_EXIT: { case EVENT_EXIT: {
if (next->param1.pid > 0) if (next->param1.pid > 0)
append_format(out, L" --on-process-exit %d", next->param1.pid); append_format(out, L" --on-process-exit %d", next->param1.pid);
else if (next->param1.pid < 0) else
append_format(out, L" --on-job-exit %d", -next->param1.pid); append_format(out, L" --on-job-exit %d", -next->param1.pid);
break; break;
} }
@ -1251,7 +1253,7 @@ static bool builtin_echo_parse_numeric_sequence(const wchar_t *str, size_t *cons
bool success = false; bool success = false;
unsigned int start = 0; // the first character of the numeric part of the sequence unsigned int start = 0; // the first character of the numeric part of the sequence
unsigned short base = 0, max_digits = 0; unsigned int base = 0, max_digits = 0;
if (builtin_echo_digit(str[0], 8) != UINT_MAX) { if (builtin_echo_digit(str[0], 8) != UINT_MAX) {
// Octal escape // Octal escape
base = 8; base = 8;
@ -1270,7 +1272,7 @@ static bool builtin_echo_parse_numeric_sequence(const wchar_t *str, size_t *cons
if (base != 0) { if (base != 0) {
unsigned int idx; unsigned int idx;
unsigned char val = '\0'; // resulting character unsigned char val = 0; // resulting character
for (idx = start; idx < start + max_digits; idx++) { for (idx = start; idx < start + max_digits; idx++) {
unsigned int digit = builtin_echo_digit(str[idx], base); unsigned int digit = builtin_echo_digit(str[idx], base);
if (digit == UINT_MAX) break; if (digit == UINT_MAX) break;
@ -1556,6 +1558,7 @@ int builtin_function(parser_t &parser, io_streams_t &streams, const wcstring_lis
} }
case 'j': case 'j':
case 'p': { case 'p': {
pid_t pid;
wchar_t *end; wchar_t *end;
event_t e(EVENT_ANY); event_t e(EVENT_ANY);
@ -1582,17 +1585,18 @@ int builtin_function(parser_t &parser, io_streams_t &streams, const wcstring_lis
append_format(*out_err, append_format(*out_err,
_(L"%ls: Cannot find calling job for event handler"), _(L"%ls: Cannot find calling job for event handler"),
argv[0]); argv[0]);
res = STATUS_BUILTIN_ERROR; res = 1;
} else { } else {
e.type = EVENT_JOB_ID; e.type = EVENT_JOB_ID;
e.param1.job_id = job_id; e.param1.job_id = job_id;
} }
} else { } else {
pid_t pid = wcstoimax(w.woptarg, &end, 10); errno = 0;
if (pid < 1 || !(*w.woptarg != L'\0' && *end == L'\0')) { pid = fish_wcstoi(w.woptarg, &end, 10);
append_format(*out_err, _(L"%ls: Invalid process id '%ls'"), argv[0], if (errno || !end || *end) {
append_format(*out_err, _(L"%ls: Invalid process id %ls"), argv[0],
w.woptarg); w.woptarg);
res = STATUS_BUILTIN_ERROR; res = 1;
break; break;
} }
@ -1640,7 +1644,7 @@ int builtin_function(parser_t &parser, io_streams_t &streams, const wcstring_lis
} }
case '?': { case '?': {
builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]);
res = STATUS_BUILTIN_ERROR; res = 1;
break; break;
} }
} }
@ -1730,7 +1734,7 @@ int builtin_function(parser_t &parser, io_streams_t &streams, const wcstring_lis
/// The random builtin generates random numbers. /// The random builtin generates random numbers.
static int builtin_random(parser_t &parser, io_streams_t &streams, wchar_t **argv) { static int builtin_random(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
static bool seeded = false; static int seeded = 0;
static struct drand48_data seed_buffer; static struct drand48_data seed_buffer;
int argc = builtin_count_args(argv); int argc = builtin_count_args(argv);
@ -1768,7 +1772,7 @@ static int builtin_random(parser_t &parser, io_streams_t &streams, wchar_t **arg
case 0: { case 0: {
long res; long res;
if (!seeded) { if (!seeded) {
seeded = true; seeded = 1;
srand48_r(time(0), &seed_buffer); srand48_r(time(0), &seed_buffer);
} }
lrand48_r(&seed_buffer, &res); lrand48_r(&seed_buffer, &res);
@ -1776,16 +1780,19 @@ static int builtin_random(parser_t &parser, io_streams_t &streams, wchar_t **arg
break; break;
} }
case 1: { case 1: {
long foo;
wchar_t *end = 0; wchar_t *end = 0;
long seedval = wcstol(argv[w.woptind], &end, 10);
if (!(*argv[w.woptind] != L'\0' && *end == L'\0')) { errno = 0;
foo = wcstol(argv[w.woptind], &end, 10);
if (errno || *end) {
streams.err.append_format(_(L"%ls: Seed value '%ls' is not a valid number\n"), streams.err.append_format(_(L"%ls: Seed value '%ls' is not a valid number\n"),
argv[0], argv[w.woptind]); argv[0], argv[w.woptind]);
return STATUS_BUILTIN_ERROR; return STATUS_BUILTIN_ERROR;
} }
seeded = true; seeded = 1;
srand48_r(seedval, &seed_buffer); srand48_r(foo, &seed_buffer);
break; break;
} }
default: { default: {
@ -1811,8 +1818,8 @@ static int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv)
const wchar_t *mode_name = READ_MODE_NAME; const wchar_t *mode_name = READ_MODE_NAME;
int nchars = 0; int nchars = 0;
wchar_t *end; wchar_t *end;
bool shell = false; int shell = 0;
bool array = false; int array = 0;
bool split_null = false; bool split_null = false;
while (1) { while (1) {
@ -1882,9 +1889,9 @@ static int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv)
break; break;
} }
case L'n': { case L'n': {
nchars = wcstoimax(w.woptarg, &end, 10); errno = 0;
if (!(*w.woptarg != L'\0' && *end == L'\0')) { nchars = fish_wcstoi(w.woptarg, &end, 10);
// MUST be an error if (errno || *end != 0) {
switch (errno) { switch (errno) {
case ERANGE: { case ERANGE: {
streams.err.append_format(_(L"%ls: Argument '%ls' is out of range\n"), streams.err.append_format(_(L"%ls: Argument '%ls' is out of range\n"),
@ -1903,11 +1910,11 @@ static int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv)
break; break;
} }
case 's': { case 's': {
shell = true; shell = 1;
break; break;
} }
case 'a': { case 'a': {
array = true; array = 1;
break; break;
} }
case L'z': { case L'z': {
@ -2303,8 +2310,9 @@ static int builtin_exit(parser_t &parser, io_streams_t &streams, wchar_t **argv)
} }
case 2: { case 2: {
wchar_t *end; wchar_t *end;
errno = 0;
ec = wcstol(argv[1], &end, 10); ec = wcstol(argv[1], &end, 10);
if (!(*argv[1] != L'\0' && *end == L'\0')) { if (errno || *end != 0) {
streams.err.append_format(_(L"%ls: Argument '%ls' must be an integer\n"), argv[0], streams.err.append_format(_(L"%ls: Argument '%ls' must be an integer\n"), argv[0],
argv[1]); argv[1]);
builtin_print_help(parser, streams, argv[0], streams.err); builtin_print_help(parser, streams, argv[0], streams.err);
@ -2369,8 +2377,9 @@ static int builtin_cd(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
res = 1; res = 1;
} else if (wchdir(dir) != 0) { } else if (wchdir(dir) != 0) {
struct stat buffer; struct stat buffer;
int status = wstat(dir, &buffer); int status;
status = wstat(dir, &buffer);
if (!status && S_ISDIR(buffer.st_mode)) { if (!status && S_ISDIR(buffer.st_mode)) {
streams.err.append_format(_(L"%ls: Permission denied: '%ls'\n"), argv[0], dir.c_str()); streams.err.append_format(_(L"%ls: Permission denied: '%ls'\n"), argv[0], dir.c_str());
@ -2551,12 +2560,14 @@ 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 // 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. // specification or if this is an malformed job id.
wchar_t *endptr; wchar_t *endptr;
bool found_job = false; int pid;
int found_job = 0;
pid_t pid = wcstoimax(argv[1], &endptr, 10); errno = 0;
if (pid >= 1 || (*argv[1] != L'\0' && *endptr == L'\0')) { pid = fish_wcstoi(argv[1], &endptr, 10);
if (!(*endptr || errno)) {
j = job_get_from_pid(pid); j = job_get_from_pid(pid);
if (j) found_job = true; if (j) found_job = 1;
} }
if (found_job) { if (found_job) {
@ -2567,25 +2578,27 @@ static int builtin_fg(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
builtin_print_help(parser, streams, argv[0], streams.err); builtin_print_help(parser, streams, argv[0], streams.err);
return STATUS_BUILTIN_ERROR; j = 0;
} else { } else {
wchar_t *end; wchar_t *end;
pid_t pid = wcstoimax(argv[1], &end, 10); int pid;
if (pid < 1 || !(*argv[1] != L'\0' && *end == L'\0')) { errno = 0;
pid = abs(fish_wcstoi(argv[1], &end, 10));
if (*end || errno) {
streams.err.append_format(BUILTIN_ERR_NOT_NUMBER, argv[0], argv[1]); streams.err.append_format(BUILTIN_ERR_NOT_NUMBER, argv[0], argv[1]);
builtin_print_help(parser, streams, argv[0], streams.err); builtin_print_help(parser, streams, argv[0], streams.err);
} else { } else {
j = job_get_from_pid(pid); j = job_get_from_pid(pid);
if (!j || !job_get_flag(j, JOB_CONSTRUCTED) || job_is_completed(j)) { 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); streams.err.append_format(_(L"%ls: No suitable job: %d\n"), argv[0], pid);
return STATUS_BUILTIN_ERROR; j = 0;
} } else if (!job_get_flag(j, JOB_CONTROL)) {
if (!job_get_flag(j, JOB_CONTROL)) {
streams.err.append_format(_(L"%ls: Can't put job %d, '%ls' to foreground because " streams.err.append_format(_(L"%ls: Can't put job %d, '%ls' to foreground because "
L"it is not under job control\n"), L"it is not under job control\n"),
argv[0], pid, j->command_wcstr()); argv[0], pid, j->command_wcstr());
return STATUS_BUILTIN_ERROR; j = 0;
} }
} }
} }
@ -2654,9 +2667,13 @@ static int builtin_bg(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
} }
} else { } else {
wchar_t *end; wchar_t *end;
for (int i = 1; argv[i]; i++) { int i;
pid_t pid = wcstoimax(argv[i], &end, 10); int pid;
if (pid < 1 || !(*argv[i] != L'\0' && *end == L'\0') || !job_get_from_pid(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)) {
streams.err.append_format(_(L"%ls: '%ls' is not a job\n"), argv[0], argv[i]); streams.err.append_format(_(L"%ls: '%ls' is not a job\n"), argv[0], argv[i]);
return STATUS_BUILTIN_ERROR; return STATUS_BUILTIN_ERROR;
} }
@ -2729,8 +2746,9 @@ static int builtin_return(parser_t &parser, io_streams_t &streams, wchar_t **arg
} }
case 2: { case 2: {
wchar_t *end; wchar_t *end;
status = wcstoimax(argv[1], &end, 10); errno = 0;
if (!(*argv[1] != L'\0' && *end == L'\0')) { status = fish_wcstoi(argv[1], &end, 10);
if (errno || *end != 0) {
streams.err.append_format(_(L"%ls: Argument '%ls' must be an integer\n"), argv[0], streams.err.append_format(_(L"%ls: Argument '%ls' must be an integer\n"), argv[0],
argv[1]); argv[1]);
builtin_print_help(parser, streams, argv[0], streams.err); builtin_print_help(parser, streams, argv[0], streams.err);

View file

@ -1,8 +1,6 @@
// Functions for executing the jobs builtin. // Functions for executing the jobs builtin.
#include "config.h" // IWYU pragma: keep #include "config.h" // IWYU pragma: keep
#include <inttypes.h>
#include <stddef.h>
#include <errno.h> #include <errno.h>
#ifdef HAVE__PROC_SELF_STAT #ifdef HAVE__PROC_SELF_STAT
#include <sys/time.h> #include <sys/time.h>
@ -177,10 +175,14 @@ int builtin_jobs(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
} else { } else {
if (w.woptind < argc) { if (w.woptind < argc) {
for (int i = w.woptind; i < argc; i++) { int i;
for (i = w.woptind; i < argc; i++) {
int pid;
wchar_t *end; wchar_t *end;
pid_t pid = wcstoimax(argv[i], &end, 10); errno = 0;
if (!(*argv[i] != L'\0' && *end == L'\0')) { pid = fish_wcstoi(argv[i], &end, 10);
if (errno || *end) {
streams.err.append_format(_(L"%ls: '%ls' is not a job\n"), argv[0], argv[i]); streams.err.append_format(_(L"%ls: '%ls' is not a job\n"), argv[0], argv[i]);
return 1; return 1;
} }

View file

@ -2,8 +2,6 @@
// IWYU pragma: no_include <cstddef> // IWYU pragma: no_include <cstddef>
#include "config.h" #include "config.h"
#include <assert.h>
#include <inttypes.h>
#include <errno.h> #include <errno.h>
#include <pwd.h> #include <pwd.h>
#include <stdarg.h> #include <stdarg.h>
@ -17,6 +15,7 @@
#ifdef HAVE_SYS_SYSCTL_H #ifdef HAVE_SYS_SYSCTL_H
#include <sys/sysctl.h> // IWYU pragma: keep #include <sys/sysctl.h> // IWYU pragma: keep
#endif #endif
#include <assert.h>
#include <vector> #include <vector>
#ifdef SunOS #ifdef SunOS
#include <procfs.h> #include <procfs.h>
@ -401,7 +400,7 @@ bool process_iterator_t::next_process(wcstring *out_str, pid_t *out_pid) {
if (buf.st_uid != getuid()) continue; if (buf.st_uid != getuid()) continue;
// Remember the pid. // Remember the pid.
pid = wcstoimax(name.c_str(), NULL, 10); pid = fish_wcstoi(name.c_str(), NULL, 10);
// The 'cmdline' file exists, it should contain the commandline. // The 'cmdline' file exists, it should contain the commandline.
FILE *cmdfile; FILE *cmdfile;
@ -488,8 +487,9 @@ static int find_job(const struct find_job_data_t *info) {
int jid; int jid;
wchar_t *end; wchar_t *end;
jid = wcstoimax(proc, &end, 10); errno = 0;
if (jid >= 1 && (*proc != L'\0' && *end == L'\0')) { jid = fish_wcstoi(proc, &end, 10);
if (jid > 0 && !errno && !*end) {
j = job_get(jid); j = job_get(jid);
if ((j != 0) && (j->command_wcstr() != 0) && (!j->command_is_empty())) { if ((j != 0) && (j->command_wcstr() != 0) && (!j->command_is_empty())) {
append_completion(&completions, to_string<long>(j->pgid)); append_completion(&completions, to_string<long>(j->pgid));

View file

@ -5,9 +5,7 @@
#include <dirent.h> #include <dirent.h>
#include <errno.h> #include <errno.h>
#include <limits.h> #include <limits.h>
#include <inttypes.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <stddef.h>
#include <unistd.h> #include <unistd.h>
#include <wchar.h> #include <wchar.h>
#include <wctype.h> #include <wctype.h>
@ -877,12 +875,12 @@ void highlighter_t::color_redirection(const parse_node_t &redirection_node) {
switch (redirect_type) { switch (redirect_type) {
case TOK_REDIRECT_FD: { case TOK_REDIRECT_FD: {
// Target should be an fd. It must be all digits, and must not overflow. // Target should be an fd. It must be all digits, and must not overflow.
// wcstoimax returns INT_MAX on overflow; we could instead check errno to // fish_wcstoi returns INT_MAX on overflow; we could instead check errno to
// disambiguiate this from a real INT_MAX fd, but instead we just disallow // disambiguiate this from a real INT_MAX fd, but instead we just disallow
// that. // that.
const wchar_t *target_cstr = target.c_str(); const wchar_t *target_cstr = target.c_str();
wchar_t *end; wchar_t *end = NULL;
int fd = wcstoimax(target_cstr, &end, 10); int fd = fish_wcstoi(target_cstr, &end, 10);
// The iswdigit check ensures there's no leading whitespace, the *end check // The iswdigit check ensures there's no leading whitespace, the *end check
// ensures the entire string was consumed, and the numeric checks ensure the // ensures the entire string was consumed, and the numeric checks ensure the

View file

@ -13,8 +13,6 @@
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <stddef.h>
#include <inttypes.h>
#include <termios.h> #include <termios.h>
#include <unistd.h> #include <unistd.h>
#include <wchar.h> #include <wchar.h>
@ -1002,8 +1000,8 @@ bool parse_execution_context_t::determine_io_chain(const parse_node_t &statement
} else { } else {
wchar_t *end = NULL; wchar_t *end = NULL;
errno = 0; errno = 0;
int old_fd = wcstoimax(target.c_str(), &end, 10); int old_fd = fish_wcstoi(target.c_str(), &end, 10);
if (old_fd < 0 || errno || *end != L'\0') { if (old_fd < 0 || errno || *end) {
errored = errored =
report_error(redirect_node, _(L"Requested redirection to '%ls', which " report_error(redirect_node, _(L"Requested redirection to '%ls', which "
L"is not a valid file descriptor"), L"is not a valid file descriptor"),

View file

@ -1,8 +1,6 @@
// The library for various signal related issues. // The library for various signal related issues.
#include "config.h" // IWYU pragma: keep #include "config.h" // IWYU pragma: keep
#include <stddef.h>
#include <inttypes.h>
#include <errno.h> #include <errno.h>
#include <signal.h> #include <signal.h>
#include <stdio.h> #include <stdio.h>
@ -147,28 +145,31 @@ static const struct lookup_entry lookup[] = {
/// Test if \c name is a string describing the signal named \c canonical. /// 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) { static int match_signal_name(const wchar_t *canonical, const wchar_t *name) {
if (wcsncasecmp(name, L"sig", 3) == 0) { if (wcsncasecmp(name, L"sig", 3) == 0) name += 3;
name += 3;
}
return wcscasecmp(canonical + 3, name) == 0; return wcscasecmp(canonical + 3, name) == 0;
} }
int wcs2sig(const wchar_t *str) { int wcs2sig(const wchar_t *str) {
int i;
wchar_t *end = 0; wchar_t *end = 0;
for (int i = 0; lookup[i].desc; i++) { for (i = 0; lookup[i].desc; i++) {
if (match_signal_name(lookup[i].name, str)) { if (match_signal_name(lookup[i].name, str)) {
return lookup[i].signal; return lookup[i].signal;
} }
} }
errno = 0;
int res = fish_wcstoi(str, &end, 10);
if (!errno && res >= 0 && !*end) return res;
int res = wcstoimax(str, &end, 10); return -1;
return (*str != L'\0' && *end == L'\0') ? res : -1;
} }
const wchar_t *sig2wcs(int sig) { const wchar_t *sig2wcs(int sig) {
for (int i = 0; lookup[i].desc; i++) { int i;
for (i = 0; lookup[i].desc; i++) {
if (lookup[i].signal == sig) { if (lookup[i].signal == sig) {
return lookup[i].name; return lookup[i].name;
} }
@ -178,7 +179,9 @@ const wchar_t *sig2wcs(int sig) {
} }
const wchar_t *signal_get_desc(int sig) { const wchar_t *signal_get_desc(int sig) {
for (int i = 0; lookup[i].desc; i++) { int i;
for (i = 0; lookup[i].desc; i++) {
if (lookup[i].signal == sig) { if (lookup[i].signal == sig) {
return _(lookup[i].desc); return _(lookup[i].desc);
} }
@ -226,19 +229,21 @@ static void handle_int(int sig, siginfo_t *info, void *context) {
default_handler(sig, info, context); default_handler(sig, info, context);
} }
/// sigchld handler. Does notification and calls the handler in proc.cpp. /// sigchld handler. Does notification and calls the handler in proc.c.
static void handle_chld(int sig, siginfo_t *info, void *context) { static void handle_chld(int sig, siginfo_t *info, void *context) {
job_handle_signal(sig, info, context); job_handle_signal(sig, info, context);
default_handler(sig, info, context); default_handler(sig, info, context);
} }
void signal_reset_handlers() { void signal_reset_handlers() {
int i;
struct sigaction act; struct sigaction act;
sigemptyset(&act.sa_mask); sigemptyset(&act.sa_mask);
act.sa_flags = 0; act.sa_flags = 0;
act.sa_handler = SIG_DFL; act.sa_handler = SIG_DFL;
for (int i = 0; lookup[i].desc; i++) { for (i = 0; lookup[i].desc; i++) {
sigaction(lookup[i].signal, &act, 0); sigaction(lookup[i].signal, &act, 0);
} }
} }

View file

@ -24,23 +24,128 @@
#include "fallback.h" // IWYU pragma: keep #include "fallback.h" // IWYU pragma: keep
#include "wutil.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}; const file_id_t kInvalidFileID = {(dev_t)-1LL, (ino_t)-1LL, (uint64_t)-1LL, -1, -1, -1, -1};
#ifndef PATH_MAX #ifndef PATH_MAX
#ifdef MAXPATHLEN #ifdef MAXPATHLEN
#define PATH_MAX MAXPATHLEN #define PATH_MAX MAXPATHLEN
#else #else
#define PATH_MAX 4096 // Fallback length of MAXPATHLEN. Hopefully a sane value. /// Fallback length of MAXPATHLEN. Hopefully a sane value.
#define PATH_MAX 4096
#endif #endif
#endif #endif
/// Lock to protect wgettext.
static pthread_mutex_t wgettext_lock;
/// Map used as cache by wgettext. /// Map used as cache by wgettext.
typedef std::map<wcstring, wcstring> wgettext_map_t; typedef std::map<wcstring, wcstring> wgettext_map_t;
/// Lock to protect wgettext.
static pthread_mutex_t wgettext_lock;
static wgettext_map_t wgettext_map; static wgettext_map_t wgettext_map;
/// Wide character version of fopen(). This sets CLO_EXEC. 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());
}
FILE *wfopen(const wcstring &path, const char *mode) { FILE *wfopen(const wcstring &path, const char *mode) {
int permissions = 0, options = 0; int permissions = 0, options = 0;
size_t idx = 0; size_t idx = 0;
@ -90,7 +195,7 @@ bool set_cloexec(int fd) {
static int wopen_internal(const wcstring &pathname, int flags, mode_t mode, bool cloexec) { static int wopen_internal(const wcstring &pathname, int flags, mode_t mode, bool cloexec) {
ASSERT_IS_NOT_FORKED_CHILD(); ASSERT_IS_NOT_FORKED_CHILD();
std::string tmp = wcs2string(pathname); cstring tmp = wcs2string(pathname);
int fd; int fd;
#ifdef O_CLOEXEC #ifdef O_CLOEXEC
@ -110,152 +215,35 @@ static int wopen_internal(const wcstring &pathname, int flags, mode_t mode, bool
return fd; 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) { int wopen_cloexec(const wcstring &pathname, int flags, mode_t mode) {
return wopen_internal(pathname, flags, mode, true); 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) { DIR *wopendir(const wcstring &name) {
const std::string tmp = wcs2string(name); const cstring tmp = wcs2string(name);
return opendir(tmp.c_str()); return opendir(tmp.c_str());
} }
/// Wide character version of stat().
int wstat(const wcstring &file_name, struct stat *buf) { int wstat(const wcstring &file_name, struct stat *buf) {
const std::string tmp = wcs2string(file_name); const cstring tmp = wcs2string(file_name);
return stat(tmp.c_str(), buf); return stat(tmp.c_str(), buf);
} }
/// Wide character version of lstat().
int lwstat(const wcstring &file_name, struct stat *buf) { int lwstat(const wcstring &file_name, struct stat *buf) {
const std::string tmp = wcs2string(file_name); const cstring tmp = wcs2string(file_name);
return lstat(tmp.c_str(), buf); return lstat(tmp.c_str(), buf);
} }
/// Wide character version of access().
int waccess(const wcstring &file_name, int mode) { int waccess(const wcstring &file_name, int mode) {
const std::string tmp = wcs2string(file_name); const cstring tmp = wcs2string(file_name);
return access(tmp.c_str(), mode); return access(tmp.c_str(), mode);
} }
/// Wide character version of unlink().
int wunlink(const wcstring &file_name) { int wunlink(const wcstring &file_name) {
const std::string tmp = wcs2string(file_name); const cstring tmp = wcs2string(file_name);
return unlink(tmp.c_str()); return unlink(tmp.c_str());
} }
/// Wide character version of perror().
void wperror(const wchar_t *s) { void wperror(const wchar_t *s) {
int e = errno; int e = errno;
if (s[0] != L'\0') { if (s[0] != L'\0') {
@ -264,7 +252,6 @@ void wperror(const wchar_t *s) {
fwprintf(stderr, L"%s\n", strerror(e)); 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 make_fd_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0); int flags = fcntl(fd, F_GETFL, 0);
int err = 0; int err = 0;
@ -275,7 +262,6 @@ int make_fd_nonblocking(int fd) {
return err == -1 ? errno : 0; return err == -1 ? errno : 0;
} }
/// Mark an fd as blocking; returns errno or 0 on success.
int make_fd_blocking(int fd) { int make_fd_blocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0); int flags = fcntl(fd, F_GETFL, 0);
int err = 0; int err = 0;
@ -290,11 +276,9 @@ static inline void safe_append(char *buffer, const char *s, size_t buffsize) {
strncat(buffer, s, buffsize - strlen(buffer) - 1); strncat(buffer, s, buffsize - strlen(buffer) - 1);
} }
/// Async-safe version of strerror(). // In general, strerror is not async-safe, and therefore we cannot use it directly. So instead we
/// 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
/// 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!
/// deprecation warning from the linker (!!), which appears impossible to suppress!
/// XXX Use strerror_r instead!
const char *safe_strerror(int err) { const char *safe_strerror(int err) {
#if defined(__UCLIBC__) #if defined(__UCLIBC__)
// uClibc does not have sys_errlist, however, its strerror is believed to be async-safe. // uClibc does not have sys_errlist, however, its strerror is believed to be async-safe.
@ -330,7 +314,6 @@ const char *safe_strerror(int err) {
return buff; return buff;
} }
/// Async-safe version of perror().
void safe_perror(const char *message) { void safe_perror(const char *message) {
// Note we cannot use strerror, because on Linux it uses gettext, which is not safe. // Note we cannot use strerror, because on Linux it uses gettext, which is not safe.
int err = errno; int err = errno;
@ -351,11 +334,8 @@ void safe_perror(const char *message) {
#ifdef HAVE_REALPATH_NULL #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) { wchar_t *wrealpath(const wcstring &pathname, wchar_t *resolved_path) {
std::string narrow_path = wcs2string(pathname); cstring narrow_path = wcs2string(pathname);
char *narrow_res = realpath(narrow_path.c_str(), NULL); char *narrow_res = realpath(narrow_path.c_str(), NULL);
if (!narrow_res) return NULL; if (!narrow_res) return NULL;
@ -383,7 +363,7 @@ wchar_t *wrealpath(const wcstring &pathname, wchar_t *resolved_path) {
#else #else
wchar_t *wrealpath(const wcstring &pathname, wchar_t *resolved_path) { wchar_t *wrealpath(const wcstring &pathname, wchar_t *resolved_path) {
std::string tmp = wcs2string(pathname); cstring tmp = wcs2string(pathname);
char narrow_buff[PATH_MAX]; char narrow_buff[PATH_MAX];
char *narrow_res = realpath(tmp.c_str(), narrow_buff); char *narrow_res = realpath(tmp.c_str(), narrow_buff);
wchar_t *res; wchar_t *res;
@ -402,7 +382,6 @@ wchar_t *wrealpath(const wcstring &pathname, wchar_t *resolved_path) {
#endif #endif
/// Wide character version of dirname().
wcstring wdirname(const wcstring &path) { wcstring wdirname(const wcstring &path) {
char *tmp = wcs2str(path.c_str()); char *tmp = wcs2str(path.c_str());
char *narrow_res = dirname(tmp); char *narrow_res = dirname(tmp);
@ -411,7 +390,6 @@ wcstring wdirname(const wcstring &path) {
return result; return result;
} }
/// Wide character version of basename().
wcstring wbasename(const wcstring &path) { wcstring wbasename(const wcstring &path) {
char *tmp = wcs2str(path.c_str()); char *tmp = wcs2str(path.c_str());
char *narrow_res = basename(tmp); char *narrow_res = basename(tmp);
@ -434,10 +412,6 @@ static void wgettext_init_if_necessary() {
pthread_once(&once, wgettext_really_init); 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) { const wcstring &wgettext(const wchar_t *in) {
// Preserve errno across this since this is often used in printing error messages. // Preserve errno across this since this is often used in printing error messages.
int err = errno; int err = errno;
@ -447,7 +421,7 @@ const wcstring &wgettext(const wchar_t *in) {
scoped_lock locker(wgettext_lock); scoped_lock locker(wgettext_lock);
wcstring &val = wgettext_map[key]; wcstring &val = wgettext_map[key];
if (val.empty()) { if (val.empty()) {
std::string mbs_in = wcs2string(key); cstring mbs_in = wcs2string(key);
char *out = fish_gettext(mbs_in.c_str()); char *out = fish_gettext(mbs_in.c_str());
val = format_string(L"%s", out); val = format_string(L"%s", out);
} }
@ -458,16 +432,14 @@ const wcstring &wgettext(const wchar_t *in) {
return val; return val;
} }
/// Wide character version of mkdir.
int wmkdir(const wcstring &name, int mode) { int wmkdir(const wcstring &name, int mode) {
std::string name_narrow = wcs2string(name); cstring name_narrow = wcs2string(name);
return mkdir(name_narrow.c_str(), mode); return mkdir(name_narrow.c_str(), mode);
} }
/// Wide character version of rename.
int wrename(const wcstring &old, const wcstring &newv) { int wrename(const wcstring &old, const wcstring &newv) {
std::string old_narrow = wcs2string(old); cstring old_narrow = wcs2string(old);
std::string new_narrow = wcs2string(newv); cstring new_narrow = wcs2string(newv);
return rename(old_narrow.c_str(), new_narrow.c_str()); return rename(old_narrow.c_str(), new_narrow.c_str());
} }
@ -549,6 +521,18 @@ int fish_wcswidth(const wchar_t *str) { return fish_wcswidth(str, wcslen(str));
/// See fallback.h for the normal definitions. /// See fallback.h for the normal definitions.
int fish_wcswidth(const wcstring &str) { return fish_wcswidth(str.c_str(), str.size()); } int fish_wcswidth(const wcstring &str) { return fish_wcswidth(str.c_str(), str.size()); }
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) { file_id_t file_id_t::file_id_from_stat(const struct stat *buf) {
assert(buf != NULL); assert(buf != NULL);

View file

@ -10,53 +10,85 @@
#include "common.h" #include "common.h"
/// Wide character version of fopen(). This sets CLO_EXEC.
FILE *wfopen(const wcstring &path, const char *mode); FILE *wfopen(const wcstring &path, const char *mode);
/// Sets CLO_EXEC on a given fd.
bool set_cloexec(int 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); 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); int make_fd_nonblocking(int fd);
/// Mark an fd as blocking; returns errno or 0 on success.
int make_fd_blocking(int fd); 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); DIR *wopendir(const wcstring &name);
/// Wide character version of stat().
int wstat(const wcstring &file_name, struct stat *buf); int wstat(const wcstring &file_name, struct stat *buf);
/// Wide character version of lstat().
int lwstat(const wcstring &file_name, struct stat *buf); int lwstat(const wcstring &file_name, struct stat *buf);
/// Wide character version of access().
int waccess(const wcstring &pathname, int mode); int waccess(const wcstring &pathname, int mode);
/// Wide character version of unlink().
int wunlink(const wcstring &pathname); int wunlink(const wcstring &pathname);
/// Wide character version of perror().
void wperror(const wchar_t *s); void wperror(const wchar_t *s);
/// Async-safe version of perror().
void safe_perror(const char *message); void safe_perror(const char *message);
/// Async-safe version of strerror().
const char *safe_strerror(int err); const char *safe_strerror(int err);
/// Wide character version of getcwd().
const wcstring wgetcwd(); const wcstring wgetcwd();
/// Wide character version of chdir().
int wchdir(const wcstring &dir); 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); 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(DIR *dir, std::wstring &out_name);
bool wreaddir_resolving(DIR *dir, const std::wstring &dir_path, std::wstring &out_name, bool wreaddir_resolving(DIR *dir, const std::wstring &dir_path, std::wstring &out_name,
bool *out_is_dir); 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); bool wreaddir_for_dirs(DIR *dir, wcstring *out_name);
/// Wide character version of dirname().
std::wstring wdirname(const std::wstring &path); std::wstring wdirname(const std::wstring &path);
/// Wide character version of basename().
std::wstring wbasename(const std::wstring &path); 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); const wcstring &wgettext(const wchar_t *in);
/// Wide character version of mkdir.
int wmkdir(const wcstring &dir, int mode); int wmkdir(const wcstring &dir, int mode);
/// Wide character version of rename.
int wrename(const wcstring &oldName, const wcstring &newName); int wrename(const wcstring &oldName, const wcstring &newName);
#define PUA1_START 0xE000 #define PUA1_START 0xE000
@ -84,6 +116,9 @@ bool wcsvarchr(wchar_t chr);
int fish_wcswidth(const wchar_t *str); int fish_wcswidth(const wchar_t *str);
int fish_wcswidth(const wcstring &str); int fish_wcswidth(const wcstring &str);
/// 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 /// Class for representing a file's inode. We use this to detect and avoid symlink loops, among
/// other things. While an inode / dev pair is sufficient to distinguish co-existing files, Linux /// other things. While an inode / dev pair is sufficient to distinguish co-existing files, Linux
/// seems to aggressively re-use inodes, so it cannot determine if a file has been deleted (ABA /// seems to aggressively re-use inodes, so it cannot determine if a file has been deleted (ABA