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