fish-shell/builtin_set.c
2006-04-22 01:05:58 +10:00

729 lines
13 KiB
C

/** \file builtin_set.c Functions defining the set builtin
Functions used for implementing the set builtin.
*/
#include <stdlib.h>
#include <stdio.h>
#include <wchar.h>
#include <wctype.h>
#include <sys/types.h>
#include <termios.h>
#include <signal.h>
#include "config.h"
#include "fallback.h"
#include "util.h"
#include "wutil.h"
#include "builtin.h"
#include "env.h"
#include "expand.h"
#include "common.h"
#include "wgetopt.h"
#include "proc.h"
#include "parser.h"
#include "translate.h"
#define BUILTIN_SET_PATH_ERROR L"%ls: Could not add component %ls to %ls.\n"
#define BUILTIN_SET_PATH_HINT L"%ls: Did you mean 'set %ls $%ls %ls'?\n"
/**
Call env_set. On error, print a description of the problem to
stderr.
*/
static void my_env_set( const wchar_t *key, const wchar_t *val, int scope )
{
switch( env_set( key, val, scope | ENV_USER ) )
{
case ENV_PERM:
{
sb_printf( sb_err, _(L"%ls: Tried to change the read-only variable '%ls'\n"), L"set", key );
break;
}
}
}
/**
Extract the name from a destination argument of the form name[index1 index2...]
*/
static int parse_fill_name( string_buffer_t *name,
const wchar_t *src)
{
if (src == 0)
{
return 0;
}
while (iswalnum(*src) || *src == L'_')
{
sb_append_char(name, *src++);
}
if (*src != L'[' && *src != L'\0')
{
sb_printf( sb_err, BUILTIN_ERR_VARCHAR, L"set", *src );
sb_append2(sb_err, parser_current_line(), L"\n", (void *)0 );
// builtin_print_help( L"set", sb_err );
return -1;
}
else
{
return 0;
}
}
/**
Extract indexes from a destination argument of the form name[index1 index2...]
*/
static int parse_fill_indexes( array_list_t *indexes,
const wchar_t *src)
{
int count = 0;
if (src == 0)
{
return 0;
}
while (*src != L'\0' && (iswalnum(*src) || *src == L'_'))
{
src++;
}
if (*src == L'\0')
{
return 0;
}
if (*src++ != L'[')
{
return -1;
}
while (iswspace(*src))
{
src++;
}
while (*src != L']')
{
wchar_t *end;
long l_ind = wcstol(src, &end, 10);
if (end == src)
{
sb_printf(sb_err, _(L"%ls: Invalid index starting at '%ls'\n"), L"set", src);
return -1;
}
int *ind = (int *) calloc(1, sizeof(int));
*ind = (int) l_ind;
al_push(indexes, ind);
src = end;
count++;
while (iswspace(*src)) src++;
}
return count;
}
/**
Update a list \c list by writing copies (using wcsdup) of the
values specified by \c values to the indexes specified by \c
indexes. The previous entries at the specidied position will be
free'd.
\return The number of elements in the list after the modifications have been made
*/
static int update_values( array_list_t *list,
array_list_t *indexes,
array_list_t *values )
{
int i;
/* Replace values where needed */
for( i = 0; i < al_get_count(indexes); i++ )
{
int ind = *(int *) al_get(indexes, i) - 1;
void *new = (void *) al_get(values, i);
free((void *) al_get(list, ind));
al_set(list, ind, new != 0 ? wcsdup(new) : wcsdup(L""));
}
return al_get_count(list);
}
/**
Return 1 if an array list of int* pointers contains the specified
value, 0 otherwise
*/
static int al_contains_int( array_list_t *list,
int val)
{
int i;
for (i = 0; i < al_get_count(list); i++)
{
int *current = (int *) al_get(list, i);
if (current != 0 && *current == val)
{
return 1;
}
}
return 0;
}
/**
Erase from a list values at specified indexes
*/
static int erase_values(array_list_t *list, array_list_t *indexes)
{
int i;
array_list_t result;
al_init(&result);
for (i = 0; i < al_get_count(list); i++)
{
if (!al_contains_int(indexes, i + 1))
{
al_push(&result, al_get(list, i));
}
else
{
free((void *) al_get(list, i));
}
}
al_truncate(list,0);
al_push_all( list, &result );
al_destroy(&result);
return al_get_count(list);
}
/**
Fill a string buffer with values from a list, using ARRAY_SEP_STR to separate them
*/
static int fill_buffer_from_list(string_buffer_t *sb, array_list_t *list)
{
int i;
for (i = 0; i < al_get_count(list); i++)
{
wchar_t *v = (wchar_t *) al_get(list, i);
if (v != 0)
{
sb_append(sb, v);
}
if (i < al_get_count(list) - 1)
sb_append(sb, ARRAY_SEP_STR);
}
return al_get_count(list);
}
/**
Print the names of all environment variables in the scope, with or without values,
with or without escaping
*/
static void print_variables(int include_values, int esc, int scope)
{
array_list_t names;
int i;
al_init( &names );
env_get_names( &names, scope );
sort_list( &names );
for( i = 0; i < al_get_count(&names); i++ )
{
wchar_t *key = (wchar_t *)al_get( &names, i );
wchar_t *e_key = esc ? escape(key, 1) : wcsdup(key);
sb_append(sb_out, e_key);
if( include_values )
{
wchar_t *value = env_get(key);
wchar_t *e_value;
e_value = esc ? expand_escape_variable(value) : wcsdup(value);
sb_append2(sb_out, L" ", e_value, (void *)0);
free(e_value);
}
sb_append(sb_out, L"\n");
free(e_key);
}
al_destroy(&names);
}
static int is_path_variable( const wchar_t *env )
{
return contains_str( env,
L"PATH",
L"CDPATH",
(void *)0 );
}
/**
The set builtin. Creates, updates and erases environment variables and environemnt variable arrays.
*/
int builtin_set( wchar_t **argv )
{
/**
Variables used for parsing the argument list
*/
const static struct woption
long_options[] =
{
{
L"export", no_argument, 0, 'x'
},
{
L"global", no_argument, 0, 'g'
},
{
L"local", no_argument, 0, 'l'
},
{
L"erase", no_argument, 0, 'e'
},
{
L"names", no_argument, 0, 'n'
} ,
{
L"unexport", no_argument, 0, 'u'
} ,
{
L"universal", no_argument, 0, 'U'
} ,
{
L"query", no_argument, 0, 'q'
} ,
{
0, 0, 0, 0
}
}
;
const wchar_t *short_options = L"+xglenuUq";
int argc = builtin_count_args(argv);
/*
Flags to set the work mode
*/
int local = 0, global = 0, export = 0;
int erase = 0, list = 0, unexport=0;
int universal = 0, query=0;
/*
Variables used for performing the actual work
*/
wchar_t *dest = 0;
array_list_t values;
string_buffer_t name_sb;
int retcode=0;
wchar_t *name;
array_list_t indexes;
int retval;
/* Parse options to obtain the requested operation and the modifiers */
woptind = 0;
while (1)
{
int c = wgetopt_long(argc, argv, short_options, long_options, 0);
if (c == -1)
{
break;
}
switch(c)
{
case 0:
break;
case 'e':
erase = 1;
break;
case 'n':
list = 1;
break;
case 'x':
export = 1;
break;
case 'l':
local = 1;
break;
case 'g':
global = 1;
break;
case 'u':
unexport = 1;
break;
case 'U':
universal = 1;
break;
case 'q':
query = 1;
break;
case '?':
return 1;
default:
break;
}
}
/*
Ok, all arguments have been parsed, let's validate them
*/
/*
If we are checking the existance of a variable (-q) we can not
also specify scope
*/
if( query && (erase || list || global || local || universal || export || unexport ) )
{
sb_printf(sb_err,
BUILTIN_ERR_COMBO2,
argv[0],
parser_current_line() );
builtin_print_help( argv[0], sb_err );
return 1;
}
/* We can't both list and erase varaibles */
if( erase && list )
{
sb_printf(sb_err,
BUILTIN_ERR_COMBO2,
argv[0],
parser_current_line() );
builtin_print_help( argv[0], sb_err );
return 1;
}
/*
Variables can only have one scope
*/
if( local + global + universal > 1 )
{
sb_printf( sb_err,
BUILTIN_ERR_GLOCAL,
argv[0],
parser_current_line() );
builtin_print_help( argv[0], sb_err );
return 1;
}
/*
Variables can only have one export status
*/
if( export && unexport )
{
sb_printf( sb_err,
BUILTIN_ERR_EXPUNEXP,
argv[0],
parser_current_line() );
builtin_print_help( argv[0], sb_err );
return 1;
}
if( query )
{
/*
Query mode. Return the number variables that do not exist
out of the specified variables.
*/
int i;
for( i=woptind; i<argc; i++ )
{
if( !env_exist( argv[i] ) )
{
retcode++;
}
}
return retcode;
}
/* Parse destination */
if( woptind < argc )
{
dest = wcsdup(argv[woptind++]);
if( !wcslen( dest ) )
{
free( dest );
sb_printf( sb_err, BUILTIN_ERR_VARNAME_ZERO, argv[0] );
return 1;
}
}
/* Parse values */
// wchar_t **values = woptind < argc ? (wchar_t **) calloc(argc - woptind, sizeof(wchar_t *)) : 0;
al_init(&values);
while( woptind < argc )
{
al_push(&values, argv[woptind++]);
}
if( is_path_variable( dest ) )
{
int i;
int error = 0;
for( i=0; i<al_get_count( &values ); i++ )
{
int show_perror = 0;
int show_hint = 0;
struct stat buff;
wchar_t *dir = (wchar_t *)al_get( &values, i );
if( wstat( dir, &buff ) )
{
error = 1;
show_perror = 1;
}
if( !( S_IFDIR & buff.st_mode ) )
{
error = 1;
}
if( error )
{
wchar_t *colon;
sb_printf( sb_err,
_(BUILTIN_SET_PATH_ERROR),
argv[0],
dir,
dest );
colon = wcschr( dir, L':' );
if( colon && *(colon+1) )
{
show_hint = 1;
}
}
if( show_perror )
{
builtin_wperror( argv[0] );
}
if( show_hint )
{
sb_printf( sb_err,
_(BUILTIN_SET_PATH_HINT),
argv[0],
dest,
dest,
wcschr( dir, L':' )+1);
}
if( error )
{
break;
}
}
if( error )
{
al_destroy(&values);
return 1;
}
}
/* Extract variable name and indexes */
sb_init(&name_sb);
retval = parse_fill_name(&name_sb, dest);
if( retval < 0 )
retcode=1;
if( !retcode )
{
name = (wchar_t *) name_sb.buff;
al_init(&indexes);
retval = parse_fill_indexes(&indexes, dest);
if (retval < 0)
retcode = 1;
}
if( !retcode )
{
int i;
int finished=0;
/* Do the actual work */
int scope = (local ? ENV_LOCAL : 0) | (global ? ENV_GLOBAL : 0) | (export ? ENV_EXPORT : 0) | (unexport ? ENV_UNEXPORT : 0) | (universal ? ENV_UNIVERSAL:0) | ENV_USER;
if( list )
{
/* Maybe we should issue an error if there are any other arguments? */
print_variables(0, 0, scope);
finished=1;
}
if( (!finished ) &&
(name == 0 || wcslen(name) == 0))
{
/* No arguments -- display name & value for all variables in scope */
if( erase )
{
sb_printf( sb_err,
_(L"%ls: Erase needs a variable name\n%ls\n"),
argv[0],
parser_current_line() );
builtin_print_help( argv[0], sb_err );
retcode = 1;
}
else
{
print_variables( 1, 1, scope );
}
finished=1;
}
if( !finished )
{
if( al_get_count( &values ) == 0 &&
al_get_count( &indexes ) == 0 &&
!erase &&
!list )
{
my_env_set( name, 0, scope );
finished = 1;
}
}
if( !finished )
{
/* There are some arguments, we have at least a variable name */
if( erase && al_get_count(&values) != 0 )
{
sb_printf( sb_err,
_(L"%ls: Values cannot be specfied with erase\n%ls\n"),
argv[0],
parser_current_line() );
builtin_print_help( argv[0], sb_err );
retcode = 1;
}
else
{
/* All ok, we can alter the specified variable */
array_list_t val_l;
al_init(&val_l);
void *old=0;
if (al_get_count(&indexes) == 0)
{
/* We will act upon the entire variable */
al_push( &val_l, wcsdup(L"") );
old = val_l.arr;
/* Build indexes for all variable or all new values */
int end_index = erase ? al_get_count(&val_l) : al_get_count(&values);
for (i = 0; i < end_index; i++)
{
int *ind = (int *) calloc(1, sizeof(int));
*ind = i + 1;
al_push(&indexes, ind);
}
}
else
{
/* We will act upon some specific indexes */
expand_variable_array( env_get(name), &val_l );
}
string_buffer_t result_sb;
sb_init(&result_sb);
if (erase)
{
int rem = erase_values(&val_l, &indexes);
if (rem == 0)
{
env_remove(name, ENV_USER);
}
else
{
fill_buffer_from_list(&result_sb, &val_l);
my_env_set(name, (wchar_t *) result_sb.buff, scope);
}
}
else
{
update_values( &val_l,
&indexes,
&values );
fill_buffer_from_list( &result_sb,
&val_l );
my_env_set(name,
(wchar_t *) result_sb.buff,
scope);
}
al_foreach( &val_l, (void (*)(const void *))&free );
al_destroy(&val_l);
sb_destroy(&result_sb);
}
}
al_foreach( &indexes, (void (*)(const void *))&free );
al_destroy(&indexes);
}
/* Common cleanup */
free(dest);
sb_destroy(&name_sb);
al_destroy( &values );
return retcode;
}