aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorRobin Haberkorn <robin.haberkorn@googlemail.com>2015-06-14 19:08:06 +0200
committerRobin Haberkorn <robin.haberkorn@googlemail.com>2015-06-14 19:08:06 +0200
commit15409bae5ffdfce4ef17c4ccf14c8cd4c1b8f37e (patch)
tree852b915a69d57679a05ef5fec0ee6608cdcf2dee /src
parent573951d4e2bb4fb1d14212583a59ce76344593cc (diff)
downloadsciteco-15409bae5ffdfce4ef17c4ccf14c8cd4c1b8f37e.tar.gz
handle environment variables more consistently
* the registers beginning with "$" are exported into sub-process environments. Therefore macros can now modify the environment (variables) of commands executed via EC/EG. A variable can be modified temporarily, e.g.: [[$FOO] ^U[$FOO]bar$ EC...$ ][$FOO] * SciTECO accesses the global environment registers instead of using g_getenv(). Therefore now, tilde-expansion will always use the current value of the "$HOME" register. Previously, both register and environment variable could diverge. * This effectively fully maps the process environment to a subset of Q-Registers beginning with "$". * This hasn't been implemented by mapping those registers to special implementations that updates the process environment directly, since g_setenv() is non-thread-safe on UNIX and we're expected to have threads soon - at least in the GTK+ UI.
Diffstat (limited to 'src')
-rw-r--r--src/interface-curses.cpp19
-rw-r--r--src/ioview.cpp11
-rw-r--r--src/main.cpp36
-rw-r--r--src/parser.cpp28
-rw-r--r--src/qregisters.cpp75
-rw-r--r--src/qregisters.h14
-rw-r--r--src/spawn.cpp17
7 files changed, 168 insertions, 32 deletions
diff --git a/src/interface-curses.cpp b/src/interface-curses.cpp
index 5f8a00c..1dcca8f 100644
--- a/src/interface-curses.cpp
+++ b/src/interface-curses.cpp
@@ -178,6 +178,13 @@ InterfaceCurses::init_interactive(void)
void
InterfaceCurses::init_batch(void)
{
+ /*
+ * NOTE: It's still safe to use g_getenv().
+ * Actually the process environment has not yet been
+ * imported into the Q-Register table.
+ * Also, the batch mode initialization will be
+ * simplified soon, anyway.
+ */
const gchar *term = g_getenv("TERM");
/*
@@ -227,7 +234,13 @@ InterfaceCurses::init_interactive(void)
{
const gchar *term = g_getenv("TERM");
- /* at least try to report a broken $TERM */
+ /*
+ * At least try to report a broken $TERM.
+ * g_getenv() may still be used here since we must refer to
+ * same value as used in init_batch() as opposed to the
+ * current value of the "$TERM" register.
+ * Also, this code will have to be simplified soon, anyway.
+ */
if (!term || !*term) {
g_fprintf(stderr, "Error initializing interactive mode: "
"$TERM is unset or empty.\n");
@@ -816,6 +829,10 @@ InterfaceCurses::event_loop_impl(void)
* Set window title to a reasonable default,
* in case it is not reset immediately by the
* shell.
+ * FIXME: It may be unsafe to access $TERM here
+ * and the value of Q-Register $TERM may have
+ * diverged. This should be adapted once we rewrite
+ * batch-mode initialization!
*/
#if !PDCURSES && defined(HAVE_TIGETSTR)
set_window_title(g_getenv("TERM") ? : "");
diff --git a/src/ioview.cpp b/src/ioview.cpp
index e43fc2c..713b3fb 100644
--- a/src/ioview.cpp
+++ b/src/ioview.cpp
@@ -36,6 +36,7 @@
#include "interface.h"
#include "undo.h"
#include "error.h"
+#include "qregisters.h"
#include "ioview.h"
#ifdef HAVE_WINDOWS_H
@@ -675,12 +676,14 @@ IOView::save(const gchar *filename)
*
* This supports only strings with a "~" prefix.
* A user name after "~" is not supported.
- * The $HOME environment variable is used to retrieve
+ * The $HOME environment variable/register is used to retrieve
* the current user's home directory.
*/
gchar *
expand_path(const gchar *path)
{
+ gchar *home, *ret;
+
if (!path)
path = "";
@@ -693,7 +696,11 @@ expand_path(const gchar *path)
* but this ensures that a proper path is constructed even if
* it does (e.g. $HOME is changed later on).
*/
- return g_build_filename(g_getenv("HOME"), path+1, NIL);
+ home = QRegisters::globals["$HOME"]->get_string();
+ ret = g_build_filename(home, path+1, NIL);
+ g_free(home);
+
+ return ret;
}
#ifdef G_OS_UNIX
diff --git a/src/main.cpp b/src/main.cpp
index 6966d22..120d73e 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -196,9 +196,15 @@ static inline void
initialize_environment(const gchar *program)
{
gchar *default_configpath, *abs_path;
- gchar **env;
/*
+ * Initialize some "special" environment variables.
+ * For ease of use and because there are no threads yet,
+ * we modify the process environment directly.
+ * Later it is imported into the global Q-Register table
+ * and the process environment should no longer be accessed
+ * directly.
+ *
* Initialize and canonicalize $HOME.
* Therefore we can refer to $HOME as the
* current user's home directory on any platform
@@ -237,20 +243,19 @@ initialize_environment(const gchar *program)
g_setenv("SCITECOPATH", abs_path, TRUE);
g_free(abs_path);
- env = g_listenv();
-
- for (gchar **key = env; *key; key++) {
- gchar name[1 + strlen(*key) + 1];
- QRegister *reg;
-
- name[0] = '$';
- strcpy(name + 1, *key);
-
- reg = QRegisters::globals.insert(name);
- reg->set_string(g_getenv(*key));
- }
-
- g_strfreev(env);
+ /*
+ * Import process environment into global Q-Register
+ * table. While it is safe to use g_setenv() early
+ * on at startup, it might be problematic later on
+ * (e.g. it's non-thread-safe).
+ * Therefore the environment registers in the global
+ * table should be used from now on to set and get
+ * environment variables.
+ * When spawning external processes that should inherit
+ * the environment variables, the environment should
+ * be exported via QRegisters::globals.get_environ().
+ */
+ QRegisters::globals.set_environ();
}
/*
@@ -388,6 +393,7 @@ main(int argc, char **argv)
}
if (!mung_file && mung_profile)
+ /* NOTE: Still safe to use g_getenv() */
mung_file = g_build_filename(g_getenv("SCITECOCONFIG"),
INI_FILE, NIL);
diff --git a/src/parser.cpp b/src/parser.cpp
index 3e3f387..f88ae90 100644
--- a/src/parser.cpp
+++ b/src/parser.cpp
@@ -1595,16 +1595,18 @@ UndoTokenChangeDir::run(void)
*
* If <directory> is omitted, the working directory
* is changed to the current user's home directory
- * as set by the \fBHOME\fP environment variable.
- * This variable is alwas initialized by \*(ST
+ * as set by the \fBHOME\fP environment variable
+ * (i.e. its corresponding \(lq$HOME\(rq environment
+ * register).
+ * This variable is always initialized by \*(ST
* (see \fBsciteco\fP(1)).
* Therefore the expression \(lqFG\fB$\fP\(rq is
- * roughly equivalent to both \(lqFG~\fB$\fP\(rq and
+ * exactly equivalent to both \(lqFG~\fB$\fP\(rq and
* \(lqFG^EQ[$HOME]\fB$\fP\(rq.
*
* The current working directory is also mapped to
- * the Q-Register \(lq$\(rq (dollar sign) which
- * may be used retrieve the current working directory.
+ * the special global Q-Register \(lq$\(rq (dollar sign)
+ * which may be used retrieve the current working directory.
*
* String-building characters are enabled on this
* command and directories can be tab-completed.
@@ -1612,19 +1614,25 @@ UndoTokenChangeDir::run(void)
State *
StateChangeDir::got_file(const gchar *filename)
{
+ gchar *dir;
+
BEGIN_EXEC(&States::start);
/* passes ownership of string to undo token object */
undo.push(new UndoTokenChangeDir(g_get_current_dir()));
- if (!*filename)
- filename = g_getenv("HOME");
+ dir = *filename ? g_strdup(filename)
+ : QRegisters::globals["$HOME"]->get_string();
- if (g_chdir(filename))
+ if (g_chdir(dir)) {
/* FIXME: Is errno usable on Windows here? */
- throw Error("Cannot change working directory "
- "to \"%s\"", filename);
+ Error err("Cannot change working directory "
+ "to \"%s\"", dir);
+ g_free(dir);
+ throw err;
+ }
+ g_free(dir);
return &States::start;
}
diff --git a/src/qregisters.cpp b/src/qregisters.cpp
index 20c1380..22d7300 100644
--- a/src/qregisters.cpp
+++ b/src/qregisters.cpp
@@ -529,7 +529,80 @@ QRegisterTable::edit(QRegister *reg)
QRegisters::current = reg;
}
-/*
+void
+QRegisterTable::set_environ(void)
+{
+ /*
+ * NOTE: Using g_get_environ() would be more efficient,
+ * but it appears to be broken, at least on Wine
+ * and Windows 2000.
+ */
+ gchar **env = g_listenv();
+
+ for (gchar **key = env; *key; key++) {
+ gchar name[1 + strlen(*key) + 1];
+ QRegister *reg;
+
+ name[0] = '$';
+ strcpy(name + 1, *key);
+
+ reg = insert(name);
+ reg->set_string(g_getenv(*key));
+ }
+
+ g_strfreev(env);
+}
+
+gchar **
+QRegisterTable::get_environ(void)
+{
+ QRegister *first = nfind("$");
+
+ gint envp_len = 1;
+ gchar **envp, **p;
+
+ /*
+ * Iterate over all registers beginning with "$" to
+ * guess the size required for the environment array.
+ * This may waste a few bytes because not __every__
+ * register beginning with "$" is an environment
+ * register.
+ */
+ for (QRegister *cur = first;
+ cur && cur->name[0] == '$';
+ cur = (QRegister *)cur->next())
+ envp_len++;
+
+ p = envp = (gchar **)g_malloc(sizeof(gchar *)*envp_len);
+
+ for (QRegister *cur = first;
+ cur && cur->name[0] == '$';
+ cur = (QRegister *)cur->next()) {
+ gchar *value;
+
+ /*
+ * Ignore the "$" register (not an environment
+ * variable register) and registers whose
+ * name contains "=" (not allowed in environment
+ * variable names).
+ */
+ if (!cur->name[1] || strchr(cur->name+1, '='))
+ continue;
+
+ value = cur->get_string();
+ /* more efficient than g_environ_setenv() */
+ *p++ = g_strconcat(cur->name+1, "=", value, NIL);
+ g_free(value);
+ }
+
+ *p = NULL;
+
+ return envp;
+}
+
+/**
+ * Free resources associated with table.
+ *
* This is similar to RBTree::clear() but
* has the advantage that we can check whether some
* register is currently edited.
diff --git a/src/qregisters.h b/src/qregisters.h
index ec40bb4..0ca230e 100644
--- a/src/qregisters.h
+++ b/src/qregisters.h
@@ -255,6 +255,10 @@ public:
insert(QRegister *reg)
{
reg->must_undo = must_undo;
+ /* FIXME: Returns already existing regs with the same name.
+ This could be used to optimize commands that initialize
+ a register if it does not yet exist (saves one table
+ lookup): */
RBTree::insert(reg);
return reg;
}
@@ -283,6 +287,13 @@ public:
return operator [](buf);
}
+ inline QRegister *
+ nfind(const gchar *name)
+ {
+ QRegister reg(name);
+ return (QRegister *)RBTree::nfind(&reg);
+ }
+
void edit(QRegister *reg);
inline QRegister *
edit(const gchar *name)
@@ -295,6 +306,9 @@ public:
return reg;
}
+ void set_environ(void);
+ gchar **get_environ(void);
+
void clear(void);
};
diff --git a/src/spawn.cpp b/src/spawn.cpp
index 9f951f5..b5d64e9 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -198,6 +198,14 @@ parse_shell_command_line(const gchar *cmdline, GError **error)
* \(lq0,128ED\(rq, and is recommended when writing cross-platform
* macros using the EC command.
*
+ * The spawned process inherits both \*(ST's current working
+ * directory and its environment variables.
+ * More precisely, \*(ST uses its environment registers
+ * to construct the spawned process' environment.
+ * Therefore it is also straight forward to change the working
+ * directory or some environment variable temporarily
+ * for a spawned process.
+ *
* Note that when run interactively and subsequently rubbed
* out, \*(ST can easily undo all changes to the editor
* state.
@@ -211,7 +219,7 @@ parse_shell_command_line(const gchar *cmdline, GError **error)
*
* In interactive mode, \*(ST performs TAB-completion
* of filenames in the <command> string parameter but
- * by doing so does not attempt any escaping of shell-relevant
+ * does not attempt any escaping of shell-relevant
* characters like whitespaces.
*/
StateExecuteCommand::StateExecuteCommand() : StateExpectString()
@@ -308,7 +316,7 @@ StateExecuteCommand::done(const gchar *str)
*/
return &States::start;
- gchar **argv;
+ gchar **argv, **envp;
static const gint flags = G_SPAWN_DO_NOT_REAP_CHILD |
G_SPAWN_SEARCH_PATH |
G_SPAWN_STDERR_TO_DEV_NULL;
@@ -330,11 +338,14 @@ StateExecuteCommand::done(const gchar *str)
if (!argv)
goto gerror;
- g_spawn_async_with_pipes(NULL, argv, NULL, (GSpawnFlags)flags,
+ envp = QRegisters::globals.get_environ();
+
+ g_spawn_async_with_pipes(NULL, argv, envp, (GSpawnFlags)flags,
NULL, NULL, &pid,
&stdin_fd, &stdout_fd, NULL,
&ctx.error);
+ g_strfreev(envp);
g_strfreev(argv);
if (ctx.error)