neovim

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

stdpaths.c (8376B)


      1 #include <assert.h>
      2 #include <stdbool.h>
      3 #include <string.h>
      4 
      5 #include "klib/kvec.h"
      6 #include "nvim/ascii_defs.h"
      7 #include "nvim/fileio.h"
      8 #include "nvim/globals.h"
      9 #include "nvim/memory.h"
     10 #include "nvim/os/os.h"
     11 #include "nvim/os/os_defs.h"
     12 #include "nvim/os/stdpaths_defs.h"
     13 #include "nvim/path.h"
     14 #include "nvim/strings.h"
     15 
     16 #include "os/stdpaths.c.generated.h"
     17 
     18 /// Names of the environment variables, mapped to XDGVarType values
     19 static const char *xdg_env_vars[] = {
     20  [kXDGConfigHome] = "XDG_CONFIG_HOME",
     21  [kXDGDataHome] = "XDG_DATA_HOME",
     22  [kXDGCacheHome] = "XDG_CACHE_HOME",
     23  [kXDGStateHome] = "XDG_STATE_HOME",
     24  [kXDGRuntimeDir] = "XDG_RUNTIME_DIR",
     25  [kXDGConfigDirs] = "XDG_CONFIG_DIRS",
     26  [kXDGDataDirs] = "XDG_DATA_DIRS",
     27 };
     28 
     29 #ifdef MSWIN
     30 static const char *const xdg_defaults_env_vars[] = {
     31  [kXDGConfigHome] = "LOCALAPPDATA",
     32  [kXDGDataHome] = "LOCALAPPDATA",
     33  [kXDGCacheHome] = "TEMP",
     34  [kXDGStateHome] = "LOCALAPPDATA",
     35  [kXDGRuntimeDir] = NULL,  // Decided by vim_mktempdir().
     36  [kXDGConfigDirs] = NULL,
     37  [kXDGDataDirs] = NULL,
     38 };
     39 #endif
     40 
     41 /// Defaults for XDGVarType values
     42 ///
     43 /// Used in case environment variables contain nothing. Need to be expanded.
     44 static const char *const xdg_defaults[] = {
     45 #ifdef MSWIN
     46  [kXDGConfigHome] = "~\\AppData\\Local",
     47  [kXDGDataHome] = "~\\AppData\\Local",
     48  [kXDGCacheHome] = "~\\AppData\\Local\\Temp",
     49  [kXDGStateHome] = "~\\AppData\\Local",
     50  [kXDGRuntimeDir] = NULL,  // Decided by vim_mktempdir().
     51  [kXDGConfigDirs] = NULL,
     52  [kXDGDataDirs] = NULL,
     53 #else
     54  [kXDGConfigHome] = "~/.config",
     55  [kXDGDataHome] = "~/.local/share",
     56  [kXDGCacheHome] = "~/.cache",
     57  [kXDGStateHome] = "~/.local/state",
     58  [kXDGRuntimeDir] = NULL,  // Decided by vim_mktempdir().
     59  [kXDGConfigDirs] = "/etc/xdg/",
     60  [kXDGDataDirs] = "/usr/local/share/:/usr/share/",
     61 #endif
     62 };
     63 
     64 /// Gets the value of $NVIM_APPNAME, or "nvim" if not set.
     65 ///
     66 /// @param namelike Write "name-like" value (no path separators) in `NameBuff`.
     67 ///
     68 /// @return $NVIM_APPNAME value
     69 const char *get_appname(bool namelike)
     70 {
     71  const char *env_val = os_getenv_noalloc("NVIM_APPNAME");
     72 
     73  if (!env_val) {
     74    xstrlcpy(NameBuff, "nvim", sizeof(NameBuff));
     75  }
     76 
     77  if (namelike) {
     78    // Appname may be a relative path, replace slashes to make it name-like.
     79    memchrsub(NameBuff, '/', '-', sizeof(NameBuff));
     80    memchrsub(NameBuff, '\\', '-', sizeof(NameBuff));
     81  }
     82 
     83  return NameBuff;
     84 }
     85 
     86 /// Ensure that APPNAME is valid. Must be a name or relative path.
     87 bool appname_is_valid(void)
     88 {
     89  const char *appname = get_appname(false);
     90  if (path_is_absolute(appname)
     91      // TODO(justinmk): on Windows, path_is_absolute says "/" is NOT absolute. Should it?
     92      || strequal(appname, "/")
     93      || strequal(appname, "\\")
     94      || strequal(appname, ".")
     95      || strequal(appname, "..")
     96 #ifdef BACKSLASH_IN_FILENAME
     97      || strstr(appname, "\\..") != NULL
     98      || strstr(appname, "..\\") != NULL
     99 #endif
    100      || strstr(appname, "/..") != NULL
    101      || strstr(appname, "../") != NULL) {
    102    return false;
    103  }
    104  return true;
    105 }
    106 
    107 /// Remove duplicate directories in the given XDG directory.
    108 /// @param[in]  List of directories possibly with duplicates
    109 /// @param[out]  List of directories without duplicates
    110 static char *xdg_remove_duplicate(char *ret, const char *sep)
    111 {
    112  kvec_t(char *) data = KV_INITIAL_VALUE;
    113  char *saveptr;
    114 
    115  char *token = os_strtok(ret, sep, &saveptr);
    116  while (token != NULL) {
    117    // Check if the directory is not already in the list
    118    bool is_duplicate = false;
    119    for (size_t i = 0; i < data.size; i++) {
    120      if (path_fnamecmp(kv_A(data, i), token) == 0) {
    121        is_duplicate = true;
    122        break;
    123      }
    124    }
    125    // If it's not a duplicate, add it to the list
    126    if (!is_duplicate) {
    127      kv_push(data, token);
    128    }
    129    token = os_strtok(NULL, sep, &saveptr);
    130  }
    131 
    132  StringBuilder result = KV_INITIAL_VALUE;
    133 
    134  for (size_t i = 0; i < data.size; i++) {
    135    if (i == 0) {
    136      kv_printf(result, "%s", kv_A(data, i));
    137    } else {
    138      kv_printf(result, "%s%s", sep, kv_A(data, i));
    139    }
    140  }
    141 
    142  kv_destroy(data);
    143  xfree(ret);
    144  return result.items;
    145 }
    146 
    147 /// Return XDG variable value
    148 ///
    149 /// @param[in]  idx  XDG variable to use.
    150 ///
    151 /// @return [allocated] variable value.
    152 char *stdpaths_get_xdg_var(const XDGVarType idx)
    153  FUNC_ATTR_WARN_UNUSED_RESULT
    154 {
    155  const char *const env = xdg_env_vars[idx];
    156  const char *const fallback = xdg_defaults[idx];
    157 
    158  char *env_val = os_getenv(env);
    159 
    160 #ifdef MSWIN
    161  if (env_val == NULL && xdg_defaults_env_vars[idx] != NULL) {
    162    env_val = os_getenv(xdg_defaults_env_vars[idx]);
    163  }
    164 #else
    165  if (env_val == NULL && os_env_exists(env, false)) {
    166    env_val = xstrdup("");
    167  }
    168 #endif
    169 
    170  char *ret = NULL;
    171  if (env_val != NULL) {
    172    ret = env_val;
    173  } else if (fallback) {
    174    ret = expand_env_save((char *)fallback);
    175  } else if (idx == kXDGRuntimeDir) {
    176    // Special-case: stdpath('run') is defined at startup.
    177    ret = vim_gettempdir();
    178    if (ret == NULL) {
    179      ret = "/tmp/";
    180    }
    181    size_t len = strlen(ret);
    182    ret = xmemdupz(ret, len >= 2 ? len - 1 : 0);  // Trim trailing slash.
    183  }
    184 
    185  if ((idx == kXDGDataDirs || idx == kXDGConfigDirs) && ret != NULL) {
    186    ret = xdg_remove_duplicate(ret, ENV_SEPSTR);
    187  }
    188 
    189  return ret;
    190 }
    191 
    192 /// Return Nvim-specific XDG directory subpath.
    193 ///
    194 /// Windows: Uses "…/$NVIM_APPNAME-data" for kXDGDataHome to avoid storing
    195 /// configuration and data files in the same path. #4403
    196 ///
    197 /// @param[in]  idx  XDG directory to use.
    198 ///
    199 /// @return [allocated] "{xdg_directory}/$NVIM_APPNAME"
    200 char *get_xdg_home(const XDGVarType idx)
    201  FUNC_ATTR_WARN_UNUSED_RESULT
    202 {
    203  char *dir = stdpaths_get_xdg_var(idx);
    204  const char *appname = get_appname(false);
    205  size_t appname_len = strlen(appname);
    206  assert(appname_len < (IOSIZE - sizeof("-data")));
    207 
    208  if (dir) {
    209    xmemcpyz(IObuff, appname, appname_len);
    210 #if defined(MSWIN)
    211    if (idx == kXDGDataHome || idx == kXDGStateHome) {
    212      xstrlcat(IObuff, "-data", IOSIZE);
    213    }
    214 #endif
    215    dir = concat_fnames_realloc(dir, IObuff, true);
    216 
    217 #ifdef BACKSLASH_IN_FILENAME
    218    slash_adjust(dir);
    219 #endif
    220  }
    221  return dir;
    222 }
    223 
    224 /// Return subpath of $XDG_CACHE_HOME
    225 ///
    226 /// @param[in]  fname  New component of the path.
    227 ///
    228 /// @return [allocated] `$XDG_CACHE_HOME/$NVIM_APPNAME/{fname}`
    229 char *stdpaths_user_cache_subpath(const char *fname)
    230  FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
    231 {
    232  return concat_fnames_realloc(get_xdg_home(kXDGCacheHome), fname, true);
    233 }
    234 
    235 /// Return subpath of $XDG_CONFIG_HOME
    236 ///
    237 /// @param[in]  fname  New component of the path.
    238 ///
    239 /// @return [allocated] `$XDG_CONFIG_HOME/$NVIM_APPNAME/{fname}`
    240 char *stdpaths_user_conf_subpath(const char *fname)
    241  FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
    242 {
    243  return concat_fnames_realloc(get_xdg_home(kXDGConfigHome), fname, true);
    244 }
    245 
    246 /// Return subpath of $XDG_DATA_HOME
    247 ///
    248 /// @param[in]  fname  New component of the path.
    249 ///
    250 /// @return [allocated] `$XDG_DATA_HOME/$NVIM_APPNAME/{fname}`
    251 char *stdpaths_user_data_subpath(const char *fname)
    252  FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
    253 {
    254  return concat_fnames_realloc(get_xdg_home(kXDGDataHome), fname, true);
    255 }
    256 
    257 /// Return subpath of $XDG_STATE_HOME
    258 ///
    259 /// @param[in]  fname  New component of the path.
    260 /// @param[in]  trailing_pathseps  Amount of trailing path separators to add.
    261 /// @param[in]  escape_commas  If true, all commas will be escaped.
    262 ///
    263 /// @return [allocated] `$XDG_STATE_HOME/$NVIM_APPNAME/{fname}`.
    264 char *stdpaths_user_state_subpath(const char *fname, const size_t trailing_pathseps,
    265                                  const bool escape_commas)
    266  FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
    267 {
    268  char *ret = concat_fnames_realloc(get_xdg_home(kXDGStateHome), fname, true);
    269  const size_t len = strlen(ret);
    270  const size_t numcommas = (escape_commas ? memcnt(ret, ',', len) : 0);
    271  if (numcommas || trailing_pathseps) {
    272    ret = xrealloc(ret, len + trailing_pathseps + numcommas + 1);
    273    for (size_t i = 0; i < len + numcommas; i++) {
    274      if (ret[i] == ',') {
    275        memmove(ret + i + 1, ret + i, len - i + numcommas);
    276        ret[i] = '\\';
    277        i++;
    278      }
    279    }
    280    if (trailing_pathseps) {
    281      memset(ret + len + numcommas, PATHSEP, trailing_pathseps);
    282    }
    283    ret[len + trailing_pathseps + numcommas] = NUL;
    284  }
    285  return ret;
    286 }