mac: Create an A/B test for batch creation for shared memory.
[chromium-blink-merge.git] / base / memory / shared_memory_posix.cc
blobcbef9d6b5a5cefaedbb60158faeb734b18a50a63
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "base/memory/shared_memory.h"
7 #include <errno.h>
8 #include <fcntl.h>
9 #include <sys/mman.h>
10 #include <sys/stat.h>
11 #include <sys/types.h>
12 #include <unistd.h>
13 #include <vector>
15 #include "base/files/file_util.h"
16 #include "base/files/scoped_file.h"
17 #include "base/lazy_instance.h"
18 #include "base/logging.h"
19 #include "base/metrics/field_trial.h"
20 #include "base/metrics/histogram.h"
21 #include "base/posix/eintr_wrapper.h"
22 #include "base/process/process_metrics.h"
23 #include "base/profiler/scoped_tracker.h"
24 #include "base/safe_strerror_posix.h"
25 #include "base/scoped_generic.h"
26 #include "base/strings/utf_string_conversions.h"
27 #include "base/synchronization/lock.h"
28 #include "base/threading/platform_thread.h"
29 #include "base/threading/thread_restrictions.h"
30 #include "base/time/time.h"
32 #if defined(OS_MACOSX)
33 #include "base/mac/foundation_util.h"
34 #endif // OS_MACOSX
36 #if defined(OS_ANDROID)
37 #include "base/os_compat_android.h"
38 #include "third_party/ashmem/ashmem.h"
39 #endif
41 namespace base {
43 namespace {
45 LazyInstance<Lock>::Leaky g_thread_lock_ = LAZY_INSTANCE_INITIALIZER;
47 #if !defined(OS_ANDROID)
48 struct ScopedPathUnlinkerTraits {
49 static FilePath* InvalidValue() { return nullptr; }
51 static void Free(FilePath* path) {
52 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/466437
53 // is fixed.
54 tracked_objects::ScopedTracker tracking_profile(
55 FROM_HERE_WITH_EXPLICIT_FUNCTION(
56 "466437 SharedMemory::Create::Unlink"));
57 if (unlink(path->value().c_str()))
58 PLOG(WARNING) << "unlink";
62 // Unlinks the FilePath when the object is destroyed.
63 typedef ScopedGeneric<FilePath*, ScopedPathUnlinkerTraits> ScopedPathUnlinker;
65 const char kSharedMemoryBatchCreate[] = "kSharedMemoryBatchCreate";
66 const char kSharedMemoryCreateStrategy[] = "SharedMemoryCreateStrategy";
68 #if defined(OS_MACOSX) && !defined(OS_IOS)
69 const int kBatchSize = 5;
71 // This variable must only be accessed if |g_thread_lock_| is held.
72 LazyInstance<std::vector<FILE*>>::Leaky g_file_pool_ =
73 LAZY_INSTANCE_INITIALIZER;
74 #endif // defined(OS_MACOSX) && !defined(OS_IOS)
76 // Whether to generate more than 1 shared memory handle at a time, and store
77 // the results in a pool.
78 bool ShouldBatchCreateSharedMemory() {
79 #if !defined(OS_MACOSX) || defined(OS_IOS)
80 return false;
81 #endif // !defined(OS_MACOSX) || defined(OS_IOS)
83 g_thread_lock_.Get().AssertAcquired();
85 static bool has_determined_group = false;
86 static bool batch_create_shared_memory = false;
88 if (has_determined_group)
89 return batch_create_shared_memory;
91 const std::string group_name =
92 base::FieldTrialList::FindFullName(kSharedMemoryCreateStrategy);
93 batch_create_shared_memory = group_name == kSharedMemoryBatchCreate;
94 has_determined_group = true;
95 return batch_create_shared_memory;
98 // Makes a temporary file, fdopens it, and then unlinks it. |fp| is populated
99 // with the fdopened FILE. |readonly_fd| is populated with the opened fd if
100 // options.share_read_only is true. |path| is populated with the location of
101 // the file before it was unlinked.
102 // Returns false if there's an unhandled failure.
103 bool CreateAnonymousSharedMemory(const SharedMemoryCreateOptions& options,
104 ScopedFILE* fp,
105 ScopedFD* readonly_fd,
106 FilePath* path) {
107 // It doesn't make sense to have a open-existing private piece of shmem
108 DCHECK(!options.open_existing_deprecated);
109 // Q: Why not use the shm_open() etc. APIs?
110 // A: Because they're limited to 4mb on OS X. FFFFFFFUUUUUUUUUUU
111 FilePath directory;
112 ScopedPathUnlinker path_unlinker;
113 if (GetShmemTempDir(options.executable, &directory)) {
114 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/466437
115 // is fixed.
116 tracked_objects::ScopedTracker tracking_profile(
117 FROM_HERE_WITH_EXPLICIT_FUNCTION(
118 "466437 SharedMemory::Create::OpenTemporaryFile"));
119 fp->reset(base::CreateAndOpenTemporaryFileInDir(directory, path));
121 // Deleting the file prevents anyone else from mapping it in (making it
122 // private), and prevents the need for cleanup (once the last fd is
123 // closed, it is truly freed).
124 if (*fp)
125 path_unlinker.reset(path);
128 if (*fp) {
129 if (options.share_read_only) {
130 // TODO(erikchen): Remove ScopedTracker below once
131 // http://crbug.com/466437 is fixed.
132 tracked_objects::ScopedTracker tracking_profile(
133 FROM_HERE_WITH_EXPLICIT_FUNCTION(
134 "466437 SharedMemory::Create::OpenReadonly"));
135 // Also open as readonly so that we can ShareReadOnlyToProcess.
136 readonly_fd->reset(HANDLE_EINTR(open(path->value().c_str(), O_RDONLY)));
137 if (!readonly_fd->is_valid()) {
138 DPLOG(ERROR) << "open(\"" << path->value() << "\", O_RDONLY) failed";
139 fp->reset();
140 return false;
144 return true;
147 #if defined(OS_MACOSX) && !defined(OS_IOS)
148 // This method must only be called on OSX, since it assumes that
149 // |options.executable| has no effect. It also doesn't fill in |path|, which is
150 // only used for error logging when |options.share_read_only| is false.
151 bool CreateAnonymousSharedMemoryFromBatch(
152 const SharedMemoryCreateOptions& options,
153 ScopedFILE* fp,
154 FilePath* path) {
155 DCHECK(!options.share_read_only);
156 g_thread_lock_.Get().AssertAcquired();
157 std::vector<FILE*>& file_pool = g_file_pool_.Get();
159 if (file_pool.empty()) {
160 for (int i = 0; i < kBatchSize; ++i) {
161 ScopedFILE temp_fp;
162 FilePath temp_path;
163 bool result =
164 CreateAnonymousSharedMemory(options, &temp_fp, NULL, &temp_path);
165 if (result)
166 file_pool.push_back(temp_fp.release());
170 if (file_pool.empty())
171 return false;
173 FILE* file = file_pool.back();
174 file_pool.pop_back();
175 fp->reset(file);
176 return true;
178 #endif // defined(OS_MACOSX) && !defined(OS_IOS)
179 #endif // !defined(OS_ANDROID)
182 SharedMemory::SharedMemory()
183 : mapped_file_(-1),
184 readonly_mapped_file_(-1),
185 inode_(0),
186 mapped_size_(0),
187 memory_(NULL),
188 read_only_(false),
189 requested_size_(0) {
192 SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only)
193 : mapped_file_(handle.fd),
194 readonly_mapped_file_(-1),
195 inode_(0),
196 mapped_size_(0),
197 memory_(NULL),
198 read_only_(read_only),
199 requested_size_(0) {
200 struct stat st;
201 if (fstat(handle.fd, &st) == 0) {
202 // If fstat fails, then the file descriptor is invalid and we'll learn this
203 // fact when Map() fails.
204 inode_ = st.st_ino;
208 SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only,
209 ProcessHandle process)
210 : mapped_file_(handle.fd),
211 readonly_mapped_file_(-1),
212 inode_(0),
213 mapped_size_(0),
214 memory_(NULL),
215 read_only_(read_only),
216 requested_size_(0) {
217 // We don't handle this case yet (note the ignored parameter); let's die if
218 // someone comes calling.
219 NOTREACHED();
222 SharedMemory::~SharedMemory() {
223 Unmap();
224 Close();
227 // static
228 bool SharedMemory::IsHandleValid(const SharedMemoryHandle& handle) {
229 return handle.fd >= 0;
232 // static
233 SharedMemoryHandle SharedMemory::NULLHandle() {
234 return SharedMemoryHandle();
237 // static
238 void SharedMemory::CloseHandle(const SharedMemoryHandle& handle) {
239 DCHECK_GE(handle.fd, 0);
240 if (close(handle.fd) < 0)
241 DPLOG(ERROR) << "close";
244 // static
245 size_t SharedMemory::GetHandleLimit() {
246 return base::GetMaxFds();
249 bool SharedMemory::CreateAndMapAnonymous(size_t size) {
250 return CreateAnonymous(size) && Map(size);
253 #if !defined(OS_ANDROID)
254 // Chromium mostly only uses the unique/private shmem as specified by
255 // "name == L"". The exception is in the StatsTable.
256 // TODO(jrg): there is no way to "clean up" all unused named shmem if
257 // we restart from a crash. (That isn't a new problem, but it is a problem.)
258 // In case we want to delete it later, it may be useful to save the value
259 // of mem_filename after FilePathForMemoryName().
260 bool SharedMemory::Create(const SharedMemoryCreateOptions& options) {
261 // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/466437
262 // is fixed.
263 tracked_objects::ScopedTracker tracking_profile1(
264 FROM_HERE_WITH_EXPLICIT_FUNCTION(
265 "466437 SharedMemory::Create::Start"));
266 DCHECK_EQ(-1, mapped_file_);
267 if (options.size == 0) return false;
269 if (options.size > static_cast<size_t>(std::numeric_limits<int>::max()))
270 return false;
272 // This function theoretically can block on the disk, but realistically
273 // the temporary files we create will just go into the buffer cache
274 // and be deleted before they ever make it out to disk.
275 base::ThreadRestrictions::ScopedAllowIO allow_io;
277 ScopedFILE fp;
278 bool fix_size = true;
279 ScopedFD readonly_fd;
281 FilePath path;
282 if (options.name_deprecated == NULL || options.name_deprecated->empty()) {
283 AutoLock a(g_thread_lock_.Get());
285 Time start_time = base::Time::Now();
286 if (options.share_read_only || !ShouldBatchCreateSharedMemory()) {
287 bool result =
288 CreateAnonymousSharedMemory(options, &fp, &readonly_fd, &path);
289 if (!result)
290 return false;
291 } else {
292 #if defined(OS_MACOSX) && !defined(OS_IOS)
293 bool result = CreateAnonymousSharedMemoryFromBatch(options, &fp, &path);
294 if (!result)
295 return false;
296 #else
297 NOTREACHED();
298 #endif // defined(OS_MACOSX) && !defined(OS_IOS)
300 if (!options.share_read_only) {
301 UMA_HISTOGRAM_TIMES("SharedMemory.TimeSpentMakingAnonymousMemory",
302 Time::Now() - start_time);
304 } else {
305 if (!FilePathForMemoryName(*options.name_deprecated, &path))
306 return false;
308 // Make sure that the file is opened without any permission
309 // to other users on the system.
310 const mode_t kOwnerOnly = S_IRUSR | S_IWUSR;
312 // First, try to create the file.
313 int fd = HANDLE_EINTR(
314 open(path.value().c_str(), O_RDWR | O_CREAT | O_EXCL, kOwnerOnly));
315 if (fd == -1 && options.open_existing_deprecated) {
316 // If this doesn't work, try and open an existing file in append mode.
317 // Opening an existing file in a world writable directory has two main
318 // security implications:
319 // - Attackers could plant a file under their control, so ownership of
320 // the file is checked below.
321 // - Attackers could plant a symbolic link so that an unexpected file
322 // is opened, so O_NOFOLLOW is passed to open().
323 fd = HANDLE_EINTR(
324 open(path.value().c_str(), O_RDWR | O_APPEND | O_NOFOLLOW));
326 // Check that the current user owns the file.
327 // If uid != euid, then a more complex permission model is used and this
328 // API is not appropriate.
329 const uid_t real_uid = getuid();
330 const uid_t effective_uid = geteuid();
331 struct stat sb;
332 if (fd >= 0 &&
333 (fstat(fd, &sb) != 0 || sb.st_uid != real_uid ||
334 sb.st_uid != effective_uid)) {
335 LOG(ERROR) <<
336 "Invalid owner when opening existing shared memory file.";
337 close(fd);
338 return false;
341 // An existing file was opened, so its size should not be fixed.
342 fix_size = false;
345 if (options.share_read_only) {
346 // Also open as readonly so that we can ShareReadOnlyToProcess.
347 readonly_fd.reset(HANDLE_EINTR(open(path.value().c_str(), O_RDONLY)));
348 if (!readonly_fd.is_valid()) {
349 DPLOG(ERROR) << "open(\"" << path.value() << "\", O_RDONLY) failed";
350 close(fd);
351 fd = -1;
352 return false;
355 if (fd >= 0) {
356 // "a+" is always appropriate: if it's a new file, a+ is similar to w+.
357 fp.reset(fdopen(fd, "a+"));
360 if (fp && fix_size) {
361 // Get current size.
362 struct stat stat;
363 if (fstat(fileno(fp.get()), &stat) != 0)
364 return false;
365 const size_t current_size = stat.st_size;
366 if (current_size != options.size) {
367 if (HANDLE_EINTR(ftruncate(fileno(fp.get()), options.size)) != 0)
368 return false;
370 requested_size_ = options.size;
372 if (fp == NULL) {
373 #if !defined(OS_MACOSX)
374 PLOG(ERROR) << "Creating shared memory in " << path.value() << " failed";
375 FilePath dir = path.DirName();
376 if (access(dir.value().c_str(), W_OK | X_OK) < 0) {
377 PLOG(ERROR) << "Unable to access(W_OK|X_OK) " << dir.value();
378 if (dir.value() == "/dev/shm") {
379 LOG(FATAL) << "This is frequently caused by incorrect permissions on "
380 << "/dev/shm. Try 'sudo chmod 1777 /dev/shm' to fix.";
383 #else
384 PLOG(ERROR) << "Creating shared memory in " << path.value() << " failed";
385 #endif
386 return false;
389 return PrepareMapFile(fp.Pass(), readonly_fd.Pass());
392 // Our current implementation of shmem is with mmap()ing of files.
393 // These files need to be deleted explicitly.
394 // In practice this call is only needed for unit tests.
395 bool SharedMemory::Delete(const std::string& name) {
396 FilePath path;
397 if (!FilePathForMemoryName(name, &path))
398 return false;
400 if (PathExists(path))
401 return base::DeleteFile(path, false);
403 // Doesn't exist, so success.
404 return true;
407 bool SharedMemory::Open(const std::string& name, bool read_only) {
408 FilePath path;
409 if (!FilePathForMemoryName(name, &path))
410 return false;
412 read_only_ = read_only;
414 const char *mode = read_only ? "r" : "r+";
415 ScopedFILE fp(base::OpenFile(path, mode));
416 ScopedFD readonly_fd(HANDLE_EINTR(open(path.value().c_str(), O_RDONLY)));
417 if (!readonly_fd.is_valid()) {
418 DPLOG(ERROR) << "open(\"" << path.value() << "\", O_RDONLY) failed";
419 return false;
421 return PrepareMapFile(fp.Pass(), readonly_fd.Pass());
423 #endif // !defined(OS_ANDROID)
425 bool SharedMemory::MapAt(off_t offset, size_t bytes) {
426 if (mapped_file_ == -1)
427 return false;
429 if (bytes > static_cast<size_t>(std::numeric_limits<int>::max()))
430 return false;
432 if (memory_)
433 return false;
435 #if defined(OS_ANDROID)
436 // On Android, Map can be called with a size and offset of zero to use the
437 // ashmem-determined size.
438 if (bytes == 0) {
439 DCHECK_EQ(0, offset);
440 int ashmem_bytes = ashmem_get_size_region(mapped_file_);
441 if (ashmem_bytes < 0)
442 return false;
443 bytes = ashmem_bytes;
445 #endif
447 memory_ = mmap(NULL, bytes, PROT_READ | (read_only_ ? 0 : PROT_WRITE),
448 MAP_SHARED, mapped_file_, offset);
450 bool mmap_succeeded = memory_ != (void*)-1 && memory_ != NULL;
451 if (mmap_succeeded) {
452 mapped_size_ = bytes;
453 DCHECK_EQ(0U, reinterpret_cast<uintptr_t>(memory_) &
454 (SharedMemory::MAP_MINIMUM_ALIGNMENT - 1));
455 } else {
456 memory_ = NULL;
459 return mmap_succeeded;
462 bool SharedMemory::Unmap() {
463 if (memory_ == NULL)
464 return false;
466 munmap(memory_, mapped_size_);
467 memory_ = NULL;
468 mapped_size_ = 0;
469 return true;
472 SharedMemoryHandle SharedMemory::handle() const {
473 return FileDescriptor(mapped_file_, false);
476 void SharedMemory::Close() {
477 if (mapped_file_ > 0) {
478 if (close(mapped_file_) < 0)
479 PLOG(ERROR) << "close";
480 mapped_file_ = -1;
482 if (readonly_mapped_file_ > 0) {
483 if (close(readonly_mapped_file_) < 0)
484 PLOG(ERROR) << "close";
485 readonly_mapped_file_ = -1;
489 void SharedMemory::LockDeprecated() {
490 g_thread_lock_.Get().Acquire();
491 LockOrUnlockCommon(F_LOCK);
494 void SharedMemory::UnlockDeprecated() {
495 LockOrUnlockCommon(F_ULOCK);
496 g_thread_lock_.Get().Release();
499 #if !defined(OS_ANDROID)
500 bool SharedMemory::PrepareMapFile(ScopedFILE fp, ScopedFD readonly_fd) {
501 DCHECK_EQ(-1, mapped_file_);
502 DCHECK_EQ(-1, readonly_mapped_file_);
503 if (fp == NULL)
504 return false;
506 // This function theoretically can block on the disk, but realistically
507 // the temporary files we create will just go into the buffer cache
508 // and be deleted before they ever make it out to disk.
509 base::ThreadRestrictions::ScopedAllowIO allow_io;
511 struct stat st = {};
512 if (fstat(fileno(fp.get()), &st))
513 NOTREACHED();
514 if (readonly_fd.is_valid()) {
515 struct stat readonly_st = {};
516 if (fstat(readonly_fd.get(), &readonly_st))
517 NOTREACHED();
518 if (st.st_dev != readonly_st.st_dev || st.st_ino != readonly_st.st_ino) {
519 LOG(ERROR) << "writable and read-only inodes don't match; bailing";
520 return false;
524 mapped_file_ = HANDLE_EINTR(dup(fileno(fp.get())));
525 if (mapped_file_ == -1) {
526 if (errno == EMFILE) {
527 LOG(WARNING) << "Shared memory creation failed; out of file descriptors";
528 return false;
529 } else {
530 NOTREACHED() << "Call to dup failed, errno=" << errno;
533 inode_ = st.st_ino;
534 readonly_mapped_file_ = readonly_fd.release();
536 return true;
539 // For the given shmem named |mem_name|, return a filename to mmap()
540 // (and possibly create). Modifies |filename|. Return false on
541 // error, or true of we are happy.
542 bool SharedMemory::FilePathForMemoryName(const std::string& mem_name,
543 FilePath* path) {
544 // mem_name will be used for a filename; make sure it doesn't
545 // contain anything which will confuse us.
546 DCHECK_EQ(std::string::npos, mem_name.find('/'));
547 DCHECK_EQ(std::string::npos, mem_name.find('\0'));
549 FilePath temp_dir;
550 if (!GetShmemTempDir(false, &temp_dir))
551 return false;
553 #if !defined(OS_MACOSX)
554 #if defined(GOOGLE_CHROME_BUILD)
555 std::string name_base = std::string("com.google.Chrome");
556 #else
557 std::string name_base = std::string("org.chromium.Chromium");
558 #endif
559 #else // OS_MACOSX
560 std::string name_base = std::string(base::mac::BaseBundleID());
561 #endif // OS_MACOSX
562 *path = temp_dir.AppendASCII(name_base + ".shmem." + mem_name);
563 return true;
565 #endif // !defined(OS_ANDROID)
567 void SharedMemory::LockOrUnlockCommon(int function) {
568 DCHECK_GE(mapped_file_, 0);
569 while (lockf(mapped_file_, function, 0) < 0) {
570 if (errno == EINTR) {
571 continue;
572 } else if (errno == ENOLCK) {
573 // temporary kernel resource exaustion
574 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(500));
575 continue;
576 } else {
577 NOTREACHED() << "lockf() failed."
578 << " function:" << function
579 << " fd:" << mapped_file_
580 << " errno:" << errno
581 << " msg:" << safe_strerror(errno);
586 bool SharedMemory::ShareToProcessCommon(ProcessHandle process,
587 SharedMemoryHandle* new_handle,
588 bool close_self,
589 ShareMode share_mode) {
590 int handle_to_dup = -1;
591 switch(share_mode) {
592 case SHARE_CURRENT_MODE:
593 handle_to_dup = mapped_file_;
594 break;
595 case SHARE_READONLY:
596 // We could imagine re-opening the file from /dev/fd, but that can't make
597 // it readonly on Mac: https://codereview.chromium.org/27265002/#msg10
598 CHECK_GE(readonly_mapped_file_, 0);
599 handle_to_dup = readonly_mapped_file_;
600 break;
603 const int new_fd = HANDLE_EINTR(dup(handle_to_dup));
604 if (new_fd < 0) {
605 DPLOG(ERROR) << "dup() failed.";
606 return false;
609 new_handle->fd = new_fd;
610 new_handle->auto_close = true;
612 if (close_self) {
613 Unmap();
614 Close();
617 return true;
620 } // namespace base