1 //===-- HostInfoLinux.cpp -------------------------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "lldb/Host/linux/HostInfoLinux.h" 10 #include "lldb/Host/Config.h" 11 #include "lldb/Host/FileSystem.h" 12 #include "lldb/Utility/Log.h" 13 14 #include "llvm/Support/Threading.h" 15 16 #include <climits> 17 #include <cstdio> 18 #include <cstring> 19 #include <sys/utsname.h> 20 #include <unistd.h> 21 22 #include <algorithm> 23 #include <mutex> 24 25 using namespace lldb_private; 26 27 namespace { 28 struct HostInfoLinuxFields { 29 llvm::once_flag m_distribution_once_flag; 30 std::string m_distribution_id; 31 llvm::once_flag m_os_version_once_flag; 32 llvm::VersionTuple m_os_version; 33 }; 34 } // namespace 35 36 static HostInfoLinuxFields *g_fields = nullptr; 37 38 void HostInfoLinux::Initialize(SharedLibraryDirectoryHelper *helper) { 39 HostInfoPosix::Initialize(helper); 40 41 g_fields = new HostInfoLinuxFields(); 42 } 43 44 void HostInfoLinux::Terminate() { 45 assert(g_fields && "Missing call to Initialize?"); 46 delete g_fields; 47 g_fields = nullptr; 48 HostInfoBase::Terminate(); 49 } 50 51 llvm::VersionTuple HostInfoLinux::GetOSVersion() { 52 assert(g_fields && "Missing call to Initialize?"); 53 llvm::call_once(g_fields->m_os_version_once_flag, []() { 54 struct utsname un; 55 if (uname(&un) != 0) 56 return; 57 58 llvm::StringRef release = un.release; 59 // The kernel release string can include a lot of stuff (e.g. 60 // 4.9.0-6-amd64). We're only interested in the numbered prefix. 61 release = release.substr(0, release.find_first_not_of("0123456789.")); 62 g_fields->m_os_version.tryParse(release); 63 }); 64 65 return g_fields->m_os_version; 66 } 67 68 llvm::Optional<std::string> HostInfoLinux::GetOSBuildString() { 69 struct utsname un; 70 ::memset(&un, 0, sizeof(utsname)); 71 72 if (uname(&un) < 0) 73 return llvm::None; 74 75 return std::string(un.release); 76 } 77 78 bool HostInfoLinux::GetOSKernelDescription(std::string &s) { 79 struct utsname un; 80 81 ::memset(&un, 0, sizeof(utsname)); 82 s.clear(); 83 84 if (uname(&un) < 0) 85 return false; 86 87 s.assign(un.version); 88 return true; 89 } 90 91 llvm::StringRef HostInfoLinux::GetDistributionId() { 92 assert(g_fields && "Missing call to Initialize?"); 93 // Try to run 'lbs_release -i', and use that response for the distribution 94 // id. 95 llvm::call_once(g_fields->m_distribution_once_flag, []() { 96 97 Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST)); 98 LLDB_LOGF(log, "attempting to determine Linux distribution..."); 99 100 // check if the lsb_release command exists at one of the following paths 101 const char *const exe_paths[] = {"/bin/lsb_release", 102 "/usr/bin/lsb_release"}; 103 104 for (size_t exe_index = 0; 105 exe_index < sizeof(exe_paths) / sizeof(exe_paths[0]); ++exe_index) { 106 const char *const get_distribution_info_exe = exe_paths[exe_index]; 107 if (access(get_distribution_info_exe, F_OK)) { 108 // this exe doesn't exist, move on to next exe 109 LLDB_LOGF(log, "executable doesn't exist: %s", 110 get_distribution_info_exe); 111 continue; 112 } 113 114 // execute the distribution-retrieval command, read output 115 std::string get_distribution_id_command(get_distribution_info_exe); 116 get_distribution_id_command += " -i"; 117 118 FILE *file = popen(get_distribution_id_command.c_str(), "r"); 119 if (!file) { 120 LLDB_LOGF(log, 121 "failed to run command: \"%s\", cannot retrieve " 122 "platform information", 123 get_distribution_id_command.c_str()); 124 break; 125 } 126 127 // retrieve the distribution id string. 128 char distribution_id[256] = {'\0'}; 129 if (fgets(distribution_id, sizeof(distribution_id) - 1, file) != 130 nullptr) { 131 LLDB_LOGF(log, "distribution id command returned \"%s\"", 132 distribution_id); 133 134 const char *const distributor_id_key = "Distributor ID:\t"; 135 if (strstr(distribution_id, distributor_id_key)) { 136 // strip newlines 137 std::string id_string(distribution_id + strlen(distributor_id_key)); 138 id_string.erase(std::remove(id_string.begin(), id_string.end(), '\n'), 139 id_string.end()); 140 141 // lower case it and convert whitespace to underscores 142 std::transform( 143 id_string.begin(), id_string.end(), id_string.begin(), 144 [](char ch) { return tolower(isspace(ch) ? '_' : ch); }); 145 146 g_fields->m_distribution_id = id_string; 147 LLDB_LOGF(log, "distribution id set to \"%s\"", 148 g_fields->m_distribution_id.c_str()); 149 } else { 150 LLDB_LOGF(log, "failed to find \"%s\" field in \"%s\"", 151 distributor_id_key, distribution_id); 152 } 153 } else { 154 LLDB_LOGF(log, 155 "failed to retrieve distribution id, \"%s\" returned no" 156 " lines", 157 get_distribution_id_command.c_str()); 158 } 159 160 // clean up the file 161 pclose(file); 162 } 163 }); 164 165 return g_fields->m_distribution_id; 166 } 167 168 FileSpec HostInfoLinux::GetProgramFileSpec() { 169 static FileSpec g_program_filespec; 170 171 if (!g_program_filespec) { 172 char exe_path[PATH_MAX]; 173 ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1); 174 if (len > 0) { 175 exe_path[len] = 0; 176 g_program_filespec.SetFile(exe_path, FileSpec::Style::native); 177 } 178 } 179 180 return g_program_filespec; 181 } 182 183 bool HostInfoLinux::ComputeSupportExeDirectory(FileSpec &file_spec) { 184 if (HostInfoPosix::ComputeSupportExeDirectory(file_spec) && 185 file_spec.IsAbsolute() && FileSystem::Instance().Exists(file_spec)) 186 return true; 187 file_spec.GetDirectory() = GetProgramFileSpec().GetDirectory(); 188 return !file_spec.GetDirectory().IsEmpty(); 189 } 190 191 bool HostInfoLinux::ComputeSystemPluginsDirectory(FileSpec &file_spec) { 192 FileSpec temp_file("/usr/lib" LLDB_LIBDIR_SUFFIX "/lldb/plugins"); 193 FileSystem::Instance().Resolve(temp_file); 194 file_spec.GetDirectory().SetCString(temp_file.GetPath().c_str()); 195 return true; 196 } 197 198 bool HostInfoLinux::ComputeUserPluginsDirectory(FileSpec &file_spec) { 199 // XDG Base Directory Specification 200 // http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html If 201 // XDG_DATA_HOME exists, use that, otherwise use ~/.local/share/lldb. 202 const char *xdg_data_home = getenv("XDG_DATA_HOME"); 203 if (xdg_data_home && xdg_data_home[0]) { 204 std::string user_plugin_dir(xdg_data_home); 205 user_plugin_dir += "/lldb"; 206 file_spec.GetDirectory().SetCString(user_plugin_dir.c_str()); 207 } else 208 file_spec.GetDirectory().SetCString("~/.local/share/lldb"); 209 return true; 210 } 211 212 void HostInfoLinux::ComputeHostArchitectureSupport(ArchSpec &arch_32, 213 ArchSpec &arch_64) { 214 HostInfoPosix::ComputeHostArchitectureSupport(arch_32, arch_64); 215 216 const char *distribution_id = GetDistributionId().data(); 217 218 // On Linux, "unknown" in the vendor slot isn't what we want for the default 219 // triple. It's probably an artifact of config.guess. 220 if (arch_32.IsValid()) { 221 arch_32.SetDistributionId(distribution_id); 222 if (arch_32.GetTriple().getVendor() == llvm::Triple::UnknownVendor) 223 arch_32.GetTriple().setVendorName(llvm::StringRef()); 224 } 225 if (arch_64.IsValid()) { 226 arch_64.SetDistributionId(distribution_id); 227 if (arch_64.GetTriple().getVendor() == llvm::Triple::UnknownVendor) 228 arch_64.GetTriple().setVendorName(llvm::StringRef()); 229 } 230 } 231