• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2014 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 "chrome/browser/extensions/extension_garbage_collector.h"
6 
7 #include "base/bind.h"
8 #include "base/file_util.h"
9 #include "base/files/file_enumerator.h"
10 #include "base/logging.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/sequenced_task_runner.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "base/time/time.h"
17 #include "chrome/browser/extensions/extension_garbage_collector_factory.h"
18 #include "chrome/browser/extensions/extension_service.h"
19 #include "chrome/browser/extensions/extension_util.h"
20 #include "chrome/browser/extensions/install_tracker.h"
21 #include "chrome/browser/extensions/pending_extension_manager.h"
22 #include "chrome/common/extensions/manifest_handlers/app_isolation_info.h"
23 #include "content/public/browser/browser_context.h"
24 #include "content/public/browser/browser_thread.h"
25 #include "content/public/browser/storage_partition.h"
26 #include "extensions/browser/extension_prefs.h"
27 #include "extensions/browser/extension_registry.h"
28 #include "extensions/browser/extension_system.h"
29 #include "extensions/common/extension.h"
30 #include "extensions/common/file_util.h"
31 #include "extensions/common/one_shot_event.h"
32 
33 namespace extensions {
34 
35 namespace {
36 
37 // Wait this many seconds before trying to garbage collect extensions again.
38 const int kGarbageCollectRetryDelayInSeconds = 30;
39 
40 // Wait this many seconds after startup to see if there are any extensions
41 // which can be garbage collected.
42 const int kGarbageCollectStartupDelay = 30;
43 
44 typedef std::multimap<std::string, base::FilePath> ExtensionPathsMultimap;
45 
CheckExtensionDirectory(const base::FilePath & path,const ExtensionPathsMultimap & extension_paths)46 void CheckExtensionDirectory(const base::FilePath& path,
47                              const ExtensionPathsMultimap& extension_paths) {
48   base::FilePath basename = path.BaseName();
49   // Clean up temporary files left if Chrome crashed or quit in the middle
50   // of an extension install.
51   if (basename.value() == file_util::kTempDirectoryName) {
52     base::DeleteFile(path, true);  // Recursive.
53     return;
54   }
55 
56   // Parse directory name as a potential extension ID.
57   std::string extension_id;
58   if (base::IsStringASCII(basename.value())) {
59     extension_id = base::UTF16ToASCII(basename.LossyDisplayName());
60     if (!Extension::IdIsValid(extension_id))
61       extension_id.clear();
62   }
63 
64   // Delete directories that aren't valid IDs.
65   if (extension_id.empty()) {
66     base::DeleteFile(path, true);  // Recursive.
67     return;
68   }
69 
70   typedef ExtensionPathsMultimap::const_iterator Iter;
71   std::pair<Iter, Iter> iter_pair = extension_paths.equal_range(extension_id);
72 
73   // If there is no entry in the prefs file, just delete the directory and
74   // move on. This can legitimately happen when an uninstall does not
75   // complete, for example, when a plugin is in use at uninstall time.
76   if (iter_pair.first == iter_pair.second) {
77     base::DeleteFile(path, true);  // Recursive.
78     return;
79   }
80 
81   // Clean up old version directories.
82   base::FileEnumerator versions_enumerator(
83       path, false /* Not recursive */, base::FileEnumerator::DIRECTORIES);
84   for (base::FilePath version_dir = versions_enumerator.Next();
85        !version_dir.empty();
86        version_dir = versions_enumerator.Next()) {
87     bool known_version = false;
88     for (Iter iter = iter_pair.first; iter != iter_pair.second; ++iter) {
89       if (version_dir.BaseName() == iter->second.BaseName()) {
90         known_version = true;
91         break;
92       }
93     }
94     if (!known_version)
95       base::DeleteFile(version_dir, true);  // Recursive.
96   }
97 }
98 
99 }  // namespace
100 
ExtensionGarbageCollector(content::BrowserContext * context)101 ExtensionGarbageCollector::ExtensionGarbageCollector(
102     content::BrowserContext* context)
103     : context_(context), crx_installs_in_progress_(0), weak_factory_(this) {
104 
105   ExtensionSystem* extension_system = ExtensionSystem::Get(context_);
106   DCHECK(extension_system);
107 
108   extension_system->ready().PostDelayed(
109       FROM_HERE,
110       base::Bind(&ExtensionGarbageCollector::GarbageCollectExtensions,
111                  weak_factory_.GetWeakPtr()),
112       base::TimeDelta::FromSeconds(kGarbageCollectStartupDelay));
113 
114   extension_system->ready().Post(
115       FROM_HERE,
116       base::Bind(
117           &ExtensionGarbageCollector::GarbageCollectIsolatedStorageIfNeeded,
118           weak_factory_.GetWeakPtr()));
119 
120   InstallTracker::Get(context_)->AddObserver(this);
121 }
122 
~ExtensionGarbageCollector()123 ExtensionGarbageCollector::~ExtensionGarbageCollector() {}
124 
125 // static
Get(content::BrowserContext * context)126 ExtensionGarbageCollector* ExtensionGarbageCollector::Get(
127     content::BrowserContext* context) {
128   return ExtensionGarbageCollectorFactory::GetForBrowserContext(context);
129 }
130 
Shutdown()131 void ExtensionGarbageCollector::Shutdown() {
132   InstallTracker::Get(context_)->RemoveObserver(this);
133 }
134 
GarbageCollectExtensionsForTest()135 void ExtensionGarbageCollector::GarbageCollectExtensionsForTest() {
136   GarbageCollectExtensions();
137 }
138 
139 // static
GarbageCollectExtensionsOnFileThread(const base::FilePath & install_directory,const ExtensionPathsMultimap & extension_paths)140 void ExtensionGarbageCollector::GarbageCollectExtensionsOnFileThread(
141     const base::FilePath& install_directory,
142     const ExtensionPathsMultimap& extension_paths) {
143   DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
144 
145   // Nothing to clean up if it doesn't exist.
146   if (!base::DirectoryExists(install_directory))
147     return;
148 
149   base::FileEnumerator enumerator(install_directory,
150                                   false,  // Not recursive.
151                                   base::FileEnumerator::DIRECTORIES);
152 
153   for (base::FilePath extension_path = enumerator.Next();
154        !extension_path.empty();
155        extension_path = enumerator.Next()) {
156     CheckExtensionDirectory(extension_path, extension_paths);
157   }
158 }
159 
GarbageCollectExtensions()160 void ExtensionGarbageCollector::GarbageCollectExtensions() {
161   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
162 
163   ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(context_);
164   DCHECK(extension_prefs);
165 
166   if (extension_prefs->pref_service()->ReadOnly())
167     return;
168 
169   if (crx_installs_in_progress_ > 0) {
170     // Don't garbage collect while there are installations in progress,
171     // which may be using the temporary installation directory. Try to garbage
172     // collect again later.
173     base::MessageLoop::current()->PostDelayedTask(
174         FROM_HERE,
175         base::Bind(&ExtensionGarbageCollector::GarbageCollectExtensions,
176                    weak_factory_.GetWeakPtr()),
177         base::TimeDelta::FromSeconds(kGarbageCollectRetryDelayInSeconds));
178     return;
179   }
180 
181   scoped_ptr<ExtensionPrefs::ExtensionsInfo> info(
182       extension_prefs->GetInstalledExtensionsInfo());
183   std::multimap<std::string, base::FilePath> extension_paths;
184   for (size_t i = 0; i < info->size(); ++i) {
185     extension_paths.insert(
186         std::make_pair(info->at(i)->extension_id, info->at(i)->extension_path));
187   }
188 
189   info = extension_prefs->GetAllDelayedInstallInfo();
190   for (size_t i = 0; i < info->size(); ++i) {
191     extension_paths.insert(
192         std::make_pair(info->at(i)->extension_id, info->at(i)->extension_path));
193   }
194 
195   ExtensionService* service =
196       ExtensionSystem::Get(context_)->extension_service();
197   if (!service->GetFileTaskRunner()->PostTask(
198           FROM_HERE,
199           base::Bind(&GarbageCollectExtensionsOnFileThread,
200                      service->install_directory(),
201                      extension_paths))) {
202     NOTREACHED();
203   }
204 }
205 
GarbageCollectIsolatedStorageIfNeeded()206 void ExtensionGarbageCollector::GarbageCollectIsolatedStorageIfNeeded() {
207   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
208 
209   ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(context_);
210   DCHECK(extension_prefs);
211   if (!extension_prefs->NeedsStorageGarbageCollection())
212     return;
213   extension_prefs->SetNeedsStorageGarbageCollection(false);
214 
215   scoped_ptr<base::hash_set<base::FilePath> > active_paths(
216       new base::hash_set<base::FilePath>());
217   scoped_ptr<ExtensionSet> extensions =
218       ExtensionRegistry::Get(context_)->GenerateInstalledExtensionsSet();
219   for (ExtensionSet::const_iterator iter = extensions->begin();
220        iter != extensions->end();
221        ++iter) {
222     if (AppIsolationInfo::HasIsolatedStorage(iter->get())) {
223       active_paths->insert(
224           content::BrowserContext::GetStoragePartitionForSite(
225               context_, util::GetSiteForExtensionId((*iter)->id(), context_))
226               ->GetPath());
227     }
228   }
229 
230   ExtensionService* service =
231       ExtensionSystem::Get(context_)->extension_service();
232   service->OnGarbageCollectIsolatedStorageStart();
233   content::BrowserContext::GarbageCollectStoragePartitions(
234       context_,
235       active_paths.Pass(),
236       base::Bind(&ExtensionService::OnGarbageCollectIsolatedStorageFinished,
237                  service->AsWeakPtr()));
238 }
239 
OnBeginCrxInstall(const std::string & extension_id)240 void ExtensionGarbageCollector::OnBeginCrxInstall(
241     const std::string& extension_id) {
242   crx_installs_in_progress_++;
243 }
244 
OnFinishCrxInstall(const std::string & extension_id,bool success)245 void ExtensionGarbageCollector::OnFinishCrxInstall(
246     const std::string& extension_id,
247     bool success) {
248   crx_installs_in_progress_--;
249   if (crx_installs_in_progress_ < 0) {
250     // This can only happen if there is a mismatch in our begin/finish
251     // accounting.
252     NOTREACHED();
253 
254     // Don't let the count go negative to avoid garbage collecting when
255     // an install is actually in progress.
256     crx_installs_in_progress_ = 0;
257   }
258 }
259 
260 }  // namespace extensions
261