1 //
2 // Copyright 2018 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6
7 // system_utils_posix.cpp: Implementation of POSIX OS-specific functions.
8
9 #include "common/debug.h"
10 #include "system_utils.h"
11
12 #include <array>
13 #include <iostream>
14
15 #include <dlfcn.h>
16 #include <grp.h>
17 #include <inttypes.h>
18 #include <pwd.h>
19 #include <signal.h>
20 #include <string.h>
21 #include <sys/mman.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24 #include <sys/wait.h>
25 #include <unistd.h>
26
27 #include "common/string_utils.h"
28
29 #ifdef ANGLE_PLATFORM_FUCHSIA
30 # include <zircon/process.h>
31 # include <zircon/syscalls.h>
32 #else
33 # include <sys/resource.h>
34 #endif
35
36 namespace angle
37 {
38
39 namespace
40 {
GetModulePath(void * moduleOrSymbol)41 std::string GetModulePath(void *moduleOrSymbol)
42 {
43 Dl_info dlInfo;
44 if (dladdr(moduleOrSymbol, &dlInfo) == 0)
45 {
46 return "";
47 }
48
49 #ifdef ANGLE_PLATFORM_LINUX
50 // Chrome changes process title on Linux that causes dladdr returns wrong module
51 // file name for executable binary, so return GetExecutablePath() if dli_fname
52 // doesn't exist.
53 struct stat buf;
54 if (stat(dlInfo.dli_fname, &buf) != 0)
55 {
56 return GetExecutablePath();
57 }
58 #endif
59
60 return dlInfo.dli_fname;
61 }
62
OpenPosixLibrary(const std::string & fullPath,int extraFlags,std::string * errorOut)63 void *OpenPosixLibrary(const std::string &fullPath, int extraFlags, std::string *errorOut)
64 {
65 void *module = dlopen(fullPath.c_str(), RTLD_NOW | extraFlags);
66 if (module)
67 {
68 if (errorOut)
69 {
70 *errorOut = fullPath;
71 }
72 }
73 else if (errorOut)
74 {
75 *errorOut = "dlopen(";
76 *errorOut += fullPath;
77 *errorOut += ") failed with error: ";
78 *errorOut += dlerror();
79 struct stat sfile;
80 if (-1 == stat(fullPath.c_str(), &sfile))
81 {
82 *errorOut += ", stat() call failed.";
83 }
84 else
85 {
86 *errorOut += ", stat() info: ";
87 struct passwd *pwuser = getpwuid(sfile.st_uid);
88 if (pwuser)
89 {
90 *errorOut += "owner: ";
91 *errorOut += pwuser->pw_name;
92 *errorOut += ", ";
93 }
94 struct group *grpnam = getgrgid(sfile.st_gid);
95 if (grpnam)
96 {
97 *errorOut += "group: ";
98 *errorOut += grpnam->gr_name;
99 *errorOut += ", ";
100 }
101 *errorOut += "perms: ";
102 *errorOut += std::to_string(sfile.st_mode);
103 *errorOut += ", links: ";
104 *errorOut += std::to_string(sfile.st_nlink);
105 *errorOut += ", size: ";
106 *errorOut += std::to_string(sfile.st_size);
107 }
108 }
109 return module;
110 }
111 } // namespace
112
GetCWD()113 Optional<std::string> GetCWD()
114 {
115 std::array<char, 4096> pathBuf;
116 char *result = getcwd(pathBuf.data(), pathBuf.size());
117 if (result == nullptr)
118 {
119 return Optional<std::string>::Invalid();
120 }
121 return std::string(pathBuf.data());
122 }
123
SetCWD(const char * dirName)124 bool SetCWD(const char *dirName)
125 {
126 return (chdir(dirName) == 0);
127 }
128
UnsetEnvironmentVar(const char * variableName)129 bool UnsetEnvironmentVar(const char *variableName)
130 {
131 return (unsetenv(variableName) == 0);
132 }
133
SetEnvironmentVar(const char * variableName,const char * value)134 bool SetEnvironmentVar(const char *variableName, const char *value)
135 {
136 return (setenv(variableName, value, 1) == 0);
137 }
138
GetEnvironmentVar(const char * variableName)139 std::string GetEnvironmentVar(const char *variableName)
140 {
141 const char *value = getenv(variableName);
142 return (value == nullptr ? std::string() : std::string(value));
143 }
144
GetPathSeparatorForEnvironmentVar()145 const char *GetPathSeparatorForEnvironmentVar()
146 {
147 return ":";
148 }
149
GetModuleDirectoryAndGetError(std::string * errorOut)150 std::string GetModuleDirectoryAndGetError(std::string *errorOut)
151 {
152 std::string directory;
153 static int placeholderSymbol = 0;
154 std::string moduleName = GetModulePath(&placeholderSymbol);
155 if (!moduleName.empty())
156 {
157 directory = moduleName.substr(0, moduleName.find_last_of('/') + 1);
158 }
159
160 // Ensure we return the full path to the module, not the relative path
161 if (!IsFullPath(directory))
162 {
163 if (errorOut)
164 {
165 *errorOut += "Directory: '";
166 *errorOut += directory;
167 *errorOut += "' is not full path";
168 }
169 Optional<std::string> cwd = GetCWD();
170 if (cwd.valid())
171 {
172 directory = ConcatenatePath(cwd.value(), directory);
173 if (errorOut)
174 {
175 *errorOut += ", so it has been modified to: '";
176 *errorOut += directory;
177 *errorOut += "'. ";
178 }
179 }
180 else if (errorOut)
181 {
182 *errorOut += " and getcwd was invalid. ";
183 }
184 }
185 return directory;
186 }
187
GetModuleDirectory()188 std::string GetModuleDirectory()
189 {
190 return GetModuleDirectoryAndGetError(nullptr);
191 }
192
OpenSystemLibraryWithExtensionAndGetError(const char * libraryName,SearchType searchType,std::string * errorOut)193 void *OpenSystemLibraryWithExtensionAndGetError(const char *libraryName,
194 SearchType searchType,
195 std::string *errorOut)
196 {
197 std::string directory;
198 if (searchType == SearchType::ModuleDir)
199 {
200 #if ANGLE_PLATFORM_IOS_FAMILY
201 // On iOS, shared libraries must be loaded from within the app bundle.
202 directory = GetExecutableDirectory() + "/Frameworks/";
203 #elif ANGLE_PLATFORM_FUCHSIA
204 // On Fuchsia the dynamic loader always looks up libraries in /pkg/lib
205 // and disallows loading of libraries via absolute paths.
206 directory = "";
207 #else
208 directory = GetModuleDirectoryAndGetError(errorOut);
209 #endif
210 }
211
212 int extraFlags = 0;
213 if (searchType == SearchType::AlreadyLoaded)
214 {
215 extraFlags = RTLD_NOLOAD;
216 }
217
218 std::string fullPath = directory + libraryName;
219 return OpenPosixLibrary(fullPath, extraFlags, errorOut);
220 }
221
GetLibrarySymbol(void * libraryHandle,const char * symbolName)222 void *GetLibrarySymbol(void *libraryHandle, const char *symbolName)
223 {
224 if (!libraryHandle)
225 {
226 return nullptr;
227 }
228
229 return dlsym(libraryHandle, symbolName);
230 }
231
GetLibraryPath(void * libraryHandle)232 std::string GetLibraryPath(void *libraryHandle)
233 {
234 if (!libraryHandle)
235 {
236 return "";
237 }
238
239 return GetModulePath(libraryHandle);
240 }
241
CloseSystemLibrary(void * libraryHandle)242 void CloseSystemLibrary(void *libraryHandle)
243 {
244 if (libraryHandle)
245 {
246 dlclose(libraryHandle);
247 }
248 }
249
IsDirectory(const char * filename)250 bool IsDirectory(const char *filename)
251 {
252 struct stat st;
253 int result = stat(filename, &st);
254 return result == 0 && ((st.st_mode & S_IFDIR) == S_IFDIR);
255 }
256
IsDebuggerAttached()257 bool IsDebuggerAttached()
258 {
259 // This could have a fuller implementation.
260 // See https://cs.chromium.org/chromium/src/base/debug/debugger_posix.cc
261 return false;
262 }
263
BreakDebugger()264 void BreakDebugger()
265 {
266 // This could have a fuller implementation.
267 // See https://cs.chromium.org/chromium/src/base/debug/debugger_posix.cc
268 abort();
269 }
270
GetExecutableExtension()271 const char *GetExecutableExtension()
272 {
273 return "";
274 }
275
GetPathSeparator()276 char GetPathSeparator()
277 {
278 return '/';
279 }
280
GetRootDirectory()281 std::string GetRootDirectory()
282 {
283 return "/";
284 }
285
CreateDirectories(const std::string & path)286 bool CreateDirectories(const std::string &path)
287 {
288 // First sanitize path so we can use "/" as universal path separator
289 std::string sanitizedPath(path);
290 MakeForwardSlashThePathSeparator(sanitizedPath);
291
292 size_t pos = 0;
293 do
294 {
295 pos = sanitizedPath.find("/", pos);
296 std::string checkPath(sanitizedPath.substr(0, pos));
297 if (!checkPath.empty() && !IsDirectory(checkPath.c_str()))
298 {
299 if (mkdir(checkPath.c_str(), S_IRWXU | S_IRWXG | S_IRWXO) == -1)
300 {
301 return false;
302 }
303 }
304 if (pos == std::string::npos)
305 {
306 break;
307 }
308 ++pos;
309 } while (true);
310 return true;
311 }
312
MakeForwardSlashThePathSeparator(std::string & path)313 void MakeForwardSlashThePathSeparator(std::string &path)
314 {
315 // Nothing to do here for *nix side
316 return;
317 }
318
GetTempDirectory()319 Optional<std::string> GetTempDirectory()
320 {
321 const char *tmp = getenv("TMPDIR");
322 if (tmp != nullptr)
323 {
324 return std::string(tmp);
325 }
326
327 #if defined(ANGLE_PLATFORM_ANDROID)
328 // Not used right now in the ANGLE test runner.
329 // return PathService::Get(DIR_CACHE, path);
330 return Optional<std::string>::Invalid();
331 #else
332 return std::string("/tmp");
333 #endif
334 }
335
CreateTemporaryFileInDirectory(const std::string & directory)336 Optional<std::string> CreateTemporaryFileInDirectory(const std::string &directory)
337 {
338 return CreateTemporaryFileInDirectoryWithExtension(directory, std::string());
339 }
340
CreateTemporaryFileInDirectoryWithExtension(const std::string & directory,const std::string & extension)341 Optional<std::string> CreateTemporaryFileInDirectoryWithExtension(const std::string &directory,
342 const std::string &extension)
343 {
344 std::string tempFileTemplate = directory + "/.angle.XXXXXX" + extension;
345
346 int fd = mkstemps(&tempFileTemplate[0], static_cast<int>(extension.size()));
347 close(fd);
348
349 if (fd != -1)
350 {
351 return tempFileTemplate;
352 }
353
354 return Optional<std::string>::Invalid();
355 }
356
GetCurrentProcessCpuTime()357 double GetCurrentProcessCpuTime()
358 {
359 #ifdef ANGLE_PLATFORM_FUCHSIA
360 static zx_handle_t me = zx_process_self();
361 zx_info_task_runtime_t task_runtime;
362 zx_object_get_info(me, ZX_INFO_TASK_RUNTIME, &task_runtime, sizeof(task_runtime), nullptr,
363 nullptr);
364 return static_cast<double>(task_runtime.cpu_time) * 1e-9;
365 #else
366 // We could also have used /proc/stat, but that requires us to read the
367 // filesystem and convert from jiffies. /proc/stat also relies on jiffies
368 // (lower resolution) while getrusage can potentially use a sched_clock()
369 // underneath that has higher resolution.
370 struct rusage usage;
371 getrusage(RUSAGE_SELF, &usage);
372 double userTime = usage.ru_utime.tv_sec + usage.ru_utime.tv_usec * 1e-6;
373 double systemTime = usage.ru_stime.tv_sec + usage.ru_stime.tv_usec * 1e-6;
374 return userTime + systemTime;
375 #endif
376 }
377
378 namespace
379 {
SetMemoryProtection(uintptr_t start,size_t size,int protections)380 bool SetMemoryProtection(uintptr_t start, size_t size, int protections)
381 {
382 int ret = mprotect(reinterpret_cast<void *>(start), size, protections);
383 if (ret < 0)
384 {
385 perror("mprotect failed");
386 }
387 return ret == 0;
388 }
389
390 class PosixPageFaultHandler : public PageFaultHandler
391 {
392 public:
PosixPageFaultHandler(PageFaultCallback callback)393 PosixPageFaultHandler(PageFaultCallback callback) : PageFaultHandler(callback) {}
~PosixPageFaultHandler()394 ~PosixPageFaultHandler() override {}
395
396 bool enable() override;
397 bool disable() override;
398 void handle(int sig, siginfo_t *info, void *unused);
399
400 private:
401 struct sigaction mDefaultBusAction = {};
402 struct sigaction mDefaultSegvAction = {};
403 };
404
405 PosixPageFaultHandler *gPosixPageFaultHandler = nullptr;
SegfaultHandlerFunction(int sig,siginfo_t * info,void * unused)406 void SegfaultHandlerFunction(int sig, siginfo_t *info, void *unused)
407 {
408 gPosixPageFaultHandler->handle(sig, info, unused);
409 }
410
handle(int sig,siginfo_t * info,void * unused)411 void PosixPageFaultHandler::handle(int sig, siginfo_t *info, void *unused)
412 {
413 bool found = false;
414 if ((sig == SIGSEGV || sig == SIGBUS) &&
415 (info->si_code == SEGV_ACCERR || info->si_code == SEGV_MAPERR))
416 {
417 found = mCallback(reinterpret_cast<uintptr_t>(info->si_addr)) ==
418 PageFaultHandlerRangeType::InRange;
419 }
420
421 // Fall back to default signal handler
422 if (!found)
423 {
424 if (sig == SIGSEGV)
425 {
426 mDefaultSegvAction.sa_sigaction(sig, info, unused);
427 }
428 else if (sig == SIGBUS)
429 {
430 mDefaultBusAction.sa_sigaction(sig, info, unused);
431 }
432 else
433 {
434 UNREACHABLE();
435 }
436 }
437 }
438
disable()439 bool PosixPageFaultHandler::disable()
440 {
441 return sigaction(SIGSEGV, &mDefaultSegvAction, nullptr) == 0 &&
442 sigaction(SIGBUS, &mDefaultBusAction, nullptr) == 0;
443 }
444
enable()445 bool PosixPageFaultHandler::enable()
446 {
447 struct sigaction sigAction = {};
448 sigAction.sa_flags = SA_SIGINFO;
449 sigAction.sa_sigaction = &SegfaultHandlerFunction;
450 sigemptyset(&sigAction.sa_mask);
451
452 // Some POSIX implementations use SIGBUS for mprotect faults
453 return sigaction(SIGSEGV, &sigAction, &mDefaultSegvAction) == 0 &&
454 sigaction(SIGBUS, &sigAction, &mDefaultBusAction) == 0;
455 }
456 } // namespace
457
458 // Set write protection
ProtectMemory(uintptr_t start,size_t size)459 bool ProtectMemory(uintptr_t start, size_t size)
460 {
461 return SetMemoryProtection(start, size, PROT_READ);
462 }
463
464 // Allow reading and writing
UnprotectMemory(uintptr_t start,size_t size)465 bool UnprotectMemory(uintptr_t start, size_t size)
466 {
467 return SetMemoryProtection(start, size, PROT_READ | PROT_WRITE);
468 }
469
GetPageSize()470 size_t GetPageSize()
471 {
472 long pageSize = sysconf(_SC_PAGE_SIZE);
473 if (pageSize < 0)
474 {
475 perror("Could not get sysconf page size");
476 return 0;
477 }
478 return static_cast<size_t>(pageSize);
479 }
480
CreatePageFaultHandler(PageFaultCallback callback)481 PageFaultHandler *CreatePageFaultHandler(PageFaultCallback callback)
482 {
483 gPosixPageFaultHandler = new PosixPageFaultHandler(callback);
484 return gPosixPageFaultHandler;
485 }
486
GetProcessMemoryUsageKB()487 uint64_t GetProcessMemoryUsageKB()
488 {
489 FILE *file = fopen("/proc/self/status", "r");
490
491 if (!file)
492 {
493 return 0;
494 }
495
496 const char *kSearchString = "VmRSS:";
497 constexpr size_t kMaxLineSize = 100;
498 std::array<char, kMaxLineSize> line = {};
499
500 uint64_t kb = 0;
501
502 while (fgets(line.data(), line.size(), file) != nullptr)
503 {
504 if (strncmp(line.data(), kSearchString, strlen(kSearchString)) == 0)
505 {
506 std::vector<std::string> strings;
507 SplitStringAlongWhitespace(line.data(), &strings);
508
509 sscanf(strings[1].c_str(), "%" SCNu64, &kb);
510 break;
511 }
512 }
513 fclose(file);
514
515 return kb;
516 }
517 } // namespace angle
518