mirror of
https://github.com/fish-shell/fish-shell
synced 2024-11-11 07:34:32 +00:00
508de57459
darcs-hash:20060421150558-ac50b-90aff08f81b3d280badba4cc14fb6a0897c39f9e.gz
729 lines
13 KiB
C
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;
|
|
}
|
|
|