mirror of
https://github.com/neovim/neovim.git
synced 2025-10-03 08:28:34 +00:00
feat: stdpath('run'), /tmp/nvim.user/ #18993
Problem:
- Since c57f6b28d7
#8519, sockets are created in ~/.local/… but XDG
spec says: "XDG_RUNTIME_DIR: Must be on the local filesystem", which
implies that XDG_STATE_DIR is potentially non-local.
- Not easy to inspect Nvim-created temp files (for debugging etc).
Solution:
- Store sockets in stdpath('run') ($XDG_RUNTIME_DIR).
- Establish "/tmp/nvim.user/" as the tempdir root shared by all Nvims.
- Make ok() actually useful.
- Introduce assert_nolog().
closes #3517
closes #17093
This commit is contained in:
@@ -1206,7 +1206,7 @@ void ex_diffpatch(exarg_T *eap)
|
||||
|| (os_chdir((char *)dirbuf) != 0)) {
|
||||
dirbuf[0] = NUL;
|
||||
} else {
|
||||
char *tempdir = (char *)vim_gettempdir();
|
||||
char *tempdir = vim_gettempdir();
|
||||
if (tempdir == NULL) {
|
||||
tempdir = "/tmp";
|
||||
}
|
||||
|
@@ -9719,6 +9719,8 @@ static void f_stdpath(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
rettv->vval.v_string = get_xdg_home(kXDGStateHome);
|
||||
} else if (strequal(p, "log")) {
|
||||
rettv->vval.v_string = get_xdg_home(kXDGStateHome);
|
||||
} else if (strequal(p, "run")) {
|
||||
rettv->vval.v_string = stdpaths_get_xdg_var(kXDGRuntimeDir);
|
||||
} else if (strequal(p, "config_dirs")) {
|
||||
get_xdg_var_list(kXDGConfigDirs, rettv);
|
||||
} else if (strequal(p, "data_dirs")) {
|
||||
|
@@ -5281,45 +5281,80 @@ void forward_slash(char_u *fname)
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Name of Vim's own temp dir. Ends in a slash.
|
||||
static char_u *vim_tempdir = NULL;
|
||||
/// Path to Nvim's own temp dir. Ends in a slash.
|
||||
static char *vim_tempdir = NULL;
|
||||
|
||||
/// Create a directory for private use by this instance of Neovim.
|
||||
/// This is done once, and the same directory is used for all temp files.
|
||||
/// Creates a directory for private use by this instance of Nvim, trying each of
|
||||
/// `TEMP_DIR_NAMES` until one succeeds.
|
||||
///
|
||||
/// Only done once, the same directory is used for all temp files.
|
||||
/// This method avoids security problems because of symlink attacks et al.
|
||||
/// It's also a bit faster, because we only need to check for an existing
|
||||
/// file when creating the directory and not for each temp file.
|
||||
static void vim_maketempdir(void)
|
||||
static void vim_mktempdir(void)
|
||||
{
|
||||
static const char *temp_dirs[] = TEMP_DIR_NAMES;
|
||||
// Try the entries in `TEMP_DIR_NAMES` to create the temp directory.
|
||||
char_u template[TEMP_FILE_PATH_MAXLEN];
|
||||
char_u path[TEMP_FILE_PATH_MAXLEN];
|
||||
static const char *temp_dirs[] = TEMP_DIR_NAMES; // Try each of these until one succeeds.
|
||||
char tmp[TEMP_FILE_PATH_MAXLEN];
|
||||
char path[TEMP_FILE_PATH_MAXLEN];
|
||||
char user[40] = { 0 };
|
||||
|
||||
(void)os_get_username(user, sizeof(user));
|
||||
|
||||
// Make sure the umask doesn't remove the executable bit.
|
||||
// "repl" has been reported to use "0177".
|
||||
mode_t umask_save = umask(0077);
|
||||
for (size_t i = 0; i < ARRAY_SIZE(temp_dirs); i++) {
|
||||
// Expand environment variables, leave room for "/nvimXXXXXX/999999999"
|
||||
expand_env((char_u *)temp_dirs[i], template, TEMP_FILE_PATH_MAXLEN - 22);
|
||||
if (!os_isdir(template)) { // directory doesn't exist
|
||||
// Expand environment variables, leave room for "/tmp/nvim.<user>/XXXXXX/999999999".
|
||||
expand_env((char_u *)temp_dirs[i], (char_u *)tmp, TEMP_FILE_PATH_MAXLEN - 64);
|
||||
if (!os_isdir((char_u *)tmp)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
add_pathsep((char *)template);
|
||||
// Concatenate with temporary directory name pattern
|
||||
STRCAT(template, "nvimXXXXXX");
|
||||
// "/tmp/" exists, now try to create "/tmp/nvim.<user>/".
|
||||
add_pathsep(tmp);
|
||||
xstrlcat(tmp, "nvim.", sizeof(tmp));
|
||||
xstrlcat(tmp, user, sizeof(tmp));
|
||||
(void)os_mkdir(tmp, 0700); // Always create, to avoid a race.
|
||||
bool owned = os_file_owned(tmp);
|
||||
bool isdir = os_isdir((char_u *)tmp);
|
||||
#ifdef UNIX
|
||||
int perm = os_getperm(tmp); // XDG_RUNTIME_DIR must be owned by the user, mode 0700.
|
||||
bool valid = isdir && owned && 0700 == (perm & 0777);
|
||||
#else
|
||||
bool valid = isdir && owned; // TODO(justinmk): Windows ACL?
|
||||
#endif
|
||||
if (valid) {
|
||||
add_pathsep(tmp);
|
||||
} else {
|
||||
if (!owned) {
|
||||
ELOG("tempdir root not owned by current user (%s): %s", user, tmp);
|
||||
} else if (!isdir) {
|
||||
ELOG("tempdir root not a directory: %s", tmp);
|
||||
}
|
||||
#ifdef UNIX
|
||||
if (0700 != (perm & 0777)) {
|
||||
ELOG("tempdir root has invalid permissions (%o): %s", perm, tmp);
|
||||
}
|
||||
#endif
|
||||
// If our "root" tempdir is invalid or fails, proceed without "<user>/".
|
||||
// Else user1 could break user2 by creating "/tmp/nvim.user2/".
|
||||
tmp[strlen(tmp) - strlen(user)] = '\0';
|
||||
}
|
||||
|
||||
if (os_mkdtemp((const char *)template, (char *)path) != 0) {
|
||||
// Now try to create "/tmp/nvim.<user>/XXXXXX".
|
||||
xstrlcat(tmp, "XXXXXX", sizeof(tmp)); // mkdtemp "template", will be replaced with random alphanumeric chars.
|
||||
int r = os_mkdtemp(tmp, path);
|
||||
if (r != 0) {
|
||||
WLOG("tempdir create failed: %s: %s", os_strerror(r), tmp);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (vim_settempdir((char *)path)) {
|
||||
if (vim_settempdir(path)) {
|
||||
// Successfully created and set temporary directory so stop trying.
|
||||
break;
|
||||
} else {
|
||||
// Couldn't set `vim_tempdir` to `path` so remove created directory.
|
||||
os_rmdir((char *)path);
|
||||
os_rmdir(path);
|
||||
}
|
||||
}
|
||||
(void)umask(umask_save);
|
||||
@@ -5415,26 +5450,27 @@ void vim_deltempdir(void)
|
||||
{
|
||||
if (vim_tempdir != NULL) {
|
||||
// remove the trailing path separator
|
||||
path_tail((char *)vim_tempdir)[-1] = NUL;
|
||||
delete_recursive((const char *)vim_tempdir);
|
||||
path_tail(vim_tempdir)[-1] = NUL;
|
||||
delete_recursive(vim_tempdir);
|
||||
XFREE_CLEAR(vim_tempdir);
|
||||
}
|
||||
}
|
||||
|
||||
/// @return the name of temp directory. This directory would be created on the first
|
||||
/// call to this function.
|
||||
char_u *vim_gettempdir(void)
|
||||
/// Gets path to Nvim's own temp dir (ending with slash).
|
||||
///
|
||||
/// Creates the directory on the first call.
|
||||
char *vim_gettempdir(void)
|
||||
{
|
||||
if (vim_tempdir == NULL) {
|
||||
vim_maketempdir();
|
||||
vim_mktempdir();
|
||||
}
|
||||
|
||||
return vim_tempdir;
|
||||
}
|
||||
|
||||
/// Set Neovim own temporary directory name to `tempdir`. This directory should
|
||||
/// be already created. Expand this name to a full path and put it in
|
||||
/// `vim_tempdir`. This avoids that using `:cd` would confuse us.
|
||||
/// Sets Nvim's own temporary directory name to `tempdir`. This directory must
|
||||
/// already exist. Expands the name to a full path and put it in `vim_tempdir`.
|
||||
/// This avoids that using `:cd` would confuse us.
|
||||
///
|
||||
/// @param tempdir must be no longer than MAXPATHL.
|
||||
///
|
||||
@@ -5447,7 +5483,7 @@ static bool vim_settempdir(char *tempdir)
|
||||
}
|
||||
vim_FullName(tempdir, buf, MAXPATHL, false);
|
||||
add_pathsep(buf);
|
||||
vim_tempdir = (char_u *)xstrdup(buf);
|
||||
vim_tempdir = xstrdup(buf);
|
||||
xfree(buf);
|
||||
return true;
|
||||
}
|
||||
@@ -5456,14 +5492,14 @@ static bool vim_settempdir(char *tempdir)
|
||||
///
|
||||
/// @note The temp file is NOT created.
|
||||
///
|
||||
/// @return pointer to the temp file name or NULL if Neovim can't create
|
||||
/// @return pointer to the temp file name or NULL if Nvim can't create
|
||||
/// temporary directory for its own temporary files.
|
||||
char_u *vim_tempname(void)
|
||||
{
|
||||
// Temp filename counter.
|
||||
static uint64_t temp_count;
|
||||
|
||||
char_u *tempdir = vim_gettempdir();
|
||||
char *tempdir = vim_gettempdir();
|
||||
if (!tempdir) {
|
||||
return NULL;
|
||||
}
|
||||
|
@@ -2508,7 +2508,7 @@ bool mch_print_begin(prt_settings_T *psettings)
|
||||
*/
|
||||
prt_dsc_start();
|
||||
prt_dsc_textline("Title", (char *)psettings->jobname);
|
||||
if (os_get_user_name(buffer, 256) == FAIL) {
|
||||
if (os_get_username(buffer, 256) == FAIL) {
|
||||
STRCPY(buffer, "Unknown");
|
||||
}
|
||||
prt_dsc_textline("For", buffer);
|
||||
|
@@ -2016,14 +2016,14 @@ static void source_startup_scripts(const mparm_T *const parmp)
|
||||
// do_user_initialization.
|
||||
#if defined(UNIX)
|
||||
// If vimrc file is not owned by user, set 'secure' mode.
|
||||
if (!file_owned(VIMRC_FILE))
|
||||
if (!os_file_owned(VIMRC_FILE)) // NOLINT(readability/braces)
|
||||
#endif
|
||||
secure = p_secure;
|
||||
|
||||
if (do_source(VIMRC_FILE, true, DOSO_VIMRC) == FAIL) {
|
||||
#if defined(UNIX)
|
||||
// if ".exrc" is not owned by user set 'secure' mode
|
||||
if (!file_owned(EXRC_FILE)) {
|
||||
if (!os_file_owned(EXRC_FILE)) {
|
||||
secure = p_secure;
|
||||
} else {
|
||||
secure = 0;
|
||||
@@ -2068,23 +2068,6 @@ static int execute_env(char *env)
|
||||
return FAIL;
|
||||
}
|
||||
|
||||
#ifdef UNIX
|
||||
/// Checks if user owns file.
|
||||
/// Use both uv_fs_stat() and uv_fs_lstat() through os_fileinfo() and
|
||||
/// os_fileinfo_link() respectively for extra security.
|
||||
static bool file_owned(const char *fname)
|
||||
{
|
||||
assert(fname != NULL);
|
||||
uid_t uid = getuid();
|
||||
FileInfo file_info;
|
||||
bool file_owned = os_fileinfo(fname, &file_info)
|
||||
&& file_info.stat.st_uid == uid;
|
||||
bool link_owned = os_fileinfo_link(fname, &file_info)
|
||||
&& file_info.stat.st_uid == uid;
|
||||
return file_owned && link_owned;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Prints the following then exits:
|
||||
/// - An error message `errstr`
|
||||
/// - A string `str` if not null
|
||||
|
@@ -311,7 +311,7 @@ int ml_open(buf_T *buf)
|
||||
b0p->b0_dirty = buf->b_changed ? B0_DIRTY : 0;
|
||||
b0p->b0_flags = get_fileformat(buf) + 1;
|
||||
set_b0_fname(b0p, buf);
|
||||
(void)os_get_user_name((char *)b0p->b0_uname, B0_UNAME_SIZE);
|
||||
(void)os_get_username((char *)b0p->b0_uname, B0_UNAME_SIZE);
|
||||
b0p->b0_uname[B0_UNAME_SIZE - 1] = NUL;
|
||||
os_get_hostname((char *)b0p->b0_hname, B0_HNAME_SIZE);
|
||||
b0p->b0_hname[B0_HNAME_SIZE - 1] = NUL;
|
||||
@@ -669,7 +669,7 @@ static void set_b0_fname(ZERO_BL *b0p, buf_T *buf)
|
||||
B0_FNAME_SIZE_CRYPT, true);
|
||||
if (b0p->b0_fname[0] == '~') {
|
||||
// If there is no user name or it is too long, don't use "~/"
|
||||
int retval = os_get_user_name(uname, B0_UNAME_SIZE);
|
||||
int retval = os_get_username(uname, B0_UNAME_SIZE);
|
||||
size_t ulen = STRLEN(uname);
|
||||
size_t flen = STRLEN(b0p->b0_fname);
|
||||
if (retval == FAIL || ulen + flen > B0_FNAME_SIZE_CRYPT - 1) {
|
||||
|
@@ -89,7 +89,7 @@ void server_teardown(void)
|
||||
///
|
||||
/// Named pipe format:
|
||||
/// - Windows: "\\.\pipe\<name>.<pid>.<counter>"
|
||||
/// - Other: "~/.local/state/nvim/<name>.<pid>.<counter>"
|
||||
/// - Other: "/tmp/nvim.user/xxx/<name>.<pid>.<counter>"
|
||||
char *server_address_new(const char *name)
|
||||
{
|
||||
static uint32_t count = 0;
|
||||
@@ -98,7 +98,7 @@ char *server_address_new(const char *name)
|
||||
int r = snprintf(fmt, sizeof(fmt), "\\\\.\\pipe\\%s.%" PRIu64 ".%" PRIu32,
|
||||
name ? name : "nvim", os_get_pid(), count++);
|
||||
#else
|
||||
char *dir = get_xdg_home(kXDGStateHome);
|
||||
char *dir = stdpaths_get_xdg_var(kXDGRuntimeDir);
|
||||
int r = snprintf(fmt, sizeof(fmt), "%s/%s.%" PRIu64 ".%" PRIu32,
|
||||
dir, name ? name : "nvim", os_get_pid(), count++);
|
||||
xfree(dir);
|
||||
|
@@ -663,7 +663,7 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo
|
||||
// Get the user directory. If this fails the shell is used to expand
|
||||
// ~user, which is slower and may fail on old versions of /bin/sh.
|
||||
var = (*dst == NUL) ? NULL
|
||||
: (char_u *)os_get_user_directory((char *)dst + 1);
|
||||
: (char_u *)os_get_userdir((char *)dst + 1);
|
||||
mustfree = true;
|
||||
if (var == NULL) {
|
||||
expand_T xpc;
|
||||
|
@@ -126,7 +126,7 @@ bool os_isrealdir(const char *name)
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the given path is a directory or not.
|
||||
/// Check if the given path exists and is a directory.
|
||||
///
|
||||
/// @return `true` if `name` is a directory.
|
||||
bool os_isdir(const char_u *name)
|
||||
@@ -791,6 +791,27 @@ int os_setperm(const char *const name, int perm)
|
||||
return (r == kLibuvSuccess ? OK : FAIL);
|
||||
}
|
||||
|
||||
#ifdef UNIX
|
||||
/// Checks if the current user owns a file.
|
||||
///
|
||||
/// Uses both uv_fs_stat() and uv_fs_lstat() via os_fileinfo() and
|
||||
/// os_fileinfo_link() respectively for extra security.
|
||||
bool os_file_owned(const char *fname)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
uid_t uid = getuid();
|
||||
FileInfo finfo;
|
||||
bool file_owned = os_fileinfo(fname, &finfo) && finfo.stat.st_uid == uid;
|
||||
bool link_owned = os_fileinfo_link(fname, &finfo) && finfo.stat.st_uid == uid;
|
||||
return file_owned && link_owned;
|
||||
}
|
||||
#else
|
||||
bool os_file_owned(const char *fname)
|
||||
{
|
||||
return true; // TODO(justinmk): Windows. #8244
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Changes the owner and group of a file, like chown(2).
|
||||
///
|
||||
/// @return 0 on success, or libuv error code on failure.
|
||||
|
@@ -4,6 +4,7 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "nvim/ascii.h"
|
||||
#include "nvim/fileio.h"
|
||||
#include "nvim/memory.h"
|
||||
#include "nvim/os/os.h"
|
||||
#include "nvim/os/stdpaths_defs.h"
|
||||
@@ -26,7 +27,7 @@ static const char *const xdg_defaults_env_vars[] = {
|
||||
[kXDGDataHome] = "LOCALAPPDATA",
|
||||
[kXDGCacheHome] = "TEMP",
|
||||
[kXDGStateHome] = "LOCALAPPDATA",
|
||||
[kXDGRuntimeDir] = NULL,
|
||||
[kXDGRuntimeDir] = NULL, // Decided by vim_mktempdir().
|
||||
[kXDGConfigDirs] = NULL,
|
||||
[kXDGDataDirs] = NULL,
|
||||
};
|
||||
@@ -41,7 +42,7 @@ static const char *const xdg_defaults[] = {
|
||||
[kXDGDataHome] = "~\\AppData\\Local",
|
||||
[kXDGCacheHome] = "~\\AppData\\Local\\Temp",
|
||||
[kXDGStateHome] = "~\\AppData\\Local",
|
||||
[kXDGRuntimeDir] = NULL,
|
||||
[kXDGRuntimeDir] = NULL, // Decided by vim_mktempdir().
|
||||
[kXDGConfigDirs] = NULL,
|
||||
[kXDGDataDirs] = NULL,
|
||||
#else
|
||||
@@ -49,7 +50,7 @@ static const char *const xdg_defaults[] = {
|
||||
[kXDGDataHome] = "~/.local/share",
|
||||
[kXDGCacheHome] = "~/.cache",
|
||||
[kXDGStateHome] = "~/.local/state",
|
||||
[kXDGRuntimeDir] = NULL,
|
||||
[kXDGRuntimeDir] = NULL, // Decided by vim_mktempdir().
|
||||
[kXDGConfigDirs] = "/etc/xdg/",
|
||||
[kXDGDataDirs] = "/usr/local/share/:/usr/share/",
|
||||
#endif
|
||||
@@ -83,6 +84,11 @@ char *stdpaths_get_xdg_var(const XDGVarType idx)
|
||||
ret = xstrdup(env_val);
|
||||
} else if (fallback) {
|
||||
ret = expand_env_save((char *)fallback);
|
||||
} else if (idx == kXDGRuntimeDir) {
|
||||
// Special-case: stdpath('run') is defined at startup.
|
||||
ret = vim_gettempdir();
|
||||
size_t len = strlen(ret);
|
||||
ret = xstrndup(ret, len >= 2 ? len - 1 : 0); // Trim trailing slash.
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
@@ -112,9 +112,13 @@ int os_get_usernames(garray_T *users)
|
||||
return OK;
|
||||
}
|
||||
|
||||
// Insert user name in s[len].
|
||||
// Return OK if a name found.
|
||||
int os_get_user_name(char *s, size_t len)
|
||||
/// Gets the username that owns the current Nvim process.
|
||||
///
|
||||
/// @param s[out] Username.
|
||||
/// @param len Length of `s`.
|
||||
///
|
||||
/// @return OK if a name found.
|
||||
int os_get_username(char *s, size_t len)
|
||||
{
|
||||
#ifdef UNIX
|
||||
return os_get_uname((uv_uid_t)getuid(), s, len);
|
||||
@@ -124,9 +128,13 @@ int os_get_user_name(char *s, size_t len)
|
||||
#endif
|
||||
}
|
||||
|
||||
// Insert user name for "uid" in s[len].
|
||||
// Return OK if a name found.
|
||||
// If the name is not found, write the uid into s[len] and return FAIL.
|
||||
/// Gets the username associated with `uid`.
|
||||
///
|
||||
/// @param uid User id.
|
||||
/// @param s[out] Username, or `uid` on failure.
|
||||
/// @param len Length of `s`.
|
||||
///
|
||||
/// @return OK if a username was found, else FAIL.
|
||||
int os_get_uname(uv_uid_t uid, char *s, size_t len)
|
||||
{
|
||||
#if defined(HAVE_PWD_H) && defined(HAVE_GETPWUID)
|
||||
@@ -142,10 +150,10 @@ int os_get_uname(uv_uid_t uid, char *s, size_t len)
|
||||
return FAIL; // a number is not a name
|
||||
}
|
||||
|
||||
// Returns the user directory for the given username.
|
||||
// The caller has to free() the returned string.
|
||||
// If the username is not found, NULL is returned.
|
||||
char *os_get_user_directory(const char *name)
|
||||
/// Gets the user directory for the given username, or NULL on failure.
|
||||
///
|
||||
/// Caller must free() the returned string.
|
||||
char *os_get_userdir(const char *name)
|
||||
{
|
||||
#if defined(HAVE_GETPWNAM) && defined(HAVE_PWD_H)
|
||||
if (name == NULL || *name == NUL) {
|
||||
|
Reference in New Issue
Block a user