Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / installer / util / delete_after_reboot_helper.cc
blobbc7cd2bf4f3b74a65c528acdbd25ad08a1bbb69e
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 //
5 // This file defines helper methods used to schedule files for deletion
6 // on next reboot. The code here is heavily borrowed and simplified from
7 // http://code.google.com/p/omaha/source/browse/trunk/common/file.cc and
8 // http://code.google.com/p/omaha/source/browse/trunk/common/utils.cc
9 //
10 // This implementation really is not fast, so do not use it where that will
11 // matter.
13 #include "chrome/installer/util/delete_after_reboot_helper.h"
15 #include <string>
16 #include <vector>
18 #include "base/files/file_enumerator.h"
19 #include "base/files/file_util.h"
20 #include "base/numerics/safe_conversions.h"
21 #include "base/strings/string_util.h"
22 #include "base/win/registry.h"
24 // The moves-pending-reboot is a MULTISZ registry key in the HKLM part of the
25 // registry.
26 const wchar_t kSessionManagerKey[] =
27 L"SYSTEM\\CurrentControlSet\\Control\\Session Manager";
28 const wchar_t kPendingFileRenameOps[] = L"PendingFileRenameOperations";
30 namespace {
32 // Returns true if this directory name is 'safe' for deletion (doesn't contain
33 // "..", doesn't specify a drive root)
34 bool IsSafeDirectoryNameForDeletion(const base::FilePath& dir_name) {
35 // empty name isn't allowed
36 if (dir_name.empty())
37 return false;
39 // require a character other than \/:. after the last :
40 // disallow anything with ".."
41 bool ok = false;
42 const wchar_t* dir_name_str = dir_name.value().c_str();
43 for (const wchar_t* s = dir_name_str; *s; ++s) {
44 if (*s != L'\\' && *s != L'/' && *s != L':' && *s != L'.')
45 ok = true;
46 if (*s == L'.' && s > dir_name_str && *(s - 1) == L'.')
47 return false;
48 if (*s == L':')
49 ok = false;
51 return ok;
54 } // end namespace
56 // Must only be called for regular files or directories that will be empty.
57 bool ScheduleFileSystemEntityForDeletion(const base::FilePath& path) {
58 // Check if the file exists, return false if not.
59 WIN32_FILE_ATTRIBUTE_DATA attrs = {0};
60 if (!::GetFileAttributesEx(path.value().c_str(),
61 ::GetFileExInfoStandard, &attrs)) {
62 PLOG(WARNING) << path.value() << " does not exist.";
63 return false;
66 DWORD flags = MOVEFILE_DELAY_UNTIL_REBOOT;
67 if (!base::DirectoryExists(path)) {
68 // This flag valid only for files
69 flags |= MOVEFILE_REPLACE_EXISTING;
72 if (!::MoveFileEx(path.value().c_str(), NULL, flags)) {
73 PLOG(ERROR) << "Could not schedule " << path.value() << " for deletion.";
74 return false;
77 #ifndef NDEBUG
78 // Useful debugging code to track down what files are in use.
79 if (flags & MOVEFILE_REPLACE_EXISTING) {
80 // Attempt to open the file exclusively.
81 HANDLE file = ::CreateFileW(path.value().c_str(),
82 GENERIC_READ | GENERIC_WRITE, 0, NULL,
83 OPEN_EXISTING, 0, NULL);
84 if (file != INVALID_HANDLE_VALUE) {
85 VLOG(1) << " file not in use: " << path.value();
86 ::CloseHandle(file);
87 } else {
88 PLOG(WARNING) << " file in use (or not found?): " << path.value();
91 #endif
93 VLOG(1) << "Scheduled for deletion: " << path.value();
94 return true;
97 bool ScheduleDirectoryForDeletion(const base::FilePath& dir_name) {
98 if (!IsSafeDirectoryNameForDeletion(dir_name)) {
99 LOG(ERROR) << "Unsafe directory name for deletion: " << dir_name.value();
100 return false;
103 // Make sure the directory exists (it is ok if it doesn't)
104 DWORD dir_attributes = ::GetFileAttributes(dir_name.value().c_str());
105 if (dir_attributes == INVALID_FILE_ATTRIBUTES) {
106 if (::GetLastError() == ERROR_FILE_NOT_FOUND) {
107 return true; // Ok if directory is missing
108 } else {
109 PLOG(ERROR) << "Could not GetFileAttributes for " << dir_name.value();
110 return false;
113 // Confirm it is a directory
114 if (!(dir_attributes & FILE_ATTRIBUTE_DIRECTORY)) {
115 LOG(ERROR) << "Scheduled directory is not a directory: "
116 << dir_name.value();
117 return false;
120 // First schedule all the normal files for deletion.
122 bool success = true;
123 base::FileEnumerator file_enum(dir_name, false,
124 base::FileEnumerator::FILES);
125 for (base::FilePath file = file_enum.Next(); !file.empty();
126 file = file_enum.Next()) {
127 success = ScheduleFileSystemEntityForDeletion(file);
128 if (!success) {
129 LOG(ERROR) << "Failed to schedule file for deletion: " << file.value();
130 return false;
135 // Then recurse to all the subdirectories.
137 bool success = true;
138 base::FileEnumerator dir_enum(dir_name, false,
139 base::FileEnumerator::DIRECTORIES);
140 for (base::FilePath sub_dir = dir_enum.Next(); !sub_dir.empty();
141 sub_dir = dir_enum.Next()) {
142 success = ScheduleDirectoryForDeletion(sub_dir);
143 if (!success) {
144 LOG(ERROR) << "Failed to schedule subdirectory for deletion: "
145 << sub_dir.value();
146 return false;
151 // Now schedule the empty directory itself
152 if (!ScheduleFileSystemEntityForDeletion(dir_name)) {
153 LOG(ERROR) << "Failed to schedule directory for deletion: "
154 << dir_name.value();
157 return true;
160 // Converts the strings found in |buffer| to a list of wstrings that is returned
161 // in |value|.
162 // |buffer| points to a series of pairs of null-terminated wchar_t strings
163 // followed by a terminating null character.
164 // |byte_count| is the length of |buffer| in bytes.
165 // |value| is a pointer to an empty vector of wstrings. On success, this vector
166 // contains all of the strings extracted from |buffer|.
167 // Returns S_OK on success, E_INVALIDARG if buffer does not meet tha above
168 // specification.
169 HRESULT MultiSZBytesToStringArray(const char* buffer, size_t byte_count,
170 std::vector<PendingMove>* value) {
171 DCHECK(buffer);
172 DCHECK(value);
173 DCHECK(value->empty());
175 DWORD data_len = byte_count / sizeof(wchar_t);
176 const wchar_t* data = reinterpret_cast<const wchar_t*>(buffer);
177 const wchar_t* data_end = data + data_len;
178 if (data_len > 1) {
179 // must be terminated by two null characters
180 if (data[data_len - 1] != 0 || data[data_len - 2] != 0) {
181 DLOG(ERROR) << "Invalid MULTI_SZ found.";
182 return E_INVALIDARG;
185 // put null-terminated strings into arrays
186 while (data < data_end) {
187 std::wstring str_from(data);
188 data += str_from.length() + 1;
189 if (data < data_end) {
190 std::wstring str_to(data);
191 data += str_to.length() + 1;
192 value->push_back(std::make_pair(str_from, str_to));
196 return S_OK;
199 void StringArrayToMultiSZBytes(const std::vector<PendingMove>& strings,
200 std::vector<char>* buffer) {
201 DCHECK(buffer);
202 buffer->clear();
204 if (strings.empty()) {
205 // Leave buffer empty if we have no strings.
206 return;
209 size_t total_wchars = 0;
211 std::vector<PendingMove>::const_iterator iter(strings.begin());
212 for (; iter != strings.end(); ++iter) {
213 total_wchars += iter->first.length();
214 total_wchars++; // Space for the null char.
215 total_wchars += iter->second.length();
216 total_wchars++; // Space for the null char.
218 total_wchars++; // Space for the extra terminating null char.
221 size_t total_length = total_wchars * sizeof(wchar_t);
222 buffer->resize(total_length);
223 wchar_t* write_pointer = reinterpret_cast<wchar_t*>(&((*buffer)[0]));
224 // Keep an end pointer around for sanity checking.
225 wchar_t* end_pointer = write_pointer + total_wchars;
227 std::vector<PendingMove>::const_iterator copy_iter(strings.begin());
228 for (; copy_iter != strings.end() && write_pointer < end_pointer;
229 copy_iter++) {
230 // First copy the source string.
231 size_t string_length = copy_iter->first.length() + 1;
232 memcpy(write_pointer, copy_iter->first.c_str(),
233 string_length * sizeof(wchar_t));
234 write_pointer += string_length;
235 // Now copy the destination string.
236 string_length = copy_iter->second.length() + 1;
237 memcpy(write_pointer, copy_iter->second.c_str(),
238 string_length * sizeof(wchar_t));
239 write_pointer += string_length;
241 // We should never run off the end while in this loop.
242 DCHECK(write_pointer < end_pointer);
244 *write_pointer = L'\0'; // Explicitly set the final null char.
245 DCHECK(++write_pointer == end_pointer);
248 base::FilePath GetShortPathName(const base::FilePath& path) {
249 std::wstring short_path;
250 DWORD length = GetShortPathName(path.value().c_str(),
251 base::WriteInto(&short_path, MAX_PATH),
252 MAX_PATH);
253 DWORD last_error = ::GetLastError();
254 DLOG_IF(WARNING, length == 0 && last_error != ERROR_PATH_NOT_FOUND)
255 << __FUNCTION__ << " gle=" << last_error;
256 if (length == 0) {
257 // GetShortPathName fails if the path is no longer present. Instead of
258 // returning an empty string, just return the original string. This will
259 // serve our purposes.
260 return path;
263 short_path.resize(length);
264 return base::FilePath(short_path);
267 HRESULT GetPendingMovesValue(std::vector<PendingMove>* pending_moves) {
268 DCHECK(pending_moves);
269 pending_moves->clear();
271 // Get the current value of the key
272 // If the Key is missing, that's totally acceptable.
273 base::win::RegKey session_manager_key(HKEY_LOCAL_MACHINE, kSessionManagerKey,
274 KEY_QUERY_VALUE);
275 HKEY session_manager_handle = session_manager_key.Handle();
276 if (!session_manager_handle)
277 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
279 // The base::RegKey Read code squashes the return code from
280 // ReqQueryValueEx, we have to do things ourselves:
281 DWORD buffer_size = 0;
282 std::vector<char> buffer;
283 buffer.resize(1);
284 DWORD type;
285 DWORD result = RegQueryValueEx(session_manager_handle, kPendingFileRenameOps,
286 0, &type, reinterpret_cast<BYTE*>(&buffer[0]),
287 &buffer_size);
289 if (result == ERROR_FILE_NOT_FOUND) {
290 // No pending moves were found.
291 return HRESULT_FROM_WIN32(result);
293 if (result != ERROR_MORE_DATA) {
294 // That was unexpected.
295 DLOG(ERROR) << "Unexpected result from RegQueryValueEx: " << result;
296 return HRESULT_FROM_WIN32(result);
298 if (type != REG_MULTI_SZ) {
299 DLOG(ERROR) << "Found PendingRename value of unexpected type.";
300 return E_UNEXPECTED;
302 if (buffer_size % 2) {
303 // The buffer size should be an even number (since we expect wchar_ts).
304 // If this is not the case, fail here.
305 DLOG(ERROR) << "Corrupt PendingRename value.";
306 return E_UNEXPECTED;
309 // There are pending file renames. Read them in.
310 buffer.resize(buffer_size);
311 result = RegQueryValueEx(session_manager_handle, kPendingFileRenameOps,
312 0, &type, reinterpret_cast<LPBYTE>(&buffer[0]),
313 &buffer_size);
314 if (result != ERROR_SUCCESS) {
315 DLOG(ERROR) << "Failed to read from " << kPendingFileRenameOps;
316 return HRESULT_FROM_WIN32(result);
319 // We now have a buffer of bytes that is actually a sequence of
320 // null-terminated wchar_t strings terminated by an additional null character.
321 // Stick this into a vector of strings for clarity.
322 HRESULT hr = MultiSZBytesToStringArray(&buffer[0], buffer.size(),
323 pending_moves);
324 return hr;
327 bool MatchPendingDeletePath(const base::FilePath& short_form_needle,
328 const base::FilePath& reg_path) {
329 // Stores the path stored in each entry.
330 base::string16 match_path(reg_path.value());
332 // First chomp the prefix since that will mess up GetShortPathName.
333 base::StringPiece16 prefix(L"\\??\\");
334 if (base::StartsWith(match_path, prefix, base::CompareCase::SENSITIVE))
335 match_path = match_path.substr(prefix.size());
337 // Get the short path name of the entry.
338 base::FilePath short_match_path(GetShortPathName(base::FilePath(match_path)));
340 // Now compare the paths. It's a match if short_form_needle is a
341 // case-insensitive prefix of short_match_path.
342 if (short_match_path.value().size() < short_form_needle.value().size())
343 return false;
344 DWORD prefix_len =
345 base::saturated_cast<DWORD>(short_form_needle.value().size());
346 return ::CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE,
347 short_match_path.value().data(), prefix_len,
348 short_form_needle.value().data(), prefix_len) ==
349 CSTR_EQUAL;
352 // Removes all pending moves for the given |directory| and any contained
353 // files or subdirectories. Returns true on success
354 bool RemoveFromMovesPendingReboot(const base::FilePath& directory) {
355 std::vector<PendingMove> pending_moves;
356 HRESULT hr = GetPendingMovesValue(&pending_moves);
357 if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) {
358 // No pending moves, nothing to do.
359 return true;
361 if (FAILED(hr)) {
362 // Couldn't read the key or the key was corrupt.
363 return false;
366 // Get the short form of |directory| and use that to match.
367 base::FilePath short_directory(GetShortPathName(directory));
369 std::vector<PendingMove> strings_to_keep;
370 for (std::vector<PendingMove>::const_iterator iter(pending_moves.begin());
371 iter != pending_moves.end(); ++iter) {
372 base::FilePath move_path(iter->first);
373 if (!MatchPendingDeletePath(short_directory, move_path)) {
374 // This doesn't match the deletions we are looking for. Preserve
375 // this string pair, making sure that it is in fact a pair.
376 strings_to_keep.push_back(*iter);
380 if (strings_to_keep.size() == pending_moves.size()) {
381 // Nothing to remove, return true.
382 return true;
385 // Write the key back into a buffer.
386 base::win::RegKey session_manager_key(HKEY_LOCAL_MACHINE, kSessionManagerKey,
387 KEY_CREATE_SUB_KEY | KEY_SET_VALUE);
388 if (!session_manager_key.Handle()) {
389 // Couldn't open / create the key.
390 LOG(ERROR) << "Failed to open session manager key for writing.";
391 return false;
394 if (strings_to_keep.size() <= 1) {
395 // We have only the trailing NULL string. Don't bother writing that.
396 return (session_manager_key.DeleteValue(kPendingFileRenameOps) ==
397 ERROR_SUCCESS);
399 std::vector<char> buffer;
400 StringArrayToMultiSZBytes(strings_to_keep, &buffer);
401 DCHECK_GT(buffer.size(), 0U);
402 if (buffer.empty())
403 return false;
404 return (session_manager_key.WriteValue(kPendingFileRenameOps, &buffer[0],
405 buffer.size(), REG_MULTI_SZ) == ERROR_SUCCESS);