blob: 89e58b2cdb1526169cd1e650c24dc35ccd9fb3a5 [file] [log] [blame]
pastarmovj@chromium.org8f452092012-05-09 19:50:10 +09001// Copyright (c) 2012 The Chromium Authors. All rights reserved.
license.botf003cfe2008-08-24 09:55:55 +09002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
initial.commit3f4a7322008-07-27 06:49:38 +09004
maruel@google.com15cfc7f2008-08-08 05:23:09 +09005#include "base/path_service.h"
avi@google.comac68ba22008-08-07 01:01:25 +09006
dbeam@chromium.orge8888492013-03-24 04:10:54 +09007#if defined(OS_WIN)
initial.commit3f4a7322008-07-27 06:49:38 +09008#include <windows.h>
9#include <shellapi.h>
10#include <shlobj.h>
avi@google.comac68ba22008-08-07 01:01:25 +090011#endif
initial.commit3f4a7322008-07-27 06:49:38 +090012
brettw@chromium.org30230a82013-06-12 02:52:44 +090013#include "base/containers/hash_tables.h"
maruel@google.com15cfc7f2008-08-08 05:23:09 +090014#include "base/file_util.h"
brettw@chromium.org59eef1f2013-02-24 14:40:52 +090015#include "base/files/file_path.h"
satish@chromium.org2940ad12010-12-14 16:48:49 +090016#include "base/lazy_instance.h"
initial.commit3f4a7322008-07-27 06:49:38 +090017#include "base/logging.h"
brettw@chromium.orgabe477a2011-01-21 13:55:52 +090018#include "base/synchronization/lock.h"
initial.commit3f4a7322008-07-27 06:49:38 +090019
brettw@chromium.orgc02b6032013-02-16 13:12:26 +090020using base::FilePath;
brettw@chromium.org99b198e2013-04-12 14:17:15 +090021using base::MakeAbsoluteFilePath;
brettw@chromium.orgc02b6032013-02-16 13:12:26 +090022
initial.commit3f4a7322008-07-27 06:49:38 +090023namespace base {
evanm@google.com3e9d5d72008-11-20 01:50:03 +090024 bool PathProvider(int key, FilePath* result);
erikkay@google.com9fc57d02008-08-09 05:16:08 +090025#if defined(OS_WIN)
evanm@google.com3e9d5d72008-11-20 01:50:03 +090026 bool PathProviderWin(int key, FilePath* result);
evanm@google.come41d3b32008-08-15 10:04:11 +090027#elif defined(OS_MACOSX)
evanm@google.com3e9d5d72008-11-20 01:50:03 +090028 bool PathProviderMac(int key, FilePath* result);
michaelbai@google.com2251c622011-06-22 07:34:50 +090029#elif defined(OS_ANDROID)
30 bool PathProviderAndroid(int key, FilePath* result);
evan@chromium.orgc1365092009-11-21 10:29:00 +090031#elif defined(OS_POSIX)
gab@chromium.org97fc1e62012-09-21 01:24:52 +090032 // PathProviderPosix is the default path provider on POSIX OSes other than
33 // Mac and Android.
evan@chromium.orgc1365092009-11-21 10:29:00 +090034 bool PathProviderPosix(int key, FilePath* result);
erikkay@google.comc7980ee2008-08-06 04:46:31 +090035#endif
initial.commit3f4a7322008-07-27 06:49:38 +090036}
37
38namespace {
39
evanm@google.com874d1672008-10-31 08:54:04 +090040typedef base::hash_map<int, FilePath> PathMap;
initial.commit3f4a7322008-07-27 06:49:38 +090041
42// We keep a linked list of providers. In a debug build we ensure that no two
43// providers claim overlapping keys.
44struct Provider {
45 PathService::ProviderFunc func;
46 struct Provider* next;
47#ifndef NDEBUG
48 int key_start;
49 int key_end;
50#endif
erikkay@google.com564ef6d2008-08-21 00:47:39 +090051 bool is_static;
initial.commit3f4a7322008-07-27 06:49:38 +090052};
53
jhawkins@chromium.org88fbaf62012-01-28 09:34:40 +090054Provider base_provider = {
initial.commit3f4a7322008-07-27 06:49:38 +090055 base::PathProvider,
56 NULL,
57#ifndef NDEBUG
58 base::PATH_START,
erikkay@google.com564ef6d2008-08-21 00:47:39 +090059 base::PATH_END,
initial.commit3f4a7322008-07-27 06:49:38 +090060#endif
erikkay@google.com564ef6d2008-08-21 00:47:39 +090061 true
initial.commit3f4a7322008-07-27 06:49:38 +090062};
63
evan@chromium.orgc1365092009-11-21 10:29:00 +090064#if defined(OS_WIN)
jhawkins@chromium.org88fbaf62012-01-28 09:34:40 +090065Provider base_provider_win = {
erikkay@google.comc7980ee2008-08-06 04:46:31 +090066 base::PathProviderWin,
67 &base_provider,
68#ifndef NDEBUG
69 base::PATH_WIN_START,
erikkay@google.com564ef6d2008-08-21 00:47:39 +090070 base::PATH_WIN_END,
erikkay@google.comc7980ee2008-08-06 04:46:31 +090071#endif
erikkay@google.com564ef6d2008-08-21 00:47:39 +090072 true
erikkay@google.comc7980ee2008-08-06 04:46:31 +090073};
74#endif
75
evan@chromium.orgc1365092009-11-21 10:29:00 +090076#if defined(OS_MACOSX)
jhawkins@chromium.org88fbaf62012-01-28 09:34:40 +090077Provider base_provider_mac = {
erikkay@google.com9fc57d02008-08-09 05:16:08 +090078 base::PathProviderMac,
79 &base_provider,
80#ifndef NDEBUG
81 base::PATH_MAC_START,
erikkay@google.com564ef6d2008-08-21 00:47:39 +090082 base::PATH_MAC_END,
erikkay@google.com9fc57d02008-08-09 05:16:08 +090083#endif
erikkay@google.com564ef6d2008-08-21 00:47:39 +090084 true
85};
erikkay@google.com9fc57d02008-08-09 05:16:08 +090086#endif
evanm@google.come41d3b32008-08-15 10:04:11 +090087
michaelbai@google.com2251c622011-06-22 07:34:50 +090088#if defined(OS_ANDROID)
jhawkins@chromium.org88fbaf62012-01-28 09:34:40 +090089Provider base_provider_android = {
michaelbai@google.com2251c622011-06-22 07:34:50 +090090 base::PathProviderAndroid,
91 &base_provider,
92#ifndef NDEBUG
gab@chromium.org97fc1e62012-09-21 01:24:52 +090093 base::PATH_ANDROID_START,
94 base::PATH_ANDROID_END,
michaelbai@google.com2251c622011-06-22 07:34:50 +090095#endif
96 true
97};
98#endif
99
100#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
jhawkins@chromium.org88fbaf62012-01-28 09:34:40 +0900101Provider base_provider_posix = {
evan@chromium.orgc1365092009-11-21 10:29:00 +0900102 base::PathProviderPosix,
evanm@google.come41d3b32008-08-15 10:04:11 +0900103 &base_provider,
104#ifndef NDEBUG
gab@chromium.org97fc1e62012-09-21 01:24:52 +0900105 base::PATH_POSIX_START,
106 base::PATH_POSIX_END,
evanm@google.come41d3b32008-08-15 10:04:11 +0900107#endif
erikkay@google.com564ef6d2008-08-21 00:47:39 +0900108 true
109};
evanm@google.come41d3b32008-08-15 10:04:11 +0900110#endif
111
112
initial.commit3f4a7322008-07-27 06:49:38 +0900113struct PathData {
brettw@chromium.orgabe477a2011-01-21 13:55:52 +0900114 base::Lock lock;
115 PathMap cache; // Cache mappings from path key to path value.
116 PathMap overrides; // Track path overrides.
initial.commit3f4a7322008-07-27 06:49:38 +0900117 Provider* providers; // Linked list of path service providers.
vitalybuka@chromium.org5a75b7e2013-01-29 09:56:56 +0900118 bool cache_disabled; // Don't use cache if true;
initial.commit3f4a7322008-07-27 06:49:38 +0900119
vitalybuka@chromium.org5a75b7e2013-01-29 09:56:56 +0900120 PathData() : cache_disabled(false) {
erikkay@google.comc7980ee2008-08-06 04:46:31 +0900121#if defined(OS_WIN)
122 providers = &base_provider_win;
erikkay@google.com9fc57d02008-08-09 05:16:08 +0900123#elif defined(OS_MACOSX)
124 providers = &base_provider_mac;
michaelbai@google.com2251c622011-06-22 07:34:50 +0900125#elif defined(OS_ANDROID)
126 providers = &base_provider_android;
evan@chromium.orgc1365092009-11-21 10:29:00 +0900127#elif defined(OS_POSIX)
128 providers = &base_provider_posix;
erikkay@google.comc7980ee2008-08-06 04:46:31 +0900129#endif
initial.commit3f4a7322008-07-27 06:49:38 +0900130 }
erikkay@google.com564ef6d2008-08-21 00:47:39 +0900131
132 ~PathData() {
133 Provider* p = providers;
134 while (p) {
135 Provider* next = p->next;
136 if (!p->is_static)
137 delete p;
138 p = next;
139 }
140 }
initial.commit3f4a7322008-07-27 06:49:38 +0900141};
maruel@chromium.org8fe7adc2009-03-04 00:01:12 +0900142
joth@chromium.orgb24883c2011-11-15 22:31:49 +0900143static base::LazyInstance<PathData> g_path_data = LAZY_INSTANCE_INITIALIZER;
satish@chromium.org2940ad12010-12-14 16:48:49 +0900144
erikkay@google.coma3ff2752008-08-13 02:33:52 +0900145static PathData* GetPathData() {
satish@chromium.org2940ad12010-12-14 16:48:49 +0900146 return g_path_data.Pointer();
erikkay@google.coma3ff2752008-08-13 02:33:52 +0900147}
initial.commit3f4a7322008-07-27 06:49:38 +0900148
pastarmovj@chromium.org5dce41a2012-09-27 04:05:12 +0900149// Tries to find |key| in the cache. |path_data| should be locked by the caller!
150bool LockedGetFromCache(int key, const PathData* path_data, FilePath* result) {
vitalybuka@chromium.org5a75b7e2013-01-29 09:56:56 +0900151 if (path_data->cache_disabled)
152 return false;
erikkay@google.com3620e4c2008-08-12 00:38:27 +0900153 // check for a cached version
154 PathMap::const_iterator it = path_data->cache.find(key);
155 if (it != path_data->cache.end()) {
156 *result = it->second;
157 return true;
158 }
159 return false;
160}
161
pastarmovj@chromium.org5dce41a2012-09-27 04:05:12 +0900162// Tries to find |key| in the overrides map. |path_data| should be locked by the
163// caller!
164bool LockedGetFromOverrides(int key, PathData* path_data, FilePath* result) {
jhawkins@chromium.orgda5f34c2011-12-15 03:47:26 +0900165 // check for an overridden version.
phajdan.jr@chromium.org26faa102010-09-10 08:49:04 +0900166 PathMap::const_iterator it = path_data->overrides.find(key);
167 if (it != path_data->overrides.end()) {
vitalybuka@chromium.org5a75b7e2013-01-29 09:56:56 +0900168 if (!path_data->cache_disabled)
169 path_data->cache[key] = it->second;
phajdan.jr@chromium.org26faa102010-09-10 08:49:04 +0900170 *result = it->second;
171 return true;
172 }
173 return false;
174}
175
pastarmovj@chromium.org5dce41a2012-09-27 04:05:12 +0900176} // namespace
erikkay@google.com3620e4c2008-08-12 00:38:27 +0900177
initial.commit3f4a7322008-07-27 06:49:38 +0900178// TODO(brettw): this function does not handle long paths (filename > MAX_PATH)
179// characters). This isn't supported very well by Windows right now, so it is
180// moot, but we should keep this in mind for the future.
181// static
evanm@google.com874d1672008-10-31 08:54:04 +0900182bool PathService::Get(int key, FilePath* result) {
erikkay@google.coma3ff2752008-08-13 02:33:52 +0900183 PathData* path_data = GetPathData();
initial.commit3f4a7322008-07-27 06:49:38 +0900184 DCHECK(path_data);
185 DCHECK(result);
pkasting@chromium.orgd23fe1a2011-04-01 05:34:25 +0900186 DCHECK_GE(key, base::DIR_CURRENT);
initial.commit3f4a7322008-07-27 06:49:38 +0900187
188 // special case the current directory because it can never be cached
erikkay@google.com9fc57d02008-08-09 05:16:08 +0900189 if (key == base::DIR_CURRENT)
erikkay@google.com1d4507f2008-08-07 01:29:44 +0900190 return file_util::GetCurrentDirectory(result);
initial.commit3f4a7322008-07-27 06:49:38 +0900191
pastarmovj@chromium.org5dce41a2012-09-27 04:05:12 +0900192 Provider* provider = NULL;
193 {
194 base::AutoLock scoped_lock(path_data->lock);
195 if (LockedGetFromCache(key, path_data, result))
196 return true;
estade@chromium.org234a6322009-02-28 10:49:55 +0900197
pastarmovj@chromium.org5dce41a2012-09-27 04:05:12 +0900198 if (LockedGetFromOverrides(key, path_data, result))
199 return true;
200
201 // Get the beginning of the list while it is still locked.
202 provider = path_data->providers;
203 }
phajdan.jr@chromium.org26faa102010-09-10 08:49:04 +0900204
evanm@google.com3e9d5d72008-11-20 01:50:03 +0900205 FilePath path;
initial.commit3f4a7322008-07-27 06:49:38 +0900206
pastarmovj@chromium.org5dce41a2012-09-27 04:05:12 +0900207 // Iterating does not need the lock because only the list head might be
208 // modified on another thread.
initial.commit3f4a7322008-07-27 06:49:38 +0900209 while (provider) {
evanm@google.com3e9d5d72008-11-20 01:50:03 +0900210 if (provider->func(key, &path))
initial.commit3f4a7322008-07-27 06:49:38 +0900211 break;
estade@chromium.org234a6322009-02-28 10:49:55 +0900212 DCHECK(path.empty()) << "provider should not have modified path";
initial.commit3f4a7322008-07-27 06:49:38 +0900213 provider = provider->next;
214 }
215
estade@chromium.org234a6322009-02-28 10:49:55 +0900216 if (path.empty())
initial.commit3f4a7322008-07-27 06:49:38 +0900217 return false;
218
maruel@chromium.org487eb9c2013-01-26 13:51:29 +0900219 if (path.ReferencesParent()) {
220 // Make sure path service never returns a path with ".." in it.
brettw@chromium.org99b198e2013-04-12 14:17:15 +0900221 path = MakeAbsoluteFilePath(path);
222 if (path.empty())
maruel@chromium.org487eb9c2013-01-26 13:51:29 +0900223 return false;
maruel@chromium.org487eb9c2013-01-26 13:51:29 +0900224 }
evanm@google.com874d1672008-10-31 08:54:04 +0900225 *result = path;
pastarmovj@chromium.org5dce41a2012-09-27 04:05:12 +0900226
227 base::AutoLock scoped_lock(path_data->lock);
vitalybuka@chromium.org5a75b7e2013-01-29 09:56:56 +0900228 if (!path_data->cache_disabled)
229 path_data->cache[key] = path;
pastarmovj@chromium.org5dce41a2012-09-27 04:05:12 +0900230
evanm@google.com874d1672008-10-31 08:54:04 +0900231 return true;
232}
233
pastarmovj@chromium.org5dce41a2012-09-27 04:05:12 +0900234// static
jcampan@chromium.orgcce10e72009-06-26 02:29:09 +0900235bool PathService::Override(int key, const FilePath& path) {
pastarmovj@chromium.org8f452092012-05-09 19:50:10 +0900236 // Just call the full function with true for the value of |create|.
237 return OverrideAndCreateIfNeeded(key, path, true);
238}
239
pastarmovj@chromium.org5dce41a2012-09-27 04:05:12 +0900240// static
pastarmovj@chromium.org8f452092012-05-09 19:50:10 +0900241bool PathService::OverrideAndCreateIfNeeded(int key,
242 const FilePath& path,
243 bool create) {
erikkay@google.coma3ff2752008-08-13 02:33:52 +0900244 PathData* path_data = GetPathData();
initial.commit3f4a7322008-07-27 06:49:38 +0900245 DCHECK(path_data);
kushi.p@gmail.come18fdd32011-03-14 07:13:33 +0900246 DCHECK_GT(key, base::DIR_CURRENT) << "invalid path key";
initial.commit3f4a7322008-07-27 06:49:38 +0900247
jcampan@chromium.orgcce10e72009-06-26 02:29:09 +0900248 FilePath file_path = path;
initial.commit3f4a7322008-07-27 06:49:38 +0900249
pastarmovj@chromium.org8f452092012-05-09 19:50:10 +0900250 // For some locations this will fail if called from inside the sandbox there-
251 // fore we protect this call with a flag.
252 if (create) {
253 // Make sure the directory exists. We need to do this before we translate
brettw@chromium.org99b198e2013-04-12 14:17:15 +0900254 // this to the absolute path because on POSIX, MakeAbsoluteFilePath fails
255 // if called on a non-existent path.
brettw@chromium.org10b64122013-07-12 02:36:07 +0900256 if (!base::PathExists(file_path) &&
pastarmovj@chromium.org8f452092012-05-09 19:50:10 +0900257 !file_util::CreateDirectory(file_path))
258 return false;
259 }
initial.commit3f4a7322008-07-27 06:49:38 +0900260
brettw@chromium.org99b198e2013-04-12 14:17:15 +0900261 // We need to have an absolute path.
262 file_path = MakeAbsoluteFilePath(file_path);
263 if (file_path.empty())
erg@google.com08945ec2009-10-28 08:31:36 +0900264 return false;
265
brettw@chromium.orgabe477a2011-01-21 13:55:52 +0900266 base::AutoLock scoped_lock(path_data->lock);
phajdan.jr@chromium.org26faa102010-09-10 08:49:04 +0900267
268 // Clear the cache now. Some of its entries could have depended
269 // on the value we are overriding, and are now out of sync with reality.
270 path_data->cache.clear();
271
phajdan.jr@chromium.org26faa102010-09-10 08:49:04 +0900272 path_data->overrides[key] = file_path;
273
initial.commit3f4a7322008-07-27 06:49:38 +0900274 return true;
275}
276
pastarmovj@chromium.org5dce41a2012-09-27 04:05:12 +0900277// static
278bool PathService::RemoveOverride(int key) {
279 PathData* path_data = GetPathData();
280 DCHECK(path_data);
281
282 base::AutoLock scoped_lock(path_data->lock);
283
284 if (path_data->overrides.find(key) == path_data->overrides.end())
285 return false;
286
287 // Clear the cache now. Some of its entries could have depended on the value
288 // we are going to remove, and are now out of sync.
289 path_data->cache.clear();
290
291 path_data->overrides.erase(key);
292
293 return true;
294}
295
296// static
initial.commit3f4a7322008-07-27 06:49:38 +0900297void PathService::RegisterProvider(ProviderFunc func, int key_start,
298 int key_end) {
erikkay@google.coma3ff2752008-08-13 02:33:52 +0900299 PathData* path_data = GetPathData();
initial.commit3f4a7322008-07-27 06:49:38 +0900300 DCHECK(path_data);
kushi.p@gmail.come18fdd32011-03-14 07:13:33 +0900301 DCHECK_GT(key_end, key_start);
initial.commit3f4a7322008-07-27 06:49:38 +0900302
initial.commit3f4a7322008-07-27 06:49:38 +0900303 Provider* p;
304
initial.commit3f4a7322008-07-27 06:49:38 +0900305 p = new Provider;
erikkay@google.com564ef6d2008-08-21 00:47:39 +0900306 p->is_static = false;
initial.commit3f4a7322008-07-27 06:49:38 +0900307 p->func = func;
initial.commit3f4a7322008-07-27 06:49:38 +0900308#ifndef NDEBUG
309 p->key_start = key_start;
310 p->key_end = key_end;
311#endif
pastarmovj@chromium.org5dce41a2012-09-27 04:05:12 +0900312
313 base::AutoLock scoped_lock(path_data->lock);
314
315#ifndef NDEBUG
316 Provider *iter = path_data->providers;
317 while (iter) {
318 DCHECK(key_start >= iter->key_end || key_end <= iter->key_start) <<
319 "path provider collision";
320 iter = iter->next;
321 }
322#endif
323
324 p->next = path_data->providers;
initial.commit3f4a7322008-07-27 06:49:38 +0900325 path_data->providers = p;
326}
vitalybuka@chromium.org5a75b7e2013-01-29 09:56:56 +0900327
328// static
329void PathService::DisableCache() {
330 PathData* path_data = GetPathData();
331 DCHECK(path_data);
332
333 base::AutoLock scoped_lock(path_data->lock);
334 path_data->cache.clear();
335 path_data->cache_disabled = true;
336}