mirror of
https://github.com/fish-shell/fish-shell
synced 2024-11-11 23:47:25 +00:00
a475dd15e6
universal variable storage into a class for better testability.
708 lines
15 KiB
C++
708 lines
15 KiB
C++
/**
|
|
\file env_universal_common.c
|
|
|
|
The utility library for universal variables. Used both by the
|
|
client library and by the daemon.
|
|
|
|
*/
|
|
#include "config.h"
|
|
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <wchar.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
#include <pwd.h>
|
|
#include <errno.h>
|
|
#include <sys/stat.h>
|
|
#include <dirent.h>
|
|
#include <wctype.h>
|
|
|
|
#include <errno.h>
|
|
#include <locale.h>
|
|
#include <dirent.h>
|
|
#include <signal.h>
|
|
#include <sys/stat.h>
|
|
#include <map>
|
|
|
|
#ifdef HAVE_SYS_SELECT_H
|
|
#include <sys/select.h>
|
|
#endif
|
|
|
|
#include "fallback.h"
|
|
#include "util.h"
|
|
|
|
#include "common.h"
|
|
#include "wutil.h"
|
|
#include "utf8.h"
|
|
#include "env_universal_common.h"
|
|
|
|
/**
|
|
Non-wide version of the set command
|
|
*/
|
|
#define SET_MBS "SET"
|
|
|
|
/**
|
|
Non-wide version of the set_export command
|
|
*/
|
|
#define SET_EXPORT_MBS "SET_EXPORT"
|
|
|
|
/**
|
|
Non-wide version of the erase command
|
|
*/
|
|
#define ERASE_MBS "ERASE"
|
|
|
|
/**
|
|
Non-wide version of the barrier command
|
|
*/
|
|
#define BARRIER_MBS "BARRIER"
|
|
|
|
/**
|
|
Non-wide version of the barrier_reply command
|
|
*/
|
|
#define BARRIER_REPLY_MBS "BARRIER_REPLY"
|
|
|
|
/**
|
|
Error message
|
|
*/
|
|
#define PARSE_ERR L"Unable to parse universal variable message: '%ls'"
|
|
|
|
/**
|
|
ERROR string for internal buffered reader
|
|
*/
|
|
#define ENV_UNIVERSAL_ERROR 0x100
|
|
|
|
/**
|
|
EAGAIN string for internal buffered reader
|
|
*/
|
|
#define ENV_UNIVERSAL_AGAIN 0x101
|
|
|
|
/**
|
|
EOF string for internal buffered reader
|
|
*/
|
|
#define ENV_UNIVERSAL_EOF 0x102
|
|
|
|
static void parse_message(wchar_t *msg,
|
|
connection_t *src);
|
|
|
|
/**
|
|
The table of all universal variables
|
|
*/
|
|
static env_universal_t s_env_universal_var;
|
|
|
|
/**
|
|
Callback function, should be called on all events
|
|
*/
|
|
static void (*callback)(fish_message_type_t type,
|
|
const wchar_t *key,
|
|
const wchar_t *val);
|
|
|
|
/* UTF <-> wchar conversions. These return a string allocated with malloc. These call sites could be cleaned up substantially to eliminate the dependence on malloc. */
|
|
static wchar_t *utf2wcs(const char *input)
|
|
{
|
|
wchar_t *result = NULL;
|
|
wcstring converted;
|
|
if (utf8_to_wchar_string(input, &converted))
|
|
{
|
|
result = wcsdup(converted.c_str());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static char *wcs2utf(const wchar_t *input)
|
|
{
|
|
char *result = NULL;
|
|
std::string converted;
|
|
if (wchar_to_utf8_string(input, &converted))
|
|
{
|
|
result = strdup(converted.c_str());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void env_universal_common_init(void (*cb)(fish_message_type_t type, const wchar_t *key, const wchar_t *val))
|
|
{
|
|
callback = cb;
|
|
}
|
|
|
|
/**
|
|
Read one byte of date form the specified connection
|
|
*/
|
|
static int read_byte(connection_t *src)
|
|
{
|
|
|
|
if (src->buffer_consumed >= src->read_buffer.size())
|
|
{
|
|
char local[ENV_UNIVERSAL_BUFFER_SIZE];
|
|
|
|
ssize_t res = read(src->fd, local, sizeof local);
|
|
|
|
// debug(4, L"Read chunk '%.*s'", res, src->buffer );
|
|
|
|
if (res < 0)
|
|
{
|
|
|
|
if (errno == EAGAIN ||
|
|
errno == EINTR)
|
|
{
|
|
return ENV_UNIVERSAL_AGAIN;
|
|
}
|
|
|
|
return ENV_UNIVERSAL_ERROR;
|
|
|
|
}
|
|
else if (res == 0)
|
|
{
|
|
return ENV_UNIVERSAL_EOF;
|
|
}
|
|
else
|
|
{
|
|
src->read_buffer.clear();
|
|
src->read_buffer.insert(src->read_buffer.begin(), local, local + res);
|
|
src->buffer_consumed = 0;
|
|
}
|
|
}
|
|
|
|
return src->read_buffer.at(src->buffer_consumed++);
|
|
|
|
}
|
|
|
|
|
|
void read_message(connection_t *src)
|
|
{
|
|
while (1)
|
|
{
|
|
|
|
int ib = read_byte(src);
|
|
char b;
|
|
|
|
switch (ib)
|
|
{
|
|
case ENV_UNIVERSAL_AGAIN:
|
|
{
|
|
return;
|
|
}
|
|
|
|
case ENV_UNIVERSAL_ERROR:
|
|
{
|
|
debug(2, L"Read error on fd %d, set killme flag", src->fd);
|
|
if (debug_level > 2)
|
|
wperror(L"read");
|
|
src->killme = 1;
|
|
return;
|
|
}
|
|
|
|
case ENV_UNIVERSAL_EOF:
|
|
{
|
|
src->killme = 1;
|
|
debug(3, L"Fd %d has reached eof, set killme flag", src->fd);
|
|
if (! src->input.empty())
|
|
{
|
|
char c = 0;
|
|
src->input.push_back(c);
|
|
debug(1,
|
|
L"Universal variable connection closed while reading command. Partial command recieved: '%s'",
|
|
&src->input.at(0));
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
b = (char)ib;
|
|
|
|
if (b == '\n')
|
|
{
|
|
wchar_t *msg;
|
|
|
|
b = 0;
|
|
src->input.push_back(b);
|
|
|
|
msg = utf2wcs(&src->input.at(0));
|
|
|
|
/*
|
|
Before calling parse_message, we must empty reset
|
|
everything, since the callback function could
|
|
potentially call read_message.
|
|
*/
|
|
src->input.clear();
|
|
|
|
if (msg)
|
|
{
|
|
parse_message(msg, src);
|
|
}
|
|
else
|
|
{
|
|
debug(0, _(L"Could not convert message '%s' to wide character string"), &src->input.at(0));
|
|
}
|
|
|
|
free(msg);
|
|
|
|
}
|
|
else
|
|
{
|
|
src->input.push_back(b);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
Remove variable with specified name
|
|
*/
|
|
void env_universal_common_remove(const wcstring &name)
|
|
{
|
|
s_env_universal_var.remove(name);
|
|
}
|
|
|
|
/**
|
|
Test if the message msg contains the command cmd
|
|
*/
|
|
static bool match(const wchar_t *msg, const wchar_t *cmd)
|
|
{
|
|
size_t len = wcslen(cmd);
|
|
if (wcsncasecmp(msg, cmd, len) != 0)
|
|
return false;
|
|
|
|
if (msg[len] && msg[len]!= L' ' && msg[len] != L'\t')
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void env_universal_common_set(const wchar_t *key, const wchar_t *val, bool exportv)
|
|
{
|
|
CHECK(key,);
|
|
CHECK(val,);
|
|
|
|
s_env_universal_var.set(key, val, exportv);
|
|
|
|
if (callback)
|
|
{
|
|
callback(exportv?SET_EXPORT:SET, key, val);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
Parse message msg
|
|
*/
|
|
static void parse_message(wchar_t *msg,
|
|
connection_t *src)
|
|
{
|
|
// debug( 3, L"parse_message( %ls );", msg );
|
|
|
|
if (msg[0] == L'#')
|
|
return;
|
|
|
|
if (match(msg, SET_STR) || match(msg, SET_EXPORT_STR))
|
|
{
|
|
wchar_t *name, *tmp;
|
|
bool exportv = match(msg, SET_EXPORT_STR);
|
|
|
|
name = msg+(exportv?wcslen(SET_EXPORT_STR):wcslen(SET_STR));
|
|
while (wcschr(L"\t ", *name))
|
|
name++;
|
|
|
|
tmp = wcschr(name, L':');
|
|
if (tmp)
|
|
{
|
|
const wcstring key(name, tmp - name);
|
|
|
|
wcstring val;
|
|
if (unescape_string(tmp + 1, &val, 0))
|
|
{
|
|
env_universal_common_set(key.c_str(), val.c_str(), exportv);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
debug(1, PARSE_ERR, msg);
|
|
}
|
|
}
|
|
else if (match(msg, ERASE_STR))
|
|
{
|
|
wchar_t *name, *tmp;
|
|
|
|
name = msg+wcslen(ERASE_STR);
|
|
while (wcschr(L"\t ", *name))
|
|
name++;
|
|
|
|
tmp = name;
|
|
while (iswalnum(*tmp) || *tmp == L'_')
|
|
tmp++;
|
|
|
|
*tmp = 0;
|
|
|
|
if (!wcslen(name))
|
|
{
|
|
debug(1, PARSE_ERR, msg);
|
|
}
|
|
|
|
env_universal_common_remove(name);
|
|
|
|
if (callback)
|
|
{
|
|
callback(ERASE, name, 0);
|
|
}
|
|
}
|
|
else if (match(msg, BARRIER_STR))
|
|
{
|
|
message_t *msg = create_message(BARRIER_REPLY, 0, 0);
|
|
msg->count = 1;
|
|
src->unsent.push(msg);
|
|
try_send_all(src);
|
|
}
|
|
else if (match(msg, BARRIER_REPLY_STR))
|
|
{
|
|
if (callback)
|
|
{
|
|
callback(BARRIER_REPLY, 0, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
debug(1, PARSE_ERR, msg);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Attempt to send the specified message to the specified file descriptor
|
|
|
|
\return 1 on sucess, 0 if the message could not be sent without blocking and -1 on error
|
|
*/
|
|
static int try_send(message_t *msg,
|
|
int fd)
|
|
{
|
|
|
|
debug(3,
|
|
L"before write of %d chars to fd %d", msg->body.size(), fd);
|
|
|
|
ssize_t res = write(fd, msg->body.c_str(), msg->body.size());
|
|
|
|
if (res != -1)
|
|
{
|
|
debug(4, L"Wrote message '%s'", msg->body.c_str());
|
|
}
|
|
else
|
|
{
|
|
debug(4, L"Failed to write message '%s'", msg->body.c_str());
|
|
}
|
|
|
|
if (res == -1)
|
|
{
|
|
switch (errno)
|
|
{
|
|
case EAGAIN:
|
|
return 0;
|
|
|
|
default:
|
|
debug(2,
|
|
L"Error while sending universal variable message to fd %d. Closing connection",
|
|
fd);
|
|
if (debug_level > 2)
|
|
wperror(L"write");
|
|
|
|
return -1;
|
|
}
|
|
}
|
|
msg->count--;
|
|
|
|
if (!msg->count)
|
|
{
|
|
delete msg;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void try_send_all(connection_t *c)
|
|
{
|
|
/* debug( 3,
|
|
L"Send all updates to connection on fd %d",
|
|
c->fd );*/
|
|
while (!c->unsent.empty())
|
|
{
|
|
switch (try_send(c->unsent.front(), c->fd))
|
|
{
|
|
case 1:
|
|
c->unsent.pop();
|
|
break;
|
|
|
|
case 0:
|
|
debug(4,
|
|
L"Socket full, send rest later");
|
|
return;
|
|
|
|
case -1:
|
|
c->killme = 1;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* The universal variable format has some funny escaping requirements; here we try to be safe */
|
|
static bool is_universal_safe_to_encode_directly(wchar_t c)
|
|
{
|
|
if (c < 32 || c > 128)
|
|
return false;
|
|
|
|
return iswalnum(c) || wcschr(L"/", c);
|
|
}
|
|
|
|
/**
|
|
Escape specified string
|
|
*/
|
|
static wcstring full_escape(const wchar_t *in)
|
|
{
|
|
wcstring out;
|
|
for (; *in; in++)
|
|
{
|
|
wchar_t c = *in;
|
|
if (is_universal_safe_to_encode_directly(c))
|
|
{
|
|
out.push_back(c);
|
|
}
|
|
else if (c <= ASCII_MAX)
|
|
{
|
|
// See #1225 for discussion of use of ASCII_MAX here
|
|
append_format(out, L"\\x%.2x", c);
|
|
}
|
|
else if (c < 65536)
|
|
{
|
|
append_format(out, L"\\u%.4x", c);
|
|
}
|
|
else
|
|
{
|
|
append_format(out, L"\\U%.8x", c);
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/* Sets the body of a message to the null-terminated list of null terminated const char *. */
|
|
void set_body(message_t *msg, ...)
|
|
{
|
|
/* Start by counting the length of all the strings */
|
|
size_t body_len = 0;
|
|
const char *arg;
|
|
va_list arg_list;
|
|
va_start(arg_list, msg);
|
|
while ((arg = va_arg(arg_list, const char *)) != NULL)
|
|
body_len += strlen(arg);
|
|
va_end(arg_list);
|
|
|
|
/* Reserve that length in the string */
|
|
msg->body.reserve(body_len + 1); //+1 for trailing NULL? Do I need that?
|
|
|
|
/* Set the string contents */
|
|
va_start(arg_list, msg);
|
|
while ((arg = va_arg(arg_list, const char *)) != NULL)
|
|
msg->body.append(arg);
|
|
va_end(arg_list);
|
|
}
|
|
|
|
/* Returns an instance of message_t allocated via new */
|
|
message_t *create_message(fish_message_type_t type,
|
|
const wchar_t *key_in,
|
|
const wchar_t *val_in)
|
|
{
|
|
char *key = NULL;
|
|
|
|
// debug( 4, L"Crete message of type %d", type );
|
|
|
|
if (key_in)
|
|
{
|
|
if (wcsvarname(key_in))
|
|
{
|
|
debug(0, L"Illegal variable name: '%ls'", key_in);
|
|
return NULL;
|
|
}
|
|
|
|
key = wcs2utf(key_in);
|
|
if (!key)
|
|
{
|
|
debug(0,
|
|
L"Could not convert %ls to narrow character string",
|
|
key_in);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
message_t *msg = new message_t;
|
|
msg->count = 0;
|
|
|
|
switch (type)
|
|
{
|
|
case SET:
|
|
case SET_EXPORT:
|
|
{
|
|
if (!val_in)
|
|
{
|
|
val_in=L"";
|
|
}
|
|
|
|
wcstring esc = full_escape(val_in);
|
|
char *val = wcs2utf(esc.c_str());
|
|
set_body(msg, (type==SET?SET_MBS:SET_EXPORT_MBS), " ", key, ":", val, "\n", NULL);
|
|
free(val);
|
|
break;
|
|
}
|
|
|
|
case ERASE:
|
|
{
|
|
set_body(msg, ERASE_MBS, " ", key, "\n", NULL);
|
|
break;
|
|
}
|
|
|
|
case BARRIER:
|
|
{
|
|
set_body(msg, BARRIER_MBS, "\n", NULL);
|
|
break;
|
|
}
|
|
|
|
case BARRIER_REPLY:
|
|
{
|
|
set_body(msg, BARRIER_REPLY_MBS, "\n", NULL);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
debug(0, L"create_message: Unknown message type");
|
|
}
|
|
}
|
|
|
|
free(key);
|
|
|
|
// debug( 4, L"Message body is '%s'", msg->body );
|
|
|
|
return msg;
|
|
}
|
|
|
|
/**
|
|
Put exported or unexported variables in a string list
|
|
*/
|
|
void env_universal_common_get_names(wcstring_list_t &lst, bool show_exported, bool show_unexported)
|
|
{
|
|
wcstring_list_t names = s_env_universal_var.get_names(show_exported, show_unexported);
|
|
lst.insert(lst.end(), names.begin(), names.end());
|
|
}
|
|
|
|
|
|
env_var_t env_universal_common_get(const wcstring &name)
|
|
{
|
|
return s_env_universal_var.get(name);
|
|
}
|
|
|
|
bool env_universal_common_get_export(const wcstring &name)
|
|
{
|
|
return s_env_universal_var.get_export(name);
|
|
}
|
|
|
|
void enqueue_all(connection_t *c)
|
|
{
|
|
s_env_universal_var.enqueue_all(c);
|
|
try_send_all(c);
|
|
}
|
|
|
|
connection_t::connection_t(int input_fd) :
|
|
fd(input_fd),
|
|
killme(false),
|
|
buffer_consumed(0)
|
|
{
|
|
}
|
|
|
|
void connection_destroy(connection_t *c)
|
|
{
|
|
/*
|
|
A connection need not always be open - we only try to close it
|
|
if it is open.
|
|
*/
|
|
if (c->fd >= 0)
|
|
{
|
|
if (close(c->fd))
|
|
{
|
|
wperror(L"close");
|
|
}
|
|
}
|
|
}
|
|
|
|
env_universal_t::env_universal_t()
|
|
{
|
|
VOMIT_ON_FAILURE(pthread_mutex_init(&lock, NULL));
|
|
}
|
|
|
|
env_universal_t::~env_universal_t()
|
|
{
|
|
pthread_mutex_destroy(&lock);
|
|
}
|
|
|
|
env_var_t env_universal_t::get(const wcstring &name) const
|
|
{
|
|
env_var_t result = env_var_t::missing_var();
|
|
var_table_t::const_iterator where = vars.find(name);
|
|
if (where != vars.end())
|
|
{
|
|
result = where->second.val;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool env_universal_t::get_export(const wcstring &name) const
|
|
{
|
|
bool result = false;
|
|
var_table_t::const_iterator where = vars.find(name);
|
|
if (where != vars.end())
|
|
{
|
|
result = where->second.exportv;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void env_universal_t::set(const wcstring &key, const wcstring &val, bool exportv)
|
|
{
|
|
scoped_lock locker(lock);
|
|
var_entry_t *entry = &vars[key];
|
|
entry->val = val;
|
|
entry->exportv = exportv;
|
|
}
|
|
|
|
void env_universal_t::remove(const wcstring &key)
|
|
{
|
|
scoped_lock locker(lock);
|
|
vars.erase(key);
|
|
}
|
|
|
|
wcstring_list_t env_universal_t::get_names(bool show_exported, bool show_unexported) const
|
|
{
|
|
wcstring_list_t result;
|
|
scoped_lock locker(lock);
|
|
var_table_t::const_iterator iter;
|
|
for (iter = vars.begin(); iter != vars.end(); ++iter)
|
|
{
|
|
const wcstring &key = iter->first;
|
|
const var_entry_t &e = iter->second;
|
|
if ((e.exportv && show_exported) || (! e.exportv && show_unexported))
|
|
{
|
|
result.push_back(key);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void env_universal_t::enqueue_all(connection_t *c) const
|
|
{
|
|
scoped_lock locker(lock);
|
|
var_table_t::const_iterator iter;
|
|
for (iter = vars.begin(); iter != vars.end(); ++iter)
|
|
{
|
|
const wcstring &key = iter->first;
|
|
const var_entry_t &entry = iter->second;
|
|
|
|
message_t *msg = create_message(entry.exportv ? SET_EXPORT : SET, key.c_str(), entry.val.c_str());
|
|
msg->count=1;
|
|
c->unsent.push(msg);
|
|
}
|
|
}
|