Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "base/memory/shared_memory.h" | 5 #include "base/memory/shared_memory.h" |
| 6 | 6 |
| 7 #include <errno.h> | 7 #include <errno.h> |
| 8 #include <fcntl.h> | 8 #include <fcntl.h> |
| 9 #include <sys/mman.h> | 9 #include <sys/mman.h> |
| 10 #include <sys/stat.h> | 10 #include <sys/stat.h> |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 23 | 23 |
| 24 #if defined(OS_MACOSX) | 24 #if defined(OS_MACOSX) |
| 25 #include "base/mac/foundation_util.h" | 25 #include "base/mac/foundation_util.h" |
| 26 #endif // OS_MACOSX | 26 #endif // OS_MACOSX |
| 27 | 27 |
| 28 #if defined(OS_ANDROID) | 28 #if defined(OS_ANDROID) |
| 29 #include "base/os_compat_android.h" | 29 #include "base/os_compat_android.h" |
| 30 #include "third_party/ashmem/ashmem.h" | 30 #include "third_party/ashmem/ashmem.h" |
| 31 #endif | 31 #endif |
| 32 | 32 |
| 33 using file_util::ScopedFD; | |
| 34 using file_util::ScopedFILE; | |
| 35 | |
| 33 namespace base { | 36 namespace base { |
| 34 | 37 |
| 35 namespace { | 38 namespace { |
| 36 | 39 |
| 37 LazyInstance<Lock>::Leaky g_thread_lock_ = LAZY_INSTANCE_INITIALIZER; | 40 LazyInstance<Lock>::Leaky g_thread_lock_ = LAZY_INSTANCE_INITIALIZER; |
| 38 | 41 |
| 39 } | 42 } |
| 40 | 43 |
| 41 SharedMemory::SharedMemory() | 44 SharedMemory::SharedMemory() |
| 42 : mapped_file_(-1), | 45 : mapped_file_(-1), |
| 46 readonly_mapped_file_(-1), | |
| 43 inode_(0), | 47 inode_(0), |
| 44 mapped_size_(0), | 48 mapped_size_(0), |
| 45 memory_(NULL), | 49 memory_(NULL), |
| 46 read_only_(false), | 50 read_only_(false), |
| 47 requested_size_(0) { | 51 requested_size_(0) { |
| 48 } | 52 } |
| 49 | 53 |
| 50 SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only) | 54 SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only) |
| 51 : mapped_file_(handle.fd), | 55 : mapped_file_(handle.fd), |
| 56 readonly_mapped_file_(-1), | |
| 52 inode_(0), | 57 inode_(0), |
| 53 mapped_size_(0), | 58 mapped_size_(0), |
| 54 memory_(NULL), | 59 memory_(NULL), |
| 55 read_only_(read_only), | 60 read_only_(read_only), |
| 56 requested_size_(0) { | 61 requested_size_(0) { |
| 57 struct stat st; | 62 struct stat st; |
| 58 if (fstat(handle.fd, &st) == 0) { | 63 if (fstat(handle.fd, &st) == 0) { |
| 59 // If fstat fails, then the file descriptor is invalid and we'll learn this | 64 // If fstat fails, then the file descriptor is invalid and we'll learn this |
| 60 // fact when Map() fails. | 65 // fact when Map() fails. |
| 61 inode_ = st.st_ino; | 66 inode_ = st.st_ino; |
| 62 } | 67 } |
| 63 } | 68 } |
| 64 | 69 |
| 65 SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only, | 70 SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only, |
| 66 ProcessHandle process) | 71 ProcessHandle process) |
| 67 : mapped_file_(handle.fd), | 72 : mapped_file_(handle.fd), |
| 73 readonly_mapped_file_(-1), | |
| 68 inode_(0), | 74 inode_(0), |
| 69 mapped_size_(0), | 75 mapped_size_(0), |
| 70 memory_(NULL), | 76 memory_(NULL), |
| 71 read_only_(read_only), | 77 read_only_(read_only), |
| 72 requested_size_(0) { | 78 requested_size_(0) { |
| 73 // We don't handle this case yet (note the ignored parameter); let's die if | 79 // We don't handle this case yet (note the ignored parameter); let's die if |
| 74 // someone comes calling. | 80 // someone comes calling. |
| 75 NOTREACHED(); | 81 NOTREACHED(); |
| 76 } | 82 } |
| 77 | 83 |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 117 if (options.size == 0) return false; | 123 if (options.size == 0) return false; |
| 118 | 124 |
| 119 if (options.size > static_cast<size_t>(std::numeric_limits<int>::max())) | 125 if (options.size > static_cast<size_t>(std::numeric_limits<int>::max())) |
| 120 return false; | 126 return false; |
| 121 | 127 |
| 122 // This function theoretically can block on the disk, but realistically | 128 // This function theoretically can block on the disk, but realistically |
| 123 // the temporary files we create will just go into the buffer cache | 129 // the temporary files we create will just go into the buffer cache |
| 124 // and be deleted before they ever make it out to disk. | 130 // and be deleted before they ever make it out to disk. |
| 125 base::ThreadRestrictions::ScopedAllowIO allow_io; | 131 base::ThreadRestrictions::ScopedAllowIO allow_io; |
| 126 | 132 |
| 127 FILE *fp; | 133 ScopedFILE fp; |
| 128 bool fix_size = true; | 134 bool fix_size = true; |
| 135 int readonly_fd_storage = -1; | |
| 136 ScopedFD readonly_fd(&readonly_fd_storage); | |
| 129 | 137 |
| 130 FilePath path; | 138 FilePath path; |
| 131 if (options.name == NULL || options.name->empty()) { | 139 if (options.name == NULL || options.name->empty()) { |
| 132 // It doesn't make sense to have a open-existing private piece of shmem | 140 // It doesn't make sense to have a open-existing private piece of shmem |
| 133 DCHECK(!options.open_existing); | 141 DCHECK(!options.open_existing); |
| 134 // Q: Why not use the shm_open() etc. APIs? | 142 // Q: Why not use the shm_open() etc. APIs? |
| 135 // A: Because they're limited to 4mb on OS X. FFFFFFFUUUUUUUUUUU | 143 // A: Because they're limited to 4mb on OS X. FFFFFFFUUUUUUUUUUU |
| 136 fp = file_util::CreateAndOpenTemporaryShmemFile(&path, options.executable); | 144 fp.reset( |
| 145 file_util::CreateAndOpenTemporaryShmemFile(&path, options.executable)); | |
| 137 | 146 |
| 138 // Deleting the file prevents anyone else from mapping it in (making it | |
| 139 // private), and prevents the need for cleanup (once the last fd is closed, | |
| 140 // it is truly freed). | |
| 141 if (fp) { | 147 if (fp) { |
| 148 // Also open as readonly so that we can ShareReadOnlyToProcess. | |
| 149 *readonly_fd = HANDLE_EINTR(open(path.value().c_str(), O_RDONLY)); | |
| 150 if (*readonly_fd < 0) { | |
| 151 DPLOG(ERROR) << "open(\"" << path.value().c_str() | |
| 152 << "\", O_RDONLY) failed"; | |
| 153 fp.reset(); | |
| 154 } | |
| 155 // Deleting the file prevents anyone else from mapping it in (making it | |
| 156 // private), and prevents the need for cleanup (once the last fd is | |
| 157 // closed, | |
|
Mark Mentovai
2013/11/20 14:21:41
Reflow this comment, this word doesn’t need to be
Jeffrey Yasskin
2013/11/20 18:02:09
Done.
| |
| 158 // it is truly freed). | |
| 142 if (unlink(path.value().c_str())) | 159 if (unlink(path.value().c_str())) |
| 143 PLOG(WARNING) << "unlink"; | 160 PLOG(WARNING) << "unlink"; |
| 144 } | 161 } |
| 145 } else { | 162 } else { |
| 146 if (!FilePathForMemoryName(*options.name, &path)) | 163 if (!FilePathForMemoryName(*options.name, &path)) |
| 147 return false; | 164 return false; |
| 148 | 165 |
| 149 // Make sure that the file is opened without any permission | 166 // Make sure that the file is opened without any permission |
| 150 // to other users on the system. | 167 // to other users on the system. |
| 151 const mode_t kOwnerOnly = S_IRUSR | S_IWUSR; | 168 const mode_t kOwnerOnly = S_IRUSR | S_IWUSR; |
| (...skipping 23 matching lines...) Expand all Loading... | |
| 175 sb.st_uid != effective_uid)) { | 192 sb.st_uid != effective_uid)) { |
| 176 LOG(ERROR) << | 193 LOG(ERROR) << |
| 177 "Invalid owner when opening existing shared memory file."; | 194 "Invalid owner when opening existing shared memory file."; |
| 178 HANDLE_EINTR(close(fd)); | 195 HANDLE_EINTR(close(fd)); |
| 179 return false; | 196 return false; |
| 180 } | 197 } |
| 181 | 198 |
| 182 // An existing file was opened, so its size should not be fixed. | 199 // An existing file was opened, so its size should not be fixed. |
| 183 fix_size = false; | 200 fix_size = false; |
| 184 } | 201 } |
| 185 fp = NULL; | 202 |
| 203 // Also open as readonly so that we can ShareReadOnlyToProcess. | |
| 204 *readonly_fd = HANDLE_EINTR(open(path.value().c_str(), O_RDONLY)); | |
| 205 if (*readonly_fd < 0) { | |
| 206 DPLOG(ERROR) << "open(\"" << path.value().c_str() | |
| 207 << "\", O_RDONLY) failed"; | |
| 208 HANDLE_EINTR(close(fd)); | |
| 209 fd = -1; | |
| 210 } | |
| 186 if (fd >= 0) { | 211 if (fd >= 0) { |
| 187 // "a+" is always appropriate: if it's a new file, a+ is similar to w+. | 212 // "a+" is always appropriate: if it's a new file, a+ is similar to w+. |
| 188 fp = fdopen(fd, "a+"); | 213 fp.reset(fdopen(fd, "a+")); |
| 189 } | 214 } |
| 190 } | 215 } |
| 191 if (fp && fix_size) { | 216 if (fp && fix_size) { |
| 192 // Get current size. | 217 // Get current size. |
| 193 struct stat stat; | 218 struct stat stat; |
| 194 if (fstat(fileno(fp), &stat) != 0) { | 219 if (fstat(fileno(fp.get()), &stat) != 0) |
| 195 file_util::CloseFile(fp); | |
| 196 return false; | 220 return false; |
| 197 } | |
| 198 const size_t current_size = stat.st_size; | 221 const size_t current_size = stat.st_size; |
| 199 if (current_size != options.size) { | 222 if (current_size != options.size) { |
| 200 if (HANDLE_EINTR(ftruncate(fileno(fp), options.size)) != 0) { | 223 if (HANDLE_EINTR(ftruncate(fileno(fp.get()), options.size)) != 0) |
| 201 file_util::CloseFile(fp); | |
| 202 return false; | 224 return false; |
| 203 } | |
| 204 } | 225 } |
| 205 requested_size_ = options.size; | 226 requested_size_ = options.size; |
| 206 } | 227 } |
| 207 if (fp == NULL) { | 228 if (fp == NULL) { |
| 208 #if !defined(OS_MACOSX) | 229 #if !defined(OS_MACOSX) |
| 209 PLOG(ERROR) << "Creating shared memory in " << path.value() << " failed"; | 230 PLOG(ERROR) << "Creating shared memory in " << path.value() << " failed"; |
| 210 FilePath dir = path.DirName(); | 231 FilePath dir = path.DirName(); |
| 211 if (access(dir.value().c_str(), W_OK | X_OK) < 0) { | 232 if (access(dir.value().c_str(), W_OK | X_OK) < 0) { |
| 212 PLOG(ERROR) << "Unable to access(W_OK|X_OK) " << dir.value(); | 233 PLOG(ERROR) << "Unable to access(W_OK|X_OK) " << dir.value(); |
| 213 if (dir.value() == "/dev/shm") { | 234 if (dir.value() == "/dev/shm") { |
| 214 LOG(FATAL) << "This is frequently caused by incorrect permissions on " | 235 LOG(FATAL) << "This is frequently caused by incorrect permissions on " |
| 215 << "/dev/shm. Try 'sudo chmod 1777 /dev/shm' to fix."; | 236 << "/dev/shm. Try 'sudo chmod 1777 /dev/shm' to fix."; |
| 216 } | 237 } |
| 217 } | 238 } |
| 218 #else | 239 #else |
| 219 PLOG(ERROR) << "Creating shared memory in " << path.value() << " failed"; | 240 PLOG(ERROR) << "Creating shared memory in " << path.value() << " failed"; |
| 220 #endif | 241 #endif |
| 221 return false; | 242 return false; |
| 222 } | 243 } |
| 223 | 244 |
| 224 return PrepareMapFile(fp); | 245 return PrepareMapFile(fp.Pass(), readonly_fd.Pass()); |
| 225 } | 246 } |
| 226 | 247 |
| 227 // Our current implementation of shmem is with mmap()ing of files. | 248 // Our current implementation of shmem is with mmap()ing of files. |
| 228 // These files need to be deleted explicitly. | 249 // These files need to be deleted explicitly. |
| 229 // In practice this call is only needed for unit tests. | 250 // In practice this call is only needed for unit tests. |
| 230 bool SharedMemory::Delete(const std::string& name) { | 251 bool SharedMemory::Delete(const std::string& name) { |
| 231 FilePath path; | 252 FilePath path; |
| 232 if (!FilePathForMemoryName(name, &path)) | 253 if (!FilePathForMemoryName(name, &path)) |
| 233 return false; | 254 return false; |
| 234 | 255 |
| 235 if (PathExists(path)) | 256 if (PathExists(path)) |
| 236 return base::DeleteFile(path, false); | 257 return base::DeleteFile(path, false); |
| 237 | 258 |
| 238 // Doesn't exist, so success. | 259 // Doesn't exist, so success. |
| 239 return true; | 260 return true; |
| 240 } | 261 } |
| 241 | 262 |
| 242 bool SharedMemory::Open(const std::string& name, bool read_only) { | 263 bool SharedMemory::Open(const std::string& name, bool read_only) { |
| 243 FilePath path; | 264 FilePath path; |
| 244 if (!FilePathForMemoryName(name, &path)) | 265 if (!FilePathForMemoryName(name, &path)) |
| 245 return false; | 266 return false; |
| 246 | 267 |
| 247 read_only_ = read_only; | 268 read_only_ = read_only; |
| 248 | 269 |
| 249 const char *mode = read_only ? "r" : "r+"; | 270 const char *mode = read_only ? "r" : "r+"; |
| 250 FILE *fp = file_util::OpenFile(path, mode); | 271 ScopedFILE fp(file_util::OpenFile(path, mode)); |
| 251 return PrepareMapFile(fp); | 272 int readonly_fd = HANDLE_EINTR(open(path.value().c_str(), O_RDONLY)); |
| 273 if (readonly_fd < 0) { | |
| 274 DPLOG(ERROR) << "open(\"" << path.value().c_str() << "\", O_RDONLY) failed"; | |
| 275 } | |
| 276 return PrepareMapFile(fp.Pass(), ScopedFD(&readonly_fd)); | |
|
Mark Mentovai
2013/11/20 14:21:41
Move the ScopedFD up (like you did above) to bette
Jeffrey Yasskin
2013/11/20 18:02:09
Done.
| |
| 252 } | 277 } |
| 253 | 278 |
| 254 #endif // !defined(OS_ANDROID) | 279 #endif // !defined(OS_ANDROID) |
| 255 | 280 |
| 256 bool SharedMemory::MapAt(off_t offset, size_t bytes) { | 281 bool SharedMemory::MapAt(off_t offset, size_t bytes) { |
| 257 if (mapped_file_ == -1) | 282 if (mapped_file_ == -1) |
| 258 return false; | 283 return false; |
| 259 | 284 |
| 260 if (bytes > static_cast<size_t>(std::numeric_limits<int>::max())) | 285 if (bytes > static_cast<size_t>(std::numeric_limits<int>::max())) |
| 261 return false; | 286 return false; |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 302 } | 327 } |
| 303 | 328 |
| 304 void SharedMemory::Close() { | 329 void SharedMemory::Close() { |
| 305 Unmap(); | 330 Unmap(); |
| 306 | 331 |
| 307 if (mapped_file_ > 0) { | 332 if (mapped_file_ > 0) { |
| 308 if (HANDLE_EINTR(close(mapped_file_)) < 0) | 333 if (HANDLE_EINTR(close(mapped_file_)) < 0) |
| 309 PLOG(ERROR) << "close"; | 334 PLOG(ERROR) << "close"; |
| 310 mapped_file_ = -1; | 335 mapped_file_ = -1; |
| 311 } | 336 } |
| 337 if (readonly_mapped_file_ > 0) { | |
| 338 if (HANDLE_EINTR(close(readonly_mapped_file_)) < 0) | |
|
Mark Mentovai
2013/11/20 14:21:41
I have determined that it’s never correct to HANDL
Jeffrey Yasskin
2013/11/20 18:02:09
Oh, ok; done. It was very slightly easier to unwra
| |
| 339 PLOG(ERROR) << "close"; | |
| 340 readonly_mapped_file_ = -1; | |
| 341 } | |
| 312 } | 342 } |
| 313 | 343 |
| 314 void SharedMemory::Lock() { | 344 void SharedMemory::Lock() { |
| 315 g_thread_lock_.Get().Acquire(); | 345 g_thread_lock_.Get().Acquire(); |
| 316 LockOrUnlockCommon(F_LOCK); | 346 LockOrUnlockCommon(F_LOCK); |
| 317 } | 347 } |
| 318 | 348 |
| 319 void SharedMemory::Unlock() { | 349 void SharedMemory::Unlock() { |
| 320 LockOrUnlockCommon(F_ULOCK); | 350 LockOrUnlockCommon(F_ULOCK); |
| 321 g_thread_lock_.Get().Release(); | 351 g_thread_lock_.Get().Release(); |
| 322 } | 352 } |
| 323 | 353 |
| 324 #if !defined(OS_ANDROID) | 354 #if !defined(OS_ANDROID) |
| 325 bool SharedMemory::PrepareMapFile(FILE *fp) { | 355 bool SharedMemory::PrepareMapFile(ScopedFILE fp, ScopedFD readonly_fd) { |
| 326 DCHECK_EQ(-1, mapped_file_); | 356 DCHECK_EQ(-1, mapped_file_); |
| 327 if (fp == NULL) return false; | 357 DCHECK_EQ(-1, readonly_mapped_file_); |
| 358 if (fp == NULL || *readonly_fd < 0) return false; | |
| 328 | 359 |
| 329 // This function theoretically can block on the disk, but realistically | 360 // This function theoretically can block on the disk, but realistically |
| 330 // the temporary files we create will just go into the buffer cache | 361 // the temporary files we create will just go into the buffer cache |
| 331 // and be deleted before they ever make it out to disk. | 362 // and be deleted before they ever make it out to disk. |
| 332 base::ThreadRestrictions::ScopedAllowIO allow_io; | 363 base::ThreadRestrictions::ScopedAllowIO allow_io; |
| 333 | 364 |
| 334 file_util::ScopedFILE file_closer(fp); | 365 struct stat st; |
| 366 struct stat readonly_st; | |
| 367 if (fstat(fileno(fp.get()), &st)) | |
| 368 NOTREACHED(); | |
| 369 if (fstat(*readonly_fd, &readonly_st)) | |
| 370 NOTREACHED(); | |
| 371 if (st.st_dev != readonly_st.st_dev || st.st_ino != readonly_st.st_ino) { | |
| 372 LOG(ERROR) << "writable and read-only inodes don't match; bailing"; | |
| 373 return false; | |
| 374 } | |
| 335 | 375 |
| 336 mapped_file_ = dup(fileno(fp)); | 376 mapped_file_ = dup(fileno(fp.get())); |
| 337 if (mapped_file_ == -1) { | 377 if (mapped_file_ == -1) { |
| 338 if (errno == EMFILE) { | 378 if (errno == EMFILE) { |
| 339 LOG(WARNING) << "Shared memory creation failed; out of file descriptors"; | 379 LOG(WARNING) << "Shared memory creation failed; out of file descriptors"; |
| 340 return false; | 380 return false; |
| 341 } else { | 381 } else { |
| 342 NOTREACHED() << "Call to dup failed, errno=" << errno; | 382 NOTREACHED() << "Call to dup failed, errno=" << errno; |
| 343 } | 383 } |
| 344 } | 384 } |
| 345 | |
| 346 struct stat st; | |
| 347 if (fstat(mapped_file_, &st)) | |
| 348 NOTREACHED(); | |
| 349 inode_ = st.st_ino; | 385 inode_ = st.st_ino; |
| 386 readonly_mapped_file_ = *readonly_fd.release(); | |
| 350 | 387 |
| 351 return true; | 388 return true; |
| 352 } | 389 } |
| 353 #endif | 390 #endif |
| 354 | 391 |
| 355 // For the given shmem named |mem_name|, return a filename to mmap() | 392 // For the given shmem named |mem_name|, return a filename to mmap() |
| 356 // (and possibly create). Modifies |filename|. Return false on | 393 // (and possibly create). Modifies |filename|. Return false on |
| 357 // error, or true of we are happy. | 394 // error, or true of we are happy. |
| 358 bool SharedMemory::FilePathForMemoryName(const std::string& mem_name, | 395 bool SharedMemory::FilePathForMemoryName(const std::string& mem_name, |
| 359 FilePath* path) { | 396 FilePath* path) { |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 392 NOTREACHED() << "lockf() failed." | 429 NOTREACHED() << "lockf() failed." |
| 393 << " function:" << function | 430 << " function:" << function |
| 394 << " fd:" << mapped_file_ | 431 << " fd:" << mapped_file_ |
| 395 << " errno:" << errno | 432 << " errno:" << errno |
| 396 << " msg:" << safe_strerror(errno); | 433 << " msg:" << safe_strerror(errno); |
| 397 } | 434 } |
| 398 } | 435 } |
| 399 } | 436 } |
| 400 | 437 |
| 401 bool SharedMemory::ShareToProcessCommon(ProcessHandle process, | 438 bool SharedMemory::ShareToProcessCommon(ProcessHandle process, |
| 402 SharedMemoryHandle *new_handle, | 439 SharedMemoryHandle* new_handle, |
| 403 bool close_self) { | 440 bool close_self, |
| 404 const int new_fd = dup(mapped_file_); | 441 ShareMode share_mode) { |
| 442 int handle_to_dup = -1; | |
| 443 switch(share_mode) { | |
| 444 case SHARE_CURRENT_MODE: | |
| 445 handle_to_dup = mapped_file_; | |
| 446 break; | |
| 447 case SHARE_READONLY: | |
| 448 // We could imagine re-opening the file from /dev/fd, but that can't make | |
| 449 // it readonly on Mac: https://codereview.chromium.org/27265002/#msg10 | |
| 450 CHECK(readonly_mapped_file_ >= 0); | |
| 451 handle_to_dup = readonly_mapped_file_; | |
| 452 break; | |
| 453 } | |
| 454 | |
| 455 const int new_fd = dup(handle_to_dup); | |
| 405 if (new_fd < 0) { | 456 if (new_fd < 0) { |
| 406 DPLOG(ERROR) << "dup() failed."; | 457 DPLOG(ERROR) << "dup() failed."; |
| 407 return false; | 458 return false; |
| 408 } | 459 } |
| 409 | 460 |
| 410 new_handle->fd = new_fd; | 461 new_handle->fd = new_fd; |
| 411 new_handle->auto_close = true; | 462 new_handle->auto_close = true; |
| 412 | 463 |
| 413 if (close_self) | 464 if (close_self) |
| 414 Close(); | 465 Close(); |
| 415 | 466 |
| 416 return true; | 467 return true; |
| 417 } | 468 } |
| 418 | 469 |
| 419 } // namespace base | 470 } // namespace base |
| OLD | NEW |