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/path_service.h"
6
7#ifdef OS_WIN
8#include <windows.h>
9#include <shellapi.h>
10#include <shlobj.h>
11#endif
12
13#include "base/file_path.h"
14#include "base/file_util.h"
15#include "base/hash_tables.h"
16#include "base/lazy_instance.h"
17#include "base/logging.h"
18#include "base/synchronization/lock.h"
19
20namespace base {
21  bool PathProvider(int key, FilePath* result);
22#if defined(OS_WIN)
23  bool PathProviderWin(int key, FilePath* result);
24#elif defined(OS_MACOSX)
25  bool PathProviderMac(int key, FilePath* result);
26#elif defined(OS_POSIX)
27  bool PathProviderPosix(int key, FilePath* result);
28#endif
29}
30
31namespace {
32
33typedef base::hash_map<int, FilePath> PathMap;
34
35// We keep a linked list of providers.  In a debug build we ensure that no two
36// providers claim overlapping keys.
37struct Provider {
38  PathService::ProviderFunc func;
39  struct Provider* next;
40#ifndef NDEBUG
41  int key_start;
42  int key_end;
43#endif
44  bool is_static;
45};
46
47static Provider base_provider = {
48  base::PathProvider,
49  NULL,
50#ifndef NDEBUG
51  base::PATH_START,
52  base::PATH_END,
53#endif
54  true
55};
56
57#if defined(OS_WIN)
58static Provider base_provider_win = {
59  base::PathProviderWin,
60  &base_provider,
61#ifndef NDEBUG
62  base::PATH_WIN_START,
63  base::PATH_WIN_END,
64#endif
65  true
66};
67#endif
68
69#if defined(OS_MACOSX)
70static Provider base_provider_mac = {
71  base::PathProviderMac,
72  &base_provider,
73#ifndef NDEBUG
74  base::PATH_MAC_START,
75  base::PATH_MAC_END,
76#endif
77  true
78};
79#endif
80
81#if defined(OS_POSIX) && !defined(OS_MACOSX)
82static Provider base_provider_posix = {
83  base::PathProviderPosix,
84  &base_provider,
85#ifndef NDEBUG
86  0,
87  0,
88#endif
89  true
90};
91#endif
92
93
94struct PathData {
95  base::Lock lock;
96  PathMap cache;        // Cache mappings from path key to path value.
97  PathMap overrides;    // Track path overrides.
98  Provider* providers;  // Linked list of path service providers.
99
100  PathData() {
101#if defined(OS_WIN)
102    providers = &base_provider_win;
103#elif defined(OS_MACOSX)
104    providers = &base_provider_mac;
105#elif defined(OS_POSIX)
106    providers = &base_provider_posix;
107#endif
108  }
109
110  ~PathData() {
111    Provider* p = providers;
112    while (p) {
113      Provider* next = p->next;
114      if (!p->is_static)
115        delete p;
116      p = next;
117    }
118  }
119};
120
121static base::LazyInstance<PathData> g_path_data(base::LINKER_INITIALIZED);
122
123static PathData* GetPathData() {
124  return g_path_data.Pointer();
125}
126
127}  // namespace
128
129
130// static
131bool PathService::GetFromCache(int key, FilePath* result) {
132  PathData* path_data = GetPathData();
133  base::AutoLock scoped_lock(path_data->lock);
134
135  // check for a cached version
136  PathMap::const_iterator it = path_data->cache.find(key);
137  if (it != path_data->cache.end()) {
138    *result = it->second;
139    return true;
140  }
141  return false;
142}
143
144// static
145bool PathService::GetFromOverrides(int key, FilePath* result) {
146  PathData* path_data = GetPathData();
147  base::AutoLock scoped_lock(path_data->lock);
148
149  // check for an overriden version.
150  PathMap::const_iterator it = path_data->overrides.find(key);
151  if (it != path_data->overrides.end()) {
152    *result = it->second;
153    return true;
154  }
155  return false;
156}
157
158// static
159void PathService::AddToCache(int key, const FilePath& path) {
160  PathData* path_data = GetPathData();
161  base::AutoLock scoped_lock(path_data->lock);
162  // Save the computed path in our cache.
163  path_data->cache[key] = path;
164}
165
166// TODO(brettw): this function does not handle long paths (filename > MAX_PATH)
167// characters). This isn't supported very well by Windows right now, so it is
168// moot, but we should keep this in mind for the future.
169// static
170bool PathService::Get(int key, FilePath* result) {
171  PathData* path_data = GetPathData();
172  DCHECK(path_data);
173  DCHECK(result);
174  DCHECK_GE(key, base::DIR_CURRENT);
175
176  // special case the current directory because it can never be cached
177  if (key == base::DIR_CURRENT)
178    return file_util::GetCurrentDirectory(result);
179
180  if (GetFromCache(key, result))
181    return true;
182
183  if (GetFromOverrides(key, result))
184    return true;
185
186  FilePath path;
187
188  // search providers for the requested path
189  // NOTE: it should be safe to iterate here without the lock
190  // since RegisterProvider always prepends.
191  Provider* provider = path_data->providers;
192  while (provider) {
193    if (provider->func(key, &path))
194      break;
195    DCHECK(path.empty()) << "provider should not have modified path";
196    provider = provider->next;
197  }
198
199  if (path.empty())
200    return false;
201
202  AddToCache(key, path);
203
204  *result = path;
205  return true;
206}
207
208bool PathService::Override(int key, const FilePath& path) {
209  PathData* path_data = GetPathData();
210  DCHECK(path_data);
211  DCHECK_GT(key, base::DIR_CURRENT) << "invalid path key";
212
213  FilePath file_path = path;
214
215  // Make sure the directory exists. We need to do this before we translate
216  // this to the absolute path because on POSIX, AbsolutePath fails if called
217  // on a non-existant path.
218  if (!file_util::PathExists(file_path) &&
219      !file_util::CreateDirectory(file_path))
220    return false;
221
222  // We need to have an absolute path, as extensions and plugins don't like
223  // relative paths, and will glady crash the browser in CHECK()s if they get a
224  // relative path.
225  if (!file_util::AbsolutePath(&file_path))
226    return false;
227
228  base::AutoLock scoped_lock(path_data->lock);
229
230  // Clear the cache now. Some of its entries could have depended
231  // on the value we are overriding, and are now out of sync with reality.
232  path_data->cache.clear();
233
234  path_data->cache[key] = file_path;
235  path_data->overrides[key] = file_path;
236
237  return true;
238}
239
240void PathService::RegisterProvider(ProviderFunc func, int key_start,
241                                   int key_end) {
242  PathData* path_data = GetPathData();
243  DCHECK(path_data);
244  DCHECK_GT(key_end, key_start);
245
246  base::AutoLock scoped_lock(path_data->lock);
247
248  Provider* p;
249
250#ifndef NDEBUG
251  p = path_data->providers;
252  while (p) {
253    DCHECK(key_start >= p->key_end || key_end <= p->key_start) <<
254      "path provider collision";
255    p = p->next;
256  }
257#endif
258
259  p = new Provider;
260  p->is_static = false;
261  p->func = func;
262  p->next = path_data->providers;
263#ifndef NDEBUG
264  p->key_start = key_start;
265  p->key_end = key_end;
266#endif
267  path_data->providers = p;
268}
269