1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/linux_util.h"
6
7 #include <dirent.h>
8 #include <errno.h>
9 #include <fcntl.h>
10 #include <limits.h>
11 #include <stdlib.h>
12 #include <sys/stat.h>
13 #include <sys/types.h>
14 #include <unistd.h>
15
16 #include <iomanip>
17 #include <memory>
18 #include <string_view>
19
20 #include "base/base_export.h"
21 #include "base/files/dir_reader_posix.h"
22 #include "base/files/file_util.h"
23 #include "base/files/scoped_file.h"
24 #include "base/strings/safe_sprintf.h"
25 #include "base/strings/string_number_conversions.h"
26 #include "base/strings/string_split.h"
27 #include "base/strings/string_tokenizer.h"
28 #include "base/strings/string_util.h"
29 #include "build/build_config.h"
30 #include "build/chromeos_buildflags.h"
31
32 namespace base {
33
34 namespace {
35
36 #if !BUILDFLAG(IS_CHROMEOS_ASH)
GetKeyValueFromOSReleaseFile(const std::string & input,const char * key)37 std::string GetKeyValueFromOSReleaseFile(const std::string& input,
38 const char* key) {
39 StringPairs key_value_pairs;
40 SplitStringIntoKeyValuePairs(input, '=', '\n', &key_value_pairs);
41 for (const auto& pair : key_value_pairs) {
42 const std::string& key_str = pair.first;
43 const std::string& value_str = pair.second;
44 if (key_str == key) {
45 // It can contain quoted characters.
46 std::stringstream ss;
47 std::string pretty_name;
48 ss << value_str;
49 // Quoted with a single tick?
50 if (value_str[0] == '\'')
51 ss >> std::quoted(pretty_name, '\'');
52 else
53 ss >> std::quoted(pretty_name);
54
55 return pretty_name;
56 }
57 }
58
59 return "";
60 }
61
ReadDistroFromOSReleaseFile(const char * file)62 bool ReadDistroFromOSReleaseFile(const char* file) {
63 static const char kPrettyName[] = "PRETTY_NAME";
64
65 std::string os_release_content;
66 if (!ReadFileToString(FilePath(file), &os_release_content))
67 return false;
68
69 std::string pretty_name =
70 GetKeyValueFromOSReleaseFile(os_release_content, kPrettyName);
71 if (pretty_name.empty())
72 return false;
73
74 SetLinuxDistro(pretty_name);
75 return true;
76 }
77
78 // https://www.freedesktop.org/software/systemd/man/os-release.html
79 class DistroNameGetter {
80 public:
DistroNameGetter()81 DistroNameGetter() {
82 static const char* const kFilesToCheck[] = {"/etc/os-release",
83 "/usr/lib/os-release"};
84 for (const char* file : kFilesToCheck) {
85 if (ReadDistroFromOSReleaseFile(file))
86 return;
87 }
88 }
89 };
90 #endif // !BUILDFLAG(IS_CHROMEOS_ASH)
91
GetThreadsFromProcessDir(const char * dir_path,std::vector<pid_t> * tids)92 bool GetThreadsFromProcessDir(const char* dir_path, std::vector<pid_t>* tids) {
93 DirReaderPosix dir_reader(dir_path);
94
95 if (!dir_reader.IsValid()) {
96 DLOG(WARNING) << "Cannot open " << dir_path;
97 return false;
98 }
99
100 while (dir_reader.Next()) {
101 pid_t tid;
102 if (StringToInt(dir_reader.name(), &tid)) {
103 tids->push_back(tid);
104 }
105 }
106
107 return true;
108 }
109
110 // Account for the terminating null character.
111 constexpr int kDistroSize = 128 + 1;
112
113 } // namespace
114
115 // We use this static string to hold the Linux distro info. If we
116 // crash, the crash handler code will send this in the crash dump.
117 char g_linux_distro[kDistroSize] =
118 #if BUILDFLAG(IS_CHROMEOS_ASH)
119 "CrOS";
120 #elif BUILDFLAG(IS_ANDROID)
121 "Android";
122 #else
123 "Unknown";
124 #endif
125
126 // This function is only supposed to be used in tests. The declaration in the
127 // header file is guarded by "#if defined(UNIT_TEST)" so that they can be used
128 // by tests but not non-test code. However, this .cc file is compiled as part
129 // of "base" where "UNIT_TEST" is not defined. So we need to specify
130 // "BASE_EXPORT" here again so that they are visible to tests.
GetKeyValueFromOSReleaseFileForTesting(const std::string & input,const char * key)131 BASE_EXPORT std::string GetKeyValueFromOSReleaseFileForTesting(
132 const std::string& input,
133 const char* key) {
134 #if !BUILDFLAG(IS_CHROMEOS_ASH)
135 return GetKeyValueFromOSReleaseFile(input, key);
136 #else
137 return "";
138 #endif // !BUILDFLAG(IS_CHROMEOS_ASH)
139 }
140
GetLinuxDistro()141 std::string GetLinuxDistro() {
142 #if !BUILDFLAG(IS_CHROMEOS_ASH)
143 // We do this check only once per process. If it fails, there's
144 // little reason to believe it will work if we attempt to run it again.
145 static DistroNameGetter distro_name_getter;
146 #endif
147 return g_linux_distro;
148 }
149
SetLinuxDistro(const std::string & distro)150 void SetLinuxDistro(const std::string& distro) {
151 std::string trimmed_distro;
152 TrimWhitespaceASCII(distro, TRIM_ALL, &trimmed_distro);
153 strlcpy(g_linux_distro, trimmed_distro.c_str(), kDistroSize);
154 }
155
GetThreadsForProcess(pid_t pid,std::vector<pid_t> * tids)156 bool GetThreadsForProcess(pid_t pid, std::vector<pid_t>* tids) {
157 // 25 > strlen("/proc//task") + strlen(std::to_string(INT_MAX)) + 1 = 22
158 char buf[25];
159 strings::SafeSPrintf(buf, "/proc/%d/task", pid);
160 return GetThreadsFromProcessDir(buf, tids);
161 }
162
GetThreadsForCurrentProcess(std::vector<pid_t> * tids)163 bool GetThreadsForCurrentProcess(std::vector<pid_t>* tids) {
164 return GetThreadsFromProcessDir("/proc/self/task", tids);
165 }
166
FindThreadIDWithSyscall(pid_t pid,const std::string & expected_data,bool * syscall_supported)167 pid_t FindThreadIDWithSyscall(pid_t pid, const std::string& expected_data,
168 bool* syscall_supported) {
169 if (syscall_supported)
170 *syscall_supported = false;
171
172 std::vector<pid_t> tids;
173 if (!GetThreadsForProcess(pid, &tids))
174 return -1;
175
176 std::vector<char> syscall_data(expected_data.size());
177 for (pid_t tid : tids) {
178 char buf[256];
179 snprintf(buf, sizeof(buf), "/proc/%d/task/%d/syscall", pid, tid);
180 ScopedFD fd(open(buf, O_RDONLY));
181 if (!fd.is_valid())
182 continue;
183
184 *syscall_supported = true;
185 if (!ReadFromFD(fd.get(), syscall_data)) {
186 continue;
187 }
188
189 if (0 == strncmp(expected_data.c_str(), syscall_data.data(),
190 expected_data.size())) {
191 return tid;
192 }
193 }
194 return -1;
195 }
196
FindThreadID(pid_t pid,pid_t ns_tid,bool * ns_pid_supported)197 pid_t FindThreadID(pid_t pid, pid_t ns_tid, bool* ns_pid_supported) {
198 *ns_pid_supported = false;
199
200 std::vector<pid_t> tids;
201 if (!GetThreadsForProcess(pid, &tids))
202 return -1;
203
204 for (pid_t tid : tids) {
205 char buf[256];
206 snprintf(buf, sizeof(buf), "/proc/%d/task/%d/status", pid, tid);
207 std::string status;
208 if (!ReadFileToString(FilePath(buf), &status))
209 return -1;
210 StringTokenizer tokenizer(status, "\n");
211 while (tokenizer.GetNext()) {
212 std::string_view value_str(tokenizer.token_piece());
213 if (!StartsWith(value_str, "NSpid"))
214 continue;
215
216 *ns_pid_supported = true;
217 std::vector<std::string_view> split_value_str = SplitStringPiece(
218 value_str, "\t", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
219 DCHECK_GE(split_value_str.size(), 2u);
220 int value;
221 // The last value in the list is the PID in the namespace.
222 if (StringToInt(split_value_str.back(), &value) && value == ns_tid) {
223 // The second value in the list is the real PID.
224 if (StringToInt(split_value_str[1], &value))
225 return value;
226 }
227 break;
228 }
229 }
230 return -1;
231 }
232
233 } // namespace base
234