diff options
Diffstat (limited to 'common.cpp')
-rw-r--r-- | common.cpp | 1407 |
1 files changed, 705 insertions, 702 deletions
@@ -24,7 +24,6 @@ parts of fish. #include <stdio.h> #include <dirent.h> #include <sys/types.h> -#include <pwd.h> #ifdef HAVE_SYS_IOCTL_H #include <sys/ioctl.h> @@ -46,9 +45,10 @@ parts of fish. #include <execinfo.h> #endif - #if HAVE_NCURSES_H #include <ncurses.h> +#elif HAVE_NCURSES_CURSES_H +#include <ncurses/curses.h> #else #include <curses.h> #endif @@ -73,6 +73,7 @@ parts of fish. #include "util.cpp" #include "fallback.cpp" +#define NOT_A_WCHAR (static_cast<wint_t>(WEOF)) struct termios shell_modes; @@ -83,16 +84,19 @@ static bool thread_assertions_configured_for_testing = false; wchar_t ellipsis_char; wchar_t omitted_newline_char; -char *profile=0; +bool g_profiling_active = false; const wchar_t *program_name; int debug_level=1; /** - This struct should be continually updated by signals as the term resizes, and as such always contain the correct current size. + This struct maintains the current state of the terminal size. It is updated on demand after receiving a SIGWINCH. + Do not touch this struct directly, it's managed with a rwlock. Use common_get_width()/common_get_height(). */ static struct winsize termsize; +static volatile bool termsize_valid; +static rwlock_t termsize_rwlock; static char *wcs2str_internal(const wchar_t *in, char *out); @@ -105,7 +109,7 @@ void show_stackframe() return; void *trace[32]; - int i, trace_size = 0; + int trace_size = 0; trace_size = backtrace(trace, 32); char **messages = backtrace_symbols(trace, trace_size); @@ -113,7 +117,7 @@ void show_stackframe() if (messages) { debug(0, L"Backtrace:"); - for (i=0; i<trace_size; i++) + for (int i=0; i<trace_size; i++) { fwprintf(stderr, L"%s\n", messages[i]); } @@ -213,14 +217,14 @@ static wcstring str2wcs_internal(const char *in, const size_t in_len) wc = ENCODE_DIRECT_BASE + (unsigned char)in[in_pos]; result.push_back(wc); in_pos++; - bzero(&state, sizeof state); + memset(&state, 0, sizeof state); } else if (ret == 0) { /* Embedded null byte! */ result.push_back(L'\0'); in_pos++; - bzero(&state, sizeof state); + memset(&state, 0, sizeof state); } else { @@ -252,7 +256,6 @@ char *wcs2str(const wchar_t *in) { if (! in) return NULL; - char *out; size_t desired_size = MAX_UTF8_BYTES*wcslen(in)+1; char local_buff[512]; if (desired_size <= sizeof local_buff / sizeof *local_buff) @@ -274,15 +277,13 @@ char *wcs2str(const wchar_t *in) else { // here we fall into the bad case of allocating a buffer probably much larger than necessary - out = (char *)malloc(MAX_UTF8_BYTES*wcslen(in)+1); + char *out = (char *)malloc(MAX_UTF8_BYTES*wcslen(in)+1); if (!out) { DIE_MEM(); } return wcs2str_internal(in, out); } - - return wcs2str_internal(in, out); } char *wcs2str(const wcstring &in) @@ -314,7 +315,7 @@ std::string wcs2string(const wcstring &input) } else { - bzero(converted, sizeof converted); + memset(converted, 0, sizeof converted); size_t len = wcrtomb(converted, wc, &state); if (len == (size_t)(-1)) { @@ -382,27 +383,6 @@ static char *wcs2str_internal(const wchar_t *in, char *out) return out; } -char **wcsv2strv(const wchar_t * const *in) -{ - size_t i, count = 0; - - while (in[count] != 0) - count++; - char **res = (char **)malloc(sizeof(char *)*(count+1)); - if (res == 0) - { - DIE_MEM(); - } - - for (i=0; i<count; i++) - { - res[i]=wcs2str(in[i]); - } - res[count]=0; - return res; - -} - wcstring format_string(const wchar_t *format, ...) { va_list va; @@ -412,7 +392,7 @@ wcstring format_string(const wchar_t *format, ...) return result; } -wcstring vformat_string(const wchar_t *format, va_list va_orig) +void append_formatv(wcstring &target, const wchar_t *format, va_list va_orig) { const int saved_err = errno; /* @@ -463,22 +443,21 @@ wcstring vformat_string(const wchar_t *format, va_list va_orig) va_end(va); } - wcstring result = wcstring(buff); + target.append(buff); if (buff != static_buff) + { free(buff); + } errno = saved_err; - return result; } -void append_formatv(wcstring &str, const wchar_t *format, va_list ap) +wcstring vformat_string(const wchar_t *format, va_list va_orig) { - /* Preserve errno across this call since it likes to stomp on it */ - int err = errno; - str.append(vformat_string(format, ap)); - errno = err; - + wcstring result; + append_formatv(result, format, va_orig); + return result; } void append_format(wcstring &str, const wchar_t *format, ...) @@ -489,17 +468,17 @@ void append_format(wcstring &str, const wchar_t *format, ...) va_end(va); } -wchar_t *wcsvarname(const wchar_t *str) +const wchar_t *wcsvarname(const wchar_t *str) { while (*str) { if ((!iswalnum(*str)) && (*str != L'_')) { - return (wchar_t *)str; + return str; } str++; } - return 0; + return NULL; } const wchar_t *wcsfuncname(const wchar_t *str) @@ -508,18 +487,19 @@ const wchar_t *wcsfuncname(const wchar_t *str) } -int wcsvarchr(wchar_t chr) +bool wcsvarchr(wchar_t chr) { return iswalnum(chr) || chr == L'_'; } +int fish_wcswidth(const wchar_t *str) +{ + return fish_wcswidth(str, wcslen(str)); +} -/** - The glibc version of wcswidth seems to hang on some strings. fish uses this replacement. -*/ -int my_wcswidth(const wchar_t *c) +int fish_wcswidth(const wcstring& str) { - return fish_wcswidth(c, wcslen(c)); + return fish_wcswidth(str.c_str(), str.size()); } wchar_t *quote_end(const wchar_t *pos) @@ -576,7 +556,7 @@ wcstring wsetlocale(int category, const wchar_t *locale) return format_string(L"%s", res); } -bool contains_internal(const wchar_t *a, ...) +bool contains_internal(const wchar_t *a, int vararg_handle, ...) { const wchar_t *arg; va_list va; @@ -584,7 +564,7 @@ bool contains_internal(const wchar_t *a, ...) CHECK(a, 0); - va_start(va, a); + va_start(va, vararg_handle); while ((arg=va_arg(va, const wchar_t *))!= 0) { if (wcscmp(a,arg) == 0) @@ -598,17 +578,19 @@ bool contains_internal(const wchar_t *a, ...) return res; } -/* wcstring variant of contains_internal. The first parameter is a wcstring, the rest are const wchar_t* */ -__sentinel bool contains_internal(const wcstring &needle, ...) +/* wcstring variant of contains_internal. The first parameter is a wcstring, the rest are const wchar_t *. vararg_handle exists only to give us a POD-value to apss to va_start */ +__sentinel bool contains_internal(const wcstring &needle, int vararg_handle, ...) { const wchar_t *arg; va_list va; int res = 0; - va_start(va, needle); + const wchar_t *needle_cstr = needle.c_str(); + va_start(va, vararg_handle); while ((arg=va_arg(va, const wchar_t *))!= 0) { - if (needle == arg) + /* libc++ has an unfortunate implementation of operator== that unconditonally wcslen's the wchar_t* parameter, so prefer wcscmp directly */ + if (! wcscmp(needle_cstr, arg)) { res=1; break; @@ -711,6 +693,23 @@ void debug(int level, const char *msg, ...) errno = errno_old; } +void print_stderr(const wcstring &str) +{ + fprintf(stderr, "%ls\n", str.c_str()); +} + +void read_ignore(int fd, void *buff, size_t count) +{ + size_t ignore __attribute__((unused)); + ignore = read(fd, buff, count); +} + +void write_ignore(int fd, const void *buff, size_t count) +{ + size_t ignore __attribute__((unused)); + ignore = write(fd, buff, count); +} + 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) { @@ -731,7 +730,7 @@ void debug_safe(int level, const char *msg, const char *param1, const char *para if (end == NULL) end = cursor + strlen(cursor); - write(STDERR_FILENO, cursor, end - cursor); + write_ignore(STDERR_FILENO, cursor, end - cursor); if (end[0] == '%' && end[1] == 's') { @@ -740,7 +739,7 @@ void debug_safe(int level, const char *msg, const char *param1, const char *para const char *format = params[param_idx++]; if (! format) format = "(null)"; - write(STDERR_FILENO, format, strlen(format)); + write_ignore(STDERR_FILENO, format, strlen(format)); cursor = end + 2; } else if (end[0] == '\0') @@ -756,12 +755,12 @@ void debug_safe(int level, const char *msg, const char *param1, const char *para } // We always append a newline - write(STDERR_FILENO, "\n", 1); + write_ignore(STDERR_FILENO, "\n", 1); errno = errno_old; } -void format_long_safe(char buff[128], long val) +void format_long_safe(char buff[64], long val) { if (val == 0) { @@ -795,7 +794,7 @@ void format_long_safe(char buff[128], long val) } } -void format_long_safe(wchar_t buff[128], long val) +void format_long_safe(wchar_t buff[64], long val) { if (val == 0) { @@ -807,10 +806,9 @@ void format_long_safe(wchar_t buff[128], long val) size_t idx = 0; bool negative = (val < 0); - while (val > 0) + while (val != 0) { long rem = val % 10; - /* Here we're assuming that wide character digits are contiguous - is that a correct assumption? */ buff[idx++] = L'0' + (wchar_t)(rem < 0 ? -rem : rem); val /= 10; } @@ -830,19 +828,18 @@ void format_long_safe(wchar_t buff[128], long val) void write_screen(const wcstring &msg, wcstring &buff) { - const wchar_t *start, *pos; int line_width = 0; - int tok_width = 0; int screen_width = common_get_width(); if (screen_width) { - start = pos = msg.c_str(); + const wchar_t *start = msg.c_str(); + const wchar_t *pos = start; while (1) { int overflow = 0; - tok_width=0; + int tok_width=0; /* Tokenize on whitespace, and also calculate the width of the token @@ -917,61 +914,28 @@ void write_screen(const wcstring &msg, wcstring &buff) buff.push_back(L'\n'); } -/** - Perform string escaping of a strinng by only quoting it. Assumes - the string has already been checked for characters that can not be - escaped this way. - */ -static wchar_t *escape_simple(const wchar_t *in) -{ - wchar_t *out; - size_t len = wcslen(in); - out = (wchar_t *)malloc(sizeof(wchar_t)*(len+3)); - if (!out) - DIE_MEM(); - - out[0] = L'\''; - wcscpy(&out[1], in); - out[len+1]=L'\''; - out[len+2]=0; - return out; -} - -wchar_t *escape(const wchar_t *in_orig, escape_flags_t flags) +/* Escape a string, storing the result in out_str */ +static void escape_string_internal(const wchar_t *orig_in, size_t in_len, wcstring *out_str, escape_flags_t flags) { - const wchar_t *in = in_orig; + assert(orig_in != NULL); + const wchar_t *in = orig_in; bool escape_all = !!(flags & ESCAPE_ALL); bool no_quoted = !!(flags & ESCAPE_NO_QUOTED); bool no_tilde = !!(flags & ESCAPE_NO_TILDE); - wchar_t *out; - wchar_t *pos; - int need_escape=0; int need_complex_escape=0; - if (!in) - { - debug(0, L"%s called with null input", __func__); - FATAL_EXIT(); - } + /* Avoid dereferencing all over the place */ + wcstring &out = *out_str; - if (!no_quoted && (wcslen(in) == 0)) + if (!no_quoted && in_len == 0) { - out = wcsdup(L"''"); - if (!out) - DIE_MEM(); - return out; + out.assign(L"''"); + return; } - - out = (wchar_t *)malloc(sizeof(wchar_t)*(wcslen(in)*4 + 1)); - pos = out; - - if (!out) - DIE_MEM(); - while (*in != 0) { @@ -981,14 +945,14 @@ wchar_t *escape(const wchar_t *in_orig, escape_flags_t flags) int val = *in - ENCODE_DIRECT_BASE; int tmp; - *(pos++) = L'\\'; - *(pos++) = L'X'; + out += L'\\'; + out += L'X'; tmp = val/16; - *pos++ = tmp > 9? L'a'+(tmp-10):L'0'+tmp; + out += tmp > 9? L'a'+(tmp-10):L'0'+tmp; tmp = val%16; - *pos++ = tmp > 9? L'a'+(tmp-10):L'0'+tmp; + out += tmp > 9? L'a'+(tmp-10):L'0'+tmp; need_escape=need_complex_escape=1; } @@ -998,32 +962,32 @@ wchar_t *escape(const wchar_t *in_orig, escape_flags_t flags) switch (c) { case L'\t': - *(pos++) = L'\\'; - *(pos++) = L't'; + out += L'\\'; + out += L't'; need_escape=need_complex_escape=1; break; case L'\n': - *(pos++) = L'\\'; - *(pos++) = L'n'; + out += L'\\'; + out += L'n'; need_escape=need_complex_escape=1; break; case L'\b': - *(pos++) = L'\\'; - *(pos++) = L'b'; + out += L'\\'; + out += L'b'; need_escape=need_complex_escape=1; break; case L'\r': - *(pos++) = L'\\'; - *(pos++) = L'r'; + out += L'\\'; + out += L'r'; need_escape=need_complex_escape=1; break; case L'\x1b': - *(pos++) = L'\\'; - *(pos++) = L'e'; + out += L'\\'; + out += L'e'; need_escape=need_complex_escape=1; break; @@ -1033,11 +997,26 @@ wchar_t *escape(const wchar_t *in_orig, escape_flags_t flags) { need_escape=need_complex_escape=1; if (escape_all) - *pos++ = L'\\'; - *pos++ = *in; + out += L'\\'; + out += *in; break; } + + + // Experimental fix for #1614 + // The hope is that any time these appear in a string, they came from wildcard expansion + case ANY_CHAR: + out += L'?'; + break; + case ANY_STRING: + out += L'*'; + break; + + case ANY_STRING_RECURSIVE: + out += L"**"; + break; + case L'&': case L'$': case L' ': @@ -1063,9 +1042,9 @@ wchar_t *escape(const wchar_t *in_orig, escape_flags_t flags) { need_escape=1; if (escape_all) - *pos++ = L'\\'; + out += L'\\'; } - *pos++ = *in; + out += *in; break; } @@ -1075,9 +1054,9 @@ wchar_t *escape(const wchar_t *in_orig, escape_flags_t flags) { if (*in <27 && *in > 0) { - *(pos++) = L'\\'; - *(pos++) = L'c'; - *(pos++) = L'a' + *in -1; + out += L'\\'; + out += L'c'; + out += L'a' + *in -1; need_escape=need_complex_escape=1; break; @@ -1086,15 +1065,15 @@ wchar_t *escape(const wchar_t *in_orig, escape_flags_t flags) int tmp = (*in)%16; - *pos++ = L'\\'; - *pos++ = L'x'; - *pos++ = ((*in>15)? L'1' : L'0'); - *pos++ = tmp > 9? L'a'+(tmp-10):L'0'+tmp; + out += L'\\'; + out += L'x'; + out += ((*in>15)? L'1' : L'0'); + out += tmp > 9? L'a'+(tmp-10):L'0'+tmp; need_escape=need_complex_escape=1; } else { - *pos++ = *in; + out += *in; } break; } @@ -1103,7 +1082,6 @@ wchar_t *escape(const wchar_t *in_orig, escape_flags_t flags) in++; } - *pos = 0; /* Use quoted escaping if possible, since most people find it @@ -1111,627 +1089,639 @@ wchar_t *escape(const wchar_t *in_orig, escape_flags_t flags) */ if (!no_quoted && need_escape && !need_complex_escape && escape_all) { - free(out); - out = escape_simple(in_orig); + 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); } +} - return out; +wcstring escape(const wchar_t *in, escape_flags_t flags) +{ + if (!in) + { + debug(0, L"%s called with null input", __func__); + FATAL_EXIT(); + } + + wcstring result; + escape_string_internal(in, wcslen(in), &result, flags); + return result; } wcstring escape_string(const wcstring &in, escape_flags_t flags) { - wchar_t *tmp = escape(in.c_str(), flags); - wcstring result(tmp); - free(tmp); + wcstring result; + escape_string_internal(in.c_str(), in.size(), &result, flags); return result; } -wchar_t *unescape(const wchar_t * orig, int flags) +/* Helper to return the last character in a string, or NOT_A_WCHAR */ +static wint_t string_last_char(const wcstring &str) { - int out_pos; - size_t in_pos; - size_t len; - int c; - int bracket_count=0; - wchar_t prev=0; - wchar_t *in; - bool unescape_special = !!(flags & UNESCAPE_SPECIAL); - bool allow_incomplete = !!(flags & UNESCAPE_INCOMPLETE); - - CHECK(orig, 0); + size_t len = str.size(); + return len == 0 ? NOT_A_WCHAR : str.at(len - 1); +} - len = wcslen(orig); - in = wcsdup(orig); +/* 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 */ +static size_t read_unquoted_escape(const wchar_t *input, wcstring *result, bool allow_incomplete, bool unescape_special) +{ + if (input[0] != L'\\') + { + // not an escape + return 0; + } - if (!in) - DIE_MEM(); + /* Here's the character we'll ultimately append, or NOT_A_WCHAR for none. Note that L'\0' is a valid thing to append. */ + wint_t result_char_or_none = NOT_A_WCHAR; - enum + bool errored = false; + size_t in_pos = 1; //in_pos always tracks the next character to read (and therefore the number of characters read so far) + const wchar_t c = input[in_pos++]; + switch (c) { - mode_unquoted, - mode_single_quotes, - mode_double_quotes - } mode = mode_unquoted; - for (in_pos=0, out_pos=0; - in_pos<len; - (prev=(out_pos>=0)?in[out_pos]:0), out_pos++, in_pos++) - { - c = in[in_pos]; - switch (mode) + /* A null character after a backslash is an error */ + case L'\0': { + /* Adjust in_pos to only include the backslash */ + assert(in_pos > 0); + in_pos--; - /* - Mode 0 means unquoted string - */ - case mode_unquoted: + /* It's an error, unless we're allowing incomplete escapes */ + if (! allow_incomplete) + errored = true; + break; + } + + /* Numeric escape sequences. No prefix means octal escape, otherwise hexadecimal. */ + 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': + case L'X': + { + long long res=0; + size_t chars=2; + int base=16; + + bool byte_literal = false; + wchar_t max_val = ASCII_MAX; + + switch (c) { - if (c == L'\\') + case L'u': { - switch (in[++in_pos]) - { - - /* - A null character after a backslash is an - error, return null - */ - case L'\0': - { - if (!allow_incomplete) - { - free(in); - return 0; - } - } + chars=4; + max_val = UCS2_MAX; + break; + } - /* - Numeric escape sequences. No prefix means - octal escape, otherwise hexadecimal. - */ - - 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': - case L'X': - { - int i; - long long res=0; - int chars=2; - int base=16; + case L'U': + { + chars=8; + max_val = WCHAR_MAX; - int byte = 0; - wchar_t max_val = ASCII_MAX; + // Don't exceed the largest Unicode code point - see #1107 + if (0x10FFFF < max_val) + max_val = (wchar_t)0x10FFFF; - switch (in[in_pos]) - { - case L'u': - { - chars=4; - max_val = UCS2_MAX; - break; - } - - case L'U': - { - chars=8; - max_val = WCHAR_MAX; - break; - } - - case L'x': - { - break; - } - - case L'X': - { - byte=1; - max_val = BYTE_MAX; - break; - } - - default: - { - base=8; - chars=3; - // note in_pod must be larger than 0 since we incremented it above - assert(in_pos > 0); - in_pos--; - break; - } - } + break; + } - for (i=0; i<chars; i++) - { - long d = convert_digit(in[++in_pos],base); + case L'x': + { + chars = 2; + max_val = ASCII_MAX; + break; + } - if (d < 0) - { - in_pos--; - break; - } + case L'X': + { + byte_literal = true; + max_val = BYTE_MAX; + break; + } - res=(res*base)+d; - } + 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 + assert(in_pos > 0); + in_pos--; + break; + } + } - if ((res <= max_val)) - { - in[out_pos] = (wchar_t)((byte?ENCODE_DIRECT_BASE:0)+res); - } - else - { - free(in); - return 0; - } + for (size_t i=0; i<chars; i++) + { + long d = convert_digit(input[in_pos],base); + if (d < 0) + { + break; + } - break; - } + res=(res*base)+d; + in_pos++; + } - /* - \a means bell (alert) - */ - case L'a': - { - in[out_pos]=L'\a'; - break; - } + if (res <= max_val) + { + result_char_or_none = (wchar_t)((byte_literal ? ENCODE_DIRECT_BASE : 0)+res); + } + else + { + errored = true; + } - /* - \b means backspace - */ - case L'b': - { - in[out_pos]=L'\b'; - break; - } + break; + } - /* - \cX means control sequence X - */ - case L'c': - { - in_pos++; - if (in[in_pos] >= L'a' && - in[in_pos] <= (L'a'+32)) - { - in[out_pos]=in[in_pos]-L'a'+1; - } - else if (in[in_pos] >= L'A' && - in[in_pos] <= (L'A'+32)) - { - in[out_pos]=in[in_pos]-L'A'+1; - } - else - { - free(in); - return 0; - } - break; + /* \a means bell (alert) */ + case L'a': + { + result_char_or_none = L'\a'; + break; + } - } + /* \b means backspace */ + case L'b': + { + result_char_or_none = L'\b'; + break; + } - /* - \x1b means escape - */ - case L'e': - { - in[out_pos]=L'\x1b'; - break; - } + /* \cX means control sequence X */ + case L'c': + { + const wchar_t sequence_char = input[in_pos++]; + 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 + { + errored = true; + } + break; + } - /* - \f means form feed - */ - case L'f': - { - in[out_pos]=L'\f'; - break; - } + /* \x1b means escape */ + case L'e': + { + result_char_or_none = L'\x1b'; + break; + } - /* - \n means newline - */ - case L'n': - { - in[out_pos]=L'\n'; - break; - } + /* + \f means form feed + */ + case L'f': + { + result_char_or_none = L'\f'; + break; + } - /* - \r means carriage return - */ - case L'r': - { - in[out_pos]=L'\r'; - break; - } + /* + \n means newline + */ + case L'n': + { + result_char_or_none = L'\n'; + break; + } - /* - \t means tab - */ - case L't': - { - in[out_pos]=L'\t'; - break; - } + /* + \r means carriage return + */ + case L'r': + { + result_char_or_none = L'\r'; + break; + } - /* - \v means vertical tab - */ - case L'v': - { - in[out_pos]=L'\v'; - break; - } + /* + \t means tab + */ + case L't': + { + result_char_or_none = L'\t'; + break; + } - /* If a backslash is followed by an actual newline, swallow them both */ - case L'\n': - out_pos--; - break; + /* + \v means vertical tab + */ + case L'v': + { + result_char_or_none = L'\v'; + break; + } - default: - { - if (unescape_special) - in[out_pos++] = INTERNAL_SEPARATOR; - in[out_pos]=in[in_pos]; - break; - } - } - } - else - { - switch (in[in_pos]) - { - case L'~': - { - if (unescape_special && (in_pos == 0)) - { - in[out_pos]=HOME_DIRECTORY; - } - else - { - in[out_pos] = L'~'; - } - break; - } + /* If a backslash is followed by an actual newline, swallow them both */ + case L'\n': + { + result_char_or_none = NOT_A_WCHAR; + break; + } - case L'%': - { - if (unescape_special && (in_pos == 0)) - { - in[out_pos]=PROCESS_EXPAND; - } - else - { - in[out_pos]=in[in_pos]; - } - break; - } + default: + { + if (unescape_special) + result->push_back(INTERNAL_SEPARATOR); + result_char_or_none = c; + break; + } + } - case L'*': - { - if (unescape_special) - { - if (out_pos > 0 && in[out_pos-1]==ANY_STRING) - { - out_pos--; - in[out_pos] = ANY_STRING_RECURSIVE; - } - else - in[out_pos]=ANY_STRING; - } - else - { - in[out_pos]=in[in_pos]; - } - break; - } + if (! errored && result_char_or_none != NOT_A_WCHAR) + { + wchar_t result_char = static_cast<wchar_t>(result_char_or_none); + // if result_char is not NOT_A_WCHAR, it must be a valid wchar + assert((wint_t)result_char == result_char_or_none); + result->push_back(result_char); + } + return errored ? 0 : in_pos; +} - case L'?': - { - if (unescape_special) - { - in[out_pos]=ANY_CHAR; - } - else - { - in[out_pos]=in[in_pos]; - } - break; - } +/* 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 */ + wcstring result; + result.reserve(input_len); - case L'$': - { - if (unescape_special) - { - in[out_pos]=VARIABLE_EXPAND; - } - else - { - in[out_pos]=in[in_pos]; - } - break; - } + const bool unescape_special = !!(flags & UNESCAPE_SPECIAL); + const bool allow_incomplete = !!(flags & UNESCAPE_INCOMPLETE); - case L'{': - { - if (unescape_special) - { - bracket_count++; - in[out_pos]=BRACKET_BEGIN; - } - else - { - in[out_pos]=in[in_pos]; - } - break; - } + int bracket_count = 0; - case L'}': - { - if (unescape_special) - { - bracket_count--; - in[out_pos]=BRACKET_END; - } - else - { - in[out_pos]=in[in_pos]; - } - break; - } + bool errored = false; + enum + { + mode_unquoted, + mode_single_quotes, + mode_double_quotes + } mode = mode_unquoted; - case L',': - { - if (unescape_special && bracket_count && prev!=BRACKET_SEP) - { - in[out_pos]=BRACKET_SEP; - } - else - { - in[out_pos]=in[in_pos]; - } - break; - } + for (size_t input_position = 0; input_position < input_len && ! errored; input_position++) + { + const wchar_t c = input[input_position]; + /* Here's the character we'll append to result, or NOT_A_WCHAR to suppress it */ + wint_t to_append_or_none = c; + if (mode == mode_unquoted) + { - case L'\'': - { - mode = mode_single_quotes; - if (unescape_special) - in[out_pos] = INTERNAL_SEPARATOR; - else - out_pos--; - break; - } + switch (c) + { + case L'\\': + { + /* Backslashes (escapes) are complicated and may result in errors, or appending INTERNAL_SEPARATORs, so we have to handle them specially */ + size_t escape_chars = read_unquoted_escape(input + input_position, &result, allow_incomplete, unescape_special); + if (escape_chars == 0) + { + /* A 0 return indicates an error */ + errored = true; + } + else + { + /* Skip over the characters we read, minus one because the outer loop will increment it */ + assert(escape_chars > 0); + input_position += escape_chars - 1; + } + /* We've already appended, don't append anything else */ + to_append_or_none = NOT_A_WCHAR; + break; + } - case L'\"': - { - mode = mode_double_quotes; - if (unescape_special) - in[out_pos] = INTERNAL_SEPARATOR; - else - out_pos--; - break; - } + case L'~': + { + if (unescape_special && (input_position == 0)) + { + to_append_or_none = HOME_DIRECTORY; + } + break; + } - default: - { - in[out_pos] = in[in_pos]; - break; - } + case L'%': + { + if (unescape_special && (input_position == 0)) + { + to_append_or_none = PROCESS_EXPAND; } + break; } - break; - } - /* - Mode 1 means single quoted string, i.e 'foo'. - A backslash at the end of a line in a single quoted string does not swallow the backslash or newline. - */ - case mode_single_quotes: - { - if (c == L'\\') + case L'*': { - switch (in[++in_pos]) + if (unescape_special) { - case '\\': - case L'\'': + /* 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) { - in[out_pos]=in[in_pos]; - break; + assert(result.size() > 0); + result.resize(result.size() - 1); + to_append_or_none = ANY_STRING_RECURSIVE; } - - case L'\0': + else { - if (!allow_incomplete) - { - free(in); - return 0; - } - else - { - //We may ever escape a NULL character, but still appending a \ in case I am wrong. - in[out_pos] = L'\\'; - } + to_append_or_none = ANY_STRING; } - break; + } + break; + } - default: - { - in[out_pos++] = L'\\'; - in[out_pos]= in[in_pos]; - } + case L'?': + { + if (unescape_special) + { + to_append_or_none = ANY_CHAR; } + break; + } + case L'$': + { + if (unescape_special) + { + to_append_or_none = VARIABLE_EXPAND; + } + break; } - if (c == L'\'') + + case L'{': { if (unescape_special) - in[out_pos] = INTERNAL_SEPARATOR; - else - out_pos--; - mode = mode_unquoted; + { + bracket_count++; + to_append_or_none = BRACKET_BEGIN; + } + break; } - else + + case L'}': { - in[out_pos] = in[in_pos]; + if (unescape_special) + { + bracket_count--; + to_append_or_none = BRACKET_END; + } + break; } - break; - } + case L',': + { + /* If the last character was a separator, then treat this as a literal comma */ + if (unescape_special && bracket_count > 0 && string_last_char(result) != BRACKET_SEP) + { + to_append_or_none = BRACKET_SEP; + } + break; + } - /* - Mode 2 means double quoted string, i.e. "foo" - */ - case mode_double_quotes: + case L'\'': + { + mode = mode_single_quotes; + to_append_or_none = unescape_special ? INTERNAL_SEPARATOR : NOT_A_WCHAR; + break; + } + + case L'\"': + { + mode = mode_double_quotes; + to_append_or_none = unescape_special ? INTERNAL_SEPARATOR : NOT_A_WCHAR; + break; + } + } + } + else if (mode == mode_single_quotes) + { + if (c == L'\\') { - switch (c) + /* A backslash may or may not escape something in single quotes */ + switch (input[input_position + 1]) { - case '"': + case '\\': + case L'\'': { - mode = mode_unquoted; - if (unescape_special) - in[out_pos] = INTERNAL_SEPARATOR; - else - out_pos--; + to_append_or_none = input[input_position + 1]; + input_position += 1; /* Skip over the backslash */ break; } - case '\\': + case L'\0': { - switch (in[++in_pos]) + if (!allow_incomplete) { - case L'\0': - { - if (!allow_incomplete) - { - free(in); - return 0; - } - else - { - //We probably don't need it since NULL character is always appended before ending this function. - in[out_pos]=in[in_pos]; - } - } - break; - case '\\': - case L'$': - case '"': - { - in[out_pos]=in[in_pos]; - break; - } + errored = true; + } + 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 + input_position += 1; /* Skip over the backslash */ + to_append_or_none = L'\\'; + } + } + break; + + default: + { + /* Literal backslash that doesn't escape anything! Leave things alone; we'll append the backslash itself */ + break; + } + } + } + else if (c == L'\'') + { + to_append_or_none = unescape_special ? INTERNAL_SEPARATOR : NOT_A_WCHAR; + mode = mode_unquoted; + } + } + else if (mode == mode_double_quotes) + { + switch (c) + { + case L'"': + { + mode = mode_unquoted; + to_append_or_none = unescape_special ? INTERNAL_SEPARATOR : NOT_A_WCHAR; + break; + } - case '\n': + case '\\': + { + switch (input[input_position + 1]) + { + case L'\0': + { + if (!allow_incomplete) { - out_pos--; - break; + errored = true; } - - default: + else { - in[out_pos++] = L'\\'; - in[out_pos] = in[in_pos]; - break; + to_append_or_none = L'\0'; } } break; - } - case '$': - { - if (unescape_special) + case '\\': + case L'$': + case '"': { - in[out_pos]=VARIABLE_EXPAND_SINGLE; + to_append_or_none = input[input_position + 1]; + input_position += 1; /* Skip over the backslash */ + break; } - else + + case '\n': { - in[out_pos]=in[in_pos]; + /* Swallow newline */ + to_append_or_none = NOT_A_WCHAR; + input_position += 1; /* Skip over the backslash */ + break; + } + + default: + { + /* Literal backslash that doesn't escape anything! Leave things alone; we'll append the backslash itself */ + break; } - break; } + break; + } - default: + case '$': + { + if (unescape_special) { - in[out_pos] = in[in_pos]; - break; + to_append_or_none = VARIABLE_EXPAND_SINGLE; } - + break; } - break; + } } + + /* Now maybe append the char */ + if (to_append_or_none != NOT_A_WCHAR) + { + wchar_t to_append_char = static_cast<wchar_t>(to_append_or_none); + // if result_char is not NOT_A_WCHAR, it must be a valid wchar + assert((wint_t)to_append_char == to_append_or_none); + result.push_back(to_append_char); + } } - if (!allow_incomplete && mode) + /* Return the string by reference, and then success */ + if (! errored) { - free(in); - return 0; + output_str->swap(result); } - - in[out_pos]=L'\0'; - return in; + return ! errored; } -bool unescape_string(wcstring &str, int escape_special) +bool unescape_string_in_place(wcstring *str, unescape_flags_t escape_special) { - bool success = false; - wchar_t *result = unescape(str.c_str(), escape_special); - if (result) + assert(str != NULL); + wcstring output; + bool success = unescape_string_internal(str->c_str(), str->size(), &output, escape_special); + if (success) { - str.replace(str.begin(), str.end(), result); - free(result); - success = true; + str->swap(output); } return success; } +bool unescape_string(const wchar_t *input, wcstring *output, unescape_flags_t escape_special) +{ + bool success = unescape_string_internal(input, wcslen(input), output, escape_special); + if (! success) + output->clear(); + return success; +} + +bool unescape_string(const wcstring &input, wcstring *output, unescape_flags_t escape_special) +{ + bool success = unescape_string_internal(input.c_str(), input.size(), output, escape_special); + if (! success) + output->clear(); + return success; +} void common_handle_winch(int signal) { -#ifdef HAVE_WINSIZE - if (ioctl(1,TIOCGWINSZ,&termsize)!=0) + /* don't run ioctl() here, it's not safe to use in signals */ + termsize_valid = false; +} + +/* updates termsize as needed, and returns a copy of the winsize. */ +static struct winsize get_current_winsize() +{ +#ifndef HAVE_WINSIZE + struct winsize retval = {0}; + retval.ws_col = 80; + retval.ws_row = 24; + return retval; +#endif + scoped_rwlock guard(termsize_rwlock, true); + struct winsize retval = termsize; + if (!termsize_valid) { - return; + struct winsize size; + if (ioctl(1,TIOCGWINSZ,&size) == 0) + { + retval = size; + guard.upgrade(); + termsize = retval; + } + termsize_valid = true; } -#else - termsize.ws_col = 80; - termsize.ws_row = 24; -#endif + return retval; } int common_get_width() { - return termsize.ws_col; + return get_current_winsize().ws_col; } int common_get_height() { - return termsize.ws_row; + return get_current_winsize().ws_row; } void tokenize_variable_array(const wcstring &val, std::vector<wcstring> &out) { size_t pos = 0, end = val.size(); - while (pos < end) + while (pos <= end) { size_t next_pos = val.find(ARRAY_SEP, pos); - if (next_pos == wcstring::npos) break; - out.push_back(val.substr(pos, next_pos - pos)); - pos = next_pos + 1; //skip the separator + if (next_pos == wcstring::npos) + { + next_pos = end; + } + out.resize(out.size() + 1); + out.back().assign(val, pos, next_pos - pos); + pos = next_pos + 1; //skip the separator, or skip past the end } - out.push_back(val.substr(pos, end - pos)); } bool string_prefixes_string(const wchar_t *proposed_prefix, const wcstring &value) @@ -2005,7 +1995,7 @@ void format_size_safe(char buff[128], unsigned long long sz) { const size_t buff_size = 128; const size_t max_len = buff_size - 1; //need to leave room for a null terminator - bzero(buff, buff_size); + memset(buff, 0, buff_size); size_t idx = 0; const char * const sz_name[]= { @@ -2154,7 +2144,7 @@ static pid_t initial_foreground_process_group = -1; bool is_forked_child(void) { - /* Just bail if nobody's called setup_fork_guards - e.g. fishd */ + /* Just bail if nobody's called setup_fork_guards, e.g. some of our tools */ if (! initial_pid) return false; bool is_child_of_fork = (getpid() != initial_pid); @@ -2233,7 +2223,7 @@ void scoped_lock::lock(void) { assert(! locked); assert(! is_forked_child()); - VOMIT_ON_FAILURE(pthread_mutex_lock(lock_obj)); + VOMIT_ON_FAILURE_NO_ERRNO(pthread_mutex_lock(lock_obj)); locked = true; } @@ -2241,7 +2231,7 @@ void scoped_lock::unlock(void) { assert(locked); assert(! is_forked_child()); - VOMIT_ON_FAILURE(pthread_mutex_unlock(lock_obj)); + VOMIT_ON_FAILURE_NO_ERRNO(pthread_mutex_unlock(lock_obj)); locked = false; } @@ -2250,11 +2240,94 @@ scoped_lock::scoped_lock(pthread_mutex_t &mutex) : lock_obj(&mutex), locked(fals this->lock(); } +scoped_lock::scoped_lock(mutex_lock_t &lock) : lock_obj(&lock.mutex), locked(false) +{ + this->lock(); +} + scoped_lock::~scoped_lock() { if (locked) this->unlock(); } +void scoped_rwlock::lock(void) +{ + assert(! (locked || locked_shared)); + assert(! is_forked_child()); + VOMIT_ON_FAILURE_NO_ERRNO(pthread_rwlock_rdlock(rwlock_obj)); + locked = true; +} + +void scoped_rwlock::unlock(void) +{ + assert(locked); + assert(! is_forked_child()); + VOMIT_ON_FAILURE_NO_ERRNO(pthread_rwlock_unlock(rwlock_obj)); + locked = false; +} + +void scoped_rwlock::lock_shared(void) +{ + assert(! (locked || locked_shared)); + assert(! is_forked_child()); + VOMIT_ON_FAILURE_NO_ERRNO(pthread_rwlock_wrlock(rwlock_obj)); + locked_shared = true; +} + +void scoped_rwlock::unlock_shared(void) +{ + assert(locked_shared); + assert(! is_forked_child()); + VOMIT_ON_FAILURE_NO_ERRNO(pthread_rwlock_unlock(rwlock_obj)); + locked_shared = false; +} + +void scoped_rwlock::upgrade(void) +{ + assert(locked_shared); + assert(! is_forked_child()); + VOMIT_ON_FAILURE_NO_ERRNO(pthread_rwlock_unlock(rwlock_obj)); + locked = false; + VOMIT_ON_FAILURE_NO_ERRNO(pthread_rwlock_wrlock(rwlock_obj)); + locked_shared = true; +} + +scoped_rwlock::scoped_rwlock(pthread_rwlock_t &rwlock, bool shared) : rwlock_obj(&rwlock), locked(false), locked_shared(false) +{ + if (shared) + { + this->lock_shared(); + } + else + { + this->lock(); + } +} + +scoped_rwlock::scoped_rwlock(rwlock_t &rwlock, bool shared) : rwlock_obj(&rwlock.rwlock), locked(false), locked_shared(false) +{ + if (shared) + { + this->lock_shared(); + } + else + { + this->lock(); + } +} + +scoped_rwlock::~scoped_rwlock() +{ + if (locked) + { + this->unlock(); + } + else if (locked_shared) + { + this->unlock_shared(); + } +} + wcstokenizer::wcstokenizer(const wcstring &s, const wcstring &separator) : buffer(), str(), @@ -2343,73 +2416,3 @@ char **make_null_terminated_array(const std::vector<std::string> &lst) { return make_null_terminated_array_helper(lst); } - -/** - Check, and create if necessary, a secure runtime path - Derived from tmux.c in tmux (http://tmux.sourceforge.net/) -*/ -static int check_runtime_path(const char * path) -{ - /* - * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net> - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER - * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - - struct stat statpath; - u_int uid = geteuid(); - - if (mkdir(path, S_IRWXU) != 0 && errno != EEXIST) - return errno; - if (lstat(path, &statpath) != 0) - return errno; - if (!S_ISDIR(statpath.st_mode) - || statpath.st_uid != uid - || (statpath.st_mode & (S_IRWXG|S_IRWXO)) != 0) - return EACCES; - return 0; -} - -/** Return the path of an appropriate runtime data directory */ -std::string common_get_runtime_path() -{ - const char *dir = getenv("XDG_RUNTIME_DIR"); - const char *uname = getenv("USER"); - std::string path; - - if (uname == NULL) - { - const struct passwd *pw = getpwuid(getuid()); - uname = pw->pw_name; - } - - if (dir == NULL) - { - // /tmp/fish.user - dir = "/tmp/fish."; - path.reserve(strlen(dir) + strlen(uname)); - path.append(dir); - path.append(uname); - if (check_runtime_path(path.c_str()) != 0) - { - debug(0, L"Runtime path not available. Try deleting the directory %s and restarting fish.", path.c_str()); - path.clear(); - } - } - else - { - path.reserve(strlen(dir)); - path.append(dir); - } - return path; -} |