|  | /* Path manipulation routines for GDB and gdbserver. | 
|  |  | 
|  | Copyright (C) 1986-2025 Free Software Foundation, Inc. | 
|  |  | 
|  | This file is part of GDB. | 
|  |  | 
|  | This program is free software; you can redistribute it and/or modify | 
|  | it under the terms of the GNU General Public License as published by | 
|  | the Free Software Foundation; either version 3 of the License, or | 
|  | (at your option) any later version. | 
|  |  | 
|  | This program is distributed in the hope that it will be useful, | 
|  | but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|  | GNU General Public License for more details. | 
|  |  | 
|  | You should have received a copy of the GNU General Public License | 
|  | along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ | 
|  |  | 
|  | #include "pathstuff.h" | 
|  | #include "host-defs.h" | 
|  | #include "filenames.h" | 
|  | #include "gdb_tilde_expand.h" | 
|  |  | 
|  | #ifdef USE_WIN32API | 
|  | #include <windows.h> | 
|  | #endif | 
|  |  | 
|  | /* See gdbsupport/pathstuff.h.  */ | 
|  |  | 
|  | char *current_directory; | 
|  |  | 
|  | /* See gdbsupport/pathstuff.h.  */ | 
|  |  | 
|  | gdb::unique_xmalloc_ptr<char> | 
|  | gdb_realpath (const char *filename) | 
|  | { | 
|  | /* On most hosts, we rely on canonicalize_file_name to compute | 
|  | the FILENAME's realpath. | 
|  |  | 
|  | But the situation is slightly more complex on Windows, due to some | 
|  | versions of GCC which were reported to generate paths where | 
|  | backslashes (the directory separator) were doubled.  For instance: | 
|  | c:\\some\\double\\slashes\\dir | 
|  | ... instead of ... | 
|  | c:\some\double\slashes\dir | 
|  | Those double-slashes were getting in the way when comparing paths, | 
|  | for instance when trying to insert a breakpoint as follow: | 
|  | (gdb) b c:/some/double/slashes/dir/foo.c:4 | 
|  | No source file named c:/some/double/slashes/dir/foo.c:4. | 
|  | (gdb) b c:\some\double\slashes\dir\foo.c:4 | 
|  | No source file named c:\some\double\slashes\dir\foo.c:4. | 
|  | To prevent this from happening, we need this function to always | 
|  | strip those extra backslashes.  While canonicalize_file_name does | 
|  | perform this simplification, it only works when the path is valid. | 
|  | Since the simplification would be useful even if the path is not | 
|  | valid (one can always set a breakpoint on a file, even if the file | 
|  | does not exist locally), we rely instead on GetFullPathName to | 
|  | perform the canonicalization.  */ | 
|  |  | 
|  | #if defined (_WIN32) | 
|  | { | 
|  | char buf[MAX_PATH]; | 
|  | DWORD len = GetFullPathName (filename, MAX_PATH, buf, NULL); | 
|  |  | 
|  | /* The file system is case-insensitive but case-preserving. | 
|  | So it is important we do not lowercase the path.  Otherwise, | 
|  | we might not be able to display the original casing in a given | 
|  | path.  */ | 
|  | if (len > 0 && len < MAX_PATH) | 
|  | return make_unique_xstrdup (buf); | 
|  | } | 
|  | #else | 
|  | { | 
|  | char *rp = canonicalize_file_name (filename); | 
|  |  | 
|  | if (rp != NULL) | 
|  | return gdb::unique_xmalloc_ptr<char> (rp); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | /* This system is a lost cause, just dup the buffer.  */ | 
|  | return make_unique_xstrdup (filename); | 
|  | } | 
|  |  | 
|  | /* See gdbsupport/pathstuff.h.  */ | 
|  |  | 
|  | std::string | 
|  | gdb_realpath_keepfile (const char *filename) | 
|  | { | 
|  | const char *base_name = lbasename (filename); | 
|  |  | 
|  | /* Extract the basename of filename, and return immediately | 
|  | a copy of filename if it does not contain any directory prefix.  */ | 
|  | if (base_name == filename) | 
|  | return filename; | 
|  |  | 
|  | std::string dir_name (filename, base_name - filename); | 
|  |  | 
|  | #ifdef HAVE_DOS_BASED_FILE_SYSTEM | 
|  | /* We need to be careful when filename is of the form 'd:foo', which | 
|  | is equivalent of d:./foo, which is totally different from d:/foo.  */ | 
|  | if (dir_name.size () == 2 && c_isalpha (dir_name[0]) && dir_name[1] == ':') | 
|  | dir_name += '.'; | 
|  | #endif | 
|  |  | 
|  | /* Canonicalize the directory prefix, and build the resulting | 
|  | filename.  If the dirname realpath already contains an ending | 
|  | directory separator, avoid doubling it.  */ | 
|  | gdb::unique_xmalloc_ptr<char> path_storage = gdb_realpath (dir_name.c_str ()); | 
|  | const char *real_path = path_storage.get (); | 
|  | return path_join (real_path, base_name); | 
|  | } | 
|  |  | 
|  | /* See gdbsupport/pathstuff.h.  */ | 
|  |  | 
|  | std::string | 
|  | gdb_abspath (const char *path, const char *cwd) | 
|  | { | 
|  | gdb_assert (path != NULL && path[0] != '\0'); | 
|  |  | 
|  | if (path[0] == '~') | 
|  | return gdb_tilde_expand (path); | 
|  |  | 
|  | if (IS_ABSOLUTE_PATH (path) || cwd == NULL) | 
|  | return path; | 
|  |  | 
|  | return path_join (cwd, path); | 
|  | } | 
|  |  | 
|  | /* See gdbsupport/pathstuff.h.  */ | 
|  |  | 
|  | const char * | 
|  | child_path (const char *parent, const char *child) | 
|  | { | 
|  | /* The child path must start with the parent path.  */ | 
|  | size_t parent_len = strlen (parent); | 
|  | if (filename_ncmp (parent, child, parent_len) != 0) | 
|  | return NULL; | 
|  |  | 
|  | /* The parent path must be a directory and the child must contain at | 
|  | least one component underneath the parent.  */ | 
|  | const char *child_component; | 
|  | if (parent_len > 0 && IS_DIR_SEPARATOR (parent[parent_len - 1])) | 
|  | { | 
|  | /* The parent path ends in a directory separator, so it is a | 
|  | directory.  The first child component starts after the common | 
|  | prefix.  */ | 
|  | child_component = child + parent_len; | 
|  | } | 
|  | else | 
|  | { | 
|  | /* The parent path does not end in a directory separator.  The | 
|  | first character in the child after the common prefix must be | 
|  | a directory separator. | 
|  |  | 
|  | Note that CHILD must hold at least parent_len characters for | 
|  | filename_ncmp to return zero.  If the character at parent_len | 
|  | is nul due to CHILD containing the same path as PARENT, the | 
|  | IS_DIR_SEPARATOR check will fail here.  */ | 
|  | if (!IS_DIR_SEPARATOR (child[parent_len])) | 
|  | return NULL; | 
|  |  | 
|  | /* The first child component starts after the separator after the | 
|  | common prefix.  */ | 
|  | child_component = child + parent_len + 1; | 
|  | } | 
|  |  | 
|  | /* The child must contain at least one non-separator character after | 
|  | the parent.  */ | 
|  | while (*child_component != '\0') | 
|  | { | 
|  | if (!IS_DIR_SEPARATOR (*child_component)) | 
|  | return child_component; | 
|  |  | 
|  | child_component++; | 
|  | } | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | /* See gdbsupport/pathstuff.h.  */ | 
|  |  | 
|  | std::string | 
|  | path_join (gdb::array_view<const char *> paths) | 
|  | { | 
|  | std::string ret; | 
|  |  | 
|  | for (int i = 0; i < paths.size (); ++i) | 
|  | { | 
|  | const char *path = paths[i]; | 
|  |  | 
|  | if (!ret.empty ()) | 
|  | { | 
|  | /* If RET doesn't already end with a separator then add one.  */ | 
|  | if (!IS_DIR_SEPARATOR (ret.back ())) | 
|  | ret += '/'; | 
|  |  | 
|  | /* Now that RET ends with a separator, ignore any at the start of | 
|  | PATH.  */ | 
|  | while (IS_DIR_SEPARATOR (path[0])) | 
|  | ++path; | 
|  | } | 
|  |  | 
|  | ret.append (path); | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* See gdbsupport/pathstuff.h.  */ | 
|  |  | 
|  | bool | 
|  | contains_dir_separator (const char *path) | 
|  | { | 
|  | for (; *path != '\0'; path++) | 
|  | { | 
|  | if (IS_DIR_SEPARATOR (*path)) | 
|  | return true; | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /* See gdbsupport/pathstuff.h.  */ | 
|  |  | 
|  | std::string | 
|  | get_standard_cache_dir () | 
|  | { | 
|  | #ifdef __APPLE__ | 
|  | #define HOME_CACHE_DIR "Library/Caches" | 
|  | #else | 
|  | #define HOME_CACHE_DIR ".cache" | 
|  | #endif | 
|  |  | 
|  | #ifndef __APPLE__ | 
|  | const char *xdg_cache_home = getenv ("XDG_CACHE_HOME"); | 
|  | if (xdg_cache_home != NULL && xdg_cache_home[0] != '\0') | 
|  | { | 
|  | /* Make sure the path is absolute and tilde-expanded.  */ | 
|  | std::string abs = gdb_abspath (xdg_cache_home); | 
|  | return path_join (abs.c_str (), "gdb"); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | const char *home = getenv ("HOME"); | 
|  | if (home != NULL && home[0] != '\0') | 
|  | { | 
|  | /* Make sure the path is absolute and tilde-expanded.  */ | 
|  | std::string abs = gdb_abspath (home); | 
|  | return path_join (abs.c_str (), HOME_CACHE_DIR,  "gdb"); | 
|  | } | 
|  |  | 
|  | #ifdef WIN32 | 
|  | const char *win_home = getenv ("LOCALAPPDATA"); | 
|  | if (win_home != NULL && win_home[0] != '\0') | 
|  | { | 
|  | /* Make sure the path is absolute and tilde-expanded.  */ | 
|  | std::string abs = gdb_abspath (win_home); | 
|  | return path_join (abs.c_str (), "gdb"); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | return {}; | 
|  | } | 
|  |  | 
|  | /* See gdbsupport/pathstuff.h.  */ | 
|  |  | 
|  | std::string | 
|  | get_standard_temp_dir () | 
|  | { | 
|  | #ifdef WIN32 | 
|  | const char *tmp = getenv ("TMP"); | 
|  | if (tmp != nullptr) | 
|  | return tmp; | 
|  |  | 
|  | tmp = getenv ("TEMP"); | 
|  | if (tmp != nullptr) | 
|  | return tmp; | 
|  |  | 
|  | error (_("Couldn't find temp dir path, both TMP and TEMP are unset.")); | 
|  |  | 
|  | #else | 
|  | const char *tmp = getenv ("TMPDIR"); | 
|  | if (tmp != nullptr) | 
|  | return tmp; | 
|  |  | 
|  | return "/tmp"; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | /* See pathstuff.h.  */ | 
|  |  | 
|  | std::string | 
|  | get_standard_config_dir () | 
|  | { | 
|  | #ifdef __APPLE__ | 
|  | #define HOME_CONFIG_DIR "Library/Preferences" | 
|  | #else | 
|  | #define HOME_CONFIG_DIR ".config" | 
|  | #endif | 
|  |  | 
|  | #ifndef __APPLE__ | 
|  | const char *xdg_config_home = getenv ("XDG_CONFIG_HOME"); | 
|  | if (xdg_config_home != NULL && xdg_config_home[0] != '\0') | 
|  | { | 
|  | /* Make sure the path is absolute and tilde-expanded.  */ | 
|  | std::string abs = gdb_abspath (xdg_config_home); | 
|  | return path_join (abs.c_str (), "gdb"); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | const char *home = getenv ("HOME"); | 
|  | if (home != NULL && home[0] != '\0') | 
|  | { | 
|  | /* Make sure the path is absolute and tilde-expanded.  */ | 
|  | std::string abs = gdb_abspath (home); | 
|  | return path_join (abs.c_str (), HOME_CONFIG_DIR, "gdb"); | 
|  | } | 
|  |  | 
|  | return {}; | 
|  | } | 
|  |  | 
|  | /* See pathstuff.h. */ | 
|  |  | 
|  | std::string | 
|  | get_standard_config_filename (const char *filename) | 
|  | { | 
|  | std::string config_dir = get_standard_config_dir (); | 
|  | if (config_dir != "") | 
|  | { | 
|  | const char *tmp = (*filename == '.') ? (filename + 1) : filename; | 
|  | std::string path = config_dir + SLASH_STRING + std::string (tmp); | 
|  | return path; | 
|  | } | 
|  |  | 
|  | return {}; | 
|  | } | 
|  |  | 
|  | /* See pathstuff.h.  */ | 
|  |  | 
|  | std::string | 
|  | find_gdb_home_config_file (const char *name, struct stat *buf) | 
|  | { | 
|  | gdb_assert (name != nullptr); | 
|  | gdb_assert (*name != '\0'); | 
|  |  | 
|  | std::string config_dir_file = get_standard_config_filename (name); | 
|  | if (!config_dir_file.empty ()) | 
|  | { | 
|  | if (stat (config_dir_file.c_str (), buf) == 0) | 
|  | return config_dir_file; | 
|  | } | 
|  |  | 
|  | const char *homedir = getenv ("HOME"); | 
|  | if (homedir != nullptr && homedir[0] != '\0') | 
|  | { | 
|  | /* Make sure the path is absolute and tilde-expanded.  */ | 
|  | std::string abs = gdb_abspath (homedir); | 
|  | std::string path = string_printf ("%s/%s", abs.c_str (), name); | 
|  | if (stat (path.c_str (), buf) == 0) | 
|  | return path; | 
|  | } | 
|  |  | 
|  | return {}; | 
|  | } | 
|  |  | 
|  | /* See gdbsupport/pathstuff.h.  */ | 
|  |  | 
|  | const char * | 
|  | get_shell () | 
|  | { | 
|  | const char *ret = getenv ("SHELL"); | 
|  | if (ret == NULL) | 
|  | ret = "/bin/sh"; | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* See gdbsupport/pathstuff.h.  */ | 
|  |  | 
|  | gdb::char_vector | 
|  | make_temp_filename (const std::string &f) | 
|  | { | 
|  | gdb::char_vector filename_temp (f.length () + 8); | 
|  | strcpy (filename_temp.data (), f.c_str ()); | 
|  | strcat (filename_temp.data () + f.size (), "-XXXXXX"); | 
|  | return filename_temp; | 
|  | } |