2016-05-01 09:37:19 +08:00
|
|
|
// The main loop of the fish program.
|
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
|
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
|
2016-01-22 11:56:39 +08:00
|
|
|
#include <assert.h>
|
2016-05-01 09:37:19 +08:00
|
|
|
#include <errno.h>
|
|
|
|
#include <fcntl.h>
|
2016-03-04 07:36:17 +08:00
|
|
|
#include <getopt.h>
|
2015-07-25 23:14:25 +08:00
|
|
|
#include <limits.h>
|
2016-05-01 09:37:19 +08:00
|
|
|
#include <locale.h>
|
|
|
|
#include <pwd.h>
|
2015-07-25 23:14:25 +08:00
|
|
|
#include <stdint.h>
|
2005-09-20 21:26:39 +08:00
|
|
|
#include <stdio.h>
|
2016-05-01 09:37:19 +08:00
|
|
|
#include <stdlib.h>
|
2005-09-20 21:26:39 +08:00
|
|
|
#include <string.h>
|
2016-05-01 09:37:19 +08:00
|
|
|
#include <sys/socket.h> // IWYU pragma: keep
|
|
|
|
#include <sys/stat.h>
|
2015-04-11 21:33:11 +08:00
|
|
|
#include <sys/un.h>
|
2016-05-01 09:37:19 +08:00
|
|
|
#include <unistd.h>
|
|
|
|
#include <wchar.h>
|
2016-04-21 14:00:54 +08:00
|
|
|
#include <memory>
|
2016-05-01 09:37:19 +08:00
|
|
|
#include <string>
|
|
|
|
#include <vector>
|
2005-09-20 21:26:39 +08:00
|
|
|
|
|
|
|
#include "builtin.h"
|
2016-05-01 09:37:19 +08:00
|
|
|
#include "common.h"
|
2005-09-20 21:26:39 +08:00
|
|
|
#include "env.h"
|
2005-10-06 06:37:08 +08:00
|
|
|
#include "event.h"
|
2016-05-01 09:37:19 +08:00
|
|
|
#include "expand.h"
|
|
|
|
#include "fallback.h" // IWYU pragma: keep
|
|
|
|
#include "fish_version.h"
|
|
|
|
#include "function.h"
|
2006-04-20 07:42:11 +08:00
|
|
|
#include "history.h"
|
2013-09-30 20:45:12 +08:00
|
|
|
#include "input.h"
|
2016-01-22 11:56:39 +08:00
|
|
|
#include "input_common.h"
|
2016-05-01 09:37:19 +08:00
|
|
|
#include "io.h"
|
|
|
|
#include "parser.h"
|
|
|
|
#include "path.h"
|
|
|
|
#include "proc.h"
|
|
|
|
#include "reader.h"
|
2016-01-22 11:56:39 +08:00
|
|
|
#include "wildcard.h"
|
2016-05-01 09:37:19 +08:00
|
|
|
#include "wutil.h" // IWYU pragma: keep
|
2005-09-20 21:26:39 +08:00
|
|
|
|
2016-04-14 00:00:07 +08:00
|
|
|
// PATH_MAX may not exist.
|
2012-07-09 06:20:39 +08:00
|
|
|
#ifndef PATH_MAX
|
2016-04-14 00:00:07 +08:00
|
|
|
#define PATH_MAX 4096
|
2012-07-09 06:20:39 +08:00
|
|
|
#endif
|
|
|
|
|
2016-05-01 09:37:19 +08:00
|
|
|
/// If we are doing profiling, the filename to output to.
|
2014-02-10 06:04:43 +08:00
|
|
|
static const char *s_profiling_output_filename = NULL;
|
|
|
|
|
2016-05-01 09:37:19 +08:00
|
|
|
static bool has_suffix(const std::string &path, const char *suffix, bool ignore_case) {
|
2012-07-09 06:20:39 +08:00
|
|
|
size_t pathlen = path.size(), suffixlen = strlen(suffix);
|
2016-05-01 09:37:19 +08:00
|
|
|
return pathlen >= suffixlen &&
|
|
|
|
!(ignore_case ? strcasecmp : strcmp)(path.c_str() + pathlen - suffixlen, suffix);
|
2012-07-09 06:20:39 +08:00
|
|
|
}
|
|
|
|
|
2016-05-01 09:37:19 +08:00
|
|
|
/// Modifies the given path by calling realpath. Returns true if realpath succeeded, false
|
|
|
|
/// otherwise.
|
|
|
|
static bool get_realpath(std::string &path) {
|
2012-07-09 06:20:39 +08:00
|
|
|
char buff[PATH_MAX], *ptr;
|
2016-05-01 09:37:19 +08:00
|
|
|
if ((ptr = realpath(path.c_str(), buff))) {
|
2012-07-09 06:20:39 +08:00
|
|
|
path = ptr;
|
|
|
|
}
|
|
|
|
return ptr != NULL;
|
|
|
|
}
|
|
|
|
|
2016-05-01 09:37:19 +08:00
|
|
|
// OS X function for getting the executable path.
|
2012-07-09 06:20:39 +08:00
|
|
|
extern "C" {
|
2016-05-01 09:37:19 +08:00
|
|
|
int _NSGetExecutablePath(char *buf, uint32_t *bufsize);
|
2012-07-09 06:20:39 +08:00
|
|
|
}
|
|
|
|
|
2016-05-01 09:37:19 +08:00
|
|
|
/// Return the path to the current executable. This needs to be realpath'd.
|
|
|
|
static std::string get_executable_path(const char *argv0) {
|
2016-04-14 00:00:07 +08:00
|
|
|
char buff[PATH_MAX];
|
2016-04-13 10:03:07 +08:00
|
|
|
|
2012-07-09 06:20:39 +08:00
|
|
|
#if __APPLE__
|
2016-04-13 10:03:07 +08:00
|
|
|
// On OS X use it's proprietary API to get the path to the executable.
|
|
|
|
uint32_t buffSize = sizeof buff;
|
|
|
|
if (_NSGetExecutablePath(buff, &buffSize) == 0) return std::string(buff);
|
|
|
|
#else
|
|
|
|
// On non-OS X UNIXes, try /proc directory.
|
|
|
|
ssize_t len;
|
2016-04-14 00:00:07 +08:00
|
|
|
len = readlink("/proc/self/exe", buff, sizeof buff - 1); // Linux
|
2016-04-13 10:03:07 +08:00
|
|
|
if (len == -1) {
|
2016-04-14 00:00:07 +08:00
|
|
|
len = readlink("/proc/curproc/file", buff, sizeof buff - 1); // BSD
|
2016-04-13 10:03:07 +08:00
|
|
|
if (len == -1) {
|
2016-04-14 00:00:07 +08:00
|
|
|
len = readlink("/proc/self/path/a.out", buff, sizeof buff - 1); // Solaris
|
2016-04-13 10:01:28 +08:00
|
|
|
}
|
|
|
|
}
|
2016-04-13 10:03:07 +08:00
|
|
|
if (len > 0) {
|
|
|
|
buff[len] = '\0';
|
|
|
|
return std::string(buff);
|
|
|
|
}
|
|
|
|
#endif
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-04-13 10:03:07 +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.
|
2012-07-09 06:20:39 +08:00
|
|
|
return std::string(argv0 ? argv0 : "");
|
|
|
|
}
|
|
|
|
|
2016-05-01 09:37:19 +08:00
|
|
|
static struct config_paths_t determine_config_directory_paths(const char *argv0) {
|
2012-07-09 06:20:39 +08:00
|
|
|
struct config_paths_t paths;
|
|
|
|
bool done = false;
|
|
|
|
std::string exec_path = get_executable_path(argv0);
|
2016-05-01 09:37:19 +08:00
|
|
|
if (get_realpath(exec_path)) {
|
2012-07-09 06:20:39 +08:00
|
|
|
#if __APPLE__
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-05-01 09:37:19 +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) {
|
2016-07-01 19:34:04 +08:00
|
|
|
const char *suffix = "Contents/Resources/base/bin/fish";
|
2012-07-09 06:20:39 +08:00
|
|
|
const size_t suffixlen = strlen(suffix);
|
2016-05-01 09:37:19 +08:00
|
|
|
if (has_suffix(exec_path, suffix, true)) {
|
|
|
|
// Looks like we're a bundle. Cut the string at the / prefixing /Contents... and
|
|
|
|
// then the rest.
|
2012-07-09 06:20:39 +08:00
|
|
|
wcstring wide_resolved_path = str2wcstring(exec_path);
|
|
|
|
wide_resolved_path.resize(exec_path.size() - suffixlen);
|
2016-07-01 19:34:04 +08:00
|
|
|
wide_resolved_path.append(L"Contents/Resources/base/");
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-05-01 09:37:19 +08:00
|
|
|
// Append share, etc, doc.
|
2012-07-09 06:20:39 +08:00
|
|
|
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
|
|
|
|
2016-05-01 09:37:19 +08:00
|
|
|
// But the bin_dir is the resolved_path, minus fish (aka the MacOS directory).
|
2012-07-09 06:20:39 +08:00
|
|
|
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
|
|
|
|
2016-05-01 09:37:19 +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-07-09 06:20:39 +08:00
|
|
|
const char *suffix = "/bin/fish";
|
2016-05-01 09:37:19 +08:00
|
|
|
if (has_suffix(exec_path, suffix, false)) {
|
2012-07-09 06:20:39 +08:00
|
|
|
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
|
|
|
|
2016-05-01 09:37:19 +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;
|
2016-05-01 09:37:19 +08:00
|
|
|
if (0 == wstat(paths.data, &buf) && 0 == wstat(paths.sysconf, &buf)) {
|
|
|
|
// The docs dir may not exist; in that case fall back to the compiled in path.
|
|
|
|
if (0 != wstat(paths.doc, &buf)) {
|
2014-01-18 06:18:45 +08:00
|
|
|
paths.doc = L"" DOCDIR;
|
|
|
|
}
|
2012-07-09 06:20:39 +08:00
|
|
|
done = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-05-01 09:37:19 +08:00
|
|
|
if (!done) {
|
|
|
|
// Fall back to what got compiled in.
|
2012-07-09 06:20:39 +08:00
|
|
|
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;
|
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;
|
|
|
|
}
|
|
|
|
|
2016-02-05 04:51:55 +08:00
|
|
|
// Source the file config.fish in the given directory.
|
2016-05-01 09:37:19 +08:00
|
|
|
static void source_config_in_directory(const wcstring &dir) {
|
|
|
|
// If the config.fish file doesn't exist or isn't readable silently return. Fish versions up
|
|
|
|
// thru 2.2.0 would instead try to source the file with stderr redirected to /dev/null to deal
|
|
|
|
// with that possibility.
|
2016-02-05 04:51:55 +08:00
|
|
|
//
|
2016-05-01 09:37:19 +08:00
|
|
|
// This introduces a race condition since the readability of the file can change between this
|
|
|
|
// test and the execution of the 'source' command. However, that is not a security problem in
|
|
|
|
// this context so we ignore it.
|
2016-02-05 04:51:55 +08:00
|
|
|
const wcstring config_pathname = dir + L"/config.fish";
|
2013-09-30 04:39:41 +08:00
|
|
|
const wcstring escaped_dir = escape_string(dir, ESCAPE_ALL);
|
2016-02-05 04:51:55 +08:00
|
|
|
const wcstring escaped_pathname = escaped_dir + L"/config.fish";
|
|
|
|
if (waccess(config_pathname, R_OK) != 0) {
|
2016-05-01 09:37:19 +08:00
|
|
|
debug(2, L"not sourcing %ls (not readable or does not exist)", escaped_pathname.c_str());
|
2016-02-05 04:51:55 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
debug(2, L"sourcing %ls", escaped_pathname.c_str());
|
|
|
|
|
|
|
|
const wcstring cmd = L"builtin source " + escaped_pathname;
|
2013-09-30 04:39:41 +08:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2016-05-01 09:37:19 +08:00
|
|
|
static int try_connect_socket(std::string &name) {
|
2015-04-11 21:33:11 +08:00
|
|
|
int s, r, ret = -1;
|
|
|
|
|
2016-05-01 09:37:19 +08:00
|
|
|
/// Connect to a DGRAM socket rather than the expected STREAM. This avoids any notification to a
|
|
|
|
/// remote socket that we have connected, preventing any surprising behaviour. If the connection
|
|
|
|
/// fails with EPROTOTYPE, the connection is probably a STREAM; if it succeeds or fails any
|
|
|
|
/// other way, there is no cause for alarm. With thanks to Andrew Lutomirski <github.com/amluto>
|
|
|
|
if ((s = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) {
|
2015-04-11 21:33:11 +08:00
|
|
|
wperror(L"socket");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
debug(3, L"Connect to socket %s at fd %d", name.c_str(), s);
|
|
|
|
|
|
|
|
struct sockaddr_un local = {};
|
|
|
|
local.sun_family = AF_UNIX;
|
|
|
|
strncpy(local.sun_path, name.c_str(), (sizeof local.sun_path) - 1);
|
|
|
|
|
|
|
|
r = connect(s, (struct sockaddr *)&local, sizeof local);
|
|
|
|
|
2016-05-01 09:37:19 +08:00
|
|
|
if (r == -1 && errno == EPROTOTYPE) {
|
2015-04-11 21:33:11 +08:00
|
|
|
ret = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
close(s);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-05-01 09:37:19 +08:00
|
|
|
/// Check for a running fishd from old versions and warn about not being able to share variables.
|
|
|
|
/// https://github.com/fish-shell/fish-shell/issues/1730
|
|
|
|
static void check_running_fishd() {
|
|
|
|
// There are two paths to check:
|
|
|
|
// $FISHD_SOCKET_DIR/fishd.socket.$USER or /tmp/fishd.socket.$USER
|
|
|
|
// - referred to as the "old socket"
|
|
|
|
// $XDG_RUNTIME_DIR/fishd.socket or /tmp/fish.$USER/fishd.socket
|
|
|
|
// - referred to as the "new socket"
|
|
|
|
// All existing versions of fish attempt to create the old socket, but
|
|
|
|
// failure in newer versions is not treated as critical, so both need
|
|
|
|
// to be checked.
|
2015-04-11 21:33:11 +08:00
|
|
|
const char *uname = getenv("USER");
|
2016-05-01 09:37:19 +08:00
|
|
|
if (uname == NULL) {
|
2015-04-11 21:33:11 +08:00
|
|
|
const struct passwd *pw = getpwuid(getuid());
|
|
|
|
uname = pw->pw_name;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *dir_old_socket = getenv("FISHD_SOCKET_DIR");
|
|
|
|
std::string path_old_socket;
|
|
|
|
|
2016-05-01 09:37:19 +08:00
|
|
|
if (dir_old_socket == NULL) {
|
2015-04-11 21:33:11 +08:00
|
|
|
path_old_socket = "/tmp/";
|
2016-05-01 09:37:19 +08:00
|
|
|
} else {
|
2015-04-11 21:33:11 +08:00
|
|
|
path_old_socket.append(dir_old_socket);
|
|
|
|
}
|
|
|
|
|
|
|
|
path_old_socket.append("fishd.socket.");
|
|
|
|
path_old_socket.append(uname);
|
|
|
|
|
|
|
|
const char *dir_new_socket = getenv("XDG_RUNTIME_DIR");
|
|
|
|
std::string path_new_socket;
|
2016-05-01 09:37:19 +08:00
|
|
|
if (dir_new_socket == NULL) {
|
2015-04-11 21:33:11 +08:00
|
|
|
path_new_socket = "/tmp/fish.";
|
|
|
|
path_new_socket.append(uname);
|
|
|
|
path_new_socket.push_back('/');
|
2016-05-01 09:37:19 +08:00
|
|
|
} else {
|
2015-04-11 21:33:11 +08:00
|
|
|
path_new_socket.append(dir_new_socket);
|
|
|
|
}
|
|
|
|
|
|
|
|
path_new_socket.append("fishd.socket");
|
|
|
|
|
2016-05-01 09:37:19 +08:00
|
|
|
if (try_connect_socket(path_old_socket) == 0 || try_connect_socket(path_new_socket) == 0) {
|
|
|
|
debug(1, _(L"Old versions of fish appear to be running. You will not be able to share "
|
|
|
|
L"variable values between old and new fish sessions. For best results, restart "
|
|
|
|
L"all running instances of fish."));
|
2015-04-11 21:33:11 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-01 09:37:19 +08:00
|
|
|
/// 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) {
|
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
|
|
|
|
2016-05-01 09:37:19 +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;
|
2016-05-01 09:37:19 +08:00
|
|
|
if (path_get_config(config_dir)) {
|
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
|
|
|
}
|
|
|
|
|
2016-05-16 10:45:02 +08:00
|
|
|
/// Parse the argument list, return the index of the first non-flag arguments.
|
2016-05-01 09:37:19 +08:00
|
|
|
static int fish_parse_opt(int argc, char **argv, std::vector<std::string> *cmds) {
|
2016-05-16 10:45:02 +08:00
|
|
|
const char *short_opts = "+hilnvc:p:d:D:";
|
2016-05-01 09:37:19 +08:00
|
|
|
const struct option long_opts[] = {{"command", required_argument, NULL, 'c'},
|
|
|
|
{"debug-level", required_argument, NULL, 'd'},
|
2016-05-16 10:45:02 +08:00
|
|
|
{"debug-stack-frames", required_argument, NULL, 'D'},
|
2016-05-01 09:37:19 +08:00
|
|
|
{"interactive", no_argument, NULL, 'i'},
|
|
|
|
{"login", no_argument, NULL, 'l'},
|
|
|
|
{"no-execute", no_argument, NULL, 'n'},
|
|
|
|
{"profile", required_argument, NULL, 'p'},
|
|
|
|
{"help", no_argument, NULL, 'h'},
|
|
|
|
{"version", no_argument, NULL, 'v'},
|
|
|
|
{NULL, 0, NULL, 0}};
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-03-04 07:36:17 +08:00
|
|
|
int opt;
|
2016-05-01 09:37:19 +08:00
|
|
|
while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
|
|
|
|
switch (opt) {
|
|
|
|
case 0: {
|
2016-03-04 07:36:17 +08:00
|
|
|
fwprintf(stderr, _(L"getopt_long() unexpectedly returned zero\n"));
|
2016-03-24 02:42:17 +08:00
|
|
|
exit(127);
|
2012-11-19 16:31:03 +08:00
|
|
|
}
|
2016-05-01 09:37:19 +08:00
|
|
|
case 'c': {
|
2015-12-19 12:05:00 +08:00
|
|
|
cmds->push_back(optarg);
|
2012-11-19 16:31:03 +08:00
|
|
|
break;
|
|
|
|
}
|
2016-05-01 09:37:19 +08:00
|
|
|
case 'd': {
|
2012-11-19 16:31:03 +08:00
|
|
|
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
|
|
|
|
2016-05-01 09:37:19 +08:00
|
|
|
if (tmp >= 0 && tmp <= 10 && !*end && !errno) {
|
2012-11-19 16:31:03 +08:00
|
|
|
debug_level = (int)tmp;
|
2016-05-01 09:37:19 +08:00
|
|
|
} else {
|
2016-05-16 10:45:02 +08:00
|
|
|
fwprintf(stderr, _(L"Invalid value '%s' for debug-level flag"), optarg);
|
2016-03-24 02:42:17 +08:00
|
|
|
exit(1);
|
2012-11-19 16:31:03 +08:00
|
|
|
}
|
|
|
|
break;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2016-05-01 09:37:19 +08:00
|
|
|
case 'h': {
|
2015-12-19 12:05:00 +08:00
|
|
|
cmds->push_back("__fish_print_help fish");
|
2012-11-19 16:31:03 +08:00
|
|
|
break;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2016-05-01 09:37:19 +08:00
|
|
|
case 'i': {
|
2015-12-19 12:05:00 +08:00
|
|
|
is_interactive_session = 1;
|
2012-11-19 16:31:03 +08:00
|
|
|
break;
|
|
|
|
}
|
2016-05-01 09:37:19 +08:00
|
|
|
case 'l': {
|
2015-12-19 12:05:00 +08:00
|
|
|
is_login = 1;
|
2012-11-19 16:31:03 +08:00
|
|
|
break;
|
|
|
|
}
|
2016-05-01 09:37:19 +08:00
|
|
|
case 'n': {
|
2015-12-19 12:05:00 +08:00
|
|
|
no_exec = 1;
|
2012-11-19 16:31:03 +08:00
|
|
|
break;
|
|
|
|
}
|
2016-05-01 09:37:19 +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;
|
|
|
|
}
|
2016-05-01 09:37:19 +08:00
|
|
|
case 'v': {
|
|
|
|
fwprintf(stdout, _(L"%s, version %s\n"), PACKAGE_NAME, get_fish_version());
|
2016-03-24 02:42:17 +08:00
|
|
|
exit(0);
|
2012-11-19 16:31:03 +08:00
|
|
|
}
|
2016-05-16 10:45:02 +08:00
|
|
|
case 'D': {
|
|
|
|
char *end;
|
|
|
|
long tmp;
|
|
|
|
|
|
|
|
errno = 0;
|
|
|
|
tmp = strtol(optarg, &end, 10);
|
|
|
|
|
|
|
|
if (tmp > 0 && tmp <= 128 && !*end && !errno) {
|
|
|
|
debug_stack_frames = (int)tmp;
|
|
|
|
} else {
|
|
|
|
fwprintf(stderr, _(L"Invalid value '%s' for debug-stack-frames flag"), optarg);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2016-05-01 09:37:19 +08:00
|
|
|
default: {
|
2016-03-04 07:36:17 +08:00
|
|
|
// We assume getopt_long() has already emitted a diagnostic msg.
|
2016-03-24 02:42:17 +08:00
|
|
|
exit(1);
|
2012-11-19 16:31:03 +08:00
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-19 12:05:00 +08:00
|
|
|
// If our command name begins with a dash that implies we're a login shell.
|
|
|
|
is_login |= argv[0][0] == '-';
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2015-12-19 12:05:00 +08:00
|
|
|
// We are an interactive session if we have not been given an explicit
|
|
|
|
// command or file to execute and stdin is a tty. Note that the -i or
|
|
|
|
// --interactive options also force interactive mode.
|
2016-05-01 09:37:19 +08:00
|
|
|
if (cmds->size() == 0 && optind == argc && isatty(STDIN_FILENO)) {
|
2015-12-19 12:05:00 +08:00
|
|
|
is_interactive_session = 1;
|
2013-10-27 06:24:49 +08:00
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2015-12-19 12:05:00 +08:00
|
|
|
return optind;
|
2006-10-26 04:54:43 +08:00
|
|
|
}
|
|
|
|
|
2016-06-18 04:08:25 +08:00
|
|
|
/// Various things we need to initialize at run-time that don't really fit any of the other init
|
|
|
|
/// routines.
|
|
|
|
static void misc_init() {
|
|
|
|
#ifdef OS_IS_CYGWIN
|
2016-06-25 15:56:25 +08:00
|
|
|
// MS Windows tty devices do not currently have either a read or write timestamp. Those
|
|
|
|
// respective fields of `struct stat` are always the current time. Which means we can't
|
|
|
|
// use them. So we assume no external program has written to the terminal behind our
|
|
|
|
// back. This makes multiline promptusable. See issue #2859 and
|
|
|
|
// https://github.com/Microsoft/BashOnWindows/issues/545
|
2016-06-18 04:08:25 +08:00
|
|
|
has_working_tty_timestamps = false;
|
|
|
|
#else
|
2016-06-25 15:31:46 +08:00
|
|
|
// This covers preview builds of Windows Subsystem for Linux (WSL).
|
|
|
|
FILE *procsyskosrel;
|
|
|
|
if ((procsyskosrel = wfopen(L"/proc/sys/kernel/osrelease", "r"))) {
|
|
|
|
wcstring osrelease;
|
|
|
|
fgetws2(&osrelease, procsyskosrel);
|
|
|
|
if (osrelease.find(L"3.4.0-Microsoft") != wcstring::npos) {
|
2016-06-18 04:08:25 +08:00
|
|
|
has_working_tty_timestamps = false;
|
|
|
|
}
|
|
|
|
}
|
2016-06-25 15:56:25 +08:00
|
|
|
if (procsyskosrel) {
|
|
|
|
fclose(procsyskosrel);
|
|
|
|
}
|
2016-06-18 04:08:25 +08:00
|
|
|
#endif // OS_IS_MS_WINDOWS
|
|
|
|
}
|
|
|
|
|
2016-05-01 09:37:19 +08:00
|
|
|
int main(int argc, char **argv) {
|
|
|
|
int res = 1;
|
|
|
|
int my_optind = 0;
|
2006-10-26 04:54:43 +08:00
|
|
|
|
2016-01-22 11:56:39 +08:00
|
|
|
// We can't do this at compile time due to the use of enum symbols.
|
2016-05-01 09:37:19 +08:00
|
|
|
assert(EXPAND_SENTINAL >= EXPAND_RESERVED_BASE && EXPAND_SENTINAL <= EXPAND_RESERVED_END);
|
|
|
|
assert(ANY_SENTINAL >= WILDCARD_RESERVED_BASE && ANY_SENTINAL <= WILDCARD_RESERVED_END);
|
|
|
|
assert(R_SENTINAL >= INPUT_COMMON_BASE && R_SENTINAL <= INPUT_COMMON_END);
|
2016-01-22 11:56:39 +08:00
|
|
|
|
2016-06-04 10:05:13 +08:00
|
|
|
program_name = L"fish";
|
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
|
|
|
|
2016-06-04 10:05:13 +08:00
|
|
|
setlocale(LC_ALL, "");
|
|
|
|
fish_setlocale();
|
2006-10-26 04:54:43 +08:00
|
|
|
|
2016-05-01 09:37:19 +08:00
|
|
|
// struct stat tmp;
|
|
|
|
// stat("----------FISH_HIT_MAIN----------", &tmp);
|
2006-11-11 18:48:40 +08:00
|
|
|
|
2016-07-28 07:35:33 +08:00
|
|
|
if (!argv[0]) {
|
|
|
|
static const char *dummy_argv[2] = {"fish", NULL};
|
|
|
|
argv = (char **)dummy_argv; //!OCLINT(parameter reassignment)
|
|
|
|
argc = 1; //!OCLINT(parameter reassignment)
|
|
|
|
}
|
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
|
|
|
|
2016-05-01 09:37:19 +08:00
|
|
|
// No-exec is prohibited when in interactive mode.
|
|
|
|
if (is_interactive_session && no_exec) {
|
2012-11-19 08:30:30 +08:00
|
|
|
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
|
|
|
|
2016-05-01 09:37:19 +08:00
|
|
|
// Only save (and therefore restore) the fg process group if we are interactive. See issues
|
|
|
|
// #197 and #1002.
|
|
|
|
if (is_interactive_session) {
|
2013-10-27 06:22:20 +08:00
|
|
|
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();
|
|
|
|
builtin_init();
|
|
|
|
function_init();
|
|
|
|
env_init(&paths);
|
|
|
|
reader_init();
|
|
|
|
history_init();
|
2016-05-01 09:37:19 +08:00
|
|
|
// For set_color to support term256 in config.fish (issue #1022).
|
2014-09-20 06:37:31 +08:00
|
|
|
update_fish_color_support();
|
2016-06-18 04:08:25 +08:00
|
|
|
misc_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-08-15 15:57:56 +08:00
|
|
|
const io_chain_t empty_ios;
|
2016-05-01 09:37:19 +08:00
|
|
|
if (read_init(paths)) {
|
|
|
|
// Stomp the exit status of any initialization commands (issue #635).
|
2013-04-10 14:48:03 +08:00
|
|
|
proc_set_last_status(STATUS_BUILTIN_OK);
|
2013-05-05 17:33:17 +08:00
|
|
|
|
2016-05-01 09:37:19 +08:00
|
|
|
// Run the commands specified as arguments, if any.
|
|
|
|
if (!cmds.empty()) {
|
|
|
|
// Do something nasty to support OpenSUSE assuming we're bash. This may modify cmds.
|
|
|
|
if (is_login) {
|
2013-01-05 05:09:01 +08:00
|
|
|
fish_xdm_login_hack_hack_hack_hack(&cmds, argc - my_optind, argv + my_optind);
|
|
|
|
}
|
2016-05-01 09:37:19 +08:00
|
|
|
for (size_t i = 0; i < cmds.size(); i++) {
|
2013-01-05 05:09:01 +08:00
|
|
|
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);
|
2016-05-01 09:37:19 +08:00
|
|
|
} else if (my_optind == argc) {
|
2016-03-16 05:03:27 +08:00
|
|
|
// Interactive mode
|
|
|
|
check_running_fishd();
|
|
|
|
res = reader_read(STDIN_FILENO, empty_ios);
|
2016-05-01 09:37:19 +08:00
|
|
|
} else {
|
|
|
|
char *file = *(argv + (my_optind++));
|
2016-03-16 05:03:27 +08:00
|
|
|
int fd = open(file, O_RDONLY);
|
2016-05-01 09:37:19 +08:00
|
|
|
if (fd == -1) {
|
2016-03-16 05:03:27 +08:00
|
|
|
perror(file);
|
2016-05-01 09:37:19 +08:00
|
|
|
} else {
|
|
|
|
// OK to not do this atomically since we cannot have gone multithreaded yet.
|
2012-03-02 16:27:40 +08:00
|
|
|
set_cloexec(fd);
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-05-01 09:37:19 +08:00
|
|
|
if (*(argv + my_optind)) {
|
2012-02-23 03:07:34 +08:00
|
|
|
wcstring sb;
|
2016-03-16 05:03:27 +08:00
|
|
|
char **ptr;
|
|
|
|
int i;
|
2016-05-01 09:37:19 +08:00
|
|
|
for (i = 1, ptr = argv + my_optind; *ptr; i++, ptr++) {
|
|
|
|
if (i != 1) sb.append(ARRAY_SEP_STR);
|
2012-11-19 08:30:30 +08:00
|
|
|
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);
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-01-04 04:12:53 +08:00
|
|
|
reader_push_current_filename(rel_filename.c_str());
|
2012-11-19 08:30:30 +08:00
|
|
|
|
|
|
|
res = reader_read(fd, empty_ios);
|
|
|
|
|
2016-05-01 09:37:19 +08:00
|
|
|
if (res) {
|
|
|
|
debug(1, _(L"Error while reading file %ls\n"), reader_current_filename()
|
|
|
|
? reader_current_filename()
|
|
|
|
: _(L"Standard input"));
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
reader_pop_current_filename();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-28 08:23:08 +08:00
|
|
|
int exit_status = res ? STATUS_UNKNOWN_COMMAND : proc_get_last_status();
|
|
|
|
|
|
|
|
proc_fire_event(L"PROCESS_EXIT", EVENT_EXIT, getpid(), exit_status);
|
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
|
|
|
|
2016-05-01 09:37:19 +08:00
|
|
|
if (g_profiling_active) {
|
2014-02-10 06:04:43 +08:00
|
|
|
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();
|
|
|
|
event_destroy();
|
2014-10-28 08:23:08 +08:00
|
|
|
exit_without_destructors(exit_status);
|
2016-05-01 09:37:19 +08:00
|
|
|
return EXIT_FAILURE; // above line should always exit
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|