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