fish-shell/src/common.cpp

2394 lines
62 KiB
C++
Raw Normal View History

/** \file common.c
Various functions, mostly string utilities, that are used by most
parts of fish.
*/
#include "config.h"
#include <unistd.h>
#ifdef HAVE_SIGINFO_H
#include <siginfo.h>
#endif
#include <stdlib.h>
#include <termios.h>
#include <wchar.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
2015-07-25 23:14:25 +08:00
#include <assert.h>
#include <math.h>
#include <signal.h>
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#include <sys/stat.h>
#include <wctype.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <locale.h>
#include <sys/time.h>
#include <algorithm>
#ifdef HAVE_EXECINFO_H
#include <execinfo.h>
#endif
#include "fallback.h"
#include "wutil.h"
#include "common.h"
#include "expand.h"
#include "wildcard.h"
#include "util.cpp"
#include "fallback.cpp"
#define NOT_A_WCHAR (static_cast<wint_t>(WEOF))
struct termios shell_modes;
// Note we foolishly assume that pthread_t is just a primitive. But it might be a struct.
static pthread_t main_thread_id = 0;
static bool thread_assertions_configured_for_testing = false;
2012-11-05 16:05:42 +08:00
wchar_t ellipsis_char;
wchar_t omitted_newline_char;
2012-11-05 16:05:42 +08:00
bool g_profiling_active = false;
const wchar_t *program_name;
int debug_level=1;
/**
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);
void show_stackframe()
{
ASSERT_IS_NOT_FORKED_CHILD();
2013-01-13 04:55:23 +08:00
/* Hack to avoid showing backtraces in the tester */
if (program_name && ! wcscmp(program_name, L"(ignore)"))
return;
void *trace[32];
int trace_size = 0;
trace_size = backtrace(trace, 32);
debug(0, L"Backtrace:");
backtrace_symbols_fd(trace, trace_size, STDERR_FILENO);
}
int fgetws2(wcstring *s, FILE *f)
{
int i=0;
wint_t c;
while (1)
{
errno=0;
c = getwc(f);
if (errno == EILSEQ || errno == EINTR)
{
continue;
}
switch (c)
{
2012-11-19 16:31:03 +08:00
/* End of line */
case WEOF:
case L'\n':
case L'\0':
return i;
/* Ignore carriage returns */
case L'\r':
break;
2012-11-19 16:31:03 +08:00
default:
i++;
s->push_back((wchar_t)c);
break;
}
}
}
2012-12-20 05:31:06 +08:00
/**
Converts the narrow character string \c in into its wide
equivalent, and return it
2012-12-20 05:31:06 +08:00
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();
assert(in != NULL);
wcstring result;
result.reserve(in_len);
mbstate_t state = {};
size_t in_pos = 0;
while (in_pos < in_len)
{
wchar_t wc = 0;
size_t ret = mbrtowc(&wc, &in[in_pos], in_len-in_pos, &state);
/* Determine whether to encode this characters with our crazy scheme */
bool use_encode_direct = false;
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;
}
if (use_encode_direct)
{
wc = ENCODE_DIRECT_BASE + (unsigned char)in[in_pos];
result.push_back(wc);
in_pos++;
memset(&state, 0, sizeof state);
}
else if (ret == 0)
{
/* Embedded null byte! */
result.push_back(L'\0');
in_pos++;
memset(&state, 0, sizeof state);
}
else
{
/* Normal case */
result.push_back(wc);
in_pos += ret;
}
}
return result;
}
wcstring str2wcstring(const char *in, size_t len)
{
return str2wcs_internal(in, len);
}
wcstring str2wcstring(const char *in)
{
return str2wcs_internal(in, strlen(in));
}
wcstring str2wcstring(const std::string &in)
{
/* Handles embedded nulls! */
return str2wcs_internal(in.data(), in.size());
}
char *wcs2str(const wchar_t *in)
{
if (! in)
return NULL;
size_t desired_size = MAX_UTF8_BYTES*wcslen(in)+1;
char local_buff[512];
if (desired_size <= sizeof local_buff / sizeof *local_buff)
{
// convert into local buff, then use strdup() so we don't waste malloc'd space
char *result = wcs2str_internal(in, local_buff);
if (result)
{
// It converted into the local buffer, so copy it
result = strdup(result);
if (! result)
{
DIE_MEM();
}
}
return result;
}
else
{
// here we fall into the bad case of allocating a buffer probably much larger than necessary
char *out = (char *)malloc(MAX_UTF8_BYTES*wcslen(in)+1);
if (!out)
{
DIE_MEM();
}
return wcs2str_internal(in, out);
}
}
char *wcs2str(const wcstring &in)
{
return wcs2str(in.c_str());
}
/* This function is distinguished from wcs2str_internal in that it allows embedded null bytes */
std::string wcs2string(const wcstring &input)
{
std::string result;
result.reserve(input.size());
2012-12-20 05:31:06 +08:00
mbstate_t state;
memset(&state, 0, sizeof(state));
2012-12-20 05:31:06 +08:00
char converted[MB_LEN_MAX + 1];
2012-12-20 05:31:06 +08:00
for (size_t i=0; i < input.size(); i++)
{
wchar_t wc = input[i];
if (wc == INTERNAL_SEPARATOR)
{
}
else if ((wc >= ENCODE_DIRECT_BASE) &&
(wc < ENCODE_DIRECT_BASE+256))
{
result.push_back(wc - ENCODE_DIRECT_BASE);
}
else
{
memset(converted, 0, sizeof converted);
size_t len = wcrtomb(converted, wc, &state);
if (len == (size_t)(-1))
{
debug(1, L"Wide character %d has no narrow representation", wc);
memset(&state, 0, sizeof(state));
}
else
{
result.append(converted, len);
}
}
}
2012-12-20 05:31:06 +08:00
return result;
}
/**
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)
{
size_t res=0;
size_t in_pos=0;
size_t out_pos = 0;
mbstate_t state;
CHECK(in, 0);
CHECK(out, 0);
memset(&state, 0, sizeof(state));
while (in[in_pos])
{
if (in[in_pos] == INTERNAL_SEPARATOR)
{
}
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
{
res = wcrtomb(&out[out_pos], in[in_pos], &state);
if (res == (size_t)(-1))
{
debug(1, L"Wide character %d has no narrow representation", in[in_pos]);
memset(&state, 0, sizeof(state));
}
else
{
out_pos += res;
}
}
in_pos++;
}
out[out_pos] = 0;
return out;
}
wcstring format_string(const wchar_t *format, ...)
{
va_list va;
va_start(va, format);
wcstring result = vformat_string(format, va);
va_end(va);
return result;
}
void append_formatv(wcstring &target, const wchar_t *format, va_list va_orig)
{
const int saved_err = errno;
/*
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);
wchar_t static_buff[256];
size_t size = 0;
wchar_t *buff = NULL;
int status = -1;
while (status < 0)
{
/* Reallocate if necessary */
if (size == 0)
{
buff = static_buff;
size = sizeof static_buff;
}
else
{
size *= 2;
if (size >= max_size)
{
buff[0] = '\0';
break;
}
buff = (wchar_t *)realloc((buff == static_buff ? NULL : buff), size);
if (buff == NULL)
{
DIE_MEM();
}
}
/* Try printing */
va_list va;
va_copy(va, va_orig);
status = vswprintf(buff, size / sizeof(wchar_t), format, va);
va_end(va);
}
target.append(buff);
if (buff != static_buff)
{
free(buff);
}
errno = saved_err;
}
wcstring vformat_string(const wchar_t *format, va_list va_orig)
{
wcstring result;
append_formatv(result, format, va_orig);
return result;
}
void append_format(wcstring &str, const wchar_t *format, ...)
{
va_list va;
va_start(va, format);
append_formatv(str, format, va);
va_end(va);
}
const wchar_t *wcsvarname(const wchar_t *str)
{
while (*str)
{
if ((!iswalnum(*str)) && (*str != L'_'))
{
return str;
}
str++;
}
return NULL;
}
const wchar_t *wcsvarname(const wcstring &str)
{
return wcsvarname(str.c_str());
}
const wchar_t *wcsfuncname(const wcstring &str)
{
return wcschr(str.c_str(), L'/');
}
2013-08-09 06:06:46 +08:00
bool wcsvarchr(wchar_t chr)
{
return iswalnum(chr) || chr == L'_';
}
int fish_wcswidth(const wchar_t *str)
{
return fish_wcswidth(str, wcslen(str));
}
int fish_wcswidth(const wcstring& str)
{
return fish_wcswidth(str.c_str(), str.size());
}
wchar_t *quote_end(const wchar_t *pos)
{
wchar_t c = *pos;
while (1)
{
pos++;
if (!*pos)
return 0;
if (*pos == L'\\')
{
pos++;
if (!*pos)
return 0;
}
else
{
if (*pos == c)
{
return (wchar_t *)pos;
}
}
}
return 0;
}
2012-02-01 13:06:52 +08:00
wcstring wsetlocale(int category, const wchar_t *locale)
{
char *lang = locale ? wcs2str(locale) : NULL;
char *res = setlocale(category, lang);
free(lang);
/*
Use ellipsis if on known unicode system, otherwise use $
*/
ellipsis_char = (wcwidth(L'\x2026') > 0) ? L'\x2026' : L'$';
// U+23CE is the "return" character
omitted_newline_char = (wcwidth(L'\x23CE') > 0) ? L'\x23CE' : L'~';
2013-05-05 17:33:17 +08:00
if (!res)
return wcstring();
2012-02-01 13:06:52 +08:00
else
return format_string(L"%s", res);
}
bool contains_internal(const wchar_t *a, int vararg_handle, ...)
{
const wchar_t *arg;
va_list va;
bool res = false;
CHECK(a, 0);
va_start(va, vararg_handle);
while ((arg=va_arg(va, const wchar_t *))!= 0)
{
if (wcscmp(a,arg) == 0)
{
res = true;
break;
}
}
va_end(va);
return res;
}
/* 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 pass to va_start */
__sentinel bool contains_internal(const wcstring &needle, int vararg_handle, ...)
{
const wchar_t *arg;
va_list va;
int res = 0;
const wchar_t *needle_cstr = needle.c_str();
va_start(va, vararg_handle);
while ((arg=va_arg(va, const wchar_t *))!= 0)
{
/* 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;
}
}
va_end(va);
return res;
}
long read_blocked(int fd, void *buf, size_t count)
{
ssize_t res;
sigset_t chldset, oldset;
sigemptyset(&chldset);
sigaddset(&chldset, SIGCHLD);
VOMIT_ON_FAILURE(pthread_sigmask(SIG_BLOCK, &chldset, &oldset));
res = read(fd, buf, count);
VOMIT_ON_FAILURE(pthread_sigmask(SIG_SETMASK, &oldset, NULL));
return res;
}
ssize_t write_loop(int fd, const char *buff, size_t count)
{
size_t out_cum=0;
while (out_cum < count)
{
ssize_t out = write(fd, &buff[out_cum], count - out_cum);
if (out < 0)
{
if (errno != EAGAIN && errno != EINTR)
{
return -1;
}
}
else
{
out_cum += (size_t)out;
}
}
return (ssize_t)out_cum;
}
2012-03-01 09:55:28 +08:00
ssize_t read_loop(int fd, void *buff, size_t count)
{
ssize_t result;
do
{
2012-03-01 09:55:28 +08:00
result = read(fd, buff, count);
}
while (result < 0 && (errno == EAGAIN || errno == EINTR));
2012-03-01 09:55:28 +08:00
return result;
}
static bool should_debug(int level)
{
if (level > debug_level)
return false;
/* Hack to not print error messages in the tests */
if (program_name && ! wcscmp(program_name, L"(ignore)"))
return false;
return true;
}
static void debug_shared(const wcstring &msg)
{
const wcstring sb = wcstring(program_name) + L": " + msg;
fwprintf(stderr, L"%ls", reformat_for_screen(sb).c_str());
}
void debug(int level, const wchar_t *msg, ...)
{
if (! should_debug(level))
return;
int errno_old = errno;
va_list va;
va_start(va, msg);
wcstring local_msg = vformat_string(msg, va);
va_end(va);
debug_shared(local_msg);
errno = errno_old;
}
void debug(int level, const char *msg, ...)
{
if (! should_debug(level))
return;
int errno_old = errno;
char local_msg[512];
va_list va;
va_start(va, msg);
vsnprintf(local_msg, sizeof local_msg, msg, va);
va_end(va);
debug_shared(str2wcstring(local_msg));
errno = errno_old;
}
void print_stderr(const wcstring &str)
{
fprintf(stderr, "%ls\n", str.c_str());
}
2014-04-28 09:27:34 +08:00
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)
{
const char * const params[] = {param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12};
if (! msg)
return;
/* Can't call printf, that may allocate memory Just call write() over and over. */
if (level > debug_level)
return;
int errno_old = errno;
size_t param_idx = 0;
const char *cursor = msg;
while (*cursor != '\0')
{
const char *end = strchr(cursor, '%');
if (end == NULL)
end = cursor + strlen(cursor);
2014-04-28 09:27:34 +08:00
write_ignore(STDERR_FILENO, cursor, end - cursor);
if (end[0] == '%' && end[1] == 's')
{
/* Handle a format string */
assert(param_idx < sizeof params / sizeof *params);
const char *format = params[param_idx++];
if (! format)
format = "(null)";
2014-04-28 09:27:34 +08:00
write_ignore(STDERR_FILENO, format, strlen(format));
cursor = end + 2;
}
else if (end[0] == '\0')
{
/* Must be at the end of the string */
cursor = end;
}
else
{
/* Some other format specifier, just skip it */
cursor = end + 1;
}
}
// We always append a newline
2014-04-28 09:27:34 +08:00
write_ignore(STDERR_FILENO, "\n", 1);
errno = errno_old;
}
void format_long_safe(char buff[64], long val)
{
if (val == 0)
{
strcpy(buff, "0");
}
else
{
/* Generate the string in reverse */
size_t idx = 0;
bool negative = (val < 0);
/* Note that we can't just negate val if it's negative, because it may be the most negative value. We do rely on round-towards-zero division though. */
while (val != 0)
{
long rem = val % 10;
buff[idx++] = '0' + (rem < 0 ? -rem : rem);
val /= 10;
}
if (negative)
buff[idx++] = '-';
2012-03-01 09:55:28 +08:00
buff[idx] = 0;
size_t left = 0, right = idx - 1;
while (left < right)
{
char tmp = buff[left];
buff[left++] = buff[right];
buff[right--] = tmp;
}
}
}
void format_long_safe(wchar_t buff[64], long val)
{
if (val == 0)
{
wcscpy(buff, L"0");
}
else
{
/* Generate the string in reverse */
size_t idx = 0;
bool negative = (val < 0);
while (val != 0)
{
long rem = val % 10;
2012-07-29 08:49:46 +08:00
buff[idx++] = L'0' + (wchar_t)(rem < 0 ? -rem : rem);
val /= 10;
}
if (negative)
buff[idx++] = L'-';
buff[idx] = 0;
size_t left = 0, right = idx - 1;
while (left < right)
{
wchar_t tmp = buff[left];
buff[left++] = buff[right];
buff[right--] = tmp;
}
}
}
wcstring reformat_for_screen(const wcstring &msg)
{
wcstring buff;
int line_width = 0;
int screen_width = common_get_width();
if (screen_width)
{
const wchar_t *start = msg.c_str();
const wchar_t *pos = start;
while (1)
{
int overflow = 0;
int tok_width=0;
/*
Tokenize on whitespace, and also calculate the width of the token
*/
while (*pos && (!wcschr(L" \n\r\t", *pos)))
{
/*
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))
{
overflow = 1;
break;
}
tok_width += fish_wcwidth(*pos);
pos++;
}
/*
If token is zero character long, we don't do anything
*/
if (pos == start)
{
start = pos = pos+1;
}
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');
buff.append(format_string(L"%ls-\n", token));
free(token);
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)
{
buff.push_back(L'\n');
line_width=0;
}
buff.append(format_string(L"%ls%ls", line_width?L" ":L"", token));
free(token);
line_width += (line_width!=0?1:0) + tok_width;
}
/*
Break on end of string
*/
if (!*pos)
{
break;
}
start=pos;
}
}
else
{
buff.append(msg);
}
buff.push_back(L'\n');
return buff;
}
/* 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)
{
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);
int need_escape=0;
int need_complex_escape=0;
/* Avoid dereferencing all over the place */
wcstring &out = *out_str;
2011-12-27 15:13:05 +08:00
if (!no_quoted && in_len == 0)
{
out.assign(L"''");
return;
}
while (*in != 0)
{
if ((*in >= ENCODE_DIRECT_BASE) &&
(*in < ENCODE_DIRECT_BASE+256))
{
int val = *in - ENCODE_DIRECT_BASE;
int tmp;
out += L'\\';
out += L'X';
tmp = val/16;
out += tmp > 9? L'a'+(tmp-10):L'0'+tmp;
tmp = val%16;
out += tmp > 9? L'a'+(tmp-10):L'0'+tmp;
need_escape=need_complex_escape=1;
}
else
{
wchar_t c = *in;
switch (c)
{
2012-11-19 16:31:03 +08:00
case L'\t':
out += L'\\';
out += L't';
2012-11-19 16:31:03 +08:00
need_escape=need_complex_escape=1;
break;
2012-11-19 16:31:03 +08:00
case L'\n':
out += L'\\';
out += L'n';
2012-11-19 16:31:03 +08:00
need_escape=need_complex_escape=1;
break;
2012-11-19 16:31:03 +08:00
case L'\b':
out += L'\\';
out += L'b';
2012-11-19 16:31:03 +08:00
need_escape=need_complex_escape=1;
break;
2012-11-19 16:31:03 +08:00
case L'\r':
out += L'\\';
out += L'r';
2012-11-19 16:31:03 +08:00
need_escape=need_complex_escape=1;
break;
2012-11-19 16:31:03 +08:00
case L'\x1b':
out += L'\\';
out += L'e';
2012-11-19 16:31:03 +08:00
need_escape=need_complex_escape=1;
break;
2012-11-19 16:31:03 +08:00
case L'\\':
case L'\'':
{
2012-11-19 16:31:03 +08:00
need_escape=need_complex_escape=1;
if (escape_all)
out += L'\\';
out += *in;
2012-11-19 16:31:03 +08:00
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;
2012-11-19 16:31:03 +08:00
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'}':
case L'?':
case L'*':
case L'|':
case L';':
case L'"':
case L'%':
case L'~':
{
2012-11-19 16:31:03 +08:00
if (! no_tilde || c != L'~')
{
2012-11-19 16:31:03 +08:00
need_escape=1;
if (escape_all)
out += L'\\';
2012-11-19 16:31:03 +08:00
}
out += *in;
2012-11-19 16:31:03 +08:00
break;
}
2012-11-19 16:31:03 +08:00
default:
{
if (*in < 32)
{
if (*in <27 && *in > 0)
{
out += L'\\';
out += L'c';
out += L'a' + *in -1;
2012-11-19 16:31:03 +08:00
need_escape=need_complex_escape=1;
break;
}
2012-11-19 16:31:03 +08:00
int tmp = (*in)%16;
out += L'\\';
out += L'x';
out += ((*in>15)? L'1' : L'0');
out += tmp > 9? L'a'+(tmp-10):L'0'+tmp;
2012-11-19 16:31:03 +08:00
need_escape=need_complex_escape=1;
}
else
{
out += *in;
2012-11-19 16:31:03 +08:00
}
break;
}
}
}
in++;
}
/*
Use quoted escaping if possible, since most people find it
easier to read.
*/
if (!no_quoted && need_escape && !need_complex_escape && escape_all)
{
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);
}
}
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)
{
wcstring result;
escape_string_internal(in.c_str(), in.size(), &result, flags);
return result;
}
/* Helper to return the last character in a string, or NOT_A_WCHAR */
static wint_t string_last_char(const wcstring &str)
{
size_t len = str.size();
return len == 0 ? NOT_A_WCHAR : str.at(len - 1);
}
/* 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 */
Merge new string builtin This adds the new builtin 'string' which supports various string manipulation and matching algorithms, including PCRE based regular expressions. Fixes #2296 Squashed commit of the following: commit 4c3eaeb6e57d76463e9683c327142b0aeafb92b8 Author: ridiculousfish <corydoras@ridiculousfish.com> Date: Sat Sep 12 12:51:30 2015 -0700 Remove testdata and doc dirs from pcre2 source commit b2a8b4b50f2398b204fb72cfe4b5ba77ece2e1ab Merge: 11c8a47 7974aab Author: ridiculousfish <corydoras@ridiculousfish.com> Date: Sat Sep 12 12:32:40 2015 -0700 Merge branch 'string' of git://github.com/msteed/fish-shell into string-test commit 7974aab6d367f999f1140ab34c2535cef5cf3b00 Author: Michael Steed <msteed@saltstack.com> Date: Fri Sep 11 13:00:02 2015 -0600 build pcre2 lib only, no docs commit eb20b43d2d96b7e6d24618158ce71078de83c40b Merge: 1a09e70 5f519cb Author: Michael Steed <msteed68@gmail.com> Date: Thu Sep 10 20:00:47 2015 -0600 Merge branch 'string' of github.com:msteed/fish-shell into string commit 1a09e709d028393c9e9e6dc9a84278f399a15f3d Author: Michael Steed <msteed68@gmail.com> Date: Thu Sep 10 19:58:24 2015 -0600 rebase on master & address the fallout commit a0ec9772cd1a0a548a501a7633be05dab4e5ee46 Author: Michael Steed <msteed68@gmail.com> Date: Thu Sep 10 19:26:45 2015 -0600 use fish's wildcard_match() for glob matching commit 64c25a01e3f7234f220ba13545cf658a7492b1a4 Author: Michael Steed <msteed68@gmail.com> Date: Thu Aug 27 08:19:23 2015 -0600 some fixes from review - string_get_arg_stdin(): simplify and don't discard the argument when the trailing newline is absent - fix calls to pcre2 for e.g. string match -r -a 'a*' 'b' - correct test for args coming from stdin commit ece7f35ec5f4093763627d68d671b6c0c876896d Author: Michael Steed <msteed68@gmail.com> Date: Sat Aug 22 19:35:56 2015 -0600 fixes from review - Makefile.in: restore iwyu target - regex_replacer_t::replace_matches(): correct size passed to realloc() commit 9ff7477a926c4572e26171cab3cd42f8086be678 Author: Michael Steed <msteed68@gmail.com> Date: Thu Aug 20 13:08:33 2015 -0600 Minor doc improvements commit baf4e096b22dde3063b85b833795eb570d660ba7 Author: Michael Steed <msteed68@gmail.com> Date: Wed Aug 19 18:29:02 2015 -0600 another attempt to fix the ci build commit 896a2c2b279a419747bea26102229fbe84534a6f Author: Michael Steed <msteed68@gmail.com> Date: Wed Aug 19 18:03:49 2015 -0600 Updates after review comments - make match/replace without -a operate on the first match on each argument - use different exit codes for "no operation performed" and errors, as grep does - refactor regex compile code - use human-friendly error messages from pcre2 - improve error handling & reporting elsewhere - add a few tests - make some doc fixes - some simplification & cleanup - fix ci build failure (I hope) commit efd47dcbda2ca247d58bee56a7774cd75a1062fd Author: Michael Steed <msteed68@gmail.com> Date: Wed Aug 12 00:26:07 2015 -0600 fix dependencies for parallel make commit ed0850e2db467362066a3d94e3ececd17c1756cd Author: Michael Steed <msteed68@gmail.com> Date: Tue Aug 11 23:37:22 2015 -0600 Add missing pcre2 files + .gitignore commit 9492e7a7e929c03554336be1ddf80ca6b37f53c5 Author: Michael Steed <msteed68@gmail.com> Date: Tue Aug 11 22:44:05 2015 -0600 add pcre2-10.20 and update license.hdr commit 1a60b933718feb20c0bf7c9e257b8e495014ea1b Author: Michael Steed <msteed68@gmail.com> Date: Tue Aug 11 22:41:19 2015 -0600 add string builtin files - string builtin source, tests, & docs - changes to configure.ac & Makefile.in commit 5f519cb2a2c05213e0a88a7add7af288bc1c1352 Author: Michael Steed <msteed68@gmail.com> Date: Thu Sep 10 19:26:45 2015 -0600 use fish's wildcard_match() for glob matching commit 2ecd24f79500879e2de5bdf1b4c19dd44fc6ac85 Author: Michael Steed <msteed68@gmail.com> Date: Thu Aug 27 08:19:23 2015 -0600 some fixes from review - string_get_arg_stdin(): simplify and don't discard the argument when the trailing newline is absent - fix calls to pcre2 for e.g. string match -r -a 'a*' 'b' - correct test for args coming from stdin commit 45b777e4dc85c05cd4a186f4bdcae543c21aaf08 Author: Michael Steed <msteed68@gmail.com> Date: Sat Aug 22 19:35:56 2015 -0600 fixes from review - Makefile.in: restore iwyu target - regex_replacer_t::replace_matches(): correct size passed to realloc() commit 981cbb6ddf742a5fe8881af916e7b870b7e6422a Author: Michael Steed <msteed68@gmail.com> Date: Thu Aug 20 13:08:33 2015 -0600 Minor doc improvements commit ddb6a2a8fdb6aa31aad41e80d5481bb32c6ed8ff Author: Michael Steed <msteed68@gmail.com> Date: Wed Aug 19 18:29:02 2015 -0600 another attempt to fix the ci build commit 1e34e3191b028162863d263e9868052f75194aa5 Author: Michael Steed <msteed68@gmail.com> Date: Wed Aug 19 18:03:49 2015 -0600 Updates after review comments - make match/replace without -a operate on the first match on each argument - use different exit codes for "no operation performed" and errors, as grep does - refactor regex compile code - use human-friendly error messages from pcre2 - improve error handling & reporting elsewhere - add a few tests - make some doc fixes - some simplification & cleanup - fix ci build failure (I hope) commit 34232e152df17a3cfbf0a094dd51d148a4f04e6f Author: Michael Steed <msteed68@gmail.com> Date: Wed Aug 12 00:26:07 2015 -0600 fix dependencies for parallel make commit 00d7e781697f53454beb91c1d0fc4b2d28d6e034 Author: Michael Steed <msteed68@gmail.com> Date: Tue Aug 11 23:37:22 2015 -0600 Add missing pcre2 files + .gitignore commit 4498aa5f576e09634f7f619443e74d2f33c108e4 Author: Michael Steed <msteed68@gmail.com> Date: Tue Aug 11 22:44:05 2015 -0600 add pcre2-10.20 and update license.hdr commit 290c58c72e22db644ccf6fa9088051644980ed0a Author: Michael Steed <msteed68@gmail.com> Date: Tue Aug 11 22:41:19 2015 -0600 add string builtin files - string builtin source, tests, & docs - changes to configure.ac & Makefile.in
2015-09-13 03:59:40 +08:00
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;
}
/* 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;
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)
{
/* 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--;
/* 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)
{
case L'u':
{
chars=4;
max_val = UCS2_MAX;
break;
}
case L'U':
{
chars=8;
max_val = WCHAR_MAX;
// Don't exceed the largest Unicode code point - see #1107
if (0x10FFFF < max_val)
max_val = (wchar_t)0x10FFFF;
break;
}
case L'x':
{
chars = 2;
max_val = ASCII_MAX;
break;
}
case L'X':
{
byte_literal = true;
max_val = BYTE_MAX;
break;
}
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;
}
}
for (size_t i=0; i<chars; i++)
{
long d = convert_digit(input[in_pos],base);
if (d < 0)
{
break;
}
res=(res*base)+d;
in_pos++;
}
if (res <= max_val)
{
result_char_or_none = (wchar_t)((byte_literal ? ENCODE_DIRECT_BASE : 0)+res);
}
else
{
errored = true;
}
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;
}
/* \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;
}
/* \x1b means escape */
case L'e':
{
result_char_or_none = L'\x1b';
break;
}
/*
\f means form feed
*/
case L'f':
{
result_char_or_none = L'\f';
break;
}
/*
\n means newline
*/
case L'n':
{
result_char_or_none = L'\n';
break;
}
/*
\r means carriage return
*/
case L'r':
{
result_char_or_none = L'\r';
break;
}
/*
\t means tab
*/
case L't':
{
result_char_or_none = L'\t';
break;
}
/*
\v means vertical tab
*/
case L'v':
{
result_char_or_none = L'\v';
break;
}
/* If a backslash is followed by an actual newline, swallow them both */
case L'\n':
{
result_char_or_none = NOT_A_WCHAR;
break;
}
default:
{
if (unescape_special)
result->push_back(INTERNAL_SEPARATOR);
result_char_or_none = c;
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;
}
/* 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);
const bool unescape_special = !!(flags & UNESCAPE_SPECIAL);
const bool allow_incomplete = !!(flags & UNESCAPE_INCOMPLETE);
int bracket_count = 0;
bool errored = false;
enum
{
mode_unquoted,
mode_single_quotes,
mode_double_quotes
} mode = mode_unquoted;
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)
{
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'~':
{
if (unescape_special && (input_position == 0))
{
to_append_or_none = HOME_DIRECTORY;
}
break;
}
case L'%':
{
if (unescape_special && (input_position == 0))
{
to_append_or_none = PROCESS_EXPAND;
}
break;
}
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)
{
assert(result.size() > 0);
result.resize(result.size() - 1);
to_append_or_none = ANY_STRING_RECURSIVE;
}
else
{
to_append_or_none = ANY_STRING;
}
}
break;
}
case L'?':
{
if (unescape_special)
{
to_append_or_none = ANY_CHAR;
}
break;
}
case L'$':
{
if (unescape_special)
{
to_append_or_none = VARIABLE_EXPAND;
}
break;
}
case L'{':
{
if (unescape_special)
{
bracket_count++;
to_append_or_none = BRACKET_BEGIN;
}
break;
}
case L'}':
{
if (unescape_special)
{
bracket_count--;
to_append_or_none = BRACKET_END;
}
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;
}
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'\\')
{
/* A backslash may or may not escape something in single quotes */
switch (input[input_position + 1])
{
case '\\':
case L'\'':
{
to_append_or_none = input[input_position + 1];
input_position += 1; /* Skip over the backslash */
break;
}
case L'\0':
{
if (!allow_incomplete)
{
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 '\\':
{
switch (input[input_position + 1])
{
case L'\0':
{
if (!allow_incomplete)
{
errored = true;
}
else
{
to_append_or_none = L'\0';
}
}
break;
case '\\':
case L'$':
case '"':
{
to_append_or_none = input[input_position + 1];
input_position += 1; /* Skip over the backslash */
break;
}
case '\n':
{
/* 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;
}
case '$':
{
if (unescape_special)
{
to_append_or_none = VARIABLE_EXPAND_SINGLE;
}
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);
}
}
/* Return the string by reference, and then success */
if (! errored)
{
output_str->swap(result);
}
return ! errored;
}
bool unescape_string_in_place(wcstring *str, unescape_flags_t escape_special)
{
assert(str != NULL);
wcstring output;
bool success = unescape_string_internal(str->c_str(), str->size(), &output, escape_special);
if (success)
{
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)
{
/* 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)
{
struct winsize size;
if (ioctl(1,TIOCGWINSZ,&size) == 0)
{
retval = size;
guard.upgrade();
termsize = retval;
}
termsize_valid = true;
}
return retval;
}
int common_get_width()
{
return get_current_winsize().ws_col;
}
int common_get_height()
{
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)
{
size_t next_pos = val.find(ARRAY_SEP, pos);
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
}
}
bool string_prefixes_string(const wchar_t *proposed_prefix, const wcstring &value)
{
size_t prefix_size = wcslen(proposed_prefix);
return prefix_size <= value.size() && value.compare(0, prefix_size, proposed_prefix) == 0;
}
bool string_prefixes_string(const wcstring &proposed_prefix, const wcstring &value)
{
size_t prefix_size = proposed_prefix.size();
return prefix_size <= value.size() && value.compare(0, prefix_size, proposed_prefix) == 0;
}
bool string_prefixes_string_case_insensitive(const wcstring &proposed_prefix, const wcstring &value)
{
size_t prefix_size = proposed_prefix.size();
return prefix_size <= value.size() && wcsncasecmp(proposed_prefix.c_str(), value.c_str(), prefix_size) == 0;
}
bool string_suffixes_string(const wcstring &proposed_suffix, const wcstring &value)
{
size_t suffix_size = proposed_suffix.size();
return suffix_size <= value.size() && value.compare(value.size() - suffix_size, suffix_size, proposed_suffix) == 0;
}
bool string_suffixes_string(const wchar_t *proposed_suffix, const wcstring &value)
{
size_t suffix_size = wcslen(proposed_suffix);
return suffix_size <= value.size() && value.compare(value.size() - suffix_size, suffix_size, proposed_suffix) == 0;
}
// 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())
{
return false;
}
2013-06-02 16:14:26 +08:00
/* Empty strings are considered to be subsequences of everything */
if (seq.empty())
{
return true;
}
2013-06-02 16:14:26 +08:00
size_t str_idx, seq_idx;
for (seq_idx = str_idx = 0; seq_idx < seq.size() && str_idx < str.size(); seq_idx++)
{
wchar_t c = seq.at(seq_idx);
size_t char_loc = str.find(c, str_idx);
if (char_loc == wcstring::npos)
{
/* Didn't find this character */
break;
}
else
{
/* We found it. Continue the search just after it. */
str_idx = char_loc + 1;
}
}
2013-06-02 16:14:26 +08:00
/* We succeeded if we exhausted our sequence */
assert(seq_idx <= seq.size());
return seq_idx == seq.size();
}
string_fuzzy_match_t::string_fuzzy_match_t(enum fuzzy_match_type_t t, size_t distance_first, size_t distance_second) :
2013-06-02 16:14:26 +08:00
type(t),
match_distance_first(distance_first),
match_distance_second(distance_second)
{
}
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
string_fuzzy_match_t result(fuzzy_match_none, 0, 0);
size_t location;
if (limit_type >= fuzzy_match_exact && string == match_against)
{
result.type = fuzzy_match_exact;
}
else if (limit_type >= fuzzy_match_prefix && string_prefixes_string(string, match_against))
{
result.type = fuzzy_match_prefix;
assert(match_against.size() >= string.size());
result.match_distance_first = match_against.size() - string.size();
}
else if (limit_type >= fuzzy_match_case_insensitive && wcscasecmp(string.c_str(), match_against.c_str()) == 0)
{
result.type = fuzzy_match_case_insensitive;
}
else if (limit_type >= fuzzy_match_prefix_case_insensitive && string_prefixes_string_case_insensitive(string, match_against))
{
result.type = fuzzy_match_prefix_case_insensitive;
assert(match_against.size() >= string.size());
result.match_distance_first = match_against.size() - string.size();
}
else if (limit_type >= fuzzy_match_substring && (location = match_against.find(string)) != wcstring::npos)
{
// string is contained within match against
result.type = fuzzy_match_substring;
assert(match_against.size() >= string.size());
result.match_distance_first = match_against.size() - string.size();
result.match_distance_second = location; //prefer earlier matches
}
else if (limit_type >= fuzzy_match_subsequence_insertions_only && subsequence_in_string(string, match_against))
{
result.type = fuzzy_match_subsequence_insertions_only;
assert(match_against.size() >= string.size());
result.match_distance_first = match_against.size() - string.size();
// it would be nice to prefer matches with greater matching runs here
}
return result;
}
template<typename T>
static inline int compare_ints(T a, T b)
{
if (a < b) return -1;
if (a == b) return 0;
return 1;
}
// 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)
{
return compare_ints(this->type, rhs.type);
}
else if (this->match_distance_first != rhs.match_distance_first)
{
return compare_ints(this->match_distance_first, rhs.match_distance_first);
}
else if (this->match_distance_second != rhs.match_distance_second)
{
return compare_ints(this->match_distance_second, rhs.match_distance_second);
}
return 0; //equal
}
bool list_contains_string(const wcstring_list_t &list, const wcstring &str)
{
return std::find(list.begin(), list.end(), str) != list.end();
}
int create_directory(const wcstring &d)
{
int ok = 0;
struct stat buf;
int stat_res = 0;
while ((stat_res = wstat(d, &buf)) != 0)
{
if (errno != EAGAIN)
break;
}
if (stat_res == 0)
{
if (S_ISDIR(buf.st_mode))
{
ok = 1;
}
}
else
{
if (errno == ENOENT)
{
wcstring dir = wdirname(d);
if (!create_directory(dir))
{
if (!wmkdir(d, 0700))
{
ok = 1;
}
}
}
}
return ok?0:-1;
}
__attribute__((noinline))
void bugreport()
{
debug(1,
_(L"This is a bug. Break on bugreport to debug."
L"If you can reproduce it, please send a bug report to %s."),
PACKAGE_BUGREPORT);
}
wcstring format_size(long long sz)
{
wcstring result;
const wchar_t *sz_name[]=
{
L"kB", L"MB", L"GB", L"TB", L"PB", L"EB", L"ZB", L"YB", 0
};
if (sz < 0)
{
result.append(L"unknown");
}
else if (sz < 1)
{
result.append(_(L"empty"));
}
else if (sz < 1024)
{
result.append(format_string(L"%lldB", sz));
}
else
{
int i;
for (i=0; sz_name[i]; i++)
{
if (sz < (1024*1024) || !sz_name[i+1])
{
long isz = ((long)sz)/1024;
if (isz > 9)
result.append(format_string(L"%d%ls", isz, sz_name[i]));
else
result.append(format_string(L"%.1f%ls", (double)sz/1024, sz_name[i]));
break;
}
sz /= 1024;
}
}
2012-02-10 02:14:06 +08:00
return result;
}
/* Crappy function to extract the most significant digit of an unsigned long long value */
static char extract_most_significant_digit(unsigned long long *xp)
{
unsigned long long place_value = 1;
unsigned long long x = *xp;
while (x >= 10)
{
x /= 10;
place_value *= 10;
}
*xp -= (place_value * x);
return x + '0';
}
void append_ull(char *buff, unsigned long long val, size_t *inout_idx, size_t max_len)
{
size_t idx = *inout_idx;
while (val > 0 && idx < max_len)
buff[idx++] = extract_most_significant_digit(&val);
*inout_idx = idx;
}
void append_str(char *buff, const char *str, size_t *inout_idx, size_t max_len)
{
size_t idx = *inout_idx;
while (*str && idx < max_len)
buff[idx++] = *str++;
*inout_idx = idx;
}
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
memset(buff, 0, buff_size);
size_t idx = 0;
const char * const sz_name[]=
{
"kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", NULL
};
if (sz < 1)
{
strncpy(buff, "empty", buff_size);
}
else if (sz < 1024)
{
append_ull(buff, sz, &idx, max_len);
append_str(buff, "B", &idx, max_len);
}
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)
{
append_ull(buff, isz, &idx, max_len);
}
else
{
if (isz == 0)
{
append_str(buff, "0", &idx, max_len);
}
else
{
append_ull(buff, isz, &idx, max_len);
}
// Maybe append a single fraction digit
unsigned long long remainder = sz % 1024;
if (remainder > 0)
{
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);
break;
}
sz /= 1024;
}
}
}
double timef()
{
int time_res;
struct timeval tv;
time_res = gettimeofday(&tv, 0);
if (time_res)
{
/*
Fixme: What on earth is the correct parameter value for NaN?
The man pages and the standard helpfully state that this
parameter is implementation defined. Gcc gives a warning if
a null pointer is used. But not even all mighty Google gives
a hint to what value should actually be returned.
*/
return nan("");
}
return (double)tv.tv_sec + 0.000001*tv.tv_usec;
}
void exit_without_destructors(int code)
{
_exit(code);
}
/* 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)
{
const wchar_t *const *arr = wide_arr.get();
if (! arr)
{
output->clear();
return;
}
std::vector<std::string> list;
for (size_t i=0; arr[i]; i++)
{
list.push_back(wcs2string(arr[i]));
}
output->set(list);
}
void append_path_component(wcstring &path, const wcstring &component)
{
if (path.empty() || component.empty())
{
path.append(component);
}
else
{
size_t path_len = path.size();
bool path_slash = path.at(path_len-1) == L'/';
bool comp_slash = component.at(0) == L'/';
if (! path_slash && ! comp_slash)
{
// Need a slash
path.push_back(L'/');
}
else if (path_slash && comp_slash)
{
// Too many slashes
path.erase(path_len - 1, 1);
}
path.append(component);
}
}
extern "C" {
__attribute__((noinline)) void debug_thread_error(void)
{
while (1) sleep(9999999);
}
}
void set_main_thread()
{
main_thread_id = pthread_self();
}
void configure_thread_assertions_for_testing(void)
{
thread_assertions_configured_for_testing = true;
}
/* Notice when we've forked */
static pid_t initial_pid = 0;
/* Be able to restore the term's foreground process group */
static pid_t initial_foreground_process_group = -1;
bool is_forked_child(void)
{
/* 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);
if (is_child_of_fork)
{
printf("Uh-oh: %d\n", getpid());
while (1) sleep(10000);
}
return is_child_of_fork;
}
void setup_fork_guards(void)
{
/* Notice when we fork by stashing our pid. This seems simpler than pthread_atfork(). */
initial_pid = getpid();
}
void save_term_foreground_process_group(void)
{
initial_foreground_process_group = tcgetpgrp(STDIN_FILENO);
}
void restore_term_foreground_process_group(void)
{
if (initial_foreground_process_group != -1)
{
/* This is called during shutdown and from a signal handler. We don't bother to complain on failure. */
if (0 > tcsetpgrp(STDIN_FILENO, initial_foreground_process_group))
{
/* Ignore failure */
}
}
}
bool is_main_thread()
{
assert(main_thread_id != 0);
return main_thread_id == pthread_self();
}
void assert_is_main_thread(const char *who)
{
if (! is_main_thread() && ! thread_assertions_configured_for_testing)
{
fprintf(stderr, "Warning: %s called off of main thread. Break on debug_thread_error to debug.\n", who);
debug_thread_error();
}
}
void assert_is_not_forked_child(const char *who)
{
if (is_forked_child())
{
fprintf(stderr, "Warning: %s called in a forked child. Break on debug_thread_error to debug.\n", who);
debug_thread_error();
}
}
void assert_is_background_thread(const char *who)
{
if (is_main_thread() && ! thread_assertions_configured_for_testing)
{
fprintf(stderr, "Warning: %s called on the main thread (may block!). Break on debug_thread_error to debug.\n", who);
debug_thread_error();
}
}
void assert_is_locked(void *vmutex, const char *who, const char *caller)
{
pthread_mutex_t *mutex = static_cast<pthread_mutex_t*>(vmutex);
if (0 == pthread_mutex_trylock(mutex))
{
fprintf(stderr, "Warning: %s is not locked when it should be in '%s'. Break on debug_thread_error to debug.\n", who, caller);
debug_thread_error();
pthread_mutex_unlock(mutex);
}
}
void scoped_lock::lock(void)
{
assert(! locked);
assert(! is_forked_child());
VOMIT_ON_FAILURE_NO_ERRNO(pthread_mutex_lock(lock_obj));
locked = true;
}
void scoped_lock::unlock(void)
{
assert(locked);
assert(! is_forked_child());
VOMIT_ON_FAILURE_NO_ERRNO(pthread_mutex_unlock(lock_obj));
locked = false;
}
scoped_lock::scoped_lock(pthread_mutex_t &mutex) : lock_obj(&mutex), locked(false)
{
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(),
state(),
sep(separator)
{
buffer = wcsdup(s.c_str());
str = buffer;
state = NULL;
}
bool wcstokenizer::next(wcstring &result)
{
wchar_t *tmp = wcstok(str, sep.c_str(), &state);
str = NULL;
if (tmp) result = tmp;
return tmp != NULL;
}
wcstokenizer::~wcstokenizer()
{
free(buffer);
}
template <typename CharType_t>
static CharType_t **make_null_terminated_array_helper(const std::vector<std::basic_string<CharType_t> > &argv)
{
size_t count = argv.size();
/* We allocate everything in one giant block. First compute how much space we need. */
/* N + 1 pointers */
size_t pointers_allocation_len = (count + 1) * sizeof(CharType_t *);
/* 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 */
pointers_allocation_len += sizeof(CharType_t) - 1;
pointers_allocation_len -= pointers_allocation_len % sizeof(CharType_t);
/* N null terminated strings */
size_t strings_allocation_len = 0;
for (size_t i=0; i < count; i++)
{
/* The size of the string, plus a null terminator */
strings_allocation_len += (argv.at(i).size() + 1) * sizeof(CharType_t);
}
/* Now allocate their sum */
unsigned char *base = static_cast<unsigned char *>(malloc(pointers_allocation_len + strings_allocation_len));
if (! base) return NULL;
/* Divvy it up into the pointers and strings */
CharType_t **pointers = reinterpret_cast<CharType_t **>(base);
CharType_t *strings = reinterpret_cast<CharType_t *>(base + pointers_allocation_len);
/* Start copying */
for (size_t i=0; i < count; i++)
{
const std::basic_string<CharType_t> &str = argv.at(i);
// store the current string pointer into self
*pointers++ = strings;
// copy the string into strings
strings = std::copy(str.begin(), str.end(), strings);
// each string needs a null terminator
*strings++ = (CharType_t)(0);
}
// array of pointers needs a null terminator
*pointers++ = NULL;
// Make sure we know what we're doing
assert((unsigned char *)pointers - base == (std::ptrdiff_t)pointers_allocation_len);
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));
// Return what we did
return reinterpret_cast<CharType_t**>(base);
}
wchar_t **make_null_terminated_array(const wcstring_list_t &lst)
{
return make_null_terminated_array_helper(lst);
}
char **make_null_terminated_array(const std::vector<std::string> &lst)
{
return make_null_terminated_array_helper(lst);
}