2016-04-19 04:35:53 +00:00
|
|
|
// The classes responsible for autoloading functions and completions.
|
2016-05-18 22:30:21 +00:00
|
|
|
#include "config.h" // IWYU pragma: keep
|
|
|
|
|
2015-07-25 15:14:25 +00:00
|
|
|
#include <errno.h>
|
2016-05-03 23:23:30 +00:00
|
|
|
#include <pthread.h>
|
2017-02-13 04:24:22 +00:00
|
|
|
#include <stddef.h>
|
2015-07-25 15:14:25 +00:00
|
|
|
#include <sys/stat.h>
|
2016-05-03 23:23:30 +00:00
|
|
|
#include <time.h>
|
2015-07-25 15:14:25 +00:00
|
|
|
#include <unistd.h>
|
2017-02-13 04:24:22 +00:00
|
|
|
|
2016-06-24 00:24:19 +00:00
|
|
|
#include <memory>
|
2016-05-03 23:23:30 +00:00
|
|
|
#include <set>
|
2015-07-25 15:14:25 +00:00
|
|
|
#include <string>
|
2017-02-11 02:47:02 +00:00
|
|
|
#include <type_traits>
|
2015-07-25 15:14:25 +00:00
|
|
|
#include <utility>
|
|
|
|
#include <vector>
|
2016-04-21 06:00:54 +00:00
|
|
|
|
|
|
|
#include "autoload.h"
|
2016-04-19 04:35:53 +00:00
|
|
|
#include "common.h"
|
|
|
|
#include "env.h"
|
|
|
|
#include "exec.h"
|
2018-09-22 04:52:47 +00:00
|
|
|
#include "parser.h"
|
2016-04-21 06:00:54 +00:00
|
|
|
#include "wutil.h" // IWYU pragma: keep
|
2012-01-25 08:36:55 +00:00
|
|
|
|
2016-06-06 04:30:24 +00:00
|
|
|
/// The time before we'll recheck an autoloaded file.
|
2012-02-13 18:28:04 +00:00
|
|
|
static const int kAutoloadStalenessInterval = 15;
|
2012-01-28 22:56:13 +00:00
|
|
|
|
2019-04-26 20:25:41 +00:00
|
|
|
/// Represents a file that we might want to autoload.
|
|
|
|
struct autoloadable_file_t {
|
|
|
|
/// The path to the file.
|
|
|
|
wcstring path;
|
|
|
|
|
|
|
|
/// The metadata for the file.
|
|
|
|
file_id_t file_id;
|
|
|
|
};
|
|
|
|
|
|
|
|
/// Class representing a cache of files that may be autoloaded.
|
|
|
|
/// This is responsible for performing cached accesses to a set of paths.
|
|
|
|
class autoload_file_cache_t {
|
|
|
|
/// A timestamp is a monotonic point in time.
|
|
|
|
using timestamp_t = std::chrono::time_point<std::chrono::steady_clock>;
|
|
|
|
|
|
|
|
/// The directories from which to load.
|
|
|
|
const wcstring_list_t dirs_;
|
|
|
|
|
|
|
|
/// Our LRU cache of checks that were misses.
|
|
|
|
/// The key is the command, the value is the time of the check.
|
|
|
|
struct misses_lru_cache_t : public lru_cache_t<misses_lru_cache_t, timestamp_t> {};
|
|
|
|
misses_lru_cache_t misses_cache_;
|
|
|
|
|
|
|
|
/// The set of files that we have returned to the caller, along with the time of the check.
|
|
|
|
/// The key is the command (not the path).
|
|
|
|
struct known_file_t {
|
|
|
|
autoloadable_file_t file;
|
|
|
|
timestamp_t last_checked;
|
|
|
|
};
|
|
|
|
std::unordered_map<wcstring, known_file_t> known_files_;
|
|
|
|
|
|
|
|
/// \return the current timestamp.
|
|
|
|
static timestamp_t current_timestamp() { return std::chrono::steady_clock::now(); }
|
|
|
|
|
|
|
|
/// \return whether a timestamp is fresh enough to use.
|
|
|
|
static bool is_fresh(timestamp_t then, timestamp_t now);
|
|
|
|
|
|
|
|
/// Attempt to find an autoloadable file by searching our path list for a given comand.
|
|
|
|
/// \return the file, or none() if none.
|
|
|
|
maybe_t<autoloadable_file_t> locate_file(const wcstring &cmd) const;
|
|
|
|
|
|
|
|
public:
|
|
|
|
/// Initialize with a set of directories.
|
|
|
|
explicit autoload_file_cache_t(wcstring_list_t dirs) : dirs_(std::move(dirs)) {}
|
|
|
|
|
2019-04-27 18:00:36 +00:00
|
|
|
/// Initialize with empty directories.
|
|
|
|
autoload_file_cache_t() = default;
|
|
|
|
|
2019-04-26 20:25:41 +00:00
|
|
|
/// \return the directories.
|
|
|
|
const wcstring_list_t &dirs() const { return dirs_; }
|
|
|
|
|
|
|
|
/// Check if a command \p cmd can be loaded.
|
|
|
|
/// If \p allow_stale is true, allow stale entries; otherwise discard them.
|
|
|
|
/// This returns an autoloadable file, or none() if there is no such file.
|
|
|
|
maybe_t<autoloadable_file_t> check(const wcstring &cmd, bool allow_stale = false);
|
|
|
|
};
|
|
|
|
|
|
|
|
maybe_t<autoloadable_file_t> autoload_file_cache_t::locate_file(const wcstring &cmd) const {
|
|
|
|
// Re-use the storage for path.
|
|
|
|
wcstring path;
|
|
|
|
for (const wcstring &dir : dirs()) {
|
|
|
|
// Construct the path as dir/cmd.fish
|
|
|
|
path = dir;
|
|
|
|
path += L"/";
|
|
|
|
path += cmd;
|
|
|
|
path += L".fish";
|
|
|
|
|
|
|
|
file_id_t file_id = file_id_for_path(path);
|
|
|
|
if (file_id != kInvalidFileID) {
|
|
|
|
// Found it.
|
|
|
|
autoloadable_file_t result;
|
|
|
|
result.path = std::move(path);
|
|
|
|
result.file_id = file_id;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return none();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool autoload_file_cache_t::is_fresh(timestamp_t then, timestamp_t now) {
|
|
|
|
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(now - then);
|
|
|
|
return seconds.count() < kAutoloadStalenessInterval;
|
|
|
|
}
|
|
|
|
|
|
|
|
maybe_t<autoloadable_file_t> autoload_file_cache_t::check(const wcstring &cmd, bool allow_stale) {
|
|
|
|
const auto now = current_timestamp();
|
|
|
|
|
|
|
|
// Check hits.
|
|
|
|
auto iter = known_files_.find(cmd);
|
|
|
|
if (iter != known_files_.end()) {
|
|
|
|
if (allow_stale || is_fresh(iter->second.last_checked, now)) {
|
|
|
|
// Re-use this cached hit.
|
|
|
|
return iter->second.file;
|
|
|
|
}
|
|
|
|
// The file is stale, remove it.
|
|
|
|
known_files_.erase(iter);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check misses.
|
|
|
|
if (timestamp_t *miss = misses_cache_.get(cmd)) {
|
|
|
|
if (allow_stale || is_fresh(*miss, now)) {
|
|
|
|
// Re-use this cached miss.
|
|
|
|
return none();
|
|
|
|
}
|
|
|
|
// The miss is stale, remove it.
|
|
|
|
misses_cache_.evict_node(cmd);
|
|
|
|
}
|
|
|
|
|
|
|
|
// We couldn't satisfy this request from the cache. Hit the disk.
|
|
|
|
// Don't re-use 'now', the disk access could have taken a long time.
|
|
|
|
maybe_t<autoloadable_file_t> file = locate_file(cmd);
|
|
|
|
if (file.has_value()) {
|
|
|
|
auto ins = known_files_.emplace(cmd, known_file_t{*file, current_timestamp()});
|
|
|
|
assert(ins.second && "Known files cache should not have contained this cmd");
|
|
|
|
(void)ins;
|
|
|
|
} else {
|
|
|
|
bool ins = misses_cache_.insert(cmd, current_timestamp());
|
|
|
|
assert(ins && "Misses cache should not have contained this cmd");
|
|
|
|
(void)ins;
|
|
|
|
}
|
|
|
|
return file;
|
|
|
|
}
|
|
|
|
|
2019-04-27 18:00:36 +00:00
|
|
|
autoloader_t::autoloader_t(wcstring env_var_name)
|
|
|
|
: env_var_name_(std::move(env_var_name)), cache_(make_unique<autoload_file_cache_t>()) {}
|
|
|
|
|
2019-04-27 20:05:12 +00:00
|
|
|
autoloader_t::autoloader_t(autoloader_t &&) = default;
|
2019-04-27 18:00:36 +00:00
|
|
|
autoloader_t::~autoloader_t() = default;
|
|
|
|
|
|
|
|
bool autoloader_t::can_autoload(const wcstring &cmd) {
|
|
|
|
return cache_->check(cmd, true /* allow stale */).has_value();
|
|
|
|
}
|
|
|
|
|
2019-04-27 20:05:12 +00:00
|
|
|
wcstring_list_t autoloader_t::get_autoloaded_commands() const {
|
|
|
|
wcstring_list_t result;
|
|
|
|
result.reserve(autoloaded_files_.size());
|
|
|
|
for (const auto &kv : autoloaded_files_) {
|
|
|
|
result.push_back(kv.first);
|
|
|
|
}
|
|
|
|
// Sort the output to make it easier to test.
|
|
|
|
std::sort(result.begin(), result.end());
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2019-04-27 18:00:36 +00:00
|
|
|
maybe_t<wcstring> autoloader_t::resolve_command(const wcstring &cmd, const environment_t &env) {
|
|
|
|
// Are we currently in the process of autoloading this?
|
|
|
|
if (current_autoloading_.count(cmd) > 0) return none();
|
|
|
|
|
|
|
|
// Check to see if our paths have changed. If so, replace our cache.
|
|
|
|
// Note we don't have to modify autoloadable_files_. We'll naturally detect if those have
|
|
|
|
// changed when we query the cache.
|
|
|
|
maybe_t<env_var_t> mvar = env.get(env_var_name_);
|
|
|
|
const wcstring_list_t empty;
|
|
|
|
const wcstring_list_t &paths = mvar ? mvar->as_list() : empty;
|
|
|
|
if (paths != cache_->dirs()) {
|
|
|
|
cache_ = make_unique<autoload_file_cache_t>(paths);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Do we have an entry to load?
|
|
|
|
auto mfile = cache_->check(cmd);
|
|
|
|
if (!mfile) return none();
|
|
|
|
|
|
|
|
// Is this file the same as what we previously autoloaded?
|
|
|
|
auto iter = autoloaded_files_.find(cmd);
|
|
|
|
if (iter != autoloaded_files_.end() && iter->second == mfile->file_id) {
|
|
|
|
// The file has been autoloaded and is unchanged.
|
|
|
|
return none();
|
|
|
|
}
|
|
|
|
|
|
|
|
// We're going to (tell our caller to) autoload this command.
|
|
|
|
current_autoloading_.insert(cmd);
|
|
|
|
autoloaded_files_[cmd] = mfile->file_id;
|
|
|
|
return std::move(mfile->path);
|
|
|
|
}
|
|
|
|
|
2016-04-19 04:35:53 +00:00
|
|
|
file_access_attempt_t access_file(const wcstring &path, int mode) {
|
2014-03-07 17:20:42 +00:00
|
|
|
file_access_attempt_t result = {};
|
2019-04-20 01:47:07 +00:00
|
|
|
file_id_t file_id = file_id_for_path(path);
|
|
|
|
if (file_id != kInvalidFileID && 0 == waccess(path, mode)) {
|
|
|
|
result.file_id = file_id;
|
2012-01-25 19:47:45 +00:00
|
|
|
}
|
|
|
|
result.last_checked = time(NULL);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2019-04-26 22:03:41 +00:00
|
|
|
void autoloader_t::perform_autoload(const wcstring &path) {
|
|
|
|
wcstring script_source = L"source " + escape_string(path, ESCAPE_ALL);
|
|
|
|
exec_subshell(script_source, parser_t::principal_parser(),
|
|
|
|
false /* do not apply exit status */);
|
|
|
|
}
|
|
|
|
|
2019-04-22 03:32:21 +00:00
|
|
|
autoload_t::autoload_t(wcstring env_var_name_var) : env_var_name(std::move(env_var_name_var)) {}
|
2012-01-26 02:59:35 +00:00
|
|
|
|
2017-01-27 20:18:16 +00:00
|
|
|
void autoload_t::entry_was_evicted(wcstring key, autoload_function_t node) {
|
2016-04-19 04:35:53 +00:00
|
|
|
// This should only ever happen on the main thread.
|
2012-01-28 22:56:13 +00:00
|
|
|
ASSERT_IS_MAIN_THREAD();
|
2012-01-26 02:59:35 +00:00
|
|
|
}
|
|
|
|
|
2016-04-19 04:35:53 +00:00
|
|
|
int autoload_t::unload(const wcstring &cmd) { return this->evict_node(cmd); }
|
2012-01-26 02:59:35 +00:00
|
|
|
|
2016-04-19 04:35:53 +00:00
|
|
|
int autoload_t::load(const wcstring &cmd, bool reload) {
|
2012-11-19 00:30:30 +00:00
|
|
|
int res;
|
2012-01-28 22:56:13 +00:00
|
|
|
ASSERT_IS_MAIN_THREAD();
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2018-09-25 02:26:46 +00:00
|
|
|
// TODO: Justify this principal_parser.
|
|
|
|
auto &parser = parser_t::principal_parser();
|
|
|
|
auto &vars = parser.vars();
|
|
|
|
|
2018-02-16 05:58:02 +00:00
|
|
|
if (!this->paths) {
|
2018-09-25 02:26:46 +00:00
|
|
|
auto path_var = vars.get(env_var_name);
|
2018-02-16 05:58:02 +00:00
|
|
|
if (path_var.missing_or_empty()) return 0;
|
|
|
|
this->paths = path_var->as_list();
|
2012-01-26 02:59:35 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-04-19 04:35:53 +00:00
|
|
|
// Mark that we're loading this. Hang onto the iterator for fast erasing later. Note that
|
|
|
|
// std::set has guarantees about not invalidating iterators, so this is safe to do across the
|
|
|
|
// callouts below.
|
2017-08-19 20:29:52 +00:00
|
|
|
auto insert_result = is_loading_set.insert(cmd);
|
|
|
|
auto where = insert_result.first;
|
2014-02-24 21:06:26 +00:00
|
|
|
bool inserted = insert_result.second;
|
|
|
|
|
2016-04-19 04:35:53 +00:00
|
|
|
// Warn and fail on infinite recursion. It's OK to do this because this function is only called
|
|
|
|
// on the main thread.
|
|
|
|
if (!inserted) {
|
|
|
|
// We failed to insert.
|
2017-05-02 04:44:30 +00:00
|
|
|
const wchar_t *fmt =
|
|
|
|
_(L"Could not autoload item '%ls', it is already being autoloaded. "
|
|
|
|
L"This is a circular dependency in the autoloading scripts, please remove it.");
|
|
|
|
debug(0, fmt, cmd.c_str());
|
2012-01-26 02:59:35 +00:00
|
|
|
return 1;
|
|
|
|
}
|
2016-04-19 04:35:53 +00:00
|
|
|
// Try loading it.
|
2018-02-16 05:58:02 +00:00
|
|
|
assert(paths && "Should have paths");
|
|
|
|
res = this->locate_file_and_maybe_load_it(cmd, true, reload, *this->paths);
|
2016-04-19 04:35:53 +00:00
|
|
|
// Clean up.
|
2014-02-24 21:06:26 +00:00
|
|
|
is_loading_set.erase(where);
|
2012-11-19 00:30:30 +00:00
|
|
|
return res;
|
2012-01-26 02:59:35 +00:00
|
|
|
}
|
|
|
|
|
2018-09-09 09:25:51 +00:00
|
|
|
bool autoload_t::can_load(const wcstring &cmd, const environment_t &vars) {
|
2017-08-28 07:25:41 +00:00
|
|
|
auto path_var = vars.get(env_var_name);
|
2016-04-19 04:35:53 +00:00
|
|
|
if (path_var.missing_or_empty()) return false;
|
2012-07-21 05:11:05 +00:00
|
|
|
|
2012-01-27 19:43:52 +00:00
|
|
|
std::vector<wcstring> path_list;
|
2017-08-28 07:25:41 +00:00
|
|
|
path_var->to_list(path_list);
|
2012-11-19 00:30:30 +00:00
|
|
|
return this->locate_file_and_maybe_load_it(cmd, false, false, path_list);
|
2012-01-27 19:43:52 +00:00
|
|
|
}
|
|
|
|
|
2018-02-16 05:58:02 +00:00
|
|
|
void autoload_t::invalidate() {
|
|
|
|
ASSERT_IS_MAIN_THREAD();
|
|
|
|
scoped_lock locker(lock);
|
|
|
|
paths.reset();
|
|
|
|
this->evict_all_nodes();
|
|
|
|
}
|
|
|
|
|
2016-04-20 01:17:39 +00:00
|
|
|
/// Check whether the given command is loaded.
|
2016-04-19 04:35:53 +00:00
|
|
|
bool autoload_t::has_tried_loading(const wcstring &cmd) {
|
2012-11-18 10:23:22 +00:00
|
|
|
scoped_lock locker(lock);
|
2017-01-27 20:18:16 +00:00
|
|
|
autoload_function_t *func = this->get(cmd);
|
2012-02-27 04:11:34 +00:00
|
|
|
return func != NULL;
|
|
|
|
}
|
|
|
|
|
2016-06-06 04:30:24 +00:00
|
|
|
/// @return Whether this function is stale.
|
2016-04-19 04:35:53 +00:00
|
|
|
static bool is_stale(const autoload_function_t *func) {
|
2017-08-19 14:46:51 +00:00
|
|
|
return time(NULL) - func->access.last_checked > kAutoloadStalenessInterval;
|
2012-02-27 04:11:34 +00:00
|
|
|
}
|
|
|
|
|
2016-04-19 04:35:53 +00:00
|
|
|
autoload_function_t *autoload_t::get_autoloaded_function_with_creation(const wcstring &cmd,
|
|
|
|
bool allow_eviction) {
|
2012-02-27 04:11:34 +00:00
|
|
|
ASSERT_IS_LOCKED(lock);
|
2017-01-27 20:18:16 +00:00
|
|
|
autoload_function_t *func = this->get(cmd);
|
2016-04-19 04:35:53 +00:00
|
|
|
if (!func) {
|
|
|
|
if (allow_eviction) {
|
2019-01-16 21:46:11 +00:00
|
|
|
this->insert(cmd, autoload_function_t(false));
|
2016-04-19 04:35:53 +00:00
|
|
|
} else {
|
2019-01-16 21:46:11 +00:00
|
|
|
this->insert_no_eviction(cmd, autoload_function_t(false));
|
2012-02-27 04:11:34 +00:00
|
|
|
}
|
2017-01-27 20:18:16 +00:00
|
|
|
func = this->get(cmd);
|
|
|
|
assert(func);
|
2012-02-27 04:11:34 +00:00
|
|
|
}
|
|
|
|
return func;
|
|
|
|
}
|
|
|
|
|
2016-10-29 02:15:05 +00:00
|
|
|
static bool use_cached(autoload_function_t *func, bool really_load, bool allow_stale_functions) {
|
|
|
|
if (!func) {
|
|
|
|
return false; // can't use a function that doesn't exist
|
|
|
|
}
|
|
|
|
if (really_load && !func->is_placeholder && !func->is_loaded) {
|
|
|
|
return false; // can't use an unloaded function
|
|
|
|
}
|
|
|
|
if (!allow_stale_functions && is_stale(func)) {
|
|
|
|
return false; // can't use a stale function
|
|
|
|
}
|
|
|
|
return true; // I guess we can use it
|
|
|
|
}
|
|
|
|
|
2016-04-20 01:17:39 +00:00
|
|
|
/// This internal helper function does all the real work. By using two functions, the internal
|
|
|
|
/// function can return on various places in the code, and the caller can take care of various
|
|
|
|
/// cleanup work.
|
2016-06-06 04:30:24 +00:00
|
|
|
/// @param cmd the command name ('grep')
|
|
|
|
/// @param really_load Whether to actually parse it as a function, or just check it it exists
|
|
|
|
/// @param reload Whether to reload it if it's already loaded
|
|
|
|
/// @param path_list The set of paths to check
|
|
|
|
/// @return If really_load is true, returns whether the function was loaded. Otherwise returns
|
|
|
|
/// whether the function existed.
|
2016-04-19 04:35:53 +00:00
|
|
|
bool autoload_t::locate_file_and_maybe_load_it(const wcstring &cmd, bool really_load, bool reload,
|
|
|
|
const wcstring_list_t &path_list) {
|
|
|
|
// Note that we are NOT locked in this function!
|
2016-10-29 02:15:05 +00:00
|
|
|
bool reloaded = false;
|
2012-01-26 02:59:35 +00:00
|
|
|
|
2016-04-19 04:35:53 +00:00
|
|
|
// Try using a cached function. If we really want the function to be loaded, require that it be
|
|
|
|
// really loaded. If we're not reloading, allow stale functions.
|
2012-01-28 23:49:50 +00:00
|
|
|
{
|
2016-04-19 04:35:53 +00:00
|
|
|
bool allow_stale_functions = !reload;
|
2012-01-28 23:49:50 +00:00
|
|
|
scoped_lock locker(lock);
|
2017-01-27 20:18:16 +00:00
|
|
|
autoload_function_t *func = this->get(cmd); // get the function
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-04-19 04:35:53 +00:00
|
|
|
// If we can use this function, return whether we were able to access it.
|
2016-10-29 02:15:05 +00:00
|
|
|
if (use_cached(func, really_load, allow_stale_functions)) {
|
2019-04-20 01:47:07 +00:00
|
|
|
return func->access.accessible();
|
2012-01-28 23:49:50 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
}
|
2016-10-29 02:15:05 +00:00
|
|
|
|
2016-04-19 04:35:53 +00:00
|
|
|
// The source of the script will end up here.
|
2012-01-26 02:59:35 +00:00
|
|
|
wcstring script_source;
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-04-19 04:35:53 +00:00
|
|
|
// Whether we found an accessible file.
|
2012-01-28 22:56:13 +00:00
|
|
|
bool found_file = false;
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2017-01-30 05:34:30 +00:00
|
|
|
// Iterate over path searching for suitable completion files.
|
|
|
|
for (size_t i = 0; i < path_list.size() && !found_file; i++) {
|
|
|
|
wcstring next = path_list.at(i);
|
|
|
|
wcstring path = next + L"/" + cmd + L".fish";
|
|
|
|
|
|
|
|
const file_access_attempt_t access = access_file(path, R_OK);
|
2019-04-20 01:47:07 +00:00
|
|
|
if (!access.accessible()) {
|
2017-01-30 05:34:30 +00:00
|
|
|
continue;
|
2012-01-26 02:59:35 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2017-01-30 05:34:30 +00:00
|
|
|
// Now we're actually going to take the lock.
|
2012-02-27 04:11:34 +00:00
|
|
|
scoped_lock locker(lock);
|
2017-01-30 05:34:30 +00:00
|
|
|
autoload_function_t *func = this->get(cmd);
|
|
|
|
|
|
|
|
// Generate the source if we need to load it.
|
|
|
|
bool need_to_load_function =
|
|
|
|
really_load &&
|
2019-04-20 01:47:07 +00:00
|
|
|
(func == NULL || func->access.file_id != access.file_id || !func->is_loaded);
|
2017-01-30 05:34:30 +00:00
|
|
|
if (need_to_load_function) {
|
|
|
|
// Generate the script source.
|
|
|
|
script_source = L"source " + escape_string(path, ESCAPE_ALL);
|
|
|
|
|
2019-04-22 03:32:21 +00:00
|
|
|
// Mark that our function is no longer a placeholder, because we will load it.
|
|
|
|
if (func) {
|
2017-01-30 05:34:30 +00:00
|
|
|
func->is_placeholder = false;
|
2016-10-31 15:54:05 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2017-01-30 05:34:30 +00:00
|
|
|
// Mark that we're reloading it.
|
|
|
|
reloaded = true;
|
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2017-01-30 05:34:30 +00:00
|
|
|
// Create the function if we haven't yet. This does not load it. Do not trigger
|
|
|
|
// eviction unless we are actually loading, because we don't want to evict off of
|
|
|
|
// the main thread.
|
|
|
|
if (!func) func = get_autoloaded_function_with_creation(cmd, really_load);
|
2012-01-28 22:56:13 +00:00
|
|
|
|
2017-01-30 05:34:30 +00:00
|
|
|
// It's a fiction to say the script is loaded at this point, but we're definitely
|
|
|
|
// going to load it down below.
|
|
|
|
if (need_to_load_function) func->is_loaded = true;
|
2016-10-31 15:54:05 +00:00
|
|
|
|
2017-01-30 05:34:30 +00:00
|
|
|
// Unconditionally record our access time.
|
|
|
|
func->access = access;
|
|
|
|
found_file = true;
|
|
|
|
}
|
2012-01-26 02:59:35 +00:00
|
|
|
|
2017-01-30 05:34:30 +00:00
|
|
|
// If no file or builtin script was found we insert a placeholder function. Later we only
|
|
|
|
// research if the current time is at least five seconds later. This way, the files won't be
|
|
|
|
// searched over and over again.
|
|
|
|
if (!found_file && script_source.empty()) {
|
|
|
|
scoped_lock locker(lock);
|
|
|
|
// Generate a placeholder.
|
|
|
|
autoload_function_t *func = this->get(cmd);
|
|
|
|
if (!func) {
|
|
|
|
if (really_load) {
|
|
|
|
this->insert(cmd, autoload_function_t(true));
|
|
|
|
} else {
|
2017-04-26 08:30:32 +00:00
|
|
|
this->insert_no_eviction(cmd, autoload_function_t(true));
|
2012-01-28 22:56:13 +00:00
|
|
|
}
|
2017-01-30 05:34:30 +00:00
|
|
|
func = this->get(cmd);
|
|
|
|
assert(func);
|
2012-01-26 02:59:35 +00:00
|
|
|
}
|
2017-01-30 05:34:30 +00:00
|
|
|
func->access.last_checked = time(NULL);
|
2012-01-26 02:59:35 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-04-19 04:35:53 +00:00
|
|
|
// If we have a script, either built-in or a file source, then run it.
|
2017-01-30 05:34:30 +00:00
|
|
|
if (really_load && !script_source.empty()) {
|
2016-04-19 04:35:53 +00:00
|
|
|
// Do nothing on failure.
|
2018-09-25 02:26:46 +00:00
|
|
|
// TODO: rationalize this use of principal_parser, or inject the loading from outside.
|
2018-09-22 04:52:47 +00:00
|
|
|
exec_subshell(script_source, parser_t::principal_parser(),
|
|
|
|
false /* do not apply exit status */);
|
2012-01-26 02:59:35 +00:00
|
|
|
}
|
|
|
|
|
2016-04-19 04:35:53 +00:00
|
|
|
if (really_load) {
|
2012-01-28 22:56:13 +00:00
|
|
|
return reloaded;
|
|
|
|
}
|
2016-10-29 00:42:50 +00:00
|
|
|
|
2017-01-30 05:34:30 +00:00
|
|
|
return found_file || !script_source.empty();
|
2012-01-26 02:59:35 +00:00
|
|
|
}
|