aboutsummaryrefslogtreecommitdiffhomepage
path: root/env.c
diff options
context:
space:
mode:
Diffstat (limited to 'env.c')
-rw-r--r--env.c763
1 files changed, 763 insertions, 0 deletions
diff --git a/env.c b/env.c
new file mode 100644
index 00000000..5a5ce979
--- /dev/null
+++ b/env.c
@@ -0,0 +1,763 @@
+/** \file env.c
+ Functions for setting and getting environment variables.
+*/
+
+#include <stdlib.h>
+#include <wchar.h>
+#include <string.h>
+#include <stdio.h>
+#include <locale.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <pwd.h>
+
+
+#if HAVE_NCURSES_H
+#include <ncurses.h>
+#else
+#include <curses.h>
+#endif
+
+#if HAVE_TERMIO_H
+#include <termio.h>
+#endif
+
+#include <term.h>
+
+#include "config.h"
+#include "util.h"
+#include "wutil.h"
+#include "proc.h"
+#include "common.h"
+#include "env.h"
+#include "sanity.h"
+#include "expand.h"
+#include "history.h"
+#include "reader.h"
+#include "parser.h"
+#include "env_universal.h"
+#include "env_universal.h"
+
+
+/**
+ Command used to start fishd
+*/
+#define FISHD_CMD L"if which fishd >/dev/null ^/dev/null; fishd ^/tmp/fish.%s.log; end"
+
+/**
+ At init, we read all the environment variables from this array
+*/
+extern char **environ;
+
+static int c1=0;
+
+/**
+ Struct representing one level in the function variable stack
+*/
+typedef struct env_node
+{
+ /**
+ Variable table
+ */
+ hash_table_t env;
+ /**
+ Does this node imply a new variable scope? If yes, all
+ non-global variables below this one in the stack are
+ invisible. If new_scope is set for the global variable node,
+ the universe will explode.
+ */
+ int new_scope;
+ /**
+ Does this node contain any variables which are exported to subshells
+ */
+ int export;
+
+ /**
+ Pointer to next level
+ */
+ struct env_node *next;
+}
+env_node_t;
+
+/**
+ A variable entry. Stores the value of a variable and whether it
+ should be exported. Obviously, it needs to be allocated large
+ enough to fit the value string.
+*/
+typedef struct var_entry
+{
+ int export; /**< Whether the variable should be exported */
+ wchar_t val[0]; /**< The value of the variable */
+}
+var_entry_t;
+
+/**
+ Top node on the function stack
+*/
+static env_node_t *top=0;
+
+/**
+ Bottom node on the function stack
+*/
+static env_node_t *global_env = 0;
+
+
+/**
+ Table for global variables
+*/
+static hash_table_t *global;
+
+/**
+ Table of variables that may not be set using the set command.
+*/
+static hash_table_t env_read_only;
+
+/**
+ Exported variable array used by execv
+*/
+static char **export_arr=0;
+
+/**
+ Flag for checking if we need to regenerate the exported variable
+ array
+*/
+static int has_changed = 1;
+
+/**
+ Number of variables marked for export. The actual number of
+ variables actually exported may be lower because of variable
+ scoping rules.
+*/
+static int export_count=0;
+
+
+/**
+ Free hash key and hash value
+*/
+static void clear_hash_entry( const void *key, const void *data )
+{
+ var_entry_t *entry = (var_entry_t *)data;
+ if( entry->export )
+ has_changed = 1;
+
+ free( (void *)key );
+ free( (void *)data );
+}
+
+/**
+ This stringbuffer is used to store the value of dynamically
+ generated variables, such as history.
+*/
+static string_buffer_t dyn_var;
+
+/**
+ Variable used by env_get_names to communicate auxiliary information
+ to add_key_to_hash
+*/
+static int get_names_show_exported;
+/**
+ Variable used by env_get_names to communicate auxiliary information
+ to add_key_to_hash
+*/
+static int get_names_show_unexported;
+
+/**
+ When fishd isn't started, this function is provided to
+ env_universal as a callback, it tries to start upÄ fishd. It's
+ implementation is a bit of a hack, since it just calls a bit of
+ shellscript, and the shell is not properly initialized ad this
+ point. Should be changed to deferr the evaluation until fish has
+ been properly initialized.
+*/
+static void start_fishd()
+{
+ string_buffer_t cmd;
+ struct passwd *pw;
+
+ sb_init( &cmd );
+ pw = getpwuid(getuid());
+
+ debug( 3, L"Spawning new copy of fishd" );
+
+ if( !pw )
+ {
+ debug( 0, L"Could not get user information" );
+ return;
+ }
+
+ sb_printf( &cmd, FISHD_CMD, pw->pw_name );
+
+ eval( (wchar_t *)cmd.buff,
+ 0,
+ TOP );
+ sb_destroy( &cmd );
+}
+
+void env_init()
+{
+ char **p;
+
+ sb_init( &dyn_var );
+
+ /*
+ These variables can not be altered directly by the user
+ */
+ hash_init( &env_read_only, &hash_wcs_func, &hash_wcs_cmp );
+
+ hash_put( &env_read_only, L"status", L"" );
+ hash_put( &env_read_only, L"history", L"" );
+ hash_put( &env_read_only, L"_", L"" );
+ hash_put( &env_read_only, L"LINES", L"" );
+ hash_put( &env_read_only, L"COLUMNS", L"" );
+ hash_put( &env_read_only, L"PWD", L"" );
+
+ /*
+ HOME should be writeable by root, since this is often a
+ convenient way to install software.
+ */
+ if( getuid() != 0 )
+ hash_put( &env_read_only, L"HOME", L"" );
+
+ top = malloc( sizeof(env_node_t) );
+ top->next = 0;
+ top->new_scope = 0;
+ top->export=0;
+ hash_init( &top->env, &hash_wcs_func, &hash_wcs_cmp );
+ global_env = top;
+ global = &top->env;
+
+ /*
+ Import environment variables
+ */
+ for( p=environ; *p; p++ )
+ {
+ wchar_t *key, *val;
+ wchar_t *pos;
+
+ key = str2wcs(*p);
+
+ if( !key )
+ continue;
+
+ val = wcschr( key, L'=' );
+
+ if( val == 0 )
+ env_set( key, L"", ENV_EXPORT );
+ else
+ {
+ *val = L'\0';
+ val++;
+ pos=val;
+ while( *pos )
+ {
+ if( *pos == L':' )
+ *pos = ARRAY_SEP;
+ pos++;
+ }
+// fwprintf( stderr, L"Set $%ls to %ls\n", key, val );
+
+ env_set( key, val, ENV_EXPORT | ENV_GLOBAL );
+ }
+ free(key);
+ }
+
+ env_universal_init( env_get( L"FISHD_SOKET_DIR"), env_get(L"USER"), &start_fishd );
+
+}
+
+void env_destroy()
+{
+ char **ptr;
+
+ env_universal_destroy();
+// fwprintf( stderr, L"Filled %d exported vars\n", c1 );
+
+ sb_destroy( &dyn_var );
+
+ while( &top->env != global )
+ env_pop();
+
+ hash_destroy( &env_read_only );
+
+ hash_foreach( global, &clear_hash_entry );
+ hash_destroy( global );
+ free( top );
+
+ if( export_arr != 0 )
+ {
+ for( ptr = export_arr; *ptr; ptr++ )
+ free( *ptr );
+
+ free( export_arr );
+ }
+}
+
+/**
+ Find the scope hashtable containing the variable with the specified
+ key
+*/
+static env_node_t *env_get_node( const wchar_t *key )
+{
+ var_entry_t* res;
+ env_node_t *env = top;
+
+
+ while( env != 0 )
+ {
+ res = (var_entry_t *) hash_get( &env->env,
+ key );
+ if( res != 0 )
+ {
+ return env;
+ }
+
+ if( env->new_scope )
+ env = global_env;
+ else
+ env = env->next;
+ }
+
+ return 0;
+}
+
+void env_set( const wchar_t *key,
+ const wchar_t *val,
+ int var_mode )
+{
+ int free_val = 0;
+ var_entry_t *entry;
+ env_node_t *node;
+ int has_changed_old = has_changed;
+ int has_changed_new = 0;
+ var_entry_t *e=0;
+
+
+ if( (var_mode & ENV_USER ) &&
+ hash_get( &env_read_only, key ) )
+ {
+ return;
+ }
+
+ if( wcscmp(key, L"LANG" )==0 )
+ {
+ fish_setlocale(LC_ALL,val);
+ }
+
+ if( var_mode & ENV_UNIVERSAL )
+ {
+ env_universal_set( key, val );
+ return;
+ }
+
+ if( val == 0 )
+ {
+ wchar_t *prev_val;
+ free_val = 1;
+ prev_val = env_get( key );
+ val = wcsdup( prev_val?prev_val:L"" );
+ }
+
+ node = env_get_node( key );
+ if( &node->env != 0 )
+ {
+ e = (var_entry_t *) hash_get( &node->env,
+ key );
+
+ if( e->export )
+ has_changed_new = 1;
+
+ }
+
+ if( (var_mode & ENV_LOCAL) ||
+ (var_mode & ENV_GLOBAL) )
+ {
+ node = ( var_mode & ENV_GLOBAL )?global_env:top;
+ }
+ else
+ {
+ if( node )
+ {
+ if( !(var_mode & ENV_EXPORT ) &&
+ !(var_mode & ENV_UNEXPORT ) )
+ {
+ var_mode = e->export?ENV_EXPORT:0;
+ }
+ }
+ else
+ {
+ if( env_universal_get( key ) )
+ {
+ env_universal_set( key, val );
+ return;
+ }
+ else
+ {
+ node = top;
+ }
+ }
+ }
+
+// env_remove( key, 0 );
+ void *k, *v;
+ hash_remove( &node->env, key, (const void **)&k, (const void **)&v );
+ free( k );
+ free( v );
+
+ entry = malloc( sizeof( var_entry_t ) +
+ sizeof(wchar_t )*(wcslen(val)+1));
+
+ if( var_mode & ENV_EXPORT)
+ {
+ entry->export = 1;
+ export_count++;
+ has_changed_new = 1;
+ }
+ else
+ entry->export = 0;
+
+ wcscpy( entry->val, val );
+
+ hash_put( &node->env, wcsdup(key), entry );
+
+ if( entry->export )
+ {
+ node->export=1;
+ }
+
+ if( free_val )
+ free((void *)val);
+
+// if( has_changed_new && !has_changed_old )
+// fwprintf( stderr, L"Reexport after setting %ls to %ls\n", key, val );
+
+ has_changed = has_changed_old | has_changed_new;
+
+}
+
+/**
+ Attempt to remove/free the specified key/value pair from the
+ specified hash table.
+*/
+static int try_remove( env_node_t *n,
+ const wchar_t *key )
+{
+ wchar_t *old_key, *old_val;
+ if( n == 0 )
+ return 0;
+
+ hash_remove( &n->env,
+ key,
+ (const void **)&old_key,
+ (const void **)&old_val );
+ if( old_key != 0 )
+ {
+ var_entry_t * v = (var_entry_t *)old_val;
+ if( v->export )
+ {
+ export_count --;
+ has_changed = 1;
+ }
+
+ free(old_key);
+ free(old_val);
+ return 1;
+ }
+
+ if( n->new_scope )
+ return try_remove( global_env, key );
+ else
+ return try_remove( n->next, key );
+}
+
+
+void env_remove( const wchar_t *key, int var_mode )
+{
+ if( (var_mode & ENV_USER ) &&
+ hash_get( &env_read_only, key ) )
+ {
+ return;
+ }
+
+ if( !try_remove( top, key ) )
+ {
+ env_universal_remove( key );
+ }
+}
+
+
+wchar_t *env_get( const wchar_t *key )
+{
+ var_entry_t *res;
+ env_node_t *env = top;
+
+ if( wcscmp( key, L"history" ) == 0 )
+ {
+ wchar_t *current;
+ int i;
+ int add_current=0;
+ sb_clear( &dyn_var );
+
+ current = reader_get_buffer();
+ if( current && wcslen( current ) )
+ {
+ add_current=1;
+ sb_append( &dyn_var, current );
+ }
+
+ for( i=add_current; i<8; i++ )
+ {
+ wchar_t *next = history_get( i-add_current );
+ if( !next )
+ {
+ debug( 1, L"No history at idx %d\n", i );
+ break;
+ }
+
+ if( i!=0)
+ sb_append( &dyn_var, ARRAY_SEP_STR );
+ sb_append( &dyn_var, next );
+ }
+ return (wchar_t *)dyn_var.buff;
+ }
+
+ while( env != 0 )
+ {
+ res = (var_entry_t *) hash_get( &env->env,
+ key );
+ if( res != 0 )
+ {
+ return res->val;
+ }
+
+ if( env->new_scope )
+ env = global_env;
+ else
+ env = env->next;
+ }
+ return env_universal_get( key );
+}
+
+static int local_scope_exports( env_node_t *n )
+{
+
+ if( n==global_env )
+ return 0;
+
+ if( n->export )
+ return 1;
+
+ if( n->new_scope )
+ return 0;
+
+ return local_scope_exports( n->next );
+}
+
+void env_push( int new_scope )
+{
+ env_node_t *node = malloc( sizeof(env_node_t) );
+ node->next = top;
+ node->export=0;
+ hash_init( &node->env, &hash_wcs_func, &hash_wcs_cmp );
+ node->new_scope=new_scope;
+ if( new_scope )
+ {
+ has_changed = local_scope_exports(top);
+ }
+ top = node;
+
+}
+
+/*static int scope_count( env_node_t *n )
+{
+ if( n == global_env )
+ return 0;
+ return( scope_count( n->next) + 1 );
+}
+*/
+
+void env_pop()
+{
+ if( &top->env != global )
+ {
+ env_node_t *killme = top;
+
+ if( killme->new_scope )
+ {
+ has_changed = killme->export || local_scope_exports( killme->next );
+ }
+
+ top = top->next;
+ hash_foreach( &killme->env, &clear_hash_entry );
+ hash_destroy( &killme->env );
+ free( killme );
+
+ }
+ else
+ {
+ debug( 0,
+ L"Tried to pop empty environment stack." );
+ sanity_lose();
+ }
+}
+
+/**
+ Recreate the table of global variables used by execv
+*/
+static void fill_arr( const void *key, const void *val, void *aux )
+{
+ var_entry_t *val_entry = (var_entry_t *)val;
+ if( val_entry->export )
+ {
+
+ c1++;
+
+ wchar_t *wcs_val = wcsdup( val_entry->val );
+ wchar_t *pos = wcs_val;
+
+ int *idx_ptr = (int *)aux;
+ char *key_str = wcs2str((wchar_t *)key);
+
+ char *val_str;
+ char *woot;
+
+ while( *pos )
+ {
+ if( *pos == ARRAY_SEP )
+ *pos = L':';
+ pos++;
+ }
+
+ val_str = wcs2str( wcs_val );
+ free( wcs_val );
+
+ woot = malloc( sizeof(char)*( strlen(key_str) +
+ strlen(val_str) + 2) );
+
+ strcpy( woot, key_str );
+ strcat( woot, "=" );
+ strcat( woot, val_str );
+ export_arr[*idx_ptr] = woot;
+ (*idx_ptr)++;
+
+ free( key_str );
+ free( val_str );
+ }
+}
+
+
+/**
+ Function used with hash_foreach to insert keys of one table into
+ another
+*/
+static void add_key_to_hash( const void *key,
+ const void *data,
+ void *aux )
+{
+ var_entry_t *e = (var_entry_t *)data;
+ if( ( e->export && get_names_show_exported) ||
+ ( !e->export && get_names_show_unexported) )
+ hash_put( (hash_table_t *)aux, key, 0 );
+}
+static void add_universal_key_to_hash( const void *key,
+ const void *data,
+ void *aux )
+{
+ hash_put( (hash_table_t *)aux, key, 0 );
+}
+
+void env_get_names( array_list_t *l, int flags )
+{
+ int show_local = flags & ENV_LOCAL;
+ int show_global = flags & ENV_GLOBAL;
+ int show_universal = flags & ENV_UNIVERSAL;
+
+ hash_table_t names;
+ env_node_t *n=top;
+
+ get_names_show_exported =
+ flags & ENV_EXPORT|| (!(flags & ENV_UNEXPORT));
+ get_names_show_unexported =
+ flags & ENV_UNEXPORT|| (!(flags & ENV_EXPORT));
+
+ if( !show_local && !show_global && !show_universal )
+ {
+ show_local =show_universal = show_global=1;
+ }
+
+ hash_init( &names, &hash_wcs_func, &hash_wcs_cmp );
+
+ if( show_local )
+ {
+ while( n )
+ {
+ if( n == global_env )
+ break;
+
+ hash_foreach2( &n->env,
+ add_key_to_hash,
+ &names );
+
+ if( n->new_scope )
+ break;
+ else
+ n = n->next;
+
+ }
+ }
+
+ if( show_global )
+ {
+ hash_foreach2( &global_env->env,
+ add_key_to_hash,
+ &names );
+ if( get_names_show_unexported )
+ al_push( l, L"history" );
+ }
+
+ if( show_universal )
+ {
+ if( get_names_show_unexported )
+ hash_foreach2( &env_universal_var,
+ add_universal_key_to_hash,
+ &names );
+ }
+
+ hash_get_keys( &names, l );
+ hash_destroy( &names );
+}
+
+
+char **env_export_arr()
+{
+ if( has_changed )
+ {
+ int pos=0;
+ char **ptr;
+ env_node_t *n=top;
+
+ if( export_arr != 0 )
+ {
+ for( ptr = export_arr; *ptr; ptr++ )
+ free( *ptr );
+ }
+
+ export_arr = realloc( export_arr,
+ sizeof(char *)*(export_count + 1) );
+
+ while( n )
+ {
+ hash_foreach2( &n->env, &fill_arr, &pos );
+
+ if( n->new_scope )
+ n = global_env;
+ else
+ n = n->next;
+
+ }
+ export_arr[pos]=0;
+ has_changed=0;
+ }
+ return export_arr;
+}