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
|
// Copyright 2010 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/util/copy_tree_work_item.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/win/shlwapi.h"
CopyTreeWorkItem::~CopyTreeWorkItem() = default;
CopyTreeWorkItem::CopyTreeWorkItem(const base::FilePath& source_path,
const base::FilePath& dest_path,
const base::FilePath& temp_path,
CopyOverWriteOption overwrite_option,
const base::FilePath& alternative_path)
: source_path_(source_path),
dest_path_(dest_path),
temp_path_(temp_path),
overwrite_option_(overwrite_option),
alternative_path_(alternative_path),
copied_to_dest_path_(false),
moved_to_backup_(false),
copied_to_alternate_path_(false),
backup_path_created_(false) {}
bool CopyTreeWorkItem::DoImpl() {
if (!base::PathExists(source_path_)) {
LOG(ERROR) << source_path_.value() << " does not exist";
return false;
}
bool dest_exist = base::PathExists(dest_path_);
// handle overwrite_option_ = IF_DIFFERENT case.
if ((dest_exist) &&
(overwrite_option_ == WorkItem::IF_DIFFERENT) && // only for single file
(!base::DirectoryExists(source_path_)) &&
(!base::DirectoryExists(dest_path_)) &&
(base::ContentsEqual(source_path_, dest_path_))) {
VLOG(1) << "Source file " << source_path_.value()
<< " and destination file " << dest_path_.value()
<< " are exactly same. Returning true.";
return true;
} else if ((dest_exist) &&
(overwrite_option_ == WorkItem::NEW_NAME_IF_IN_USE) &&
(!base::DirectoryExists(source_path_)) &&
(!base::DirectoryExists(dest_path_)) &&
(IsFileInUse(dest_path_))) {
// handle overwrite_option_ = NEW_NAME_IF_IN_USE case.
if (alternative_path_.empty() || base::PathExists(alternative_path_) ||
!base::CopyFile(source_path_, alternative_path_)) {
LOG(ERROR) << "failed to copy " << source_path_.value() << " to "
<< alternative_path_.value();
return false;
} else {
copied_to_alternate_path_ = true;
VLOG(1) << "Copied source file " << source_path_.value()
<< " to alternative path " << alternative_path_.value();
return true;
}
} else if ((dest_exist) && (overwrite_option_ == WorkItem::IF_NOT_PRESENT)) {
// handle overwrite_option_ = IF_NOT_PRESENT case.
return true;
}
// In all cases that reach here, move dest to a backup path.
if (dest_exist) {
if (!backup_path_.CreateUniqueTempDirUnderPath(temp_path_)) {
PLOG(ERROR) << "Failed to get backup path in folder "
<< temp_path_.value();
return false;
}
backup_path_created_ = true;
base::FilePath backup =
backup_path_.GetPath().Append(dest_path_.BaseName());
if (base::Move(dest_path_, backup)) {
moved_to_backup_ = true;
VLOG(1) << "Moved destination " << dest_path_.value()
<< " to backup path " << backup.value();
} else {
PLOG(ERROR) << "failed moving " << dest_path_.value() << " to "
<< backup.value();
return false;
}
}
// In all cases that reach here, copy source to destination.
if (base::CopyDirectory(source_path_, dest_path_, true)) {
copied_to_dest_path_ = true;
VLOG(1) << "Copied source " << source_path_.value() << " to destination "
<< dest_path_.value();
} else {
LOG(ERROR) << "failed copy " << source_path_.value() << " to "
<< dest_path_.value();
return false;
}
return true;
}
void CopyTreeWorkItem::RollbackImpl() {
// Normally the delete operations below should not fail unless some
// programs like anti-virus are inspecting the files we just copied.
// If this does happen sometimes, we may consider using Move instead of
// Delete here. For now we just log the error and continue with the
// rest of rollback operation.
if (copied_to_dest_path_ && !base::DeletePathRecursively(dest_path_)) {
LOG(ERROR) << "Can not delete " << dest_path_.value();
}
if (moved_to_backup_) {
base::FilePath backup(backup_path_.GetPath().Append(dest_path_.BaseName()));
if (!base::Move(backup, dest_path_)) {
PLOG(ERROR) << "failed move " << backup.value() << " to "
<< dest_path_.value();
}
}
if (copied_to_alternate_path_ &&
!base::DeletePathRecursively(alternative_path_)) {
LOG(ERROR) << "Can not delete " << alternative_path_.value();
}
}
// static
bool CopyTreeWorkItem::IsFileInUse(const base::FilePath& path) {
if (!base::PathExists(path))
return false;
// A running executable is open with exclusive write access, so attempting to
// write to it will fail with a sharing violation. A more precise method would
// be to open the file with DELETE access and attempt to set the delete
// disposition on the handle. This would fail if the file was mapped into a
// process's address space, but succeed otherwise. This seems like overkill,
// however.
HANDLE handle =
::CreateFile(path.value().c_str(), FILE_WRITE_DATA,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nullptr, OPEN_EXISTING, 0, nullptr);
if (handle == INVALID_HANDLE_VALUE) {
// By and large, we expect the error to be ERROR_SHARING_VIOLATION if the
// file is being executed (see above). It may also be something like
// ERROR_ACCESS_DENIED; e.g., if the file was deleted but open handles to it
// remain. Consider any failure to open the file to mean that it's in-use
// and shouldn't be replaced.
return true;
}
CloseHandle(handle);
return false;
}
|