/fish.cpp
C++ | 543 lines | 384 code | 85 blank | 74 comment | 46 complexity | b546de1571becd7885d4646b32bdd2c0 MD5 | raw file
Possible License(s): LGPL-2.0
- /*
- Copyright (C) 2005-2008 Axel Liljencrantz
- 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.
- 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.
- */
- /** \file fish.c
- The main loop of <tt>fish</tt>.
- */
- #include "config.h"
- #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>
- #include <sys/param.h>
- #ifdef HAVE_GETOPT_H
- #include <getopt.h>
- #endif
- #include <locale.h>
- #include <signal.h>
- #include "fallback.h"
- #include "util.h"
- #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"
- #include "exec.h"
- #include "event.h"
- #include "output.h"
- #include "history.h"
- #include "path.h"
- /* PATH_MAX may not exist */
- #ifndef PATH_MAX
- #define PATH_MAX 1024
- #endif
- /**
- The string describing the single-character options accepted by the main fish binary
- */
- #define GETOPT_STRING "+hilnvc:p:d:"
- static bool has_suffix(const std::string &path, const char *suffix, bool ignore_case)
- {
- size_t pathlen = path.size(), suffixlen = strlen(suffix);
- return pathlen >= suffixlen && ! (ignore_case ? strcasecmp : strcmp)(path.c_str() + pathlen - suffixlen, suffix);
- }
- /* 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);
-
- /* Loop until we're big enough */
- char *mbuff = (char *)malloc(buffSize);
- while (0 > _NSGetExecutablePath(mbuff, &buffSize))
- mbuff = (char *)realloc(mbuff, buffSize);
-
- /* 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
- 0 < readlink("/proc/curproc/file", buff, sizeof buff) || // BSD
- 0 < readlink("/proc/self/path/a.out", buff, sizeof buff)) // Solaris
- {
- return std::string(buff);
- }
- }
-
- /* 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))
- {
- #if __APPLE__
-
- /* 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/");
-
- /* Append share, etc, doc */
- paths.data = wide_resolved_path + L"share/fish";
- paths.sysconf = wide_resolved_path + L"etc/fish";
- paths.doc = wide_resolved_path + L"doc/fish";
-
- /* 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"));
-
- done = true;
- }
- }
- #endif
-
- if (! done)
- {
- /* The next check is that we are in a reloctable directory tree like this:
- bin/fish
- etc/fish
- share/fish
-
- 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));
-
- paths.data = base_path + L"/share/fish";
- paths.sysconf = base_path + L"/etc/fish";
- paths.doc = base_path + L"/share/doc/fish";
- paths.bin = base_path + L"/bin";
-
- struct stat buf;
- if (0 == wstat(paths.data, &buf) && 0 == wstat(paths.sysconf, &buf))
- {
- done = true;
- }
- }
- }
- }
-
- 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";
-
- done = true;
- }
-
- return paths;
- }
- /**
- Parse init files. exec_path is the path of fish executable as determined by argv[0].
- */
- static int read_init(const struct config_paths_t &paths)
- {
- parser_t &parser = parser_t::principal_parser();
- parser.eval( L"builtin . " + paths.data + L"/config.fish 2>/dev/null", 0, TOP );
- parser.eval( L"builtin . " + paths.sysconf + L"/config.fish 2>/dev/null", 0, 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.
- */
- if (path_get_config(config_dir))
- {
- wcstring config_dir_escaped = escape_string( config_dir, 1 );
- wcstring eval_buff = format_string(L"builtin . %ls/config.fish 2>/dev/null", config_dir_escaped.c_str());
- parser.eval( eval_buff, 0, TOP );
- }
-
- return 1;
- }
- /**
- Parse the argument list, return the index of the first non-switch
- arguments.
- */
- static int fish_parse_opt( int argc, char **argv, const char **cmd_ptr )
- {
- 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 )
- {
- case 0:
- {
- break;
- }
-
- case 'c':
- {
- *cmd_ptr = optarg;
- is_interactive_session = 0;
- break;
- }
-
- case 'd':
- {
- char *end;
- int tmp;
- errno = 0;
- tmp = strtol(optarg, &end, 10);
-
- if( tmp >= 0 && tmp <=10 && !*end && !errno )
- {
- debug_level=tmp;
- }
- else
- {
- debug( 0, _(L"Invalid value '%s' for debug level switch"), optarg );
- exit_without_destructors(1);
- }
- break;
- }
-
- case 'h':
- {
- *cmd_ptr = "__fish_print_help fish";
- break;
- }
-
- case 'i':
- {
- force_interactive = 1;
- break;
- }
-
- case 'l':
- {
- is_login=1;
- break;
- }
-
- case 'n':
- {
- no_exec=1;
- break;
- }
-
- case 'p':
- {
- profile = optarg;
- break;
- }
-
- case 'v':
- {
- fwprintf( stderr,
- _(L"%s, version %s\n"),
- PACKAGE_NAME,
- PACKAGE_VERSION );
- exit_without_destructors( 0 );
- }
-
- case '?':
- {
- exit_without_destructors( 1 );
- }
-
- }
- }
- 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;
- }
- /**
- Calls a bunch of init functions, parses the init files and then
- parses commands from stdin or files, depending on arguments
- */
- extern int g_fork_count;
- int main( int argc, char **argv )
- {
- struct stat tmp;
- int res=1;
- const char *cmd=0;
- int my_optind=0;
- set_main_thread();
- setup_fork_guards();
-
- wsetlocale( LC_ALL, L"" );
- is_interactive_session=1;
- program_name=L"fish";
- stat("----------FISH_HIT_MAIN----------", &tmp);
- 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();
- //parser_init();
- builtin_init();
- function_init();
- env_init(&paths);
- reader_init();
- history_init();
- parser_t &parser = parser_t::principal_parser();
- if (g_log_forks)
- printf("%d: g_fork_count: %d\n", __LINE__, g_fork_count);
- if( read_init(paths) )
- {
- if( cmd != 0 )
- {
- wchar_t *cmd_wcs = str2wcs( cmd );
- res = parser.eval( cmd_wcs, 0, TOP );
- free(cmd_wcs);
- reader_exit(0, 0);
- }
- else
- {
- if( my_optind == argc )
- {
- res = reader_read( STDIN_FILENO, 0 );
- }
- 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;
- }
-
- // OK to not do this atomically since we cannot have gone multithreaded yet
- set_cloexec(fd);
-
- if( *(argv+my_optind))
- {
- wcstring sb;
- 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, 0 );
- 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 );
-
- history_destroy();
- proc_destroy();
- builtin_destroy();
- reader_destroy();
- parser.destroy();
- wutil_destroy();
- event_destroy();
-
- env_destroy();
-
- if (g_log_forks)
- printf("%d: g_fork_count: %d\n", __LINE__, g_fork_count);
-
- return res?STATUS_UNKNOWN_COMMAND:proc_get_last_status();
- }