michael@0: // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. michael@0: // Use of this source code is governed by a BSD-style license that can be michael@0: // found in the LICENSE file. michael@0: michael@0: #include "base/shared_memory.h" michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "base/file_util.h" michael@0: #include "base/logging.h" michael@0: #include "base/platform_thread.h" michael@0: #include "base/string_util.h" michael@0: michael@0: namespace base { michael@0: michael@0: SharedMemory::SharedMemory() michael@0: : mapped_file_(-1), michael@0: inode_(0), michael@0: memory_(NULL), michael@0: read_only_(false), michael@0: max_size_(0) { michael@0: } michael@0: michael@0: SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only) michael@0: : mapped_file_(handle.fd), michael@0: inode_(0), michael@0: memory_(NULL), michael@0: read_only_(read_only), michael@0: max_size_(0) { michael@0: struct stat st; michael@0: if (fstat(handle.fd, &st) == 0) { michael@0: // If fstat fails, then the file descriptor is invalid and we'll learn this michael@0: // fact when Map() fails. michael@0: inode_ = st.st_ino; michael@0: } michael@0: } michael@0: michael@0: SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only, michael@0: ProcessHandle process) michael@0: : mapped_file_(handle.fd), michael@0: memory_(NULL), michael@0: read_only_(read_only), michael@0: max_size_(0) { michael@0: // We don't handle this case yet (note the ignored parameter); let's die if michael@0: // someone comes calling. michael@0: NOTREACHED(); michael@0: } michael@0: michael@0: SharedMemory::~SharedMemory() { michael@0: Close(); michael@0: } michael@0: michael@0: // static michael@0: bool SharedMemory::IsHandleValid(const SharedMemoryHandle& handle) { michael@0: return handle.fd >= 0; michael@0: } michael@0: michael@0: // static michael@0: SharedMemoryHandle SharedMemory::NULLHandle() { michael@0: return SharedMemoryHandle(); michael@0: } michael@0: michael@0: bool SharedMemory::Create(const std::string &cname, bool read_only, michael@0: bool open_existing, size_t size) { michael@0: read_only_ = read_only; michael@0: michael@0: std::wstring name = UTF8ToWide(cname); michael@0: michael@0: int posix_flags = 0; michael@0: posix_flags |= read_only ? O_RDONLY : O_RDWR; michael@0: if (!open_existing || mapped_file_ <= 0) michael@0: posix_flags |= O_CREAT; michael@0: michael@0: if (!CreateOrOpen(name, posix_flags, size)) michael@0: return false; michael@0: michael@0: max_size_ = size; michael@0: return true; michael@0: } michael@0: michael@0: // Our current implementation of shmem is with mmap()ing of files. michael@0: // These files need to be deleted explicitly. michael@0: // In practice this call is only needed for unit tests. michael@0: bool SharedMemory::Delete(const std::wstring& name) { michael@0: std::wstring mem_filename; michael@0: if (FilenameForMemoryName(name, &mem_filename) == false) michael@0: return false; michael@0: michael@0: FilePath path(WideToUTF8(mem_filename)); michael@0: if (file_util::PathExists(path)) { michael@0: return file_util::Delete(path, false); michael@0: } michael@0: michael@0: // Doesn't exist, so success. michael@0: return true; michael@0: } michael@0: michael@0: bool SharedMemory::Open(const std::wstring &name, bool read_only) { michael@0: read_only_ = read_only; michael@0: michael@0: int posix_flags = 0; michael@0: posix_flags |= read_only ? O_RDONLY : O_RDWR; michael@0: michael@0: return CreateOrOpen(name, posix_flags, 0); michael@0: } michael@0: michael@0: // For the given shmem named |memname|, return a filename to mmap() michael@0: // (and possibly create). Modifies |filename|. Return false on michael@0: // error, or true of we are happy. michael@0: bool SharedMemory::FilenameForMemoryName(const std::wstring &memname, michael@0: std::wstring *filename) { michael@0: std::wstring mem_filename; michael@0: michael@0: // mem_name will be used for a filename; make sure it doesn't michael@0: // contain anything which will confuse us. michael@0: DCHECK(memname.find_first_of(L"/") == std::string::npos); michael@0: DCHECK(memname.find_first_of(L"\0") == std::string::npos); michael@0: michael@0: FilePath temp_dir; michael@0: if (file_util::GetShmemTempDir(&temp_dir) == false) michael@0: return false; michael@0: michael@0: mem_filename = UTF8ToWide(temp_dir.value()); michael@0: file_util::AppendToPath(&mem_filename, L"com.google.chrome.shmem." + memname); michael@0: *filename = mem_filename; michael@0: return true; michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: // A class to handle auto-closing of FILE*'s. michael@0: class ScopedFILEClose { michael@0: public: michael@0: inline void operator()(FILE* x) const { michael@0: if (x) { michael@0: fclose(x); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: typedef scoped_ptr_malloc ScopedFILE; michael@0: michael@0: } michael@0: michael@0: // Chromium mostly only use the unique/private shmem as specified by michael@0: // "name == L"". The exception is in the StatsTable. michael@0: // TODO(jrg): there is no way to "clean up" all unused named shmem if michael@0: // we restart from a crash. (That isn't a new problem, but it is a problem.) michael@0: // In case we want to delete it later, it may be useful to save the value michael@0: // of mem_filename after FilenameForMemoryName(). michael@0: bool SharedMemory::CreateOrOpen(const std::wstring &name, michael@0: int posix_flags, size_t size) { michael@0: DCHECK(mapped_file_ == -1); michael@0: michael@0: ScopedFILE file_closer; michael@0: FILE *fp; michael@0: michael@0: if (name == L"") { michael@0: // It doesn't make sense to have a read-only private piece of shmem michael@0: DCHECK(posix_flags & (O_RDWR | O_WRONLY)); michael@0: michael@0: FilePath path; michael@0: fp = file_util::CreateAndOpenTemporaryShmemFile(&path); michael@0: michael@0: // Deleting the file prevents anyone else from mapping it in michael@0: // (making it private), and prevents the need for cleanup (once michael@0: // the last fd is closed, it is truly freed). michael@0: file_util::Delete(path, false); michael@0: } else { michael@0: std::wstring mem_filename; michael@0: if (FilenameForMemoryName(name, &mem_filename) == false) michael@0: return false; michael@0: michael@0: std::string mode; michael@0: switch (posix_flags) { michael@0: case (O_RDWR | O_CREAT): michael@0: // Careful: "w+" will truncate if it already exists. michael@0: mode = "a+"; michael@0: break; michael@0: case O_RDWR: michael@0: mode = "r+"; michael@0: break; michael@0: case O_RDONLY: michael@0: mode = "r"; michael@0: break; michael@0: default: michael@0: NOTIMPLEMENTED(); michael@0: break; michael@0: } michael@0: michael@0: fp = file_util::OpenFile(mem_filename, mode.c_str()); michael@0: } michael@0: michael@0: if (fp == NULL) michael@0: return false; michael@0: file_closer.reset(fp); // close when we go out of scope michael@0: michael@0: // Make sure the (new) file is the right size. michael@0: // According to the man page, "Use of truncate() to extend a file is michael@0: // not portable." michael@0: if (size && (posix_flags & (O_RDWR | O_CREAT))) { michael@0: // Get current size. michael@0: struct stat stat; michael@0: if (fstat(fileno(fp), &stat) != 0) michael@0: return false; michael@0: size_t current_size = stat.st_size; michael@0: if (current_size != size) { michael@0: if (ftruncate(fileno(fp), size) != 0) michael@0: return false; michael@0: if (fseeko(fp, size, SEEK_SET) != 0) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: mapped_file_ = dup(fileno(fp)); michael@0: DCHECK(mapped_file_ >= 0); michael@0: michael@0: struct stat st; michael@0: if (fstat(mapped_file_, &st)) michael@0: NOTREACHED(); michael@0: inode_ = st.st_ino; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool SharedMemory::Map(size_t bytes) { michael@0: if (mapped_file_ == -1) michael@0: return false; michael@0: michael@0: memory_ = mmap(NULL, bytes, PROT_READ | (read_only_ ? 0 : PROT_WRITE), michael@0: MAP_SHARED, mapped_file_, 0); michael@0: michael@0: if (memory_) michael@0: max_size_ = bytes; michael@0: michael@0: bool mmap_succeeded = (memory_ != (void*)-1); michael@0: DCHECK(mmap_succeeded) << "Call to mmap failed, errno=" << errno; michael@0: return mmap_succeeded; michael@0: } michael@0: michael@0: bool SharedMemory::Unmap() { michael@0: if (memory_ == NULL) michael@0: return false; michael@0: michael@0: munmap(memory_, max_size_); michael@0: memory_ = NULL; michael@0: max_size_ = 0; michael@0: return true; michael@0: } michael@0: michael@0: bool SharedMemory::ShareToProcessCommon(ProcessHandle process, michael@0: SharedMemoryHandle *new_handle, michael@0: bool close_self) { michael@0: const int new_fd = dup(mapped_file_); michael@0: DCHECK(new_fd >= -1); michael@0: new_handle->fd = new_fd; michael@0: new_handle->auto_close = true; michael@0: michael@0: if (close_self) michael@0: Close(); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: michael@0: void SharedMemory::Close() { michael@0: Unmap(); michael@0: michael@0: if (mapped_file_ > 0) { michael@0: close(mapped_file_); michael@0: mapped_file_ = -1; michael@0: } michael@0: } michael@0: michael@0: #ifdef ANDROID michael@0: void SharedMemory::LockOrUnlockCommon(int function) { michael@0: DCHECK(mapped_file_ >= 0); michael@0: struct flock lockreq; michael@0: lockreq.l_type = function; michael@0: lockreq.l_whence = SEEK_SET; michael@0: lockreq.l_start = 0; michael@0: lockreq.l_len = 0; michael@0: while (fcntl(mapped_file_, F_SETLKW, &lockreq) < 0) { michael@0: if (errno == EINTR) { michael@0: continue; michael@0: } else if (errno == ENOLCK) { michael@0: // temporary kernel resource exaustion michael@0: PlatformThread::Sleep(500); michael@0: continue; michael@0: } else { michael@0: NOTREACHED() << "lockf() failed." michael@0: << " function:" << function michael@0: << " fd:" << mapped_file_ michael@0: << " errno:" << errno michael@0: << " msg:" << strerror(errno); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void SharedMemory::Lock() { michael@0: LockOrUnlockCommon(F_WRLCK); michael@0: } michael@0: michael@0: void SharedMemory::Unlock() { michael@0: LockOrUnlockCommon(F_UNLCK); michael@0: } michael@0: #else michael@0: void SharedMemory::LockOrUnlockCommon(int function) { michael@0: DCHECK(mapped_file_ >= 0); michael@0: while (lockf(mapped_file_, function, 0) < 0) { michael@0: if (errno == EINTR) { michael@0: continue; michael@0: } else if (errno == ENOLCK) { michael@0: // temporary kernel resource exaustion michael@0: PlatformThread::Sleep(500); michael@0: continue; michael@0: } else { michael@0: NOTREACHED() << "lockf() failed." michael@0: << " function:" << function michael@0: << " fd:" << mapped_file_ michael@0: << " errno:" << errno michael@0: << " msg:" << strerror(errno); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void SharedMemory::Lock() { michael@0: LockOrUnlockCommon(F_LOCK); michael@0: } michael@0: michael@0: void SharedMemory::Unlock() { michael@0: LockOrUnlockCommon(F_ULOCK); michael@0: } michael@0: #endif michael@0: michael@0: SharedMemoryHandle SharedMemory::handle() const { michael@0: return FileDescriptor(mapped_file_, false); michael@0: } michael@0: michael@0: } // namespace base