xref: /aosp_15_r20/external/angle/src/common/system_utils_posix.cpp (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
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