2016-04-28 07:10:14 +08:00
|
|
|
// Various functions, mostly string utilities, that are used by most parts of fish.
|
2005-10-05 17:58:00 +08:00
|
|
|
#include "config.h"
|
|
|
|
|
2017-06-21 12:55:16 +08:00
|
|
|
#include <ctype.h>
|
2016-05-16 10:45:02 +08:00
|
|
|
#include <cxxabi.h>
|
|
|
|
#include <dlfcn.h>
|
2005-09-20 21:26:39 +08:00
|
|
|
#include <errno.h>
|
2016-12-15 11:21:36 +08:00
|
|
|
#include <fcntl.h>
|
2005-09-20 21:26:39 +08:00
|
|
|
#include <limits.h>
|
2019-02-06 12:36:38 +08:00
|
|
|
#include <paths.h>
|
2017-09-21 13:00:14 +08:00
|
|
|
#include <pthread.h>
|
2012-11-19 08:30:30 +08:00
|
|
|
#include <stdarg.h>
|
2016-04-21 14:00:54 +08:00
|
|
|
#include <stddef.h>
|
2016-04-28 07:10:14 +08:00
|
|
|
#include <stdint.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <sys/stat.h>
|
2005-09-28 09:43:09 +08:00
|
|
|
#include <sys/time.h>
|
2016-04-28 07:10:14 +08:00
|
|
|
#include <termios.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <wctype.h>
|
2019-05-05 18:09:25 +08:00
|
|
|
#include <cstring>
|
|
|
|
#include <cwchar>
|
2007-01-20 10:36:49 +08:00
|
|
|
#ifdef HAVE_EXECINFO_H
|
|
|
|
#include <execinfo.h>
|
|
|
|
#endif
|
2016-04-28 07:10:14 +08:00
|
|
|
#ifdef HAVE_SIGINFO_H
|
|
|
|
#include <siginfo.h>
|
|
|
|
#endif
|
|
|
|
#ifdef HAVE_SYS_IOCTL_H
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#endif
|
2017-02-14 12:37:27 +08:00
|
|
|
|
2019-02-15 07:53:07 +08:00
|
|
|
#ifdef __linux__
|
|
|
|
// Includes for WSL detection
|
|
|
|
#include <sys/utsname.h>
|
|
|
|
#endif
|
|
|
|
|
2018-10-10 11:33:20 +08:00
|
|
|
#ifdef __FreeBSD__
|
|
|
|
#include <sys/sysctl.h>
|
2018-10-10 13:29:41 +08:00
|
|
|
#elif __APPLE__
|
|
|
|
#include <mach-o/dyld.h>
|
2018-10-10 11:33:20 +08:00
|
|
|
#endif
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
#include <algorithm>
|
2018-12-31 09:25:50 +08:00
|
|
|
#include <atomic>
|
2019-02-04 08:06:10 +08:00
|
|
|
#include <locale>
|
2016-04-28 07:10:14 +08:00
|
|
|
#include <memory> // IWYU pragma: keep
|
2017-02-11 10:47:02 +08:00
|
|
|
#include <type_traits>
|
2007-01-20 10:36:49 +08:00
|
|
|
|
2005-09-20 21:26:39 +08:00
|
|
|
#include "common.h"
|
2017-01-19 05:54:54 +08:00
|
|
|
#include "env.h"
|
2005-09-20 21:26:39 +08:00
|
|
|
#include "expand.h"
|
2016-04-28 07:10:14 +08:00
|
|
|
#include "fallback.h" // IWYU pragma: keep
|
2019-05-28 06:56:53 +08:00
|
|
|
#include "flog.h"
|
2018-04-25 06:53:30 +08:00
|
|
|
#include "future_feature_flags.h"
|
2019-04-29 06:00:36 +08:00
|
|
|
#include "global_safety.h"
|
2019-05-30 03:33:44 +08:00
|
|
|
#include "iothread.h"
|
2017-01-19 05:54:54 +08:00
|
|
|
#include "proc.h"
|
2019-05-26 10:19:03 +08:00
|
|
|
#include "signal.h"
|
2005-09-20 21:26:39 +08:00
|
|
|
#include "wildcard.h"
|
2016-04-28 07:10:14 +08:00
|
|
|
#include "wutil.h" // IWYU pragma: keep
|
2005-09-20 21:26:39 +08:00
|
|
|
|
2017-01-27 09:18:38 +08:00
|
|
|
constexpr wint_t NOT_A_WCHAR = static_cast<wint_t>(WEOF);
|
2012-03-02 16:27:40 +08:00
|
|
|
|
2012-11-19 08:30:30 +08:00
|
|
|
struct termios shell_modes;
|
2005-09-20 21:26:39 +08:00
|
|
|
|
2018-12-31 09:39:59 +08:00
|
|
|
/// This allows us to notice when we've forked.
|
2019-04-29 06:00:36 +08:00
|
|
|
static relaxed_atomic_bool_t is_forked_proc{false};
|
2018-12-31 09:39:59 +08:00
|
|
|
/// This allows us to bypass the main thread checks
|
2019-04-29 06:00:36 +08:00
|
|
|
static relaxed_atomic_bool_t thread_asserts_cfg_for_testing{false};
|
|
|
|
|
|
|
|
static relaxed_atomic_t<wchar_t> ellipsis_char;
|
|
|
|
wchar_t get_ellipsis_char() { return ellipsis_char; }
|
|
|
|
|
|
|
|
static relaxed_atomic_t<const wchar_t *> ellipsis_str;
|
|
|
|
const wchar_t *get_ellipsis_str() { return ellipsis_str; }
|
|
|
|
|
|
|
|
static relaxed_atomic_t<const wchar_t *> omitted_newline_str;
|
|
|
|
const wchar_t *get_omitted_newline_str() { return omitted_newline_str; }
|
|
|
|
|
|
|
|
static relaxed_atomic_t<int> omitted_newline_width;
|
|
|
|
int get_omitted_newline_width() { return omitted_newline_width; }
|
|
|
|
|
|
|
|
static relaxed_atomic_t<wchar_t> obfuscation_read_char;
|
|
|
|
wchar_t get_obfuscation_read_char() { return obfuscation_read_char; }
|
2012-01-06 05:58:48 +08:00
|
|
|
|
2014-02-10 06:04:43 +08:00
|
|
|
bool g_profiling_active = false;
|
2018-11-28 22:08:24 +08:00
|
|
|
const wchar_t *program_name;
|
2019-05-05 06:28:20 +08:00
|
|
|
std::atomic<int> debug_level{1}; // default maximum debug output level (errors and warnings)
|
2019-04-29 06:00:36 +08:00
|
|
|
|
|
|
|
static relaxed_atomic_t<int> debug_stack_frames{0};
|
|
|
|
void set_debug_stack_frames(int v) { debug_stack_frames = v; }
|
|
|
|
int get_debug_stack_frames() { return debug_stack_frames; }
|
2016-05-16 10:45:02 +08:00
|
|
|
|
|
|
|
/// Be able to restore the term's foreground process group.
|
2018-09-29 12:20:50 +08:00
|
|
|
/// This is set during startup and not modified after.
|
2019-04-29 06:56:49 +08:00
|
|
|
static relaxed_atomic_t<pid_t> initial_fg_process_group{-1};
|
2005-09-25 03:31:17 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
/// This struct maintains the current state of the terminal size. It is updated on demand after
|
2019-04-29 07:56:48 +08:00
|
|
|
/// receiving a SIGWINCH. Use common_get_width()/common_get_height() to read it lazily.
|
|
|
|
static constexpr struct winsize k_invalid_termsize = {USHRT_MAX, USHRT_MAX, USHRT_MAX, USHRT_MAX};
|
2019-05-13 03:33:13 +08:00
|
|
|
static owning_lock<struct winsize> s_termsize{k_invalid_termsize};
|
2019-04-29 07:56:48 +08:00
|
|
|
|
|
|
|
static relaxed_atomic_bool_t s_termsize_valid{false};
|
2005-10-14 19:40:33 +08:00
|
|
|
|
2012-12-13 07:44:01 +08:00
|
|
|
static char *wcs2str_internal(const wchar_t *in, char *out);
|
2016-05-20 10:27:22 +08:00
|
|
|
static void debug_shared(const wchar_t msg_level, const wcstring &msg);
|
2007-01-20 10:36:49 +08:00
|
|
|
|
2018-09-29 12:20:50 +08:00
|
|
|
#if defined(OS_IS_CYGWIN) || defined(WSL)
|
|
|
|
// MS Windows tty devices do not currently have either a read or write timestamp. Those
|
|
|
|
// respective fields of `struct stat` are always the current time. Which means we can't
|
|
|
|
// use them. So we assume no external program has written to the terminal behind our
|
|
|
|
// back. This makes multiline promptusable. See issue #2859 and
|
|
|
|
// https://github.com/Microsoft/BashOnWindows/issues/545
|
|
|
|
const bool has_working_tty_timestamps = false;
|
|
|
|
#else
|
|
|
|
const bool has_working_tty_timestamps = true;
|
|
|
|
#endif
|
2016-06-18 04:08:25 +08:00
|
|
|
|
2017-06-23 11:47:54 +08:00
|
|
|
/// Convert a character to its integer equivalent if it is a valid character for the requested base.
|
|
|
|
/// Return the integer value if it is valid else -1.
|
|
|
|
long convert_digit(wchar_t d, int base) {
|
|
|
|
long res = -1;
|
|
|
|
if ((d <= L'9') && (d >= L'0')) {
|
|
|
|
res = d - L'0';
|
|
|
|
} else if ((d <= L'z') && (d >= L'a')) {
|
|
|
|
res = d + 10 - L'a';
|
|
|
|
} else if ((d <= L'Z') && (d >= L'A')) {
|
|
|
|
res = d + 10 - L'A';
|
|
|
|
}
|
|
|
|
if (res >= base) {
|
|
|
|
res = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Test whether the char is a valid hex digit as used by the `escape_string_*()` functions.
|
2019-03-13 06:07:07 +08:00
|
|
|
static bool is_hex_digit(int c) { return std::strchr("0123456789ABCDEF", c) != NULL; }
|
2017-06-23 11:47:54 +08:00
|
|
|
|
|
|
|
/// This is a specialization of `convert_digit()` that only handles base 16 and only uppercase.
|
|
|
|
long convert_hex_digit(wchar_t d) {
|
|
|
|
if ((d <= L'9') && (d >= L'0')) {
|
|
|
|
return d - L'0';
|
|
|
|
} else if ((d <= L'Z') && (d >= L'A')) {
|
|
|
|
return 10 + d - L'A';
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2019-02-15 07:53:07 +08:00
|
|
|
bool is_windows_subsystem_for_linux() {
|
|
|
|
#if defined(WSL)
|
|
|
|
return true;
|
|
|
|
#elif not defined(__linux__)
|
|
|
|
return false;
|
2019-02-15 08:30:10 +08:00
|
|
|
#else
|
2019-02-15 07:53:07 +08:00
|
|
|
// We are purposely not using std::call_once as it may invoke locking, which is an unnecessary
|
|
|
|
// overhead since there's no actual race condition here - even if multiple threads call this
|
|
|
|
// routine simultaneously the first time around, we just end up needlessly querying uname(2) one
|
|
|
|
// more time.
|
|
|
|
|
|
|
|
static bool wsl_state = []() {
|
|
|
|
utsname info;
|
|
|
|
uname(&info);
|
|
|
|
|
|
|
|
// Sample utsname.release under WSL: 4.4.0-17763-Microsoft
|
2019-03-13 06:07:07 +08:00
|
|
|
if (std::strstr(info.release, "Microsoft") != nullptr) {
|
|
|
|
const char *dash = std::strchr(info.release, '-');
|
2019-02-15 08:17:58 +08:00
|
|
|
if (dash == nullptr || strtod(dash + 1, nullptr) < 17763) {
|
2019-05-05 18:09:25 +08:00
|
|
|
debug(1,
|
|
|
|
"This version of WSL is not supported and fish will probably not work "
|
|
|
|
"correctly!\n"
|
|
|
|
"Please upgrade to Windows 10 1809 (17763) or higher to use fish!");
|
2019-02-15 08:17:58 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
2019-02-15 07:53:07 +08:00
|
|
|
}();
|
|
|
|
|
|
|
|
// Subsequent calls to this function may take place after fork() and before exec() in
|
|
|
|
// postfork.cpp. Make sure we never dynamically allocate any memory in the fast path!
|
|
|
|
return wsl_state;
|
2019-02-15 08:30:10 +08:00
|
|
|
#endif
|
2019-02-15 07:53:07 +08:00
|
|
|
}
|
|
|
|
|
2016-05-20 10:27:22 +08:00
|
|
|
#ifdef HAVE_BACKTRACE_SYMBOLS
|
2016-05-16 10:45:02 +08:00
|
|
|
// This function produces a stack backtrace with demangled function & method names. It is based on
|
|
|
|
// https://gist.github.com/fmela/591333 but adapted to the style of the fish project.
|
2019-05-05 18:09:25 +08:00
|
|
|
[[gnu::noinline]] static const wcstring_list_t demangled_backtrace(int max_frames,
|
|
|
|
int skip_levels) {
|
2016-05-16 10:45:02 +08:00
|
|
|
void *callstack[128];
|
|
|
|
const int n_max_frames = sizeof(callstack) / sizeof(callstack[0]);
|
|
|
|
int n_frames = backtrace(callstack, n_max_frames);
|
|
|
|
char **symbols = backtrace_symbols(callstack, n_frames);
|
|
|
|
wchar_t text[1024];
|
2019-03-15 02:15:50 +08:00
|
|
|
wcstring_list_t backtrace_text;
|
2016-05-16 10:45:02 +08:00
|
|
|
|
|
|
|
if (skip_levels + max_frames < n_frames) n_frames = skip_levels + max_frames;
|
|
|
|
|
|
|
|
for (int i = skip_levels; i < n_frames; i++) {
|
|
|
|
Dl_info info;
|
|
|
|
if (dladdr(callstack[i], &info) && info.dli_sname) {
|
|
|
|
char *demangled = NULL;
|
|
|
|
int status = -1;
|
|
|
|
if (info.dli_sname[0] == '_')
|
|
|
|
demangled = abi::__cxa_demangle(info.dli_sname, NULL, 0, &status);
|
|
|
|
swprintf(text, sizeof(text) / sizeof(wchar_t), L"%-3d %s + %td", i - skip_levels,
|
|
|
|
status == 0 ? demangled : info.dli_sname == 0 ? symbols[i] : info.dli_sname,
|
|
|
|
(char *)callstack[i] - (char *)info.dli_saddr);
|
|
|
|
free(demangled);
|
|
|
|
} else {
|
|
|
|
swprintf(text, sizeof(text) / sizeof(wchar_t), L"%-3d %s", i - skip_levels, symbols[i]);
|
|
|
|
}
|
|
|
|
backtrace_text.push_back(text);
|
|
|
|
}
|
|
|
|
free(symbols);
|
|
|
|
return backtrace_text;
|
|
|
|
}
|
|
|
|
|
2019-02-10 20:20:01 +08:00
|
|
|
[[gnu::noinline]] void show_stackframe(const wchar_t msg_level, int frame_count, int skip_levels) {
|
2016-10-21 09:53:31 +08:00
|
|
|
if (frame_count < 1) return;
|
2013-01-13 04:55:23 +08:00
|
|
|
|
2019-03-15 01:52:26 +08:00
|
|
|
wcstring_list_t bt = demangled_backtrace(frame_count, skip_levels + 2);
|
|
|
|
debug_shared(msg_level, L"Backtrace:\n" + join_strings(bt, L'\n') + L'\n');
|
2007-01-20 10:36:49 +08:00
|
|
|
}
|
|
|
|
|
2016-05-23 10:00:13 +08:00
|
|
|
#else // HAVE_BACKTRACE_SYMBOLS
|
2016-05-20 10:27:22 +08:00
|
|
|
|
2019-02-10 20:20:01 +08:00
|
|
|
[[gnu::noinline]] void show_stackframe(const wchar_t msg_level, int, int) {
|
2016-05-20 10:27:22 +08:00
|
|
|
debug_shared(msg_level, L"Sorry, but your system does not support backtraces");
|
|
|
|
}
|
2016-05-23 10:00:13 +08:00
|
|
|
#endif // HAVE_BACKTRACE_SYMBOLS
|
2016-05-20 10:27:22 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
int fgetws2(wcstring *s, FILE *f) {
|
|
|
|
int i = 0;
|
2012-11-19 08:30:30 +08:00
|
|
|
wint_t c;
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
while (1) {
|
|
|
|
errno = 0;
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2019-03-13 05:06:01 +08:00
|
|
|
c = std::fgetwc(f);
|
2016-04-28 07:10:14 +08:00
|
|
|
if (errno == EILSEQ || errno == EINTR) {
|
2012-11-19 08:30:30 +08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
switch (c) {
|
|
|
|
// End of line.
|
2012-11-19 16:31:03 +08:00
|
|
|
case WEOF:
|
|
|
|
case L'\n':
|
2016-04-28 07:10:14 +08:00
|
|
|
case L'\0': {
|
2012-11-19 16:31:03 +08:00
|
|
|
return i;
|
2016-04-28 07:10:14 +08:00
|
|
|
}
|
|
|
|
// Ignore carriage returns.
|
|
|
|
case L'\r': {
|
2012-11-19 16:31:03 +08:00
|
|
|
break;
|
2016-04-28 07:10:14 +08:00
|
|
|
}
|
|
|
|
default: {
|
2012-11-19 16:31:03 +08:00
|
|
|
i++;
|
|
|
|
s->push_back((wchar_t)c);
|
|
|
|
break;
|
2016-04-28 07:10:14 +08:00
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
/// Converts the narrow character string \c in into its wide equivalent, and return it.
|
|
|
|
///
|
|
|
|
/// The string may contain embedded nulls.
|
|
|
|
///
|
|
|
|
/// This function encodes illegal character sequences in a reversible way using the private use
|
|
|
|
/// area.
|
|
|
|
static wcstring str2wcs_internal(const char *in, const size_t in_len) {
|
|
|
|
if (in_len == 0) return wcstring();
|
2012-12-21 04:25:35 +08:00
|
|
|
assert(in != NULL);
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2012-12-21 04:25:35 +08:00
|
|
|
wcstring result;
|
|
|
|
result.reserve(in_len);
|
|
|
|
size_t in_pos = 0;
|
2016-03-11 10:17:39 +08:00
|
|
|
|
2016-05-23 10:00:13 +08:00
|
|
|
if (MB_CUR_MAX == 1) {
|
|
|
|
// Single-byte locale, all values are legal.
|
2016-04-28 07:10:14 +08:00
|
|
|
while (in_pos < in_len) {
|
2016-03-11 10:17:39 +08:00
|
|
|
result.push_back((unsigned char)in[in_pos]);
|
|
|
|
in_pos++;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
mbstate_t state = {};
|
2016-04-28 07:10:14 +08:00
|
|
|
while (in_pos < in_len) {
|
2016-05-19 08:46:13 +08:00
|
|
|
bool use_encode_direct = false;
|
2016-07-30 18:08:57 +08:00
|
|
|
size_t ret = 0;
|
2012-12-21 04:25:35 +08:00
|
|
|
wchar_t wc = 0;
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-05-19 08:46:13 +08:00
|
|
|
if ((in[in_pos] & 0xF8) == 0xF8) {
|
2019-03-13 05:06:01 +08:00
|
|
|
// Protect against broken std::mbrtowc() implementations which attempt to encode UTF-8
|
2016-05-19 08:46:13 +08:00
|
|
|
// sequences longer than four bytes (e.g., OS X Snow Leopard).
|
2012-12-21 04:25:35 +08:00
|
|
|
use_encode_direct = true;
|
2016-10-30 10:01:19 +08:00
|
|
|
} else if (sizeof(wchar_t) == 2 && //!OCLINT(constant if expression)
|
|
|
|
(in[in_pos] & 0xF8) == 0xF0) {
|
2016-05-23 10:00:13 +08:00
|
|
|
// Assume we are in a UTF-16 environment (e.g., Cygwin) using a UTF-8 encoding.
|
|
|
|
// The bits set check will be true for a four byte UTF-8 sequence that requires
|
2019-03-13 05:06:01 +08:00
|
|
|
// two UTF-16 chars. Something that doesn't work with our simple use of std::mbrtowc().
|
2016-05-23 10:00:13 +08:00
|
|
|
use_encode_direct = true;
|
2016-05-19 08:46:13 +08:00
|
|
|
} else {
|
2019-03-13 05:06:01 +08:00
|
|
|
ret = std::mbrtowc(&wc, &in[in_pos], in_len - in_pos, &state);
|
2016-05-23 10:00:13 +08:00
|
|
|
// Determine whether to encode this character with our crazy scheme.
|
2016-05-19 08:46:13 +08:00
|
|
|
if (wc >= ENCODE_DIRECT_BASE && wc < ENCODE_DIRECT_BASE + 256) {
|
|
|
|
use_encode_direct = true;
|
|
|
|
} else if (wc == INTERNAL_SEPARATOR) {
|
|
|
|
use_encode_direct = true;
|
|
|
|
} else if (ret == (size_t)-2) {
|
|
|
|
// Incomplete sequence.
|
|
|
|
use_encode_direct = true;
|
|
|
|
} else if (ret == (size_t)-1) {
|
|
|
|
// Invalid data.
|
|
|
|
use_encode_direct = true;
|
|
|
|
} else if (ret > in_len - in_pos) {
|
|
|
|
// Other error codes? Terrifying, should never happen.
|
|
|
|
use_encode_direct = true;
|
2016-10-30 10:01:19 +08:00
|
|
|
} else if (sizeof(wchar_t) == 2 && wc >= 0xD800 && //!OCLINT(constant if expression)
|
|
|
|
wc <= 0xDFFF) {
|
2016-05-23 10:00:13 +08:00
|
|
|
// If we get a surrogate pair char on a UTF-16 system (e.g., Cygwin) then
|
|
|
|
// it's guaranteed the UTF-8 decoding is wrong so use direct encoding.
|
|
|
|
use_encode_direct = true;
|
2016-05-19 08:46:13 +08:00
|
|
|
}
|
2012-12-21 04:25:35 +08:00
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
if (use_encode_direct) {
|
2012-12-21 04:25:35 +08:00
|
|
|
wc = ENCODE_DIRECT_BASE + (unsigned char)in[in_pos];
|
|
|
|
result.push_back(wc);
|
2012-11-19 08:30:30 +08:00
|
|
|
in_pos++;
|
2019-03-13 06:07:07 +08:00
|
|
|
std::memset(&state, 0, sizeof state);
|
2016-10-30 10:01:19 +08:00
|
|
|
} else if (ret == 0) { // embedded null byte!
|
2012-12-21 04:25:35 +08:00
|
|
|
result.push_back(L'\0');
|
|
|
|
in_pos++;
|
2019-03-13 06:07:07 +08:00
|
|
|
std::memset(&state, 0, sizeof state);
|
2016-10-30 10:01:19 +08:00
|
|
|
} else { // normal case
|
2012-12-21 04:25:35 +08:00
|
|
|
result.push_back(wc);
|
|
|
|
in_pos += ret;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
}
|
2016-10-30 10:01:19 +08:00
|
|
|
|
2012-12-21 04:25:35 +08:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
wcstring str2wcstring(const char *in, size_t len) { return str2wcs_internal(in, len); }
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2019-03-13 06:07:07 +08:00
|
|
|
wcstring str2wcstring(const char *in) { return str2wcs_internal(in, std::strlen(in)); }
|
2012-12-21 04:25:35 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
wcstring str2wcstring(const std::string &in) {
|
2016-05-19 08:46:13 +08:00
|
|
|
// Handles embedded nulls!
|
2012-12-21 04:25:35 +08:00
|
|
|
return str2wcs_internal(in.data(), in.size());
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
|
2017-12-19 18:05:20 +08:00
|
|
|
wcstring str2wcstring(const std::string &in, size_t len) {
|
|
|
|
// Handles embedded nulls!
|
|
|
|
return str2wcs_internal(in.data(), len);
|
|
|
|
}
|
|
|
|
|
2018-10-21 01:24:42 +08:00
|
|
|
char *wcs2str(const wchar_t *in, size_t len) {
|
2016-04-28 07:10:14 +08:00
|
|
|
if (!in) return NULL;
|
2018-10-21 01:24:42 +08:00
|
|
|
size_t desired_size = MAX_UTF8_BYTES * len + 1;
|
2012-03-09 15:21:07 +08:00
|
|
|
char local_buff[512];
|
2016-04-28 07:10:14 +08:00
|
|
|
if (desired_size <= sizeof local_buff / sizeof *local_buff) {
|
|
|
|
// Convert into local buff, then use strdup() so we don't waste malloc'd space.
|
2012-03-09 15:21:07 +08:00
|
|
|
char *result = wcs2str_internal(in, local_buff);
|
2016-04-28 07:10:14 +08:00
|
|
|
if (result) {
|
|
|
|
// It converted into the local buffer, so copy it.
|
2012-03-09 15:21:07 +08:00
|
|
|
result = strdup(result);
|
2017-02-14 12:37:27 +08:00
|
|
|
assert(result);
|
2012-03-09 15:21:07 +08:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2016-05-05 06:19:47 +08:00
|
|
|
|
|
|
|
// Here we probably allocate a buffer probably much larger than necessary.
|
2018-10-21 01:24:42 +08:00
|
|
|
char *out = (char *)malloc(MAX_UTF8_BYTES * len + 1);
|
2017-02-14 12:37:27 +08:00
|
|
|
assert(out);
|
2018-09-28 10:28:39 +08:00
|
|
|
// Instead of returning the return value of wcs2str_internal, return `out` directly.
|
|
|
|
// This eliminates false warnings in coverity about resource leaks.
|
2018-02-09 06:59:38 +08:00
|
|
|
wcs2str_internal(in, out);
|
|
|
|
return out;
|
2006-02-08 22:58:47 +08:00
|
|
|
}
|
|
|
|
|
2019-03-13 05:06:01 +08:00
|
|
|
char *wcs2str(const wchar_t *in) { return wcs2str(in, std::wcslen(in)); }
|
2018-10-21 01:24:42 +08:00
|
|
|
char *wcs2str(const wcstring &in) { return wcs2str(in.c_str(), in.length()); }
|
2013-01-13 06:18:34 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
/// This function is distinguished from wcs2str_internal in that it allows embedded null bytes.
|
|
|
|
std::string wcs2string(const wcstring &input) {
|
2012-12-13 07:44:01 +08:00
|
|
|
std::string result;
|
|
|
|
result.reserve(input.size());
|
2012-12-20 05:31:06 +08:00
|
|
|
|
2016-03-11 10:17:39 +08:00
|
|
|
mbstate_t state = {};
|
2016-11-06 13:03:20 +08:00
|
|
|
char converted[MB_LEN_MAX];
|
2012-12-20 05:31:06 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
for (size_t i = 0; i < input.size(); i++) {
|
2012-12-13 07:44:01 +08:00
|
|
|
wchar_t wc = input[i];
|
2016-04-28 07:10:14 +08:00
|
|
|
if (wc == INTERNAL_SEPARATOR) {
|
2016-11-02 11:42:02 +08:00
|
|
|
; // do nothing
|
2016-04-28 07:10:14 +08:00
|
|
|
} else if (wc >= ENCODE_DIRECT_BASE && wc < ENCODE_DIRECT_BASE + 256) {
|
2012-12-13 07:44:01 +08:00
|
|
|
result.push_back(wc - ENCODE_DIRECT_BASE);
|
2016-11-02 11:42:02 +08:00
|
|
|
} else if (MB_CUR_MAX == 1) { // single-byte locale (C/POSIX/ISO-8859)
|
2016-03-11 10:17:39 +08:00
|
|
|
// If `wc` contains a wide character we emit a question-mark.
|
2016-04-28 07:10:14 +08:00
|
|
|
if (wc & ~0xFF) {
|
2016-03-11 10:17:39 +08:00
|
|
|
wc = '?';
|
|
|
|
}
|
|
|
|
converted[0] = wc;
|
|
|
|
result.append(converted, 1);
|
2016-04-28 07:10:14 +08:00
|
|
|
} else {
|
2019-03-13 06:07:07 +08:00
|
|
|
std::memset(converted, 0, sizeof converted);
|
2019-03-13 05:06:01 +08:00
|
|
|
size_t len = std::wcrtomb(converted, wc, &state);
|
2016-11-06 13:03:20 +08:00
|
|
|
if (len == (size_t)-1) {
|
2019-06-19 18:34:16 +08:00
|
|
|
FLOGF(char_encoding, L"Wide character U+%4X has no narrow representation", wc);
|
2019-03-13 06:07:07 +08:00
|
|
|
std::memset(&state, 0, sizeof(state));
|
2016-04-28 07:10:14 +08:00
|
|
|
} else {
|
2012-12-13 07:44:01 +08:00
|
|
|
result.append(converted, len);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-12-20 05:31:06 +08:00
|
|
|
|
2011-12-27 11:18:46 +08:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
/// Converts the wide character string \c in into it's narrow equivalent, stored in \c out. \c out
|
|
|
|
/// must have enough space to fit the entire string.
|
|
|
|
///
|
|
|
|
/// This function decodes illegal character sequences in a reversible way using the private use
|
|
|
|
/// area.
|
|
|
|
static char *wcs2str_internal(const wchar_t *in, char *out) {
|
2019-05-28 08:24:19 +08:00
|
|
|
assert(in && out && "in and out must not be null");
|
2016-03-11 10:17:39 +08:00
|
|
|
size_t in_pos = 0;
|
|
|
|
size_t out_pos = 0;
|
|
|
|
mbstate_t state = {};
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
while (in[in_pos]) {
|
|
|
|
if (in[in_pos] == INTERNAL_SEPARATOR) {
|
2016-11-02 11:42:02 +08:00
|
|
|
; // do nothing
|
2016-04-28 07:10:14 +08:00
|
|
|
} else if (in[in_pos] >= ENCODE_DIRECT_BASE && in[in_pos] < ENCODE_DIRECT_BASE + 256) {
|
|
|
|
out[out_pos++] = in[in_pos] - ENCODE_DIRECT_BASE;
|
|
|
|
} else if (MB_CUR_MAX == 1) // single-byte locale (C/POSIX/ISO-8859)
|
2016-03-11 10:17:39 +08:00
|
|
|
{
|
|
|
|
// If `wc` contains a wide character we emit a question-mark.
|
2016-04-28 07:10:14 +08:00
|
|
|
if (in[in_pos] & ~0xFF) {
|
2016-03-11 10:17:39 +08:00
|
|
|
out[out_pos++] = '?';
|
2016-04-28 07:10:14 +08:00
|
|
|
} else {
|
2016-03-11 10:17:39 +08:00
|
|
|
out[out_pos++] = (unsigned char)in[in_pos];
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
} else {
|
2019-03-13 05:06:01 +08:00
|
|
|
size_t len = std::wcrtomb(&out[out_pos], in[in_pos], &state);
|
2016-04-28 07:10:14 +08:00
|
|
|
if (len == (size_t)-1) {
|
2019-06-19 18:34:16 +08:00
|
|
|
FLOGF(char_encoding, L"Wide character U+%4X has no narrow representation", in[in_pos]);
|
2019-03-13 06:07:07 +08:00
|
|
|
std::memset(&state, 0, sizeof(state));
|
2016-04-28 07:10:14 +08:00
|
|
|
} else {
|
2016-03-11 10:17:39 +08:00
|
|
|
out_pos += len;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
in_pos++;
|
|
|
|
}
|
|
|
|
out[out_pos] = 0;
|
|
|
|
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
2016-11-06 13:03:20 +08:00
|
|
|
/// Test if the character can be encoded using the current locale.
|
|
|
|
static bool can_be_encoded(wchar_t wc) {
|
|
|
|
char converted[MB_LEN_MAX];
|
|
|
|
mbstate_t state = {};
|
|
|
|
|
2019-03-13 05:06:01 +08:00
|
|
|
return std::wcrtomb(converted, wc, &state) != (size_t)-1;
|
2016-11-06 13:03:20 +08:00
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
wcstring format_string(const wchar_t *format, ...) {
|
2012-11-19 08:30:30 +08:00
|
|
|
va_list va;
|
|
|
|
va_start(va, format);
|
2012-02-10 10:43:36 +08:00
|
|
|
wcstring result = vformat_string(format, va);
|
2012-11-19 08:30:30 +08:00
|
|
|
va_end(va);
|
2012-02-10 10:43:36 +08:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
void append_formatv(wcstring &target, const wchar_t *format, va_list va_orig) {
|
2012-03-04 11:12:06 +08:00
|
|
|
const int saved_err = errno;
|
2016-04-28 07:10:14 +08:00
|
|
|
// As far as I know, there is no way to check if a vswprintf-call failed because of a badly
|
|
|
|
// formated string option or because the supplied destination string was to small. In GLIBC,
|
|
|
|
// errno seems to be set to EINVAL either way.
|
|
|
|
//
|
|
|
|
// Because of this, on failiure we try to increase the buffer size until the free space is
|
|
|
|
// larger than max_size, at which point it will conclude that the error was probably due to a
|
|
|
|
// badly formated string option, and return an error. Make sure to null terminate string before
|
|
|
|
// that, though.
|
|
|
|
const size_t max_size = (128 * 1024 * 1024);
|
2012-03-04 11:12:06 +08:00
|
|
|
wchar_t static_buff[256];
|
|
|
|
size_t size = 0;
|
|
|
|
wchar_t *buff = NULL;
|
|
|
|
int status = -1;
|
2016-04-28 07:10:14 +08:00
|
|
|
while (status < 0) {
|
|
|
|
// Reallocate if necessary.
|
|
|
|
if (size == 0) {
|
2012-03-04 11:12:06 +08:00
|
|
|
buff = static_buff;
|
|
|
|
size = sizeof static_buff;
|
2016-04-28 07:10:14 +08:00
|
|
|
} else {
|
2012-03-04 11:12:06 +08:00
|
|
|
size *= 2;
|
2016-04-28 07:10:14 +08:00
|
|
|
if (size >= max_size) {
|
2012-03-04 11:12:06 +08:00
|
|
|
buff[0] = '\0';
|
|
|
|
break;
|
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
buff = (wchar_t *)realloc((buff == static_buff ? NULL : buff), size);
|
2017-02-14 12:37:27 +08:00
|
|
|
assert(buff != NULL);
|
2012-03-04 11:12:06 +08:00
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
// Try printing.
|
2012-11-19 08:30:30 +08:00
|
|
|
va_list va;
|
|
|
|
va_copy(va, va_orig);
|
2019-03-13 05:06:01 +08:00
|
|
|
status = std::vswprintf(buff, size / sizeof(wchar_t), format, va);
|
2012-11-19 08:30:30 +08:00
|
|
|
va_end(va);
|
2012-03-04 11:12:06 +08:00
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2014-05-01 07:29:52 +08:00
|
|
|
target.append(buff);
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
if (buff != static_buff) {
|
2012-03-04 11:12:06 +08:00
|
|
|
free(buff);
|
2014-05-01 07:29:52 +08:00
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2012-03-04 11:12:06 +08:00
|
|
|
errno = saved_err;
|
2011-12-27 11:18:46 +08:00
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
wcstring vformat_string(const wchar_t *format, va_list va_orig) {
|
2014-05-01 07:29:52 +08:00
|
|
|
wcstring result;
|
|
|
|
append_formatv(result, format, va_orig);
|
|
|
|
return result;
|
2013-03-25 06:24:29 +08:00
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
void append_format(wcstring &str, const wchar_t *format, ...) {
|
2012-11-19 08:30:30 +08:00
|
|
|
va_list va;
|
|
|
|
va_start(va, format);
|
2013-03-25 06:24:29 +08:00
|
|
|
append_formatv(str, format, va);
|
2012-11-19 08:30:30 +08:00
|
|
|
va_end(va);
|
2012-02-23 02:51:06 +08:00
|
|
|
}
|
2012-02-10 10:43:36 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
wchar_t *quote_end(const wchar_t *pos) {
|
2012-11-19 08:30:30 +08:00
|
|
|
wchar_t c = *pos;
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
while (1) {
|
2012-11-19 08:30:30 +08:00
|
|
|
pos++;
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
if (!*pos) return 0;
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
if (*pos == L'\\') {
|
2012-11-19 08:30:30 +08:00
|
|
|
pos++;
|
2016-04-28 07:10:14 +08:00
|
|
|
if (!*pos) return 0;
|
|
|
|
} else {
|
|
|
|
if (*pos == c) {
|
2012-11-19 08:30:30 +08:00
|
|
|
return (wchar_t *)pos;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-06-04 10:05:13 +08:00
|
|
|
void fish_setlocale() {
|
2017-09-21 13:00:14 +08:00
|
|
|
// Use various Unicode symbols if they can be encoded using the current locale, else a simple
|
|
|
|
// ASCII char alternative. All of the can_be_encoded() invocations should return the same
|
|
|
|
// true/false value since the code points are in the BMP but we're going to be paranoid. This
|
|
|
|
// is also technically wrong if we're not in a Unicode locale but we expect (or hope)
|
|
|
|
// can_be_encoded() will return false in that case.
|
2018-03-10 04:40:35 +08:00
|
|
|
if (can_be_encoded(L'\u2026')) {
|
|
|
|
ellipsis_char = L'\u2026';
|
|
|
|
ellipsis_str = L"\u2026";
|
2018-09-28 10:28:39 +08:00
|
|
|
} else {
|
|
|
|
ellipsis_char = L'$'; // "horizontal ellipsis"
|
2018-03-10 04:40:35 +08:00
|
|
|
ellipsis_str = L"...";
|
|
|
|
}
|
2019-01-31 04:31:11 +08:00
|
|
|
|
2018-03-29 03:27:25 +08:00
|
|
|
if (is_windows_subsystem_for_linux()) {
|
2018-09-28 10:28:39 +08:00
|
|
|
// neither of \u23CE and \u25CF can be displayed in the default fonts on Windows, though
|
|
|
|
// they can be *encoded* just fine. Use alternative glyphs.
|
2019-05-05 18:09:25 +08:00
|
|
|
omitted_newline_str = L"\u00b6"; // "pilcrow"
|
2019-01-31 05:42:59 +08:00
|
|
|
omitted_newline_width = 1;
|
2019-05-05 18:09:25 +08:00
|
|
|
obfuscation_read_char = L'\u2022'; // "bullet"
|
2019-01-31 04:31:11 +08:00
|
|
|
} else if (is_console_session()) {
|
2019-01-31 05:42:59 +08:00
|
|
|
omitted_newline_str = L"^J";
|
|
|
|
omitted_newline_width = 2;
|
2019-01-31 04:31:11 +08:00
|
|
|
obfuscation_read_char = L'*';
|
2018-09-28 10:28:39 +08:00
|
|
|
} else {
|
2019-01-31 05:42:59 +08:00
|
|
|
if (can_be_encoded(L'\u23CE')) {
|
|
|
|
omitted_newline_str = L"\u23CE";
|
|
|
|
omitted_newline_width = 1;
|
|
|
|
} else {
|
|
|
|
omitted_newline_str = L"^J";
|
|
|
|
omitted_newline_width = 2;
|
|
|
|
}
|
2018-03-29 03:27:25 +08:00
|
|
|
obfuscation_read_char = can_be_encoded(L'\u25CF') ? L'\u25CF' : L'#'; // "black circle"
|
|
|
|
}
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
long read_blocked(int fd, void *buf, size_t count) {
|
2016-12-29 10:52:33 +08:00
|
|
|
long bytes_read = 0;
|
|
|
|
|
|
|
|
while (count) {
|
|
|
|
ssize_t res = read(fd, (char *)buf + bytes_read, count);
|
|
|
|
if (res == 0) {
|
|
|
|
break;
|
|
|
|
} else if (res == -1) {
|
|
|
|
if (errno == EINTR) continue;
|
|
|
|
if (errno == EAGAIN) return bytes_read ? bytes_read : -1;
|
|
|
|
return -1;
|
|
|
|
} else {
|
|
|
|
bytes_read += res;
|
|
|
|
count -= res;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return bytes_read;
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
|
|
|
|
2017-01-07 15:51:49 +08:00
|
|
|
/// Loop a write request while failure is non-critical. Return -1 and set errno in case of critical
|
|
|
|
/// error.
|
2016-04-28 07:10:14 +08:00
|
|
|
ssize_t write_loop(int fd, const char *buff, size_t count) {
|
|
|
|
size_t out_cum = 0;
|
|
|
|
while (out_cum < count) {
|
2013-01-05 14:32:40 +08:00
|
|
|
ssize_t out = write(fd, &buff[out_cum], count - out_cum);
|
2016-04-28 07:10:14 +08:00
|
|
|
if (out < 0) {
|
|
|
|
if (errno != EAGAIN && errno != EINTR) {
|
2012-11-19 08:30:30 +08:00
|
|
|
return -1;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
} else {
|
2012-11-19 08:30:30 +08:00
|
|
|
out_cum += (size_t)out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return (ssize_t)out_cum;
|
2009-02-23 04:28:52 +08:00
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
ssize_t read_loop(int fd, void *buff, size_t count) {
|
2012-03-01 09:55:28 +08:00
|
|
|
ssize_t result;
|
2016-04-28 07:10:14 +08:00
|
|
|
do {
|
2012-03-01 09:55:28 +08:00
|
|
|
result = read(fd, buff, count);
|
2016-04-28 07:10:14 +08:00
|
|
|
} while (result < 0 && (errno == EAGAIN || errno == EINTR));
|
2012-03-01 09:55:28 +08:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-02-15 13:09:15 +08:00
|
|
|
/// Hack to not print error messages in the tests. Do not call this from functions in this module
|
|
|
|
/// like `debug()`. It is only intended to supress diagnostic noise from testing things like the
|
|
|
|
/// fish parser where we expect a lot of diagnostic messages due to testing error conditions.
|
2016-12-04 05:27:50 +08:00
|
|
|
bool should_suppress_stderr_for_tests() {
|
2019-03-13 05:06:01 +08:00
|
|
|
return program_name && !std::wcscmp(program_name, TESTS_PROGRAM_NAME);
|
2016-12-04 05:27:50 +08:00
|
|
|
}
|
|
|
|
|
2016-05-16 10:45:02 +08:00
|
|
|
static void debug_shared(const wchar_t level, const wcstring &msg) {
|
2018-12-31 09:41:17 +08:00
|
|
|
pid_t current_pid;
|
|
|
|
if (!is_forked_child()) {
|
2019-03-13 05:06:01 +08:00
|
|
|
std::fwprintf(stderr, L"<%lc> %ls: %ls\n", (unsigned long)level, program_name, msg.c_str());
|
2016-05-16 10:45:02 +08:00
|
|
|
} else {
|
2018-12-31 09:41:17 +08:00
|
|
|
current_pid = getpid();
|
2019-05-05 18:09:25 +08:00
|
|
|
std::fwprintf(stderr, L"<%lc> %ls: %d: %ls\n", (unsigned long)level, program_name,
|
|
|
|
current_pid, msg.c_str());
|
2016-05-16 10:45:02 +08:00
|
|
|
}
|
2012-07-18 03:47:01 +08:00
|
|
|
}
|
2006-05-31 23:40:28 +08:00
|
|
|
|
2018-09-29 12:20:50 +08:00
|
|
|
static const wchar_t level_char[] = {L'E', L'W', L'2', L'3', L'4', L'5'};
|
2019-02-10 20:20:01 +08:00
|
|
|
[[gnu::noinline]] void debug_impl(int level, const wchar_t *msg, ...) {
|
2012-07-18 03:47:01 +08:00
|
|
|
int errno_old = errno;
|
|
|
|
va_list va;
|
2012-11-19 08:30:30 +08:00
|
|
|
va_start(va, msg);
|
2012-07-18 03:47:01 +08:00
|
|
|
wcstring local_msg = vformat_string(msg, va);
|
2012-11-19 08:30:30 +08:00
|
|
|
va_end(va);
|
2016-05-16 10:45:02 +08:00
|
|
|
const wchar_t msg_level = level <= 5 ? level_char[level] : L'9';
|
|
|
|
debug_shared(msg_level, local_msg);
|
|
|
|
if (debug_stack_frames > 0) {
|
|
|
|
show_stackframe(msg_level, debug_stack_frames, 1);
|
|
|
|
}
|
2012-07-18 03:47:01 +08:00
|
|
|
errno = errno_old;
|
|
|
|
}
|
|
|
|
|
2019-02-10 20:20:01 +08:00
|
|
|
[[gnu::noinline]] void debug_impl(int level, const char *msg, ...) {
|
2016-04-28 07:10:14 +08:00
|
|
|
if (!should_debug(level)) return;
|
2012-07-18 03:47:01 +08:00
|
|
|
int errno_old = errno;
|
|
|
|
char local_msg[512];
|
|
|
|
va_list va;
|
2012-11-19 08:30:30 +08:00
|
|
|
va_start(va, msg);
|
2012-07-18 03:47:01 +08:00
|
|
|
vsnprintf(local_msg, sizeof local_msg, msg, va);
|
2012-11-19 08:30:30 +08:00
|
|
|
va_end(va);
|
2016-05-16 10:45:02 +08:00
|
|
|
const wchar_t msg_level = level <= 5 ? level_char[level] : L'9';
|
|
|
|
debug_shared(msg_level, str2wcstring(local_msg));
|
|
|
|
if (debug_stack_frames > 0) {
|
|
|
|
show_stackframe(msg_level, debug_stack_frames, 1);
|
|
|
|
}
|
2012-07-18 03:47:01 +08:00
|
|
|
errno = errno_old;
|
2012-02-23 02:51:06 +08:00
|
|
|
}
|
2006-01-15 19:58:05 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
void debug_safe(int level, const char *msg, const char *param1, const char *param2,
|
|
|
|
const char *param3, const char *param4, const char *param5, const char *param6,
|
|
|
|
const char *param7, const char *param8, const char *param9, const char *param10,
|
|
|
|
const char *param11, const char *param12) {
|
|
|
|
const char *const params[] = {param1, param2, param3, param4, param5, param6,
|
|
|
|
param7, param8, param9, param10, param11, param12};
|
|
|
|
if (!msg) return;
|
2012-07-18 03:47:01 +08:00
|
|
|
|
2017-01-14 12:34:15 +08:00
|
|
|
// Can't call fwprintf, that may allocate memory Just call write() over and over.
|
2016-04-28 07:10:14 +08:00
|
|
|
if (level > debug_level) return;
|
2012-03-01 03:27:14 +08:00
|
|
|
int errno_old = errno;
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2012-03-01 03:27:14 +08:00
|
|
|
size_t param_idx = 0;
|
|
|
|
const char *cursor = msg;
|
2016-04-28 07:10:14 +08:00
|
|
|
while (*cursor != '\0') {
|
2019-03-13 06:07:07 +08:00
|
|
|
const char *end = std::strchr(cursor, '%');
|
|
|
|
if (end == NULL) end = cursor + std::strlen(cursor);
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2017-08-12 22:54:26 +08:00
|
|
|
ignore_result(write(STDERR_FILENO, cursor, end - cursor));
|
2012-03-01 03:27:14 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
if (end[0] == '%' && end[1] == 's') {
|
|
|
|
// Handle a format string.
|
2012-03-09 15:21:07 +08:00
|
|
|
assert(param_idx < sizeof params / sizeof *params);
|
|
|
|
const char *format = params[param_idx++];
|
2016-04-28 07:10:14 +08:00
|
|
|
if (!format) format = "(null)";
|
2019-03-13 06:07:07 +08:00
|
|
|
ignore_result(write(STDERR_FILENO, format, std::strlen(format)));
|
2012-03-01 03:27:14 +08:00
|
|
|
cursor = end + 2;
|
2016-04-28 07:10:14 +08:00
|
|
|
} else if (end[0] == '\0') {
|
|
|
|
// Must be at the end of the string.
|
2012-03-01 03:27:14 +08:00
|
|
|
cursor = end;
|
2016-04-28 07:10:14 +08:00
|
|
|
} else {
|
|
|
|
// Some other format specifier, just skip it.
|
2012-03-01 03:27:14 +08:00
|
|
|
cursor = end + 1;
|
|
|
|
}
|
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
// We always append a newline.
|
2017-08-12 22:54:26 +08:00
|
|
|
ignore_result(write(STDERR_FILENO, "\n", 1));
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2012-03-01 03:27:14 +08:00
|
|
|
errno = errno_old;
|
|
|
|
}
|
|
|
|
|
2019-02-04 08:06:10 +08:00
|
|
|
// Careful to not negate LLONG_MIN.
|
|
|
|
static unsigned long long absolute_value(long long x) {
|
|
|
|
if (x >= 0) return static_cast<unsigned long long>(x);
|
|
|
|
x = -(x + 1);
|
|
|
|
return static_cast<unsigned long long>(x) + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename CharT>
|
|
|
|
void format_safe_impl(CharT *buff, size_t size, unsigned long long val) {
|
|
|
|
size_t idx = 0;
|
2016-04-28 07:10:14 +08:00
|
|
|
if (val == 0) {
|
2019-02-04 08:06:10 +08:00
|
|
|
buff[idx++] = '0';
|
2016-04-28 07:10:14 +08:00
|
|
|
} else {
|
2019-02-04 08:06:10 +08:00
|
|
|
// Generate the string backwards, then reverse it.
|
2016-04-28 07:10:14 +08:00
|
|
|
while (val != 0) {
|
2019-02-04 08:06:10 +08:00
|
|
|
buff[idx++] = (val % 10) + '0';
|
2012-03-01 03:27:14 +08:00
|
|
|
val /= 10;
|
|
|
|
}
|
2019-02-04 08:06:10 +08:00
|
|
|
std::reverse(buff, buff + idx);
|
2012-03-01 03:27:14 +08:00
|
|
|
}
|
2019-02-04 08:06:10 +08:00
|
|
|
buff[idx++] = '\0';
|
|
|
|
assert(idx <= size && "Buffer overflowed");
|
2012-03-01 03:27:14 +08:00
|
|
|
}
|
|
|
|
|
2019-02-04 08:06:10 +08:00
|
|
|
void format_long_safe(char buff[64], long val) {
|
|
|
|
unsigned long long uval = absolute_value(val);
|
|
|
|
if (val >= 0) {
|
|
|
|
format_safe_impl(buff, 64, uval);
|
2016-04-28 07:10:14 +08:00
|
|
|
} else {
|
2019-02-04 08:06:10 +08:00
|
|
|
buff[0] = '-';
|
|
|
|
format_safe_impl(buff + 1, 63, uval);
|
|
|
|
}
|
|
|
|
}
|
2012-03-04 07:20:30 +08:00
|
|
|
|
2019-02-04 08:06:10 +08:00
|
|
|
void format_long_safe(wchar_t buff[64], long val) {
|
|
|
|
unsigned long long uval = absolute_value(val);
|
|
|
|
if (val >= 0) {
|
|
|
|
format_safe_impl(buff, 64, uval);
|
|
|
|
} else {
|
|
|
|
buff[0] = '-';
|
|
|
|
format_safe_impl(buff + 1, 63, uval);
|
2012-03-04 07:20:30 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-04 08:06:10 +08:00
|
|
|
void format_ullong_safe(wchar_t buff[64], unsigned long long val) {
|
|
|
|
return format_safe_impl(buff, 64, val);
|
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
void narrow_string_safe(char buff[64], const wchar_t *s) {
|
2016-02-28 17:38:28 +08:00
|
|
|
size_t idx = 0;
|
2016-04-28 07:10:14 +08:00
|
|
|
for (size_t widx = 0; s[widx] != L'\0'; widx++) {
|
2016-02-28 17:38:28 +08:00
|
|
|
wchar_t c = s[widx];
|
2016-04-28 07:10:14 +08:00
|
|
|
if (c <= 127) {
|
2016-02-28 17:38:28 +08:00
|
|
|
buff[idx++] = char(c);
|
2016-04-28 07:10:14 +08:00
|
|
|
if (idx + 1 == 64) {
|
2016-02-28 17:38:28 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
buff[idx] = '\0';
|
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
wcstring reformat_for_screen(const wcstring &msg) {
|
2015-09-22 02:24:49 +08:00
|
|
|
wcstring buff;
|
2012-11-19 08:30:30 +08:00
|
|
|
int line_width = 0;
|
|
|
|
int screen_width = common_get_width();
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
if (screen_width) {
|
2014-01-13 05:33:35 +08:00
|
|
|
const wchar_t *start = msg.c_str();
|
|
|
|
const wchar_t *pos = start;
|
2016-04-28 07:10:14 +08:00
|
|
|
while (1) {
|
2012-11-19 08:30:30 +08:00
|
|
|
int overflow = 0;
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
int tok_width = 0;
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
// Tokenize on whitespace, and also calculate the width of the token.
|
2019-03-13 05:06:01 +08:00
|
|
|
while (*pos && (!std::wcschr(L" \n\r\t", *pos))) {
|
2016-04-28 07:10:14 +08:00
|
|
|
// Check is token is wider than one line. If so we mark it as an overflow and break
|
|
|
|
// the token.
|
|
|
|
if ((tok_width + fish_wcwidth(*pos)) > (screen_width - 1)) {
|
2012-11-19 08:30:30 +08:00
|
|
|
overflow = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
tok_width += fish_wcwidth(*pos);
|
|
|
|
pos++;
|
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
// If token is zero character long, we don't do anything.
|
|
|
|
if (pos == start) {
|
2016-06-15 10:21:50 +08:00
|
|
|
pos = pos + 1;
|
2016-04-28 07:10:14 +08:00
|
|
|
} else if (overflow) {
|
|
|
|
// In case of overflow, we print a newline, except if we already are at position 0.
|
|
|
|
wchar_t *token = wcsndup(start, pos - start);
|
|
|
|
if (line_width != 0) buff.push_back(L'\n');
|
2012-02-23 02:51:06 +08:00
|
|
|
buff.append(format_string(L"%ls-\n", token));
|
2012-11-19 08:30:30 +08:00
|
|
|
free(token);
|
2016-04-28 07:10:14 +08:00
|
|
|
line_width = 0;
|
|
|
|
} else {
|
|
|
|
// Print the token.
|
|
|
|
wchar_t *token = wcsndup(start, pos - start);
|
|
|
|
if ((line_width + (line_width != 0 ? 1 : 0) + tok_width) > screen_width) {
|
2012-02-23 02:51:06 +08:00
|
|
|
buff.push_back(L'\n');
|
2016-04-28 07:10:14 +08:00
|
|
|
line_width = 0;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
buff.append(format_string(L"%ls%ls", line_width ? L" " : L"", token));
|
2012-11-19 08:30:30 +08:00
|
|
|
free(token);
|
2016-04-28 07:10:14 +08:00
|
|
|
line_width += (line_width != 0 ? 1 : 0) + tok_width;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
// Break on end of string.
|
|
|
|
if (!*pos) {
|
2012-11-19 08:30:30 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
start = pos;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
} else {
|
2012-02-23 02:51:06 +08:00
|
|
|
buff.append(msg);
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-02-23 02:51:06 +08:00
|
|
|
buff.push_back(L'\n');
|
2015-09-22 02:24:49 +08:00
|
|
|
return buff;
|
2006-01-15 19:58:05 +08:00
|
|
|
}
|
|
|
|
|
2017-06-21 12:55:16 +08:00
|
|
|
/// Escape a string in a fashion suitable for using as a URL. Store the result in out_str.
|
2018-01-08 17:52:29 +08:00
|
|
|
static void escape_string_url(const wcstring &in, wcstring &out) {
|
2018-11-07 19:37:47 +08:00
|
|
|
const std::string narrow = wcs2string(in);
|
|
|
|
for (auto &c1 : narrow) {
|
2017-06-21 12:55:16 +08:00
|
|
|
// This silliness is so we get the correct result whether chars are signed or unsigned.
|
|
|
|
unsigned int c2 = (unsigned int)c1 & 0xFF;
|
|
|
|
if (!(c2 & 0x80) &&
|
|
|
|
(isalnum(c2) || c2 == '/' || c2 == '.' || c2 == '~' || c2 == '-' || c2 == '_')) {
|
|
|
|
// The above characters don't need to be encoded.
|
|
|
|
out.push_back((wchar_t)c2);
|
|
|
|
} else {
|
|
|
|
// All other chars need to have their UTF-8 representation encoded in hex.
|
|
|
|
wchar_t buf[4];
|
|
|
|
swprintf(buf, sizeof buf / sizeof buf[0], L"%%%02X", c2);
|
|
|
|
out.append(buf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2005-10-14 19:40:33 +08:00
|
|
|
|
2017-06-23 11:47:54 +08:00
|
|
|
/// Reverse the effects of `escape_string_url()`. By definition the string has consist of just ASCII
|
|
|
|
/// chars.
|
|
|
|
static bool unescape_string_url(const wchar_t *in, wcstring *out) {
|
|
|
|
std::string result;
|
|
|
|
result.reserve(out->size());
|
|
|
|
for (wchar_t c = *in; c; c = *++in) {
|
|
|
|
if (c > 0x7F) return false; // invalid character means we can't decode the string
|
|
|
|
if (c == '%') {
|
|
|
|
int c1 = in[1];
|
|
|
|
if (c1 == 0) return false; // found unexpected end of string
|
|
|
|
if (c1 == '%') {
|
|
|
|
result.push_back('%');
|
|
|
|
in++;
|
|
|
|
} else {
|
|
|
|
int c2 = in[2];
|
|
|
|
if (c2 == 0) return false; // string ended prematurely
|
|
|
|
long d1 = convert_digit(c1, 16);
|
|
|
|
if (d1 < 0) return false;
|
|
|
|
long d2 = convert_digit(c2, 16);
|
|
|
|
if (d2 < 0) return false;
|
|
|
|
result.push_back(16 * d1 + d2);
|
|
|
|
in += 2;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
result.push_back(c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
*out = str2wcstring(result);
|
|
|
|
return true;
|
|
|
|
}
|
2017-06-21 12:55:16 +08:00
|
|
|
|
|
|
|
/// Escape a string in a fashion suitable for using as a fish var name. Store the result in out_str.
|
2018-01-08 17:52:29 +08:00
|
|
|
static void escape_string_var(const wcstring &in, wcstring &out) {
|
2017-06-21 12:55:16 +08:00
|
|
|
bool prev_was_hex_encoded = false;
|
2018-11-07 19:37:47 +08:00
|
|
|
const std::string narrow = wcs2string(in);
|
|
|
|
for (auto c1 : narrow) {
|
|
|
|
// This silliness is so we get the correct result whether chars are signed or unsigned.
|
|
|
|
unsigned int c2 = (unsigned int)c1 & 0xFF;
|
|
|
|
if (!(c2 & 0x80) && isalnum(c2) && (!prev_was_hex_encoded || !is_hex_digit(c2))) {
|
2017-06-21 12:55:16 +08:00
|
|
|
// ASCII alphanumerics don't need to be encoded.
|
|
|
|
if (prev_was_hex_encoded) {
|
|
|
|
out.push_back(L'_');
|
|
|
|
prev_was_hex_encoded = false;
|
|
|
|
}
|
2018-11-07 19:37:47 +08:00
|
|
|
out.push_back((wchar_t)c2);
|
|
|
|
} else if (c2 == '_') {
|
2017-06-21 12:55:16 +08:00
|
|
|
// Underscores are encoded by doubling them.
|
|
|
|
out.append(L"__");
|
|
|
|
prev_was_hex_encoded = false;
|
|
|
|
} else {
|
|
|
|
// All other chars need to have their UTF-8 representation encoded in hex.
|
|
|
|
wchar_t buf[4];
|
2018-11-07 19:37:47 +08:00
|
|
|
swprintf(buf, sizeof buf / sizeof buf[0], L"_%02X", c2);
|
2017-06-21 12:55:16 +08:00
|
|
|
out.append(buf);
|
|
|
|
prev_was_hex_encoded = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (prev_was_hex_encoded) {
|
|
|
|
out.push_back(L'_');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-23 11:47:54 +08:00
|
|
|
/// Reverse the effects of `escape_string_var()`. By definition the string has consist of just ASCII
|
|
|
|
/// chars.
|
|
|
|
static bool unescape_string_var(const wchar_t *in, wcstring *out) {
|
|
|
|
std::string result;
|
|
|
|
result.reserve(out->size());
|
|
|
|
bool prev_was_hex_encoded = false;
|
|
|
|
for (wchar_t c = *in; c; c = *++in) {
|
|
|
|
if (c > 0x7F) return false; // invalid character means we can't decode the string
|
|
|
|
if (c == '_') {
|
|
|
|
int c1 = in[1];
|
|
|
|
if (c1 == 0) {
|
|
|
|
if (prev_was_hex_encoded) break;
|
|
|
|
return false; // found unexpected escape char at end of string
|
|
|
|
}
|
|
|
|
if (c1 == '_') {
|
|
|
|
result.push_back('_');
|
|
|
|
in++;
|
|
|
|
} else if (is_hex_digit(c1)) {
|
|
|
|
int c2 = in[2];
|
|
|
|
if (c2 == 0) return false; // string ended prematurely
|
|
|
|
long d1 = convert_hex_digit(c1);
|
|
|
|
if (d1 < 0) return false;
|
|
|
|
long d2 = convert_hex_digit(c2);
|
|
|
|
if (d2 < 0) return false;
|
|
|
|
result.push_back(16 * d1 + d2);
|
|
|
|
in += 2;
|
|
|
|
prev_was_hex_encoded = true;
|
|
|
|
}
|
|
|
|
// No "else" clause because if the first char after an underscore is not another
|
|
|
|
// underscore or a valid hex character then the underscore is there to improve
|
|
|
|
// readability after we've encoded a character not valid in a var name.
|
|
|
|
} else {
|
|
|
|
result.push_back(c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
*out = str2wcstring(result);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-06-21 12:55:16 +08:00
|
|
|
/// Escape a string in a fashion suitable for using in fish script. Store the result in out_str.
|
|
|
|
static void escape_string_script(const wchar_t *orig_in, size_t in_len, wcstring &out,
|
|
|
|
escape_flags_t flags) {
|
2014-01-09 07:06:09 +08:00
|
|
|
const wchar_t *in = orig_in;
|
2018-04-25 06:53:30 +08:00
|
|
|
const bool escape_all = static_cast<bool>(flags & ESCAPE_ALL);
|
|
|
|
const bool no_quoted = static_cast<bool>(flags & ESCAPE_NO_QUOTED);
|
|
|
|
const bool no_tilde = static_cast<bool>(flags & ESCAPE_NO_TILDE);
|
2018-05-06 10:44:57 +08:00
|
|
|
const bool no_caret = feature_test(features_t::stderr_nocaret);
|
|
|
|
const bool no_qmark = feature_test(features_t::qmark_noglob);
|
2005-10-14 19:40:33 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
int need_escape = 0;
|
|
|
|
int need_complex_escape = 0;
|
2005-10-14 19:40:33 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
if (!no_quoted && in_len == 0) {
|
2014-01-09 07:06:09 +08:00
|
|
|
out.assign(L"''");
|
|
|
|
return;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-05-09 17:33:42 +08:00
|
|
|
|
2018-01-02 22:59:08 +08:00
|
|
|
for (size_t i = 0; i < in_len; i++) {
|
2016-04-28 07:10:14 +08:00
|
|
|
if ((*in >= ENCODE_DIRECT_BASE) && (*in < ENCODE_DIRECT_BASE + 256)) {
|
2012-11-19 08:30:30 +08:00
|
|
|
int val = *in - ENCODE_DIRECT_BASE;
|
|
|
|
int tmp;
|
2012-05-14 11:49:14 +08:00
|
|
|
|
2014-01-09 07:06:09 +08:00
|
|
|
out += L'\\';
|
|
|
|
out += L'X';
|
2012-02-27 12:11:34 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
tmp = val / 16;
|
|
|
|
out += tmp > 9 ? L'a' + (tmp - 10) : L'0' + tmp;
|
2006-10-19 19:50:23 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
tmp = val % 16;
|
|
|
|
out += tmp > 9 ? L'a' + (tmp - 10) : L'0' + tmp;
|
|
|
|
need_escape = need_complex_escape = 1;
|
2006-11-17 22:58:25 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
} else {
|
2012-11-19 08:30:30 +08:00
|
|
|
wchar_t c = *in;
|
2016-04-28 07:10:14 +08:00
|
|
|
switch (c) {
|
|
|
|
case L'\t': {
|
2014-01-09 07:06:09 +08:00
|
|
|
out += L'\\';
|
|
|
|
out += L't';
|
2016-04-28 07:10:14 +08:00
|
|
|
need_escape = need_complex_escape = 1;
|
2012-11-19 16:31:03 +08:00
|
|
|
break;
|
2016-04-28 07:10:14 +08:00
|
|
|
}
|
|
|
|
case L'\n': {
|
2014-01-09 07:06:09 +08:00
|
|
|
out += L'\\';
|
|
|
|
out += L'n';
|
2016-04-28 07:10:14 +08:00
|
|
|
need_escape = need_complex_escape = 1;
|
2012-11-19 16:31:03 +08:00
|
|
|
break;
|
2016-04-28 07:10:14 +08:00
|
|
|
}
|
|
|
|
case L'\b': {
|
2014-01-09 07:06:09 +08:00
|
|
|
out += L'\\';
|
|
|
|
out += L'b';
|
2016-04-28 07:10:14 +08:00
|
|
|
need_escape = need_complex_escape = 1;
|
2012-11-19 16:31:03 +08:00
|
|
|
break;
|
2016-04-28 07:10:14 +08:00
|
|
|
}
|
|
|
|
case L'\r': {
|
2014-01-09 07:06:09 +08:00
|
|
|
out += L'\\';
|
|
|
|
out += L'r';
|
2016-04-28 07:10:14 +08:00
|
|
|
need_escape = need_complex_escape = 1;
|
2012-11-19 16:31:03 +08:00
|
|
|
break;
|
2016-04-28 07:10:14 +08:00
|
|
|
}
|
2018-06-18 13:01:32 +08:00
|
|
|
case L'\x1B': {
|
2014-01-09 07:06:09 +08:00
|
|
|
out += L'\\';
|
|
|
|
out += L'e';
|
2016-04-28 07:10:14 +08:00
|
|
|
need_escape = need_complex_escape = 1;
|
2012-11-19 16:31:03 +08:00
|
|
|
break;
|
2016-04-28 07:10:14 +08:00
|
|
|
}
|
2012-11-19 16:31:03 +08:00
|
|
|
case L'\\':
|
2016-04-28 07:10:14 +08:00
|
|
|
case L'\'': {
|
|
|
|
need_escape = need_complex_escape = 1;
|
2017-03-14 11:19:08 +08:00
|
|
|
out += L'\\';
|
2014-01-09 07:06:09 +08:00
|
|
|
out += *in;
|
2012-11-19 16:31:03 +08:00
|
|
|
break;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2018-05-06 10:11:57 +08:00
|
|
|
case ANY_CHAR: {
|
|
|
|
// See #1614
|
|
|
|
out += L'?';
|
|
|
|
break;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
case ANY_STRING: {
|
2014-08-17 10:25:36 +08:00
|
|
|
out += L'*';
|
|
|
|
break;
|
2016-04-28 07:10:14 +08:00
|
|
|
}
|
|
|
|
case ANY_STRING_RECURSIVE: {
|
2014-08-17 10:25:36 +08:00
|
|
|
out += L"**";
|
|
|
|
break;
|
2016-04-28 07:10:14 +08:00
|
|
|
}
|
2018-04-25 06:53:30 +08:00
|
|
|
|
2012-11-19 16:31:03 +08:00
|
|
|
case L'&':
|
|
|
|
case L'$':
|
|
|
|
case L' ':
|
|
|
|
case L'#':
|
2018-04-25 06:53:30 +08:00
|
|
|
case L'^':
|
2012-11-19 16:31:03 +08:00
|
|
|
case L'<':
|
|
|
|
case L'>':
|
|
|
|
case L'(':
|
|
|
|
case L')':
|
|
|
|
case L'[':
|
|
|
|
case L']':
|
|
|
|
case L'{':
|
|
|
|
case L'}':
|
2018-05-06 10:11:57 +08:00
|
|
|
case L'?':
|
2012-11-19 16:31:03 +08:00
|
|
|
case L'*':
|
|
|
|
case L'|':
|
|
|
|
case L';':
|
|
|
|
case L'"':
|
2018-10-11 05:26:29 +08:00
|
|
|
case L'%':
|
2016-04-28 07:10:14 +08:00
|
|
|
case L'~': {
|
2018-09-28 10:28:39 +08:00
|
|
|
bool char_is_normal = (c == L'~' && no_tilde) || (c == L'^' && no_caret) ||
|
|
|
|
(c == L'?' && no_qmark);
|
2018-04-25 06:53:30 +08:00
|
|
|
if (!char_is_normal) {
|
2016-04-28 07:10:14 +08:00
|
|
|
need_escape = 1;
|
|
|
|
if (escape_all) out += L'\\';
|
2012-11-19 16:31:03 +08:00
|
|
|
}
|
2014-01-09 07:06:09 +08:00
|
|
|
out += *in;
|
2012-11-19 16:31:03 +08:00
|
|
|
break;
|
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
default: {
|
|
|
|
if (*in < 32) {
|
|
|
|
if (*in < 27 && *in > 0) {
|
2014-01-09 07:06:09 +08:00
|
|
|
out += L'\\';
|
|
|
|
out += L'c';
|
2016-04-28 07:10:14 +08:00
|
|
|
out += L'a' + *in - 1;
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
need_escape = need_complex_escape = 1;
|
2012-11-19 16:31:03 +08:00
|
|
|
break;
|
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
int tmp = (*in) % 16;
|
2014-01-09 07:06:09 +08:00
|
|
|
out += L'\\';
|
|
|
|
out += L'x';
|
2016-04-28 07:10:14 +08:00
|
|
|
out += ((*in > 15) ? L'1' : L'0');
|
|
|
|
out += tmp > 9 ? L'a' + (tmp - 10) : L'0' + tmp;
|
|
|
|
need_escape = need_complex_escape = 1;
|
|
|
|
} else {
|
2014-01-09 07:06:09 +08:00
|
|
|
out += *in;
|
2012-11-19 16:31:03 +08:00
|
|
|
}
|
|
|
|
break;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
in++;
|
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
// Use quoted escaping if possible, since most people find it easier to read.
|
|
|
|
if (!no_quoted && need_escape && !need_complex_escape && escape_all) {
|
2014-01-09 07:06:09 +08:00
|
|
|
wchar_t single_quote = L'\'';
|
|
|
|
out.clear();
|
|
|
|
out.reserve(2 + in_len);
|
|
|
|
out.push_back(single_quote);
|
|
|
|
out.append(orig_in, in_len);
|
|
|
|
out.push_back(single_quote);
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2014-01-09 07:06:09 +08:00
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2018-11-15 13:30:11 +08:00
|
|
|
/// Escapes a string for use in a regex string. Not safe for use with `eval` as only
|
|
|
|
/// characters reserved by PCRE2 are escaped, i.e. it relies on fish's automatic escaping
|
|
|
|
/// of subshell output in subsequent concatenation or for use as an argument.
|
|
|
|
/// \param in is the raw string to be searched for literally when substituted in a PCRE2 expression.
|
|
|
|
static wcstring escape_string_pcre2(const wcstring &in) {
|
|
|
|
wcstring out;
|
2019-05-05 18:09:25 +08:00
|
|
|
out.reserve(in.size() * 1.3); // a wild guess
|
2018-11-15 13:30:11 +08:00
|
|
|
|
|
|
|
for (auto c : in) {
|
|
|
|
switch (c) {
|
|
|
|
case L'.':
|
|
|
|
case L'^':
|
|
|
|
case L'$':
|
|
|
|
case L'*':
|
|
|
|
case L'+':
|
|
|
|
case L'(':
|
|
|
|
case L')':
|
|
|
|
case L'?':
|
|
|
|
case L'[':
|
|
|
|
case L'{':
|
|
|
|
case L'}':
|
|
|
|
case L'\\':
|
|
|
|
case L'|':
|
2019-05-05 18:09:25 +08:00
|
|
|
// these two only *need* to be escaped within a character class, and technically it
|
|
|
|
// makes no sense to ever use process substitution output to compose a character class,
|
|
|
|
// but...
|
2018-11-15 13:30:11 +08:00
|
|
|
case L'-':
|
|
|
|
case L']':
|
|
|
|
out.push_back('\\');
|
2018-12-12 01:19:47 +08:00
|
|
|
/* FALLTHROUGH */
|
2018-11-15 13:30:11 +08:00
|
|
|
default:
|
|
|
|
out.push_back(c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
2017-06-21 12:55:16 +08:00
|
|
|
wcstring escape_string(const wchar_t *in, escape_flags_t flags, escape_string_style_t style) {
|
2014-09-26 09:20:03 +08:00
|
|
|
wcstring result;
|
2017-06-21 12:55:16 +08:00
|
|
|
|
|
|
|
switch (style) {
|
|
|
|
case STRING_STYLE_SCRIPT: {
|
2019-03-13 05:06:01 +08:00
|
|
|
escape_string_script(in, std::wcslen(in), result, flags);
|
2017-06-21 12:55:16 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case STRING_STYLE_URL: {
|
|
|
|
escape_string_url(in, result);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case STRING_STYLE_VAR: {
|
|
|
|
escape_string_var(in, result);
|
|
|
|
break;
|
|
|
|
}
|
2018-11-17 10:21:05 +08:00
|
|
|
case STRING_STYLE_REGEX: {
|
2018-11-15 13:30:11 +08:00
|
|
|
result = escape_string_pcre2(in);
|
|
|
|
break;
|
|
|
|
}
|
2017-06-21 12:55:16 +08:00
|
|
|
}
|
|
|
|
|
2014-09-26 09:20:03 +08:00
|
|
|
return result;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
|
2017-06-21 12:55:16 +08:00
|
|
|
wcstring escape_string(const wcstring &in, escape_flags_t flags, escape_string_style_t style) {
|
2014-01-09 07:06:09 +08:00
|
|
|
wcstring result;
|
2017-06-21 12:55:16 +08:00
|
|
|
|
|
|
|
switch (style) {
|
|
|
|
case STRING_STYLE_SCRIPT: {
|
|
|
|
escape_string_script(in.c_str(), in.size(), result, flags);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case STRING_STYLE_URL: {
|
2018-01-03 19:29:01 +08:00
|
|
|
escape_string_url(in, result);
|
2017-06-21 12:55:16 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case STRING_STYLE_VAR: {
|
2018-01-03 19:29:01 +08:00
|
|
|
escape_string_var(in, result);
|
2017-06-21 12:55:16 +08:00
|
|
|
break;
|
|
|
|
}
|
2018-11-17 10:21:05 +08:00
|
|
|
case STRING_STYLE_REGEX: {
|
2018-11-15 13:30:11 +08:00
|
|
|
result = escape_string_pcre2(in);
|
|
|
|
break;
|
|
|
|
}
|
2017-06-21 12:55:16 +08:00
|
|
|
}
|
|
|
|
|
2012-11-19 08:30:30 +08:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-06-24 04:15:32 +08:00
|
|
|
wcstring debug_escape(const wcstring &in) {
|
|
|
|
wcstring result;
|
|
|
|
result.reserve(in.size());
|
|
|
|
for (wchar_t wc : in) {
|
|
|
|
uint32_t c = static_cast<uint32_t>(wc);
|
|
|
|
if (c <= 127 && isprint(c)) {
|
|
|
|
result.push_back(wc);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define TEST(x) \
|
|
|
|
case x: \
|
|
|
|
append_format(result, L"<%s>", #x); \
|
|
|
|
break;
|
|
|
|
switch (wc) {
|
|
|
|
TEST(HOME_DIRECTORY)
|
|
|
|
TEST(VARIABLE_EXPAND)
|
|
|
|
TEST(VARIABLE_EXPAND_SINGLE)
|
|
|
|
TEST(BRACE_BEGIN)
|
|
|
|
TEST(BRACE_END)
|
|
|
|
TEST(BRACE_SEP)
|
|
|
|
TEST(BRACE_SPACE)
|
|
|
|
TEST(INTERNAL_SEPARATOR)
|
|
|
|
TEST(VARIABLE_EXPAND_EMPTY)
|
|
|
|
TEST(EXPAND_SENTINAL)
|
|
|
|
TEST(ANY_CHAR)
|
|
|
|
TEST(ANY_STRING)
|
|
|
|
TEST(ANY_STRING_RECURSIVE)
|
|
|
|
TEST(ANY_SENTINAL)
|
|
|
|
default:
|
|
|
|
append_format(result, L"<\\x%02x>", c);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
/// Helper to return the last character in a string, or NOT_A_WCHAR.
|
|
|
|
static wint_t string_last_char(const wcstring &str) {
|
2017-01-27 09:18:38 +08:00
|
|
|
return str.empty() ? NOT_A_WCHAR : str.back();
|
2013-11-25 14:57:49 +08:00
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
/// Given a null terminated string starting with a backslash, read the escape as if it is unquoted,
|
|
|
|
/// appending to result. Return the number of characters consumed, or 0 on error.
|
2018-09-28 10:22:55 +08:00
|
|
|
maybe_t<size_t> read_unquoted_escape(const wchar_t *input, wcstring *result, bool allow_incomplete,
|
|
|
|
bool unescape_special) {
|
|
|
|
assert(input[0] == L'\\' && "Not an escape");
|
2013-11-25 14:57:49 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
// Here's the character we'll ultimately append, or NOT_A_WCHAR for none. Note that L'\0' is a
|
|
|
|
// valid thing to append.
|
2014-05-14 13:30:41 +08:00
|
|
|
wint_t result_char_or_none = NOT_A_WCHAR;
|
2013-11-25 14:57:49 +08:00
|
|
|
|
|
|
|
bool errored = false;
|
2016-04-28 07:10:14 +08:00
|
|
|
size_t in_pos = 1; // in_pos always tracks the next character to read (and therefore the number
|
|
|
|
// of characters read so far)
|
2013-11-25 14:57:49 +08:00
|
|
|
const wchar_t c = input[in_pos++];
|
2016-04-28 07:10:14 +08:00
|
|
|
switch (c) {
|
|
|
|
// A null character after a backslash is an error.
|
|
|
|
case L'\0': {
|
|
|
|
// Adjust in_pos to only include the backslash.
|
2013-11-25 14:57:49 +08:00
|
|
|
assert(in_pos > 0);
|
|
|
|
in_pos--;
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
// It's an error, unless we're allowing incomplete escapes.
|
|
|
|
if (!allow_incomplete) errored = true;
|
2013-11-25 14:57:49 +08:00
|
|
|
break;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
// Numeric escape sequences. No prefix means octal escape, otherwise hexadecimal.
|
2013-11-25 14:57:49 +08:00
|
|
|
case L'0':
|
|
|
|
case L'1':
|
|
|
|
case L'2':
|
|
|
|
case L'3':
|
|
|
|
case L'4':
|
|
|
|
case L'5':
|
|
|
|
case L'6':
|
|
|
|
case L'7':
|
|
|
|
case L'u':
|
|
|
|
case L'U':
|
|
|
|
case L'x':
|
2016-04-28 07:10:14 +08:00
|
|
|
case L'X': {
|
|
|
|
long long res = 0;
|
|
|
|
size_t chars = 2;
|
|
|
|
int base = 16;
|
2013-11-25 14:57:49 +08:00
|
|
|
bool byte_literal = false;
|
|
|
|
wchar_t max_val = ASCII_MAX;
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
switch (c) {
|
|
|
|
case L'u': {
|
|
|
|
chars = 4;
|
2013-11-25 14:57:49 +08:00
|
|
|
max_val = UCS2_MAX;
|
|
|
|
break;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
case L'U': {
|
|
|
|
chars = 8;
|
2013-11-25 14:57:49 +08:00
|
|
|
max_val = WCHAR_MAX;
|
2014-01-15 17:40:40 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
// Don't exceed the largest Unicode code point - see #1107.
|
|
|
|
if (0x10FFFF < max_val) max_val = (wchar_t)0x10FFFF;
|
2013-11-25 14:57:49 +08:00
|
|
|
break;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
case L'x': {
|
2013-11-25 14:57:49 +08:00
|
|
|
chars = 2;
|
|
|
|
max_val = ASCII_MAX;
|
|
|
|
break;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
case L'X': {
|
2013-11-25 14:57:49 +08:00
|
|
|
byte_literal = true;
|
|
|
|
max_val = BYTE_MAX;
|
|
|
|
break;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
default: {
|
|
|
|
base = 8;
|
|
|
|
chars = 3;
|
|
|
|
// Note that in_pos currently is just after the first post-backslash character;
|
|
|
|
// we want to start our escape from there.
|
2013-11-25 14:57:49 +08:00
|
|
|
assert(in_pos > 0);
|
|
|
|
in_pos--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
for (size_t i = 0; i < chars; i++) {
|
|
|
|
long d = convert_digit(input[in_pos], base);
|
|
|
|
if (d < 0) {
|
2013-11-25 14:57:49 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
res = (res * base) + d;
|
2013-11-25 14:57:49 +08:00
|
|
|
in_pos++;
|
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
if (res <= max_val) {
|
|
|
|
result_char_or_none = (wchar_t)((byte_literal ? ENCODE_DIRECT_BASE : 0) + res);
|
|
|
|
} else {
|
2013-11-25 14:57:49 +08:00
|
|
|
errored = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
// \a means bell (alert).
|
|
|
|
case L'a': {
|
2014-05-14 13:30:41 +08:00
|
|
|
result_char_or_none = L'\a';
|
2013-11-25 14:57:49 +08:00
|
|
|
break;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
// \b means backspace.
|
|
|
|
case L'b': {
|
2014-05-14 13:30:41 +08:00
|
|
|
result_char_or_none = L'\b';
|
2013-11-25 14:57:49 +08:00
|
|
|
break;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
// \cX means control sequence X.
|
|
|
|
case L'c': {
|
2013-11-25 14:57:49 +08:00
|
|
|
const wchar_t sequence_char = input[in_pos++];
|
2016-04-28 07:10:14 +08:00
|
|
|
if (sequence_char >= L'a' && sequence_char <= (L'a' + 32)) {
|
|
|
|
result_char_or_none = sequence_char - L'a' + 1;
|
|
|
|
} else if (sequence_char >= L'A' && sequence_char <= (L'A' + 32)) {
|
|
|
|
result_char_or_none = sequence_char - L'A' + 1;
|
|
|
|
} else {
|
2013-11-25 14:57:49 +08:00
|
|
|
errored = true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2018-06-18 13:01:32 +08:00
|
|
|
// \x1B means escape.
|
2016-04-28 07:10:14 +08:00
|
|
|
case L'e': {
|
2018-06-18 13:01:32 +08:00
|
|
|
result_char_or_none = L'\x1B';
|
2013-11-25 14:57:49 +08:00
|
|
|
break;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
// \f means form feed.
|
|
|
|
case L'f': {
|
2014-05-14 13:30:41 +08:00
|
|
|
result_char_or_none = L'\f';
|
2013-11-25 14:57:49 +08:00
|
|
|
break;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
// \n means newline.
|
|
|
|
case L'n': {
|
2014-05-14 13:30:41 +08:00
|
|
|
result_char_or_none = L'\n';
|
2013-11-25 14:57:49 +08:00
|
|
|
break;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
// \r means carriage return.
|
|
|
|
case L'r': {
|
2014-05-14 13:30:41 +08:00
|
|
|
result_char_or_none = L'\r';
|
2013-11-25 14:57:49 +08:00
|
|
|
break;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
// \t means tab.
|
|
|
|
case L't': {
|
2014-05-14 13:30:41 +08:00
|
|
|
result_char_or_none = L'\t';
|
2013-11-25 14:57:49 +08:00
|
|
|
break;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
// \v means vertical tab.
|
|
|
|
case L'v': {
|
2014-05-14 13:30:41 +08:00
|
|
|
result_char_or_none = L'\v';
|
2013-11-25 14:57:49 +08:00
|
|
|
break;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
// If a backslash is followed by an actual newline, swallow them both.
|
|
|
|
case L'\n': {
|
2014-05-14 13:30:41 +08:00
|
|
|
result_char_or_none = NOT_A_WCHAR;
|
2013-11-25 14:57:49 +08:00
|
|
|
break;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
default: {
|
|
|
|
if (unescape_special) result->push_back(INTERNAL_SEPARATOR);
|
2014-05-14 13:30:41 +08:00
|
|
|
result_char_or_none = c;
|
2013-11-25 14:57:49 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
if (!errored && result_char_or_none != NOT_A_WCHAR) {
|
2014-05-14 13:30:41 +08:00
|
|
|
wchar_t result_char = static_cast<wchar_t>(result_char_or_none);
|
2016-04-28 07:10:14 +08:00
|
|
|
// If result_char is not NOT_A_WCHAR, it must be a valid wchar.
|
2014-05-14 13:30:41 +08:00
|
|
|
assert((wint_t)result_char == result_char_or_none);
|
2013-11-25 14:57:49 +08:00
|
|
|
result->push_back(result_char);
|
|
|
|
}
|
2018-09-28 10:22:55 +08:00
|
|
|
if (errored) return none();
|
|
|
|
|
|
|
|
return in_pos;
|
2013-11-25 14:57:49 +08:00
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
/// Returns the unescaped version of input_str into output_str (by reference). Returns true if
|
|
|
|
/// successful. If false, the contents of output_str are undefined (!).
|
|
|
|
static bool unescape_string_internal(const wchar_t *const input, const size_t input_len,
|
|
|
|
wcstring *output_str, unescape_flags_t flags) {
|
|
|
|
// Set up result string, which we'll swap with the output on success.
|
2013-11-25 14:57:49 +08:00
|
|
|
wcstring result;
|
|
|
|
result.reserve(input_len);
|
|
|
|
|
2016-10-21 12:14:40 +08:00
|
|
|
const bool unescape_special = static_cast<bool>(flags & UNESCAPE_SPECIAL);
|
|
|
|
const bool allow_incomplete = static_cast<bool>(flags & UNESCAPE_INCOMPLETE);
|
2013-11-25 14:57:49 +08:00
|
|
|
|
2019-05-19 02:31:41 +08:00
|
|
|
// The positions of open braces.
|
|
|
|
std::vector<size_t> braces;
|
|
|
|
// The positions of variable expansions or brace ","s.
|
|
|
|
// We only read braces as expanders if there's a variable expansion or "," in them.
|
|
|
|
std::vector<size_t> vars_or_seps;
|
2018-03-12 11:02:43 +08:00
|
|
|
bool brace_text_start = false;
|
2018-03-12 08:51:54 +08:00
|
|
|
int brace_count = 0;
|
2013-11-25 14:57:49 +08:00
|
|
|
|
|
|
|
bool errored = false;
|
2018-09-28 10:28:39 +08:00
|
|
|
enum {
|
|
|
|
mode_unquoted,
|
|
|
|
mode_single_quotes,
|
|
|
|
mode_double_quotes,
|
|
|
|
} mode = mode_unquoted;
|
2013-11-25 14:57:49 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
for (size_t input_position = 0; input_position < input_len && !errored; input_position++) {
|
2013-11-25 14:57:49 +08:00
|
|
|
const wchar_t c = input[input_position];
|
2016-04-28 07:10:14 +08:00
|
|
|
// Here's the character we'll append to result, or NOT_A_WCHAR to suppress it.
|
2014-05-14 13:30:41 +08:00
|
|
|
wint_t to_append_or_none = c;
|
2016-04-28 07:10:14 +08:00
|
|
|
if (mode == mode_unquoted) {
|
|
|
|
switch (c) {
|
|
|
|
case L'\\': {
|
|
|
|
// Backslashes (escapes) are complicated and may result in errors, or appending
|
|
|
|
// INTERNAL_SEPARATORs, so we have to handle them specially.
|
2018-09-28 10:22:55 +08:00
|
|
|
auto escape_chars = read_unquoted_escape(input + input_position, &result,
|
|
|
|
allow_incomplete, unescape_special);
|
|
|
|
if (!escape_chars) {
|
|
|
|
// A none() return indicates an error.
|
2013-11-25 14:57:49 +08:00
|
|
|
errored = true;
|
2016-04-28 07:10:14 +08:00
|
|
|
} else {
|
|
|
|
// Skip over the characters we read, minus one because the outer loop will
|
|
|
|
// increment it.
|
2018-09-28 10:22:55 +08:00
|
|
|
assert(*escape_chars > 0);
|
|
|
|
input_position += *escape_chars - 1;
|
2013-11-25 14:57:49 +08:00
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
// We've already appended, don't append anything else.
|
2014-05-14 13:30:41 +08:00
|
|
|
to_append_or_none = NOT_A_WCHAR;
|
2013-11-25 14:57:49 +08:00
|
|
|
break;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
case L'~': {
|
|
|
|
if (unescape_special && (input_position == 0)) {
|
2014-05-14 13:30:41 +08:00
|
|
|
to_append_or_none = HOME_DIRECTORY;
|
2013-11-25 14:57:49 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2018-10-11 05:26:29 +08:00
|
|
|
case L'%': {
|
|
|
|
// Note that this only recognizes %self if the string is literally %self.
|
|
|
|
// %self/foo will NOT match this.
|
|
|
|
if (unescape_special && input_position == 0 &&
|
2019-03-13 05:06:01 +08:00
|
|
|
!std::wcscmp(input, PROCESS_EXPAND_SELF_STR)) {
|
2018-10-11 05:26:29 +08:00
|
|
|
to_append_or_none = PROCESS_EXPAND_SELF;
|
2019-03-15 06:12:14 +08:00
|
|
|
input_position += PROCESS_EXPAND_SELF_STR_LEN - 1; // skip over 'self's
|
2018-10-11 05:26:29 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
case L'*': {
|
|
|
|
if (unescape_special) {
|
|
|
|
// In general, this is ANY_STRING. But as a hack, if the last appended char
|
|
|
|
// is ANY_STRING, delete the last char and store ANY_STRING_RECURSIVE to
|
|
|
|
// reflect the fact that ** is the recursive wildcard.
|
|
|
|
if (string_last_char(result) == ANY_STRING) {
|
2013-11-25 14:57:49 +08:00
|
|
|
assert(result.size() > 0);
|
|
|
|
result.resize(result.size() - 1);
|
2014-05-14 13:30:41 +08:00
|
|
|
to_append_or_none = ANY_STRING_RECURSIVE;
|
2016-04-28 07:10:14 +08:00
|
|
|
} else {
|
2014-05-14 13:30:41 +08:00
|
|
|
to_append_or_none = ANY_STRING;
|
2013-11-25 14:57:49 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2018-05-06 10:11:57 +08:00
|
|
|
case L'?': {
|
2018-05-06 10:44:57 +08:00
|
|
|
if (unescape_special && !feature_test(features_t::qmark_noglob)) {
|
2018-05-06 10:11:57 +08:00
|
|
|
to_append_or_none = ANY_CHAR;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
case L'$': {
|
|
|
|
if (unescape_special) {
|
2014-05-14 13:30:41 +08:00
|
|
|
to_append_or_none = VARIABLE_EXPAND;
|
2019-05-19 02:31:41 +08:00
|
|
|
vars_or_seps.push_back(input_position);
|
2013-11-25 14:57:49 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
case L'{': {
|
|
|
|
if (unescape_special) {
|
2018-03-12 08:51:54 +08:00
|
|
|
brace_count++;
|
2018-03-11 03:16:07 +08:00
|
|
|
to_append_or_none = BRACE_BEGIN;
|
2019-05-28 10:47:13 +08:00
|
|
|
// We need to store where the brace *ends up* in the output because of
|
|
|
|
// NOT_A_WCHAR.
|
2019-05-19 02:31:41 +08:00
|
|
|
braces.push_back(result.size());
|
2013-11-25 14:57:49 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
case L'}': {
|
|
|
|
if (unescape_special) {
|
2018-05-02 22:33:28 +08:00
|
|
|
// HACK: The completion machinery sometimes hands us partial tokens.
|
|
|
|
// We can't parse them properly, but it shouldn't hurt,
|
|
|
|
// so we don't assert here.
|
|
|
|
// See #4954.
|
2018-09-28 10:28:39 +08:00
|
|
|
// assert(brace_count > 0 && "imbalanced brackets are a tokenizer error, we
|
|
|
|
// shouldn't be able to get here");
|
2018-03-12 08:51:54 +08:00
|
|
|
brace_count--;
|
2018-03-12 11:02:43 +08:00
|
|
|
brace_text_start = brace_text_start && brace_count > 0;
|
2018-03-11 03:16:07 +08:00
|
|
|
to_append_or_none = BRACE_END;
|
2019-05-19 02:31:41 +08:00
|
|
|
if (braces.size()) {
|
|
|
|
// If we didn't have a var or separator since the last '{',
|
|
|
|
// put the literal back.
|
|
|
|
if (!vars_or_seps.size() || vars_or_seps.back() < braces.back()) {
|
|
|
|
result[braces.back()] = L'{';
|
|
|
|
// We also need to turn all spaces back.
|
|
|
|
for (size_t i = braces.back() + 1; i < result.size(); i++) {
|
|
|
|
if (result[i] == BRACE_SPACE) result[i] = L' ';
|
|
|
|
}
|
|
|
|
to_append_or_none = L'}';
|
|
|
|
}
|
|
|
|
|
2019-05-28 10:47:13 +08:00
|
|
|
// Remove all seps inside the current brace pair, so if we have a
|
|
|
|
// surrounding pair we only get seps inside *that*.
|
2019-05-19 02:31:41 +08:00
|
|
|
if (vars_or_seps.size()) {
|
2019-05-28 10:47:13 +08:00
|
|
|
while (vars_or_seps.size() && vars_or_seps.back() > braces.back())
|
|
|
|
vars_or_seps.pop_back();
|
2019-05-19 02:31:41 +08:00
|
|
|
}
|
|
|
|
braces.pop_back();
|
|
|
|
}
|
2013-11-25 14:57:49 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
case L',': {
|
2018-03-12 08:51:54 +08:00
|
|
|
if (unescape_special && brace_count > 0) {
|
2018-03-11 03:16:07 +08:00
|
|
|
to_append_or_none = BRACE_SEP;
|
2018-03-12 11:02:43 +08:00
|
|
|
brace_text_start = false;
|
2019-05-19 02:31:41 +08:00
|
|
|
vars_or_seps.push_back(input_position);
|
2013-11-25 14:57:49 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2018-03-12 11:02:43 +08:00
|
|
|
case L'\n':
|
|
|
|
case L'\t':
|
2018-03-12 08:51:54 +08:00
|
|
|
case L' ': {
|
2018-03-12 11:02:43 +08:00
|
|
|
if (unescape_special && brace_count > 0) {
|
2018-09-22 11:14:09 +08:00
|
|
|
to_append_or_none = brace_text_start ? wint_t(BRACE_SPACE) : NOT_A_WCHAR;
|
2018-03-12 11:02:43 +08:00
|
|
|
}
|
2018-03-12 08:51:54 +08:00
|
|
|
break;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
case L'\'': {
|
2013-11-25 14:57:49 +08:00
|
|
|
mode = mode_single_quotes;
|
2017-01-27 09:18:38 +08:00
|
|
|
to_append_or_none = unescape_special ? wint_t(INTERNAL_SEPARATOR) : NOT_A_WCHAR;
|
2013-11-25 14:57:49 +08:00
|
|
|
break;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
case L'\"': {
|
2013-11-25 14:57:49 +08:00
|
|
|
mode = mode_double_quotes;
|
2017-01-27 09:18:38 +08:00
|
|
|
to_append_or_none = unescape_special ? wint_t(INTERNAL_SEPARATOR) : NOT_A_WCHAR;
|
2013-11-25 14:57:49 +08:00
|
|
|
break;
|
|
|
|
}
|
2018-03-12 11:02:43 +08:00
|
|
|
default: {
|
|
|
|
if (unescape_special && brace_count > 0) {
|
|
|
|
brace_text_start = true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2013-11-25 14:57:49 +08:00
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
} else if (mode == mode_single_quotes) {
|
|
|
|
if (c == L'\\') {
|
|
|
|
// A backslash may or may not escape something in single quotes.
|
|
|
|
switch (input[input_position + 1]) {
|
2013-11-25 14:57:49 +08:00
|
|
|
case '\\':
|
2016-04-28 07:10:14 +08:00
|
|
|
case L'\'': {
|
2014-05-14 13:30:41 +08:00
|
|
|
to_append_or_none = input[input_position + 1];
|
2016-04-28 07:10:14 +08:00
|
|
|
input_position += 1; // skip over the backslash
|
2013-11-25 14:57:49 +08:00
|
|
|
break;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
case L'\0': {
|
|
|
|
if (!allow_incomplete) {
|
2013-11-25 14:57:49 +08:00
|
|
|
errored = true;
|
2016-04-28 07:10:14 +08:00
|
|
|
} else {
|
|
|
|
// PCA this line had the following cryptic comment: 'We may ever escape
|
|
|
|
// a NULL character, but still appending a \ in case I am wrong.' Not
|
|
|
|
// sure what it means or the importance of this.
|
2013-11-25 14:57:49 +08:00
|
|
|
input_position += 1; /* Skip over the backslash */
|
2014-05-14 13:30:41 +08:00
|
|
|
to_append_or_none = L'\\';
|
2013-11-25 14:57:49 +08:00
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
break;
|
2013-11-25 14:57:49 +08:00
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
default: {
|
|
|
|
// Literal backslash that doesn't escape anything! Leave things alone; we'll
|
|
|
|
// append the backslash itself.
|
2013-11-25 14:57:49 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
} else if (c == L'\'') {
|
2017-01-27 09:18:38 +08:00
|
|
|
to_append_or_none = unescape_special ? wint_t(INTERNAL_SEPARATOR) : NOT_A_WCHAR;
|
2013-11-25 14:57:49 +08:00
|
|
|
mode = mode_unquoted;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
} else if (mode == mode_double_quotes) {
|
|
|
|
switch (c) {
|
|
|
|
case L'"': {
|
2013-11-25 14:57:49 +08:00
|
|
|
mode = mode_unquoted;
|
2017-01-27 09:18:38 +08:00
|
|
|
to_append_or_none = unescape_special ? wint_t(INTERNAL_SEPARATOR) : NOT_A_WCHAR;
|
2013-11-25 14:57:49 +08:00
|
|
|
break;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
case '\\': {
|
|
|
|
switch (input[input_position + 1]) {
|
|
|
|
case L'\0': {
|
|
|
|
if (!allow_incomplete) {
|
2013-11-25 14:57:49 +08:00
|
|
|
errored = true;
|
2016-04-28 07:10:14 +08:00
|
|
|
} else {
|
2014-05-14 13:30:41 +08:00
|
|
|
to_append_or_none = L'\0';
|
2013-11-25 14:57:49 +08:00
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
break;
|
2013-11-25 14:57:49 +08:00
|
|
|
}
|
|
|
|
case '\\':
|
|
|
|
case L'$':
|
2016-04-28 07:10:14 +08:00
|
|
|
case '"': {
|
2014-05-14 13:30:41 +08:00
|
|
|
to_append_or_none = input[input_position + 1];
|
2013-11-25 14:57:49 +08:00
|
|
|
input_position += 1; /* Skip over the backslash */
|
|
|
|
break;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
case '\n': {
|
2013-11-25 14:57:49 +08:00
|
|
|
/* Swallow newline */
|
2014-05-14 13:30:41 +08:00
|
|
|
to_append_or_none = NOT_A_WCHAR;
|
2013-11-26 17:39:16 +08:00
|
|
|
input_position += 1; /* Skip over the backslash */
|
2013-11-25 14:57:49 +08:00
|
|
|
break;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
default: {
|
|
|
|
/* Literal backslash that doesn't escape anything! Leave things alone;
|
|
|
|
* we'll append the backslash itself */
|
2013-11-25 14:57:49 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
case '$': {
|
|
|
|
if (unescape_special) {
|
2014-05-14 13:30:41 +08:00
|
|
|
to_append_or_none = VARIABLE_EXPAND_SINGLE;
|
2019-05-19 02:31:41 +08:00
|
|
|
vars_or_seps.push_back(input_position);
|
2013-11-25 14:57:49 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2019-05-05 18:09:25 +08:00
|
|
|
default: {
|
|
|
|
break;
|
|
|
|
}
|
2013-11-25 14:57:49 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
// Now maybe append the char.
|
|
|
|
if (to_append_or_none != NOT_A_WCHAR) {
|
2014-05-14 13:30:41 +08:00
|
|
|
wchar_t to_append_char = static_cast<wchar_t>(to_append_or_none);
|
2016-04-28 07:10:14 +08:00
|
|
|
// If result_char is not NOT_A_WCHAR, it must be a valid wchar.
|
2014-05-14 13:30:41 +08:00
|
|
|
assert((wint_t)to_append_char == to_append_or_none);
|
|
|
|
result.push_back(to_append_char);
|
2013-11-25 14:57:49 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
// Return the string by reference, and then success.
|
|
|
|
if (!errored) {
|
2017-01-27 08:14:50 +08:00
|
|
|
*output_str = std::move(result);
|
2013-11-25 14:57:49 +08:00
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
return !errored;
|
2013-11-25 14:57:49 +08:00
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
bool unescape_string_in_place(wcstring *str, unescape_flags_t escape_special) {
|
2013-11-25 14:57:49 +08:00
|
|
|
assert(str != NULL);
|
|
|
|
wcstring output;
|
|
|
|
bool success = unescape_string_internal(str->c_str(), str->size(), &output, escape_special);
|
2016-04-28 07:10:14 +08:00
|
|
|
if (success) {
|
2017-01-27 08:14:50 +08:00
|
|
|
*str = std::move(output);
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
2017-06-23 11:47:54 +08:00
|
|
|
bool unescape_string(const wchar_t *input, wcstring *output, unescape_flags_t escape_special,
|
|
|
|
escape_string_style_t style) {
|
2017-07-05 05:41:45 +08:00
|
|
|
bool success = false;
|
2017-06-23 11:47:54 +08:00
|
|
|
switch (style) {
|
|
|
|
case STRING_STYLE_SCRIPT: {
|
2019-03-13 05:06:01 +08:00
|
|
|
success = unescape_string_internal(input, std::wcslen(input), output, escape_special);
|
2017-06-23 11:47:54 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case STRING_STYLE_URL: {
|
|
|
|
success = unescape_string_url(input, output);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case STRING_STYLE_VAR: {
|
|
|
|
success = unescape_string_var(input, output);
|
|
|
|
break;
|
|
|
|
}
|
2018-11-17 10:21:05 +08:00
|
|
|
case STRING_STYLE_REGEX: {
|
2018-11-15 13:30:11 +08:00
|
|
|
// unescaping PCRE2 is not needed/supported, the PCRE2 engine is responsible for that
|
|
|
|
success = false;
|
|
|
|
break;
|
|
|
|
}
|
2017-06-23 11:47:54 +08:00
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
if (!success) output->clear();
|
2013-11-25 14:57:49 +08:00
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
2017-06-23 11:47:54 +08:00
|
|
|
bool unescape_string(const wcstring &input, wcstring *output, unescape_flags_t escape_special,
|
|
|
|
escape_string_style_t style) {
|
2017-07-05 05:41:45 +08:00
|
|
|
bool success = false;
|
2017-06-23 11:47:54 +08:00
|
|
|
switch (style) {
|
|
|
|
case STRING_STYLE_SCRIPT: {
|
|
|
|
success = unescape_string_internal(input.c_str(), input.size(), output, escape_special);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case STRING_STYLE_URL: {
|
|
|
|
success = unescape_string_url(input.c_str(), output);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case STRING_STYLE_VAR: {
|
|
|
|
success = unescape_string_var(input.c_str(), output);
|
|
|
|
break;
|
|
|
|
}
|
2018-11-17 10:21:05 +08:00
|
|
|
case STRING_STYLE_REGEX: {
|
2018-11-15 13:30:11 +08:00
|
|
|
// unescaping PCRE2 is not needed/supported, the PCRE2 engine is responsible for that
|
|
|
|
success = false;
|
|
|
|
break;
|
|
|
|
}
|
2017-06-23 11:47:54 +08:00
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
if (!success) output->clear();
|
2013-11-25 14:57:49 +08:00
|
|
|
return success;
|
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2017-01-19 05:54:54 +08:00
|
|
|
/// Used to invalidate our idea of having a valid window size. This can occur when either the
|
|
|
|
/// COLUMNS or LINES variables are changed. This is also invoked when the shell regains control of
|
|
|
|
/// the tty since it is possible the terminal size changed while an external command was running.
|
|
|
|
void invalidate_termsize(bool invalidate_vars) {
|
2019-04-29 07:56:48 +08:00
|
|
|
s_termsize_valid = false;
|
2017-01-19 05:54:54 +08:00
|
|
|
if (invalidate_vars) {
|
2019-05-13 03:33:13 +08:00
|
|
|
auto termsize = s_termsize.acquire();
|
|
|
|
termsize->ws_col = termsize->ws_row = USHRT_MAX;
|
2017-01-19 05:54:54 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Handle SIGWINCH. This is also invoked when the shell regains control of the tty since it is
|
|
|
|
/// possible the terminal size changed while an external command was running.
|
2019-05-13 09:23:00 +08:00
|
|
|
void common_handle_winch(int signal) {
|
|
|
|
(void)signal;
|
|
|
|
s_termsize_valid = false;
|
|
|
|
}
|
2014-08-24 15:59:03 +08:00
|
|
|
|
2017-01-19 05:54:54 +08:00
|
|
|
/// Validate the new terminal size. Fallback to the env vars if necessary. Ensure the values are
|
|
|
|
/// sane and if not fallback to a default of 80x24.
|
2018-09-25 10:26:46 +08:00
|
|
|
static void validate_new_termsize(struct winsize *new_termsize, const environment_t &vars) {
|
2017-01-19 05:54:54 +08:00
|
|
|
if (new_termsize->ws_col == 0 || new_termsize->ws_row == 0) {
|
|
|
|
#ifdef HAVE_WINSIZE
|
|
|
|
if (shell_is_interactive()) {
|
|
|
|
debug(1, _(L"Current terminal parameters have rows and/or columns set to zero."));
|
|
|
|
debug(1, _(L"The stty command can be used to correct this "
|
|
|
|
L"(e.g., stty rows 80 columns 24)."));
|
|
|
|
}
|
2014-08-24 15:59:03 +08:00
|
|
|
#endif
|
2017-01-19 05:54:54 +08:00
|
|
|
// Fallback to the environment vars.
|
2018-09-25 10:26:46 +08:00
|
|
|
maybe_t<env_var_t> col_var = vars.get(L"COLUMNS");
|
|
|
|
maybe_t<env_var_t> row_var = vars.get(L"LINES");
|
2017-01-19 05:54:54 +08:00
|
|
|
if (!col_var.missing_or_empty() && !row_var.missing_or_empty()) {
|
|
|
|
// Both vars have to have valid values.
|
2017-08-28 15:25:41 +08:00
|
|
|
int col = fish_wcstoi(col_var->as_string().c_str());
|
2017-01-19 05:54:54 +08:00
|
|
|
bool col_ok = errno == 0 && col > 0 && col <= USHRT_MAX;
|
2017-08-28 15:25:41 +08:00
|
|
|
int row = fish_wcstoi(row_var->as_string().c_str());
|
2017-01-19 05:54:54 +08:00
|
|
|
bool row_ok = errno == 0 && row > 0 && row <= USHRT_MAX;
|
|
|
|
if (col_ok && row_ok) {
|
|
|
|
new_termsize->ws_col = col;
|
|
|
|
new_termsize->ws_row = row;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (new_termsize->ws_col < MIN_TERM_COL || new_termsize->ws_row < MIN_TERM_ROW) {
|
|
|
|
if (shell_is_interactive()) {
|
|
|
|
debug(1, _(L"Current terminal parameters set terminal size to unreasonable value."));
|
|
|
|
debug(1, _(L"Defaulting terminal size to 80x24."));
|
2014-08-24 15:59:03 +08:00
|
|
|
}
|
2017-01-19 05:54:54 +08:00
|
|
|
new_termsize->ws_col = DFLT_TERM_COL;
|
|
|
|
new_termsize->ws_row = DFLT_TERM_ROW;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Export the new terminal size as env vars and to the kernel if possible.
|
2018-09-25 10:26:46 +08:00
|
|
|
static void export_new_termsize(struct winsize *new_termsize, env_stack_t &vars) {
|
|
|
|
auto cols = vars.get(L"COLUMNS", ENV_EXPORT);
|
2018-09-14 15:36:26 +08:00
|
|
|
vars.set_one(L"COLUMNS", ENV_GLOBAL | (cols.missing_or_empty() ? ENV_DEFAULT : ENV_EXPORT),
|
2019-02-19 15:12:03 +08:00
|
|
|
std::to_wstring(int(new_termsize->ws_col)));
|
2017-02-11 07:20:09 +08:00
|
|
|
|
2018-09-25 10:26:46 +08:00
|
|
|
auto lines = vars.get(L"LINES", ENV_EXPORT);
|
2019-02-19 15:12:03 +08:00
|
|
|
vars.set_one(L"LINES", ENV_GLOBAL | (lines.missing_or_empty() ? ENV_DEFAULT : ENV_EXPORT),
|
|
|
|
std::to_wstring(int(new_termsize->ws_row)));
|
2017-01-19 05:54:54 +08:00
|
|
|
|
|
|
|
#ifdef HAVE_WINSIZE
|
2017-10-16 12:13:30 +08:00
|
|
|
// Only write the new terminal size if we are in the foreground (#4477)
|
|
|
|
if (tcgetpgrp(STDOUT_FILENO) == getpgrp()) {
|
|
|
|
ioctl(STDOUT_FILENO, TIOCSWINSZ, new_termsize);
|
|
|
|
}
|
2017-01-19 05:54:54 +08:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2019-05-13 03:33:13 +08:00
|
|
|
/// Get the current termsize, lazily computing it. Return by reference if it changed.
|
|
|
|
static struct winsize get_current_winsize_prim(bool *changed, const environment_t &vars) {
|
|
|
|
auto termsize = s_termsize.acquire();
|
|
|
|
if (s_termsize_valid) return *termsize;
|
2017-01-30 11:33:30 +08:00
|
|
|
|
|
|
|
struct winsize new_termsize = {0, 0, 0, 0};
|
2017-01-19 05:54:54 +08:00
|
|
|
#ifdef HAVE_WINSIZE
|
2017-01-30 11:33:30 +08:00
|
|
|
errno = 0;
|
|
|
|
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &new_termsize) != -1 &&
|
2019-05-13 03:33:13 +08:00
|
|
|
new_termsize.ws_col == termsize->ws_col && new_termsize.ws_row == termsize->ws_row) {
|
2019-04-29 07:56:48 +08:00
|
|
|
s_termsize_valid = true;
|
2019-05-13 03:33:13 +08:00
|
|
|
return *termsize;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2017-01-30 11:33:30 +08:00
|
|
|
#endif
|
2018-09-25 10:26:46 +08:00
|
|
|
validate_new_termsize(&new_termsize, vars);
|
2019-05-13 03:33:13 +08:00
|
|
|
termsize->ws_col = new_termsize.ws_col;
|
|
|
|
termsize->ws_row = new_termsize.ws_row;
|
|
|
|
*changed = true;
|
2019-04-29 07:56:48 +08:00
|
|
|
s_termsize_valid = true;
|
2019-05-13 03:33:13 +08:00
|
|
|
return *termsize;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Updates termsize as needed, and returns a copy of the winsize.
|
|
|
|
struct winsize get_current_winsize() {
|
|
|
|
bool changed = false;
|
|
|
|
auto &vars = env_stack_t::globals();
|
|
|
|
struct winsize termsize = get_current_winsize_prim(&changed, vars);
|
|
|
|
if (changed) {
|
|
|
|
// TODO: this may call us reentrantly through the environment dispatch mechanism. We need to
|
|
|
|
// rationalize this.
|
|
|
|
export_new_termsize(&termsize, vars);
|
2019-05-14 05:05:42 +08:00
|
|
|
// Hack: due to the dispatch the termsize may have just become invalid. Stomp it back to
|
|
|
|
// valid. What a mess.
|
|
|
|
*s_termsize.acquire() = termsize;
|
|
|
|
s_termsize_valid = true;
|
2019-05-13 03:33:13 +08:00
|
|
|
}
|
2017-01-30 11:33:30 +08:00
|
|
|
return termsize;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
int common_get_width() { return get_current_winsize().ws_col; }
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
int common_get_height() { return get_current_winsize().ws_row; }
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
bool string_prefixes_string(const wchar_t *proposed_prefix, const wcstring &value) {
|
2018-10-21 05:38:49 +08:00
|
|
|
return string_prefixes_string(proposed_prefix, value.c_str());
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
bool string_prefixes_string(const wcstring &proposed_prefix, const wcstring &value) {
|
2012-11-19 08:30:30 +08:00
|
|
|
size_t prefix_size = proposed_prefix.size();
|
|
|
|
return prefix_size <= value.size() && value.compare(0, prefix_size, proposed_prefix) == 0;
|
|
|
|
}
|
|
|
|
|
2017-09-02 05:33:59 +08:00
|
|
|
bool string_prefixes_string(const wchar_t *proposed_prefix, const wchar_t *value) {
|
|
|
|
for (size_t idx = 0; proposed_prefix[idx] != L'\0'; idx++) {
|
|
|
|
// Note if the prefix is longer than value, then we will compare a nonzero prefix character
|
|
|
|
// against a zero value character, and so we'll return false;
|
|
|
|
if (proposed_prefix[idx] != value[idx]) return false;
|
|
|
|
}
|
|
|
|
// We must have that proposed_prefix[idx] == L'\0', so we have a prefix match.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-10-21 05:38:49 +08:00
|
|
|
bool string_prefixes_string(const char *proposed_prefix, const std::string &value) {
|
|
|
|
return string_prefixes_string(proposed_prefix, value.c_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
bool string_prefixes_string(const char *proposed_prefix, const char *value) {
|
|
|
|
for (size_t idx = 0; proposed_prefix[idx] != L'\0'; idx++) {
|
|
|
|
if (proposed_prefix[idx] != value[idx]) return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
bool string_prefixes_string_case_insensitive(const wcstring &proposed_prefix,
|
|
|
|
const wcstring &value) {
|
2012-11-19 08:30:30 +08:00
|
|
|
size_t prefix_size = proposed_prefix.size();
|
2016-04-28 07:10:14 +08:00
|
|
|
return prefix_size <= value.size() &&
|
|
|
|
wcsncasecmp(proposed_prefix.c_str(), value.c_str(), prefix_size) == 0;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
bool string_suffixes_string(const wcstring &proposed_suffix, const wcstring &value) {
|
2012-11-19 08:30:30 +08:00
|
|
|
size_t suffix_size = proposed_suffix.size();
|
2016-04-28 07:10:14 +08:00
|
|
|
return suffix_size <= value.size() &&
|
|
|
|
value.compare(value.size() - suffix_size, suffix_size, proposed_suffix) == 0;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
bool string_suffixes_string(const wchar_t *proposed_suffix, const wcstring &value) {
|
2019-03-13 05:06:01 +08:00
|
|
|
size_t suffix_size = std::wcslen(proposed_suffix);
|
2016-04-28 07:10:14 +08:00
|
|
|
return suffix_size <= value.size() &&
|
|
|
|
value.compare(value.size() - suffix_size, suffix_size, proposed_suffix) == 0;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
|
2018-09-28 10:28:39 +08:00
|
|
|
bool string_suffixes_string_case_insensitive(const wcstring &proposed_suffix,
|
|
|
|
const wcstring &value) {
|
2018-04-15 12:55:35 +08:00
|
|
|
size_t suffix_size = proposed_suffix.size();
|
2018-09-28 10:28:39 +08:00
|
|
|
return suffix_size <= value.size() && wcsncasecmp(value.c_str() + (value.size() - suffix_size),
|
|
|
|
proposed_suffix.c_str(), suffix_size) == 0;
|
2018-04-15 12:55:35 +08:00
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
/// Returns true if seq, represented as a subsequence, is contained within string.
|
|
|
|
static bool subsequence_in_string(const wcstring &seq, const wcstring &str) {
|
|
|
|
// Impossible if seq is larger than string.
|
|
|
|
if (seq.size() > str.size()) {
|
2013-05-26 06:41:18 +08:00
|
|
|
return false;
|
|
|
|
}
|
2013-06-02 16:14:26 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
// Empty strings are considered to be subsequences of everything.
|
|
|
|
if (seq.empty()) {
|
2013-05-26 06:41:18 +08:00
|
|
|
return true;
|
|
|
|
}
|
2013-06-02 16:14:26 +08:00
|
|
|
|
2013-05-26 06:41:18 +08:00
|
|
|
size_t str_idx, seq_idx;
|
2016-04-28 07:10:14 +08:00
|
|
|
for (seq_idx = str_idx = 0; seq_idx < seq.size() && str_idx < str.size(); seq_idx++) {
|
2013-05-26 06:41:18 +08:00
|
|
|
wchar_t c = seq.at(seq_idx);
|
|
|
|
size_t char_loc = str.find(c, str_idx);
|
2016-04-28 07:10:14 +08:00
|
|
|
if (char_loc == wcstring::npos) {
|
|
|
|
break; // didn't find this character
|
|
|
|
} else {
|
|
|
|
str_idx = char_loc + 1; // we found it, continue the search just after it
|
2013-05-26 06:41:18 +08:00
|
|
|
}
|
|
|
|
}
|
2013-06-02 16:14:26 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
// We succeeded if we exhausted our sequence.
|
2013-05-26 06:41:18 +08:00
|
|
|
assert(seq_idx <= seq.size());
|
|
|
|
return seq_idx == seq.size();
|
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
string_fuzzy_match_t::string_fuzzy_match_t(enum fuzzy_match_type_t t, size_t distance_first,
|
|
|
|
size_t distance_second)
|
|
|
|
: type(t), match_distance_first(distance_first), match_distance_second(distance_second) {}
|
2013-05-26 06:41:18 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
string_fuzzy_match_t string_fuzzy_match_string(const wcstring &string,
|
|
|
|
const wcstring &match_against,
|
|
|
|
fuzzy_match_type_t limit_type) {
|
|
|
|
// Distances are generally the amount of text not matched.
|
2013-05-26 06:41:18 +08:00
|
|
|
string_fuzzy_match_t result(fuzzy_match_none, 0, 0);
|
|
|
|
size_t location;
|
2016-04-28 07:10:14 +08:00
|
|
|
if (limit_type >= fuzzy_match_exact && string == match_against) {
|
2013-05-26 06:41:18 +08:00
|
|
|
result.type = fuzzy_match_exact;
|
2016-04-28 07:10:14 +08:00
|
|
|
} else if (limit_type >= fuzzy_match_prefix && string_prefixes_string(string, match_against)) {
|
2013-05-26 06:41:18 +08:00
|
|
|
result.type = fuzzy_match_prefix;
|
|
|
|
assert(match_against.size() >= string.size());
|
|
|
|
result.match_distance_first = match_against.size() - string.size();
|
2016-04-28 07:10:14 +08:00
|
|
|
} else if (limit_type >= fuzzy_match_case_insensitive &&
|
|
|
|
wcscasecmp(string.c_str(), match_against.c_str()) == 0) {
|
2013-05-26 06:41:18 +08:00
|
|
|
result.type = fuzzy_match_case_insensitive;
|
2016-04-28 07:10:14 +08:00
|
|
|
} else if (limit_type >= fuzzy_match_prefix_case_insensitive &&
|
|
|
|
string_prefixes_string_case_insensitive(string, match_against)) {
|
2013-05-26 06:41:18 +08:00
|
|
|
result.type = fuzzy_match_prefix_case_insensitive;
|
|
|
|
assert(match_against.size() >= string.size());
|
|
|
|
result.match_distance_first = match_against.size() - string.size();
|
2016-04-28 07:10:14 +08:00
|
|
|
} else if (limit_type >= fuzzy_match_substring &&
|
|
|
|
(location = match_against.find(string)) != wcstring::npos) {
|
|
|
|
// String is contained within match against.
|
2013-05-26 06:41:18 +08:00
|
|
|
result.type = fuzzy_match_substring;
|
|
|
|
assert(match_against.size() >= string.size());
|
|
|
|
result.match_distance_first = match_against.size() - string.size();
|
2016-04-28 07:10:14 +08:00
|
|
|
result.match_distance_second = location; // prefer earlier matches
|
2018-10-22 03:02:38 +08:00
|
|
|
} else if (limit_type >= fuzzy_match_substring_case_insensitive &&
|
2018-10-28 19:23:29 +08:00
|
|
|
(location = ifind(match_against, string, true)) != wcstring::npos) {
|
2018-10-17 10:45:04 +08:00
|
|
|
// A case-insensitive version of the string is in the match against.
|
|
|
|
result.type = fuzzy_match_substring_case_insensitive;
|
|
|
|
assert(match_against.size() >= string.size());
|
|
|
|
result.match_distance_first = match_against.size() - string.size();
|
|
|
|
result.match_distance_second = location; // prefer earlier matches
|
2016-04-28 07:10:14 +08:00
|
|
|
} else if (limit_type >= fuzzy_match_subsequence_insertions_only &&
|
|
|
|
subsequence_in_string(string, match_against)) {
|
2013-05-26 06:41:18 +08:00
|
|
|
result.type = fuzzy_match_subsequence_insertions_only;
|
|
|
|
assert(match_against.size() >= string.size());
|
|
|
|
result.match_distance_first = match_against.size() - string.size();
|
2016-04-28 07:10:14 +08:00
|
|
|
// It would be nice to prefer matches with greater matching runs here.
|
2013-05-26 06:41:18 +08:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
template <typename T>
|
|
|
|
static inline int compare_ints(T a, T b) {
|
2013-05-26 06:41:18 +08:00
|
|
|
if (a < b) return -1;
|
|
|
|
if (a == b) return 0;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
/// Compare types; if the types match, compare distances.
|
|
|
|
int string_fuzzy_match_t::compare(const string_fuzzy_match_t &rhs) const {
|
|
|
|
if (this->type != rhs.type) {
|
2013-05-26 06:41:18 +08:00
|
|
|
return compare_ints(this->type, rhs.type);
|
2016-04-28 07:10:14 +08:00
|
|
|
} else if (this->match_distance_first != rhs.match_distance_first) {
|
2013-05-26 06:41:18 +08:00
|
|
|
return compare_ints(this->match_distance_first, rhs.match_distance_first);
|
2016-04-28 07:10:14 +08:00
|
|
|
} else if (this->match_distance_second != rhs.match_distance_second) {
|
2013-05-26 06:41:18 +08:00
|
|
|
return compare_ints(this->match_distance_second, rhs.match_distance_second);
|
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
return 0; // equal
|
2013-05-26 06:41:18 +08:00
|
|
|
}
|
|
|
|
|
2019-02-04 08:06:10 +08:00
|
|
|
template <bool Fuzzy, typename T>
|
|
|
|
size_t ifind_impl(const T &haystack, const T &needle) {
|
|
|
|
using char_t = typename T::value_type;
|
|
|
|
std::locale locale;
|
|
|
|
|
|
|
|
auto ieq = [&locale](char_t c1, char_t c2) {
|
|
|
|
if (c1 == c2 || std::toupper(c1, locale) == std::toupper(c2, locale)) return true;
|
|
|
|
|
|
|
|
// In fuzzy matching treat treat `-` and `_` as equal (#3584).
|
|
|
|
if (Fuzzy) {
|
|
|
|
if ((c1 == '-' || c1 == '_') && (c2 == '-' || c2 == '_')) return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
|
|
|
auto result = std::search(haystack.begin(), haystack.end(), needle.begin(), needle.end(), ieq);
|
|
|
|
if (result != haystack.end()) {
|
|
|
|
return result - haystack.begin();
|
|
|
|
}
|
|
|
|
return T::npos;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t ifind(const wcstring &haystack, const wcstring &needle, bool fuzzy) {
|
|
|
|
return fuzzy ? ifind_impl<true>(haystack, needle) : ifind_impl<false>(haystack, needle);
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t ifind(const std::string &haystack, const std::string &needle, bool fuzzy) {
|
|
|
|
return fuzzy ? ifind_impl<true>(haystack, needle) : ifind_impl<false>(haystack, needle);
|
|
|
|
}
|
|
|
|
|
2018-03-25 14:32:55 +08:00
|
|
|
wcstring_list_t split_string(const wcstring &val, wchar_t sep) {
|
|
|
|
wcstring_list_t out;
|
|
|
|
size_t pos = 0, end = val.size();
|
|
|
|
while (pos <= end) {
|
|
|
|
size_t next_pos = val.find(sep, pos);
|
|
|
|
if (next_pos == wcstring::npos) {
|
|
|
|
next_pos = end;
|
|
|
|
}
|
|
|
|
out.emplace_back(val, pos, next_pos - pos);
|
|
|
|
pos = next_pos + 1; // skip the separator, or skip past the end
|
|
|
|
}
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
|
|
|
wcstring join_strings(const wcstring_list_t &vals, wchar_t sep) {
|
2018-10-01 07:34:01 +08:00
|
|
|
if (vals.empty()) return wcstring{};
|
|
|
|
|
|
|
|
// Reserve the size we will need.
|
|
|
|
// count-1 separators, plus the length of all strings.
|
|
|
|
size_t size = vals.size() - 1;
|
|
|
|
for (const wcstring &s : vals) {
|
|
|
|
size += s.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Construct the string.
|
2018-03-25 14:32:55 +08:00
|
|
|
wcstring result;
|
2018-10-01 07:34:01 +08:00
|
|
|
result.reserve(size);
|
2018-03-25 14:32:55 +08:00
|
|
|
bool first = true;
|
|
|
|
for (const wcstring &s : vals) {
|
|
|
|
if (!first) {
|
|
|
|
result.push_back(sep);
|
|
|
|
}
|
|
|
|
result.append(s);
|
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
int create_directory(const wcstring &d) {
|
2016-10-23 02:21:13 +08:00
|
|
|
bool ok = false;
|
2012-11-19 08:30:30 +08:00
|
|
|
struct stat buf;
|
|
|
|
int stat_res = 0;
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
while ((stat_res = wstat(d, &buf)) != 0) {
|
|
|
|
if (errno != EAGAIN) break;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
if (stat_res == 0) {
|
2016-10-23 02:21:13 +08:00
|
|
|
if (S_ISDIR(buf.st_mode)) ok = true;
|
|
|
|
} else if (errno == ENOENT) {
|
|
|
|
wcstring dir = wdirname(d);
|
|
|
|
if (!create_directory(dir) && !wmkdir(d, 0700)) ok = true;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
return ok ? 0 : -1;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
|
2019-02-10 20:20:01 +08:00
|
|
|
[[gnu::noinline]] void bugreport() {
|
2019-05-28 06:56:53 +08:00
|
|
|
FLOG(error, _(L"This is a bug. Break on 'bugreport' to debug."));
|
2019-05-30 19:04:40 +08:00
|
|
|
FLOG(error, _(L"If you can reproduce it, please report: "), PACKAGE_BUGREPORT, L'.');
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
wcstring format_size(long long sz) {
|
2012-11-19 08:30:30 +08:00
|
|
|
wcstring result;
|
2016-04-28 07:10:14 +08:00
|
|
|
const wchar_t *sz_name[] = {L"kB", L"MB", L"GB", L"TB", L"PB", L"EB", L"ZB", L"YB", 0};
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
if (sz < 0) {
|
2012-11-19 08:30:30 +08:00
|
|
|
result.append(L"unknown");
|
2016-04-28 07:10:14 +08:00
|
|
|
} else if (sz < 1) {
|
2012-11-19 08:30:30 +08:00
|
|
|
result.append(_(L"empty"));
|
2016-04-28 07:10:14 +08:00
|
|
|
} else if (sz < 1024) {
|
2012-11-19 08:30:30 +08:00
|
|
|
result.append(format_string(L"%lldB", sz));
|
2016-04-28 07:10:14 +08:00
|
|
|
} else {
|
2012-11-19 08:30:30 +08:00
|
|
|
int i;
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
for (i = 0; sz_name[i]; i++) {
|
|
|
|
if (sz < (1024 * 1024) || !sz_name[i + 1]) {
|
|
|
|
long isz = ((long)sz) / 1024;
|
2012-11-19 08:30:30 +08:00
|
|
|
if (isz > 9)
|
|
|
|
result.append(format_string(L"%d%ls", isz, sz_name[i]));
|
|
|
|
else
|
2016-04-28 07:10:14 +08:00
|
|
|
result.append(format_string(L"%.1f%ls", (double)sz / 1024, sz_name[i]));
|
2012-11-19 08:30:30 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
sz /= 1024;
|
|
|
|
}
|
|
|
|
}
|
2012-02-10 02:14:06 +08:00
|
|
|
return result;
|
2007-10-15 17:51:08 +08:00
|
|
|
}
|
2009-02-03 06:46:45 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
/// Crappy function to extract the most significant digit of an unsigned long long value.
|
|
|
|
static char extract_most_significant_digit(unsigned long long *xp) {
|
2012-03-01 03:27:14 +08:00
|
|
|
unsigned long long place_value = 1;
|
|
|
|
unsigned long long x = *xp;
|
2016-04-28 07:10:14 +08:00
|
|
|
while (x >= 10) {
|
2012-03-01 03:27:14 +08:00
|
|
|
x /= 10;
|
|
|
|
place_value *= 10;
|
|
|
|
}
|
|
|
|
*xp -= (place_value * x);
|
|
|
|
return x + '0';
|
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
void append_ull(char *buff, unsigned long long val, size_t *inout_idx, size_t max_len) {
|
2012-03-01 03:27:14 +08:00
|
|
|
size_t idx = *inout_idx;
|
2016-04-28 07:10:14 +08:00
|
|
|
while (val > 0 && idx < max_len) buff[idx++] = extract_most_significant_digit(&val);
|
2012-03-01 03:27:14 +08:00
|
|
|
*inout_idx = idx;
|
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
void append_str(char *buff, const char *str, size_t *inout_idx, size_t max_len) {
|
2012-03-01 03:27:14 +08:00
|
|
|
size_t idx = *inout_idx;
|
2016-04-28 07:10:14 +08:00
|
|
|
while (*str && idx < max_len) buff[idx++] = *str++;
|
2012-03-01 03:27:14 +08:00
|
|
|
*inout_idx = idx;
|
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
void format_size_safe(char buff[128], unsigned long long sz) {
|
2012-03-01 03:27:14 +08:00
|
|
|
const size_t buff_size = 128;
|
2016-04-28 07:10:14 +08:00
|
|
|
const size_t max_len = buff_size - 1; // need to leave room for a null terminator
|
2019-03-13 06:07:07 +08:00
|
|
|
std::memset(buff, 0, buff_size);
|
2012-03-01 03:27:14 +08:00
|
|
|
size_t idx = 0;
|
2016-04-28 07:10:14 +08:00
|
|
|
const char *const sz_name[] = {"kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", NULL};
|
|
|
|
if (sz < 1) {
|
2012-03-01 03:27:14 +08:00
|
|
|
strncpy(buff, "empty", buff_size);
|
2016-04-28 07:10:14 +08:00
|
|
|
} else if (sz < 1024) {
|
2012-03-01 03:27:14 +08:00
|
|
|
append_ull(buff, sz, &idx, max_len);
|
|
|
|
append_str(buff, "B", &idx, max_len);
|
2016-04-28 07:10:14 +08:00
|
|
|
} else {
|
|
|
|
for (size_t i = 0; sz_name[i]; i++) {
|
|
|
|
if (sz < (1024 * 1024) || !sz_name[i + 1]) {
|
|
|
|
unsigned long long isz = sz / 1024;
|
|
|
|
if (isz > 9) {
|
2012-03-01 03:27:14 +08:00
|
|
|
append_ull(buff, isz, &idx, max_len);
|
2016-04-28 07:10:14 +08:00
|
|
|
} else {
|
2018-02-09 06:54:27 +08:00
|
|
|
append_ull(buff, isz, &idx, max_len);
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
// Maybe append a single fraction digit.
|
2012-03-01 03:27:14 +08:00
|
|
|
unsigned long long remainder = sz % 1024;
|
2016-04-28 07:10:14 +08:00
|
|
|
if (remainder > 0) {
|
2012-03-01 03:27:14 +08:00
|
|
|
char tmp[3] = {'.', extract_most_significant_digit(&remainder), 0};
|
|
|
|
append_str(buff, tmp, &idx, max_len);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
append_str(buff, sz_name[i], &idx, max_len);
|
2012-11-19 08:30:30 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
sz /= 1024;
|
|
|
|
}
|
2012-03-01 03:27:14 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-25 10:25:48 +08:00
|
|
|
/// Return the number of seconds from the UNIX epoch, with subsecond precision. This function uses
|
|
|
|
/// the gettimeofday function and will have the same precision as that function.
|
2016-04-28 07:10:14 +08:00
|
|
|
double timef() {
|
2012-11-19 08:30:30 +08:00
|
|
|
struct timeval tv;
|
2017-02-15 13:09:15 +08:00
|
|
|
assert_with_errno(gettimeofday(&tv, 0) != -1);
|
2016-06-25 10:25:48 +08:00
|
|
|
// return (double)tv.tv_sec + 0.000001 * tv.tv_usec;
|
|
|
|
return (double)tv.tv_sec + 1e-6 * tv.tv_usec;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
void exit_without_destructors(int code) { _exit(code); }
|
2012-02-29 07:11:46 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
/// Helper function to convert from a null_terminated_array_t<wchar_t> to a
|
|
|
|
/// null_terminated_array_t<char_t>.
|
|
|
|
void convert_wide_array_to_narrow(const null_terminated_array_t<wchar_t> &wide_arr,
|
|
|
|
null_terminated_array_t<char> *output) {
|
2012-03-01 03:27:14 +08:00
|
|
|
const wchar_t *const *arr = wide_arr.get();
|
2016-04-28 07:10:14 +08:00
|
|
|
if (!arr) {
|
2013-02-23 08:22:56 +08:00
|
|
|
output->clear();
|
|
|
|
return;
|
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2012-03-01 03:27:14 +08:00
|
|
|
std::vector<std::string> list;
|
2016-04-28 07:10:14 +08:00
|
|
|
for (size_t i = 0; arr[i]; i++) {
|
2012-03-01 03:27:14 +08:00
|
|
|
list.push_back(wcs2string(arr[i]));
|
|
|
|
}
|
2013-02-23 08:22:56 +08:00
|
|
|
output->set(list);
|
2012-03-01 03:27:14 +08:00
|
|
|
}
|
|
|
|
|
2018-09-02 05:05:59 +08:00
|
|
|
void autoclose_fd_t::close() {
|
|
|
|
if (fd_ < 0) return;
|
2018-09-06 12:48:32 +08:00
|
|
|
if (::close(fd_) == -1) {
|
|
|
|
wperror(L"close");
|
2018-09-02 05:05:59 +08:00
|
|
|
}
|
|
|
|
fd_ = -1;
|
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
void append_path_component(wcstring &path, const wcstring &component) {
|
|
|
|
if (path.empty() || component.empty()) {
|
2012-05-09 17:33:42 +08:00
|
|
|
path.append(component);
|
2016-04-28 07:10:14 +08:00
|
|
|
} else {
|
2012-05-09 17:33:42 +08:00
|
|
|
size_t path_len = path.size();
|
2016-04-28 07:10:14 +08:00
|
|
|
bool path_slash = path.at(path_len - 1) == L'/';
|
2012-05-09 17:33:42 +08:00
|
|
|
bool comp_slash = component.at(0) == L'/';
|
2016-04-28 07:10:14 +08:00
|
|
|
if (!path_slash && !comp_slash) {
|
2012-05-09 17:33:42 +08:00
|
|
|
// Need a slash
|
|
|
|
path.push_back(L'/');
|
2016-04-28 07:10:14 +08:00
|
|
|
} else if (path_slash && comp_slash) {
|
|
|
|
// Too many slashes.
|
2012-05-09 17:33:42 +08:00
|
|
|
path.erase(path_len - 1, 1);
|
|
|
|
}
|
2011-12-27 11:18:46 +08:00
|
|
|
path.append(component);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
extern "C" {
|
2019-02-10 20:20:01 +08:00
|
|
|
[[gnu::noinline]] void debug_thread_error(void) {
|
2019-05-22 09:39:01 +08:00
|
|
|
// Wait for a SIGINT. We can't use sigsuspend() because the signal may be delivered on another
|
|
|
|
// thread.
|
2019-05-26 10:19:03 +08:00
|
|
|
sigint_checker_t sigint;
|
|
|
|
sigint.wait();
|
2011-12-27 11:18:46 +08:00
|
|
|
}
|
2012-01-06 05:58:48 +08:00
|
|
|
}
|
|
|
|
|
2018-12-31 09:25:50 +08:00
|
|
|
void set_main_thread() {
|
2019-05-30 03:33:44 +08:00
|
|
|
// Just call thread_id() once to force increment of thread_id.
|
|
|
|
uint64_t tid = thread_id();
|
|
|
|
assert(tid == 1 && "main thread should have thread ID 1");
|
|
|
|
(void)tid;
|
2018-12-31 09:25:50 +08:00
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
|
2018-02-19 10:33:04 +08:00
|
|
|
void configure_thread_assertions_for_testing() { thread_asserts_cfg_for_testing = true; }
|
2012-05-14 11:19:02 +08:00
|
|
|
|
2019-05-05 18:09:25 +08:00
|
|
|
bool is_forked_child() { return is_forked_proc; }
|
2012-02-28 10:43:24 +08:00
|
|
|
|
2018-02-19 10:33:04 +08:00
|
|
|
void setup_fork_guards() {
|
2018-12-31 10:21:36 +08:00
|
|
|
is_forked_proc = false;
|
2019-04-29 06:56:49 +08:00
|
|
|
static std::once_flag fork_guard_flag;
|
|
|
|
std::call_once(fork_guard_flag,
|
|
|
|
[] { pthread_atfork(nullptr, nullptr, [] { is_forked_proc = true; }); });
|
2012-02-28 10:43:24 +08:00
|
|
|
}
|
|
|
|
|
2018-09-29 12:20:50 +08:00
|
|
|
void save_term_foreground_process_group() {
|
|
|
|
ASSERT_IS_MAIN_THREAD();
|
|
|
|
initial_fg_process_group = tcgetpgrp(STDIN_FILENO);
|
|
|
|
}
|
2012-11-18 18:16:14 +08:00
|
|
|
|
2018-02-19 10:33:04 +08:00
|
|
|
void restore_term_foreground_process_group() {
|
2017-02-11 10:47:02 +08:00
|
|
|
if (initial_fg_process_group == -1) return;
|
|
|
|
// This is called during shutdown and from a signal handler. We don't bother to complain on
|
|
|
|
// failure because doing so is unlikely to be noticed.
|
|
|
|
if (tcsetpgrp(STDIN_FILENO, initial_fg_process_group) == -1 && errno == ENOTTY) {
|
|
|
|
redirect_tty_output();
|
2012-11-18 18:16:14 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-30 03:33:44 +08:00
|
|
|
bool is_main_thread() { return thread_id() == 1; }
|
2012-01-06 05:58:48 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
void assert_is_main_thread(const char *who) {
|
2016-11-03 12:54:57 +08:00
|
|
|
if (!is_main_thread() && !thread_asserts_cfg_for_testing) {
|
2019-05-30 19:04:40 +08:00
|
|
|
FLOGF(error, L"%s called off of main thread.", who);
|
|
|
|
FLOGF(error, L"Break on debug_thread_error to debug.");
|
2011-12-27 11:18:46 +08:00
|
|
|
debug_thread_error();
|
2012-02-28 10:43:24 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
void assert_is_not_forked_child(const char *who) {
|
|
|
|
if (is_forked_child()) {
|
2019-05-30 19:04:40 +08:00
|
|
|
FLOGF(error, L"%s called in a forked child.", who);
|
|
|
|
FLOG(error, L"Break on debug_thread_error to debug.");
|
2012-02-28 10:43:24 +08:00
|
|
|
debug_thread_error();
|
2011-12-27 11:18:46 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
void assert_is_background_thread(const char *who) {
|
2016-11-03 12:54:57 +08:00
|
|
|
if (is_main_thread() && !thread_asserts_cfg_for_testing) {
|
2019-05-30 19:04:40 +08:00
|
|
|
FLOGF(error, L"%s called on the main thread (may block!).", who);
|
|
|
|
FLOG(error, L"Break on debug_thread_error to debug.");
|
2011-12-27 11:18:46 +08:00
|
|
|
debug_thread_error();
|
2012-02-25 04:13:35 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-31 10:15:49 +08:00
|
|
|
void assert_is_locked(void *vmutex, const char *who, const char *caller) {
|
|
|
|
std::mutex *mutex = static_cast<std::mutex *>(vmutex);
|
|
|
|
|
|
|
|
// Note that std::mutex.try_lock() is allowed to return false when the mutex isn't
|
|
|
|
// actually locked; fortunately we are checking the opposite so we're safe.
|
|
|
|
if (mutex->try_lock()) {
|
2019-05-30 19:04:40 +08:00
|
|
|
FLOGF(error, L"%s is not locked when it should be in '%s'", who, caller);
|
|
|
|
FLOG(error, L"Break on debug_thread_error to debug.");
|
2012-02-25 04:13:35 +08:00
|
|
|
debug_thread_error();
|
2018-12-31 10:15:49 +08:00
|
|
|
mutex->unlock();
|
2014-08-24 15:59:03 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-23 08:22:56 +08:00
|
|
|
template <typename CharType_t>
|
2016-04-28 07:10:14 +08:00
|
|
|
static CharType_t **make_null_terminated_array_helper(
|
|
|
|
const std::vector<std::basic_string<CharType_t> > &argv) {
|
2013-02-23 08:22:56 +08:00
|
|
|
size_t count = argv.size();
|
2013-02-24 04:47:10 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
// We allocate everything in one giant block. First compute how much space we need.
|
|
|
|
// N + 1 pointers.
|
2013-02-23 08:22:56 +08:00
|
|
|
size_t pointers_allocation_len = (count + 1) * sizeof(CharType_t *);
|
2013-02-24 04:47:10 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
// In the very unlikely event that CharType_t has stricter alignment requirements than does a
|
|
|
|
// pointer, round us up to the size of a CharType_t.
|
2013-02-23 08:22:56 +08:00
|
|
|
pointers_allocation_len += sizeof(CharType_t) - 1;
|
|
|
|
pointers_allocation_len -= pointers_allocation_len % sizeof(CharType_t);
|
2013-02-24 04:47:10 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
// N null terminated strings.
|
2013-02-23 08:22:56 +08:00
|
|
|
size_t strings_allocation_len = 0;
|
2016-04-28 07:10:14 +08:00
|
|
|
for (size_t i = 0; i < count; i++) {
|
|
|
|
// The size of the string, plus a null terminator.
|
2013-02-23 08:22:56 +08:00
|
|
|
strings_allocation_len += (argv.at(i).size() + 1) * sizeof(CharType_t);
|
|
|
|
}
|
2013-02-24 04:47:10 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
// Now allocate their sum.
|
|
|
|
unsigned char *base =
|
|
|
|
static_cast<unsigned char *>(malloc(pointers_allocation_len + strings_allocation_len));
|
|
|
|
if (!base) return NULL;
|
2013-02-24 04:47:10 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
// Divvy it up into the pointers and strings.
|
2013-02-23 08:22:56 +08:00
|
|
|
CharType_t **pointers = reinterpret_cast<CharType_t **>(base);
|
|
|
|
CharType_t *strings = reinterpret_cast<CharType_t *>(base + pointers_allocation_len);
|
2013-02-24 04:47:10 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
// Start copying.
|
|
|
|
for (size_t i = 0; i < count; i++) {
|
2013-02-23 08:22:56 +08:00
|
|
|
const std::basic_string<CharType_t> &str = argv.at(i);
|
2016-04-28 07:10:14 +08:00
|
|
|
*pointers++ = strings; // store the current string pointer into self
|
|
|
|
strings = std::copy(str.begin(), str.end(), strings); // copy the string into strings
|
|
|
|
*strings++ = (CharType_t)(0); // each string needs a null terminator
|
2013-02-23 08:22:56 +08:00
|
|
|
}
|
2016-04-28 07:10:14 +08:00
|
|
|
*pointers++ = NULL; // array of pointers needs a null terminator
|
2013-02-24 04:47:10 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
// Make sure we know what we're doing.
|
2013-02-24 04:47:10 +08:00
|
|
|
assert((unsigned char *)pointers - base == (std::ptrdiff_t)pointers_allocation_len);
|
2016-04-28 07:10:14 +08:00
|
|
|
assert((unsigned char *)strings - (unsigned char *)pointers ==
|
|
|
|
(std::ptrdiff_t)strings_allocation_len);
|
|
|
|
assert((unsigned char *)strings - base ==
|
|
|
|
(std::ptrdiff_t)(pointers_allocation_len + strings_allocation_len));
|
2013-02-24 04:47:10 +08:00
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
return reinterpret_cast<CharType_t **>(base);
|
2013-02-23 08:22:56 +08:00
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
wchar_t **make_null_terminated_array(const wcstring_list_t &lst) {
|
2013-02-23 08:22:56 +08:00
|
|
|
return make_null_terminated_array_helper(lst);
|
|
|
|
}
|
|
|
|
|
2016-04-28 07:10:14 +08:00
|
|
|
char **make_null_terminated_array(const std::vector<std::string> &lst) {
|
2013-02-23 08:22:56 +08:00
|
|
|
return make_null_terminated_array_helper(lst);
|
|
|
|
}
|
2016-05-17 05:30:31 +08:00
|
|
|
|
2016-12-15 11:21:36 +08:00
|
|
|
/// Test if the specified character is in a range that fish uses interally to store special tokens.
|
|
|
|
///
|
|
|
|
/// NOTE: This is used when tokenizing the input. It is also used when reading input, before
|
|
|
|
/// tokenization, to replace such chars with REPLACEMENT_WCHAR if they're not part of a quoted
|
|
|
|
/// string. We don't want external input to be able to feed reserved characters into our
|
|
|
|
/// lexer/parser or code evaluator.
|
2016-10-18 07:23:29 +08:00
|
|
|
//
|
|
|
|
// TODO: Actually implement the replacement as documented above.
|
|
|
|
bool fish_reserved_codepoint(wchar_t c) {
|
|
|
|
return (c >= RESERVED_CHAR_BASE && c < RESERVED_CHAR_END) ||
|
2019-03-24 09:07:32 +08:00
|
|
|
(c >= ENCODE_DIRECT_BASE && c < ENCODE_DIRECT_END);
|
2016-10-18 07:23:29 +08:00
|
|
|
}
|
2016-12-15 11:21:36 +08:00
|
|
|
|
2017-01-11 13:52:10 +08:00
|
|
|
/// Reopen stdin, stdout and/or stderr on /dev/null. This is invoked when we find that our tty has
|
|
|
|
/// become invalid.
|
2016-12-15 11:21:36 +08:00
|
|
|
void redirect_tty_output() {
|
|
|
|
struct termios t;
|
|
|
|
int fd = open("/dev/null", O_WRONLY);
|
2018-02-09 07:05:13 +08:00
|
|
|
if (fd == -1) {
|
|
|
|
__fish_assert("Could not open /dev/null!", __FILE__, __LINE__, errno);
|
|
|
|
}
|
2017-01-11 13:52:10 +08:00
|
|
|
if (tcgetattr(STDIN_FILENO, &t) == -1 && errno == EIO) dup2(fd, STDIN_FILENO);
|
|
|
|
if (tcgetattr(STDOUT_FILENO, &t) == -1 && errno == EIO) dup2(fd, STDOUT_FILENO);
|
|
|
|
if (tcgetattr(STDERR_FILENO, &t) == -1 && errno == EIO) dup2(fd, STDERR_FILENO);
|
2016-12-15 11:21:36 +08:00
|
|
|
close(fd);
|
|
|
|
}
|
2017-02-14 12:37:27 +08:00
|
|
|
|
|
|
|
/// Display a failed assertion message, dump a stack trace if possible, then die.
|
2017-06-17 12:50:08 +08:00
|
|
|
[[noreturn]] void __fish_assert(const char *msg, const char *file, size_t line, int error) {
|
2017-02-15 13:09:15 +08:00
|
|
|
if (error) {
|
2019-05-30 17:54:09 +08:00
|
|
|
FLOGF(error, L"%s:%zu: failed assertion: %s: errno %d (%s)", file, line, msg, error,
|
2019-06-04 11:30:48 +08:00
|
|
|
std::strerror(error));
|
2017-02-15 13:09:15 +08:00
|
|
|
} else {
|
2019-05-30 17:54:09 +08:00
|
|
|
FLOGF(error, L"%s:%zu: failed assertion: %s", file, line, msg);
|
2017-02-15 13:09:15 +08:00
|
|
|
}
|
2017-02-14 12:37:27 +08:00
|
|
|
show_stackframe(L'E', 99, 1);
|
|
|
|
abort();
|
|
|
|
}
|
2017-04-20 14:43:02 +08:00
|
|
|
|
|
|
|
/// Test if the given char is valid in a variable name.
|
|
|
|
bool valid_var_name_char(wchar_t chr) { return fish_iswalnum(chr) || chr == L'_'; }
|
|
|
|
|
|
|
|
/// Test if the given string is a valid variable name.
|
2019-03-15 01:28:48 +08:00
|
|
|
bool valid_var_name(const wcstring &str) {
|
2019-03-15 01:37:13 +08:00
|
|
|
return std::find_if_not(str.begin(), str.end(), valid_var_name_char) == str.end();
|
2017-04-20 14:43:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Test if the string is a valid function name.
|
|
|
|
bool valid_func_name(const wcstring &str) {
|
2019-03-15 01:28:48 +08:00
|
|
|
if (str.empty()) return false;
|
2017-04-20 14:43:02 +08:00
|
|
|
if (str.at(0) == L'-') return false;
|
|
|
|
if (str.find_first_of(L'/') != wcstring::npos) return false;
|
|
|
|
return true;
|
|
|
|
}
|
2018-10-10 11:33:20 +08:00
|
|
|
|
|
|
|
/// Return the path to the current executable. This needs to be realpath'd.
|
|
|
|
std::string get_executable_path(const char *argv0) {
|
|
|
|
char buff[PATH_MAX];
|
|
|
|
|
|
|
|
#if __APPLE__
|
|
|
|
// On OS X use it's proprietary API to get the path to the executable.
|
|
|
|
// This is basically grabbing exec_path after argc, argv, envp, ...: for us
|
|
|
|
// https://opensource.apple.com/source/adv_cmds/adv_cmds-163/ps/print.c
|
|
|
|
uint32_t buffSize = sizeof buff;
|
|
|
|
if (_NSGetExecutablePath(buff, &buffSize) == 0) return std::string(buff);
|
|
|
|
#elif __FreeBSD__
|
|
|
|
// FreeBSD does not have /proc by default, but it can be mounted as procfs via the
|
|
|
|
// Linux compatibility layer. Per sysctl(3), passing in a process ID of -1 returns
|
|
|
|
// the value for the current process.
|
|
|
|
size_t buff_size = sizeof buff;
|
2019-05-05 18:09:25 +08:00
|
|
|
int name[] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
|
2018-10-10 11:33:20 +08:00
|
|
|
int result = sysctl(name, sizeof(name) / sizeof(int), buff, &buff_size, nullptr, 0);
|
|
|
|
if (result != 0) {
|
|
|
|
wperror(L"sysctl KERN_PROC_PATHNAME");
|
2019-05-05 18:09:25 +08:00
|
|
|
} else {
|
2018-10-10 11:33:20 +08:00
|
|
|
return std::string(buff);
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
// On other unixes, fall back to the Linux-ish /proc/ directory
|
|
|
|
ssize_t len;
|
|
|
|
len = readlink("/proc/self/exe", buff, sizeof buff - 1); // Linux
|
|
|
|
if (len == -1) {
|
|
|
|
len = readlink("/proc/curproc/file", buff, sizeof buff - 1); // other BSDs
|
|
|
|
if (len == -1) {
|
|
|
|
len = readlink("/proc/self/path/a.out", buff, sizeof buff - 1); // Solaris
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (len > 0) {
|
|
|
|
buff[len] = '\0';
|
|
|
|
return std::string(buff);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Just return argv0, which probably won't work (i.e. it's not an absolute path or a path
|
|
|
|
// relative to the working directory, but instead something the caller found via $PATH). We'll
|
|
|
|
// eventually fall back to the compile time paths.
|
|
|
|
return std::string(argv0 ? argv0 : "");
|
|
|
|
}
|
|
|
|
|
2019-02-06 12:36:38 +08:00
|
|
|
/// Return a path to a directory where we can store temporary files.
|
|
|
|
std::string get_path_to_tmp_dir() {
|
|
|
|
char *env_tmpdir = getenv("TMPDIR");
|
|
|
|
if (env_tmpdir) {
|
|
|
|
return env_tmpdir;
|
|
|
|
}
|
|
|
|
#if defined(_CS_DARWIN_USER_TEMP_DIR)
|
|
|
|
char osx_tmpdir[PATH_MAX];
|
|
|
|
size_t n = confstr(_CS_DARWIN_USER_TEMP_DIR, osx_tmpdir, PATH_MAX);
|
|
|
|
if (0 < n && n <= PATH_MAX) {
|
|
|
|
return osx_tmpdir;
|
|
|
|
} else {
|
|
|
|
return "/tmp";
|
|
|
|
}
|
|
|
|
#elif defined(P_tmpdir)
|
|
|
|
return P_tmpdir;
|
|
|
|
#elif defined(_PATH_TMP)
|
|
|
|
return _PATH_TMP;
|
|
|
|
#else
|
|
|
|
return "/tmp";
|
|
|
|
#endif
|
|
|
|
}
|
2019-01-30 09:59:41 +08:00
|
|
|
|
|
|
|
// This function attempts to distinguish between a console session (at the actual login vty) and a
|
|
|
|
// session within a terminal emulator inside a desktop environment or over SSH. Unfortunately
|
|
|
|
// there are few values of $TERM that we can interpret as being exclusively console sessions, and
|
|
|
|
// most common operating systems do not use them. The value is cached for the duration of the fish
|
|
|
|
// session. We err on the side of assuming it's not a console session. This approach isn't
|
|
|
|
// bullet-proof and that's OK.
|
|
|
|
bool is_console_session() {
|
2019-04-29 06:56:49 +08:00
|
|
|
static const bool console_session = []() {
|
2019-01-30 09:59:41 +08:00
|
|
|
ASSERT_IS_MAIN_THREAD();
|
|
|
|
|
|
|
|
const char *tty_name = ttyname(0);
|
|
|
|
auto len = strlen("/dev/tty");
|
|
|
|
const char *TERM = getenv("TERM");
|
|
|
|
return
|
|
|
|
// Test that the tty matches /dev/(console|dcons|tty[uv\d])
|
2019-05-05 18:09:25 +08:00
|
|
|
tty_name &&
|
|
|
|
((strncmp(tty_name, "/dev/tty", len) == 0 &&
|
|
|
|
(tty_name[len] == 'u' || tty_name[len] == 'v' || isdigit(tty_name[len]))) ||
|
|
|
|
strcmp(tty_name, "/dev/dcons") == 0 || strcmp(tty_name, "/dev/console") == 0)
|
2019-01-30 09:59:41 +08:00
|
|
|
// and that $TERM is simple, e.g. `xterm` or `vt100`, not `xterm-something`
|
|
|
|
&& (!TERM || !strchr(TERM, '-') || !strcmp(TERM, "sun-color"));
|
|
|
|
}();
|
|
|
|
return console_session;
|
|
|
|
}
|