neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

env.c (37431B)


      1 // Environment inspection
      2 
      3 #include <assert.h>
      4 #include <limits.h>
      5 #include <stdbool.h>
      6 #include <stddef.h>
      7 #include <stdint.h>
      8 #include <string.h>
      9 #include <uv.h>
     10 
     11 #include "auto/config.h"
     12 #include "nvim/ascii_defs.h"
     13 #include "nvim/buffer_defs.h"
     14 #include "nvim/charset.h"
     15 #include "nvim/cmdexpand.h"
     16 #include "nvim/cmdexpand_defs.h"
     17 #include "nvim/eval.h"
     18 #include "nvim/eval/fs.h"
     19 #include "nvim/eval/vars.h"
     20 #include "nvim/globals.h"
     21 #include "nvim/log.h"
     22 #include "nvim/macros_defs.h"
     23 #include "nvim/map_defs.h"
     24 #include "nvim/memory.h"
     25 #include "nvim/message.h"
     26 #include "nvim/option_vars.h"
     27 #include "nvim/os/fs.h"
     28 #include "nvim/os/os.h"
     29 #include "nvim/os/os_defs.h"
     30 #include "nvim/path.h"
     31 #include "nvim/strings.h"
     32 #include "nvim/types_defs.h"
     33 #include "nvim/version.h"
     34 #include "nvim/vim_defs.h"
     35 
     36 #ifdef MSWIN
     37 # include "nvim/mbyte.h"
     38 #endif
     39 
     40 #ifdef BACKSLASH_IN_FILENAME
     41 # include "nvim/fileio.h"
     42 #endif
     43 
     44 #ifdef __APPLE__
     45 # include <mach/task.h>
     46 #endif
     47 
     48 #ifdef HAVE__NSGETENVIRON
     49 # include <crt_externs.h>
     50 #endif
     51 
     52 #ifdef HAVE_SYS_UTSNAME_H
     53 # include <sys/utsname.h>
     54 #endif
     55 
     56 #include "auto/pathdef.h"
     57 
     58 #include "os/env.c.generated.h"
     59 
     60 void env_init(void)
     61 {
     62  nvim_testing = os_env_exists("NVIM_TEST", false);
     63 }
     64 
     65 /// Like getenv(), but returns NULL if the variable is empty.
     66 /// Result must be freed by the caller.
     67 /// @see os_env_exists
     68 /// @see os_getenv_noalloc
     69 char *os_getenv(const char *name)
     70  FUNC_ATTR_NONNULL_ALL
     71 {
     72  char *e = NULL;
     73  if (name[0] == NUL) {
     74    return NULL;
     75  }
     76  int r = 0;
     77 #define INIT_SIZE 64
     78  size_t size = INIT_SIZE;
     79  char buf[INIT_SIZE];
     80  r = uv_os_getenv(name, buf, &size);
     81  if (r == UV_ENOBUFS) {
     82    e = xmalloc(size);
     83    r = uv_os_getenv(name, e, &size);
     84    if (r != 0 || size == 0 || e[0] == NUL) {
     85      XFREE_CLEAR(e);
     86      goto end;
     87    }
     88  } else if (r != 0 || size == 0 || buf[0] == NUL) {
     89    e = NULL;
     90    goto end;
     91  } else {
     92    // NB: `size` param of uv_os_getenv() includes the NUL-terminator,
     93    // except when it does not include the NUL-terminator.
     94    e = xmemdupz(buf, size);
     95  }
     96 end:
     97  if (r != 0 && r != UV_ENOENT && r != UV_UNKNOWN) {
     98    ELOG("uv_os_getenv(%s) failed: %d %s", name, r, uv_err_name(r));
     99  }
    100  return e;
    101 }
    102 
    103 /// Like getenv(), but stores the value in `buf` instead of allocating.
    104 /// Value is truncated if it exceeds `bufsize`.
    105 ///
    106 /// @return `buf` on success, NULL on failure
    107 /// @see os_env_exists
    108 /// @see os_getenv_noalloc
    109 char *os_getenv_buf(const char *const name, char *const buf, const size_t bufsize)
    110  FUNC_ATTR_NONNULL_ALL
    111 {
    112  if (name[0] == NUL) {
    113    return NULL;
    114  }
    115 
    116  size_t size = bufsize;
    117  int r = uv_os_getenv(name, buf, &size);
    118  if (r == UV_ENOBUFS) {
    119    char *e = xmalloc(size);
    120    r = uv_os_getenv(name, e, &size);
    121    if (r == 0 && size != 0 && e[0] != NUL) {
    122      xmemcpyz(buf, e, MIN(bufsize, size) - 1);
    123    }
    124    xfree(e);
    125  }
    126 
    127  if (r != 0 || size == 0 || buf[0] == NUL) {
    128    if (r != 0 && r != UV_ENOENT && r != UV_UNKNOWN) {
    129      ELOG("uv_os_getenv(%s) failed: %d %s", name, r, uv_err_name(r));
    130    }
    131    return NULL;
    132  }
    133  return buf;
    134 }
    135 
    136 /// Like getenv(), but use `NameBuff` instead of allocating.
    137 /// Value is truncated if it exceeds sizeof(NameBuff).
    138 ///
    139 /// @return pointer to `NameBuff` on success, NULL on failure
    140 /// @see os_env_exists
    141 /// @see os_getenv_buf
    142 char *os_getenv_noalloc(const char *name)
    143  FUNC_ATTR_NONNULL_ALL
    144 {
    145  return os_getenv_buf(name, NameBuff, sizeof(NameBuff));
    146 }
    147 
    148 /// Returns true if environment variable `name` is defined (even if empty).
    149 /// Returns false if not found (UV_ENOENT) or other failure.
    150 ///
    151 /// @param name the environment variable in question
    152 /// @param nonempty Require a non-empty value. Treat empty as "does not exist".
    153 /// @return whether the variable exists
    154 bool os_env_exists(const char *name, bool nonempty)
    155  FUNC_ATTR_NONNULL_ALL
    156 {
    157  if (name[0] == NUL) {
    158    return false;
    159  }
    160  // Use a tiny buffer because we don't care about the value: if uv_os_getenv()
    161  // returns UV_ENOBUFS, the env var was found.
    162  char buf[2];
    163  size_t size = sizeof(buf);
    164  int r = uv_os_getenv(name, buf, &size);
    165  assert(r != UV_EINVAL);
    166  if (r != 0 && r != UV_ENOENT && r != UV_ENOBUFS) {
    167    ELOG("uv_os_getenv(%s) failed: %d %s", name, r, uv_err_name(r));
    168  }
    169  return ((r == 0 && (!nonempty || size > 0)) || r == UV_ENOBUFS);
    170 }
    171 
    172 /// Sets an environment variable.
    173 ///
    174 /// Windows (Vim-compat): Empty string (:let $FOO="") undefines the env var.
    175 int os_setenv(const char *name, const char *value, int overwrite)
    176  FUNC_ATTR_NONNULL_ALL
    177 {
    178  if (name[0] == NUL) {
    179    return -1;
    180  }
    181 #ifdef MSWIN
    182  if (!overwrite && !os_env_exists(name, true)) {
    183    return 0;
    184  }
    185  if (value[0] == NUL) {
    186    // Windows (Vim-compat): Empty string undefines the env var.
    187    return os_unsetenv(name);
    188  }
    189 #else
    190  if (!overwrite && os_env_exists(name, false)) {
    191    return 0;
    192  }
    193 #endif
    194  int r;
    195 #ifdef MSWIN
    196  // libintl uses getenv() for LC_ALL/LANG/etc., so we must use _putenv_s().
    197  if (striequal(name, "LC_ALL") || striequal(name, "LANGUAGE")
    198      || striequal(name, "LANG") || striequal(name, "LC_MESSAGES")) {
    199    r = _putenv_s(name, value);  // NOLINT
    200    assert(r == 0);
    201  }
    202 #endif
    203  r = uv_os_setenv(name, value);
    204  assert(r != UV_EINVAL);
    205  if (r != 0) {
    206    ELOG("uv_os_setenv(%s) failed: %d %s", name, r, uv_err_name(r));
    207  }
    208  return r == 0 ? 0 : -1;
    209 }
    210 
    211 /// Unset environment variable
    212 int os_unsetenv(const char *name)
    213  FUNC_ATTR_NONNULL_ALL
    214 {
    215  if (name[0] == NUL) {
    216    return -1;
    217  }
    218  int r = uv_os_unsetenv(name);
    219  if (r != 0) {
    220    ELOG("uv_os_unsetenv(%s) failed: %d %s", name, r, uv_err_name(r));
    221  }
    222  return r == 0 ? 0 : -1;
    223 }
    224 
    225 /// Returns number of variables in the current environment variables block
    226 size_t os_get_fullenv_size(void)
    227 {
    228  size_t len = 0;
    229 #ifdef MSWIN
    230  wchar_t *envstrings = GetEnvironmentStringsW();
    231  wchar_t *p = envstrings;
    232  size_t l;
    233  if (!envstrings) {
    234    return len;
    235  }
    236  // GetEnvironmentStringsW() result has this format:
    237  //    var1=value1\0var2=value2\0...varN=valueN\0\0
    238  while ((l = wcslen(p)) != 0) {
    239    p += l + 1;
    240    len++;
    241  }
    242 
    243  FreeEnvironmentStringsW(envstrings);
    244 #else
    245 # if defined(HAVE__NSGETENVIRON)
    246  char **environ = *_NSGetEnviron();
    247 # else
    248  extern char **environ;
    249 # endif
    250 
    251  while (environ[len] != NULL) {
    252    len++;
    253  }
    254 
    255 #endif
    256  return len;
    257 }
    258 
    259 void os_free_fullenv(char **env)
    260 {
    261  if (!env) {
    262    return;
    263  }
    264  for (char **it = env; *it; it++) {
    265    XFREE_CLEAR(*it);
    266  }
    267  xfree(env);
    268 }
    269 
    270 /// Copies the current environment variables into the given array, `env`.  Each
    271 /// array element is of the form "NAME=VALUE".
    272 /// Result must be freed by the caller.
    273 ///
    274 /// @param[out]  env  array to populate with environment variables
    275 /// @param  env_size  size of `env`, @see os_fullenv_size
    276 void os_copy_fullenv(char **env, size_t env_size)
    277 {
    278 #ifdef MSWIN
    279  wchar_t *envstrings = GetEnvironmentStringsW();
    280  if (!envstrings) {
    281    return;
    282  }
    283  wchar_t *p = envstrings;
    284  size_t i = 0;
    285  size_t l;
    286  // GetEnvironmentStringsW() result has this format:
    287  //    var1=value1\0var2=value2\0...varN=valueN\0\0
    288  while ((l = wcslen(p)) != 0 && i < env_size) {
    289    char *utf8_str;
    290    int conversion_result = utf16_to_utf8(p, -1, &utf8_str);
    291    if (conversion_result != 0) {
    292      semsg("utf16_to_utf8 failed: %d", conversion_result);
    293      break;
    294    }
    295    p += l + 1;
    296 
    297    env[i] = utf8_str;
    298    i++;
    299  }
    300 
    301  FreeEnvironmentStringsW(envstrings);
    302 #else
    303 # if defined(HAVE__NSGETENVIRON)
    304  char **environ = *_NSGetEnviron();
    305 # else
    306  extern char **environ;
    307 # endif
    308 
    309  for (size_t i = 0; i < env_size && environ[i] != NULL; i++) {
    310    env[i] = xstrdup(environ[i]);
    311  }
    312 #endif
    313 }
    314 
    315 /// Copy value of the environment variable at `index` in the current
    316 /// environment variables block.
    317 /// Result must be freed by the caller.
    318 ///
    319 /// @param index nth item in environment variables block
    320 /// @return [allocated] environment variable's value, or NULL
    321 char *os_getenvname_at_index(size_t index)
    322 {
    323 #ifdef MSWIN
    324  wchar_t *envstrings = GetEnvironmentStringsW();
    325  if (!envstrings) {
    326    return NULL;
    327  }
    328  wchar_t *p = envstrings;
    329  char *name = NULL;
    330  size_t i = 0;
    331  size_t l;
    332  // GetEnvironmentStringsW() result has this format:
    333  //    var1=value1\0var2=value2\0...varN=valueN\0\0
    334  while ((l = wcslen(p)) != 0 && i <= index) {
    335    if (i == index) {
    336      char *utf8_str;
    337      int conversion_result = utf16_to_utf8(p, -1, &utf8_str);
    338      if (conversion_result != 0) {
    339        semsg("utf16_to_utf8 failed: %d", conversion_result);
    340        break;
    341      }
    342 
    343      // Some Windows env vars start with =, so skip over that to find the
    344      // separator between name/value
    345      const char *const end = strchr(utf8_str + (utf8_str[0] == '=' ? 1 : 0), '=');
    346      assert(end != NULL);
    347      ptrdiff_t len = end - utf8_str;
    348      assert(len > 0);
    349      name = xmemdupz(utf8_str, (size_t)len);
    350      xfree(utf8_str);
    351      break;
    352    }
    353 
    354    // Advance past the name and NUL
    355    p += l + 1;
    356    i++;
    357  }
    358 
    359  FreeEnvironmentStringsW(envstrings);
    360  return name;
    361 #else
    362 # if defined(HAVE__NSGETENVIRON)
    363  char **environ = *_NSGetEnviron();
    364 # else
    365  extern char **environ;
    366 # endif
    367 
    368  // check if index is inside the environ array
    369  for (size_t i = 0; i <= index; i++) {
    370    if (environ[i] == NULL) {
    371      return NULL;
    372    }
    373  }
    374  char *str = environ[index];
    375  assert(str != NULL);
    376  const char * const end = strchr(str, '=');
    377  assert(end != NULL);
    378  ptrdiff_t len = end - str;
    379  assert(len > 0);
    380  return xmemdupz(str, (size_t)len);
    381 #endif
    382 }
    383 
    384 /// Get the process ID of the Nvim process.
    385 ///
    386 /// @return the process ID.
    387 int64_t os_get_pid(void)
    388 {
    389 #ifdef MSWIN
    390  return (int64_t)GetCurrentProcessId();
    391 #else
    392  return (int64_t)getpid();
    393 #endif
    394 }
    395 
    396 /// Signals to the OS that Nvim is an application for "interactive work"
    397 /// which should be prioritized similar to a GUI app.
    398 void os_hint_priority(void)
    399 {
    400 #ifdef __APPLE__
    401  // By default, processes have the TASK_UNSPECIFIED "role", which means all of its threads are
    402  // clamped to Default QoS. Setting the role to TASK_DEFAULT_APPLICATION removes this clamp.
    403  integer_t policy = TASK_DEFAULT_APPLICATION;
    404  task_policy_set(mach_task_self(), TASK_CATEGORY_POLICY, &policy, 1);
    405 #endif
    406 }
    407 
    408 /// Gets the hostname of the current machine.
    409 ///
    410 /// @param hostname   Buffer to store the hostname.
    411 /// @param size       Size of `hostname`.
    412 void os_get_hostname(char *hostname, size_t size)
    413 {
    414 #ifdef HAVE_SYS_UTSNAME_H
    415  struct utsname vutsname;
    416 
    417  if (uname(&vutsname) < 0) {
    418    *hostname = NUL;
    419  } else {
    420    xstrlcpy(hostname, vutsname.nodename, size);
    421  }
    422 #elif defined(MSWIN)
    423  wchar_t host_utf16[MAX_COMPUTERNAME_LENGTH + 1];
    424  DWORD host_wsize = sizeof(host_utf16) / sizeof(host_utf16[0]);
    425  if (GetComputerNameW(host_utf16, &host_wsize) == 0) {
    426    *hostname = NUL;
    427    DWORD err = GetLastError();
    428    semsg("GetComputerNameW failed: %d", err);
    429    return;
    430  }
    431  host_utf16[host_wsize] = NUL;
    432 
    433  char *host_utf8;
    434  int conversion_result = utf16_to_utf8(host_utf16, -1, &host_utf8);
    435  if (conversion_result != 0) {
    436    semsg("utf16_to_utf8 failed: %d", conversion_result);
    437    return;
    438  }
    439  xstrlcpy(hostname, host_utf8, size);
    440  xfree(host_utf8);
    441 #else
    442  emsg("os_get_hostname failed: missing uname()");
    443  *hostname = NUL;
    444 #endif
    445 }
    446 
    447 /// The "real" home directory as determined by `init_homedir`.
    448 static char *homedir = NULL;
    449 static char *os_uv_homedir(void);
    450 
    451 /// Gets the "real", resolved user home directory as determined by `init_homedir`.
    452 const char *os_homedir(void)
    453 {
    454  if (!homedir) {
    455    emsg("os_homedir failed: homedir not initialized");
    456    return NULL;
    457  }
    458  return homedir;
    459 }
    460 
    461 /// Sets `homedir` to the "real", resolved user home directory, as follows:
    462 ///   1. get value of $HOME
    463 ///   2. if $HOME is not set, try the following
    464 /// For Windows:
    465 ///   1. assemble homedir using HOMEDRIVE and HOMEPATH
    466 ///   2. try os_uv_homedir()
    467 ///   3. resolve a direct reference to another system variable
    468 ///   4. guess C drive
    469 /// For Unix:
    470 ///   1. try os_uv_homedir()
    471 ///   2. go to that directory
    472 ///     This also works with mounts and links.
    473 ///     Don't do this for Windows, it will change the "current dir" for a drive.
    474 ///   3. fall back to current working directory as a last resort
    475 void init_homedir(void)
    476 {
    477  // In case we are called a second time.
    478  xfree(homedir);
    479  homedir = NULL;
    480 
    481  char *var = os_getenv("HOME");
    482  char *tofree = var;
    483 
    484 #ifdef MSWIN
    485  // Typically, $HOME is not defined on Windows, unless the user has
    486  // specifically defined it for Vim's sake. However, on Windows NT
    487  // platforms, $HOMEDRIVE and $HOMEPATH are automatically defined for
    488  // each user. Try constructing $HOME from these.
    489  if (var == NULL) {
    490    char *homedrive = os_getenv("HOMEDRIVE");
    491    char *homepath = os_getenv("HOMEPATH");
    492    if (homepath == NULL) {
    493      homepath = xstrdup("\\");
    494    }
    495    if (homedrive != NULL
    496        && strlen(homedrive) + strlen(homepath) < MAXPATHL) {
    497      snprintf(os_buf, MAXPATHL, "%s%s", homedrive, homepath);
    498      if (os_buf[0] != NUL) {
    499        var = os_buf;
    500      }
    501    }
    502    xfree(homepath);
    503    xfree(homedrive);
    504  }
    505  if (var == NULL) {
    506    var = os_uv_homedir();
    507  }
    508 
    509  // Weird but true: $HOME may contain an indirect reference to another
    510  // variable, esp. "%USERPROFILE%".  Happens when $USERPROFILE isn't set
    511  // when $HOME is being set.
    512  if (var != NULL && *var == '%') {
    513    const char *p = strchr(var + 1, '%');
    514    if (p != NULL) {
    515      vim_snprintf(os_buf, (size_t)(p - var), "%s", var + 1);
    516      var = NULL;
    517      char *exp = os_getenv(os_buf);
    518      if (exp != NULL) {
    519        if (*exp != NUL && strlen(exp) + strlen(p) < MAXPATHL) {
    520          vim_snprintf(os_buf, MAXPATHL, "%s%s", exp, p + 1);
    521          var = os_buf;
    522        }
    523        xfree(exp);
    524      }
    525    }
    526  }
    527 
    528  // Default home dir is C:/
    529  // Best assumption we can make in such a situation.
    530  if (var == NULL
    531      // Empty means "undefined"
    532      || *var == NUL) {
    533    var = "C:/";
    534  }
    535 #endif
    536 
    537 #ifdef UNIX
    538  if (var == NULL) {
    539    var = os_uv_homedir();
    540  }
    541 
    542  // Get the actual path.  This resolves links.
    543  if (var != NULL && os_realpath(var, IObuff, IOSIZE) != NULL) {
    544    var = IObuff;
    545  }
    546 
    547  // Fall back to current working directory if home is not found
    548  if ((var == NULL || *var == NUL)
    549      && os_dirname(os_buf, sizeof(os_buf)) == OK) {
    550    var = os_buf;
    551  }
    552 #endif
    553  if (var != NULL) {
    554    homedir = xstrdup(var);
    555  }
    556  xfree(tofree);
    557 }
    558 
    559 static char homedir_buf[MAXPATHL];
    560 
    561 static char *os_uv_homedir(void)
    562 {
    563  homedir_buf[0] = NUL;
    564  size_t homedir_size = MAXPATHL;
    565  // http://docs.libuv.org/en/v1.x/misc.html#c.uv_os_homedir
    566  int ret_value = uv_os_homedir(homedir_buf, &homedir_size);
    567  if (ret_value == 0 && homedir_size < MAXPATHL) {
    568    return homedir_buf;
    569  }
    570  ELOG("uv_os_homedir() failed %d: %s", ret_value, os_strerror(ret_value));
    571  homedir_buf[0] = NUL;
    572  return NULL;
    573 }
    574 
    575 #if defined(EXITFREE)
    576 
    577 void free_homedir(void)
    578 {
    579  xfree(homedir);
    580 }
    581 
    582 #endif
    583 
    584 /// Call expand_env() and store the result in an allocated string.
    585 /// This is not very memory efficient, this expects the result to be freed
    586 /// again soon.
    587 /// @param src String containing environment variables to expand
    588 /// @see {expand_env}
    589 char *expand_env_save(char *src)
    590 {
    591  return expand_env_save_opt(src, false);
    592 }
    593 
    594 /// Similar to expand_env_save() but when "one" is `true` handle the string as
    595 /// one file name, i.e. only expand "~" at the start.
    596 /// @param src String containing environment variables to expand
    597 /// @param one Should treat as only one file name
    598 /// @see {expand_env}
    599 char *expand_env_save_opt(char *src, bool one)
    600 {
    601  char *p = xmalloc(MAXPATHL);
    602  expand_env_esc(src, p, MAXPATHL, false, one, NULL);
    603  return p;
    604 }
    605 
    606 /// Expand environment variable with path name.
    607 /// "~/" is also expanded, using $HOME. For Unix "~user/" is expanded.
    608 /// Skips over "\ ", "\~" and "\$" (not for Win32 though).
    609 /// If anything fails no expansion is done and dst equals src.
    610 ///
    611 /// @param src        Input string e.g. "$HOME/vim.hlp"
    612 /// @param dst[out]   Where to put the result
    613 /// @param dstlen     Maximum length of the result
    614 size_t expand_env(char *src, char *dst, int dstlen)
    615 {
    616  return expand_env_esc(src, dst, dstlen, false, false, NULL);
    617 }
    618 
    619 /// Expand environment variable with path name and escaping.
    620 /// @see expand_env
    621 ///
    622 /// @param srcp       Input string e.g. "$HOME/vim.hlp"
    623 /// @param dst[out]   Where to put the result
    624 /// @param dstlen     Maximum length of the result
    625 /// @param esc        Escape spaces in expanded variables
    626 /// @param one        `srcp` is a single filename
    627 /// @param prefix     Start again after this (can be NULL)
    628 size_t expand_env_esc(const char *restrict srcp, char *restrict dst, int dstlen, bool esc, bool one,
    629                      char *prefix)
    630  FUNC_ATTR_NONNULL_ARG(1, 2)
    631 {
    632  char *tail;
    633  char *var;
    634  bool copy_char;
    635  bool mustfree;  // var was allocated, need to free it later
    636  bool at_start = true;  // at start of a name
    637  char *const dst_start = dst;
    638 
    639  int prefix_len = (prefix == NULL) ? 0 : (int)strlen(prefix);
    640 
    641  char *src = skipwhite(srcp);
    642  dstlen--;  // leave one char space for "\,"
    643  while (*src && dstlen > 0) {
    644    // Skip over `=expr`.
    645    if (src[0] == '`' && src[1] == '=') {
    646      var = src;
    647      src += 2;
    648      skip_expr(&src, NULL);
    649      if (*src == '`') {
    650        src++;
    651      }
    652      size_t len = (size_t)(src - var);
    653      if (len > (size_t)dstlen) {
    654        len = (size_t)dstlen;
    655      }
    656      memcpy(dst, var, len);
    657      dst += len;
    658      dstlen -= (int)len;
    659      continue;
    660    }
    661 
    662    copy_char = true;
    663    if ((*src == '$') || (*src == '~' && at_start)) {
    664      mustfree = false;
    665 
    666      // The variable name is copied into dst temporarily, because it may
    667      // be a string in read-only memory and a NUL needs to be appended.
    668      if (*src != '~') {  // environment var
    669        tail = src + 1;
    670        var = dst;
    671        int c = dstlen - 1;
    672 
    673 #ifdef UNIX
    674        // Unix has ${var-name} type environment vars
    675        if (*tail == '{' && !vim_isIDc('{')) {
    676          tail++;               // ignore '{'
    677          while (c-- > 0 && *tail != NUL && *tail != '}') {
    678            *var++ = *tail++;
    679          }
    680        } else
    681 #endif
    682        {
    683          while (c-- > 0 && *tail != NUL && vim_isIDc((uint8_t)(*tail))) {
    684            *var++ = *tail++;
    685          }
    686        }
    687 
    688 #if defined(UNIX)
    689        // Verify that we have found the end of a Unix ${VAR} style variable
    690        if (src[1] == '{' && *tail != '}') {
    691          var = NULL;
    692        } else {
    693          if (src[1] == '{') {
    694            tail++;
    695          }
    696 #endif
    697        *var = NUL;
    698        var = vim_getenv(dst);
    699        mustfree = true;
    700 #if defined(UNIX)
    701      }
    702 #endif
    703      } else if (src[1] == NUL  // home directory
    704                 || vim_ispathsep(src[1])
    705                 || vim_strchr(" ,\t\n", (uint8_t)src[1]) != NULL) {
    706        var = homedir;
    707        tail = src + 1;
    708      } else {  // user directory
    709 #if defined(UNIX)
    710        // Copy ~user to dst[], so we can put a NUL after it.
    711        tail = src;
    712        var = dst;
    713        int c = dstlen - 1;
    714        while (c-- > 0
    715               && *tail
    716               && vim_isfilec((uint8_t)(*tail))
    717               && !vim_ispathsep(*tail)) {
    718          *var++ = *tail++;
    719        }
    720        *var = NUL;
    721        // Get the user directory. If this fails the shell is used to expand
    722        // ~user, which is slower and may fail on old versions of /bin/sh.
    723        var = (*dst == NUL) ? NULL
    724                            : os_get_userdir(dst + 1);
    725        mustfree = true;
    726        if (var == NULL) {
    727          expand_T xpc;
    728 
    729          ExpandInit(&xpc);
    730          xpc.xp_context = EXPAND_FILES;
    731          var = ExpandOne(&xpc, dst, NULL,
    732                          WILD_ADD_SLASH|WILD_SILENT, WILD_EXPAND_FREE);
    733          mustfree = true;
    734        }
    735 #else
    736        // cannot expand user's home directory, so don't try
    737        var = NULL;
    738        tail = "";  // for gcc
    739 #endif  // UNIX
    740      }
    741 
    742 #ifdef BACKSLASH_IN_FILENAME
    743      // If 'shellslash' is set change backslashes to forward slashes.
    744      // Can't use slash_adjust(), p_ssl may be set temporarily.
    745      if (p_ssl && var != NULL && vim_strchr(var, '\\') != NULL) {
    746        char *p = xstrdup(var);
    747 
    748        if (mustfree) {
    749          xfree(var);
    750        }
    751        var = p;
    752        mustfree = true;
    753        forward_slash(var);
    754      }
    755 #endif
    756 
    757      // If "var" contains white space, escape it with a backslash.
    758      // Required for ":e ~/tt" when $HOME includes a space.
    759      if (esc && var != NULL && strpbrk(var, " \t") != NULL) {
    760        char *p = vim_strsave_escaped(var, " \t");
    761 
    762        if (mustfree) {
    763          xfree(var);
    764        }
    765        var = p;
    766        mustfree = true;
    767      }
    768 
    769      if (var != NULL && *var != NUL) {
    770        int c = (int)strlen(var);
    771        if ((size_t)c + strlen(tail) + 1 < (unsigned)dstlen) {
    772          STRCPY(dst, var);
    773          dstlen -= c;
    774          // if var[] ends in a path separator and tail[] starts
    775          // with it, skip a character
    776          if (after_pathsep(dst, dst + c)
    777 #if defined(BACKSLASH_IN_FILENAME)
    778              && dst[c - 1] != ':'
    779 #endif
    780              && vim_ispathsep(*tail)) {
    781            tail++;
    782          }
    783          dst += c;
    784          src = tail;
    785          copy_char = false;
    786        }
    787      }
    788      if (mustfree) {
    789        xfree(var);
    790      }
    791    }
    792 
    793    if (copy_char) {  // copy at least one char
    794      // Recognize the start of a new name, for '~'.
    795      // Don't do this when "one" is true, to avoid expanding "~" in
    796      // ":edit foo ~ foo".
    797      at_start = false;
    798      if (src[0] == '\\' && src[1] != NUL) {
    799        *dst++ = *src++;
    800        dstlen--;
    801      } else if ((src[0] == ' ' || src[0] == ',') && !one) {
    802        at_start = true;
    803      }
    804      if (dstlen > 0) {
    805        *dst++ = *src++;
    806        dstlen--;
    807 
    808        if (prefix != NULL
    809            && src - prefix_len >= srcp
    810            && strncmp(src - prefix_len, prefix, (size_t)prefix_len) == 0) {
    811          at_start = true;
    812        }
    813      }
    814    }
    815  }
    816  *dst = NUL;
    817 
    818  return (size_t)(dst - dst_start);
    819 }
    820 
    821 /// Check if the directory "vimdir/runtime" exists.
    822 /// Return NULL if not, return its name in allocated memory otherwise.
    823 /// @param vimdir directory to test
    824 static char *vim_runtime_dir(const char *vimdir)
    825 {
    826  if (vimdir == NULL || *vimdir == NUL) {
    827    return NULL;
    828  }
    829  char *p = concat_fnames(vimdir, RUNTIME_DIRNAME, true);
    830  if (os_isdir(p)) {
    831    return p;
    832  }
    833  xfree(p);
    834  return NULL;
    835 }
    836 
    837 /// If `dirname + "/"` precedes `pend` in the path, return the pointer to
    838 /// `dirname + "/" + pend`.  Otherwise return `pend`.
    839 ///
    840 /// Examples (path = /usr/local/share/nvim/runtime/doc/help.txt):
    841 ///
    842 ///   pend    = help.txt
    843 ///   dirname = doc
    844 ///   -> doc/help.txt
    845 ///
    846 ///   pend    = doc/help.txt
    847 ///   dirname = runtime
    848 ///   -> runtime/doc/help.txt
    849 ///
    850 ///   pend    = runtime/doc/help.txt
    851 ///   dirname = vim74
    852 ///   -> runtime/doc/help.txt
    853 ///
    854 /// @param path    Path to a file
    855 /// @param pend    A suffix of the path
    856 /// @param dirname The immediate path fragment before the pend
    857 /// @return The new pend including dirname or just pend
    858 static char *remove_tail(char *path, char *pend, char *dirname)
    859 {
    860  size_t len = strlen(dirname);
    861  char *new_tail = pend - len - 1;
    862 
    863  if (new_tail >= path
    864      && path_fnamencmp(new_tail, dirname, len) == 0
    865      && (new_tail == path || after_pathsep(path, new_tail))) {
    866    return new_tail;
    867  }
    868  return pend;
    869 }
    870 
    871 /// Iterates $PATH-like delimited list `val`.
    872 ///
    873 /// @note Environment variables must not be modified during iteration.
    874 ///
    875 /// @param[in]   delim Delimiter character.
    876 /// @param[in]   val   Value of the environment variable to iterate over.
    877 /// @param[in]   iter  Pointer used for iteration. Must be NULL on first
    878 ///                    iteration.
    879 /// @param[out]  dir   Location where pointer to the start of the current
    880 ///                    directory name should be saved. May be set to NULL.
    881 /// @param[out]  len   Location where current directory length should be saved.
    882 ///
    883 /// @return Next iter argument value or NULL when iteration should stop.
    884 const void *vim_env_iter(const char delim, const char *const val, const void *const iter,
    885                         const char **const dir, size_t *const len)
    886  FUNC_ATTR_NONNULL_ARG(2, 4, 5) FUNC_ATTR_WARN_UNUSED_RESULT
    887 {
    888  const char *varval = iter;
    889  if (varval == NULL) {
    890    varval = val;
    891  }
    892  *dir = varval;
    893  const char *const dirend = strchr(varval, delim);
    894  if (dirend == NULL) {
    895    *len = strlen(varval);
    896    return NULL;
    897  }
    898  *len = (size_t)(dirend - varval);
    899  return dirend + 1;
    900 }
    901 
    902 /// Iterates $PATH-like delimited list `val` in reverse order.
    903 ///
    904 /// @note Environment variables must not be modified during iteration.
    905 ///
    906 /// @param[in]   delim Delimiter character.
    907 /// @param[in]   val   Value of the environment variable to iterate over.
    908 /// @param[in]   iter  Pointer used for iteration. Must be NULL on first
    909 ///                    iteration.
    910 /// @param[out]  dir   Location where pointer to the start of the current
    911 ///                    directory name should be saved. May be set to NULL.
    912 /// @param[out]  len   Location where current directory length should be saved.
    913 ///
    914 /// @return Next iter argument value or NULL when iteration should stop.
    915 const void *vim_env_iter_rev(const char delim, const char *const val, const void *const iter,
    916                             const char **const dir, size_t *const len)
    917  FUNC_ATTR_NONNULL_ARG(2, 4, 5) FUNC_ATTR_WARN_UNUSED_RESULT
    918 {
    919  const char *varend = iter;
    920  if (varend == NULL) {
    921    varend = val + strlen(val) - 1;
    922  }
    923  const size_t varlen = (size_t)(varend - val) + 1;
    924  const char *const colon = xmemrchr(val, (uint8_t)delim, varlen);
    925  if (colon == NULL) {
    926    *len = varlen;
    927    *dir = val;
    928    return NULL;
    929  }
    930  *dir = colon + 1;
    931  *len = (size_t)(varend - colon);
    932  return colon - 1;
    933 }
    934 
    935 /// @param[out] exe_name should be at least MAXPATHL in size
    936 void vim_get_prefix_from_exepath(char *exe_name)
    937 {
    938  // TODO(bfredl): param could have been written as "char exe_name[MAXPATHL]"
    939  // but c_grammar.lua does not recognize it (yet).
    940  xstrlcpy(exe_name, get_vim_var_str(VV_PROGPATH), MAXPATHL * sizeof(*exe_name));
    941  char *path_end = path_tail_with_sep(exe_name);
    942  *path_end = NUL;  // remove the trailing "nvim.exe"
    943  path_end = path_tail(exe_name);
    944  *path_end = NUL;  // remove the trailing "bin/"
    945 }
    946 
    947 /// Vim getenv() wrapper with special handling of $HOME, $VIM, $VIMRUNTIME,
    948 /// allowing the user to override the Nvim runtime directory at runtime.
    949 /// Result must be freed by the caller.
    950 ///
    951 /// @param name Environment variable to expand
    952 /// @return [allocated] Expanded environment variable, or NULL
    953 char *vim_getenv(const char *name)
    954 {
    955  // init_path() should have been called before now.
    956  assert(get_vim_var_str(VV_PROGPATH)[0] != NUL);
    957 
    958 #ifdef MSWIN
    959  if (strcmp(name, "HOME") == 0) {
    960    return xstrdup(homedir);
    961  }
    962 #endif
    963 
    964  char *kos_env_path = os_getenv(name);
    965  if (kos_env_path != NULL) {
    966    return kos_env_path;
    967  }
    968 
    969  bool vimruntime = (strcmp(name, "VIMRUNTIME") == 0);
    970  if (!vimruntime && strcmp(name, "VIM") != 0) {
    971    return NULL;
    972  }
    973 
    974  // When expanding $VIMRUNTIME fails, try using $VIM/vim<version> or $VIM.
    975  // Don't do this when default_vimruntime_dir is non-empty.
    976  char *vim_path = NULL;
    977  if (vimruntime
    978      && *default_vimruntime_dir == NUL) {
    979    kos_env_path = os_getenv("VIM");    // kos_env_path was NULL.
    980    if (kos_env_path != NULL) {
    981      vim_path = vim_runtime_dir(kos_env_path);
    982      if (vim_path == NULL) {
    983        vim_path = kos_env_path;
    984      } else {
    985        xfree(kos_env_path);
    986      }
    987    }
    988  }
    989 
    990  // When expanding $VIM or $VIMRUNTIME fails, try using:
    991  // - the directory name from 'helpfile' (unless it contains '$')
    992  // - the executable name from argv[0]
    993  if (vim_path == NULL) {
    994    if (p_hf != NULL && vim_strchr(p_hf, '$') == NULL) {
    995      vim_path = p_hf;
    996    }
    997 
    998    char exe_name[MAXPATHL];
    999    // Find runtime path relative to the nvim binary: ../share/nvim/runtime
   1000    if (vim_path == NULL) {
   1001      vim_get_prefix_from_exepath(exe_name);
   1002      if (append_path(exe_name, "share/nvim/runtime/", MAXPATHL) == OK) {
   1003        vim_path = exe_name;
   1004      }
   1005    }
   1006 
   1007    if (vim_path != NULL) {
   1008      // remove the file name
   1009      char *vim_path_end = path_tail(vim_path);
   1010 
   1011      // remove "doc/" from 'helpfile', if present
   1012      if (vim_path == p_hf) {
   1013        vim_path_end = remove_tail(vim_path, vim_path_end, "doc");
   1014      }
   1015 
   1016      // for $VIM, remove "runtime/", if present
   1017      if (!vimruntime) {
   1018        vim_path_end = remove_tail(vim_path, vim_path_end, RUNTIME_DIRNAME);
   1019      }
   1020 
   1021      // remove trailing path separator
   1022      if (vim_path_end > vim_path && after_pathsep(vim_path, vim_path_end)) {
   1023        vim_path_end--;
   1024      }
   1025 
   1026      // check that the result is a directory name
   1027      assert(vim_path_end >= vim_path);
   1028      vim_path = xmemdupz(vim_path, (size_t)(vim_path_end - vim_path));
   1029 
   1030      if (!os_isdir(vim_path)) {
   1031        xfree(vim_path);
   1032        vim_path = NULL;
   1033      }
   1034    }
   1035    assert(vim_path != exe_name);
   1036  }
   1037 
   1038  // When there is a pathdef.c file we can use default_vim_dir and
   1039  // default_vimruntime_dir
   1040  if (vim_path == NULL) {
   1041    // Only use default_vimruntime_dir when it is not empty
   1042    if (vimruntime && *default_vimruntime_dir != NUL) {
   1043      vim_path = xstrdup(default_vimruntime_dir);
   1044    } else if (*default_vim_dir != NUL) {
   1045      if (vimruntime
   1046          && (vim_path = vim_runtime_dir(default_vim_dir)) == NULL) {
   1047        vim_path = xstrdup(default_vim_dir);
   1048      }
   1049    }
   1050  }
   1051 
   1052  // Set the environment variable, so that the new value can be found fast
   1053  // next time, and others can also use it (e.g. Perl).
   1054  if (vim_path != NULL) {
   1055    if (vimruntime) {
   1056      os_setenv("VIMRUNTIME", vim_path, 1);
   1057      didset_vimruntime = true;
   1058    } else {
   1059      os_setenv("VIM", vim_path, 1);
   1060      didset_vim = true;
   1061    }
   1062  }
   1063  return vim_path;
   1064 }
   1065 
   1066 /// Replace home directory by "~" in each space or comma separated file name in
   1067 /// 'src'.
   1068 ///
   1069 /// Replace home directory with tilde in each file name
   1070 ///
   1071 /// If anything fails (except when out of space) dst equals src.
   1072 ///
   1073 /// @param[in]  buf  When not NULL, uses this buffer to check whether it is
   1074 ///                  a help file. If it is then path to file is removed
   1075 ///                  completely, `one` is ignored and assumed to be true.
   1076 /// @param[in]  src  Input file names. Assumed to be a space/comma separated
   1077 ///                  list unless `one` is true.
   1078 /// @param[out]  dst  Where to put the result.
   1079 /// @param[in]  dstlen  Destination length.
   1080 /// @param[in]  one  If true, assumes source is a single file name and not
   1081 ///                  a list of them.
   1082 ///
   1083 /// @return length of the string put into dst, does not include NUL byte.
   1084 size_t home_replace(const buf_T *const buf, const char *src, char *const dst, size_t dstlen,
   1085                    const bool one)
   1086  FUNC_ATTR_NONNULL_ARG(3)
   1087 {
   1088  size_t dirlen = 0;
   1089  size_t envlen = 0;
   1090 
   1091  if (src == NULL) {
   1092    *dst = NUL;
   1093    return 0;
   1094  }
   1095 
   1096  if (buf != NULL && buf->b_help) {
   1097    const size_t dlen = xstrlcpy(dst, path_tail(src), dstlen);
   1098    return MIN(dlen, dstlen - 1);
   1099  }
   1100 
   1101  // We check both the value of the $HOME environment variable and the
   1102  // "real" home directory.
   1103  if (homedir != NULL) {
   1104    dirlen = strlen(homedir);
   1105  }
   1106 
   1107  char *homedir_env = os_getenv("HOME");
   1108 #ifdef MSWIN
   1109  if (homedir_env == NULL) {
   1110    homedir_env = os_getenv("USERPROFILE");
   1111  }
   1112 #endif
   1113  char *homedir_env_mod = homedir_env;
   1114  bool must_free = false;
   1115 
   1116  if (homedir_env_mod != NULL && *homedir_env_mod == '~') {
   1117    must_free = true;
   1118    size_t usedlen = 0;
   1119    size_t flen = strlen(homedir_env_mod);
   1120    char *fbuf = NULL;
   1121    modify_fname(":p", false, &usedlen, &homedir_env_mod, &fbuf, &flen);
   1122    flen = strlen(homedir_env_mod);
   1123    assert(homedir_env_mod != homedir_env);
   1124    if (vim_ispathsep(homedir_env_mod[flen - 1])) {
   1125      // Remove the trailing / that is added to a directory.
   1126      homedir_env_mod[flen - 1] = NUL;
   1127    }
   1128  }
   1129 
   1130  if (homedir_env_mod != NULL) {
   1131    envlen = strlen(homedir_env_mod);
   1132  }
   1133 
   1134  if (!one) {
   1135    src = skipwhite(src);
   1136  }
   1137  char *dst_p = dst;
   1138  while (*src && dstlen > 0) {
   1139    // Here we are at the beginning of a file name.
   1140    // First, check to see if the beginning of the file name matches
   1141    // $HOME or the "real" home directory. Check that there is a '/'
   1142    // after the match (so that if e.g. the file is "/home/pieter/bla",
   1143    // and the home directory is "/home/piet", the file does not end up
   1144    // as "~er/bla" (which would seem to indicate the file "bla" in user
   1145    // er's home directory)).
   1146    char *p = homedir;
   1147    size_t len = dirlen;
   1148    while (true) {
   1149      if (len
   1150          && path_fnamencmp(src, p, len) == 0
   1151          && (vim_ispathsep(src[len])
   1152              || (!one && (src[len] == ',' || src[len] == ' '))
   1153              || src[len] == NUL)) {
   1154        src += len;
   1155        if (--dstlen > 0) {
   1156          *dst_p++ = '~';
   1157        }
   1158 
   1159        // Do not add directory separator into dst, because dst is
   1160        // expected to just return the directory name without the
   1161        // directory separator '/'.
   1162        break;
   1163      }
   1164      if (p == homedir_env_mod) {
   1165        break;
   1166      }
   1167      p = homedir_env_mod;
   1168      len = envlen;
   1169    }
   1170 
   1171    if (dstlen == 0) {
   1172      break;  // Avoid overflowing below.
   1173    }
   1174    // if (!one) skip to separator: space or comma.
   1175    while (*src && (one || (*src != ',' && *src != ' ')) && --dstlen > 0) {
   1176      *dst_p++ = *src++;
   1177    }
   1178    if (dstlen == 0) {
   1179      break;  // Avoid overflowing below.
   1180    }
   1181    // Skip separator.
   1182    while ((*src == ' ' || *src == ',') && --dstlen > 0) {
   1183      *dst_p++ = *src++;
   1184    }
   1185  }
   1186  // If (dstlen == 0) out of space, what to do???
   1187 
   1188  *dst_p = NUL;
   1189 
   1190  xfree(homedir_env);
   1191 
   1192  if (must_free) {
   1193    xfree(homedir_env_mod);
   1194  }
   1195  return (size_t)(dst_p - dst);
   1196 }
   1197 
   1198 /// Like home_replace, store the replaced string in allocated memory.
   1199 /// @param buf When not NULL, check for help files
   1200 /// @param src Input file name
   1201 char *home_replace_save(buf_T *buf, const char *src)
   1202  FUNC_ATTR_NONNULL_RET
   1203 {
   1204  size_t len = 3;             // space for "~/" and trailing NUL
   1205  if (src != NULL) {          // just in case
   1206    len += strlen(src);
   1207  }
   1208  char *dst = xmalloc(len);
   1209  home_replace(buf, src, dst, len, true);
   1210  return dst;
   1211 }
   1212 
   1213 /// Function given to ExpandGeneric() to obtain an environment variable name.
   1214 char *get_env_name(expand_T *xp, int idx)
   1215 {
   1216  assert(idx >= 0);
   1217  char *envname = os_getenvname_at_index((size_t)idx);
   1218  if (envname) {
   1219    xstrlcpy(xp->xp_buf, envname, EXPAND_BUF_LEN);
   1220    xfree(envname);
   1221    return xp->xp_buf;
   1222  }
   1223  return NULL;
   1224 }
   1225 
   1226 /// Appends the head of `fname` to $PATH and sets it in the environment.
   1227 ///
   1228 /// @param fname  Full path whose parent directory will be appended to $PATH.
   1229 ///
   1230 /// @return true if `path` was appended-to
   1231 bool os_setenv_append_path(const char *fname)
   1232  FUNC_ATTR_NONNULL_ALL
   1233 {
   1234 #ifdef MSWIN
   1235 // 8191 (plus NUL) is considered the practical maximum.
   1236 # define MAX_ENVPATHLEN 8192
   1237 #else
   1238 // No prescribed maximum on unix.
   1239 # define MAX_ENVPATHLEN INT_MAX
   1240 #endif
   1241  if (!path_is_absolute(fname)) {
   1242    internal_error("os_setenv_append_path()");
   1243    return false;
   1244  }
   1245  const char *tail = path_tail_with_sep((char *)fname);
   1246  size_t dirlen = (size_t)(tail - fname);
   1247  assert(tail >= fname && dirlen + 1 < sizeof(os_buf));
   1248  xmemcpyz(os_buf, fname, dirlen);
   1249  char *path = os_getenv("PATH");
   1250  const size_t pathlen = path ? strlen(path) : 0;
   1251  const size_t newlen = pathlen + dirlen + 2;
   1252  bool retval = false;
   1253  if (newlen < MAX_ENVPATHLEN) {
   1254    char *temp = xmalloc(newlen);
   1255    if (pathlen == 0) {
   1256      temp[0] = NUL;
   1257    } else {
   1258      xstrlcpy(temp, path, newlen);
   1259      if (ENV_SEPCHAR != path[pathlen - 1]) {
   1260        xstrlcat(temp, ENV_SEPSTR, newlen);
   1261      }
   1262    }
   1263    xstrlcat(temp, os_buf, newlen);
   1264    os_setenv("PATH", temp, 1);
   1265    xfree(temp);
   1266    retval = true;
   1267  }
   1268  xfree(path);
   1269  return retval;
   1270 }
   1271 
   1272 /// Returns true if `sh` looks like it resolves to "cmd.exe".
   1273 bool os_shell_is_cmdexe(const char *sh)
   1274  FUNC_ATTR_NONNULL_ALL
   1275 {
   1276  if (*sh == NUL) {
   1277    return false;
   1278  }
   1279  if (striequal(sh, "$COMSPEC")) {
   1280    char *comspec = os_getenv_noalloc("COMSPEC");
   1281    return striequal("cmd.exe", path_tail(comspec));
   1282  }
   1283  if (striequal(sh, "cmd.exe") || striequal(sh, "cmd")) {
   1284    return true;
   1285  }
   1286  return striequal("cmd.exe", path_tail(sh));
   1287 }
   1288 
   1289 /// Removes environment variable "name" and take care of side effects.
   1290 void vim_unsetenv_ext(const char *var)
   1291 {
   1292  os_unsetenv(var);
   1293 
   1294  // "homedir" is not cleared, keep using the old value until $HOME is set.
   1295  if (STRICMP(var, "VIM") == 0) {
   1296    didset_vim = false;
   1297  } else if (STRICMP(var, "VIMRUNTIME") == 0) {
   1298    didset_vimruntime = false;
   1299  }
   1300 }
   1301 
   1302 /// Set environment variable "name" and take care of side effects.
   1303 void vim_setenv_ext(const char *name, const char *val)
   1304 {
   1305  os_setenv(name, val, 1);
   1306  if (STRICMP(name, "HOME") == 0) {
   1307    init_homedir();
   1308  } else if (didset_vim && STRICMP(name, "VIM") == 0) {
   1309    didset_vim = false;
   1310  } else if (didset_vimruntime && STRICMP(name, "VIMRUNTIME") == 0) {
   1311    didset_vimruntime = false;
   1312  }
   1313 }
   1314 
   1315 #ifdef MSWIN
   1316 /// Restore a previous environment variable value, or unset it if NULL.
   1317 /// "must_free" indicates whether "old_value" was allocated.
   1318 void restore_env_var(const char *name, char *old_value, bool must_free)
   1319 {
   1320  if (old_value != NULL) {
   1321    os_setenv(name, old_value, true);
   1322    if (must_free) {
   1323      xfree(old_value);
   1324    }
   1325    return;
   1326  }
   1327  os_unsetenv(name);
   1328 }
   1329 #endif