1// Copyright (c) 2011 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. 4 5#include "base/shared_memory.h" 6 7#include <errno.h> 8#include <fcntl.h> 9#include <sys/mman.h> 10#include <sys/stat.h> 11#include <unistd.h> 12 13#include "base/file_util.h" 14#include "base/logging.h" 15#include "base/threading/platform_thread.h" 16#include "base/safe_strerror_posix.h" 17#include "base/threading/thread_restrictions.h" 18#include "base/utf_string_conversions.h" 19 20namespace base { 21 22namespace { 23// Paranoia. Semaphores and shared memory segments should live in different 24// namespaces, but who knows what's out there. 25const char kSemaphoreSuffix[] = "-sem"; 26} 27 28SharedMemory::SharedMemory() 29 : mapped_file_(-1), 30 mapped_size_(0), 31 inode_(0), 32 memory_(NULL), 33 read_only_(false), 34 created_size_(0) { 35} 36 37SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only) 38 : mapped_file_(handle.fd), 39 mapped_size_(0), 40 inode_(0), 41 memory_(NULL), 42 read_only_(read_only), 43 created_size_(0) { 44 struct stat st; 45 if (fstat(handle.fd, &st) == 0) { 46 // If fstat fails, then the file descriptor is invalid and we'll learn this 47 // fact when Map() fails. 48 inode_ = st.st_ino; 49 } 50} 51 52SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only, 53 ProcessHandle process) 54 : mapped_file_(handle.fd), 55 mapped_size_(0), 56 inode_(0), 57 memory_(NULL), 58 read_only_(read_only), 59 created_size_(0) { 60 // We don't handle this case yet (note the ignored parameter); let's die if 61 // someone comes calling. 62 NOTREACHED(); 63} 64 65SharedMemory::~SharedMemory() { 66 Close(); 67} 68 69// static 70bool SharedMemory::IsHandleValid(const SharedMemoryHandle& handle) { 71 return handle.fd >= 0; 72} 73 74// static 75SharedMemoryHandle SharedMemory::NULLHandle() { 76 return SharedMemoryHandle(); 77} 78 79// static 80void SharedMemory::CloseHandle(const SharedMemoryHandle& handle) { 81 DCHECK(handle.fd >= 0); 82 if (HANDLE_EINTR(close(handle.fd)) < 0) 83 PLOG(ERROR) << "close"; 84} 85 86bool SharedMemory::CreateAndMapAnonymous(uint32 size) { 87 return CreateAnonymous(size) && Map(size); 88} 89 90bool SharedMemory::CreateAnonymous(uint32 size) { 91 return CreateNamed("", false, size); 92} 93 94// Chromium mostly only uses the unique/private shmem as specified by 95// "name == L"". The exception is in the StatsTable. 96// TODO(jrg): there is no way to "clean up" all unused named shmem if 97// we restart from a crash. (That isn't a new problem, but it is a problem.) 98// In case we want to delete it later, it may be useful to save the value 99// of mem_filename after FilePathForMemoryName(). 100bool SharedMemory::CreateNamed(const std::string& name, 101 bool open_existing, uint32 size) { 102 DCHECK_EQ(-1, mapped_file_); 103 if (size == 0) return false; 104 105 // This function theoretically can block on the disk, but realistically 106 // the temporary files we create will just go into the buffer cache 107 // and be deleted before they ever make it out to disk. 108 base::ThreadRestrictions::ScopedAllowIO allow_io; 109 110 FILE *fp; 111 bool fix_size = true; 112 113 FilePath path; 114 if (name.empty()) { 115 // It doesn't make sense to have a open-existing private piece of shmem 116 DCHECK(!open_existing); 117 // Q: Why not use the shm_open() etc. APIs? 118 // A: Because they're limited to 4mb on OS X. FFFFFFFUUUUUUUUUUU 119 fp = file_util::CreateAndOpenTemporaryShmemFile(&path); 120 121 // Deleting the file prevents anyone else from mapping it in 122 // (making it private), and prevents the need for cleanup (once 123 // the last fd is closed, it is truly freed). 124 if (fp) 125 file_util::Delete(path, false); 126 127 } else { 128 if (!FilePathForMemoryName(name, &path)) 129 return false; 130 131 fp = file_util::OpenFile(path, "w+x"); 132 if (fp == NULL && open_existing) { 133 // "w+" will truncate if it already exists. 134 fp = file_util::OpenFile(path, "a+"); 135 fix_size = false; 136 } 137 } 138 if (fp && fix_size) { 139 // Get current size. 140 struct stat stat; 141 if (fstat(fileno(fp), &stat) != 0) 142 return false; 143 const uint32 current_size = stat.st_size; 144 if (current_size != size) { 145 if (HANDLE_EINTR(ftruncate(fileno(fp), size)) != 0) 146 return false; 147 if (fseeko(fp, size, SEEK_SET) != 0) 148 return false; 149 } 150 created_size_ = size; 151 } 152 if (fp == NULL) { 153#if !defined(OS_MACOSX) 154 PLOG(ERROR) << "Creating shared memory in " << path.value() << " failed"; 155 FilePath dir = path.DirName(); 156 if (access(dir.value().c_str(), W_OK | X_OK) < 0) { 157 PLOG(ERROR) << "Unable to access(W_OK|X_OK) " << dir.value(); 158 if (dir.value() == "/dev/shm") { 159 LOG(FATAL) << "This is frequently caused by incorrect permissions on " 160 << "/dev/shm. Try 'sudo chmod 1777 /dev/shm' to fix."; 161 } 162 } 163#else 164 PLOG(ERROR) << "Creating shared memory in " << path.value() << " failed"; 165#endif 166 return false; 167 } 168 169 return PrepareMapFile(fp); 170} 171 172// Our current implementation of shmem is with mmap()ing of files. 173// These files need to be deleted explicitly. 174// In practice this call is only needed for unit tests. 175bool SharedMemory::Delete(const std::string& name) { 176 FilePath path; 177 if (!FilePathForMemoryName(name, &path)) 178 return false; 179 180 if (file_util::PathExists(path)) { 181 return file_util::Delete(path, false); 182 } 183 184 // Doesn't exist, so success. 185 return true; 186} 187 188bool SharedMemory::Open(const std::string& name, bool read_only) { 189 FilePath path; 190 if (!FilePathForMemoryName(name, &path)) 191 return false; 192 193 read_only_ = read_only; 194 195 const char *mode = read_only ? "r" : "r+"; 196 FILE *fp = file_util::OpenFile(path, mode); 197 return PrepareMapFile(fp); 198} 199 200bool SharedMemory::Map(uint32 bytes) { 201 if (mapped_file_ == -1) 202 return false; 203 204 memory_ = mmap(NULL, bytes, PROT_READ | (read_only_ ? 0 : PROT_WRITE), 205 MAP_SHARED, mapped_file_, 0); 206 207 if (memory_) 208 mapped_size_ = bytes; 209 210 bool mmap_succeeded = (memory_ != (void*)-1); 211 DCHECK(mmap_succeeded) << "Call to mmap failed, errno=" << errno; 212 return mmap_succeeded; 213} 214 215bool SharedMemory::Unmap() { 216 if (memory_ == NULL) 217 return false; 218 219 munmap(memory_, mapped_size_); 220 memory_ = NULL; 221 mapped_size_ = 0; 222 return true; 223} 224 225SharedMemoryHandle SharedMemory::handle() const { 226 return FileDescriptor(mapped_file_, false); 227} 228 229void SharedMemory::Close() { 230 Unmap(); 231 232 if (mapped_file_ > 0) { 233 if (HANDLE_EINTR(close(mapped_file_)) < 0) 234 PLOG(ERROR) << "close"; 235 mapped_file_ = -1; 236 } 237} 238 239void SharedMemory::Lock() { 240#if !defined(ANDROID) 241 LockOrUnlockCommon(F_LOCK); 242#endif 243} 244 245void SharedMemory::Unlock() { 246#if !defined(ANDROID) 247 LockOrUnlockCommon(F_ULOCK); 248#endif 249} 250 251bool SharedMemory::PrepareMapFile(FILE *fp) { 252 DCHECK_EQ(-1, mapped_file_); 253 if (fp == NULL) return false; 254 255 // This function theoretically can block on the disk, but realistically 256 // the temporary files we create will just go into the buffer cache 257 // and be deleted before they ever make it out to disk. 258 base::ThreadRestrictions::ScopedAllowIO allow_io; 259 260 file_util::ScopedFILE file_closer(fp); 261 262 mapped_file_ = dup(fileno(fp)); 263 if (mapped_file_ == -1) { 264 if (errno == EMFILE) { 265 LOG(WARNING) << "Shared memory creation failed; out of file descriptors"; 266 return false; 267 } else { 268 NOTREACHED() << "Call to dup failed, errno=" << errno; 269 } 270 } 271 272 struct stat st; 273 if (fstat(mapped_file_, &st)) 274 NOTREACHED(); 275 inode_ = st.st_ino; 276 277 return true; 278} 279 280// For the given shmem named |mem_name|, return a filename to mmap() 281// (and possibly create). Modifies |filename|. Return false on 282// error, or true of we are happy. 283bool SharedMemory::FilePathForMemoryName(const std::string& mem_name, 284 FilePath* path) { 285 // mem_name will be used for a filename; make sure it doesn't 286 // contain anything which will confuse us. 287 DCHECK_EQ(std::string::npos, mem_name.find('/')); 288 DCHECK_EQ(std::string::npos, mem_name.find('\0')); 289 290 FilePath temp_dir; 291 if (!file_util::GetShmemTempDir(&temp_dir)) 292 return false; 293 294 *path = temp_dir.AppendASCII("com.google.chrome.shmem." + mem_name); 295 return true; 296} 297 298void SharedMemory::LockOrUnlockCommon(int function) { 299 300 DCHECK_GE(mapped_file_, 0); 301#if !defined(ANDROID) 302 while (lockf(mapped_file_, function, 0) < 0) { 303 if (errno == EINTR) { 304 continue; 305 } else if (errno == ENOLCK) { 306 // temporary kernel resource exaustion 307 base::PlatformThread::Sleep(500); 308 continue; 309 } else { 310 NOTREACHED() << "lockf() failed." 311 << " function:" << function 312 << " fd:" << mapped_file_ 313 << " errno:" << errno 314 << " msg:" << safe_strerror(errno); 315 } 316 } 317#endif 318} 319 320bool SharedMemory::ShareToProcessCommon(ProcessHandle process, 321 SharedMemoryHandle *new_handle, 322 bool close_self) { 323 const int new_fd = dup(mapped_file_); 324 DCHECK_GE(new_fd, 0); 325 new_handle->fd = new_fd; 326 new_handle->auto_close = true; 327 328 if (close_self) 329 Close(); 330 331 return true; 332} 333 334} // namespace base 335