1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "mozilla/FileUtils.h"
8
9 #include <errno.h>
10 #include <stdio.h>
11 #include <inttypes.h>
12
13 #include "nscore.h"
14 #include "private/pprio.h"
15 #include "prmem.h"
16 #include "mozilla/Assertions.h"
17 #include "mozilla/MemUtils.h"
18 #include "mozilla/BaseProfilerMarkers.h"
19
20 #if defined(XP_MACOSX)
21 # include <fcntl.h>
22 # include <unistd.h>
23 # include <mach/machine.h>
24 # include <mach-o/fat.h>
25 # include <mach-o/loader.h>
26 # include <sys/mman.h>
27 # include <sys/stat.h>
28 # include <limits.h>
29 #elif defined(XP_UNIX)
30 # include <fcntl.h>
31 # include <unistd.h>
32 # if defined(LINUX)
33 # include <elf.h>
34 # endif
35 # include <sys/types.h>
36 # include <sys/stat.h>
37 #elif defined(XP_WIN)
38 # include <nsWindowsHelpers.h>
39 # include <mozilla/NativeNt.h>
40 # include <mozilla/ScopeExit.h>
41 #endif
42
43 // Functions that are not to be used in standalone glue must be implemented
44 // within this #if block
45 #if defined(MOZILLA_INTERNAL_API)
46
47 # include "nsString.h"
48
fallocate(PRFileDesc * aFD,int64_t aLength)49 bool mozilla::fallocate(PRFileDesc* aFD, int64_t aLength) {
50 # if defined(HAVE_POSIX_FALLOCATE)
51 return posix_fallocate(PR_FileDesc2NativeHandle(aFD), 0, aLength) == 0;
52 # elif defined(XP_WIN)
53 int64_t oldpos = PR_Seek64(aFD, 0, PR_SEEK_CUR);
54 if (oldpos == -1) {
55 return false;
56 }
57
58 if (PR_Seek64(aFD, aLength, PR_SEEK_SET) != aLength) {
59 return false;
60 }
61
62 bool retval = (0 != SetEndOfFile((HANDLE)PR_FileDesc2NativeHandle(aFD)));
63
64 PR_Seek64(aFD, oldpos, PR_SEEK_SET);
65 return retval;
66 # elif defined(XP_MACOSX)
67 int fd = PR_FileDesc2NativeHandle(aFD);
68 fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, aLength};
69 // Try to get a continous chunk of disk space
70 int ret = fcntl(fd, F_PREALLOCATE, &store);
71 if (ret == -1) {
72 // OK, perhaps we are too fragmented, allocate non-continuous
73 store.fst_flags = F_ALLOCATEALL;
74 ret = fcntl(fd, F_PREALLOCATE, &store);
75 if (ret == -1) {
76 return false;
77 }
78 }
79 return ftruncate(fd, aLength) == 0;
80 # elif defined(XP_UNIX)
81 // The following is copied from fcntlSizeHint in sqlite
82 /* If the OS does not have posix_fallocate(), fake it. First use
83 ** ftruncate() to set the file size, then write a single byte to
84 ** the last byte in each block within the extended region. This
85 ** is the same technique used by glibc to implement posix_fallocate()
86 ** on systems that do not have a real fallocate() system call.
87 */
88 int64_t oldpos = PR_Seek64(aFD, 0, PR_SEEK_CUR);
89 if (oldpos == -1) {
90 return false;
91 }
92
93 struct stat buf;
94 int fd = PR_FileDesc2NativeHandle(aFD);
95 if (fstat(fd, &buf)) {
96 return false;
97 }
98
99 if (buf.st_size >= aLength) {
100 return false;
101 }
102
103 const int nBlk = buf.st_blksize;
104
105 if (!nBlk) {
106 return false;
107 }
108
109 if (ftruncate(fd, aLength)) {
110 return false;
111 }
112
113 int nWrite; // Return value from write()
114 int64_t iWrite = ((buf.st_size + 2 * nBlk - 1) / nBlk) * nBlk -
115 1; // Next offset to write to
116 while (iWrite < aLength) {
117 nWrite = 0;
118 if (PR_Seek64(aFD, iWrite, PR_SEEK_SET) == iWrite) {
119 nWrite = PR_Write(aFD, "", 1);
120 }
121 if (nWrite != 1) {
122 break;
123 }
124 iWrite += nBlk;
125 }
126
127 PR_Seek64(aFD, oldpos, PR_SEEK_SET);
128 return nWrite == 1;
129 # endif
130 return false;
131 }
132
ReadAheadLib(nsIFile * aFile)133 void mozilla::ReadAheadLib(nsIFile* aFile) {
134 # if defined(XP_WIN)
135 nsAutoString path;
136 if (!aFile || NS_FAILED(aFile->GetPath(path))) {
137 return;
138 }
139 ReadAheadLib(path.get());
140 # elif defined(LINUX) && !defined(ANDROID) || defined(XP_MACOSX)
141 nsAutoCString nativePath;
142 if (!aFile || NS_FAILED(aFile->GetNativePath(nativePath))) {
143 return;
144 }
145 ReadAheadLib(nativePath.get());
146 # endif
147 }
148
ReadAheadFile(nsIFile * aFile,const size_t aOffset,const size_t aCount,mozilla::filedesc_t * aOutFd)149 void mozilla::ReadAheadFile(nsIFile* aFile, const size_t aOffset,
150 const size_t aCount, mozilla::filedesc_t* aOutFd) {
151 # if defined(XP_WIN)
152 nsAutoString path;
153 if (!aFile || NS_FAILED(aFile->GetPath(path))) {
154 return;
155 }
156 ReadAheadFile(path.get(), aOffset, aCount, aOutFd);
157 # elif defined(LINUX) && !defined(ANDROID) || defined(XP_MACOSX)
158 nsAutoCString nativePath;
159 if (!aFile || NS_FAILED(aFile->GetNativePath(nativePath))) {
160 return;
161 }
162 ReadAheadFile(nativePath.get(), aOffset, aCount, aOutFd);
163 # endif
164 }
165
GetLibraryName(mozilla::pathstr_t aDirectory,const char * aLib)166 mozilla::PathString mozilla::GetLibraryName(mozilla::pathstr_t aDirectory,
167 const char* aLib) {
168 # ifdef XP_WIN
169 nsAutoString fullName;
170 if (aDirectory) {
171 fullName.Assign(aDirectory);
172 fullName.Append('\\');
173 }
174 AppendUTF8toUTF16(MakeStringSpan(aLib), fullName);
175 if (!strstr(aLib, ".dll")) {
176 fullName.AppendLiteral(".dll");
177 }
178 return std::move(fullName);
179 # else
180 char* temp = PR_GetLibraryName(aDirectory, aLib);
181 if (!temp) {
182 return ""_ns;
183 }
184 nsAutoCString libname(temp);
185 PR_FreeLibraryName(temp);
186 return std::move(libname);
187 # endif
188 }
189
GetLibraryFilePathname(mozilla::pathstr_t aName,PRFuncPtr aAddr)190 mozilla::PathString mozilla::GetLibraryFilePathname(mozilla::pathstr_t aName,
191 PRFuncPtr aAddr) {
192 # ifdef XP_WIN
193 HMODULE handle = GetModuleHandleW(char16ptr_t(aName));
194 if (!handle) {
195 return u""_ns;
196 }
197
198 nsAutoString path;
199 path.SetLength(MAX_PATH);
200 DWORD len = GetModuleFileNameW(handle, char16ptr_t(path.BeginWriting()),
201 path.Length());
202 if (!len) {
203 return u""_ns;
204 }
205
206 path.SetLength(len);
207 return std::move(path);
208 # else
209 char* temp = PR_GetLibraryFilePathname(aName, aAddr);
210 if (!temp) {
211 return ""_ns;
212 }
213 nsAutoCString path(temp);
214 PR_Free(temp); // PR_GetLibraryFilePathname() uses PR_Malloc().
215 return std::move(path);
216 # endif
217 }
218
219 #endif // defined(MOZILLA_INTERNAL_API)
220
221 #if defined(LINUX) && !defined(ANDROID)
222
223 static const unsigned int bufsize = 4096;
224
225 # ifdef __LP64__
226 typedef Elf64_Ehdr Elf_Ehdr;
227 typedef Elf64_Phdr Elf_Phdr;
228 static const unsigned char ELFCLASS = ELFCLASS64;
229 typedef Elf64_Off Elf_Off;
230 # else
231 typedef Elf32_Ehdr Elf_Ehdr;
232 typedef Elf32_Phdr Elf_Phdr;
233 static const unsigned char ELFCLASS = ELFCLASS32;
234 typedef Elf32_Off Elf_Off;
235 # endif
236
237 #elif defined(XP_MACOSX)
238
239 # if defined(__i386__)
240 static const uint32_t CPU_TYPE = CPU_TYPE_X86;
241 # elif defined(__x86_64__)
242 static const uint32_t CPU_TYPE = CPU_TYPE_X86_64;
243 # elif defined(__ppc__)
244 static const uint32_t CPU_TYPE = CPU_TYPE_POWERPC;
245 # elif defined(__ppc64__)
246 static const uint32_t CPU_TYPE = CPU_TYPE_POWERPC64;
247 # elif defined(__aarch64__)
248 static const uint32_t CPU_TYPE = CPU_TYPE_ARM64;
249 # else
250 # error Unsupported CPU type
251 # endif
252
253 # ifdef __LP64__
254 # undef LC_SEGMENT
255 # define LC_SEGMENT LC_SEGMENT_64
256 # undef MH_MAGIC
257 # define MH_MAGIC MH_MAGIC_64
258 # define cpu_mach_header mach_header_64
259 # define segment_command segment_command_64
260 # else
261 # define cpu_mach_header mach_header
262 # endif
263
264 class ScopedMMap {
265 public:
ScopedMMap(const char * aFilePath)266 explicit ScopedMMap(const char* aFilePath) : buf(nullptr) {
267 fd = open(aFilePath, O_RDONLY);
268 if (fd < 0) {
269 return;
270 }
271 struct stat st;
272 if (fstat(fd, &st) < 0) {
273 return;
274 }
275 size = st.st_size;
276 buf = (char*)mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
277 }
~ScopedMMap()278 ~ScopedMMap() {
279 if (buf) {
280 munmap(buf, size);
281 }
282 if (fd >= 0) {
283 close(fd);
284 }
285 }
operator char*()286 operator char*() { return buf; }
getFd()287 int getFd() { return fd; }
288
289 private:
290 int fd;
291 char* buf;
292 size_t size;
293 };
294 #endif
295
ReadAhead(mozilla::filedesc_t aFd,const size_t aOffset,const size_t aCount)296 void mozilla::ReadAhead(mozilla::filedesc_t aFd, const size_t aOffset,
297 const size_t aCount) {
298 #if defined(XP_WIN)
299
300 LARGE_INTEGER fpOriginal;
301 LARGE_INTEGER fpOffset;
302 # if defined(HAVE_LONG_LONG)
303 fpOffset.QuadPart = 0;
304 # else
305 fpOffset.u.LowPart = 0;
306 fpOffset.u.HighPart = 0;
307 # endif
308
309 // Get the current file pointer so that we can restore it. This isn't
310 // really necessary other than to provide the same semantics regarding the
311 // file pointer that other platforms do
312 if (!SetFilePointerEx(aFd, fpOffset, &fpOriginal, FILE_CURRENT)) {
313 return;
314 }
315
316 if (aOffset) {
317 # if defined(HAVE_LONG_LONG)
318 fpOffset.QuadPart = static_cast<LONGLONG>(aOffset);
319 # else
320 fpOffset.u.LowPart = aOffset;
321 fpOffset.u.HighPart = 0;
322 # endif
323
324 if (!SetFilePointerEx(aFd, fpOffset, nullptr, FILE_BEGIN)) {
325 return;
326 }
327 }
328
329 char buf[64 * 1024];
330 size_t totalBytesRead = 0;
331 DWORD dwBytesRead;
332 // Do dummy reads to trigger kernel-side readhead via
333 // FILE_FLAG_SEQUENTIAL_SCAN. Abort when underfilling because during testing
334 // the buffers are read fully A buffer that's not keeping up would imply that
335 // readahead isn't working right
336 while (totalBytesRead < aCount &&
337 ReadFile(aFd, buf, sizeof(buf), &dwBytesRead, nullptr) &&
338 dwBytesRead == sizeof(buf)) {
339 totalBytesRead += dwBytesRead;
340 }
341
342 // Restore the file pointer
343 SetFilePointerEx(aFd, fpOriginal, nullptr, FILE_BEGIN);
344
345 #elif defined(LINUX) && !defined(ANDROID)
346
347 readahead(aFd, aOffset, aCount);
348
349 #elif defined(XP_MACOSX)
350
351 struct radvisory ra;
352 ra.ra_offset = aOffset;
353 ra.ra_count = aCount;
354 // The F_RDADVISE fcntl is equivalent to Linux' readahead() system call.
355 fcntl(aFd, F_RDADVISE, &ra);
356
357 #endif
358 }
359
ReadAheadLib(mozilla::pathstr_t aFilePath)360 void mozilla::ReadAheadLib(mozilla::pathstr_t aFilePath) {
361 if (!aFilePath) {
362 return;
363 }
364
365 #ifdef MOZ_GECKO_PROFILER
366 # ifdef XP_WIN
367 auto WideToUTF8 = [](const wchar_t* aStr) -> std::string {
368 std::string s;
369 // Determine the number of output bytes (including null terminator).
370 const int numConv = ::WideCharToMultiByte(CP_UTF8, 0, aStr, -1, nullptr, 0,
371 nullptr, nullptr);
372 if (numConv == 0) {
373 return s;
374 }
375 s.resize(numConv);
376 const int numConvd = ::WideCharToMultiByte(CP_UTF8, 0, aStr, -1, s.data(),
377 numConv, nullptr, nullptr);
378 if (numConvd != numConv) {
379 // Error during conversion, remove any temporary data.
380 s.clear();
381 }
382 return s;
383 };
384 # endif
385
386 AUTO_BASE_PROFILER_MARKER_TEXT("ReadAheadLib", OTHER, {},
387 # ifdef XP_WIN
388 WideToUTF8(aFilePath)
389 # else
390 aFilePath
391 # endif
392 );
393 #endif
394
395 #if defined(XP_WIN)
396 if (!CanPrefetchMemory()) {
397 ReadAheadFile(aFilePath);
398 return;
399 }
400 nsAutoHandle fd(CreateFileW(aFilePath, GENERIC_READ | GENERIC_EXECUTE,
401 FILE_SHARE_READ, nullptr, OPEN_EXISTING,
402 FILE_FLAG_SEQUENTIAL_SCAN, nullptr));
403 if (!fd) {
404 return;
405 }
406
407 nsAutoHandle mapping(CreateFileMapping(
408 fd, nullptr, SEC_IMAGE | PAGE_EXECUTE_READ, 0, 0, nullptr));
409 if (!mapping) {
410 return;
411 }
412
413 PVOID data = MapViewOfFile(
414 mapping, FILE_MAP_READ | FILE_MAP_EXECUTE | SEC_IMAGE, 0, 0, 0);
415 if (!data) {
416 return;
417 }
418 auto guard = MakeScopeExit([=]() { UnmapViewOfFile(data); });
419 mozilla::nt::PEHeaders headers(data);
420 Maybe<Span<const uint8_t>> bounds = headers.GetBounds();
421 if (!bounds) {
422 return;
423 }
424
425 PrefetchMemory((uint8_t*)data, bounds->Length());
426
427 #elif defined(LINUX) && !defined(ANDROID)
428 int fd = open(aFilePath, O_RDONLY);
429 if (fd < 0) {
430 return;
431 }
432
433 union {
434 char buf[bufsize];
435 Elf_Ehdr ehdr;
436 } elf;
437 // Read ELF header (ehdr) and program header table (phdr).
438 // We check that the ELF magic is found, that the ELF class matches
439 // our own, and that the program header table as defined in the ELF
440 // headers fits in the buffer we read.
441 if ((read(fd, elf.buf, bufsize) <= 0) || (memcmp(elf.buf, ELFMAG, 4)) ||
442 (elf.ehdr.e_ident[EI_CLASS] != ELFCLASS) ||
443 // Upcast e_phentsize so the multiplication is done in the same precision
444 // as the subsequent addition, to satisfy static analyzers and avoid
445 // issues with abnormally large program header tables.
446 (elf.ehdr.e_phoff +
447 (static_cast<Elf_Off>(elf.ehdr.e_phentsize) * elf.ehdr.e_phnum) >=
448 bufsize)) {
449 close(fd);
450 return;
451 }
452 // The program header table contains segment definitions. One such
453 // segment type is PT_LOAD, which describes how the dynamic loader
454 // is going to map the file in memory. We use that information to
455 // find the biggest offset from the library that will be mapped in
456 // memory.
457 Elf_Phdr* phdr = (Elf_Phdr*)&elf.buf[elf.ehdr.e_phoff];
458 Elf_Off end = 0;
459 for (int phnum = elf.ehdr.e_phnum; phnum; phdr++, phnum--) {
460 if ((phdr->p_type == PT_LOAD) && (end < phdr->p_offset + phdr->p_filesz)) {
461 end = phdr->p_offset + phdr->p_filesz;
462 }
463 }
464 // Let the kernel read ahead what the dynamic loader is going to
465 // map in memory soon after.
466 if (end > 0) {
467 ReadAhead(fd, 0, end);
468 }
469 close(fd);
470 #elif defined(XP_MACOSX)
471 ScopedMMap buf(aFilePath);
472 char* base = buf;
473 if (!base) {
474 return;
475 }
476
477 // An OSX binary might either be a fat (universal) binary or a
478 // Mach-O binary. A fat binary actually embeds several Mach-O
479 // binaries. If we have a fat binary, find the offset where the
480 // Mach-O binary for our CPU type can be found.
481 struct fat_header* fh = (struct fat_header*)base;
482
483 if (OSSwapBigToHostInt32(fh->magic) == FAT_MAGIC) {
484 uint32_t nfat_arch = OSSwapBigToHostInt32(fh->nfat_arch);
485 struct fat_arch* arch = (struct fat_arch*)&buf[sizeof(struct fat_header)];
486 for (; nfat_arch; arch++, nfat_arch--) {
487 if (OSSwapBigToHostInt32(arch->cputype) == CPU_TYPE) {
488 base += OSSwapBigToHostInt32(arch->offset);
489 break;
490 }
491 }
492 if (base == buf) {
493 return;
494 }
495 }
496
497 // Check Mach-O magic in the Mach header
498 struct cpu_mach_header* mh = (struct cpu_mach_header*)base;
499 if (mh->magic != MH_MAGIC) {
500 return;
501 }
502
503 // The Mach header is followed by a sequence of load commands.
504 // Each command has a header containing the command type and the
505 // command size. LD_SEGMENT commands describes how the dynamic
506 // loader is going to map the file in memory. We use that
507 // information to find the biggest offset from the library that
508 // will be mapped in memory.
509 char* cmd = &base[sizeof(struct cpu_mach_header)];
510 uint32_t end = 0;
511 for (uint32_t ncmds = mh->ncmds; ncmds; ncmds--) {
512 struct segment_command* sh = (struct segment_command*)cmd;
513 if (sh->cmd != LC_SEGMENT) {
514 continue;
515 }
516 if (end < sh->fileoff + sh->filesize) {
517 end = sh->fileoff + sh->filesize;
518 }
519 cmd += sh->cmdsize;
520 }
521 // Let the kernel read ahead what the dynamic loader is going to
522 // map in memory soon after.
523 if (end > 0) {
524 ReadAhead(buf.getFd(), base - buf, end);
525 }
526 #endif
527 }
528
ReadAheadFile(mozilla::pathstr_t aFilePath,const size_t aOffset,const size_t aCount,mozilla::filedesc_t * aOutFd)529 void mozilla::ReadAheadFile(mozilla::pathstr_t aFilePath, const size_t aOffset,
530 const size_t aCount, mozilla::filedesc_t* aOutFd) {
531 #if defined(XP_WIN)
532 if (!aFilePath) {
533 if (aOutFd) {
534 *aOutFd = INVALID_HANDLE_VALUE;
535 }
536 return;
537 }
538 HANDLE fd = CreateFileW(aFilePath, GENERIC_READ, FILE_SHARE_READ, nullptr,
539 OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
540 if (aOutFd) {
541 *aOutFd = fd;
542 }
543 if (fd == INVALID_HANDLE_VALUE) {
544 return;
545 }
546 ReadAhead(fd, aOffset, aCount);
547 if (!aOutFd) {
548 CloseHandle(fd);
549 }
550 #elif defined(LINUX) && !defined(ANDROID) || defined(XP_MACOSX)
551 if (!aFilePath) {
552 if (aOutFd) {
553 *aOutFd = -1;
554 }
555 return;
556 }
557 int fd = open(aFilePath, O_RDONLY);
558 if (aOutFd) {
559 *aOutFd = fd;
560 }
561 if (fd < 0) {
562 return;
563 }
564 size_t count;
565 if (aCount == SIZE_MAX) {
566 struct stat st;
567 if (fstat(fd, &st) < 0) {
568 if (!aOutFd) {
569 close(fd);
570 }
571 return;
572 }
573 count = st.st_size;
574 } else {
575 count = aCount;
576 }
577 ReadAhead(fd, aOffset, count);
578 if (!aOutFd) {
579 close(fd);
580 }
581 #endif
582 }
583