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