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 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 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 123 ExtensionGarbageCollector::~ExtensionGarbageCollector() {} 124 125 // static 126 ExtensionGarbageCollector* ExtensionGarbageCollector::Get( 127 content::BrowserContext* context) { 128 return ExtensionGarbageCollectorFactory::GetForBrowserContext(context); 129 } 130 131 void ExtensionGarbageCollector::Shutdown() { 132 InstallTracker::Get(context_)->RemoveObserver(this); 133 } 134 135 void ExtensionGarbageCollector::GarbageCollectExtensionsForTest() { 136 GarbageCollectExtensions(); 137 } 138 139 // static 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 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 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 240 void ExtensionGarbageCollector::OnBeginCrxInstall( 241 const std::string& extension_id) { 242 crx_installs_in_progress_++; 243 } 244 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