michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: #include michael@0: michael@0: #include "nscore.h" michael@0: #include "nsStringGlue.h" michael@0: #include "private/pprio.h" michael@0: #include "mozilla/Assertions.h" michael@0: #include "mozilla/FileUtils.h" michael@0: michael@0: #if defined(XP_MACOSX) michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #elif defined(XP_UNIX) michael@0: #include michael@0: #include michael@0: #if defined(LINUX) michael@0: #include michael@0: #endif michael@0: #include michael@0: #include michael@0: #elif defined(XP_WIN) michael@0: #include michael@0: #endif michael@0: michael@0: // Functions that are not to be used in standalone glue must be implemented michael@0: // within this #if block michael@0: #if !defined(XPCOM_GLUE) michael@0: michael@0: bool michael@0: mozilla::fallocate(PRFileDesc *aFD, int64_t aLength) michael@0: { michael@0: #if defined(HAVE_POSIX_FALLOCATE) michael@0: return posix_fallocate(PR_FileDesc2NativeHandle(aFD), 0, aLength) == 0; michael@0: #elif defined(XP_WIN) michael@0: int64_t oldpos = PR_Seek64(aFD, 0, PR_SEEK_CUR); michael@0: if (oldpos == -1) michael@0: return false; michael@0: michael@0: if (PR_Seek64(aFD, aLength, PR_SEEK_SET) != aLength) michael@0: return false; michael@0: michael@0: bool retval = (0 != SetEndOfFile((HANDLE)PR_FileDesc2NativeHandle(aFD))); michael@0: michael@0: PR_Seek64(aFD, oldpos, PR_SEEK_SET); michael@0: return retval; michael@0: #elif defined(XP_MACOSX) michael@0: int fd = PR_FileDesc2NativeHandle(aFD); michael@0: fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, aLength}; michael@0: // Try to get a continous chunk of disk space michael@0: int ret = fcntl(fd, F_PREALLOCATE, &store); michael@0: if (-1 == ret) { michael@0: // OK, perhaps we are too fragmented, allocate non-continuous michael@0: store.fst_flags = F_ALLOCATEALL; michael@0: ret = fcntl(fd, F_PREALLOCATE, &store); michael@0: if (-1 == ret) michael@0: return false; michael@0: } michael@0: return 0 == ftruncate(fd, aLength); michael@0: #elif defined(XP_UNIX) michael@0: // The following is copied from fcntlSizeHint in sqlite michael@0: /* If the OS does not have posix_fallocate(), fake it. First use michael@0: ** ftruncate() to set the file size, then write a single byte to michael@0: ** the last byte in each block within the extended region. This michael@0: ** is the same technique used by glibc to implement posix_fallocate() michael@0: ** on systems that do not have a real fallocate() system call. michael@0: */ michael@0: int64_t oldpos = PR_Seek64(aFD, 0, PR_SEEK_CUR); michael@0: if (oldpos == -1) michael@0: return false; michael@0: michael@0: struct stat buf; michael@0: int fd = PR_FileDesc2NativeHandle(aFD); michael@0: if (fstat(fd, &buf)) michael@0: return false; michael@0: michael@0: if (buf.st_size >= aLength) michael@0: return false; michael@0: michael@0: const int nBlk = buf.st_blksize; michael@0: michael@0: if (!nBlk) michael@0: return false; michael@0: michael@0: if (ftruncate(fd, aLength)) michael@0: return false; michael@0: michael@0: int nWrite; // Return value from write() michael@0: int64_t iWrite = ((buf.st_size + 2 * nBlk - 1) / nBlk) * nBlk - 1; // Next offset to write to michael@0: while (iWrite < aLength) { michael@0: nWrite = 0; michael@0: if (PR_Seek64(aFD, iWrite, PR_SEEK_SET) == iWrite) michael@0: nWrite = PR_Write(aFD, "", 1); michael@0: if (nWrite != 1) break; michael@0: iWrite += nBlk; michael@0: } michael@0: michael@0: PR_Seek64(aFD, oldpos, PR_SEEK_SET); michael@0: return nWrite == 1; michael@0: #endif michael@0: return false; michael@0: } michael@0: michael@0: #ifdef ReadSysFile_PRESENT michael@0: michael@0: bool michael@0: mozilla::ReadSysFile( michael@0: const char* aFilename, michael@0: char* aBuf, michael@0: size_t aBufSize) michael@0: { michael@0: int fd = MOZ_TEMP_FAILURE_RETRY(open(aFilename, O_RDONLY)); michael@0: if (fd < 0) { michael@0: return false; michael@0: } michael@0: ScopedClose autoClose(fd); michael@0: if (aBufSize == 0) { michael@0: return true; michael@0: } michael@0: ssize_t bytesRead; michael@0: size_t offset = 0; michael@0: do { michael@0: bytesRead = MOZ_TEMP_FAILURE_RETRY( michael@0: read(fd, aBuf + offset, aBufSize - offset)); michael@0: if (bytesRead == -1) { michael@0: return false; michael@0: } michael@0: offset += bytesRead; michael@0: } while (bytesRead > 0 && offset < aBufSize); michael@0: MOZ_ASSERT(offset <= aBufSize); michael@0: if (offset > 0 && aBuf[offset - 1] == '\n') { michael@0: offset--; michael@0: } michael@0: if (offset == aBufSize) { michael@0: MOZ_ASSERT(offset > 0); michael@0: offset--; michael@0: } michael@0: aBuf[offset] = '\0'; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: mozilla::ReadSysFile( michael@0: const char* aFilename, michael@0: int* aVal) michael@0: { michael@0: char valBuf[32]; michael@0: if (!ReadSysFile(aFilename, valBuf, sizeof(valBuf))) { michael@0: return false; michael@0: } michael@0: return sscanf(valBuf, "%d", aVal) == 1; michael@0: } michael@0: michael@0: bool michael@0: mozilla::ReadSysFile( michael@0: const char* aFilename, michael@0: bool* aVal) michael@0: { michael@0: int v; michael@0: if (!ReadSysFile(aFilename, &v)) { michael@0: return false; michael@0: } michael@0: *aVal = (v != 0); michael@0: return true; michael@0: } michael@0: michael@0: #endif /* ReadSysFile_PRESENT */ michael@0: michael@0: void michael@0: mozilla::ReadAheadLib(nsIFile* aFile) michael@0: { michael@0: #if defined(XP_WIN) michael@0: nsAutoString path; michael@0: if (!aFile || NS_FAILED(aFile->GetPath(path))) { michael@0: return; michael@0: } michael@0: ReadAheadLib(path.get()); michael@0: #elif defined(LINUX) && !defined(ANDROID) || defined(XP_MACOSX) michael@0: nsAutoCString nativePath; michael@0: if (!aFile || NS_FAILED(aFile->GetNativePath(nativePath))) { michael@0: return; michael@0: } michael@0: ReadAheadLib(nativePath.get()); michael@0: #endif michael@0: } michael@0: michael@0: void michael@0: mozilla::ReadAheadFile(nsIFile* aFile, const size_t aOffset, michael@0: const size_t aCount, mozilla::filedesc_t* aOutFd) michael@0: { michael@0: #if defined(XP_WIN) michael@0: nsAutoString path; michael@0: if (!aFile || NS_FAILED(aFile->GetPath(path))) { michael@0: return; michael@0: } michael@0: ReadAheadFile(path.get(), aOffset, aCount, aOutFd); michael@0: #elif defined(LINUX) && !defined(ANDROID) || defined(XP_MACOSX) michael@0: nsAutoCString nativePath; michael@0: if (!aFile || NS_FAILED(aFile->GetNativePath(nativePath))) { michael@0: return; michael@0: } michael@0: ReadAheadFile(nativePath.get(), aOffset, aCount, aOutFd); michael@0: #endif michael@0: } michael@0: michael@0: #endif // !defined(XPCOM_GLUE) michael@0: michael@0: #if defined(LINUX) && !defined(ANDROID) michael@0: michael@0: static const unsigned int bufsize = 4096; michael@0: michael@0: #ifdef __LP64__ michael@0: typedef Elf64_Ehdr Elf_Ehdr; michael@0: typedef Elf64_Phdr Elf_Phdr; michael@0: static const unsigned char ELFCLASS = ELFCLASS64; michael@0: typedef Elf64_Off Elf_Off; michael@0: #else michael@0: typedef Elf32_Ehdr Elf_Ehdr; michael@0: typedef Elf32_Phdr Elf_Phdr; michael@0: static const unsigned char ELFCLASS = ELFCLASS32; michael@0: typedef Elf32_Off Elf_Off; michael@0: #endif michael@0: michael@0: #elif defined(XP_MACOSX) michael@0: michael@0: #if defined(__i386__) michael@0: static const uint32_t CPU_TYPE = CPU_TYPE_X86; michael@0: #elif defined(__x86_64__) michael@0: static const uint32_t CPU_TYPE = CPU_TYPE_X86_64; michael@0: #elif defined(__ppc__) michael@0: static const uint32_t CPU_TYPE = CPU_TYPE_POWERPC; michael@0: #elif defined(__ppc64__) michael@0: static const uint32_t CPU_TYPE = CPU_TYPE_POWERPC64; michael@0: #else michael@0: #error Unsupported CPU type michael@0: #endif michael@0: michael@0: #ifdef __LP64__ michael@0: #undef LC_SEGMENT michael@0: #define LC_SEGMENT LC_SEGMENT_64 michael@0: #undef MH_MAGIC michael@0: #define MH_MAGIC MH_MAGIC_64 michael@0: #define cpu_mach_header mach_header_64 michael@0: #define segment_command segment_command_64 michael@0: #else michael@0: #define cpu_mach_header mach_header michael@0: #endif michael@0: michael@0: class ScopedMMap michael@0: { michael@0: public: michael@0: ScopedMMap(const char *aFilePath) michael@0: : buf(nullptr) michael@0: { michael@0: fd = open(aFilePath, O_RDONLY); michael@0: if (fd < 0) { michael@0: return; michael@0: } michael@0: struct stat st; michael@0: if (fstat(fd, &st) < 0) { michael@0: return; michael@0: } michael@0: size = st.st_size; michael@0: buf = (char *)mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0); michael@0: } michael@0: ~ScopedMMap() michael@0: { michael@0: if (buf) { michael@0: munmap(buf, size); michael@0: } michael@0: if (fd >= 0) { michael@0: close(fd); michael@0: } michael@0: } michael@0: operator char *() { return buf; } michael@0: int getFd() { return fd; } michael@0: private: michael@0: int fd; michael@0: char *buf; michael@0: size_t size; michael@0: }; michael@0: #endif michael@0: michael@0: void michael@0: mozilla::ReadAhead(mozilla::filedesc_t aFd, const size_t aOffset, michael@0: const size_t aCount) michael@0: { michael@0: #if defined(XP_WIN) michael@0: michael@0: LARGE_INTEGER fpOriginal; michael@0: LARGE_INTEGER fpOffset; michael@0: #if defined(HAVE_LONG_LONG) michael@0: fpOffset.QuadPart = 0; michael@0: #else michael@0: fpOffset.u.LowPart = 0; michael@0: fpOffset.u.HighPart = 0; michael@0: #endif michael@0: michael@0: // Get the current file pointer so that we can restore it. This isn't michael@0: // really necessary other than to provide the same semantics regarding the michael@0: // file pointer that other platforms do michael@0: if (!SetFilePointerEx(aFd, fpOffset, &fpOriginal, FILE_CURRENT)) { michael@0: return; michael@0: } michael@0: michael@0: if (aOffset) { michael@0: #if defined(HAVE_LONG_LONG) michael@0: fpOffset.QuadPart = static_cast(aOffset); michael@0: #else michael@0: fpOffset.u.LowPart = aOffset; michael@0: fpOffset.u.HighPart = 0; michael@0: #endif michael@0: michael@0: if (!SetFilePointerEx(aFd, fpOffset, nullptr, FILE_BEGIN)) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: char buf[64 * 1024]; michael@0: size_t totalBytesRead = 0; michael@0: DWORD dwBytesRead; michael@0: // Do dummy reads to trigger kernel-side readhead via FILE_FLAG_SEQUENTIAL_SCAN. michael@0: // Abort when underfilling because during testing the buffers are read fully michael@0: // A buffer that's not keeping up would imply that readahead isn't working right michael@0: while (totalBytesRead < aCount && michael@0: ReadFile(aFd, buf, sizeof(buf), &dwBytesRead, nullptr) && michael@0: dwBytesRead == sizeof(buf)) { michael@0: totalBytesRead += dwBytesRead; michael@0: } michael@0: michael@0: // Restore the file pointer michael@0: SetFilePointerEx(aFd, fpOriginal, nullptr, FILE_BEGIN); michael@0: michael@0: #elif defined(LINUX) && !defined(ANDROID) michael@0: michael@0: readahead(aFd, aOffset, aCount); michael@0: michael@0: #elif defined(XP_MACOSX) michael@0: michael@0: struct radvisory ra; michael@0: ra.ra_offset = aOffset; michael@0: ra.ra_count = aCount; michael@0: // The F_RDADVISE fcntl is equivalent to Linux' readahead() system call. michael@0: fcntl(aFd, F_RDADVISE, &ra); michael@0: michael@0: #endif michael@0: } michael@0: michael@0: void michael@0: mozilla::ReadAheadLib(mozilla::pathstr_t aFilePath) michael@0: { michael@0: if (!aFilePath) { michael@0: return; michael@0: } michael@0: #if defined(XP_WIN) michael@0: ReadAheadFile(aFilePath); michael@0: #elif defined(LINUX) && !defined(ANDROID) michael@0: int fd = open(aFilePath, O_RDONLY); michael@0: if (fd < 0) { michael@0: return; michael@0: } michael@0: michael@0: union { michael@0: char buf[bufsize]; michael@0: Elf_Ehdr ehdr; michael@0: } elf; michael@0: // Read ELF header (ehdr) and program header table (phdr). michael@0: // We check that the ELF magic is found, that the ELF class matches michael@0: // our own, and that the program header table as defined in the ELF michael@0: // headers fits in the buffer we read. michael@0: if ((read(fd, elf.buf, bufsize) <= 0) || michael@0: (memcmp(elf.buf, ELFMAG, 4)) || michael@0: (elf.ehdr.e_ident[EI_CLASS] != ELFCLASS) || michael@0: (elf.ehdr.e_phoff + elf.ehdr.e_phentsize * elf.ehdr.e_phnum >= bufsize)) { michael@0: close(fd); michael@0: return; michael@0: } michael@0: // The program header table contains segment definitions. One such michael@0: // segment type is PT_LOAD, which describes how the dynamic loader michael@0: // is going to map the file in memory. We use that information to michael@0: // find the biggest offset from the library that will be mapped in michael@0: // memory. michael@0: Elf_Phdr *phdr = (Elf_Phdr *)&elf.buf[elf.ehdr.e_phoff]; michael@0: Elf_Off end = 0; michael@0: for (int phnum = elf.ehdr.e_phnum; phnum; phdr++, phnum--) { michael@0: if ((phdr->p_type == PT_LOAD) && michael@0: (end < phdr->p_offset + phdr->p_filesz)) { michael@0: end = phdr->p_offset + phdr->p_filesz; michael@0: } michael@0: } michael@0: // Let the kernel read ahead what the dynamic loader is going to michael@0: // map in memory soon after. michael@0: if (end > 0) { michael@0: ReadAhead(fd, 0, end); michael@0: } michael@0: close(fd); michael@0: #elif defined(XP_MACOSX) michael@0: ScopedMMap buf(aFilePath); michael@0: char *base = buf; michael@0: if (!base) { michael@0: return; michael@0: } michael@0: michael@0: // An OSX binary might either be a fat (universal) binary or a michael@0: // Mach-O binary. A fat binary actually embeds several Mach-O michael@0: // binaries. If we have a fat binary, find the offset where the michael@0: // Mach-O binary for our CPU type can be found. michael@0: struct fat_header *fh = (struct fat_header *)base; michael@0: michael@0: if (OSSwapBigToHostInt32(fh->magic) == FAT_MAGIC) { michael@0: uint32_t nfat_arch = OSSwapBigToHostInt32(fh->nfat_arch); michael@0: struct fat_arch *arch = (struct fat_arch *)&buf[sizeof(struct fat_header)]; michael@0: for (; nfat_arch; arch++, nfat_arch--) { michael@0: if (OSSwapBigToHostInt32(arch->cputype) == CPU_TYPE) { michael@0: base += OSSwapBigToHostInt32(arch->offset); michael@0: break; michael@0: } michael@0: } michael@0: if (base == buf) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // Check Mach-O magic in the Mach header michael@0: struct cpu_mach_header *mh = (struct cpu_mach_header *)base; michael@0: if (mh->magic != MH_MAGIC) { michael@0: return; michael@0: } michael@0: michael@0: // The Mach header is followed by a sequence of load commands. michael@0: // Each command has a header containing the command type and the michael@0: // command size. LD_SEGMENT commands describes how the dynamic michael@0: // loader is going to map the file in memory. We use that michael@0: // information to find the biggest offset from the library that michael@0: // will be mapped in memory. michael@0: char *cmd = &base[sizeof(struct cpu_mach_header)]; michael@0: uint32_t end = 0; michael@0: for (uint32_t ncmds = mh->ncmds; ncmds; ncmds--) { michael@0: struct segment_command *sh = (struct segment_command *)cmd; michael@0: if (sh->cmd != LC_SEGMENT) { michael@0: continue; michael@0: } michael@0: if (end < sh->fileoff + sh->filesize) { michael@0: end = sh->fileoff + sh->filesize; michael@0: } michael@0: cmd += sh->cmdsize; michael@0: } michael@0: // Let the kernel read ahead what the dynamic loader is going to michael@0: // map in memory soon after. michael@0: if (end > 0) { michael@0: ReadAhead(buf.getFd(), base - buf, end); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: void michael@0: mozilla::ReadAheadFile(mozilla::pathstr_t aFilePath, const size_t aOffset, michael@0: const size_t aCount, mozilla::filedesc_t* aOutFd) michael@0: { michael@0: #if defined(XP_WIN) michael@0: if (!aFilePath) { michael@0: if (aOutFd) { michael@0: *aOutFd = INVALID_HANDLE_VALUE; michael@0: } michael@0: return; michael@0: } michael@0: HANDLE fd = CreateFileW(aFilePath, GENERIC_READ, FILE_SHARE_READ, nullptr, michael@0: OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, nullptr); michael@0: if (aOutFd) { michael@0: *aOutFd = fd; michael@0: } michael@0: if (fd == INVALID_HANDLE_VALUE) { michael@0: return; michael@0: } michael@0: ReadAhead(fd, aOffset, aCount); michael@0: if (!aOutFd) { michael@0: CloseHandle(fd); michael@0: } michael@0: #elif defined(LINUX) && !defined(ANDROID) || defined(XP_MACOSX) michael@0: if (!aFilePath) { michael@0: if (aOutFd) { michael@0: *aOutFd = -1; michael@0: } michael@0: return; michael@0: } michael@0: int fd = open(aFilePath, O_RDONLY); michael@0: if (aOutFd) { michael@0: *aOutFd = fd; michael@0: } michael@0: if (fd < 0) { michael@0: return; michael@0: } michael@0: size_t count; michael@0: if (aCount == SIZE_MAX) { michael@0: struct stat st; michael@0: if (fstat(fd, &st) < 0) { michael@0: if (!aOutFd) { michael@0: close(fd); michael@0: } michael@0: return; michael@0: } michael@0: count = st.st_size; michael@0: } else { michael@0: count = aCount; michael@0: } michael@0: ReadAhead(fd, aOffset, count); michael@0: if (!aOutFd) { michael@0: close(fd); michael@0: } michael@0: #endif michael@0: } michael@0: