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