File: base_file_win.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (309 lines) | stat: -rw-r--r-- 11,603 bytes parent folder | download | duplicates (9)
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
// 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 "components/download/public/common/base_file.h"

#include <objbase.h>

#include <shobjidl.h>
#include <windows.h>

#include <shellapi.h>
#include <wrl/client.h>
#include <wrl/implements.h>

#include "base/threading/scoped_blocking_call.h"
#include "base/win/com_init_util.h"
#include "components/download/public/common/download_interrupt_reasons_utils.h"
#include "components/download/public/common/download_stats.h"

namespace download {
namespace {

// By and large the errors seen here are listed in sherrors.h, included from
// shobjidl.h.
DownloadInterruptReason HRESULTToDownloadInterruptReason(HRESULT hr) {
  // S_OK, other success values are aggregated here.
  if (SUCCEEDED(hr) && HRESULT_FACILITY(hr) != FACILITY_SHELL)
    return DOWNLOAD_INTERRUPT_REASON_NONE;

  DownloadInterruptReason reason = DOWNLOAD_INTERRUPT_REASON_NONE;
  // All of the remaining HRESULTs to be considered are either from the copy
  // engine, or are unknown; we've got handling for all the copy engine errors,
  // and otherwise we'll just return the generic error reason.
  switch (hr) {
    case COPYENGINE_S_YES:
    case COPYENGINE_S_NOT_HANDLED:
    case COPYENGINE_S_USER_RETRY:
    case COPYENGINE_S_MERGE:
    case COPYENGINE_S_DONT_PROCESS_CHILDREN:
    case COPYENGINE_S_ALREADY_DONE:
    case COPYENGINE_S_PENDING:
    case COPYENGINE_S_KEEP_BOTH:
    case COPYENGINE_S_COLLISIONRESOLVED:
    case COPYENGINE_S_PROGRESS_PAUSE:
      return DOWNLOAD_INTERRUPT_REASON_NONE;

    case COPYENGINE_S_CLOSE_PROGRAM:
      // Like sharing violations, another process is using the file we want to
      // touch, so wait for it to close.
    case COPYENGINE_E_SHARING_VIOLATION_SRC:
    case COPYENGINE_E_SHARING_VIOLATION_DEST:
      // Sharing violations are encountered when some other process has a file
      // open; often it's antivirus scanning, and this error can be treated as
      // transient, as we assume eventually the other process will close its
      // handle.
      reason = DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR;
      break;

    case COPYENGINE_E_PATH_TOO_DEEP_DEST:
    case COPYENGINE_E_PATH_TOO_DEEP_SRC:
    case COPYENGINE_E_NEWFILE_NAME_TOO_LONG:
    case COPYENGINE_E_NEWFOLDER_NAME_TOO_LONG:
      // Any of these errors can be encountered if MAXPATH is hit while writing
      // out a filename. This can happen really just about anywhere.
      reason = DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG;
      break;

    case COPYENGINE_S_USER_IGNORED:
      // On Windows 7, inability to access a file may return "user ignored"
      // instead of correctly reporting the failure.
    case COPYENGINE_E_ACCESS_DENIED_DEST:
    case COPYENGINE_E_ACCESS_DENIED_SRC:
      // There's a security problem, or the file is otherwise inaccessible.
    case COPYENGINE_E_DEST_IS_RO_CD:
    case COPYENGINE_E_DEST_IS_RW_CD:
    case COPYENGINE_E_DEST_IS_R_CD:
    case COPYENGINE_E_DEST_IS_RO_DVD:
    case COPYENGINE_E_DEST_IS_RW_DVD:
    case COPYENGINE_E_DEST_IS_R_DVD:
    case COPYENGINE_E_SRC_IS_RO_CD:
    case COPYENGINE_E_SRC_IS_RW_CD:
    case COPYENGINE_E_SRC_IS_R_CD:
    case COPYENGINE_E_SRC_IS_RO_DVD:
    case COPYENGINE_E_SRC_IS_RW_DVD:
    case COPYENGINE_E_SRC_IS_R_DVD:
      // When the source is actually a disk, and a Move is attempted, it can't
      // delete the source. This is unlikely to be encountered in our scenario.
      reason = DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
      break;

    case COPYENGINE_E_FILE_TOO_LARGE:
    case COPYENGINE_E_DISK_FULL:
    case COPYENGINE_E_REMOVABLE_FULL:
    case COPYENGINE_E_DISK_FULL_CLEAN:
      // No room for the file in the destination location.
      reason = DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE;
      break;

    case COPYENGINE_E_ALREADY_EXISTS_NORMAL:
    case COPYENGINE_E_ALREADY_EXISTS_READONLY:
    case COPYENGINE_E_ALREADY_EXISTS_SYSTEM:
    case COPYENGINE_E_ALREADY_EXISTS_FOLDER:
      // The destination already exists and can't be replaced.
    case COPYENGINE_E_INVALID_FILES_SRC:
    case COPYENGINE_E_INVALID_FILES_DEST:
      // Either the source or destination file was invalid.
    case COPYENGINE_E_STREAM_LOSS:
    case COPYENGINE_E_EA_LOSS:
    case COPYENGINE_E_PROPERTY_LOSS:
    case COPYENGINE_E_PROPERTIES_LOSS:
    case COPYENGINE_E_ENCRYPTION_LOSS:
      // The destination can't support some functionality that the file needs.
      // The interesting one here is E_STREAM_LOSS, especially with MOTW.
    case COPYENGINE_E_FLD_IS_FILE_DEST:
    case COPYENGINE_E_FILE_IS_FLD_DEST:
      // There is an existing file with the same name as a new folder, and
      // vice versa.
    case COPYENGINE_E_ROOT_DIR_DEST:
    case COPYENGINE_E_ROOT_DIR_SRC:
    case COPYENGINE_E_DIFF_DIR:
    case COPYENGINE_E_SAME_FILE:
    case COPYENGINE_E_MANY_SRC_1_DEST:
    case COPYENGINE_E_DEST_SUBTREE:
    case COPYENGINE_E_DEST_SAME_TREE:
    case COPYENGINE_E_USER_CANCELLED:
    case COPYENGINE_E_CANCELLED:
    case COPYENGINE_E_REQUIRES_ELEVATION:
      reason = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
      break;
  }

  if (reason != DOWNLOAD_INTERRUPT_REASON_NONE) {
    return reason;
  }

  // Copy operations may still return Win32 error codes, so handle those here.
  if (HRESULT_FACILITY(hr) == FACILITY_WIN32) {
    return ConvertFileErrorToInterruptReason(
        base::File::OSErrorToFileError(HRESULT_CODE(hr)));
  }

  return DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
}

class FileOperationProgressSink
    : public Microsoft::WRL::RuntimeClass<
          Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
          IFileOperationProgressSink> {
 public:
  FileOperationProgressSink() = default;

  FileOperationProgressSink(const FileOperationProgressSink&) = delete;
  FileOperationProgressSink& operator=(const FileOperationProgressSink&) =
      delete;

  HRESULT GetOperationResult() { return result_; }

  // IFileOperationProgressSink:
  IFACEMETHODIMP FinishOperations(HRESULT hr) override {
    // If a failure has already been captured, don't bother overriding it. That
    // way, the original failure can be propagated; in the event that the new
    // HRESULT is also a success, overwriting will not harm anything and
    // captures the final state of the whole operation.
    if (SUCCEEDED(result_))
      result_ = hr;
    return S_OK;
  }

  IFACEMETHODIMP PauseTimer() override { return S_OK; }
  IFACEMETHODIMP PostCopyItem(DWORD,
                              IShellItem*,
                              IShellItem*,
                              PCWSTR,
                              HRESULT,
                              IShellItem*) override {
    return E_NOTIMPL;
  }
  IFACEMETHODIMP PostDeleteItem(DWORD,
                                IShellItem*,
                                HRESULT,
                                IShellItem*) override {
    return E_NOTIMPL;
  }
  IFACEMETHODIMP PostMoveItem(DWORD,
                              IShellItem*,
                              IShellItem*,
                              PCWSTR,
                              HRESULT hr,
                              IShellItem*) override {
    // Like in FinishOperations, overwriting with a different success value
    // does not have a negative impact, but replacing an existing failure will
    // cause issues.
    if (SUCCEEDED(result_))
      result_ = hr;
    return S_OK;
  }
  IFACEMETHODIMP PostNewItem(DWORD,
                             IShellItem*,
                             PCWSTR,
                             PCWSTR,
                             DWORD,
                             HRESULT,
                             IShellItem*) override {
    return E_NOTIMPL;
  }
  IFACEMETHODIMP
  PostRenameItem(DWORD, IShellItem*, PCWSTR, HRESULT, IShellItem*) override {
    return E_NOTIMPL;
  }
  IFACEMETHODIMP PreCopyItem(DWORD, IShellItem*, IShellItem*, PCWSTR) override {
    return E_NOTIMPL;
  }
  IFACEMETHODIMP PreDeleteItem(DWORD, IShellItem*) override {
    return E_NOTIMPL;
  }
  IFACEMETHODIMP PreMoveItem(DWORD, IShellItem*, IShellItem*, PCWSTR) override {
    return S_OK;
  }
  IFACEMETHODIMP PreNewItem(DWORD, IShellItem*, PCWSTR) override {
    return E_NOTIMPL;
  }
  IFACEMETHODIMP PreRenameItem(DWORD, IShellItem*, PCWSTR) override {
    return E_NOTIMPL;
  }
  IFACEMETHODIMP ResetTimer() override { return S_OK; }
  IFACEMETHODIMP ResumeTimer() override { return S_OK; }
  IFACEMETHODIMP StartOperations() override { return S_OK; }
  IFACEMETHODIMP UpdateProgress(UINT, UINT) override { return S_OK; }

 protected:
  ~FileOperationProgressSink() override = default;

 private:
  HRESULT result_ = S_OK;
};

}  // namespace

// Renames a file using IFileOperation::MoveItem() to ensure that the target
// file gets the correct default security descriptor in the new path.
// Returns a network error, or net::OK for success.
DownloadInterruptReason BaseFile::MoveFileAndAdjustPermissions(
    const base::FilePath& new_path) {
  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                base::BlockingType::MAY_BLOCK);

  base::win::AssertComInitialized();
  Microsoft::WRL::ComPtr<IShellItem> original_path;
  HRESULT hr = SHCreateItemFromParsingName(full_path_.value().c_str(), nullptr,
                                           IID_PPV_ARGS(&original_path));

  // |new_path| can be broken down to provide the new folder, as well as the
  // new filename. We'll start with the folder, which the caller should ensure
  // exists.
  Microsoft::WRL::ComPtr<IShellItem> destination_folder;
  if (SUCCEEDED(hr)) {
    hr =
        SHCreateItemFromParsingName(new_path.DirName().value().c_str(), nullptr,
                                    IID_PPV_ARGS(&destination_folder));
  }

  Microsoft::WRL::ComPtr<IFileOperation> file_operation;
  if (SUCCEEDED(hr)) {
    hr = CoCreateInstance(CLSID_FileOperation, nullptr, CLSCTX_INPROC_SERVER,
                          IID_PPV_ARGS(&file_operation));
  }

  if (SUCCEEDED(hr)) {
    // Don't show any UI, don't migrate security attributes (use the
    // destination's attributes), and stop on first error-retaining the original
    // failure reason.
    hr = file_operation->SetOperationFlags(
        FOF_NO_UI | FOF_NOCOPYSECURITYATTRIBS | FOFX_EARLYFAILURE);
  }

  Microsoft::WRL::ComPtr<FileOperationProgressSink> sink =
      Microsoft::WRL::Make<FileOperationProgressSink>();
  if (SUCCEEDED(hr)) {
    hr = file_operation->MoveItem(original_path.Get(), destination_folder.Get(),
                                  new_path.BaseName().value().c_str(),
                                  sink.Get());
  }

  if (SUCCEEDED(hr))
    hr = file_operation->PerformOperations();

  if (SUCCEEDED(hr))
    hr = sink->GetOperationResult();

  // Convert HRESULT to DownloadInterruptReason.
  DownloadInterruptReason interrupt_reason =
      HRESULTToDownloadInterruptReason(hr);

  if (interrupt_reason == DOWNLOAD_INTERRUPT_REASON_NONE) {
    // The operation could still have been aborted; we can't get a better reason
    // at this point, but we've got more information to go by.
    BOOL any_operations_aborted = TRUE;
    file_operation->GetAnyOperationsAborted(&any_operations_aborted);
    if (any_operations_aborted)
      interrupt_reason = DOWNLOAD_INTERRUPT_REASON_FILE_FAILED;
  } else {
    return LogInterruptReason("IFileOperation::MoveItem", hr, interrupt_reason);
  }

  return interrupt_reason;
}

}  // namespace download