2005-09-20 21:26:39 +08:00
/*
2008-01-13 03:18:48 +08:00
Copyright ( C ) 2005 - 2008 Axel Liljencrantz
2005-09-20 21:26:39 +08:00
2006-11-01 22:47:47 +08:00
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation .
2005-09-20 21:26:39 +08:00
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation , Inc . , 59 Temple Place - Suite 330 , Boston , MA 02111 - 1307 , USA .
*/
2008-01-13 03:21:35 +08:00
/** \file fish.c
2005-09-20 21:26:39 +08:00
The main loop of < tt > fish < / tt > .
*/
# include "config.h"
2006-02-28 21:17:16 +08:00
2005-09-20 21:26:39 +08:00
# include <stdlib.h>
# include <stdio.h>
# include <wchar.h>
# include <string.h>
# include <unistd.h>
# include <errno.h>
# include <unistd.h>
# include <termios.h>
# include <fcntl.h>
2012-07-09 06:20:39 +08:00
# include <sys/param.h>
2005-09-20 21:26:39 +08:00
# ifdef HAVE_GETOPT_H
# include <getopt.h>
# endif
# include <locale.h>
# include <signal.h>
2006-02-28 21:17:16 +08:00
# include "fallback.h"
2005-09-20 21:26:39 +08:00
# include "util.h"
2006-02-28 21:17:16 +08:00
2005-09-20 21:26:39 +08:00
# include "common.h"
# include "reader.h"
# include "builtin.h"
# include "function.h"
# include "complete.h"
# include "wutil.h"
# include "env.h"
# include "sanity.h"
# include "proc.h"
# include "parser.h"
# include "expand.h"
# include "intern.h"
2005-10-06 06:37:08 +08:00
# include "exec.h"
# include "event.h"
2005-10-15 08:51:26 +08:00
# include "output.h"
2006-04-20 07:42:11 +08:00
# include "history.h"
2006-10-19 19:50:23 +08:00
# include "path.h"
2005-09-20 21:26:39 +08:00
2012-07-09 06:20:39 +08:00
/* PATH_MAX may not exist */
# ifndef PATH_MAX
# define PATH_MAX 1024
# endif
2006-05-18 21:00:39 +08:00
/**
The string describing the single - character options accepted by the main fish binary
*/
2010-10-03 11:46:26 +08:00
# define GETOPT_STRING "+hilnvc:p:d:"
2006-03-10 21:38:09 +08:00
2012-07-09 06:20:39 +08:00
static bool has_suffix ( const std : : string & path , const char * suffix , bool ignore_case )
{
size_t pathlen = path . size ( ) , suffixlen = strlen ( suffix ) ;
2012-11-19 08:30:30 +08:00
return pathlen > = suffixlen & & ! ( ignore_case ? strcasecmp : strcmp ) ( path . c_str ( ) + pathlen - suffixlen , suffix ) ;
2012-07-09 06:20:39 +08:00
}
/* Modifies the given path by calling realpath. Returns true if realpath succeeded, false otherwise */
static bool get_realpath ( std : : string & path )
{
char buff [ PATH_MAX ] , * ptr ;
if ( ( ptr = realpath ( path . c_str ( ) , buff ) ) )
{
path = ptr ;
}
return ptr ! = NULL ;
}
/* OS X function for getting the executable path */
extern " C " {
int _NSGetExecutablePath ( char * buf , uint32_t * bufsize ) ;
}
/* Return the path to the current executable. This needs to be realpath'd. */
static std : : string get_executable_path ( const char * argv0 )
{
char buff [ PATH_MAX ] ;
# if __APPLE__
{
/* Returns 0 on success, -1 if the buffer is too small */
uint32_t buffSize = sizeof buff ;
if ( 0 = = _NSGetExecutablePath ( buff , & buffSize ) )
return std : : string ( buff ) ;
2012-11-19 08:30:30 +08:00
2012-07-09 06:20:39 +08:00
/* Loop until we're big enough */
char * mbuff = ( char * ) malloc ( buffSize ) ;
while ( 0 > _NSGetExecutablePath ( mbuff , & buffSize ) )
mbuff = ( char * ) realloc ( mbuff , buffSize ) ;
2012-11-19 08:30:30 +08:00
2012-07-09 06:20:39 +08:00
/* Return the string */
std : : string result = mbuff ;
free ( mbuff ) ;
return result ;
}
# endif
{
/* On other Unixes, try /proc directory. This might be worth breaking out into macros. */
if ( 0 < readlink ( " /proc/self/exe " , buff , sizeof buff ) | | // Linux
2012-11-19 08:30:30 +08:00
0 < readlink ( " /proc/curproc/file " , buff , sizeof buff ) | | // BSD
0 < readlink ( " /proc/self/path/a.out " , buff , sizeof buff ) ) // Solaris
2012-07-09 06:20:39 +08:00
{
return std : : string ( buff ) ;
}
}
2012-11-19 08:30:30 +08:00
2012-07-09 06:20:39 +08:00
/* Just return argv0, which probably won't work (i.e. it's not an absolute path or a path relative to the working directory, but instead something the caller found via $PATH). We'll eventually fall back to the compile time paths. */
return std : : string ( argv0 ? argv0 : " " ) ;
}
static struct config_paths_t determine_config_directory_paths ( const char * argv0 )
{
struct config_paths_t paths ;
bool done = false ;
std : : string exec_path = get_executable_path ( argv0 ) ;
if ( get_realpath ( exec_path ) )
2012-11-19 08:30:30 +08:00
{
2012-07-09 06:20:39 +08:00
# if __APPLE__
2012-11-19 08:30:30 +08:00
2012-07-09 06:20:39 +08:00
/* On OS X, maybe we're an app bundle, and should use the bundle's files. Since we don't link CF, use this lame approach to test it: see if the resolved path ends with /Contents/MacOS/fish, case insensitive since HFS+ usually is.
*/
if ( ! done )
{
const char * suffix = " /Contents/MacOS/fish " ;
const size_t suffixlen = strlen ( suffix ) ;
if ( has_suffix ( exec_path , suffix , true ) )
{
/* Looks like we're a bundle. Cut the string at the / prefixing /Contents... and then the rest */
wcstring wide_resolved_path = str2wcstring ( exec_path ) ;
wide_resolved_path . resize ( exec_path . size ( ) - suffixlen ) ;
wide_resolved_path . append ( L " /Contents/Resources/ " ) ;
2012-11-19 08:30:30 +08:00
2012-07-09 06:20:39 +08:00
/* Append share, etc, doc */
paths . data = wide_resolved_path + L " share/fish " ;
paths . sysconf = wide_resolved_path + L " etc/fish " ;
2012-07-09 06:42:47 +08:00
paths . doc = wide_resolved_path + L " doc/fish " ;
2012-11-19 08:30:30 +08:00
2012-07-09 06:20:39 +08:00
/* But the bin_dir is the resolved_path, minus fish (aka the MacOS directory) */
paths . bin = str2wcstring ( exec_path ) ;
paths . bin . resize ( paths . bin . size ( ) - strlen ( " /fish " ) ) ;
2012-11-19 08:30:30 +08:00
2012-07-09 06:20:39 +08:00
done = true ;
}
}
# endif
2012-11-19 08:30:30 +08:00
2012-07-09 06:20:39 +08:00
if ( ! done )
{
/* The next check is that we are in a reloctable directory tree like this:
bin / fish
etc / fish
share / fish
2012-11-19 08:30:30 +08:00
2012-07-09 06:20:39 +08:00
Check it !
*/
const char * suffix = " /bin/fish " ;
if ( has_suffix ( exec_path , suffix , false ) )
{
wcstring base_path = str2wcstring ( exec_path ) ;
base_path . resize ( base_path . size ( ) - strlen ( suffix ) ) ;
2012-11-19 08:30:30 +08:00
2012-07-09 06:20:39 +08:00
paths . data = base_path + L " /share/fish " ;
paths . sysconf = base_path + L " /etc/fish " ;
2012-07-09 06:42:47 +08:00
paths . doc = base_path + L " /share/doc/fish " ;
2012-07-09 06:20:39 +08:00
paths . bin = base_path + L " /bin " ;
2012-11-19 08:30:30 +08:00
2012-07-09 06:20:39 +08:00
struct stat buf ;
if ( 0 = = wstat ( paths . data , & buf ) & & 0 = = wstat ( paths . sysconf , & buf ) )
{
done = true ;
}
}
}
}
2012-11-19 08:30:30 +08:00
2012-07-09 06:20:39 +08:00
if ( ! done )
{
/* Fall back to what got compiled in. */
paths . data = L " " DATADIR " /fish " ;
paths . sysconf = L " " SYSCONFDIR " /fish " ;
paths . doc = L " " DATADIR " /doc/fish " ;
paths . bin = L " " PREFIX " /bin " ;
2012-11-19 08:30:30 +08:00
2012-07-09 06:20:39 +08:00
done = true ;
}
2012-11-19 08:30:30 +08:00
2012-07-09 06:20:39 +08:00
return paths ;
}
2005-09-20 21:26:39 +08:00
/**
2012-07-09 06:20:39 +08:00
Parse init files . exec_path is the path of fish executable as determined by argv [ 0 ] .
2005-09-20 21:26:39 +08:00
*/
2012-07-09 06:20:39 +08:00
static int read_init ( const struct config_paths_t & paths )
2005-09-20 21:26:39 +08:00
{
2012-01-23 13:57:30 +08:00
parser_t & parser = parser_t : : principal_parser ( ) ;
2012-08-15 15:57:56 +08:00
const io_chain_t empty_ios ;
2012-11-19 08:30:30 +08:00
parser . eval ( L " builtin . " + paths . data + L " /config.fish 2>/dev/null " , empty_ios , TOP ) ;
parser . eval ( L " builtin . " + paths . sysconf + L " /config.fish 2>/dev/null " , empty_ios , TOP ) ;
/*
We need to get the configuration directory before we can source the user configuration file
*/
wcstring config_dir ;
/*
If path_get_config returns false then we have no configuration directory
and no custom config to load .
*/
2012-02-08 14:44:10 +08:00
if ( path_get_config ( config_dir ) )
2012-11-19 08:30:30 +08:00
{
wcstring config_dir_escaped = escape_string ( config_dir , 1 ) ;
2012-02-08 14:44:10 +08:00
wcstring eval_buff = format_string ( L " builtin . %ls/config.fish 2>/dev/null " , config_dir_escaped . c_str ( ) ) ;
2012-11-19 08:30:30 +08:00
parser . eval ( eval_buff , empty_ios , TOP ) ;
}
return 1 ;
2005-09-20 21:26:39 +08:00
}
2008-01-14 00:47:47 +08:00
/**
2008-01-09 09:23:38 +08:00
Parse the argument list , return the index of the first non - switch
arguments .
*/
2012-11-19 08:30:30 +08:00
static int fish_parse_opt ( int argc , char * * argv , const char * * cmd_ptr )
2005-09-20 21:26:39 +08:00
{
2012-11-19 08:30:30 +08:00
int my_optind ;
int force_interactive = 0 ;
while ( 1 )
{
static struct option
long_options [ ] =
{
{
" command " , required_argument , 0 , ' c '
}
,
{
" debug-level " , required_argument , 0 , ' d '
}
,
{
" interactive " , no_argument , 0 , ' i '
}
,
{
" login " , no_argument , 0 , ' l '
}
,
{
" no-execute " , no_argument , 0 , ' n '
}
,
{
" profile " , required_argument , 0 , ' p '
}
,
{
" help " , no_argument , 0 , ' h '
}
,
{
" version " , no_argument , 0 , ' v '
}
,
{
0 , 0 , 0 , 0
}
}
;
int opt_index = 0 ;
int opt = getopt_long ( argc ,
argv ,
GETOPT_STRING ,
long_options ,
& opt_index ) ;
if ( opt = = - 1 )
break ;
switch ( opt )
{
2012-11-19 16:31:03 +08:00
case 0 :
{
break ;
}
2012-11-19 08:30:30 +08:00
2012-11-19 16:31:03 +08:00
case ' c ' :
{
* cmd_ptr = optarg ;
is_interactive_session = 0 ;
break ;
}
2012-11-19 08:30:30 +08:00
2012-11-19 16:31:03 +08:00
case ' d ' :
{
char * end ;
long tmp ;
2012-11-19 08:30:30 +08:00
2012-11-19 16:31:03 +08:00
errno = 0 ;
tmp = strtol ( optarg , & end , 10 ) ;
2012-11-19 08:30:30 +08:00
2012-11-19 16:31:03 +08:00
if ( tmp > = 0 & & tmp < = 10 & & ! * end & & ! errno )
{
debug_level = ( int ) tmp ;
}
else
{
debug ( 0 , _ ( L " Invalid value '%s' for debug level switch " ) , optarg ) ;
exit_without_destructors ( 1 ) ;
}
break ;
2012-11-19 08:30:30 +08:00
}
2012-11-19 16:31:03 +08:00
case ' h ' :
2012-11-19 08:30:30 +08:00
{
2012-11-19 16:31:03 +08:00
* cmd_ptr = " __fish_print_help fish " ;
break ;
2012-11-19 08:30:30 +08:00
}
2012-11-19 16:31:03 +08:00
case ' i ' :
{
force_interactive = 1 ;
break ;
}
2012-11-19 08:30:30 +08:00
2012-11-19 16:31:03 +08:00
case ' l ' :
{
is_login = 1 ;
break ;
}
2012-11-19 08:30:30 +08:00
2012-11-19 16:31:03 +08:00
case ' n ' :
{
no_exec = 1 ;
break ;
}
2012-11-19 08:30:30 +08:00
2012-11-19 16:31:03 +08:00
case ' p ' :
{
profile = optarg ;
break ;
}
2012-11-19 08:30:30 +08:00
2012-11-19 16:31:03 +08:00
case ' v ' :
{
fwprintf ( stderr ,
_ ( L " %s, version %s \n " ) ,
PACKAGE_NAME ,
PACKAGE_VERSION ) ;
exit_without_destructors ( 0 ) ;
}
2012-11-19 08:30:30 +08:00
2012-11-19 16:31:03 +08:00
case ' ? ' :
{
exit_without_destructors ( 1 ) ;
}
2012-11-19 08:30:30 +08:00
}
}
my_optind = optind ;
is_login | = ( strcmp ( argv [ 0 ] , " -fish " ) = = 0 ) ;
/*
We are an interactive session if we have not been given an
explicit command to execute , _and_ stdin is a tty .
*/
is_interactive_session & = ( * cmd_ptr = = 0 ) ;
is_interactive_session & = ( my_optind = = argc ) ;
is_interactive_session & = isatty ( STDIN_FILENO ) ;
/*
We are also an interactive session if we have are forced -
*/
is_interactive_session | = force_interactive ;
return my_optind ;
2006-10-26 04:54:43 +08:00
}
/**
Calls a bunch of init functions , parses the init files and then
parses commands from stdin or files , depending on arguments
*/
2012-11-19 08:30:30 +08:00
static wcstring full_escape ( const wchar_t * in )
2012-10-09 05:47:25 +08:00
{
2012-11-19 08:30:30 +08:00
wcstring out ;
for ( ; * in ; in + + )
{
if ( * in < 32 )
{
append_format ( out , L " \\ x%.2x " , * in ) ;
}
else if ( * in < 128 )
{
out . push_back ( * in ) ;
}
else if ( * in < 65536 )
{
append_format ( out , L " \\ u%.4x " , * in ) ;
}
else
{
append_format ( out , L " \\ U%.8x " , * in ) ;
}
}
return out ;
2012-10-09 05:47:25 +08:00
}
2012-03-07 07:12:37 +08:00
extern int g_fork_count ;
2012-11-19 08:30:30 +08:00
int main ( int argc , char * * argv )
{
int res = 1 ;
const char * cmd = 0 ;
int my_optind = 0 ;
2006-10-26 04:54:43 +08:00
2012-11-19 08:30:30 +08:00
set_main_thread ( ) ;
2012-02-28 10:43:24 +08:00
setup_fork_guards ( ) ;
2012-11-18 18:16:14 +08:00
save_term_foreground_process_group ( ) ;
2012-11-19 08:30:30 +08:00
wsetlocale ( LC_ALL , L " " ) ;
is_interactive_session = 1 ;
program_name = L " fish " ;
2006-10-26 04:54:43 +08:00
2012-10-09 05:47:25 +08:00
//struct stat tmp;
//stat("----------FISH_HIT_MAIN----------", &tmp);
2006-11-11 18:48:40 +08:00
2012-11-19 08:30:30 +08:00
my_optind = fish_parse_opt ( argc , argv , & cmd ) ;
/*
No - exec is prohibited when in interactive mode
*/
if ( is_interactive_session & & no_exec )
{
debug ( 1 , _ ( L " Can not use the no-execute mode when running an interactive session " ) ) ;
no_exec = 0 ;
}
const struct config_paths_t paths = determine_config_directory_paths ( argv [ 0 ] ) ;
proc_init ( ) ;
event_init ( ) ;
wutil_init ( ) ;
builtin_init ( ) ;
function_init ( ) ;
env_init ( & paths ) ;
reader_init ( ) ;
history_init ( ) ;
2006-03-10 21:38:09 +08:00
2012-01-23 13:40:08 +08:00
parser_t & parser = parser_t : : principal_parser ( ) ;
2012-03-07 07:51:48 +08:00
if ( g_log_forks )
printf ( " %d: g_fork_count: %d \n " , __LINE__ , g_fork_count ) ;
2012-03-07 07:12:37 +08:00
2012-08-15 15:57:56 +08:00
const io_chain_t empty_ios ;
2012-11-19 08:30:30 +08:00
if ( read_init ( paths ) )
{
if ( cmd ! = 0 )
{
wchar_t * cmd_wcs = str2wcs ( cmd ) ;
res = parser . eval ( cmd_wcs , empty_ios , TOP ) ;
free ( cmd_wcs ) ;
reader_exit ( 0 , 0 ) ;
}
else
{
if ( my_optind = = argc )
{
res = reader_read ( STDIN_FILENO , empty_ios ) ;
}
else
{
char * * ptr ;
char * file = * ( argv + ( my_optind + + ) ) ;
int i ;
int fd ;
wchar_t * rel_filename , * abs_filename ;
if ( ( fd = open ( file , O_RDONLY ) ) = = - 1 )
{
wperror ( L " open " ) ;
return 1 ;
}
2012-03-02 16:27:40 +08:00
// OK to not do this atomically since we cannot have gone multithreaded yet
set_cloexec ( fd ) ;
2012-11-19 08:30:30 +08:00
if ( * ( argv + my_optind ) )
{
2012-02-23 03:07:34 +08:00
wcstring sb ;
2012-11-19 08:30:30 +08:00
for ( i = 1 , ptr = argv + my_optind ; * ptr ; i + + , ptr + + )
{
if ( i ! = 1 )
sb . append ( ARRAY_SEP_STR ) ;
sb . append ( str2wcstring ( * ptr ) ) ;
}
env_set ( L " argv " , sb . c_str ( ) , 0 ) ;
}
rel_filename = str2wcs ( file ) ;
abs_filename = wrealpath ( rel_filename , 0 ) ;
if ( ! abs_filename )
{
abs_filename = wcsdup ( rel_filename ) ;
}
reader_push_current_filename ( intern ( abs_filename ) ) ;
free ( rel_filename ) ;
free ( abs_filename ) ;
res = reader_read ( fd , empty_ios ) ;
if ( res )
{
debug ( 1 ,
_ ( L " Error while reading file %ls \n " ) ,
reader_current_filename ( ) ? reader_current_filename ( ) : _ ( L " Standard input " ) ) ;
}
reader_pop_current_filename ( ) ;
}
}
}
proc_fire_event ( L " PROCESS_EXIT " , EVENT_EXIT , getpid ( ) , res ) ;
2012-11-18 18:16:14 +08:00
restore_term_foreground_process_group ( ) ;
2012-11-19 08:30:30 +08:00
history_destroy ( ) ;
proc_destroy ( ) ;
builtin_destroy ( ) ;
reader_destroy ( ) ;
parser . destroy ( ) ;
wutil_destroy ( ) ;
event_destroy ( ) ;
env_destroy ( ) ;
2012-03-07 07:51:48 +08:00
if ( g_log_forks )
printf ( " %d: g_fork_count: %d \n " , __LINE__ , g_fork_count ) ;
2012-11-19 08:30:30 +08:00
return res ? STATUS_UNKNOWN_COMMAND : proc_get_last_status ( ) ;
2005-09-20 21:26:39 +08:00
}