michael@0: // Copyright (c) 2012 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/path_service.h" michael@0: michael@0: #if defined(OS_WIN) michael@0: #include michael@0: #include michael@0: #include michael@0: #endif michael@0: michael@0: #include "base/containers/hash_tables.h" michael@0: #include "base/file_util.h" michael@0: #include "base/files/file_path.h" michael@0: #include "base/lazy_instance.h" michael@0: #include "base/logging.h" michael@0: #include "base/synchronization/lock.h" michael@0: michael@0: using base::FilePath; michael@0: using base::MakeAbsoluteFilePath; michael@0: michael@0: namespace base { michael@0: bool PathProvider(int key, FilePath* result); michael@0: #if defined(OS_WIN) michael@0: bool PathProviderWin(int key, FilePath* result); michael@0: #elif defined(OS_MACOSX) michael@0: bool PathProviderMac(int key, FilePath* result); michael@0: #elif defined(OS_ANDROID) michael@0: bool PathProviderAndroid(int key, FilePath* result); michael@0: #elif defined(OS_POSIX) michael@0: // PathProviderPosix is the default path provider on POSIX OSes other than michael@0: // Mac and Android. michael@0: bool PathProviderPosix(int key, FilePath* result); michael@0: #endif michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: typedef base::hash_map PathMap; michael@0: michael@0: // We keep a linked list of providers. In a debug build we ensure that no two michael@0: // providers claim overlapping keys. michael@0: struct Provider { michael@0: PathService::ProviderFunc func; michael@0: struct Provider* next; michael@0: #ifndef NDEBUG michael@0: int key_start; michael@0: int key_end; michael@0: #endif michael@0: bool is_static; michael@0: }; michael@0: michael@0: Provider base_provider = { michael@0: base::PathProvider, michael@0: NULL, michael@0: #ifndef NDEBUG michael@0: base::PATH_START, michael@0: base::PATH_END, michael@0: #endif michael@0: true michael@0: }; michael@0: michael@0: #if defined(OS_WIN) michael@0: Provider base_provider_win = { michael@0: base::PathProviderWin, michael@0: &base_provider, michael@0: #ifndef NDEBUG michael@0: base::PATH_WIN_START, michael@0: base::PATH_WIN_END, michael@0: #endif michael@0: true michael@0: }; michael@0: #endif michael@0: michael@0: #if defined(OS_MACOSX) michael@0: Provider base_provider_mac = { michael@0: base::PathProviderMac, michael@0: &base_provider, michael@0: #ifndef NDEBUG michael@0: base::PATH_MAC_START, michael@0: base::PATH_MAC_END, michael@0: #endif michael@0: true michael@0: }; michael@0: #endif michael@0: michael@0: #if defined(OS_ANDROID) michael@0: Provider base_provider_android = { michael@0: base::PathProviderAndroid, michael@0: &base_provider, michael@0: #ifndef NDEBUG michael@0: base::PATH_ANDROID_START, michael@0: base::PATH_ANDROID_END, michael@0: #endif michael@0: true michael@0: }; michael@0: #endif michael@0: michael@0: #if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID) michael@0: Provider base_provider_posix = { michael@0: base::PathProviderPosix, michael@0: &base_provider, michael@0: #ifndef NDEBUG michael@0: base::PATH_POSIX_START, michael@0: base::PATH_POSIX_END, michael@0: #endif michael@0: true michael@0: }; michael@0: #endif michael@0: michael@0: michael@0: struct PathData { michael@0: base::Lock lock; michael@0: PathMap cache; // Cache mappings from path key to path value. michael@0: PathMap overrides; // Track path overrides. michael@0: Provider* providers; // Linked list of path service providers. michael@0: bool cache_disabled; // Don't use cache if true; michael@0: michael@0: PathData() : cache_disabled(false) { michael@0: #if defined(OS_WIN) michael@0: providers = &base_provider_win; michael@0: #elif defined(OS_MACOSX) michael@0: providers = &base_provider_mac; michael@0: #elif defined(OS_ANDROID) michael@0: providers = &base_provider_android; michael@0: #elif defined(OS_POSIX) michael@0: providers = &base_provider_posix; michael@0: #endif michael@0: } michael@0: michael@0: ~PathData() { michael@0: Provider* p = providers; michael@0: while (p) { michael@0: Provider* next = p->next; michael@0: if (!p->is_static) michael@0: delete p; michael@0: p = next; michael@0: } michael@0: } michael@0: }; michael@0: michael@0: static base::LazyInstance g_path_data = LAZY_INSTANCE_INITIALIZER; michael@0: michael@0: static PathData* GetPathData() { michael@0: return g_path_data.Pointer(); michael@0: } michael@0: michael@0: // Tries to find |key| in the cache. |path_data| should be locked by the caller! michael@0: bool LockedGetFromCache(int key, const PathData* path_data, FilePath* result) { michael@0: if (path_data->cache_disabled) michael@0: return false; michael@0: // check for a cached version michael@0: PathMap::const_iterator it = path_data->cache.find(key); michael@0: if (it != path_data->cache.end()) { michael@0: *result = it->second; michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: // Tries to find |key| in the overrides map. |path_data| should be locked by the michael@0: // caller! michael@0: bool LockedGetFromOverrides(int key, PathData* path_data, FilePath* result) { michael@0: // check for an overridden version. michael@0: PathMap::const_iterator it = path_data->overrides.find(key); michael@0: if (it != path_data->overrides.end()) { michael@0: if (!path_data->cache_disabled) michael@0: path_data->cache[key] = it->second; michael@0: *result = it->second; michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: } // namespace michael@0: michael@0: // TODO(brettw): this function does not handle long paths (filename > MAX_PATH) michael@0: // characters). This isn't supported very well by Windows right now, so it is michael@0: // moot, but we should keep this in mind for the future. michael@0: // static michael@0: bool PathService::Get(int key, FilePath* result) { michael@0: PathData* path_data = GetPathData(); michael@0: DCHECK(path_data); michael@0: DCHECK(result); michael@0: DCHECK_GE(key, base::DIR_CURRENT); michael@0: michael@0: // special case the current directory because it can never be cached michael@0: if (key == base::DIR_CURRENT) michael@0: return file_util::GetCurrentDirectory(result); michael@0: michael@0: Provider* provider = NULL; michael@0: { michael@0: base::AutoLock scoped_lock(path_data->lock); michael@0: if (LockedGetFromCache(key, path_data, result)) michael@0: return true; michael@0: michael@0: if (LockedGetFromOverrides(key, path_data, result)) michael@0: return true; michael@0: michael@0: // Get the beginning of the list while it is still locked. michael@0: provider = path_data->providers; michael@0: } michael@0: michael@0: FilePath path; michael@0: michael@0: // Iterating does not need the lock because only the list head might be michael@0: // modified on another thread. michael@0: while (provider) { michael@0: if (provider->func(key, &path)) michael@0: break; michael@0: DCHECK(path.empty()) << "provider should not have modified path"; michael@0: provider = provider->next; michael@0: } michael@0: michael@0: if (path.empty()) michael@0: return false; michael@0: michael@0: if (path.ReferencesParent()) { michael@0: // Make sure path service never returns a path with ".." in it. michael@0: path = MakeAbsoluteFilePath(path); michael@0: if (path.empty()) michael@0: return false; michael@0: } michael@0: *result = path; michael@0: michael@0: base::AutoLock scoped_lock(path_data->lock); michael@0: if (!path_data->cache_disabled) michael@0: path_data->cache[key] = path; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // static michael@0: bool PathService::Override(int key, const FilePath& path) { michael@0: // Just call the full function with true for the value of |create|. michael@0: return OverrideAndCreateIfNeeded(key, path, true); michael@0: } michael@0: michael@0: // static michael@0: bool PathService::OverrideAndCreateIfNeeded(int key, michael@0: const FilePath& path, michael@0: bool create) { michael@0: PathData* path_data = GetPathData(); michael@0: DCHECK(path_data); michael@0: DCHECK_GT(key, base::DIR_CURRENT) << "invalid path key"; michael@0: michael@0: FilePath file_path = path; michael@0: michael@0: // For some locations this will fail if called from inside the sandbox there- michael@0: // fore we protect this call with a flag. michael@0: if (create) { michael@0: // Make sure the directory exists. We need to do this before we translate michael@0: // this to the absolute path because on POSIX, MakeAbsoluteFilePath fails michael@0: // if called on a non-existent path. michael@0: if (!base::PathExists(file_path) && michael@0: !file_util::CreateDirectory(file_path)) michael@0: return false; michael@0: } michael@0: michael@0: // We need to have an absolute path. michael@0: file_path = MakeAbsoluteFilePath(file_path); michael@0: if (file_path.empty()) michael@0: return false; michael@0: michael@0: base::AutoLock scoped_lock(path_data->lock); michael@0: michael@0: // Clear the cache now. Some of its entries could have depended michael@0: // on the value we are overriding, and are now out of sync with reality. michael@0: path_data->cache.clear(); michael@0: michael@0: path_data->overrides[key] = file_path; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // static michael@0: bool PathService::RemoveOverride(int key) { michael@0: PathData* path_data = GetPathData(); michael@0: DCHECK(path_data); michael@0: michael@0: base::AutoLock scoped_lock(path_data->lock); michael@0: michael@0: if (path_data->overrides.find(key) == path_data->overrides.end()) michael@0: return false; michael@0: michael@0: // Clear the cache now. Some of its entries could have depended on the value michael@0: // we are going to remove, and are now out of sync. michael@0: path_data->cache.clear(); michael@0: michael@0: path_data->overrides.erase(key); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // static michael@0: void PathService::RegisterProvider(ProviderFunc func, int key_start, michael@0: int key_end) { michael@0: PathData* path_data = GetPathData(); michael@0: DCHECK(path_data); michael@0: DCHECK_GT(key_end, key_start); michael@0: michael@0: Provider* p; michael@0: michael@0: p = new Provider; michael@0: p->is_static = false; michael@0: p->func = func; michael@0: #ifndef NDEBUG michael@0: p->key_start = key_start; michael@0: p->key_end = key_end; michael@0: #endif michael@0: michael@0: base::AutoLock scoped_lock(path_data->lock); michael@0: michael@0: #ifndef NDEBUG michael@0: Provider *iter = path_data->providers; michael@0: while (iter) { michael@0: DCHECK(key_start >= iter->key_end || key_end <= iter->key_start) << michael@0: "path provider collision"; michael@0: iter = iter->next; michael@0: } michael@0: #endif michael@0: michael@0: p->next = path_data->providers; michael@0: path_data->providers = p; michael@0: } michael@0: michael@0: // static michael@0: void PathService::DisableCache() { michael@0: PathData* path_data = GetPathData(); michael@0: DCHECK(path_data); michael@0: michael@0: base::AutoLock scoped_lock(path_data->lock); michael@0: path_data->cache.clear(); michael@0: path_data->cache_disabled = true; michael@0: }