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