blob: ea061dfb5dc11aadb3d9c4ad791a5c723f77333d [file] [log] [blame]
Avi Drissman4a8573c2022-09-09 19:35:541// Copyright 2020 The Chromium Authors
Yann Dago78100ea2020-02-04 17:46:342// 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/downgrade/downgrade_utils.h"
6
7#include "base/files/file_enumerator.h"
8#include "base/files/file_util.h"
Hans Wennborgf6ad69c2020-06-18 18:02:329#include "base/logging.h"
Yann Dago78100ea2020-02-04 17:46:3410#include "build/build_config.h"
11#include "chrome/browser/downgrade/user_data_downgrade.h"
12
Xiaohan Wangfb1e615d2022-01-15 22:25:3413#if BUILDFLAG(IS_WIN)
Yann Dago78100ea2020-02-04 17:46:3414#include <windows.h>
Xiaohan Wangfb1e615d2022-01-15 22:25:3415#elif BUILDFLAG(IS_POSIX)
Yann Dago78100ea2020-02-04 17:46:3416#include "base/files/file.h"
17#endif
18
19namespace downgrade {
20
21base::FilePath GetTempDirNameForDelete(const base::FilePath& dir,
22 const base::FilePath& name) {
23 if (dir.empty())
24 return base::FilePath();
25
26 return base::GetUniquePath(
27 dir.Append(name).AddExtension(kDowngradeDeleteSuffix));
28}
29
30bool MoveWithoutFallback(const base::FilePath& source,
31 const base::FilePath& target) {
Xiaohan Wangfb1e615d2022-01-15 22:25:3432#if BUILDFLAG(IS_WIN)
Yann Dago78100ea2020-02-04 17:46:3433 // TODO(grt): check whether or not this is sufficiently atomic when |source|
34 // is on a network share.
35 auto result = ::MoveFileEx(source.value().c_str(), target.value().c_str(), 0);
36 PLOG_IF(ERROR, !result) << source << " -> " << target;
37 return result;
Hzj_jie04163172024-04-29 19:37:1638#elif BUILDFLAG(IS_POSIX)
Yann Dago78100ea2020-02-04 17:46:3439 // Windows compatibility: if |target| exists, |source| and |target|
40 // must be the same type, either both files, or both directories.
41 base::stat_wrapper_t target_info;
danakj9fa839d2024-05-03 16:34:2242 if (base::File::Stat(target, &target_info) == 0) {
Yann Dago78100ea2020-02-04 17:46:3443 base::stat_wrapper_t source_info;
danakj9fa839d2024-05-03 16:34:2244 if (base::File::Stat(source, &source_info) != 0) {
Yann Dago78100ea2020-02-04 17:46:3445 return false;
danakj9fa839d2024-05-03 16:34:2246 }
Yann Dago78100ea2020-02-04 17:46:3447 if (S_ISDIR(target_info.st_mode) != S_ISDIR(source_info.st_mode))
48 return false;
49 }
50
51 auto result = rename(source.value().c_str(), target.value().c_str());
52 PLOG_IF(ERROR, result) << source << " -> " << target;
53 return !result;
54#endif
55}
56
Yann Dagof20d6f02021-09-20 17:21:0257bool MoveContents(const base::FilePath& source,
58 const base::FilePath& target,
59 ExclusionPredicate exclusion_predicate) {
Yann Dago78100ea2020-02-04 17:46:3460 // Implementation note: moving is better than deleting in this case since it
61 // avoids certain failure modes. For example: on Windows, a file that is open
62 // with FILE_SHARE_DELETE can be moved or marked for deletion. If it is moved
63 // aside, the containing directory may then be eligible for deletion. If, on
64 // the other hand, it is marked for deletion, it cannot be moved nor can its
65 // containing directory be moved or deleted.
Yann Dagof20d6f02021-09-20 17:21:0266 bool all_succeeded = base::CreateDirectory(target);
67 if (!all_succeeded) {
Yann Dago78100ea2020-02-04 17:46:3468 PLOG(ERROR) << target;
Yann Dagof20d6f02021-09-20 17:21:0269 return all_succeeded;
Yann Dago78100ea2020-02-04 17:46:3470 }
71
Yann Dago78100ea2020-02-04 17:46:3472 base::FileEnumerator enumerator(
73 source, false,
74 base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
75 for (base::FilePath path = enumerator.Next(); !path.empty();
76 path = enumerator.Next()) {
77 const base::FileEnumerator::FileInfo info = enumerator.GetInfo();
78 const base::FilePath name = info.GetName();
79 if (exclusion_predicate && exclusion_predicate.Run(name))
80 continue;
81 const base::FilePath this_target = target.Append(name);
82 // A directory can be moved unless any file within it is open. A simple file
83 // can be moved unless it is opened without FILE_SHARE_DELETE. (As with most
84 // things in life, there are exceptions to this rule, but they are
85 // uncommon. For example, a file opened without FILE_SHARE_DELETE can be
86 // moved as long as it was opened only with some combination of
87 // READ_CONTROL, WRITE_DAC, WRITE_OWNER, and SYNCHRONIZE access rights.
88 // Since this short list excludes such useful rights as FILE_EXECUTE,
89 // FILE_READ_DATA, and most anything else one would want a file for, it's
90 // likely an uncommon scenario. See OpenFileTest in base/files for more.)
91 if (MoveWithoutFallback(path, this_target))
92 continue;
93 if (!info.IsDirectory()) {
Yann Dagof20d6f02021-09-20 17:21:0294 all_succeeded = false;
Yann Dago78100ea2020-02-04 17:46:3495 continue;
96 }
Yann Dagof20d6f02021-09-20 17:21:0297 MoveContents(path, this_target, ExclusionPredicate());
Yann Dago78100ea2020-02-04 17:46:3498 // If everything within the directory was moved, it may be possible to
99 // delete it now.
Lei Zhangfa6861ee2020-06-25 20:37:16100 if (!base::DeleteFile(path))
Yann Dagof20d6f02021-09-20 17:21:02101 all_succeeded = false;
Yann Dago78100ea2020-02-04 17:46:34102 }
Yann Dagof20d6f02021-09-20 17:21:02103 return all_succeeded;
Yann Dago78100ea2020-02-04 17:46:34104}
105
106} // namespace downgrade