1// This is an open source non-commercial project. Dear PVS-Studio, please check
2// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
3
4// Environment inspection
5
6#include <assert.h>
7#include <uv.h>
8
9#include "nvim/vim.h"
10#include "nvim/ascii.h"
11#include "nvim/charset.h"
12#include "nvim/fileio.h"
13#include "nvim/os/os.h"
14#include "nvim/memory.h"
15#include "nvim/message.h"
16#include "nvim/path.h"
17#include "nvim/macros.h"
18#include "nvim/strings.h"
19#include "nvim/eval.h"
20#include "nvim/ex_getln.h"
21#include "nvim/version.h"
22#include "nvim/map.h"
23
24#ifdef WIN32
25#include "nvim/mbyte.h" // for utf8_to_utf16, utf16_to_utf8
26#endif
27
28#ifdef HAVE__NSGETENVIRON
29#include <crt_externs.h>
30#endif
31
32#ifdef HAVE_SYS_UTSNAME_H
33#include <sys/utsname.h>
34#endif
35
36// Because `uv_os_getenv` requires allocating, we must manage a map to maintain
37// the behavior of `os_getenv`.
38static PMap(cstr_t) *envmap;
39static uv_mutex_t mutex;
40
41void env_init(void)
42{
43 envmap = pmap_new(cstr_t)();
44 uv_mutex_init(&mutex);
45}
46
47/// Like getenv(), but returns NULL if the variable is empty.
48/// @see os_env_exists
49const char *os_getenv(const char *name)
50 FUNC_ATTR_NONNULL_ALL
51{
52 char *e;
53 size_t size = 64;
54 if (name[0] == '\0') {
55 return NULL;
56 }
57 uv_mutex_lock(&mutex);
58 if (pmap_has(cstr_t)(envmap, name)
59 && !!(e = (char *)pmap_get(cstr_t)(envmap, name))) {
60 if (e[0] != '\0') {
61 // Found non-empty cached env var.
62 // NOTE: This risks incoherence if an in-process library changes the
63 // environment without going through our os_setenv() wrapper. If
64 // that turns out to be a problem, we can just remove this codepath.
65 goto end;
66 }
67 pmap_del2(envmap, name);
68 }
69 e = xmalloc(size);
70 int r = uv_os_getenv(name, e, &size);
71 if (r == UV_ENOBUFS) {
72 e = xrealloc(e, size);
73 r = uv_os_getenv(name, e, &size);
74 }
75 if (r != 0 || size == 0 || e[0] == '\0') {
76 xfree(e);
77 e = NULL;
78 if (r != 0 && r != UV_ENOENT && r != UV_UNKNOWN) {
79 ELOG("uv_os_getenv(%s) failed: %d %s", name, r, uv_err_name(r));
80 }
81 goto end;
82 }
83 pmap_put(cstr_t)(envmap, xstrdup(name), e);
84end:
85 uv_mutex_unlock(&mutex);
86 return (e == NULL || size == 0 || e[0] == '\0') ? NULL : e;
87}
88
89/// Returns true if environment variable `name` is defined (even if empty).
90/// Returns false if not found (UV_ENOENT) or other failure.
91bool os_env_exists(const char *name)
92 FUNC_ATTR_NONNULL_ALL
93{
94 if (name[0] == '\0') {
95 return false;
96 }
97 // Use a tiny buffer because we don't care about the value: if uv_os_getenv()
98 // returns UV_ENOBUFS, the env var was found.
99 char buf[1];
100 size_t size = sizeof(buf);
101 int r = uv_os_getenv(name, buf, &size);
102 assert(r != UV_EINVAL);
103 if (r != 0 && r != UV_ENOENT && r != UV_ENOBUFS) {
104 ELOG("uv_os_getenv(%s) failed: %d %s", name, r, uv_err_name(r));
105#ifdef WIN32
106 return (r == UV_UNKNOWN);
107#endif
108 }
109 return (r == 0 || r == UV_ENOBUFS);
110}
111
112/// Sets an environment variable.
113///
114/// Windows (Vim-compat): Empty string (:let $FOO="") undefines the env var.
115///
116/// @warning Existing pointers to the result of os_getenv("foo") are
117/// INVALID after os_setenv("foo", …).
118int os_setenv(const char *name, const char *value, int overwrite)
119 FUNC_ATTR_NONNULL_ALL
120{
121 if (name[0] == '\0') {
122 return -1;
123 }
124#ifdef WIN32
125 if (!overwrite && os_getenv(name) != NULL) {
126 return 0;
127 }
128 if (value[0] == '\0') {
129 // Windows (Vim-compat): Empty string undefines the env var.
130 return os_unsetenv(name);
131 }
132#else
133 if (!overwrite && os_env_exists(name)) {
134 return 0;
135 }
136#endif
137 uv_mutex_lock(&mutex);
138 int r = uv_os_setenv(name, value);
139 assert(r != UV_EINVAL);
140 // Destroy the old map item. Do this AFTER uv_os_setenv(), because `value`
141 // could be a previous os_getenv() result.
142 pmap_del2(envmap, name);
143 if (r != 0) {
144 ELOG("uv_os_setenv(%s) failed: %d %s", name, r, uv_err_name(r));
145 }
146 uv_mutex_unlock(&mutex);
147 return r == 0 ? 0 : -1;
148}
149
150/// Unset environment variable
151int os_unsetenv(const char *name)
152 FUNC_ATTR_NONNULL_ALL
153{
154 if (name[0] == '\0') {
155 return -1;
156 }
157 uv_mutex_lock(&mutex);
158 pmap_del2(envmap, name);
159 int r = uv_os_unsetenv(name);
160 if (r != 0) {
161 ELOG("uv_os_unsetenv(%s) failed: %d %s", name, r, uv_err_name(r));
162 }
163 uv_mutex_unlock(&mutex);
164 return r == 0 ? 0 : -1;
165}
166
167char *os_getenvname_at_index(size_t index)
168{
169#ifdef _WIN32
170 wchar_t *env = GetEnvironmentStringsW();
171 if (!env) {
172 return NULL;
173 }
174 char *name = NULL;
175 size_t current_index = 0;
176 // GetEnvironmentStringsW() result has this format:
177 // var1=value1\0var2=value2\0...varN=valueN\0\0
178 for (wchar_t *it = env; *it != L'\0' || *(it + 1) != L'\0'; it++) {
179 if (index == current_index) {
180 char *utf8_str;
181 int conversion_result = utf16_to_utf8(it, -1, &utf8_str);
182 if (conversion_result != 0) {
183 EMSG2("utf16_to_utf8 failed: %d", conversion_result);
184 break;
185 }
186 size_t namesize = 0;
187 while (utf8_str[namesize] != '=' && utf8_str[namesize] != NUL) {
188 namesize++;
189 }
190 name = (char *)vim_strnsave((char_u *)utf8_str, namesize);
191 xfree(utf8_str);
192 break;
193 }
194 if (*it == L'\0') {
195 current_index++;
196 }
197 }
198
199 FreeEnvironmentStringsW(env);
200 return name;
201#else
202# if defined(HAVE__NSGETENVIRON)
203 char **environ = *_NSGetEnviron();
204# else
205 extern char **environ;
206# endif
207 // Check if index is inside the environ array and is not the last element.
208 for (size_t i = 0; i <= index; i++) {
209 if (environ[i] == NULL) {
210 return NULL;
211 }
212 }
213 char *str = environ[index];
214 size_t namesize = 0;
215 while (str[namesize] != '=' && str[namesize] != NUL) {
216 namesize++;
217 }
218 char *name = (char *)vim_strnsave((char_u *)str, namesize);
219 return name;
220#endif
221}
222
223/// Get the process ID of the Neovim process.
224///
225/// @return the process ID.
226int64_t os_get_pid(void)
227{
228#ifdef _WIN32
229 return (int64_t)GetCurrentProcessId();
230#else
231 return (int64_t)getpid();
232#endif
233}
234
235/// Gets the hostname of the current machine.
236///
237/// @param hostname Buffer to store the hostname.
238/// @param size Size of `hostname`.
239void os_get_hostname(char *hostname, size_t size)
240{
241#ifdef HAVE_SYS_UTSNAME_H
242 struct utsname vutsname;
243
244 if (uname(&vutsname) < 0) {
245 *hostname = '\0';
246 } else {
247 xstrlcpy(hostname, vutsname.nodename, size);
248 }
249#elif defined(WIN32)
250 wchar_t host_utf16[MAX_COMPUTERNAME_LENGTH + 1];
251 DWORD host_wsize = sizeof(host_utf16) / sizeof(host_utf16[0]);
252 if (GetComputerNameW(host_utf16, &host_wsize) == 0) {
253 *hostname = '\0';
254 DWORD err = GetLastError();
255 EMSG2("GetComputerNameW failed: %d", err);
256 return;
257 }
258 host_utf16[host_wsize] = '\0';
259
260 char *host_utf8;
261 int conversion_result = utf16_to_utf8(host_utf16, -1, &host_utf8);
262 if (conversion_result != 0) {
263 EMSG2("utf16_to_utf8 failed: %d", conversion_result);
264 return;
265 }
266 xstrlcpy(hostname, host_utf8, size);
267 xfree(host_utf8);
268#else
269 EMSG("os_get_hostname failed: missing uname()");
270 *hostname = '\0';
271#endif
272}
273
274/// To get the "real" home directory:
275/// - get value of $HOME
276/// For Unix:
277/// - go to that directory
278/// - do os_dirname() to get the real name of that directory.
279/// This also works with mounts and links.
280/// Don't do this for Windows, it will change the "current dir" for a drive.
281static char *homedir = NULL;
282
283void init_homedir(void)
284{
285 // In case we are called a second time.
286 xfree(homedir);
287 homedir = NULL;
288
289 const char *var = os_getenv("HOME");
290
291#ifdef WIN32
292 // Typically, $HOME is not defined on Windows, unless the user has
293 // specifically defined it for Vim's sake. However, on Windows NT
294 // platforms, $HOMEDRIVE and $HOMEPATH are automatically defined for
295 // each user. Try constructing $HOME from these.
296 if (var == NULL) {
297 const char *homedrive = os_getenv("HOMEDRIVE");
298 const char *homepath = os_getenv("HOMEPATH");
299 if (homepath == NULL) {
300 homepath = "\\";
301 }
302 if (homedrive != NULL
303 && strlen(homedrive) + strlen(homepath) < MAXPATHL) {
304 snprintf(os_buf, MAXPATHL, "%s%s", homedrive, homepath);
305 if (os_buf[0] != NUL) {
306 var = os_buf;
307 }
308 }
309 }
310 if (var == NULL) {
311 var = os_getenv("USERPROFILE");
312 }
313
314 // Weird but true: $HOME may contain an indirect reference to another
315 // variable, esp. "%USERPROFILE%". Happens when $USERPROFILE isn't set
316 // when $HOME is being set.
317 if (var != NULL && *var == '%') {
318 const char *p = strchr(var + 1, '%');
319 if (p != NULL) {
320 vim_snprintf(os_buf, (size_t)(p - var), "%s", var + 1);
321 const char *exp = os_getenv(os_buf);
322 if (exp != NULL && *exp != NUL
323 && STRLEN(exp) + STRLEN(p) < MAXPATHL) {
324 vim_snprintf(os_buf, MAXPATHL, "%s%s", exp, p + 1);
325 var = os_buf;
326 }
327 }
328 }
329
330 // Default home dir is C:/
331 // Best assumption we can make in such a situation.
332 if (var == NULL
333 // Empty means "undefined"
334 || *var == NUL) {
335 var = "C:/";
336 }
337#endif
338
339 if (var != NULL) {
340#ifdef UNIX
341 // Change to the directory and get the actual path. This resolves
342 // links. Don't do it when we can't return.
343 if (os_dirname((char_u *)os_buf, MAXPATHL) == OK && os_chdir(os_buf) == 0) {
344 if (!os_chdir(var) && os_dirname(IObuff, IOSIZE) == OK) {
345 var = (char *)IObuff;
346 }
347 if (os_chdir(os_buf) != 0) {
348 EMSG(_(e_prev_dir));
349 }
350 }
351#endif
352 homedir = xstrdup(var);
353 }
354}
355
356#if defined(EXITFREE)
357
358void free_homedir(void)
359{
360 xfree(homedir);
361}
362
363#endif
364
365/// Call expand_env() and store the result in an allocated string.
366/// This is not very memory efficient, this expects the result to be freed
367/// again soon.
368/// @param src String containing environment variables to expand
369/// @see {expand_env}
370char_u *expand_env_save(char_u *src)
371{
372 return expand_env_save_opt(src, false);
373}
374
375/// Similar to expand_env_save() but when "one" is `true` handle the string as
376/// one file name, i.e. only expand "~" at the start.
377/// @param src String containing environment variables to expand
378/// @param one Should treat as only one file name
379/// @see {expand_env}
380char_u *expand_env_save_opt(char_u *src, bool one)
381{
382 char_u *p = xmalloc(MAXPATHL);
383 expand_env_esc(src, p, MAXPATHL, false, one, NULL);
384 return p;
385}
386
387/// Expand environment variable with path name.
388/// "~/" is also expanded, using $HOME. For Unix "~user/" is expanded.
389/// Skips over "\ ", "\~" and "\$" (not for Win32 though).
390/// If anything fails no expansion is done and dst equals src.
391///
392/// @param src Input string e.g. "$HOME/vim.hlp"
393/// @param dst[out] Where to put the result
394/// @param dstlen Maximum length of the result
395void expand_env(char_u *src, char_u *dst, int dstlen)
396{
397 expand_env_esc(src, dst, dstlen, false, false, NULL);
398}
399
400/// Expand environment variable with path name and escaping.
401/// @see expand_env
402///
403/// @param srcp Input string e.g. "$HOME/vim.hlp"
404/// @param dst[out] Where to put the result
405/// @param dstlen Maximum length of the result
406/// @param esc Escape spaces in expanded variables
407/// @param one `srcp` is a single filename
408/// @param prefix Start again after this (can be NULL)
409void expand_env_esc(char_u *restrict srcp,
410 char_u *restrict dst,
411 int dstlen,
412 bool esc,
413 bool one,
414 char_u *prefix)
415 FUNC_ATTR_NONNULL_ARG(1, 2)
416{
417 char_u *tail;
418 char_u *var;
419 bool copy_char;
420 bool mustfree; // var was allocated, need to free it later
421 bool at_start = true; // at start of a name
422
423 int prefix_len = (prefix == NULL) ? 0 : (int)STRLEN(prefix);
424
425 char_u *src = skipwhite(srcp);
426 dstlen--; // leave one char space for "\,"
427 while (*src && dstlen > 0) {
428 // Skip over `=expr`.
429 if (src[0] == '`' && src[1] == '=') {
430 var = src;
431 src += 2;
432 (void)skip_expr(&src);
433 if (*src == '`') {
434 src++;
435 }
436 size_t len = (size_t)(src - var);
437 if (len > (size_t)dstlen) {
438 len = (size_t)dstlen;
439 }
440 memcpy((char *)dst, (char *)var, len);
441 dst += len;
442 dstlen -= (int)len;
443 continue;
444 }
445
446 copy_char = true;
447 if ((*src == '$') || (*src == '~' && at_start)) {
448 mustfree = false;
449
450 // The variable name is copied into dst temporarily, because it may
451 // be a string in read-only memory and a NUL needs to be appended.
452 if (*src != '~') { // environment var
453 tail = src + 1;
454 var = dst;
455 int c = dstlen - 1;
456
457#ifdef UNIX
458 // Unix has ${var-name} type environment vars
459 if (*tail == '{' && !vim_isIDc('{')) {
460 tail++; // ignore '{'
461 while (c-- > 0 && *tail != NUL && *tail != '}') {
462 *var++ = *tail++;
463 }
464 } else // NOLINT
465#endif
466 {
467 while (c-- > 0 && *tail != NUL && vim_isIDc(*tail)) {
468 *var++ = *tail++;
469 }
470 }
471
472#if defined(UNIX)
473 // Verify that we have found the end of a Unix ${VAR} style variable
474 if (src[1] == '{' && *tail != '}') {
475 var = NULL;
476 } else {
477 if (src[1] == '{') {
478 tail++;
479 }
480#endif
481 *var = NUL;
482 var = (char_u *)vim_getenv((char *)dst);
483 mustfree = true;
484#if defined(UNIX)
485 }
486#endif
487 } else if (src[1] == NUL // home directory
488 || vim_ispathsep(src[1])
489 || vim_strchr((char_u *)" ,\t\n", src[1]) != NULL) {
490 var = (char_u *)homedir;
491 tail = src + 1;
492 } else { // user directory
493#if defined(UNIX)
494 // Copy ~user to dst[], so we can put a NUL after it.
495 tail = src;
496 var = dst;
497 int c = dstlen - 1;
498 while (c-- > 0
499 && *tail
500 && vim_isfilec(*tail)
501 && !vim_ispathsep(*tail)) {
502 *var++ = *tail++;
503 }
504 *var = NUL;
505 // Get the user directory. If this fails the shell is used to expand
506 // ~user, which is slower and may fail on old versions of /bin/sh.
507 var = (*dst == NUL) ? NULL
508 : (char_u *)os_get_user_directory((char *)dst + 1);
509 mustfree = true;
510 if (var == NULL) {
511 expand_T xpc;
512
513 ExpandInit(&xpc);
514 xpc.xp_context = EXPAND_FILES;
515 var = ExpandOne(&xpc, dst, NULL,
516 WILD_ADD_SLASH|WILD_SILENT, WILD_EXPAND_FREE);
517 mustfree = true;
518 }
519#else
520 // cannot expand user's home directory, so don't try
521 var = NULL;
522 tail = (char_u *)""; // for gcc
523#endif // UNIX
524 }
525
526#ifdef BACKSLASH_IN_FILENAME
527 // If 'shellslash' is set change backslashes to forward slashes.
528 // Can't use slash_adjust(), p_ssl may be set temporarily.
529 if (p_ssl && var != NULL && vim_strchr(var, '\\') != NULL) {
530 char_u *p = vim_strsave(var);
531
532 if (mustfree) {
533 xfree(var);
534 }
535 var = p;
536 mustfree = true;
537 forward_slash(var);
538 }
539#endif
540
541 // If "var" contains white space, escape it with a backslash.
542 // Required for ":e ~/tt" when $HOME includes a space.
543 if (esc && var != NULL && vim_strpbrk(var, (char_u *)" \t") != NULL) {
544 char_u *p = vim_strsave_escaped(var, (char_u *)" \t");
545
546 if (mustfree) {
547 xfree(var);
548 }
549 var = p;
550 mustfree = true;
551 }
552
553 if (var != NULL && *var != NUL
554 && (STRLEN(var) + STRLEN(tail) + 1 < (unsigned)dstlen)) {
555 STRCPY(dst, var);
556 dstlen -= (int)STRLEN(var);
557 int c = (int)STRLEN(var);
558 // if var[] ends in a path separator and tail[] starts
559 // with it, skip a character
560 if (*var != NUL && after_pathsep((char *)dst, (char *)dst + c)
561#if defined(BACKSLASH_IN_FILENAME)
562 && dst[-1] != ':'
563#endif
564 && vim_ispathsep(*tail))
565 ++tail;
566 dst += c;
567 src = tail;
568 copy_char = false;
569 }
570 if (mustfree) {
571 xfree(var);
572 }
573 }
574
575 if (copy_char) { // copy at least one char
576 // Recognize the start of a new name, for '~'.
577 // Don't do this when "one" is true, to avoid expanding "~" in
578 // ":edit foo ~ foo".
579 at_start = false;
580 if (src[0] == '\\' && src[1] != NUL) {
581 *dst++ = *src++;
582 --dstlen;
583 } else if ((src[0] == ' ' || src[0] == ',') && !one) {
584 at_start = true;
585 }
586 if (dstlen > 0) {
587 *dst++ = *src++;
588 dstlen--;
589
590 if (prefix != NULL
591 && src - prefix_len >= srcp
592 && STRNCMP(src - prefix_len, prefix, prefix_len) == 0) {
593 at_start = true;
594 }
595 }
596 }
597 }
598 *dst = NUL;
599}
600
601/// Check if the directory "vimdir/<version>" or "vimdir/runtime" exists.
602/// Return NULL if not, return its name in allocated memory otherwise.
603/// @param vimdir directory to test
604static char *vim_version_dir(const char *vimdir)
605{
606 if (vimdir == NULL || *vimdir == NUL) {
607 return NULL;
608 }
609 char *p = concat_fnames(vimdir, VIM_VERSION_NODOT, true);
610 if (os_isdir((char_u *)p)) {
611 return p;
612 }
613 xfree(p);
614 p = concat_fnames(vimdir, RUNTIME_DIRNAME, true);
615 if (os_isdir((char_u *)p)) {
616 return p;
617 }
618 xfree(p);
619 return NULL;
620}
621
622/// If `dirname + "/"` precedes `pend` in the path, return the pointer to
623/// `dirname + "/" + pend`. Otherwise return `pend`.
624///
625/// Examples (path = /usr/local/share/nvim/runtime/doc/help.txt):
626///
627/// pend = help.txt
628/// dirname = doc
629/// -> doc/help.txt
630///
631/// pend = doc/help.txt
632/// dirname = runtime
633/// -> runtime/doc/help.txt
634///
635/// pend = runtime/doc/help.txt
636/// dirname = vim74
637/// -> runtime/doc/help.txt
638///
639/// @param path Path to a file
640/// @param pend A suffix of the path
641/// @param dirname The immediate path fragment before the pend
642/// @return The new pend including dirname or just pend
643static char *remove_tail(char *path, char *pend, char *dirname)
644{
645 size_t len = STRLEN(dirname);
646 char *new_tail = pend - len - 1;
647
648 if (new_tail >= path
649 && fnamencmp((char_u *)new_tail, (char_u *)dirname, len) == 0
650 && (new_tail == path || after_pathsep(path, new_tail))) {
651 return new_tail;
652 }
653 return pend;
654}
655
656/// Iterates $PATH-like delimited list `val`.
657///
658/// @note Environment variables must not be modified during iteration.
659///
660/// @param[in] delim Delimiter character.
661/// @param[in] val Value of the environment variable to iterate over.
662/// @param[in] iter Pointer used for iteration. Must be NULL on first
663/// iteration.
664/// @param[out] dir Location where pointer to the start of the current
665/// directory name should be saved. May be set to NULL.
666/// @param[out] len Location where current directory length should be saved.
667///
668/// @return Next iter argument value or NULL when iteration should stop.
669const void *vim_env_iter(const char delim,
670 const char *const val,
671 const void *const iter,
672 const char **const dir,
673 size_t *const len)
674 FUNC_ATTR_NONNULL_ARG(2, 4, 5) FUNC_ATTR_WARN_UNUSED_RESULT
675{
676 const char *varval = (const char *) iter;
677 if (varval == NULL) {
678 varval = val;
679 }
680 *dir = varval;
681 const char *const dirend = strchr(varval, delim);
682 if (dirend == NULL) {
683 *len = strlen(varval);
684 return NULL;
685 } else {
686 *len = (size_t) (dirend - varval);
687 return dirend + 1;
688 }
689}
690
691/// Iterates $PATH-like delimited list `val` in reverse order.
692///
693/// @note Environment variables must not be modified during iteration.
694///
695/// @param[in] delim Delimiter character.
696/// @param[in] val Value of the environment variable to iterate over.
697/// @param[in] iter Pointer used for iteration. Must be NULL on first
698/// iteration.
699/// @param[out] dir Location where pointer to the start of the current
700/// directory name should be saved. May be set to NULL.
701/// @param[out] len Location where current directory length should be saved.
702///
703/// @return Next iter argument value or NULL when iteration should stop.
704const void *vim_env_iter_rev(const char delim,
705 const char *const val,
706 const void *const iter,
707 const char **const dir,
708 size_t *const len)
709 FUNC_ATTR_NONNULL_ARG(2, 4, 5) FUNC_ATTR_WARN_UNUSED_RESULT
710{
711 const char *varend = (const char *) iter;
712 if (varend == NULL) {
713 varend = val + strlen(val) - 1;
714 }
715 const size_t varlen = (size_t)(varend - val) + 1;
716 const char *const colon = xmemrchr(val, (uint8_t)delim, varlen);
717 if (colon == NULL) {
718 *len = varlen;
719 *dir = val;
720 return NULL;
721 } else {
722 *dir = colon + 1;
723 *len = (size_t) (varend - colon);
724 return colon - 1;
725 }
726}
727
728/// Vim getenv() wrapper with special handling of $HOME, $VIM, $VIMRUNTIME,
729/// allowing the user to override the Nvim runtime directory at runtime.
730/// Result must be freed by the caller.
731///
732/// @param name Environment variable to expand
733/// @return [allocated] Expanded environment variable, or NULL
734char *vim_getenv(const char *name)
735{
736 // init_path() should have been called before now.
737 assert(get_vim_var_str(VV_PROGPATH)[0] != NUL);
738
739#ifdef WIN32
740 if (strcmp(name, "HOME") == 0) {
741 return xstrdup(homedir);
742 }
743#endif
744
745 const char *kos_env_path = os_getenv(name);
746 if (kos_env_path != NULL) {
747 return xstrdup(kos_env_path);
748 }
749
750 bool vimruntime = (strcmp(name, "VIMRUNTIME") == 0);
751 if (!vimruntime && strcmp(name, "VIM") != 0) {
752 return NULL;
753 }
754
755 // When expanding $VIMRUNTIME fails, try using $VIM/vim<version> or $VIM.
756 // Don't do this when default_vimruntime_dir is non-empty.
757 char *vim_path = NULL;
758 if (vimruntime
759#ifdef HAVE_PATHDEF
760 && *default_vimruntime_dir == NUL
761#endif
762 ) {
763 kos_env_path = os_getenv("VIM");
764 if (kos_env_path != NULL) {
765 vim_path = vim_version_dir(kos_env_path);
766 if (vim_path == NULL) {
767 vim_path = xstrdup(kos_env_path);
768 }
769 }
770 }
771
772 // When expanding $VIM or $VIMRUNTIME fails, try using:
773 // - the directory name from 'helpfile' (unless it contains '$')
774 // - the executable name from argv[0]
775 if (vim_path == NULL) {
776 if (p_hf != NULL && vim_strchr(p_hf, '$') == NULL) {
777 vim_path = (char *)p_hf;
778 }
779
780 char exe_name[MAXPATHL];
781 // Find runtime path relative to the nvim binary: ../share/nvim/runtime
782 if (vim_path == NULL) {
783 xstrlcpy(exe_name, (char *)get_vim_var_str(VV_PROGPATH),
784 sizeof(exe_name));
785 char *path_end = (char *)path_tail_with_sep((char_u *)exe_name);
786 *path_end = '\0'; // remove the trailing "nvim.exe"
787 path_end = (char *)path_tail((char_u *)exe_name);
788 *path_end = '\0'; // remove the trailing "bin/"
789 if (append_path(
790 exe_name,
791 "share" _PATHSEPSTR "nvim" _PATHSEPSTR "runtime" _PATHSEPSTR,
792 MAXPATHL) == OK) {
793 vim_path = exe_name; // -V507
794 }
795 }
796
797 if (vim_path != NULL) {
798 // remove the file name
799 char *vim_path_end = (char *)path_tail((char_u *)vim_path);
800
801 // remove "doc/" from 'helpfile', if present
802 if (vim_path == (char *)p_hf) {
803 vim_path_end = remove_tail(vim_path, vim_path_end, "doc");
804 }
805
806 // for $VIM, remove "runtime/" or "vim54/", if present
807 if (!vimruntime) {
808 vim_path_end = remove_tail(vim_path, vim_path_end, RUNTIME_DIRNAME);
809 vim_path_end = remove_tail(vim_path, vim_path_end, VIM_VERSION_NODOT);
810 }
811
812 // remove trailing path separator
813 if (vim_path_end > vim_path && after_pathsep(vim_path, vim_path_end)) {
814 vim_path_end--;
815 }
816
817 // check that the result is a directory name
818 assert(vim_path_end >= vim_path);
819 vim_path = xstrndup(vim_path, (size_t)(vim_path_end - vim_path));
820
821 if (!os_isdir((char_u *)vim_path)) {
822 xfree(vim_path);
823 vim_path = NULL;
824 }
825 }
826 assert(vim_path != exe_name);
827 }
828
829#ifdef HAVE_PATHDEF
830 // When there is a pathdef.c file we can use default_vim_dir and
831 // default_vimruntime_dir
832 if (vim_path == NULL) {
833 // Only use default_vimruntime_dir when it is not empty
834 if (vimruntime && *default_vimruntime_dir != NUL) {
835 vim_path = xstrdup(default_vimruntime_dir);
836 } else if (*default_vim_dir != NUL) {
837 if (vimruntime
838 && (vim_path = vim_version_dir(default_vim_dir)) == NULL) {
839 vim_path = xstrdup(default_vim_dir);
840 }
841 }
842 }
843#endif
844
845 // Set the environment variable, so that the new value can be found fast
846 // next time, and others can also use it (e.g. Perl).
847 if (vim_path != NULL) {
848 if (vimruntime) {
849 os_setenv("VIMRUNTIME", vim_path, 1);
850 didset_vimruntime = true;
851 } else {
852 os_setenv("VIM", vim_path, 1);
853 didset_vim = true;
854 }
855 }
856 return vim_path;
857}
858
859/// Replace home directory by "~" in each space or comma separated file name in
860/// 'src'.
861///
862/// Replace home directory with tilde in each file name
863///
864/// If anything fails (except when out of space) dst equals src.
865///
866/// @param[in] buf When not NULL, uses this buffer to check whether it is
867/// a help file. If it is then path to file is removed
868/// completely, `one` is ignored and assumed to be true.
869/// @param[in] src Input file names. Assumed to be a space/comma separated
870/// list unless `one` is true.
871/// @param[out] dst Where to put the result.
872/// @param[in] dstlen Destination length.
873/// @param[in] one If true, assumes source is a single file name and not
874/// a list of them.
875///
876/// @return length of the string put into dst, does not include NUL byte.
877size_t home_replace(const buf_T *const buf, const char_u *src,
878 char_u *const dst, size_t dstlen, const bool one)
879 FUNC_ATTR_NONNULL_ARG(3)
880{
881 size_t dirlen = 0;
882 size_t envlen = 0;
883
884 if (src == NULL) {
885 *dst = NUL;
886 return 0;
887 }
888
889 if (buf != NULL && buf->b_help) {
890 const size_t dlen = xstrlcpy((char *)dst, (char *)path_tail(src), dstlen);
891 return MIN(dlen, dstlen - 1);
892 }
893
894 // We check both the value of the $HOME environment variable and the
895 // "real" home directory.
896 if (homedir != NULL) {
897 dirlen = strlen(homedir);
898 }
899
900 const char *homedir_env = os_getenv("HOME");
901#ifdef WIN32
902 if (homedir_env == NULL) {
903 homedir_env = os_getenv("USERPROFILE");
904 }
905#endif
906 char *homedir_env_mod = (char *)homedir_env;
907 bool must_free = false;
908
909 if (homedir_env_mod != NULL && *homedir_env_mod == '~') {
910 must_free = true;
911 size_t usedlen = 0;
912 size_t flen = strlen(homedir_env_mod);
913 char_u *fbuf = NULL;
914 (void)modify_fname((char_u *)":p", false, &usedlen,
915 (char_u **)&homedir_env_mod, &fbuf, &flen);
916 flen = strlen(homedir_env_mod);
917 assert(homedir_env_mod != homedir_env);
918 if (vim_ispathsep(homedir_env_mod[flen - 1])) {
919 // Remove the trailing / that is added to a directory.
920 homedir_env_mod[flen - 1] = NUL;
921 }
922 }
923
924 if (homedir_env_mod != NULL) {
925 envlen = strlen(homedir_env_mod);
926 }
927
928 if (!one) {
929 src = skipwhite(src);
930 }
931 char *dst_p = (char *)dst;
932 while (*src && dstlen > 0) {
933 // Here we are at the beginning of a file name.
934 // First, check to see if the beginning of the file name matches
935 // $HOME or the "real" home directory. Check that there is a '/'
936 // after the match (so that if e.g. the file is "/home/pieter/bla",
937 // and the home directory is "/home/piet", the file does not end up
938 // as "~er/bla" (which would seem to indicate the file "bla" in user
939 // er's home directory)).
940 char *p = homedir;
941 size_t len = dirlen;
942 for (;;) {
943 if (len
944 && fnamencmp(src, (char_u *)p, len) == 0
945 && (vim_ispathsep(src[len])
946 || (!one && (src[len] == ',' || src[len] == ' '))
947 || src[len] == NUL)) {
948 src += len;
949 if (--dstlen > 0) {
950 *dst_p++ = '~';
951 }
952
953 // If it's just the home directory, add "/".
954 if (!vim_ispathsep(src[0]) && --dstlen > 0) {
955 *dst_p++ = '/';
956 }
957 break;
958 }
959 if (p == homedir_env_mod) {
960 break;
961 }
962 p = homedir_env_mod;
963 len = envlen;
964 }
965
966 // if (!one) skip to separator: space or comma.
967 while (*src && (one || (*src != ',' && *src != ' ')) && --dstlen > 0) {
968 *dst_p++ = (char)(*src++);
969 }
970 // Skip separator.
971 while ((*src == ' ' || *src == ',') && --dstlen > 0) {
972 *dst_p++ = (char)(*src++);
973 }
974 }
975 // If (dstlen == 0) out of space, what to do???
976
977 *dst_p = NUL;
978
979 if (must_free) {
980 xfree(homedir_env_mod);
981 }
982 return (size_t)(dst_p - (char *)dst);
983}
984
985/// Like home_replace, store the replaced string in allocated memory.
986/// @param buf When not NULL, check for help files
987/// @param src Input file name
988char_u * home_replace_save(buf_T *buf, char_u *src) FUNC_ATTR_NONNULL_RET
989{
990 size_t len = 3; // space for "~/" and trailing NUL
991 if (src != NULL) { // just in case
992 len += STRLEN(src);
993 }
994 char_u *dst = xmalloc(len);
995 home_replace(buf, src, dst, len, true);
996 return dst;
997}
998
999
1000/// Function given to ExpandGeneric() to obtain an environment variable name.
1001char_u *get_env_name(expand_T *xp, int idx)
1002{
1003# define ENVNAMELEN 100
1004 // this static buffer is needed to avoid a memory leak in ExpandGeneric
1005 static char_u name[ENVNAMELEN];
1006 assert(idx >= 0);
1007 char *envname = os_getenvname_at_index((size_t)idx);
1008 if (envname) {
1009 STRLCPY(name, envname, ENVNAMELEN);
1010 xfree(envname);
1011 return name;
1012 }
1013 return NULL;
1014}
1015
1016/// Appends the head of `fname` to $PATH and sets it in the environment.
1017///
1018/// @param fname Full path whose parent directory will be appended to $PATH.
1019///
1020/// @return true if `path` was appended-to
1021bool os_setenv_append_path(const char *fname)
1022 FUNC_ATTR_NONNULL_ALL
1023{
1024#ifdef WIN32
1025// 8191 (plus NUL) is considered the practical maximum.
1026# define MAX_ENVPATHLEN 8192
1027#else
1028// No prescribed maximum on unix.
1029# define MAX_ENVPATHLEN INT_MAX
1030#endif
1031 if (!path_is_absolute((char_u *)fname)) {
1032 internal_error("os_setenv_append_path()");
1033 return false;
1034 }
1035 const char *tail = (char *)path_tail_with_sep((char_u *)fname);
1036 size_t dirlen = (size_t)(tail - fname);
1037 assert(tail >= fname && dirlen + 1 < sizeof(os_buf));
1038 xstrlcpy(os_buf, fname, dirlen + 1);
1039 const char *path = os_getenv("PATH");
1040 const size_t pathlen = path ? strlen(path) : 0;
1041 const size_t newlen = pathlen + dirlen + 2;
1042 if (newlen < MAX_ENVPATHLEN) {
1043 char *temp = xmalloc(newlen);
1044 if (pathlen == 0) {
1045 temp[0] = NUL;
1046 } else {
1047 xstrlcpy(temp, path, newlen);
1048 xstrlcat(temp, ENV_SEPSTR, newlen);
1049 }
1050 xstrlcat(temp, os_buf, newlen);
1051 os_setenv("PATH", temp, 1);
1052 xfree(temp);
1053 return true;
1054 }
1055 return false;
1056}
1057
1058/// Returns true if `sh` looks like it resolves to "cmd.exe".
1059bool os_shell_is_cmdexe(const char *sh)
1060 FUNC_ATTR_NONNULL_ALL
1061{
1062 if (*sh == NUL) {
1063 return false;
1064 }
1065 if (striequal(sh, "$COMSPEC")) {
1066 const char *comspec = os_getenv("COMSPEC");
1067 return striequal("cmd.exe", (char *)path_tail((char_u *)comspec));
1068 }
1069 if (striequal(sh, "cmd.exe") || striequal(sh, "cmd")) {
1070 return true;
1071 }
1072 return striequal("cmd.exe", (char *)path_tail((char_u *)sh));
1073}
1074