aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/os/env.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/os/env.c')
-rw-r--r--src/nvim/os/env.c598
1 files changed, 598 insertions, 0 deletions
diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c
index b2e62453c5..3bea2908d5 100644
--- a/src/nvim/os/env.c
+++ b/src/nvim/os/env.c
@@ -1,11 +1,23 @@
// env.c -- environment variable access
+#include <assert.h>
+
#include <uv.h>
+// vim.h must be included before charset.h (and possibly others) or things
+// blow up
+#include "nvim/vim.h"
#include "nvim/ascii.h"
+#include "nvim/charset.h"
#include "nvim/os/os.h"
+#include "nvim/memory.h"
+#include "nvim/message.h"
#include "nvim/misc2.h"
+#include "nvim/path.h"
#include "nvim/strings.h"
+#include "nvim/eval.h"
+#include "nvim/ex_getln.h"
+#include "nvim/version.h"
#ifdef HAVE__NSGETENVIRON
#include <crt_externs.h>
@@ -87,3 +99,589 @@ void os_get_hostname(char *hostname, size_t len)
#endif
}
+/// To get the "real" home directory:
+/// - get value of $HOME
+/// For Unix:
+/// - go to that directory
+/// - do os_dirname() to get the real name of that directory.
+/// This also works with mounts and links.
+/// Don't do this for MS-DOS, it will change the "current dir" for a drive.
+static char_u *homedir = NULL;
+
+void init_homedir(void)
+{
+ char_u *var;
+
+ /* In case we are called a second time (when 'encoding' changes). */
+ free(homedir);
+ homedir = NULL;
+
+ var = (char_u *)os_getenv("HOME");
+
+ if (var != NULL && *var == NUL) /* empty is same as not set */
+ var = NULL;
+
+
+ if (var != NULL) {
+#ifdef UNIX
+ /*
+ * Change to the directory and get the actual path. This resolves
+ * links. Don't do it when we can't return.
+ */
+ if (os_dirname(NameBuff, MAXPATHL) == OK
+ && os_chdir((char *)NameBuff) == 0) {
+ if (!os_chdir((char *)var) && os_dirname(IObuff, IOSIZE) == OK)
+ var = IObuff;
+ if (os_chdir((char *)NameBuff) != 0)
+ EMSG(_(e_prev_dir));
+ }
+#endif
+ homedir = vim_strsave(var);
+ }
+}
+
+#if defined(EXITFREE)
+
+void free_homedir(void)
+{
+ free(homedir);
+}
+
+#endif
+
+/// Call expand_env() and store the result in an allocated string.
+/// This is not very memory efficient, this expects the result to be freed
+/// again soon.
+/// @param src String containing environment variables to expand
+/// @see {expand_env}
+char_u *expand_env_save(char_u *src)
+{
+ return expand_env_save_opt(src, false);
+}
+
+/// Similar to expand_env_save() but when "one" is `true` handle the string as
+/// one file name, i.e. only expand "~" at the start.
+/// @param src String containing environment variables to expand
+/// @param one Should treat as only one file name
+/// @see {expand_env}
+char_u *expand_env_save_opt(char_u *src, bool one)
+{
+ char_u *p = xmalloc(MAXPATHL);
+ expand_env_esc(src, p, MAXPATHL, false, one, NULL);
+ return p;
+}
+
+/// Expand environment variable with path name.
+/// "~/" is also expanded, using $HOME. For Unix "~user/" is expanded.
+/// Skips over "\ ", "\~" and "\$" (not for Win32 though).
+/// If anything fails no expansion is done and dst equals src.
+/// @param src Input string e.g. "$HOME/vim.hlp"
+/// @param dst Where to put the result
+/// @param dstlen Maximum length of the result
+void expand_env(char_u *src, char_u *dst, int dstlen)
+{
+ expand_env_esc(src, dst, dstlen, false, false, NULL);
+}
+
+/// Expand environment variable with path name and escaping.
+/// "~/" is also expanded, using $HOME. For Unix "~user/" is expanded.
+/// Skips over "\ ", "\~" and "\$" (not for Win32 though).
+/// If anything fails no expansion is done and dst equals src.
+/// startstr recognize the start of a new name, for '~' expansion.
+/// @param srcp Input string e.g. "$HOME/vim.hlp"
+/// @param dst Where to put the result
+/// @param dstlen Maximum length of the result
+/// @param esc Should we escape spaces in expanded variables?
+/// @param one Should we expand more than one '~'?
+/// @param startstr Common prefix for paths, can be NULL
+void expand_env_esc(char_u *srcp, char_u *dst, int dstlen, bool esc, bool one,
+ char_u *startstr)
+{
+ char_u *src;
+ char_u *tail;
+ int c;
+ char_u *var;
+ bool copy_char;
+ bool mustfree; /* var was allocated, need to free it later */
+ bool at_start = true; /* at start of a name */
+ int startstr_len = 0;
+
+ if (startstr != NULL)
+ startstr_len = (int)STRLEN(startstr);
+
+ src = skipwhite(srcp);
+ --dstlen; /* leave one char space for "\," */
+ while (*src && dstlen > 0) {
+ copy_char = true;
+ if ((*src == '$') || (*src == '~' && at_start)) {
+ mustfree = false;
+
+ /*
+ * The variable name is copied into dst temporarily, because it may
+ * be a string in read-only memory and a NUL needs to be appended.
+ */
+ if (*src != '~') { /* environment var */
+ tail = src + 1;
+ var = dst;
+ c = dstlen - 1;
+
+#ifdef UNIX
+ /* Unix has ${var-name} type environment vars */
+ if (*tail == '{' && !vim_isIDc('{')) {
+ tail++; /* ignore '{' */
+ while (c-- > 0 && *tail && *tail != '}')
+ *var++ = *tail++;
+ } else // NOLINT
+#endif
+ {
+ while (c-- > 0 && *tail != NUL && vim_isIDc(*tail)) {
+ *var++ = *tail++;
+ }
+ }
+
+#if defined(UNIX)
+ // Verify that we have found the end of a UNIX ${VAR} style variable
+ if (src[1] == '{' && *tail != '}') {
+ var = NULL;
+ } else if (src[1] == '{') {
+ ++tail;
+ }
+#elif defined(MSWIN)
+ // Verify that we have found the end of a Windows %VAR% style variable
+ if (src[0] == '%' && *tail != '%') {
+ var = NULL;
+ } else if (src[0] == '%') {
+ ++tail;
+ }
+#endif
+ *var = NUL;
+ var = vim_getenv(dst, &mustfree);
+ } else if ( src[1] == NUL /* home directory */
+ || vim_ispathsep(src[1])
+ || vim_strchr((char_u *)" ,\t\n", src[1]) != NULL) {
+ var = homedir;
+ tail = src + 1;
+ } else { /* user directory */
+#if defined(UNIX)
+ /*
+ * Copy ~user to dst[], so we can put a NUL after it.
+ */
+ tail = src;
+ var = dst;
+ c = dstlen - 1;
+ while ( c-- > 0
+ && *tail
+ && vim_isfilec(*tail)
+ && !vim_ispathsep(*tail))
+ *var++ = *tail++;
+ *var = NUL;
+ /*
+ * Use os_get_user_directory() to get the user directory.
+ * If this function fails, the shell is used to
+ * expand ~user. This is slower and may fail if the shell
+ * does not support ~user (old versions of /bin/sh).
+ */
+ var = (char_u *)os_get_user_directory((char *)dst + 1);
+ mustfree = true;
+ if (var == NULL)
+ {
+ expand_T xpc;
+
+ ExpandInit(&xpc);
+ xpc.xp_context = EXPAND_FILES;
+ var = ExpandOne(&xpc, dst, NULL,
+ WILD_ADD_SLASH|WILD_SILENT, WILD_EXPAND_FREE);
+ mustfree = true;
+ }
+#else
+ /* cannot expand user's home directory, so don't try */
+ var = NULL;
+ tail = (char_u *)""; /* for gcc */
+#endif /* UNIX */
+ }
+
+#ifdef BACKSLASH_IN_FILENAME
+ /* If 'shellslash' is set change backslashes to forward slashes.
+ * Can't use slash_adjust(), p_ssl may be set temporarily. */
+ if (p_ssl && var != NULL && vim_strchr(var, '\\') != NULL) {
+ char_u *p = vim_strsave(var);
+
+ if (mustfree) {
+ free(var);
+ }
+ var = p;
+ mustfree = true;
+ forward_slash(var);
+ }
+#endif
+
+ /* If "var" contains white space, escape it with a backslash.
+ * Required for ":e ~/tt" when $HOME includes a space. */
+ if (esc && var != NULL && vim_strpbrk(var, (char_u *)" \t") != NULL) {
+ char_u *p = vim_strsave_escaped(var, (char_u *)" \t");
+
+ if (mustfree)
+ free(var);
+ var = p;
+ mustfree = true;
+ }
+
+ if (var != NULL && *var != NUL
+ && (STRLEN(var) + STRLEN(tail) + 1 < (unsigned)dstlen)) {
+ STRCPY(dst, var);
+ dstlen -= (int)STRLEN(var);
+ c = (int)STRLEN(var);
+ /* if var[] ends in a path separator and tail[] starts
+ * with it, skip a character */
+ if (*var != NUL && after_pathsep(dst, dst + c)
+#if defined(BACKSLASH_IN_FILENAME)
+ && dst[-1] != ':'
+#endif
+ && vim_ispathsep(*tail))
+ ++tail;
+ dst += c;
+ src = tail;
+ copy_char = false;
+ }
+ if (mustfree)
+ free(var);
+ }
+
+ if (copy_char) { /* copy at least one char */
+ /*
+ * Recognize the start of a new name, for '~'.
+ * Don't do this when "one" is true, to avoid expanding "~" in
+ * ":edit foo ~ foo".
+ */
+ at_start = false;
+ if (src[0] == '\\' && src[1] != NUL) {
+ *dst++ = *src++;
+ --dstlen;
+ } else if ((src[0] == ' ' || src[0] == ',') && !one) {
+ at_start = true;
+ }
+ *dst++ = *src++;
+ --dstlen;
+
+ if (startstr != NULL && src - startstr_len >= srcp
+ && STRNCMP(src - startstr_len, startstr, startstr_len) == 0)
+ at_start = true;
+ }
+ }
+ *dst = NUL;
+}
+
+/// Check if the directory "vimdir/<version>" or "vimdir/runtime" exists.
+/// Return NULL if not, return its name in allocated memory otherwise.
+/// @param vimdir directory to test
+static char_u *vim_version_dir(char_u *vimdir)
+{
+ char_u *p;
+
+ if (vimdir == NULL || *vimdir == NUL)
+ return NULL;
+ p = concat_fnames(vimdir, (char_u *)VIM_VERSION_NODOT, true);
+ if (os_isdir(p))
+ return p;
+ free(p);
+ p = concat_fnames(vimdir, (char_u *)RUNTIME_DIRNAME, true);
+ if (os_isdir(p))
+ return p;
+ free(p);
+ return NULL;
+}
+
+/// If the string between "p" and "pend" ends in "name/", return "pend" minus
+/// the length of "name/". Otherwise return "pend".
+static char_u *remove_tail(char_u *p, char_u *pend, char_u *name)
+{
+ int len = (int)STRLEN(name) + 1;
+ char_u *newend = pend - len;
+
+ if (newend >= p
+ && fnamencmp(newend, name, len - 1) == 0
+ && (newend == p || after_pathsep(p, newend)))
+ return newend;
+ return pend;
+}
+
+/// Vim's version of getenv().
+/// Special handling of $HOME, $VIM and $VIMRUNTIME, allowing the user to
+/// override the vim runtime directory at runtime. Also does ACP to 'enc'
+/// conversion for Win32.
+/// @param name Name of environment variable to expand
+/// @param[out] mustfree Ouput parameter for the caller to determine if they are
+/// responsible for releasing memory. Must be initialized to false
+/// by the caller.
+char_u *vim_getenv(char_u *name, bool *mustfree)
+{
+ char_u *p;
+ char_u *pend;
+ int vimruntime;
+
+
+ p = (char_u *)os_getenv((char *)name);
+ if (p != NULL && *p == NUL) /* empty is the same as not set */
+ p = NULL;
+
+ if (p != NULL) {
+ return p;
+ }
+
+ vimruntime = (STRCMP(name, "VIMRUNTIME") == 0);
+ if (!vimruntime && STRCMP(name, "VIM") != 0)
+ return NULL;
+
+ /*
+ * When expanding $VIMRUNTIME fails, try using $VIM/vim<version> or $VIM.
+ * Don't do this when default_vimruntime_dir is non-empty.
+ */
+ if (vimruntime
+#ifdef HAVE_PATHDEF
+ && *default_vimruntime_dir == NUL
+#endif
+ ) {
+ p = (char_u *)os_getenv("VIM");
+ if (p != NULL && *p == NUL) /* empty is the same as not set */
+ p = NULL;
+ if (p != NULL) {
+ p = vim_version_dir(p);
+ if (p != NULL)
+ *mustfree = true;
+ else
+ p = (char_u *)os_getenv("VIM");
+ }
+ }
+
+ /*
+ * When expanding $VIM or $VIMRUNTIME fails, try using:
+ * - the directory name from 'helpfile' (unless it contains '$')
+ * - the executable name from argv[0]
+ */
+ if (p == NULL) {
+ if (p_hf != NULL && vim_strchr(p_hf, '$') == NULL)
+ p = p_hf;
+ if (p != NULL) {
+ /* remove the file name */
+ pend = path_tail(p);
+
+ /* remove "doc/" from 'helpfile', if present */
+ if (p == p_hf)
+ pend = remove_tail(p, pend, (char_u *)"doc");
+
+ /* for $VIM, remove "runtime/" or "vim54/", if present */
+ if (!vimruntime) {
+ pend = remove_tail(p, pend, (char_u *)RUNTIME_DIRNAME);
+ pend = remove_tail(p, pend, (char_u *)VIM_VERSION_NODOT);
+ }
+
+ /* remove trailing path separator */
+ if (pend > p && after_pathsep(p, pend))
+ --pend;
+
+ // check that the result is a directory name
+ assert(pend >= p);
+ p = vim_strnsave(p, (size_t)(pend - p));
+
+ if (!os_isdir(p)) {
+ free(p);
+ p = NULL;
+ } else {
+ *mustfree = true;
+ }
+ }
+ }
+
+#ifdef HAVE_PATHDEF
+ /* When there is a pathdef.c file we can use default_vim_dir and
+ * default_vimruntime_dir */
+ if (p == NULL) {
+ /* Only use default_vimruntime_dir when it is not empty */
+ if (vimruntime && *default_vimruntime_dir != NUL) {
+ p = default_vimruntime_dir;
+ *mustfree = false;
+ } else if (*default_vim_dir != NUL) {
+ if (vimruntime && (p = vim_version_dir(default_vim_dir)) != NULL) {
+ *mustfree = true;
+ } else {
+ p = default_vim_dir;
+ *mustfree = false;
+ }
+ }
+ }
+#endif
+
+ /*
+ * Set the environment variable, so that the new value can be found fast
+ * next time, and others can also use it (e.g. Perl).
+ */
+ if (p != NULL) {
+ if (vimruntime) {
+ vim_setenv((char_u *)"VIMRUNTIME", p);
+ didset_vimruntime = true;
+ } else {
+ vim_setenv((char_u *)"VIM", p);
+ didset_vim = true;
+ }
+ }
+ return p;
+}
+
+/// Replace home directory by "~" in each space or comma separated file name in
+/// 'src'.
+/// If anything fails (except when out of space) dst equals src.
+/// @param buf When not NULL, check for help files
+/// @param src Input file name
+/// @param dst Where to put the result
+/// @param dstlen Maximum length of the result
+/// @param one If true, only replace one file name, including spaces and commas
+/// in the file name
+void home_replace(buf_T *buf, char_u *src, char_u *dst, int dstlen, bool one)
+{
+ size_t dirlen = 0, envlen = 0;
+ size_t len;
+ char_u *homedir_env, *homedir_env_orig;
+ char_u *p;
+
+ if (src == NULL) {
+ *dst = NUL;
+ return;
+ }
+
+ /*
+ * If the file is a help file, remove the path completely.
+ */
+ if (buf != NULL && buf->b_help) {
+ STRCPY(dst, path_tail(src));
+ return;
+ }
+
+ /*
+ * We check both the value of the $HOME environment variable and the
+ * "real" home directory.
+ */
+ if (homedir != NULL)
+ dirlen = STRLEN(homedir);
+
+ homedir_env_orig = homedir_env = (char_u *)os_getenv("HOME");
+ /* Empty is the same as not set. */
+ if (homedir_env != NULL && *homedir_env == NUL)
+ homedir_env = NULL;
+
+ if (homedir_env != NULL && vim_strchr(homedir_env, '~') != NULL) {
+ int usedlen = 0;
+ int flen;
+ char_u *fbuf = NULL;
+
+ flen = (int)STRLEN(homedir_env);
+ (void)modify_fname((char_u *)":p", &usedlen,
+ &homedir_env, &fbuf, &flen);
+ flen = (int)STRLEN(homedir_env);
+ if (flen > 0 && vim_ispathsep(homedir_env[flen - 1]))
+ /* Remove the trailing / that is added to a directory. */
+ homedir_env[flen - 1] = NUL;
+ }
+
+ if (homedir_env != NULL)
+ envlen = STRLEN(homedir_env);
+
+ if (!one)
+ src = skipwhite(src);
+ while (*src && dstlen > 0) {
+ /*
+ * Here we are at the beginning of a file name.
+ * First, check to see if the beginning of the file name matches
+ * $HOME or the "real" home directory. Check that there is a '/'
+ * after the match (so that if e.g. the file is "/home/pieter/bla",
+ * and the home directory is "/home/piet", the file does not end up
+ * as "~er/bla" (which would seem to indicate the file "bla" in user
+ * er's home directory)).
+ */
+ p = homedir;
+ len = dirlen;
+ for (;; ) {
+ if ( len
+ && fnamencmp(src, p, len) == 0
+ && (vim_ispathsep(src[len])
+ || (!one && (src[len] == ',' || src[len] == ' '))
+ || src[len] == NUL)) {
+ src += len;
+ if (--dstlen > 0)
+ *dst++ = '~';
+
+ /*
+ * If it's just the home directory, add "/".
+ */
+ if (!vim_ispathsep(src[0]) && --dstlen > 0)
+ *dst++ = '/';
+ break;
+ }
+ if (p == homedir_env)
+ break;
+ p = homedir_env;
+ len = envlen;
+ }
+
+ /* if (!one) skip to separator: space or comma */
+ while (*src && (one || (*src != ',' && *src != ' ')) && --dstlen > 0)
+ *dst++ = *src++;
+ /* skip separator */
+ while ((*src == ' ' || *src == ',') && --dstlen > 0)
+ *dst++ = *src++;
+ }
+ /* if (dstlen == 0) out of space, what to do??? */
+
+ *dst = NUL;
+
+ if (homedir_env != homedir_env_orig)
+ free(homedir_env);
+}
+
+/// Like home_replace, store the replaced string in allocated memory.
+/// @param buf When not NULL, check for help files
+/// @param src Input file name
+char_u * home_replace_save(buf_T *buf, char_u *src) FUNC_ATTR_NONNULL_RET
+{
+ size_t len = 3; /* space for "~/" and trailing NUL */
+ if (src != NULL) /* just in case */
+ len += STRLEN(src);
+ char_u *dst = xmalloc(len);
+ home_replace(buf, src, dst, (int)len, true);
+ return dst;
+}
+
+/// Our portable version of setenv.
+/// Has special handling for $VIMRUNTIME to keep the localization machinery
+/// sane.
+void vim_setenv(char_u *name, char_u *val)
+{
+ os_setenv((char *)name, (char *)val, 1);
+ /*
+ * When setting $VIMRUNTIME adjust the directory to find message
+ * translations to $VIMRUNTIME/lang.
+ */
+ if (*val != NUL && STRICMP(name, "VIMRUNTIME") == 0) {
+ char_u *buf = concat_str(val, (char_u *)"/lang");
+ bindtextdomain(VIMPACKAGE, (char *)buf);
+ free(buf);
+ }
+}
+
+
+/// Function given to ExpandGeneric() to obtain an environment variable name.
+char_u *get_env_name(expand_T *xp, int idx)
+{
+# define ENVNAMELEN 100
+ // this static buffer is needed to avoid a memory leak in ExpandGeneric
+ static char_u name[ENVNAMELEN];
+ assert(idx >= 0);
+ char *envname = os_getenvname_at_index((size_t)idx);
+ if (envname) {
+ STRLCPY(name, envname, ENVNAMELEN);
+ free(envname);
+ return name;
+ } else {
+ return NULL;
+ }
+}
+