diff --git a/src/env.cpp b/src/env.cpp index 4f8921cec..fe0104f1c 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -415,7 +415,7 @@ void env_init(const struct config_paths_t *paths, bool do_uvars, bool default_pa init_input(); // Complain about invalid config paths. - path_emit_config_directory_errors(vars); + path_emit_config_directory_messages(vars); if (do_uvars) { // Set up universal variables. The empty string means to use the default path. diff --git a/src/path.cpp b/src/path.cpp index 60f47119c..749c108f3 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -6,7 +6,12 @@ #include "path.h" #include +#include +#include #include +#if defined(__linux__) +#include +#endif #include #include @@ -100,6 +105,42 @@ bool path_is_executable(const std::string &path) { return true; } +/// \return 1 if the path is remote, 0 if local, -1 if unknown. +static int path_is_remote(const wcstring &path) { + std::string narrow = wcs2string(path); +#if defined(__linux__) + struct statfs buf {}; + if (statfs(narrow.c_str(), &buf) < 0) { + return -1; + } + // Linux has constants for these like NFS_SUPER_MAGIC, SMB_SUPER_MAGIC, CIFS_MAGIC_NUMBER but + // these are in varying headers. Simply hard code them. + // NOTE: The cast is necessary for 32-bit systems because of the 4-byte CIFS_MAGIC_NUMBER + switch (static_cast(buf.f_type)) { + case 0x6969: // NFS_SUPER_MAGIC + case 0x517B: // SMB_SUPER_MAGIC + case 0xFE534D42U: // SMB2_MAGIC_NUMBER - not in the manpage + case 0xFF534D42U: // CIFS_MAGIC_NUMBER + return 1; + default: + // Other FSes are assumed local. + return 0; + } +#elif defined(ST_LOCAL) + // ST_LOCAL is a flag to statvfs, which is itself standardized. + // In practice the only system to use this path is NetBSD. + struct statvfs buf {}; + if (statvfs(narrow.c_str(), &buf) < 0) return -1; + return (buf.f_flag & ST_LOCAL) ? 0 : 1; +#elif defined(MNT_LOCAL) + struct statfs buf {}; + if (statfs(narrow.c_str(), &buf) < 0) return -1; + return (buf.f_flags & MNT_LOCAL) ? 0 : 1; +#else + return -1; +#endif +} + wcstring_list_t path_get_paths(const wcstring &cmd, const environment_t &vars) { FLOGF(path, L"path_get_paths('%ls')", cmd.c_str()); wcstring_list_t paths; @@ -293,6 +334,7 @@ static int create_directory(const wcstring &d) { struct base_directory_t { wcstring path{}; /// the path where we attempted to create the directory. int err{0}; /// the error code if creating the directory failed, or 0 on success. + int is_remote{-1}; /// 1 if the directory is remote (e.g. NFS), 0 if local, -1 if unknown. bool used_xdg{false}; /// whether an XDG variable was used in resolving the directory. bool success() const { return err == 0; } @@ -326,6 +368,8 @@ static base_directory_t make_base_directory(const wcstring &xdg_var, result.err = errno; } else { result.err = 0; + // Need to append a trailing slash to check the contents of the directory, not its parent. + result.is_remote = path_is_remote(result.path + L'/'); } return result; } @@ -340,12 +384,15 @@ static const base_directory_t &get_config_directory() { return s_dir; } -void path_emit_config_directory_errors(env_stack_t &vars) { +void path_emit_config_directory_messages(env_stack_t &vars) { const auto &data = get_data_directory(); if (!data.success()) { maybe_issue_path_warning(L"data", _(L"Your history will not be saved."), data.used_xdg, L"XDG_DATA_HOME", data.path, data.err, vars); } + if (data.is_remote > 0) { + FLOG(path, "data path appears to be on a network volume"); + } const auto &config = get_config_directory(); if (!config.success()) { @@ -353,6 +400,9 @@ void path_emit_config_directory_errors(env_stack_t &vars) { config.used_xdg, L"XDG_CONFIG_HOME", config.path, config.err, vars); } + if (config.is_remote > 0) { + FLOG(path, "config path appears to be on a network volume"); + } } bool path_get_config(wcstring &path) { diff --git a/src/path.h b/src/path.h index b411050e2..596c904e0 100644 --- a/src/path.h +++ b/src/path.h @@ -32,7 +32,7 @@ bool path_get_data(wcstring &path); /// Emit any errors if config directories are missing. /// Use the given environment stack to ensure this only occurs once. class env_stack_t; -void path_emit_config_directory_errors(env_stack_t &vars); +void path_emit_config_directory_messages(env_stack_t &vars); /// Finds the full path of an executable. ///