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
2013-12-14 04:51:52 +08:00
Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 , USA
2005-09-20 21:26:39 +08:00
*/
2014-02-28 18:15:24 +08:00
/** \file fish.c
The main loop of < tt > fish < / tt > .
*/
2005-09-20 21:26:39 +08:00
# 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"
2013-09-30 20:45:12 +08:00
# include "input.h"
2014-05-01 15:46:27 +08:00
# include "fish_version.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
2014-02-10 06:04:43 +08:00
/* If we are doing profiling, the filename to output to */
static const char * s_profiling_output_filename = NULL ;
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 ) ) ;
2014-04-01 01:01:39 +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 " ;
2014-04-01 01:01:39 +08:00
2014-01-18 06:18:45 +08:00
/* Check only that the data and sysconf directories exist. Handle the doc directories separately */
2012-07-09 06:20:39 +08:00
struct stat buf ;
2014-01-18 06:18:45 +08:00
if ( 0 = = wstat ( paths . data , & buf ) & & 0 = = wstat ( paths . sysconf , & buf ) )
2012-07-09 06:20:39 +08:00
{
2014-01-18 06:18:45 +08:00
/* The docs dir may not exist; in that case fall back to the compiled in path */
if ( 0 ! = wstat ( paths . doc , & buf ) )
{
paths . doc = L " " DOCDIR ;
}
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
if ( ! done )
{
/* Fall back to what got compiled in. */
paths . data = L " " DATADIR " /fish " ;
paths . sysconf = L " " SYSCONFDIR " /fish " ;
2013-10-29 00:01:21 +08:00
paths . doc = L " " DOCDIR ;
2013-04-16 13:22:19 +08:00
paths . bin = L " " BINDIR ;
2014-02-28 18:15:24 +08:00
done = true ;
2012-07-09 06:20:39 +08:00
}
2012-11-19 08:30:30 +08:00
2012-07-09 06:20:39 +08:00
return paths ;
}
2013-09-30 04:39:41 +08:00
/* Source the file config.fish in the given directory */
static void source_config_in_directory ( const wcstring & dir )
{
/* We want to execute a command like 'builtin source dir/config.fish 2>/dev/null' */
const wcstring escaped_dir = escape_string ( dir , ESCAPE_ALL ) ;
const wcstring cmd = L " builtin source " + escaped_dir + L " /config.fish 2>/dev/null " ;
parser_t & parser = parser_t : : principal_parser ( ) ;
2014-02-21 02:57:13 +08:00
parser . set_is_within_fish_initialization ( true ) ;
2013-09-30 04:39:41 +08:00
parser . eval ( cmd , io_chain_t ( ) , TOP ) ;
2014-02-21 02:57:13 +08:00
parser . set_is_within_fish_initialization ( false ) ;
2013-09-30 04:39:41 +08:00
}
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
{
2013-09-30 04:39:41 +08:00
source_config_in_directory ( paths . data ) ;
source_config_in_directory ( paths . sysconf ) ;
2012-11-19 08:30:30 +08:00
2013-09-30 04:39:41 +08:00
/* We need to get the configuration directory before we can source the user configuration file. If path_get_config returns false then we have no configuration directory and no custom config to load. */
2012-11-19 08:30:30 +08:00
wcstring config_dir ;
2012-02-08 14:44:10 +08:00
if ( path_get_config ( config_dir ) )
2012-11-19 08:30:30 +08:00
{
2013-09-30 04:39:41 +08:00
source_config_in_directory ( config_dir ) ;
2012-11-19 08:30:30 +08:00
}
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 .
*/
2013-01-05 05:09:01 +08:00
static int fish_parse_opt ( int argc , char * * argv , std : : vector < std : : string > * out_cmds )
2005-09-20 21:26:39 +08:00
{
2012-11-19 08:30:30 +08:00
int my_optind ;
int force_interactive = 0 ;
2013-01-05 05:09:01 +08:00
bool has_cmd = false ;
2012-11-19 08:30:30 +08:00
while ( 1 )
{
static struct option
long_options [ ] =
{
2013-01-05 05:09:01 +08:00
{ " 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 }
2012-11-19 08:30:30 +08:00
}
;
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 ' :
{
2013-01-05 05:09:01 +08:00
out_cmds - > push_back ( optarg ? optarg : " " ) ;
has_cmd = true ;
2012-11-19 16:31:03 +08:00
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
{
2013-01-05 05:09:01 +08:00
out_cmds - > push_back ( " __fish_print_help fish " ) ;
has_cmd = true ;
2012-11-19 16:31:03 +08:00
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 ' :
{
2014-02-10 06:04:43 +08:00
s_profiling_output_filename = optarg ;
g_profiling_active = true ;
2012-11-19 16:31:03 +08:00
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 ,
2014-05-01 15:46:27 +08:00
get_fish_version ( ) ) ;
2012-11-19 16:31:03 +08:00
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 ) ;
2013-10-27 06:24:49 +08:00
/* We are an interactive session if we are either forced, or have not been given an explicit command to execute and stdin is a tty. */
2013-10-27 06:27:39 +08:00
if ( force_interactive )
{
2013-10-27 06:24:49 +08:00
is_interactive_session = true ;
2013-10-27 06:27:39 +08:00
}
else if ( is_interactive_session )
{
2013-10-27 06:24:49 +08:00
is_interactive_session = ! has_cmd & & ( my_optind = = argc ) & & isatty ( STDIN_FILENO ) ;
}
2012-11-19 08:30:30 +08:00
return my_optind ;
2006-10-26 04:54:43 +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 ;
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-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
2013-01-05 05:09:01 +08:00
std : : vector < std : : string > cmds ;
my_optind = fish_parse_opt ( argc , argv , & cmds ) ;
2012-11-19 08:30:30 +08:00
/*
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 ;
}
2013-10-27 06:27:39 +08:00
2013-10-27 06:22:20 +08:00
/* Only save (and therefore restore) the fg process group if we are interactive. See #197, #1002 */
if ( is_interactive_session )
{
save_term_foreground_process_group ( ) ;
}
2012-11-19 08:30:30 +08:00
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 ( ) ;
2013-09-30 20:45:12 +08:00
/* For setcolor to support term256 in config.fish (#1022) */
update_fish_term256 ( ) ;
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 ) )
{
2013-04-10 14:48:03 +08:00
/* Stop the exit status of any initialization commands (#635) */
proc_set_last_status ( STATUS_BUILTIN_OK ) ;
2013-05-05 17:33:17 +08:00
2013-01-05 05:09:01 +08:00
/* Run the commands specified as arguments, if any */
if ( ! cmds . empty ( ) )
2012-11-19 08:30:30 +08:00
{
2013-01-05 05:09:01 +08:00
/* Do something nasty to support OpenSUSE assuming we're bash. This may modify cmds. */
if ( is_login )
{
fish_xdm_login_hack_hack_hack_hack ( & cmds , argc - my_optind , argv + my_optind ) ;
}
for ( size_t i = 0 ; i < cmds . size ( ) ; i + + )
{
const wcstring cmd_wcs = str2wcstring ( cmds . at ( i ) ) ;
res = parser . eval ( cmd_wcs , empty_ios , TOP ) ;
}
2012-11-19 08:30:30 +08:00
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 ;
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 ) ;
}
2012-12-20 05:31:06 +08:00
const wcstring rel_filename = str2wcstring ( file ) ;
const wchar_t * abs_filename = wrealpath ( rel_filename , NULL ) ;
2012-11-19 08:30:30 +08:00
if ( ! abs_filename )
{
2012-12-20 05:31:06 +08:00
abs_filename = wcsdup ( rel_filename . c_str ( ) ) ;
2012-11-19 08:30:30 +08:00
}
reader_push_current_filename ( intern ( abs_filename ) ) ;
2012-12-20 05:31:06 +08:00
free ( ( void * ) abs_filename ) ;
2012-11-19 08:30:30 +08:00
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 ) ;
2013-10-27 06:27:39 +08:00
2013-10-27 06:22:20 +08:00
restore_term_mode ( ) ;
2012-11-18 18:16:14 +08:00
restore_term_foreground_process_group ( ) ;
2014-04-01 01:01:39 +08:00
2014-02-10 06:04:43 +08:00
if ( g_profiling_active )
{
parser . emit_profiling ( s_profiling_output_filename ) ;
}
2014-04-01 01:01:39 +08:00
2012-11-19 08:30:30 +08:00
history_destroy ( ) ;
proc_destroy ( ) ;
builtin_destroy ( ) ;
reader_destroy ( ) ;
wutil_destroy ( ) ;
event_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
2013-04-20 01:21:46 +08:00
exit_without_destructors ( res ? STATUS_UNKNOWN_COMMAND : proc_get_last_status ( ) ) ;
return EXIT_FAILURE ; //above line should always exit
2005-09-20 21:26:39 +08:00
}