OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 // | 4 // |
5 // This file defines the methods useful for uninstalling Chrome. | 5 // This file defines the methods useful for uninstalling Chrome. |
6 | 6 |
7 #include "chrome/installer/setup/uninstall.h" | 7 #include "chrome/installer/setup/uninstall.h" |
8 | 8 |
9 #include <windows.h> | 9 #include <windows.h> |
10 | 10 |
(...skipping 179 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
190 | 190 |
191 // If chrome has been reactivated, clear all events for this brand as well. | 191 // If chrome has been reactivated, clear all events for this brand as well. |
192 string16 reactivation_brand_wide; | 192 string16 reactivation_brand_wide; |
193 if (GoogleUpdateSettings::GetReactivationBrand(&reactivation_brand_wide)) { | 193 if (GoogleUpdateSettings::GetReactivationBrand(&reactivation_brand_wide)) { |
194 std::string reactivation_brand(WideToASCII(reactivation_brand_wide)); | 194 std::string reactivation_brand(WideToASCII(reactivation_brand_wide)); |
195 rlz_lib::SupplementaryBranding branding(reactivation_brand.c_str()); | 195 rlz_lib::SupplementaryBranding branding(reactivation_brand.c_str()); |
196 rlz_lib::ClearProductState(rlz_lib::CHROME, points); | 196 rlz_lib::ClearProductState(rlz_lib::CHROME, points); |
197 } | 197 } |
198 } | 198 } |
199 | 199 |
| 200 // Decides whether setup.exe and the installer archive should be removed based |
| 201 // on the original and installer states: |
| 202 // * non-multi product being uninstalled: remove both |
| 203 // * any multi product left besides App Host: keep both |
| 204 // * only App Host left: keep setup.exe |
| 205 void CheckShouldRemoveSetupAndArchive( |
| 206 const installer::InstallationState& original_state, |
| 207 const installer::InstallerState& installer_state, |
| 208 bool* remove_setup, |
| 209 bool* remove_archive) { |
| 210 *remove_setup = true; |
| 211 *remove_archive = true; |
| 212 |
| 213 // If any multi-install product is left (other than App Host) we must leave |
| 214 // the installer and archive. For the App Host, we only leave the installer. |
| 215 if (!installer_state.is_multi_install()) { |
| 216 VLOG(1) << "Removing all installer files for a non-multi installation."; |
| 217 } else { |
| 218 for (size_t i = 0; i < BrowserDistribution::NUM_TYPES; ++i) { |
| 219 BrowserDistribution::Type dist_type = |
| 220 static_cast<BrowserDistribution::Type>(i); |
| 221 const installer::ProductState* product_state = |
| 222 original_state.GetProductState( |
| 223 installer_state.system_install(), dist_type); |
| 224 if (product_state && product_state->is_multi_install() && |
| 225 !installer_state.FindProduct(dist_type)) { |
| 226 // setup.exe will not be removed as there is a remaining multi-install |
| 227 // product. |
| 228 *remove_setup = false; |
| 229 // As a special case, we can still remove the actual archive if the |
| 230 // only remaining product is the App Host. |
| 231 if (dist_type != BrowserDistribution::CHROME_APP_HOST) { |
| 232 VLOG(1) << "Keeping all installer files due to a remaining " |
| 233 << "multi-install product."; |
| 234 *remove_archive = false; |
| 235 return; |
| 236 } |
| 237 VLOG(1) << "Keeping setup.exe due to a remaining " |
| 238 << "app-host installation."; |
| 239 } |
| 240 } |
| 241 VLOG(1) << "Removing the installer archive."; |
| 242 if (remove_setup) |
| 243 VLOG(1) << "Removing setup.exe."; |
| 244 } |
| 245 } |
| 246 |
| 247 // Removes all files from the installer directory, leaving setup.exe iff |
| 248 // |remove_setup| is false. |
| 249 // Returns false in case of an error. |
| 250 bool RemoveInstallerFiles(const FilePath& install_directory, |
| 251 bool remove_setup) { |
| 252 using file_util::FileEnumerator; |
| 253 FileEnumerator file_enumerator( |
| 254 install_directory, |
| 255 false, |
| 256 FileEnumerator::FILES | FileEnumerator::DIRECTORIES); |
| 257 bool success = true; |
| 258 |
| 259 FilePath setup_exe_base_name(installer::kSetupExe); |
| 260 |
| 261 while (true) { |
| 262 FilePath to_delete(file_enumerator.Next()); |
| 263 if (to_delete.empty()) |
| 264 break; |
| 265 if (!remove_setup && to_delete.BaseName() == setup_exe_base_name) |
| 266 continue; |
| 267 |
| 268 VLOG(1) << "Deleting install path " << to_delete.value(); |
| 269 if (!file_util::Delete(to_delete, true)) { |
| 270 LOG(ERROR) << "Failed to delete path: " << to_delete.value(); |
| 271 success = false; |
| 272 } |
| 273 } |
| 274 |
| 275 return success; |
| 276 } |
| 277 |
200 } // namespace | 278 } // namespace |
201 | 279 |
202 namespace installer { | 280 namespace installer { |
203 | 281 |
204 // Kills all Chrome processes, immediately. | 282 // Kills all Chrome processes, immediately. |
205 void CloseAllChromeProcesses() { | 283 void CloseAllChromeProcesses() { |
206 base::CleanupProcesses(installer::kChromeExe, base::TimeDelta(), | 284 base::CleanupProcesses(installer::kChromeExe, base::TimeDelta(), |
207 content::RESULT_CODE_HUNG, NULL); | 285 content::RESULT_CODE_HUNG, NULL); |
208 base::CleanupProcesses(installer::kNaClExe, base::TimeDelta(), | 286 base::CleanupProcesses(installer::kNaClExe, base::TimeDelta(), |
209 content::RESULT_CODE_HUNG, NULL); | 287 content::RESULT_CODE_HUNG, NULL); |
(...skipping 182 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
392 const FilePath product_dir(user_data_dir.DirName()); | 470 const FilePath product_dir(user_data_dir.DirName()); |
393 if (!product_dir.empty()) | 471 if (!product_dir.empty()) |
394 DeleteEmptyDir(product_dir); | 472 DeleteEmptyDir(product_dir); |
395 } | 473 } |
396 } | 474 } |
397 | 475 |
398 return result; | 476 return result; |
399 } | 477 } |
400 | 478 |
401 bool MoveSetupOutOfInstallFolder(const InstallerState& installer_state, | 479 bool MoveSetupOutOfInstallFolder(const InstallerState& installer_state, |
402 const FilePath& setup_path, | 480 const FilePath& setup_exe) { |
403 const Version& installed_version) { | |
404 bool ret = false; | 481 bool ret = false; |
405 FilePath setup_exe(installer_state.GetInstallerDirectory(installed_version) | |
406 .Append(setup_path.BaseName())); | |
407 FilePath temp_file; | 482 FilePath temp_file; |
408 if (!file_util::CreateTemporaryFile(&temp_file)) { | 483 if (!file_util::CreateTemporaryFile(&temp_file)) { |
409 LOG(ERROR) << "Failed to create temporary file for setup.exe."; | 484 LOG(ERROR) << "Failed to create temporary file for setup.exe."; |
410 } else { | 485 } else { |
411 VLOG(1) << "Attempting to move setup to: " << temp_file.value(); | 486 VLOG(1) << "Attempting to move setup to: " << temp_file.value(); |
412 ret = file_util::Move(setup_exe, temp_file); | 487 ret = file_util::Move(setup_exe, temp_file); |
413 PLOG_IF(ERROR, !ret) << "Failed to move setup to " << temp_file.value(); | 488 PLOG_IF(ERROR, !ret) << "Failed to move setup to " << temp_file.value(); |
414 | 489 |
415 // We cannot delete the file right away, but try to delete it some other | 490 // We cannot delete the file right away, but try to delete it some other |
416 // way. Either with the help of a different process or the system. | 491 // way. Either with the help of a different process or the system. |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
453 } | 528 } |
454 | 529 |
455 DeleteInstallTempDir(target_path); | 530 DeleteInstallTempDir(target_path); |
456 | 531 |
457 DeleteResult result = DELETE_SUCCEEDED; | 532 DeleteResult result = DELETE_SUCCEEDED; |
458 | 533 |
459 FilePath app_host_exe(target_path.Append(installer::kChromeAppHostExe)); | 534 FilePath app_host_exe(target_path.Append(installer::kChromeAppHostExe)); |
460 if (!file_util::Delete(app_host_exe, false)) { | 535 if (!file_util::Delete(app_host_exe, false)) { |
461 result = DELETE_FAILED; | 536 result = DELETE_FAILED; |
462 LOG(ERROR) << "Failed to delete path: " << app_host_exe.value(); | 537 LOG(ERROR) << "Failed to delete path: " << app_host_exe.value(); |
463 } else { | |
464 result = DeleteApplicationProductAndVendorDirectories(target_path); | |
465 } | 538 } |
466 | 539 |
467 return result; | 540 return result; |
468 } | 541 } |
469 | 542 |
470 DeleteResult DeleteChromeFilesAndFolders(const InstallerState& installer_state, | 543 DeleteResult DeleteChromeFilesAndFolders(const InstallerState& installer_state, |
471 const Version& installed_version) { | 544 const FilePath& installer_path) { |
472 const FilePath& target_path = installer_state.target_path(); | 545 const FilePath& target_path = installer_state.target_path(); |
473 if (target_path.empty()) { | 546 if (target_path.empty()) { |
474 LOG(ERROR) << "DeleteChromeFilesAndFolders: no installation destination " | 547 LOG(ERROR) << "DeleteChromeFilesAndFolders: no installation destination " |
475 << "path."; | 548 << "path."; |
476 return DELETE_FAILED; // Nothing else we can do to uninstall, so we return. | 549 return DELETE_FAILED; // Nothing else we can do to uninstall, so we return. |
477 } | 550 } |
478 | 551 |
479 DeleteInstallTempDir(target_path); | 552 DeleteInstallTempDir(target_path); |
480 | 553 |
481 DeleteResult result = DELETE_SUCCEEDED; | 554 DeleteResult result = DELETE_SUCCEEDED; |
482 | 555 |
| 556 FilePath installer_directory; |
| 557 if (target_path.IsParent(installer_path)) |
| 558 installer_directory = installer_path.DirName(); |
| 559 |
| 560 // Enumerate all the files in target_path recursively (breadth-first). |
| 561 // We delete a file or folder unless it is a parent/child of the installer |
| 562 // directory. For parents of the installer directory, we will later recurse |
| 563 // and delete all the children (that are not also parents/children of the |
| 564 // installer directory). |
483 using file_util::FileEnumerator; | 565 using file_util::FileEnumerator; |
484 FileEnumerator file_enumerator(target_path, false, | 566 FileEnumerator file_enumerator( |
485 FileEnumerator::FILES | FileEnumerator::DIRECTORIES); | 567 target_path, true, FileEnumerator::FILES | FileEnumerator::DIRECTORIES); |
486 while (true) { | 568 while (true) { |
487 FilePath to_delete(file_enumerator.Next()); | 569 FilePath to_delete(file_enumerator.Next()); |
488 if (to_delete.empty()) | 570 if (to_delete.empty()) |
489 break; | 571 break; |
490 if (to_delete.BaseName().value() == installer::kChromeAppHostExe) | 572 if (to_delete.BaseName().value() == installer::kChromeAppHostExe) |
491 continue; | 573 continue; |
| 574 if (!installer_directory.empty() && |
| 575 (to_delete == installer_directory || |
| 576 installer_directory.IsParent(to_delete) || |
| 577 to_delete.IsParent(installer_directory))) { |
| 578 continue; |
| 579 } |
492 | 580 |
493 VLOG(1) << "Deleting install path " << to_delete.value(); | 581 VLOG(1) << "Deleting install path " << to_delete.value(); |
494 if (!file_util::Delete(to_delete, true)) { | 582 if (!file_util::Delete(to_delete, true)) { |
495 LOG(ERROR) << "Failed to delete path (1st try): " << to_delete.value(); | 583 LOG(ERROR) << "Failed to delete path (1st try): " << to_delete.value(); |
496 if (installer_state.FindProduct(BrowserDistribution::CHROME_FRAME)) { | 584 if (installer_state.FindProduct(BrowserDistribution::CHROME_FRAME)) { |
497 // We don't try killing Chrome processes for Chrome Frame builds since | 585 // We don't try killing Chrome processes for Chrome Frame builds since |
498 // that is unlikely to help. Instead, schedule files for deletion and | 586 // that is unlikely to help. Instead, schedule files for deletion and |
499 // return a value that will trigger a reboot prompt. | 587 // return a value that will trigger a reboot prompt. |
500 FileEnumerator::FindInfo find_info; | 588 FileEnumerator::FindInfo find_info; |
501 file_enumerator.GetFindInfo(&find_info); | 589 file_enumerator.GetFindInfo(&find_info); |
502 if (FileEnumerator::IsDirectory(find_info)) | 590 if (FileEnumerator::IsDirectory(find_info)) |
503 ScheduleDirectoryForDeletion(to_delete.value().c_str()); | 591 ScheduleDirectoryForDeletion(to_delete.value().c_str()); |
504 else | 592 else |
505 ScheduleFileSystemEntityForDeletion(to_delete.value().c_str()); | 593 ScheduleFileSystemEntityForDeletion(to_delete.value().c_str()); |
506 result = DELETE_REQUIRES_REBOOT; | 594 result = DELETE_REQUIRES_REBOOT; |
507 } else { | 595 } else { |
508 // Try closing any running Chrome processes and deleting files once | 596 // Try closing any running Chrome processes and deleting files once |
509 // again. | 597 // again. |
510 CloseAllChromeProcesses(); | 598 CloseAllChromeProcesses(); |
511 if (!file_util::Delete(to_delete, true)) { | 599 if (!file_util::Delete(to_delete, true)) { |
512 LOG(ERROR) << "Failed to delete path (2nd try): " | 600 LOG(ERROR) << "Failed to delete path (2nd try): " |
513 << to_delete.value(); | 601 << to_delete.value(); |
514 result = DELETE_FAILED; | 602 result = DELETE_FAILED; |
515 break; | 603 break; |
516 } | 604 } |
517 } | 605 } |
518 } | 606 } |
519 } | 607 } |
520 | 608 |
521 if (result == DELETE_REQUIRES_REBOOT) { | |
522 // Delete the Application directory at reboot if empty. | |
523 ScheduleFileSystemEntityForDeletion(target_path.value().c_str()); | |
524 | |
525 // If we need a reboot to continue, schedule the parent directories for | |
526 // deletion unconditionally. If they are not empty, the session manager | |
527 // will not delete them on reboot. | |
528 ScheduleParentAndGrandparentForDeletion(target_path); | |
529 } else { | |
530 result = DeleteApplicationProductAndVendorDirectories(target_path); | |
531 } | |
532 return result; | 609 return result; |
533 } | 610 } |
534 | 611 |
535 // This method checks if Chrome is currently running or if the user has | 612 // This method checks if Chrome is currently running or if the user has |
536 // cancelled the uninstall operation by clicking Cancel on the confirmation | 613 // cancelled the uninstall operation by clicking Cancel on the confirmation |
537 // box that Chrome pops up. | 614 // box that Chrome pops up. |
538 InstallStatus IsChromeActiveOrUserCancelled( | 615 InstallStatus IsChromeActiveOrUserCancelled( |
539 const InstallerState& installer_state, | 616 const InstallerState& installer_state, |
540 const Product& product) { | 617 const Product& product) { |
541 int32 exit_code = content::RESULT_CODE_NORMAL_EXIT; | 618 int32 exit_code = content::RESULT_CODE_NORMAL_EXIT; |
(...skipping 674 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1216 product); | 1293 product); |
1217 ret = installer::UNINSTALL_SUCCESSFUL; | 1294 ret = installer::UNINSTALL_SUCCESSFUL; |
1218 | 1295 |
1219 // When deleting files, we must make sure that we're either a "single" | 1296 // When deleting files, we must make sure that we're either a "single" |
1220 // (aka non-multi) installation or we are the Chrome Binaries. | 1297 // (aka non-multi) installation or we are the Chrome Binaries. |
1221 | 1298 |
1222 std::vector<FilePath> local_state_folders; | 1299 std::vector<FilePath> local_state_folders; |
1223 GetLocalStateFolders(product, &local_state_folders); | 1300 GetLocalStateFolders(product, &local_state_folders); |
1224 FilePath backup_state_file(BackupLocalStateFile(local_state_folders)); | 1301 FilePath backup_state_file(BackupLocalStateFile(local_state_folders)); |
1225 | 1302 |
1226 DeleteResult delete_result = DELETE_SUCCEEDED; | |
1227 | |
1228 if (product.is_chrome_app_host()) { | 1303 if (product.is_chrome_app_host()) { |
1229 DeleteAppHostFilesAndFolders(installer_state, product_state->version()); | 1304 DeleteAppHostFilesAndFolders(installer_state, product_state->version()); |
1230 } else if (!installer_state.is_multi_install() || | 1305 } else if (!installer_state.is_multi_install() || |
1231 product.is_chrome_binaries()) { | 1306 product.is_chrome_binaries()) { |
1232 | 1307 DeleteResult delete_result = DeleteChromeFilesAndFolders( |
1233 // In order to be able to remove the folder in which we're running, we | 1308 installer_state, cmd_line.GetProgram()); |
1234 // need to move setup.exe out of the install folder. | 1309 if (delete_result == DELETE_FAILED) { |
1235 // TODO(tommi): What if the temp folder is on a different volume? | 1310 ret = installer::UNINSTALL_FAILED; |
1236 MoveSetupOutOfInstallFolder(installer_state, setup_path, | 1311 } else if (delete_result == DELETE_REQUIRES_REBOOT) { |
1237 product_state->version()); | 1312 ret = installer::UNINSTALL_REQUIRES_REBOOT; |
1238 delete_result = DeleteChromeFilesAndFolders(installer_state, | 1313 } |
1239 product_state->version()); | |
1240 } | 1314 } |
1241 | 1315 |
1242 if (delete_profile) | 1316 if (delete_profile) |
1243 DeleteLocalState(local_state_folders, product.is_chrome_frame()); | 1317 DeleteLocalState(local_state_folders, product.is_chrome_frame()); |
1244 | 1318 |
1245 if (delete_result == DELETE_FAILED) { | |
1246 ret = installer::UNINSTALL_FAILED; | |
1247 } else if (delete_result == DELETE_REQUIRES_REBOOT) { | |
1248 ret = installer::UNINSTALL_REQUIRES_REBOOT; | |
1249 } | |
1250 | |
1251 if (!force_uninstall) { | 1319 if (!force_uninstall) { |
1252 VLOG(1) << "Uninstallation complete. Launching post-uninstall operations."; | 1320 VLOG(1) << "Uninstallation complete. Launching post-uninstall operations."; |
1253 browser_dist->DoPostUninstallOperations(product_state->version(), | 1321 browser_dist->DoPostUninstallOperations(product_state->version(), |
1254 backup_state_file, distribution_data); | 1322 backup_state_file, distribution_data); |
1255 } | 1323 } |
1256 | 1324 |
1257 // Try and delete the preserved local state once the post-install | 1325 // Try and delete the preserved local state once the post-install |
1258 // operations are complete. | 1326 // operations are complete. |
1259 if (!backup_state_file.empty()) | 1327 if (!backup_state_file.empty()) |
1260 file_util::Delete(backup_state_file, false); | 1328 file_util::Delete(backup_state_file, false); |
1261 | 1329 |
1262 // If user-level Chrome is being uninstalled and system-level Chrome is | 1330 // If user-level Chrome is being uninstalled and system-level Chrome is |
1263 // present, launch the system-level Active Setup command to do post-install | 1331 // present, launch the system-level Active Setup command to do post-install |
1264 // tasks for this user (i.e., create shortcuts). | 1332 // tasks for this user (i.e., create shortcuts). |
1265 if (product.is_chrome() && !installer_state.system_install() && | 1333 if (product.is_chrome() && !installer_state.system_install() && |
1266 original_state.GetProductState(true, browser_dist->GetType())) { | 1334 original_state.GetProductState(true, browser_dist->GetType())) { |
1267 InstallUtil::TriggerActiveSetupCommand(); | 1335 InstallUtil::TriggerActiveSetupCommand(); |
1268 } | 1336 } |
1269 | 1337 |
1270 return ret; | 1338 return ret; |
1271 } | 1339 } |
1272 | 1340 |
| 1341 void CleanUpInstallationDirectoryAfterUninstall( |
| 1342 const InstallationState& original_state, |
| 1343 const InstallerState& installer_state, |
| 1344 const CommandLine& cmd_line, |
| 1345 installer::InstallStatus* uninstall_status) { |
| 1346 if (*uninstall_status != installer::UNINSTALL_SUCCESSFUL && |
| 1347 *uninstall_status != installer::UNINSTALL_REQUIRES_REBOOT) { |
| 1348 return; |
| 1349 } |
| 1350 const FilePath target_path(installer_state.target_path()); |
| 1351 if (target_path.empty()) { |
| 1352 LOG(ERROR) << "No installation destination path."; |
| 1353 *uninstall_status = installer::UNINSTALL_FAILED; |
| 1354 return; |
| 1355 } |
| 1356 FilePath setup_exe(cmd_line.GetProgram()); |
| 1357 file_util::AbsolutePath(&setup_exe); |
| 1358 if (!target_path.IsParent(setup_exe)) { |
| 1359 LOG(INFO) << "setup.exe is not in target path. Skipping installer cleanup."; |
| 1360 return; |
| 1361 } |
| 1362 FilePath install_directory(setup_exe.DirName()); |
| 1363 |
| 1364 bool remove_setup = true; |
| 1365 bool remove_archive = true; |
| 1366 CheckShouldRemoveSetupAndArchive(original_state, installer_state, |
| 1367 &remove_setup, &remove_archive); |
| 1368 if (!remove_archive) |
| 1369 return; |
| 1370 |
| 1371 if (remove_setup) { |
| 1372 // In order to be able to remove the folder in which we're running, we |
| 1373 // need to move setup.exe out of the install folder. |
| 1374 // TODO(tommi): What if the temp folder is on a different volume? |
| 1375 MoveSetupOutOfInstallFolder(installer_state, setup_exe); |
| 1376 } |
| 1377 |
| 1378 // Remove files from "...\<product>\Application\<version>\Installer" |
| 1379 if (!RemoveInstallerFiles(install_directory, remove_setup)) { |
| 1380 *uninstall_status = installer::UNINSTALL_FAILED; |
| 1381 return; |
| 1382 } |
| 1383 |
| 1384 if (!remove_setup) |
| 1385 return; |
| 1386 |
| 1387 // Try to remove the empty directory hierarchy. |
| 1388 |
| 1389 // Delete "...\<product>\Application\<version>\Installer" |
| 1390 if (DeleteEmptyDir(install_directory) != DELETE_SUCCEEDED) { |
| 1391 *uninstall_status = installer::UNINSTALL_FAILED; |
| 1392 return; |
| 1393 } |
| 1394 |
| 1395 // Delete "...\<product>\Application\<version>" |
| 1396 DeleteResult delete_result = DeleteEmptyDir(install_directory.DirName()); |
| 1397 if (delete_result == DELETE_FAILED || |
| 1398 (delete_result == DELETE_NOT_EMPTY && |
| 1399 *uninstall_status != installer::UNINSTALL_REQUIRES_REBOOT)) { |
| 1400 *uninstall_status = installer::UNINSTALL_FAILED; |
| 1401 return; |
| 1402 } |
| 1403 |
| 1404 if (*uninstall_status == installer::UNINSTALL_REQUIRES_REBOOT) { |
| 1405 // Delete the Application directory at reboot if empty. |
| 1406 ScheduleFileSystemEntityForDeletion(target_path.value().c_str()); |
| 1407 |
| 1408 // If we need a reboot to continue, schedule the parent directories for |
| 1409 // deletion unconditionally. If they are not empty, the session manager |
| 1410 // will not delete them on reboot. |
| 1411 ScheduleParentAndGrandparentForDeletion(target_path); |
| 1412 } else if (DeleteApplicationProductAndVendorDirectories(target_path) == |
| 1413 installer::DELETE_FAILED) { |
| 1414 *uninstall_status = installer::UNINSTALL_FAILED; |
| 1415 } |
| 1416 } |
| 1417 |
1273 } // namespace installer | 1418 } // namespace installer |
OLD | NEW |