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 }