From 552d8f394e7aa11f44fff479a69e93b81a9b2376 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Tue, 8 Jan 2013 02:39:22 -0800 Subject: [PATCH] Make fishd base its variable files on the MAC address instead of hostname Fixes https://github.com/fish-shell/fish-shell/issues/183 --- builtin_test.cpp | 8 +- common.cpp | 5 +- configure.ac | 1 + env_universal_common.cpp | 2 +- fish_tests.cpp | 18 ++-- fishd.cpp | 225 ++++++++++++++++++++++++++++++++------- osx/config.h | 3 + reader.cpp | 2 +- 8 files changed, 206 insertions(+), 58 deletions(-) diff --git a/builtin_test.cpp b/builtin_test.cpp index 79251252d..a130ab993 100644 --- a/builtin_test.cpp +++ b/builtin_test.cpp @@ -580,7 +580,7 @@ expression *test_parser::parse_args(const wcstring_list_t &args, wcstring &err) append_format(err, L"test: unexpected argument at index %lu: '%ls'\n", (unsigned long)result->range.end, args.at(result->range.end).c_str()); } errored = true; - + delete result; result = NULL; } @@ -802,14 +802,14 @@ int builtin_test(parser_t &parser, wchar_t **argv) /* The first argument should be the name of the command ('test') */ if (! argv[0]) return BUILTIN_TEST_FAIL; - + /* Whether we are invoked with bracket '[' or not */ const bool is_bracket = ! wcscmp(argv[0], L"["); size_t argc = 0; while (argv[argc + 1]) argc++; - + /* If we're bracket, the last argument ought to be ]; we ignore it. Note that argc is the number of arguments after the command name; thus argv[argc] is the last argument. */ if (is_bracket) { @@ -825,7 +825,7 @@ int builtin_test(parser_t &parser, wchar_t **argv) } } - + /* Collect the arguments into a list */ const wcstring_list_t args(argv + 1, argv + 1 + argc); diff --git a/common.cpp b/common.cpp index 352074e78..e3b65e368 100644 --- a/common.cpp +++ b/common.cpp @@ -163,9 +163,8 @@ int fgetws2(wcstring *s, FILE *f) } /** - Converts the narrow character string \c in into it's wide - equivalent, stored in \c out. \c out must have enough space to fit - the entire string. + Converts the narrow character string \c in into its wide + equivalent, and return it The string may contain embedded nulls. diff --git a/configure.ac b/configure.ac index 262f5b3b9..6ff376520 100644 --- a/configure.ac +++ b/configure.ac @@ -720,6 +720,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 gettext ) AC_CHECK_FUNCS( dcgettext backtrace backtrace_symbols sysconf ) +AC_CHECK_FUNCS( getifaddrs ) # # The Makefile also needs to know if we have gettext, so it knows if diff --git a/env_universal_common.cpp b/env_universal_common.cpp index 0a8bcfaae..4f419bfeb 100644 --- a/env_universal_common.cpp +++ b/env_universal_common.cpp @@ -276,7 +276,7 @@ start_conversion: /* FreeBSD has this prototype: size_t iconv (iconv_t, const char **...) OS X and Linux this one: size_t iconv (iconv_t, char **...) AFAIK there's no single type that can be passed as both char ** and const char **. - So we cast the function pointer instead (!) + Hence this hack. */ nconv = hack_iconv(cd, &in, &in_len, &nout, &out_len); diff --git a/fish_tests.cpp b/fish_tests.cpp index 89e77a4f2..c35818ad2 100644 --- a/fish_tests.cpp +++ b/fish_tests.cpp @@ -839,7 +839,7 @@ static bool run_test_test(int expected, const wcstring &str) copy(istream_iterator >(iss), istream_iterator >(), back_inserter >(lst)); - + bool bracket = run_one_test_test(expected, lst, true); bool nonbracket = run_one_test_test(expected, lst, false); assert(bracket == nonbracket); @@ -850,13 +850,13 @@ static void test_test_brackets() { // Ensure [ knows it needs a ] parser_t parser(PARSER_TYPE_GENERAL, true); - + const wchar_t *argv1[] = {L"[", L"foo", NULL}; assert(builtin_test(parser, (wchar_t **)argv1) != 0); - + const wchar_t *argv2[] = {L"[", L"foo", L"]", NULL}; assert(builtin_test(parser, (wchar_t **)argv2) == 0); - + const wchar_t *argv3[] = {L"[", L"foo", L"]", L"bar", NULL}; assert(builtin_test(parser, (wchar_t **)argv3) != 0); @@ -918,11 +918,11 @@ static void test_test() /* We didn't properly handle multiple "just strings" either */ assert(run_test_test(0, L"foo")); assert(run_test_test(0, L"foo -a bar")); - + /* These should be errors */ assert(run_test_test(1, L"foo bar")); assert(run_test_test(1, L"foo bar baz")); - + /* This crashed */ assert(run_test_test(1, L"1 = 1 -a = 1")); } @@ -1029,13 +1029,13 @@ static void test_autosuggestion_combining() { say(L"Testing autosuggestion combining"); assert(combine_command_and_autosuggestion(L"alpha", L"alphabeta") == L"alphabeta"); - + // when the last token contains no capital letters, we use the case of the autosuggestion assert(combine_command_and_autosuggestion(L"alpha", L"ALPHABETA") == L"ALPHABETA"); - + // when the last token contains capital letters, we use its case assert(combine_command_and_autosuggestion(L"alPha", L"alphabeTa") == L"alPhabeTa"); - + // if autosuggestion is not longer than input, use the input's case assert(combine_command_and_autosuggestion(L"alpha", L"ALPHAA") == L"ALPHAA"); assert(combine_command_and_autosuggestion(L"alpha", L"ALPHA") == L"alpha"); diff --git a/fishd.cpp b/fishd.cpp index af5bc3069..6cfd02b31 100644 --- a/fishd.cpp +++ b/fishd.cpp @@ -52,6 +52,7 @@ time the original barrier request was sent have been received. #include #include #include +#include #include #include #include @@ -98,6 +99,9 @@ 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 */ @@ -109,7 +113,7 @@ time the original barrier request was sent have been received. #define SAVE_MSG "# This file is automatically generated by the fishd universal variable daemon.\n# Do NOT edit it directly, your changes will be overwritten.\n" /** - The name of the save file. The hostname is appended to this. + The name of the save file. The machine identifier is appended to this. */ #define FILE "fishd." @@ -223,9 +227,14 @@ static void sprint_rand_digits(char *str, int maxlen) and ignores errors returned by gettimeofday. Cast to unsigned so that wrapping occurs on overflow as per ANSI C. */ - (void)gettimeofday(&tv, NULL); - unsigned long long seed = tv.tv_sec + tv.tv_usec * 1000000ULL; - srand((unsigned int)seed); + static bool seeded = false; + if (! seeded) + { + (void)gettimeofday(&tv, NULL); + unsigned long long seed = tv.tv_sec + tv.tv_usec * 1000000ULL; + srand((unsigned int)seed); + seeded = true; + } max = (int)(1 + (maxlen - 1) * (rand() / (RAND_MAX + 1.0))); for (i = 0; i < max; i++) { @@ -235,6 +244,7 @@ static void sprint_rand_digits(char *str, int maxlen) } + /** Generate a filename unique in an NFS namespace by creating a copy of str and appending .{hostname}.{pid} to it. If gethostname() fails then a pseudo- @@ -264,6 +274,118 @@ static std::string gen_unique_nfs_filename(const char *filename) } +/* Thanks to Jan Brittenson + http://lists.apple.com/archives/xcode-users/2009/May/msg00062.html +*/ +#ifdef SIOCGIFHWADDR + +/* Linux */ +#include +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 +#include +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 @@ -661,53 +783,76 @@ static wcstring fishd_get_config() /** Load or save all variables */ -static void load_or_save(int save) +static bool load_or_save_variables_at_path(bool save, const std::string &path) { - const wcstring wdir = fishd_get_config(); - char hostname[HOSTNAME_LEN]; - connection_t c; - int fd; + bool result = false; - if (wdir.empty()) - return; + debug(4, L"Open file for %s: '%s'", + save?"saving":"loading", + path.c_str()); - std::string dir = wcs2string(wdir); + /* OK to not use CLO_EXEC here because fishd is single threaded */ + int fd = open(path.c_str(), save?(O_CREAT | O_TRUNC | O_WRONLY):O_RDONLY, 0600); + if (fd >= 0) + { + /* Success */ + result = true; + connection_t c = {}; + connection_init(&c, fd); - gethostname(hostname, HOSTNAME_LEN); + if (save) + { + /* Save to the file */ + write_loop(c.fd, SAVE_MSG, strlen(SAVE_MSG)); + enqueue_all(&c); + } + else + { + /* Read from the file */ + read_message(&c); + } + connection_destroy(&c); + } + return result; +} + + +static std::string get_variables_file_path(const std::string &dir, const std::string &identifier) +{ std::string name; name.append(dir); name.append("/"); name.append(FILE); - name.append(hostname); + name.append(identifier); + return name; +} - debug(4, L"Open file for %s: '%s'", - save?"saving":"loading", - name.c_str()); +static bool load_or_save_variables(bool save) +{ + const wcstring wdir = fishd_get_config(); + const std::string dir = wcs2string(wdir); + if (dir.empty()) + return false; - /* OK to not use CLO_EXEC here because fishd is single threaded */ - fd = open(name.c_str(), save?(O_CREAT | O_TRUNC | O_WRONLY):O_RDONLY, 0600); - - if (fd == -1) + const std::string machine_id = get_machine_identifier(); + const std::string machine_id_path = get_variables_file_path(dir, machine_id); + bool success = load_or_save_variables_at_path(save, machine_id_path); + if (! success && ! save && errno == ENOENT) { - debug(1, L"Could not open load/save file. No previous saves?"); - wperror(L"open"); - return; + /* We failed to load, because the file was not found. Older fish used the hostname only. Try *moving* the filename based on the hostname into place; if that succeeds try again. Silently "upgraded." */ + std::string hostname_id; + if (get_hostname_identifier(&hostname_id) && hostname_id != machine_id) + { + std::string hostname_path = get_variables_file_path(dir, hostname_id); + if (0 == rename(hostname_path.c_str(), machine_id_path.c_str())) + { + /* We renamed - try again */ + success = load_or_save_variables_at_path(save, machine_id_path); + } + } } - debug(4, L"File open on fd %d", c.fd); - - connection_init(&c, fd); - - if (save) - { - - write_loop(c.fd, SAVE_MSG, strlen(SAVE_MSG)); - enqueue_all(&c); - } - else - read_message(&c); - - connection_destroy(&c); + return success; } /** @@ -715,7 +860,7 @@ static void load_or_save(int save) */ static void load() { - load_or_save(0); + load_or_save_variables(false /* load, not save */); } /** @@ -723,7 +868,7 @@ static void load() */ static void save() { - load_or_save(1); + load_or_save_variables(true /* save, not load */); } /** diff --git a/osx/config.h b/osx/config.h index a72a8825d..7206cb380 100644 --- a/osx/config.h +++ b/osx/config.h @@ -34,6 +34,9 @@ /* Define to 1 if you have the `fwprintf' function. */ #define HAVE_FWPRINTF 1 +/* Define to 1 if you have the `getifaddrs' function. */ +#define HAVE_GETIFADDRS 1 + /* Define to 1 if you have the header file. */ #define HAVE_GETOPT_H 1 diff --git a/reader.cpp b/reader.cpp index 8b18287e3..3a91861a7 100644 --- a/reader.cpp +++ b/reader.cpp @@ -509,7 +509,7 @@ static void reader_repaint() // Combine the command and autosuggestion into one string wcstring full_line = combine_command_and_autosuggestion(data->command_line, data->autosuggestion); - + size_t len = full_line.size(); if (len < 1) len = 1;