forked from chromium/chromium
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathsetup_main.cc
1692 lines (1520 loc) · 75 KB
/
setup_main.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/installer/setup/setup_main.h"
// clang-format off
#include <windows.h> // Must be included before msi.h.
#include <msi.h>
// clang-format on
#include <psapi.h>
#include <shellapi.h>
#include <shlobj.h>
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include <optional>
#include <string>
#include "base/at_exit.h"
#include "base/clang_profiling_buildflags.h"
#include "base/command_line.h"
#include "base/containers/span.h"
#include "base/dcheck_is_on.h"
#include "base/debug/alias.h"
#include "base/debug/dump_without_crashing.h"
#include "base/debug/handle_hooks_win.h"
#include "base/file_version_info.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/persistent_histogram_storage.h"
#include "base/numerics/safe_conversions.h"
#include "base/path_service.h"
#include "base/process/launch.h"
#include "base/process/memory.h"
#include "base/process/process.h"
#include "base/strings/strcat_win.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_executor.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "base/types/expected_macros.h"
#include "base/values.h"
#include "base/version.h"
#include "base/win/current_module.h"
#include "base/win/process_startup_helper.h"
#include "base/win/registry.h"
#include "base/win/resource_exhaustion.h"
#include "base/win/scoped_com_initializer.h"
#include "base/win/scoped_handle.h"
#include "base/win/windows_handle_util.h"
#include "build/branding_buildflags.h"
#include "build/build_config.h"
#include "chrome/browser/enterprise/connectors/device_trust/key_management/core/network/win_key_network_delegate.h"
#include "chrome/browser/enterprise/connectors/device_trust/key_management/installer/key_rotation_manager.h"
#include "chrome/browser/enterprise/connectors/device_trust/key_management/installer/key_rotation_types.h"
#include "chrome/browser/enterprise/connectors/device_trust/key_management/installer/management_service/rotate_util.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/install_static/install_details.h"
#include "chrome/install_static/install_util.h"
#include "chrome/installer/setup/archive_patch_helper.h"
#include "chrome/installer/setup/brand_behaviors.h"
#include "chrome/installer/setup/configure_app_container_sandbox.h"
#include "chrome/installer/setup/downgrade_cleanup.h"
#include "chrome/installer/setup/install.h"
#include "chrome/installer/setup/install_params.h"
#include "chrome/installer/setup/install_worker.h"
#include "chrome/installer/setup/installer_crash_reporting.h"
#include "chrome/installer/setup/installer_state.h"
#include "chrome/installer/setup/launch_chrome.h"
#include "chrome/installer/setup/modify_params.h"
#include "chrome/installer/setup/scoped_thread_pool.h"
#include "chrome/installer/setup/setup_constants.h"
#include "chrome/installer/setup/setup_install_details.h"
#include "chrome/installer/setup/setup_singleton.h"
#include "chrome/installer/setup/setup_util.h"
#include "chrome/installer/setup/uninstall.h"
#include "chrome/installer/setup/unpack_archive.h"
#include "chrome/installer/util/app_command.h"
#include "chrome/installer/util/conditional_work_item_list.h"
#include "chrome/installer/util/delete_after_reboot_helper.h"
#include "chrome/installer/util/delete_old_versions.h"
#include "chrome/installer/util/delete_tree_work_item.h"
#include "chrome/installer/util/google_update_constants.h"
#include "chrome/installer/util/google_update_settings.h"
#include "chrome/installer/util/helper.h"
#include "chrome/installer/util/html_dialog.h"
#include "chrome/installer/util/initial_preferences.h"
#include "chrome/installer/util/initial_preferences_constants.h"
#include "chrome/installer/util/install_service_work_item.h"
#include "chrome/installer/util/install_util.h"
#include "chrome/installer/util/installation_state.h"
#include "chrome/installer/util/installer_util_strings.h"
#include "chrome/installer/util/l10n_string_util.h"
#include "chrome/installer/util/logging_installer.h"
#include "chrome/installer/util/lzma_util.h"
#include "chrome/installer/util/self_cleaning_temp_dir.h"
#include "chrome/installer/util/shell_util.h"
#include "chrome/installer/util/util_constants.h"
#include "components/crash/core/app/crash_switches.h"
#include "components/crash/core/app/run_as_crashpad_handler_win.h"
#include "content/public/common/content_switches.h"
#include "url/gurl.h"
#if BUILDFLAG(CLANG_PROFILING)
#include "base/test/clang_profiling.h"
#endif
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
#include "chrome/installer/util/google_update_util.h"
#endif
using installer::InitialPreferences;
using installer::InstallationState;
using installer::InstallerState;
using installer::ProductState;
namespace {
const wchar_t kSystemPrincipalSid[] = L"S-1-5-18";
const wchar_t kDisplayVersion[] = L"DisplayVersion";
// Overwrite an existing DisplayVersion as written by the MSI installer
// with the real version number of Chrome.
LONG OverwriteDisplayVersion(const std::wstring& path,
const std::wstring& value,
REGSAM wowkey) {
base::win::RegKey key;
LONG result = 0;
std::wstring existing;
if ((result = key.Open(HKEY_LOCAL_MACHINE, path.c_str(),
KEY_QUERY_VALUE | KEY_SET_VALUE | wowkey)) !=
ERROR_SUCCESS) {
VLOG(1) << "Skipping DisplayVersion update because registry key " << path
<< " does not exist in "
<< (wowkey == KEY_WOW64_64KEY ? "64" : "32") << "bit hive";
return result;
}
if ((result = key.ReadValue(kDisplayVersion, &existing)) != ERROR_SUCCESS) {
LOG(ERROR) << "Failed to set DisplayVersion: " << kDisplayVersion
<< " not found under " << path;
return result;
}
if ((result = key.WriteValue(kDisplayVersion, value.c_str())) !=
ERROR_SUCCESS) {
LOG(ERROR) << "Failed to set DisplayVersion: " << kDisplayVersion
<< " could not be written under " << path;
return result;
}
VLOG(1) << "Set DisplayVersion at " << path << " to " << value << " from "
<< existing;
return ERROR_SUCCESS;
}
LONG OverwriteDisplayVersions(const std::wstring& product,
const std::wstring& value) {
// The version is held in two places. First change it in the MSI Installer
// registry entry. It is held under a "squashed guid" key.
std::wstring reg_path = base::StrCat(
{L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Installer\\UserData\\",
kSystemPrincipalSid, L"\\Products\\", InstallUtil::GuidToSquid(product),
L"\\InstallProperties"});
LONG result1 = OverwriteDisplayVersion(reg_path, value, KEY_WOW64_64KEY);
// The display version also exists under the Unininstall registry key with
// the original guid. Check both WOW64_64 and WOW64_32.
reg_path = base::StrCat(
{L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{", product,
L"}"});
// Consider the operation a success if either of these succeeds.
LONG result2 = OverwriteDisplayVersion(reg_path, value, KEY_WOW64_64KEY);
LONG result3 = OverwriteDisplayVersion(reg_path, value, KEY_WOW64_32KEY);
return result1 != ERROR_SUCCESS
? result1
: (result2 != ERROR_SUCCESS ? result3 : ERROR_SUCCESS);
}
// Launches a subprocess of `setup_exe` (the full path to this executable in the
// target installation directory) that will wait for msiexec to finish its work
// and then overwrite the DisplayVersion values in the Windows registry. `id` is
// the MSI product ID and `version` is the new Chrome version. The child will
// run with verbose logging enabled if `verbose_logging` is true.
void DelayedOverwriteDisplayVersions(const base::FilePath& setup_exe,
const std::string& id,
const base::Version& version,
bool verbose_logging) {
DCHECK(install_static::IsSystemInstall());
// Create an event to be given to the child process that it will signal
// immediately before blocking on msiexec's mutex.
SECURITY_ATTRIBUTES attributes = {};
attributes.nLength = sizeof(attributes);
attributes.bInheritHandle = TRUE;
base::win::ScopedHandle start_event(::CreateEventW(
&attributes, /*bManualReset=*/TRUE, /*bInitialState=*/FALSE,
/*lpName=*/nullptr));
PLOG_IF(ERROR, !start_event.IsValid()) << "Failed to create child event";
base::CommandLine command_line(setup_exe);
command_line.AppendSwitchASCII(installer::switches::kSetDisplayVersionProduct,
id);
command_line.AppendSwitchASCII(installer::switches::kSetDisplayVersionValue,
version.GetString());
if (start_event.IsValid()) {
command_line.AppendSwitchNative(
installer::switches::kStartupEventHandle,
base::NumberToWString(base::win::HandleToUint32(start_event.Get())));
}
InstallUtil::AppendModeAndChannelSwitches(&command_line);
command_line.AppendSwitch(installer::switches::kSystemLevel);
if (verbose_logging) {
command_line.AppendSwitch(installer::switches::kVerboseLogging);
}
base::LaunchOptions launch_options;
if (start_event.IsValid()) {
launch_options.handles_to_inherit.push_back(start_event.Get());
}
launch_options.force_breakaway_from_job_ = true;
base::Process writer = base::LaunchProcess(command_line, launch_options);
if (!writer.IsValid()) {
PLOG(ERROR) << "Failed to set DisplayVersion: "
<< "could not launch subprocess to make desired changes."
<< " <<" << command_line.GetCommandLineString() << ">>";
return;
}
if (!start_event.IsValid()) {
return;
}
// Wait up to 30 seconds for either the start event to be signaled or for the
// child process to terminate (i.e., in case it crashes).
constexpr DWORD kWaitForStartTimeoutMs = 30 * 1000;
const HANDLE handles[] = {start_event.Get(), writer.Handle()};
auto wait_result =
::WaitForMultipleObjects(std::size(handles), &handles[0],
/*bWaitAll=*/FALSE, kWaitForStartTimeoutMs);
if (wait_result == WAIT_OBJECT_0) {
VLOG(1) << "Proceeding after waiting for DisplayVersion overwrite child.";
} else if (wait_result == WAIT_OBJECT_0 + 1) {
LOG(ERROR) << "Proceeding after unexpected DisplayVersion overwrite "
"child termination.";
} else if (wait_result == WAIT_TIMEOUT) {
LOG(ERROR) << "Proceeding after unexpected timeout waiting for "
"DisplayVersion overwrite child.";
} else {
DCHECK_EQ(wait_result, WAIT_FAILED);
PLOG(ERROR) << "Proceeding after failing to wait for DisplayVersion "
"overwrite child";
}
}
// Signals `event` if it is valid and then closes it.
void SignalAndCloseEvent(base::win::ScopedHandle event) {
if (event.IsValid() && !::SetEvent(event.Get())) {
// Failure to signal the event likely means that the handle is invalid.
// Clear the ScopedHandle to prevent a crash upon close and proceed with the
// operation. The parent process will wait for 30s in this case (see
// DelayedOverwriteDisplayVersions) and will then continue on its merry way.
if (auto error = ::GetLastError(); error != ERROR_INVALID_HANDLE) {
// It is highly unexpected that this would fail for any other reason. Send
// diagnostics for analysis just in case.
// TODO(grt): Check for data and remove this in June 2024.
base::debug::Alias(&error);
base::debug::DumpWithoutCrashing();
}
(void)event.release();
}
}
// Waits for msiexec to release its mutex and then overwrites DisplayVersion in
// the Windows registry.
LONG OverwriteDisplayVersionsAfterMsiexec(base::win::ScopedHandle startup_event,
const std::wstring& product,
const std::wstring& value) {
bool adjusted_priority = false;
bool acquired_mutex = false;
base::win::ScopedHandle msi_handle(::OpenMutexW(
SYNCHRONIZE, /*bInheritHandle=*/FALSE, L"Global\\_MSIExecute"));
if (msi_handle.IsValid()) {
VLOG(1) << "Blocking to acquire MSI mutex.";
// Raise the priority class for the process so that it can do its work as
// soon as possible after acquiring the mutex.
adjusted_priority =
::SetPriorityClass(::GetCurrentProcess(), REALTIME_PRIORITY_CLASS) != 0;
// Notify the parent process that this one is ready to go.
SignalAndCloseEvent(std::move(startup_event));
const auto wait_result = ::WaitForSingleObject(msi_handle.Get(), INFINITE);
if (wait_result == WAIT_FAILED) {
// The handle is valid and was opened with SYNCHRONIZE, so the wait should
// never fail. If it does, wait ten seconds and proceed with the overwrite
// to match the old behavior.
PLOG(ERROR) << "Overwriting DisplayVersion in 10s after failing to wait "
"for the MSI mutex";
base::PlatformThread::Sleep(base::Seconds(10));
} else {
CHECK(wait_result == WAIT_ABANDONED || wait_result == WAIT_OBJECT_0)
<< "WaitForSingleObject: " << wait_result;
VLOG(1) << "Acquired MSI mutex; overwriting DisplayVersion.";
acquired_mutex = true;
}
} else {
// The mutex should still be held by msiexec since the parent setup.exe
// (which is run in the context of a Windows Installer operation) is
// blocking on this process.
PLOG(ERROR) << "Overwriting DisplayVersion immediately after failing to "
"open the MSI mutex";
// Notify the parent process that this one is ready to go.
SignalAndCloseEvent(std::move(startup_event));
}
auto result = OverwriteDisplayVersions(product, value);
if (adjusted_priority) {
::SetPriorityClass(::GetCurrentProcess(), NORMAL_PRIORITY_CLASS);
}
if (acquired_mutex) {
::ReleaseMutex(msi_handle.Get());
}
return result;
}
// Repetitively attempts to delete all files that belong to old versions of
// Chrome from |install_dir|. Waits 15 seconds before the first attempt and 5
// minutes after each unsuccessful attempt. Returns when no files that belong to
// an old version of Chrome remain or when another process tries to acquire the
// SetupSingleton.
installer::InstallStatus RepeatDeleteOldVersions(
const base::FilePath& install_dir,
const installer::SetupSingleton& setup_singleton) {
// The 99th percentile of the number of attempts it takes to successfully
// delete old versions is 2.75. The 75th percentile is 1.77. 98% of calls to
// this function will successfully delete old versions.
// Source: 30 days of UMA data on June 25, 2019.
constexpr int kMaxNumAttempts = 3;
int num_attempts = 0;
while (num_attempts < kMaxNumAttempts) {
// Wait 15 seconds before the first attempt because trying to delete old
// files right away is likely to fail. Indeed, this is called in 2
// occasions:
// - When the installer fails to delete old files after a not-in-use update:
// retrying immediately is likely to fail again.
// - When executables are successfully renamed on Chrome startup or
// shutdown: old files can't be deleted because Chrome is still in use.
// Wait 5 minutes after an unsuccessful attempt because retrying immediately
// is likely to fail again.
const base::TimeDelta max_wait_time =
num_attempts == 0 ? base::Seconds(15) : base::Minutes(5);
if (setup_singleton.WaitForInterrupt(max_wait_time)) {
VLOG(1) << "Exiting --delete-old-versions process because another "
"process tries to acquire the SetupSingleton.";
return installer::SETUP_SINGLETON_RELEASED;
}
// SetPriorityClass with PROCESS_MODE_BACKGROUND_BEGIN will cap the process
// working set to 32 MiB. This was experimentally determined after being
// reported in https://crbug.com/1475179. This can lead to extreme
// inefficiency as most CPU time is spent faulting in pages and then
// immediately trimming the working set. In one trace 99% of CPU time was
// spent handling page faults, so avoid SetPriorityClass with
// PROCESS_MODE_BACKGROUND_BEGIN.
base::ScopedClosureRunner restore_priority;
if (::SetThreadPriority(::GetCurrentThread(),
THREAD_MODE_BACKGROUND_BEGIN) != 0) {
// Be aware that a thread restoring itself to normal priority from
// background priority is inherently somewhat of a priority inversion.
restore_priority.ReplaceClosure(base::BindOnce([]() {
::SetThreadPriority(::GetCurrentThread(), THREAD_MODE_BACKGROUND_END);
}));
}
const bool delete_old_versions_success =
installer::DeleteOldVersions(install_dir);
++num_attempts;
if (delete_old_versions_success) {
VLOG(1) << "Successfully deleted all old files from "
"--delete-old-versions process.";
return installer::DELETE_OLD_VERSIONS_SUCCESS;
} else if (num_attempts == 1) {
VLOG(1) << "Failed to delete all old files from --delete-old-versions "
"process. Will retry every five minutes.";
}
}
VLOG(1) << "Exiting --delete-old-versions process after retrying too many "
"times to delete all old files.";
DCHECK_EQ(num_attempts, kMaxNumAttempts);
return installer::DELETE_OLD_VERSIONS_TOO_MANY_ATTEMPTS;
}
// This function is called when --rename-chrome-exe option is specified on
// setup.exe command line. This function assumes an in-use update has happened
// for Chrome so there should be files called new_chrome.exe and
// new_chrome_proxy.exe on the file system and a key called 'opv' in the
// registry. This function will move new_chrome.exe to chrome.exe,
// new_chrome_proxy.exe to chrome_proxy.exe and delete 'opv' key in one atomic
// operation. This function also deletes elevation policies associated with the
// old version if they exist. |setup_exe| is the path to the current executable.
installer::InstallStatus RenameChromeExecutables(
const base::FilePath& setup_exe,
const InstallationState& original_state,
InstallerState* installer_state) {
const base::FilePath& target_path = installer_state->target_path();
base::FilePath chrome_exe(target_path.Append(installer::kChromeExe));
base::FilePath chrome_new_exe(target_path.Append(installer::kChromeNewExe));
base::FilePath chrome_old_exe(target_path.Append(installer::kChromeOldExe));
base::FilePath chrome_proxy_exe(
target_path.Append(installer::kChromeProxyExe));
base::FilePath chrome_proxy_new_exe(
target_path.Append(installer::kChromeProxyNewExe));
base::FilePath chrome_proxy_old_exe(
target_path.Append(installer::kChromeProxyOldExe));
// Create a temporary backup directory on the same volume as chrome.exe so
// that moving in-use files doesn't lead to trouble.
installer::SelfCleaningTempDir temp_path;
if (!temp_path.Initialize(target_path.DirName(),
installer::kInstallTempDir)) {
PLOG(ERROR)
<< "Failed to create Temp directory "
<< target_path.DirName().Append(installer::kInstallTempDir).value();
return installer::RENAME_FAILED;
}
std::unique_ptr<WorkItemList> install_list(WorkItem::CreateWorkItemList());
// Move chrome.exe to old_chrome.exe, then move new_chrome.exe to chrome.exe.
install_list->AddMoveTreeWorkItem(chrome_exe, chrome_old_exe,
temp_path.path(), WorkItem::ALWAYS_MOVE);
install_list->AddMoveTreeWorkItem(chrome_new_exe, chrome_exe,
temp_path.path(), WorkItem::ALWAYS_MOVE);
install_list->AddDeleteTreeWorkItem(chrome_new_exe, temp_path.path());
// Move chrome_proxy.exe to old_chrome_proxy.exe if it exists (a previous
// installation may not have included it), then move new_chrome_proxy.exe to
// chrome_proxy.exe.
std::unique_ptr<WorkItemList> existing_proxy_rename_list(
WorkItem::CreateConditionalWorkItemList(
new ConditionRunIfFileExists(chrome_proxy_exe)));
existing_proxy_rename_list->set_log_message("ExistingProxyRenameItemList");
existing_proxy_rename_list->AddMoveTreeWorkItem(
chrome_proxy_exe, chrome_proxy_old_exe, temp_path.path(),
WorkItem::ALWAYS_MOVE);
install_list->AddWorkItem(existing_proxy_rename_list.release());
install_list->AddMoveTreeWorkItem(chrome_proxy_new_exe, chrome_proxy_exe,
temp_path.path(), WorkItem::ALWAYS_MOVE);
install_list->AddDeleteTreeWorkItem(chrome_proxy_new_exe, temp_path.path());
AddFinalizeUpdateWorkItems(original_state,
base::Version(chrome::kChromeVersion),
*installer_state, setup_exe, install_list.get());
// Add work items to delete Chrome's "opv", "cpv", and "cmd" values.
// TODO(grt): Clean this up; https://crbug.com/577816.
const HKEY reg_root = installer_state->root_key();
const std::wstring clients_key = install_static::GetClientsKeyPath();
install_list->AddDeleteRegValueWorkItem(reg_root, clients_key,
KEY_WOW64_32KEY,
google_update::kRegOldVersionField);
install_list->AddDeleteRegValueWorkItem(
reg_root, clients_key, KEY_WOW64_32KEY,
google_update::kRegCriticalVersionField);
installer::AppCommand(installer::kCmdRenameChromeExe, {})
.AddDeleteAppCommandWorkItems(reg_root, install_list.get());
installer::AppCommand(installer::kCmdAlternateRenameChromeExe, {})
.AddDeleteAppCommandWorkItems(reg_root, install_list.get());
if (!installer_state->system_install()) {
install_list->AddDeleteRegValueWorkItem(
reg_root, clients_key, KEY_WOW64_32KEY, installer::kCmdRenameChromeExe);
}
// If a channel was specified by policy, update the "channel" registry value
// with it so that the browser knows which channel to use, otherwise delete
// whatever value that key holds.
installer::AddChannelWorkItems(reg_root, clients_key, install_list.get());
// old_chrome.exe is still in use in most cases, so ignore failures here.
install_list->AddDeleteTreeWorkItem(chrome_old_exe, temp_path.path())
->set_best_effort(true);
install_list->AddDeleteTreeWorkItem(chrome_proxy_old_exe, temp_path.path())
->set_best_effort(true);
installer::InstallStatus ret = installer::RENAME_SUCCESSFUL;
if (install_list->Do()) {
installer::LaunchDeleteOldVersionsProcess(setup_exe, *installer_state);
} else {
LOG(ERROR) << "Renaming of executables failed. Rolling back any changes.";
install_list->Rollback();
ret = installer::RENAME_FAILED;
}
// temp_path's dtor will take care of deleting or scheduling itself for
// deletion at reboot when this scope closes.
VLOG(1) << "Deleting temporary directory " << temp_path.path().value();
return ret;
}
// Checks for compatibility between the current state of the system and the
// desired operation.
// Also blocks simultaneous user-level and system-level installs. In the case
// of trying to install user-level Chrome when system-level exists, the
// existing system-level Chrome is launched.
// When the pre-install conditions are not satisfied, the result is written to
// the registry (via WriteInstallerResult), |status| is set appropriately, and
// false is returned.
bool CheckPreInstallConditions(const InstallationState& original_state,
const InstallerState& installer_state,
installer::InstallStatus* status) {
if (!installer_state.system_install()) {
// This is a user-level installation. Make sure that we are not installing
// on top of an existing system-level installation.
const ProductState* user_level_product_state =
original_state.GetProductState(false);
const ProductState* system_level_product_state =
original_state.GetProductState(true);
// Allow upgrades to proceed so that out-of-date versions are not left
// around.
if (user_level_product_state) {
return true;
}
// This is a new user-level install...
if (system_level_product_state) {
// ... and the product already exists at system-level.
LOG(ERROR) << "Already installed version "
<< system_level_product_state->version().GetString()
<< " at system-level conflicts with this one at user-level.";
// Instruct Google Update to launch the existing system-level Chrome.
// There should be no error dialog.
base::FilePath install_path(
installer::GetInstalledDirectory(/*system_install=*/true));
if (install_path.empty()) {
// Give up if we failed to construct the install path.
*status = installer::OS_ERROR;
installer_state.WriteInstallerResult(*status, IDS_INSTALL_OS_ERROR_BASE,
nullptr);
} else {
*status = installer::EXISTING_VERSION_LAUNCHED;
base::FilePath chrome_exe = install_path.Append(installer::kChromeExe);
base::CommandLine cmd(chrome_exe);
cmd.AppendSwitch(switches::kForceFirstRun);
installer_state.WriteInstallerResult(
*status, IDS_INSTALL_EXISTING_VERSION_LAUNCHED_BASE, nullptr);
VLOG(1) << "Launching existing system-level chrome instead.";
base::LaunchProcess(cmd, base::LaunchOptions());
}
return false;
}
}
return true;
}
// Initializes |temp_path| to "Temp" within the target directory, and
// |unpack_path| to a random directory beginning with "source" within
// |temp_path|. Returns false on error.
bool CreateTemporaryAndUnpackDirectories(
const InstallerState& installer_state,
installer::SelfCleaningTempDir* temp_path,
base::FilePath* unpack_path) {
DCHECK(temp_path && unpack_path);
if (!temp_path->Initialize(installer_state.target_path().DirName(),
installer::kInstallTempDir)) {
PLOG(ERROR) << "Could not create temporary path.";
return false;
}
VLOG(1) << "Created path " << temp_path->path().value();
if (!base::CreateTemporaryDirInDir(
temp_path->path(), installer::kInstallSourceDir, unpack_path)) {
PLOG(ERROR) << "Could not create temporary path for unpacked archive.";
return false;
}
return true;
}
installer::InstallStatus UninstallProducts(InstallationState& original_state,
InstallerState& installer_state,
const base::FilePath& setup_exe,
const base::CommandLine& cmd_line) {
// System-level Chrome will be launched via this command if its program gets
// set below.
base::CommandLine system_level_cmd(base::CommandLine::NO_PROGRAM);
if (cmd_line.HasSwitch(installer::switches::kSelfDestruct) &&
!installer_state.system_install()) {
const base::FilePath system_install_dir(
installer::GetInstalledDirectory(/*system_install=*/true));
if (!system_install_dir.empty()) {
system_level_cmd.SetProgram(
system_install_dir.Append(installer::kChromeExe));
}
}
installer::InstallStatus install_status = installer::UNINSTALL_SUCCESSFUL;
const bool force = cmd_line.HasSwitch(installer::switches::kForceUninstall);
const bool remove_all =
!cmd_line.HasSwitch(installer::switches::kDoNotRemoveSharedItems);
const base::Version current_version(
installer_state.GetCurrentVersion(original_state));
const installer::ModifyParams modify_params = {
installer_state,
original_state,
setup_exe,
current_version,
};
install_status = UninstallProduct(modify_params, remove_all, force, cmd_line);
installer::CleanUpInstallationDirectoryAfterUninstall(
installer_state.target_path(), setup_exe, &install_status);
// The app and vendor dirs may now be empty. Make a last-ditch attempt to
// delete them.
installer::DeleteChromeDirectoriesIfEmpty(installer_state.target_path());
// Trigger Active Setup if it was requested for the chrome product. This needs
// to be done after the UninstallProduct calls as some of them might
// otherwise terminate the process launched by TriggerActiveSetupCommand().
if (cmd_line.HasSwitch(installer::switches::kTriggerActiveSetup)) {
InstallUtil::TriggerActiveSetupCommand();
}
if (!system_level_cmd.GetProgram().empty()) {
base::LaunchProcess(system_level_cmd, base::LaunchOptions());
}
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
// Tell Google Update that an uninstall has taken place. Ignore the return
// value: success or failure of Google Update has no bearing on the success
// or failure of Chrome's uninstallation.
google_update::UninstallGoogleUpdate(installer_state.system_install());
#endif // BUILDFLAG(GOOGLE_CHROME_BRANDING)
return install_status;
}
installer::InstallStatus InstallProducts(InstallationState& original_state,
const base::FilePath& setup_exe,
const base::CommandLine& cmd_line,
const InitialPreferences& prefs,
InstallerState* installer_state) {
DCHECK(installer_state);
installer::InstallStatus install_status = installer::UNKNOWN_STATUS;
installer::ArchiveType archive_type = installer::UNKNOWN_ARCHIVE_TYPE;
installer_state->SetStage(installer::PRECONDITIONS);
// Remove any legacy "-stage:*" values from the product's "ap" value.
installer::UpdateInstallStatus(archive_type, install_status);
// Drop to background processing mode if the process was started below the
// normal process priority class. This is done here because InstallProducts-
// Helper has read-only access to the state and because the action also
// affects everything else that runs below.
const bool entered_background_mode = installer::AdjustThreadPriority();
VLOG_IF(1, entered_background_mode) << "Entered background processing mode.";
if (CheckPreInstallConditions(original_state, *installer_state,
&install_status)) {
VLOG(1) << "Installing to " << installer_state->target_path().value();
install_status =
InstallProductsHelper(original_state, setup_exe, cmd_line, prefs,
*installer_state, &archive_type);
} else {
// CheckPreInstallConditions must set the status on failure.
DCHECK_NE(install_status, installer::UNKNOWN_STATUS);
}
// Delete the initial preferences file if present. Note that we do not care
// about rollback here and we schedule for deletion on reboot if the delete
// fails. As such, we do not use DeleteTreeWorkItem.
if (cmd_line.HasSwitch(installer::switches::kInstallerData)) {
base::FilePath prefs_path(
cmd_line.GetSwitchValuePath(installer::switches::kInstallerData));
if (!base::DeleteFile(prefs_path)) {
LOG(ERROR) << "Failed deleting initial preferences file "
<< prefs_path.value()
<< ", scheduling for deletion after reboot.";
ScheduleFileSystemEntityForDeletion(prefs_path);
}
}
UpdateInstallStatus(archive_type, install_status);
return install_status;
}
installer::InstallStatus ShowEulaDialog(const std::wstring& inner_frame) {
VLOG(1) << "About to show EULA";
std::wstring eula_path = installer::GetLocalizedEulaResource();
if (eula_path.empty()) {
LOG(ERROR) << "No EULA path available";
return installer::EULA_REJECTED;
}
// Newer versions of the caller pass an inner frame parameter that must
// be given to the html page being launched.
installer::EulaHTMLDialog dlg(eula_path, inner_frame);
installer::EulaHTMLDialog::Outcome outcome = dlg.ShowModal();
if (installer::EulaHTMLDialog::REJECTED == outcome) {
LOG(ERROR) << "EULA rejected or EULA failure";
return installer::EULA_REJECTED;
}
if (installer::EulaHTMLDialog::ACCEPTED_OPT_IN == outcome) {
VLOG(1) << "EULA accepted (opt-in)";
return installer::EULA_ACCEPTED_OPT_IN;
}
VLOG(1) << "EULA accepted (no opt-in)";
return installer::EULA_ACCEPTED;
}
// Creates the sentinel indicating that the EULA was required and has been
// accepted.
bool CreateEulaSentinel() {
base::FilePath eula_sentinel;
if (!InstallUtil::GetEulaSentinelFilePath(&eula_sentinel)) {
return false;
}
return (base::CreateDirectory(eula_sentinel.DirName()) &&
base::WriteFile(eula_sentinel, ""));
}
installer::InstallStatus RegisterDevChrome(
const installer::ModifyParams& modify_params,
const base::CommandLine& cmd_line) {
const InstallationState& original_state = *modify_params.installation_state;
const base::FilePath& setup_exe = *modify_params.setup_path;
// Only proceed with registering a dev chrome if no real Chrome installation
// of the same install mode is present on this system.
const ProductState* existing_chrome = original_state.GetProductState(false);
if (!existing_chrome) {
existing_chrome = original_state.GetProductState(true);
}
if (existing_chrome) {
const std::wstring name = InstallUtil::GetDisplayName();
const std::wstring message = base::StrCat(
{L"You already have a full-installation (non-dev) of ", name,
L", please uninstall it first using Add/Remove Programs in the "
L"control panel."});
LOG(ERROR) << "Aborting operation: another installation of " << name
<< " was found, as a last resort (if the product is not present "
"in Add/Remove Programs), try executing: "
<< existing_chrome->uninstall_command().GetCommandLineString();
MessageBox(nullptr, message.c_str(), nullptr, MB_ICONERROR);
return installer::INSTALL_FAILED;
}
base::FilePath chrome_exe(
cmd_line.GetSwitchValuePath(installer::switches::kRegisterDevChrome));
if (chrome_exe.empty()) {
chrome_exe = setup_exe.DirName().Append(installer::kChromeExe);
}
if (!chrome_exe.IsAbsolute()) {
chrome_exe = base::MakeAbsoluteFilePath(chrome_exe);
}
installer::InstallStatus status = installer::FIRST_INSTALL_SUCCESS;
if (base::PathExists(chrome_exe)) {
// Create the Start menu shortcut and pin it to the Win7+ taskbar.
ShellUtil::ShortcutProperties shortcut_properties(ShellUtil::CURRENT_USER);
ShellUtil::AddDefaultShortcutProperties(chrome_exe, &shortcut_properties);
shortcut_properties.set_pin_to_taskbar(true);
ShellUtil::CreateOrUpdateShortcut(
ShellUtil::SHORTCUT_LOCATION_START_MENU_ROOT, shortcut_properties,
ShellUtil::SHELL_SHORTCUT_CREATE_ALWAYS);
// Register Chrome at user-level and make it default.
if (ShellUtil::CanMakeChromeDefaultUnattended()) {
ShellUtil::MakeChromeDefault(ShellUtil::CURRENT_USER, chrome_exe, true);
} else {
ShellUtil::ShowMakeChromeDefaultSystemUI(chrome_exe);
}
} else {
LOG(ERROR) << "Path not found: " << chrome_exe.value();
status = installer::INSTALL_FAILED;
}
return status;
}
installer::InstallStatus CreateShortcutsInChildProc(
const InstallerState& installer_state,
const InitialPreferences& prefs,
installer::InstallShortcutLevel install_level,
installer::InstallShortcutOperation install_operation) {
// Create shortcut in a child process so that shell crashes don't make the
// install/update fail. Pass install operation on the command line since
// it can't be deduced by the child process;
// Creates shortcuts for Chrome.
const base::FilePath chrome_exe(
installer_state.target_path().Append(installer::kChromeExe));
// Install per-user shortcuts on user-level installs and all-users shortcuts
// on system-level installs. Note that Active Setup will take care of
// installing missing per-user shortcuts on system-level install (i.e.,
// quick launch, taskbar pin, and possibly deleted all-users shortcuts).
CreateOrUpdateShortcuts(chrome_exe, prefs, install_level, install_operation);
// TODO(): Plumb shortcut creation failure through and return a
// failure exit code.
return installer::CREATE_SHORTCUTS_SUCCESS;
}
// Verifies that the system tracing service may be enabled or disabled.
// Returns INSTALL_REPAIRED on success, or another InstallStatus value on
// failure.
int VerifySystemTracingAllowed(const installer::InstallerState& installer_state,
const base::Version& current_version) {
const bool is_developer = base::CommandLine::ForCurrentProcess()->HasSwitch(
installer::switches::kDeveloper);
if (!is_developer &&
(!installer_state.system_install() || !current_version.IsValid())) {
LOG(ERROR) << "system tracing is only supported for existing per-machine "
"installs.";
return installer::INSTALL_FAILED;
}
if (!::IsUserAnAdmin()) {
LOG(ERROR) << "system tracing setup requires administrative rights.";
return installer::INSUFFICIENT_RIGHTS;
}
return installer::INSTALL_REPAIRED;
}
int EnableSystemTracing(const installer::InstallerState& installer_state,
const base::Version& current_version) {
if (int error = VerifySystemTracingAllowed(installer_state, current_version);
error != installer::INSTALL_REPAIRED) {
return error;
}
base::FilePath tracing_service_exe(installer::GetTracingServicePath(
installer_state.target_path(), current_version));
// If the command line includes "--developer", register
// elevated_tracing_service.exe in the current executable's directory. This is
// intended for use by developers who wish to run the browser in their
// development directory and have it use a tracing service from the same
// directory. Use with caution: this will likely break tracing for a normal
// installation of the same browser (e.g., stable Google Chrome if running a
// branded build), and may be overwritten by an update of the same browser.
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
installer::switches::kDeveloper)) {
base::FilePath dir_exe;
if (base::PathService::Get(base::DIR_EXE, &dir_exe)) {
tracing_service_exe =
dir_exe.Append(installer::kElevatedTracingServiceExe);
}
}
installer::InstallServiceWorkItem work_item(
install_static::GetTracingServiceName(),
install_static::GetTracingServiceDisplayName(),
installer::GetLocalizedStringF(IDS_TRACING_SERVICE_DESCRIPTION_BASE,
{install_static::GetBaseAppName()}),
SERVICE_DEMAND_START, base::CommandLine(tracing_service_exe),
base::CommandLine(base::CommandLine::NO_PROGRAM),
install_static::GetClientStateKeyPath(),
{install_static::GetTracingServiceClsid()},
{install_static::GetTracingServiceIid()});
if (work_item.Do()) {
return installer::INSTALL_REPAIRED;
}
work_item.Rollback();
return installer::INSTALL_FAILED;
}
int DisableSystemTracing(const installer::InstallerState& installer_state,
const base::Version& current_version) {
if (int error = VerifySystemTracingAllowed(installer_state, current_version);
error != installer::INSTALL_REPAIRED) {
return error;
}
return installer::InstallServiceWorkItem::DeleteService(
install_static::GetTracingServiceName(),
install_static::GetClientStateKeyPath(),
{install_static::GetTracingServiceClsid()},
{install_static::GetTracingServiceIid()})
? installer::INSTALL_REPAIRED
: installer::INSTALL_FAILED;
}
// This method processes any command line options that make setup.exe do
// various tasks other than installation (renaming chrome.exe, showing eula
// among others). This function returns true if any such command line option
// has been found and processed (so setup.exe should exit at that point).
bool HandleNonInstallCmdLineOptions(installer::ModifyParams& modify_params,
const base::CommandLine& cmd_line,
const InitialPreferences& prefs,
int* exit_code) {
installer::InstallerState* installer_state =
&(*modify_params.installer_state);
installer::InstallationState* original_state =
&(*modify_params.installation_state);
const base::FilePath& setup_exe = *modify_params.setup_path;
// TODO(gab): Add a local |status| variable which each block below sets;
// only determine the |exit_code| from |status| at the end (this will allow
// this method to validate that
// (!handled || status != installer::UNKNOWN_STATUS)).
bool handled = true;
// TODO(tommi): Split these checks up into functions and use a data driven
// map of switch->function.
if (cmd_line.HasSwitch(installer::switches::kUpdateSetupExe)) {
installer_state->SetStage(installer::UPDATING_SETUP);
installer::InstallStatus status = installer::SETUP_PATCH_FAILED;
// If --update-setup-exe command line option is given, we apply the given
// patch to current exe, and store the resulting binary in the path
// specified by --new-setup-exe. But we need to first unpack the file
// given in --update-setup-exe.
const base::FilePath compressed_archive(
cmd_line.GetSwitchValuePath(installer::switches::kUpdateSetupExe));
VLOG(1) << "Opening archive " << compressed_archive.value();
// The top unpack failure result with 28 days aggregation (>=0.01%)
// Setup.Install.LzmaUnPackResult_SetupExePatch
// 0.02% PATH_NOT_FOUND
//
// More information can also be found with metric:
// Setup.Install.LzmaUnPackNTSTATUS_SetupExePatch
// We use the `new_setup_exe` directory as the working directory for
// `ArchivePatchHelper::UncompressAndPatch`. For System installs, this
// directory would be under %ProgramFiles% (a directory that only admins can
// write to by default) and hence a secure location.
const base::FilePath new_setup_exe(
cmd_line.GetSwitchValuePath(installer::switches::kNewSetupExe));
if (installer::ArchivePatchHelper::UncompressAndPatch(
new_setup_exe.DirName(), compressed_archive, setup_exe,
new_setup_exe, installer::UnPackConsumer::SETUP_EXE_PATCH)) {
status = installer::NEW_VERSION_UPDATED;
}
*exit_code = InstallUtil::GetInstallReturnCode(status);
if (*exit_code) {
LOG(WARNING) << "setup.exe patching failed.";
installer_state->WriteInstallerResult(status, IDS_SETUP_PATCH_FAILED_BASE,
nullptr);
}
} else if (cmd_line.HasSwitch(installer::switches::kShowEula)) {
// Check if we need to show the EULA. If it is passed as a command line
// then the dialog is shown and regardless of the outcome setup exits here.
std::wstring inner_frame =
cmd_line.GetSwitchValueNative(installer::switches::kShowEula);
*exit_code = ShowEulaDialog(inner_frame);
if (installer::EULA_REJECTED != *exit_code) {
if (GoogleUpdateSettings::SetEulaConsent(*original_state, true)) {
CreateEulaSentinel();
}
}
} else if (cmd_line.HasSwitch(installer::switches::kConfigureUserSettings)) {
// NOTE: Should the work done here, on kConfigureUserSettings, change:
// kActiveSetupVersion in install_worker.cc needs to be increased for Active
// Setup to invoke this again for all users of this install.
installer::InstallStatus status = installer::INVALID_STATE_FOR_OPTION;
if (installer_state->system_install()) {
bool force =
cmd_line.HasSwitch(installer::switches::kForceConfigureUserSettings);
installer::HandleActiveSetupForBrowser(*installer_state, setup_exe,
force);
status = installer::INSTALL_REPAIRED;
} else {
LOG(DFATAL)
<< "--configure-user-settings is incompatible with user-level";
}
*exit_code = InstallUtil::GetInstallReturnCode(status);
} else if (cmd_line.HasSwitch(installer::switches::kRegisterDevChrome)) {
installer::InstallStatus status =
RegisterDevChrome(modify_params, cmd_line);
*exit_code = InstallUtil::GetInstallReturnCode(status);
} else if (cmd_line.HasSwitch(installer::switches::kRegisterChromeBrowser)) {
installer::InstallStatus status = installer::UNKNOWN_STATUS;