mirror of
https://github.com/fish-shell/fish-shell.git
synced 2025-01-20 01:52:49 +08:00
Merges changes to support running fish without fishd.
In the new mode (not yet enabled), universal variables are set by reading and writing the fishd file directly, with some file locking for synchronization. This enables forwards and backwards compatibility. However there is no compatibility with simultaneous edits. Changes may be lost if fishd and the new mechanisms both attempt writes. fishd is still enabled by default for now; it will be disabled in a future commit. You can opt into the new mechanism (disabling fishd) by setting the environment variable fish_use_fishd to 0 before starting fish. This cannot itself be a universal variable, because of bootstrapping: the value is needed to determine how we read universal variables in the first place. Universal variable change notifications (i.e. reacting immediately to live edits) are tricky. Checking for changes is simple and relatively inexpensive (just a stat()), but relying solely on that would require frequent wakeups, and show up in fs_usage. So how do we get change notifications into an fd that we can monitor via select()? We support a few strategies, expressed as universal_notifier_t::notifier_strategy_t. By default we use notifyd on OS X and a named pipe on Linux / everywhere else. This is also configurable at runtime via the fish_universal_notifier variable.
This commit is contained in:
commit
451c97f35a
|
@ -2225,6 +2225,11 @@ scoped_lock::scoped_lock(pthread_mutex_t &mutex) : lock_obj(&mutex), locked(fals
|
|||
this->lock();
|
||||
}
|
||||
|
||||
scoped_lock::scoped_lock(lock_t &lock) : lock_obj(&lock.mutex), locked(false)
|
||||
{
|
||||
this->lock();
|
||||
}
|
||||
|
||||
scoped_lock::~scoped_lock()
|
||||
{
|
||||
if (locked) this->unlock();
|
||||
|
|
20
common.h
20
common.h
|
@ -537,6 +537,22 @@ public:
|
|||
|
||||
bool is_forked_child();
|
||||
|
||||
|
||||
class lock_t
|
||||
{
|
||||
public:
|
||||
pthread_mutex_t mutex;
|
||||
lock_t()
|
||||
{
|
||||
pthread_mutex_init(&mutex, NULL);
|
||||
}
|
||||
|
||||
~lock_t()
|
||||
{
|
||||
pthread_mutex_destroy(&mutex);
|
||||
}
|
||||
};
|
||||
|
||||
/* Basic scoped lock class */
|
||||
class scoped_lock
|
||||
{
|
||||
|
@ -551,6 +567,7 @@ public:
|
|||
void lock(void);
|
||||
void unlock(void);
|
||||
scoped_lock(pthread_mutex_t &mutex);
|
||||
scoped_lock(lock_t &lock);
|
||||
~scoped_lock();
|
||||
};
|
||||
|
||||
|
@ -849,6 +866,9 @@ void assert_is_not_forked_child(const char *who);
|
|||
#define ASSERT_IS_NOT_FORKED_CHILD_TRAMPOLINE(x) assert_is_not_forked_child(x)
|
||||
#define ASSERT_IS_NOT_FORKED_CHILD() ASSERT_IS_NOT_FORKED_CHILD_TRAMPOLINE(__FUNCTION__)
|
||||
|
||||
/** Macro to help suppress potentially unused variable warnings */
|
||||
#define USE(var) (void)(var)
|
||||
|
||||
extern "C" {
|
||||
__attribute__((noinline)) void debug_thread_error(void);
|
||||
}
|
||||
|
|
|
@ -376,6 +376,7 @@ AC_DEFINE(
|
|||
# Check for os dependant libraries for all binaries.
|
||||
AC_SEARCH_LIBS( connect, socket, , [AC_MSG_ERROR([Cannot find the socket library, needed to build this package.] )] )
|
||||
AC_SEARCH_LIBS( nanosleep, rt, , [AC_MSG_ERROR([Cannot find the rt library, needed to build this package.] )] )
|
||||
AC_SEARCH_LIBS( shm_open, rt, , [AC_MSG_ERROR([Cannot find the rt library, needed to build this package.] )] )
|
||||
AC_SEARCH_LIBS( pthread_create, pthread, , [AC_MSG_ERROR([Cannot find the pthread library, needed to build this package.] )] )
|
||||
AC_SEARCH_LIBS( setupterm, [ncurses tinfo curses], , [AC_MSG_ERROR([Could not find a curses implementation, needed to build fish. If this is Linux, try running 'sudo apt-get install libncurses5-dev' or 'sudo yum install ncurses-devel'])] )
|
||||
AC_SEARCH_LIBS( [nan], [m], [AC_DEFINE( [HAVE_NAN], [1], [Define to 1 if you have the nan function])] )
|
||||
|
@ -535,6 +536,7 @@ AC_CHECK_FUNCS( wcsdup wcsndup wcslen wcscasecmp wcsncasecmp fwprintf )
|
|||
AC_CHECK_FUNCS( futimes wcwidth wcswidth wcstok fputwc fgetwc )
|
||||
AC_CHECK_FUNCS( wcstol wcslcat wcslcpy lrand48_r killpg mkostemp )
|
||||
AC_CHECK_FUNCS( backtrace backtrace_symbols sysconf getifaddrs getpeerucred getpeereid )
|
||||
AC_CHECK_FUNCS( inotify_init inotify_init1 )
|
||||
|
||||
if test x$local_gettext != xno; then
|
||||
AC_CHECK_FUNCS( gettext dcgettext )
|
||||
|
@ -764,6 +766,7 @@ case $target_os in
|
|||
;;
|
||||
esac
|
||||
|
||||
|
||||
# Tell the world what we know
|
||||
AC_CONFIG_FILES([Makefile])
|
||||
AC_OUTPUT
|
||||
|
|
|
@ -248,11 +248,8 @@ static void check_connection(void)
|
|||
static void env_universal_remove_all()
|
||||
{
|
||||
size_t i;
|
||||
|
||||
wcstring_list_t lst;
|
||||
env_universal_common_get_names(lst,
|
||||
1,
|
||||
1);
|
||||
env_universal_common_get_names(lst, true, true);
|
||||
for (i=0; i<lst.size(); i++)
|
||||
{
|
||||
const wcstring &key = lst.at(i);
|
||||
|
@ -269,6 +266,8 @@ static void env_universal_remove_all()
|
|||
*/
|
||||
static void reconnect()
|
||||
{
|
||||
assert(synchronizes_via_fishd());
|
||||
|
||||
if (get_socket_count >= RECONNECT_COUNT)
|
||||
return;
|
||||
|
||||
|
@ -286,24 +285,38 @@ static void reconnect()
|
|||
}
|
||||
}
|
||||
|
||||
void env_universal_read_from_file()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void env_universal_init(wchar_t * p,
|
||||
wchar_t *u,
|
||||
void (*sf)(),
|
||||
void (*cb)(fish_message_type_t type, const wchar_t *name, const wchar_t *val))
|
||||
{
|
||||
path=p;
|
||||
user=u;
|
||||
start_fishd=sf;
|
||||
external_callback = cb;
|
||||
|
||||
env_universal_server.fd = get_socket();
|
||||
env_universal_common_init(&callback);
|
||||
env_universal_read_all();
|
||||
s_env_univeral_inited = true;
|
||||
if (env_universal_server.fd >= 0)
|
||||
if (! synchronizes_via_fishd())
|
||||
{
|
||||
env_universal_barrier();
|
||||
external_callback = cb;
|
||||
env_universal_common_init(&callback);
|
||||
env_universal_read_from_file();
|
||||
s_env_univeral_inited = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
path=p;
|
||||
user=u;
|
||||
start_fishd=sf;
|
||||
external_callback = cb;
|
||||
|
||||
env_universal_server.fd = get_socket();
|
||||
env_universal_common_init(&callback);
|
||||
env_universal_read_all();
|
||||
s_env_univeral_inited = true;
|
||||
if (env_universal_server.fd >= 0)
|
||||
{
|
||||
env_universal_barrier();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -374,8 +387,15 @@ bool env_universal_get_export(const wcstring &name)
|
|||
void env_universal_barrier()
|
||||
{
|
||||
ASSERT_IS_MAIN_THREAD();
|
||||
UNIVERSAL_LOG("BARRIER");
|
||||
message_t *msg;
|
||||
fd_set fds;
|
||||
|
||||
if (! synchronizes_via_fishd())
|
||||
{
|
||||
env_universal_common_sync();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!s_env_univeral_inited || is_dead())
|
||||
return;
|
||||
|
@ -441,9 +461,10 @@ void env_universal_set(const wcstring &name, const wcstring &value, bool exportv
|
|||
|
||||
debug(3, L"env_universal_set( \"%ls\", \"%ls\" )", name.c_str(), value.c_str());
|
||||
|
||||
if (is_dead())
|
||||
if (! synchronizes_via_fishd() || is_dead())
|
||||
{
|
||||
env_universal_common_set(name.c_str(), value.c_str(), exportv);
|
||||
env_universal_barrier();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -478,7 +499,7 @@ int env_universal_remove(const wchar_t *name)
|
|||
L"env_universal_remove( \"%ls\" )",
|
||||
name);
|
||||
|
||||
if (is_dead())
|
||||
if (! synchronizes_via_fishd() || is_dead())
|
||||
{
|
||||
env_universal_common_remove(name_str);
|
||||
}
|
||||
|
@ -504,3 +525,4 @@ void env_universal_get_names(wcstring_list_t &lst,
|
|||
show_exported,
|
||||
show_unexported);
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -4,6 +4,7 @@
|
|||
#include <wchar.h>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include "util.h"
|
||||
#include "env.h"
|
||||
|
||||
|
@ -188,6 +189,9 @@ env_var_t env_universal_common_get(const wcstring &name);
|
|||
*/
|
||||
bool env_universal_common_get_export(const wcstring &name);
|
||||
|
||||
/** Synchronizes all changse: writes everything out, reads stuff in */
|
||||
void env_universal_common_sync();
|
||||
|
||||
/**
|
||||
Add messages about all existing variables to the specified connection
|
||||
*/
|
||||
|
@ -200,13 +204,45 @@ void enqueue_all(connection_t *c);
|
|||
*/
|
||||
void connection_destroy(connection_t *c);
|
||||
|
||||
typedef std::vector<struct callback_data_t> callback_data_list_t;
|
||||
|
||||
/** Class representing universal variables */
|
||||
class env_universal_t
|
||||
{
|
||||
/* Current values */
|
||||
var_table_t vars;
|
||||
|
||||
/* Keys that have been modified, and need to be written. A value here that is not present in vars indicates a deleted value. */
|
||||
std::set<wcstring> modified;
|
||||
|
||||
/* Path that we save to. If empty, use the default */
|
||||
const wcstring explicit_vars_path;
|
||||
|
||||
mutable pthread_mutex_t lock;
|
||||
bool tried_renaming;
|
||||
bool load_from_path(const wcstring &path, callback_data_list_t *callbacks);
|
||||
void load_from_fd(int fd, callback_data_list_t *callbacks);
|
||||
void erase_unmodified_values();
|
||||
|
||||
void parse_message_internal(wchar_t *msg, connection_t *src, callback_data_list_t *callbacks);
|
||||
|
||||
void set_internal(const wcstring &key, const wcstring &val, bool exportv, bool overwrite);
|
||||
void remove_internal(const wcstring &name, bool overwrite);
|
||||
|
||||
/* Functions concerned with saving */
|
||||
bool open_and_acquire_lock(const wcstring &path, int *out_fd);
|
||||
bool open_temporary_file(const wcstring &directory, wcstring *out_path, int *out_fd);
|
||||
void write_to_fd(int fd);
|
||||
bool move_new_vars_file_into_place(const wcstring &src, const wcstring &dst);
|
||||
|
||||
/* File id from which we last read */
|
||||
file_id_t last_read_file;
|
||||
|
||||
void read_message_internal(connection_t *src, callback_data_list_t *callbacks);
|
||||
void enqueue_all_internal(connection_t *c) const;
|
||||
|
||||
public:
|
||||
env_universal_t();
|
||||
env_universal_t(const wcstring &path);
|
||||
~env_universal_t();
|
||||
|
||||
/* Get the value of the variable with the specified name */
|
||||
|
@ -226,6 +262,103 @@ public:
|
|||
|
||||
/* Writes variables to the connection */
|
||||
void enqueue_all(connection_t *c) const;
|
||||
|
||||
/** Loads variables at the correct path */
|
||||
bool load();
|
||||
|
||||
/** Reads and writes variables at the correct path. Returns true if modified variables were written. */
|
||||
bool sync(callback_data_list_t *callbacks);
|
||||
|
||||
/* Internal use */
|
||||
void read_message(connection_t *src, callback_data_list_t *callbacks);
|
||||
};
|
||||
|
||||
/** The "universal notifier" is an object responsible for broadcasting and receiving universal variable change notifications. These notifications do not contain the change, but merely indicate that the uvar file has changed. It is up to the uvar subsystem to re-read the file.
|
||||
|
||||
We support a few notificatins strategies. Not all strategies are supported on all platforms.
|
||||
|
||||
Notifiers may request polling, and/or provide a file descriptor to be watched for readability in select().
|
||||
|
||||
To request polling, the notifier overrides usec_delay_between_polls() to return a positive value. That value will be used as the timeout in select(). When select returns, the loop invokes poll(). poll() should return true to indicate that the file may have changed.
|
||||
|
||||
To provide a file descriptor, the notifier overrides notification_fd() to return a non-negative fd. This will be added to the "read" file descriptor list in select(). If the fd is readable, notification_fd_became_readable() will be called; that function should be overridden to return true if the file may have changed.
|
||||
|
||||
*/
|
||||
class universal_notifier_t
|
||||
{
|
||||
public:
|
||||
enum notifier_strategy_t
|
||||
{
|
||||
// Default meta-strategy to use the 'best' notifier for the system
|
||||
strategy_default,
|
||||
|
||||
// Use a value in shared memory. Simple, but requires polling and therefore semi-frequent wakeups.
|
||||
strategy_shmem_polling,
|
||||
|
||||
// Strategy that uses a named pipe. Somewhat complex, but portable and doesn't require polling most of the time.
|
||||
strategy_named_pipe,
|
||||
|
||||
// Strategy that attempts to detect file changes directly via inotify. Doesn't handle certain edge cases (like the file missing); not yet recommended. Linux only.
|
||||
strategy_inotify,
|
||||
|
||||
// Strategy that uses notify(3). Simple and efficient, but OS X only.
|
||||
strategy_notifyd,
|
||||
|
||||
// Null notifier, does nothing
|
||||
strategy_null
|
||||
};
|
||||
|
||||
protected:
|
||||
universal_notifier_t();
|
||||
|
||||
private:
|
||||
/* No copying */
|
||||
universal_notifier_t &operator=(const universal_notifier_t &);
|
||||
universal_notifier_t(const universal_notifier_t &x);
|
||||
static notifier_strategy_t resolve_default_strategy();
|
||||
|
||||
public:
|
||||
|
||||
virtual ~universal_notifier_t();
|
||||
|
||||
/* Factory constructor. Free with delete */
|
||||
static universal_notifier_t *new_notifier_for_strategy(notifier_strategy_t strat, const wchar_t *test_path = NULL);
|
||||
|
||||
/* Default instance. Other instances are possible for testing. */
|
||||
static universal_notifier_t &default_notifier();
|
||||
|
||||
/* Does a fast poll(). Returns true if changed. */
|
||||
virtual bool poll();
|
||||
|
||||
/* Triggers a notification */
|
||||
virtual void post_notification();
|
||||
|
||||
/* Recommended delay between polls. A value of 0 means no polling required (so no timeout) */
|
||||
virtual unsigned long usec_delay_between_polls() const;
|
||||
|
||||
/* Returns the fd from which to watch for events, or -1 if none */
|
||||
virtual int notification_fd();
|
||||
|
||||
/* The notification_fd is readable; drain it. Returns true if a notification is considered to have been posted. */
|
||||
virtual bool notification_fd_became_readable(int fd);
|
||||
};
|
||||
|
||||
std::string get_machine_identifier();
|
||||
bool get_hostname_identifier(std::string *result);
|
||||
|
||||
/* Temporary */
|
||||
bool synchronizes_via_fishd();
|
||||
|
||||
bool universal_log_enabled();
|
||||
#define UNIVERSAL_LOG(x) if (universal_log_enabled()) fprintf(stderr, "UNIVERSAL LOG: %s\n", x)
|
||||
|
||||
/* Environment variable for requesting a particular universal notifier. See fetch_default_strategy_from_environment for names. */
|
||||
#define UNIVERSAL_NOTIFIER_ENV_NAME "fish_universal_notifier"
|
||||
|
||||
/* Environment variable for enabling universal variable logging (to stderr) */
|
||||
#define UNIVERSAL_LOGGING_ENV_NAME "fish_universal_log"
|
||||
|
||||
/* Environment variable for enabling fishd */
|
||||
#define UNIVERSAL_USE_FISHD "fish_use_fishd"
|
||||
|
||||
#endif
|
||||
|
|
316
fish_tests.cpp
316
fish_tests.cpp
|
@ -63,6 +63,13 @@
|
|||
#include "pager.h"
|
||||
#include "input.h"
|
||||
#include "utf8.h"
|
||||
#include "env_universal_common.h"
|
||||
|
||||
#if HAVE_INOTIFY_INIT || HAVE_INOTIFY_INIT1
|
||||
#include <sys/inotify.h>
|
||||
#include <sys/utsname.h>
|
||||
#endif
|
||||
|
||||
|
||||
static const char * const * s_arguments;
|
||||
static int s_test_run_count = 0;
|
||||
|
@ -2162,6 +2169,313 @@ static void test_input()
|
|||
}
|
||||
}
|
||||
|
||||
#define UVARS_PER_THREAD 8
|
||||
#define UVARS_TEST_PATH L"/tmp/fish_uvars_test/varsfile.txt"
|
||||
|
||||
static int test_universal_helper(int *x)
|
||||
{
|
||||
env_universal_t uvars(UVARS_TEST_PATH);
|
||||
for (int j=0; j < UVARS_PER_THREAD; j++)
|
||||
{
|
||||
const wcstring key = format_string(L"key_%d_%d", *x, j);
|
||||
const wcstring val = format_string(L"val_%d_%d", *x, j);
|
||||
uvars.set(key, val, false);
|
||||
bool synced = uvars.sync(NULL);
|
||||
if (! synced)
|
||||
{
|
||||
err(L"Failed to sync universal variables");
|
||||
}
|
||||
fputc('.', stderr);
|
||||
}
|
||||
|
||||
/* Last step is to delete the first key */
|
||||
uvars.remove(format_string(L"key_%d_%d", *x, 0));
|
||||
bool synced = uvars.sync(NULL);
|
||||
if (! synced)
|
||||
{
|
||||
err(L"Failed to sync universal variables");
|
||||
}
|
||||
fputc('.', stderr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void test_universal()
|
||||
{
|
||||
say(L"Testing universal variables");
|
||||
if (system("mkdir -p /tmp/fish_uvars_test/")) err(L"mkdir failed");
|
||||
|
||||
const int threads = 16;
|
||||
for (int i=0; i < threads; i++)
|
||||
{
|
||||
iothread_perform(test_universal_helper, (void (*)(int *, int))NULL, new int(i));
|
||||
}
|
||||
iothread_drain_all();
|
||||
|
||||
env_universal_t uvars(UVARS_TEST_PATH);
|
||||
bool loaded = uvars.load();
|
||||
if (! loaded)
|
||||
{
|
||||
err(L"Failed to load universal variables");
|
||||
}
|
||||
for (int i=0; i < threads; i++)
|
||||
{
|
||||
for (int j=0; j < UVARS_PER_THREAD; j++)
|
||||
{
|
||||
const wcstring key = format_string(L"key_%d_%d", i, j);
|
||||
env_var_t expected_val;
|
||||
if (j == 0)
|
||||
{
|
||||
expected_val = env_var_t::missing_var();
|
||||
}
|
||||
else
|
||||
{
|
||||
expected_val = format_string(L"val_%d_%d", i, j);
|
||||
}
|
||||
const env_var_t var = uvars.get(key);
|
||||
if (j == 0)
|
||||
{
|
||||
assert(expected_val.missing());
|
||||
}
|
||||
if (var != expected_val)
|
||||
{
|
||||
const wchar_t *missing_desc = L"<missing>";
|
||||
err(L"Wrong value for key %ls: expected %ls, got %ls\n", key.c_str(), (expected_val.missing() ? missing_desc : expected_val.c_str()), (var.missing() ? missing_desc : var.c_str()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (system("rm -Rf /tmp/fish_uvars_test")) err(L"rrm failed");
|
||||
putc('\n', stderr);
|
||||
}
|
||||
|
||||
bool poll_notifier(universal_notifier_t *note)
|
||||
{
|
||||
bool result = false;
|
||||
if (note->usec_delay_between_polls() > 0)
|
||||
{
|
||||
result = note->poll();
|
||||
}
|
||||
|
||||
int fd = note->notification_fd();
|
||||
if (! result && fd >= 0)
|
||||
{
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(fd, &fds);
|
||||
struct timeval tv = {0, 0};
|
||||
if (select(fd + 1, &fds, NULL, NULL, &tv) > 0 && FD_ISSET(fd, &fds))
|
||||
{
|
||||
result = note->notification_fd_became_readable(fd);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void trigger_or_wait_for_notification(universal_notifier_t *notifier, universal_notifier_t::notifier_strategy_t strategy)
|
||||
{
|
||||
switch (strategy)
|
||||
{
|
||||
case universal_notifier_t::strategy_default:
|
||||
assert(0 && "strategy_default should be passed");
|
||||
break;
|
||||
|
||||
case universal_notifier_t::strategy_shmem_polling:
|
||||
// nothing required
|
||||
break;
|
||||
|
||||
case universal_notifier_t::strategy_notifyd:
|
||||
// notifyd requires a round trip to the notifyd server, which means we have to wait a little bit to receive it
|
||||
// In practice, this seems to be enough
|
||||
usleep(1000000 / 25);
|
||||
break;
|
||||
|
||||
case universal_notifier_t::strategy_named_pipe:
|
||||
case universal_notifier_t::strategy_null:
|
||||
break;
|
||||
|
||||
case universal_notifier_t::strategy_inotify:
|
||||
{
|
||||
// Hacktastic. Replace the file, then wait
|
||||
char cmd[512];
|
||||
sprintf(cmd, "touch %ls ; mv %ls %ls", UVARS_TEST_PATH L".tmp", UVARS_TEST_PATH ".tmp", UVARS_TEST_PATH);
|
||||
if (system(cmd)) err(L"Command failed: %s", cmd);
|
||||
usleep(1000000 / 25);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void test_notifiers_with_strategy(universal_notifier_t::notifier_strategy_t strategy)
|
||||
{
|
||||
assert(strategy != universal_notifier_t::strategy_default);
|
||||
say(L"Testing universal notifiers with strategy %d", (int)strategy);
|
||||
universal_notifier_t *notifiers[16];
|
||||
size_t notifier_count = sizeof notifiers / sizeof *notifiers;
|
||||
|
||||
// Populate array of notifiers
|
||||
for (size_t i=0; i < notifier_count; i++)
|
||||
{
|
||||
notifiers[i] = universal_notifier_t::new_notifier_for_strategy(strategy, UVARS_TEST_PATH);
|
||||
}
|
||||
|
||||
// Nobody should poll yet
|
||||
for (size_t i=0; i < notifier_count; i++)
|
||||
{
|
||||
if (poll_notifier(notifiers[i]))
|
||||
{
|
||||
err(L"Universal variable notifier polled true before any changes, with strategy %d", (int)strategy);
|
||||
}
|
||||
}
|
||||
|
||||
// Tweak each notifier. Verify that others see it.
|
||||
for (size_t post_idx=0; post_idx < notifier_count; post_idx++)
|
||||
{
|
||||
notifiers[post_idx]->post_notification();
|
||||
|
||||
// Do special stuff to "trigger" a notification for testing
|
||||
trigger_or_wait_for_notification(notifiers[post_idx], strategy);
|
||||
|
||||
for (size_t i=0; i < notifier_count; i++)
|
||||
{
|
||||
// We aren't concerned with the one who posted
|
||||
// Poll from it (to drain it), and then skip it
|
||||
if (i == post_idx)
|
||||
{
|
||||
poll_notifier(notifiers[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! poll_notifier(notifiers[i]))
|
||||
{
|
||||
err(L"Universal variable notifier (%lu) %p polled failed to notice changes, with strategy %d", i, notifiers[i], (int)strategy);
|
||||
}
|
||||
}
|
||||
|
||||
// Named pipes have special cleanup requirements
|
||||
if (strategy == universal_notifier_t::strategy_named_pipe)
|
||||
{
|
||||
usleep(1000000 / 10); //corresponds to NAMED_PIPE_FLASH_DURATION_USEC
|
||||
// Have to clean up the posted one first, so that the others see the pipe become no longer readable
|
||||
poll_notifier(notifiers[post_idx]);
|
||||
for (size_t i=0; i < notifier_count; i++)
|
||||
{
|
||||
poll_notifier(notifiers[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Nobody should poll now
|
||||
for (size_t i=0; i < notifier_count; i++)
|
||||
{
|
||||
if (poll_notifier(notifiers[i]))
|
||||
{
|
||||
err(L"Universal variable notifier polled true after all changes, with strategy %d", (int)strategy);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up
|
||||
for (size_t i=0; i < notifier_count; i++)
|
||||
{
|
||||
delete notifiers[i];
|
||||
}
|
||||
}
|
||||
|
||||
#if HAVE_INOTIFY_INIT
|
||||
#define INOTIFY_TEST_PATH "/tmp/inotify_test.tmp"
|
||||
static bool test_basic_inotify_support()
|
||||
{
|
||||
bool inotify_works = true;
|
||||
int fd = inotify_init();
|
||||
if (fd < 0)
|
||||
{
|
||||
err(L"inotify_init failed");
|
||||
}
|
||||
|
||||
/* Mark fd as nonblocking */
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
if (flags == -1)
|
||||
{
|
||||
err(L"fcntl GETFL failed");
|
||||
}
|
||||
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
|
||||
{
|
||||
err(L"fcntl SETFL failed");
|
||||
}
|
||||
|
||||
if (system("touch " INOTIFY_TEST_PATH))
|
||||
{
|
||||
err(L"touch failed");
|
||||
}
|
||||
|
||||
/* Add file to watch list */
|
||||
int wd = inotify_add_watch(fd, INOTIFY_TEST_PATH, IN_DELETE | IN_DELETE_SELF);
|
||||
if (wd < 0)
|
||||
{
|
||||
err(L"inotify_add_watch failed: %s", strerror(errno));
|
||||
}
|
||||
|
||||
/* Delete file */
|
||||
if (system("rm " INOTIFY_TEST_PATH))
|
||||
{
|
||||
err(L"rm failed");
|
||||
}
|
||||
|
||||
/* Verify that file is deleted */
|
||||
struct stat statbuf;
|
||||
if (stat(INOTIFY_TEST_PATH, &statbuf) != -1 || errno != ENOENT)
|
||||
{
|
||||
err(L"File at path " INOTIFY_TEST_PATH " still exists after deleting it");
|
||||
}
|
||||
|
||||
/* The fd should be readable now or very shortly */
|
||||
struct timeval tv = {1, 0};
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(fd, &fds);
|
||||
int count = select(fd + 1, &fds, NULL, NULL, &tv);
|
||||
if (count == 0 || ! FD_ISSET(fd, &fds))
|
||||
{
|
||||
inotify_works = false;
|
||||
err(L"inotify file descriptor not readable. Is inotify busted?");
|
||||
struct utsname version = {};
|
||||
uname(&version);
|
||||
fprintf(stderr, "kernel version %s - %s\n", version.release, version.version);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "inotify seems OK\n");
|
||||
}
|
||||
|
||||
if (fd >= 0)
|
||||
{
|
||||
close(fd);
|
||||
}
|
||||
|
||||
return inotify_works;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void test_universal_notifiers()
|
||||
{
|
||||
if (system("mkdir -p /tmp/fish_uvars_test/ && touch /tmp/fish_uvars_test/varsfile.txt")) err(L"mkdir failed");
|
||||
test_notifiers_with_strategy(universal_notifier_t::strategy_shmem_polling);
|
||||
test_notifiers_with_strategy(universal_notifier_t::strategy_named_pipe);
|
||||
#if __APPLE__
|
||||
test_notifiers_with_strategy(universal_notifier_t::strategy_notifyd);
|
||||
#endif
|
||||
|
||||
// inotify test disabled pending investigation into why this fails on travis-ci
|
||||
// https://github.com/travis-ci/travis-ci/issues/2342
|
||||
#if 0
|
||||
#if HAVE_INOTIFY_INIT
|
||||
test_basic_inotify_support();
|
||||
test_notifiers_with_strategy(universal_notifier_t::strategy_inotify);
|
||||
#endif
|
||||
#endif
|
||||
if (system("rm -Rf /tmp/fish_uvars_test/")) err(L"rm failed");
|
||||
}
|
||||
|
||||
class history_tests_t
|
||||
{
|
||||
public:
|
||||
|
@ -3214,6 +3528,8 @@ int main(int argc, char **argv)
|
|||
if (should_test_function("colors")) test_colors();
|
||||
if (should_test_function("complete")) test_complete();
|
||||
if (should_test_function("input")) test_input();
|
||||
if (should_test_function("universal")) test_universal();
|
||||
if (should_test_function("universal_notifiers")) test_universal_notifiers();
|
||||
if (should_test_function("completion_insertions")) test_completion_insertions();
|
||||
if (should_test_function("autosuggestion_combining")) test_autosuggestion_combining();
|
||||
if (should_test_function("autosuggest_suggest_special")) test_autosuggest_suggest_special();
|
||||
|
|
117
fishd.cpp
117
fishd.cpp
|
@ -101,9 +101,6 @@ time the original barrier request was sent have been received.
|
|||
#define MSG_DONTWAIT 0
|
||||
#endif
|
||||
|
||||
/* Length of a MAC address */
|
||||
#define MAC_ADDRESS_MAX_LEN 6
|
||||
|
||||
/**
|
||||
Small greeting to show that fishd is running
|
||||
*/
|
||||
|
@ -270,119 +267,6 @@ static std::string gen_unique_nfs_filename(const std::string &filename)
|
|||
return newname;
|
||||
}
|
||||
|
||||
|
||||
/* Thanks to Jan Brittenson
|
||||
http://lists.apple.com/archives/xcode-users/2009/May/msg00062.html
|
||||
*/
|
||||
#ifdef SIOCGIFHWADDR
|
||||
|
||||
/* Linux */
|
||||
#include <net/if.h>
|
||||
static bool get_mac_address(unsigned char macaddr[MAC_ADDRESS_MAX_LEN], const char *interface = "eth0")
|
||||
{
|
||||
bool result = false;
|
||||
const int dummy = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (dummy >= 0)
|
||||
{
|
||||
struct ifreq r;
|
||||
strncpy((char *)r.ifr_name, interface, sizeof r.ifr_name - 1);
|
||||
r.ifr_name[sizeof r.ifr_name - 1] = 0;
|
||||
if (ioctl(dummy, SIOCGIFHWADDR, &r) >= 0)
|
||||
{
|
||||
memcpy(macaddr, r.ifr_hwaddr.sa_data, MAC_ADDRESS_MAX_LEN);
|
||||
result = true;
|
||||
}
|
||||
close(dummy);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#elif defined(HAVE_GETIFADDRS)
|
||||
|
||||
/* OS X and BSD */
|
||||
#include <ifaddrs.h>
|
||||
#include <net/if_dl.h>
|
||||
static bool get_mac_address(unsigned char macaddr[MAC_ADDRESS_MAX_LEN], const char *interface = "en0")
|
||||
{
|
||||
// BSD, Mac OS X
|
||||
struct ifaddrs *ifap;
|
||||
bool ok = false;
|
||||
|
||||
if (getifaddrs(&ifap) == 0)
|
||||
{
|
||||
for (const ifaddrs *p = ifap; p; p = p->ifa_next)
|
||||
{
|
||||
if (p->ifa_addr->sa_family == AF_LINK)
|
||||
{
|
||||
if (p->ifa_name && p->ifa_name[0] &&
|
||||
! strcmp((const char*)p->ifa_name, interface))
|
||||
{
|
||||
|
||||
const sockaddr_dl& sdl = *(sockaddr_dl*)p->ifa_addr;
|
||||
|
||||
size_t alen = sdl.sdl_alen;
|
||||
if (alen > MAC_ADDRESS_MAX_LEN) alen = MAC_ADDRESS_MAX_LEN;
|
||||
memcpy(macaddr, sdl.sdl_data + sdl.sdl_nlen, alen);
|
||||
ok = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
freeifaddrs(ifap);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
/* Unsupported */
|
||||
static bool get_mac_address(unsigned char macaddr[MAC_ADDRESS_MAX_LEN])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* Function to get an identifier based on the hostname */
|
||||
static bool get_hostname_identifier(std::string *result)
|
||||
{
|
||||
bool success = false;
|
||||
char hostname[HOSTNAME_LEN + 1] = {};
|
||||
if (gethostname(hostname, HOSTNAME_LEN) == 0)
|
||||
{
|
||||
result->assign(hostname);
|
||||
success = true;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
/* Get a sort of unique machine identifier. Prefer the MAC address; if that fails, fall back to the hostname; if that fails, pick something. */
|
||||
static std::string get_machine_identifier(void)
|
||||
{
|
||||
std::string result;
|
||||
unsigned char mac_addr[MAC_ADDRESS_MAX_LEN] = {};
|
||||
if (get_mac_address(mac_addr))
|
||||
{
|
||||
result.reserve(2 * MAC_ADDRESS_MAX_LEN);
|
||||
for (size_t i=0; i < MAC_ADDRESS_MAX_LEN; i++)
|
||||
{
|
||||
char buff[3];
|
||||
snprintf(buff, sizeof buff, "%02x", mac_addr[i]);
|
||||
result.append(buff);
|
||||
}
|
||||
}
|
||||
else if (get_hostname_identifier(&result))
|
||||
{
|
||||
/* Hooray */
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Fallback */
|
||||
result.assign("nohost");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
The number of milliseconds to wait between polls when attempting to acquire
|
||||
a lockfile
|
||||
|
@ -817,6 +701,7 @@ static std::string get_variables_file_path(const std::string &dir, const std::st
|
|||
return name;
|
||||
}
|
||||
|
||||
|
||||
static bool load_or_save_variables(bool save)
|
||||
{
|
||||
const wcstring wdir = fishd_get_config();
|
||||
|
|
20
history.cpp
20
history.cpp
|
@ -149,8 +149,6 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
static const file_id_t kInvalidFileID((dev_t)(-1), (ino_t)(-1));
|
||||
|
||||
/* Lock a file via fcntl; returns true on success, false on failure. */
|
||||
static bool history_file_lock(int fd, short type)
|
||||
{
|
||||
|
@ -162,20 +160,6 @@ static bool history_file_lock(int fd, short type)
|
|||
return ret != -1;
|
||||
}
|
||||
|
||||
/* Get a file_id_t corresponding to the given fd */
|
||||
static file_id_t history_file_identify(int fd)
|
||||
{
|
||||
file_id_t result = kInvalidFileID;
|
||||
struct stat buf = {};
|
||||
if (0 == fstat(fd, &buf))
|
||||
{
|
||||
result.first = buf.st_dev;
|
||||
result.second = buf.st_ino;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/* Our LRU cache is used for restricting the amount of history we have, and limiting how long we order it. */
|
||||
class history_lru_node_t : public lru_node_t
|
||||
{
|
||||
|
@ -1047,7 +1031,7 @@ bool history_t::map_file(const wcstring &name, const char **out_map_start, size_
|
|||
|
||||
/* Get the file ID if requested */
|
||||
if (file_id != NULL)
|
||||
*file_id = history_file_identify(fd);
|
||||
*file_id = file_id_for_fd(fd);
|
||||
|
||||
/* Take a read lock to guard against someone else appending. This is released when the file is closed (below). We will read the file after releasing the lock, but that's not a problem, because we never modify already written data. In short, the purpose of this lock is to ensure we don't see the file size change mid-update.
|
||||
|
||||
|
@ -1479,7 +1463,7 @@ bool history_t::save_internal_via_appending()
|
|||
if (out_fd >= 0)
|
||||
{
|
||||
/* Check to see if the file changed */
|
||||
if (history_file_identify(out_fd) != mmap_file_id)
|
||||
if (file_id_for_fd(out_fd) != mmap_file_id)
|
||||
file_changed = true;
|
||||
|
||||
/* Exclusive lock on the entire file. This is released when we close the file (below). This may fail on (e.g.) lockless NFS. If so, proceed as if it did not fail; the risk is that we may get interleaved history items, which is considered better than no history, or forcing everything through the slow copy-move mode. We try to minimize this possibility by writing with O_APPEND.
|
||||
|
|
|
@ -96,7 +96,7 @@ static wint_t readb()
|
|||
input_flush_callbacks();
|
||||
|
||||
fd_set fdset;
|
||||
int fd_max=0;
|
||||
int fd_max = 0;
|
||||
int ioport = iothread_port();
|
||||
int res;
|
||||
|
||||
|
@ -105,15 +105,36 @@ static wint_t readb()
|
|||
if (env_universal_server.fd > 0)
|
||||
{
|
||||
FD_SET(env_universal_server.fd, &fdset);
|
||||
if (fd_max < env_universal_server.fd) fd_max = env_universal_server.fd;
|
||||
fd_max = maxi(fd_max, env_universal_server.fd);
|
||||
}
|
||||
if (ioport > 0)
|
||||
{
|
||||
FD_SET(ioport, &fdset);
|
||||
if (fd_max < ioport) fd_max = ioport;
|
||||
fd_max = maxi(fd_max, ioport);
|
||||
}
|
||||
|
||||
res = select(fd_max + 1, &fdset, 0, 0, 0);
|
||||
|
||||
/* Get our uvar notifier */
|
||||
universal_notifier_t ¬ifier = universal_notifier_t::default_notifier();
|
||||
|
||||
/* Get the notification fd (possibly none) */
|
||||
int notifier_fd = notifier.notification_fd();
|
||||
if (notifier_fd > 0)
|
||||
{
|
||||
FD_SET(notifier_fd, &fdset);
|
||||
fd_max = maxi(fd_max, notifier_fd);
|
||||
}
|
||||
|
||||
/* Get its suggested delay (possibly none) */
|
||||
struct timeval tv = {};
|
||||
const unsigned long usecs_delay = notifier.usec_delay_between_polls();
|
||||
if (usecs_delay > 0)
|
||||
{
|
||||
unsigned long usecs_per_sec = 1000000;
|
||||
tv.tv_sec = (int)(usecs_delay / usecs_per_sec);
|
||||
tv.tv_usec = (int)(usecs_delay % usecs_per_sec);
|
||||
}
|
||||
|
||||
res = select(fd_max + 1, &fdset, 0, 0, usecs_delay > 0 ? &tv : NULL);
|
||||
if (res==-1)
|
||||
{
|
||||
switch (errno)
|
||||
|
@ -162,6 +183,18 @@ static wint_t readb()
|
|||
return lookahead_pop();
|
||||
}
|
||||
}
|
||||
|
||||
/* Check to see if we want a barrier */
|
||||
bool barrier_from_poll = notifier.poll();
|
||||
bool barrier_from_readability = false;
|
||||
if (notifier_fd > 0 && FD_ISSET(notifier_fd, &fdset))
|
||||
{
|
||||
barrier_from_readability = notifier.notification_fd_became_readable(notifier_fd);
|
||||
}
|
||||
if (barrier_from_poll || barrier_from_readability)
|
||||
{
|
||||
env_universal_barrier();
|
||||
}
|
||||
|
||||
if (ioport > 0 && FD_ISSET(ioport, &fdset))
|
||||
{
|
||||
|
|
|
@ -38,7 +38,12 @@ int iothread_perform(int (*handler)(T *), void (*completionCallback)(T *, int),
|
|||
return iothread_perform_base((int (*)(void *))handler, (void (*)(void *, int))completionCallback, static_cast<void *>(context));
|
||||
}
|
||||
|
||||
/** Helper templates */
|
||||
template<typename T>
|
||||
int iothread_perform(int (*handler)(T *), T *context)
|
||||
{
|
||||
return iothread_perform_base((int (*)(void *))handler, (void (*)(void *, int))0, static_cast<void *>(context));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
int iothread_perform_on_main(int (*handler)(T *), T *context)
|
||||
{
|
||||
|
|
|
@ -971,7 +971,7 @@ static int wildcard_expand_internal(const wchar_t *wc,
|
|||
// Insert a "file ID" into visited_files
|
||||
// If the insertion fails, we've already visited this file (i.e. a symlink loop)
|
||||
// If we're not recursive, insert anyways (in case we loop back around in a future recursive segment), but continue on; the idea being that literal path components should still work
|
||||
const file_id_t file_id(buf.st_dev, buf.st_ino);
|
||||
const file_id_t file_id = file_id_t::file_id_from_stat(&buf);
|
||||
if (S_ISDIR(buf.st_mode) && (visited_files.insert(file_id).second || ! is_recursive))
|
||||
{
|
||||
new_dir.push_back(L'/');
|
||||
|
|
114
wutil.cpp
114
wutil.cpp
|
@ -35,6 +35,8 @@
|
|||
|
||||
typedef std::string cstring;
|
||||
|
||||
const file_id_t kInvalidFileID = {-1, -1, -1, -1, -1, -1};
|
||||
|
||||
/**
|
||||
Minimum length of the internal covnersion buffers
|
||||
*/
|
||||
|
@ -246,46 +248,46 @@ int wopen_cloexec(const wcstring &pathname, int flags, mode_t mode)
|
|||
|
||||
int wcreat(const wcstring &pathname, mode_t mode)
|
||||
{
|
||||
cstring tmp = wcs2string(pathname);
|
||||
const cstring tmp = wcs2string(pathname);
|
||||
return creat(tmp.c_str(), mode);
|
||||
}
|
||||
|
||||
DIR *wopendir(const wcstring &name)
|
||||
{
|
||||
cstring tmp = wcs2string(name);
|
||||
const cstring tmp = wcs2string(name);
|
||||
return opendir(tmp.c_str());
|
||||
}
|
||||
|
||||
int wstat(const wcstring &file_name, struct stat *buf)
|
||||
{
|
||||
cstring tmp = wcs2string(file_name);
|
||||
const cstring tmp = wcs2string(file_name);
|
||||
return stat(tmp.c_str(), buf);
|
||||
}
|
||||
|
||||
int lwstat(const wcstring &file_name, struct stat *buf)
|
||||
{
|
||||
cstring tmp = wcs2string(file_name);
|
||||
const cstring tmp = wcs2string(file_name);
|
||||
return lstat(tmp.c_str(), buf);
|
||||
}
|
||||
|
||||
int waccess(const wcstring &file_name, int mode)
|
||||
{
|
||||
cstring tmp = wcs2string(file_name);
|
||||
const cstring tmp = wcs2string(file_name);
|
||||
return access(tmp.c_str(), mode);
|
||||
}
|
||||
|
||||
int wunlink(const wcstring &file_name)
|
||||
{
|
||||
cstring tmp = wcs2string(file_name);
|
||||
const cstring tmp = wcs2string(file_name);
|
||||
return unlink(tmp.c_str());
|
||||
}
|
||||
|
||||
void wperror(const wcstring &s)
|
||||
void wperror(const wchar_t *s)
|
||||
{
|
||||
int e = errno;
|
||||
if (!s.empty())
|
||||
if (s[0] != L'\0')
|
||||
{
|
||||
fwprintf(stderr, L"%ls: ", s.c_str());
|
||||
fwprintf(stderr, L"%ls: ", s);
|
||||
}
|
||||
fwprintf(stderr, L"%s\n", strerror(e));
|
||||
}
|
||||
|
@ -525,3 +527,97 @@ int fish_wcstoi(const wchar_t *str, wchar_t ** endptr, int base)
|
|||
}
|
||||
return (int)ret;
|
||||
}
|
||||
|
||||
file_id_t file_id_t::file_id_from_stat(const struct stat *buf)
|
||||
{
|
||||
assert(buf != NULL);
|
||||
|
||||
file_id_t result = {};
|
||||
result.device = buf->st_dev;
|
||||
result.inode = buf->st_ino;
|
||||
result.size = buf->st_size;
|
||||
result.change_seconds = buf->st_ctime;
|
||||
|
||||
#if STAT_HAVE_NSEC
|
||||
result.change_nanoseconds = buf->st_ctime_nsec;
|
||||
#elif defined(__APPLE__)
|
||||
result.change_nanoseconds = buf->st_ctimespec.tv_nsec;
|
||||
#elif defined(_BSD_SOURCE) || defined(_SVID_SOURCE) || defined(_XOPEN_SOURCE)
|
||||
result.change_nanoseconds = buf->st_ctim.tv_nsec;
|
||||
#else
|
||||
result.change_nanoseconds = 0;
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
|
||||
result.generation = buf->st_gen;
|
||||
#else
|
||||
result.generation = 0;
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
file_id_t file_id_for_fd(int fd)
|
||||
{
|
||||
file_id_t result = kInvalidFileID;
|
||||
struct stat buf = {};
|
||||
if (0 == fstat(fd, &buf))
|
||||
{
|
||||
result = file_id_t::file_id_from_stat(&buf);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
file_id_t file_id_for_path(const wcstring &path)
|
||||
{
|
||||
file_id_t result = kInvalidFileID;
|
||||
struct stat buf = {};
|
||||
if (0 == wstat(path, &buf))
|
||||
{
|
||||
result = file_id_t::file_id_from_stat(&buf);
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
bool file_id_t::operator==(const file_id_t &rhs) const
|
||||
{
|
||||
return device == rhs.device &&
|
||||
inode == rhs.inode &&
|
||||
size == rhs.size &&
|
||||
change_seconds == rhs.change_seconds &&
|
||||
change_nanoseconds == rhs.change_nanoseconds &&
|
||||
generation == rhs.generation;
|
||||
}
|
||||
|
||||
bool file_id_t::operator!=(const file_id_t &rhs) const
|
||||
{
|
||||
return ! (*this == rhs);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
int compare(T a, T b)
|
||||
{
|
||||
if (a < b)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (a > b)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool file_id_t::operator<(const file_id_t &rhs) const
|
||||
{
|
||||
/* Compare each field, stopping when we get to a non-equal field */
|
||||
int ret = 0;
|
||||
if (! ret) ret = compare(device, rhs.device);
|
||||
if (! ret) ret = compare(inode, rhs.inode);
|
||||
if (! ret) ret = compare(size, rhs.size);
|
||||
if (! ret) ret = compare(generation, rhs.generation);
|
||||
if (! ret) ret = compare(change_seconds, rhs.change_seconds);
|
||||
if (! ret) ret = compare(change_nanoseconds, rhs.change_nanoseconds);
|
||||
return ret < 0;
|
||||
}
|
||||
|
|
28
wutil.h
28
wutil.h
|
@ -16,6 +16,7 @@
|
|||
#include <stdarg.h>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <stdint.h>
|
||||
#include "common.h"
|
||||
|
||||
/**
|
||||
|
@ -84,7 +85,7 @@ int wunlink(const wcstring &pathname);
|
|||
/**
|
||||
Wide character version of perror().
|
||||
*/
|
||||
void wperror(const wcstring &s);
|
||||
void wperror(const wchar_t *s);
|
||||
|
||||
/**
|
||||
Async-safe version of perror().
|
||||
|
@ -158,8 +159,29 @@ int wrename(const wcstring &oldName, const wcstring &newName);
|
|||
/** Like wcstol(), but fails on a value outside the range of an int */
|
||||
int fish_wcstoi(const wchar_t *str, wchar_t ** endptr, int base);
|
||||
|
||||
/** Class for representing a file's inode. We use this to detect and avoid symlink loops, among other things. */
|
||||
typedef std::pair<dev_t, ino_t> file_id_t;
|
||||
/** Class for representing a file's inode. We use this to detect and avoid symlink loops, among other things. While an inode / dev pair is sufficient to distinguish co-existing files, Linux seems to aggressively re-use inodes, so it cannot determine if a file has been deleted (ABA problem). Therefore we include richer information. */
|
||||
struct file_id_t
|
||||
{
|
||||
dev_t device;
|
||||
ino_t inode;
|
||||
uint64_t size;
|
||||
time_t change_seconds;
|
||||
long change_nanoseconds;
|
||||
uint32_t generation;
|
||||
|
||||
bool operator==(const file_id_t &rhs) const;
|
||||
bool operator!=(const file_id_t &rhs) const;
|
||||
|
||||
// Used to permit these as keys in std::map
|
||||
bool operator<(const file_id_t &rhs) const;
|
||||
|
||||
static file_id_t file_id_from_stat(const struct stat *buf);
|
||||
};
|
||||
|
||||
file_id_t file_id_for_fd(int fd);
|
||||
file_id_t file_id_for_path(const wcstring &path);
|
||||
|
||||
extern const file_id_t kInvalidFileID;
|
||||
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue
Block a user