delete_after_reboot_helper.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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. 12 13#include "chrome/installer/util/delete_after_reboot_helper.h" 14 15#include <string> 16#include <vector> 17 18#include "base/file_util.h" 19#include "base/win/registry.h" 20#include "base/string_util.h" 21 22// The moves-pending-reboot is a MULTISZ registry key in the HKLM part of the 23// registry. 24const wchar_t kSessionManagerKey[] = 25 L"SYSTEM\\CurrentControlSet\\Control\\Session Manager"; 26const wchar_t kPendingFileRenameOps[] = L"PendingFileRenameOperations"; 27 28namespace { 29 30// Returns true if this directory name is 'safe' for deletion (doesn't contain 31// "..", doesn't specify a drive root) 32bool IsSafeDirectoryNameForDeletion(const wchar_t* dir_name) { 33 DCHECK(dir_name); 34 35 // empty name isn't allowed 36 if (!(dir_name && *dir_name)) 37 return false; 38 39 // require a character other than \/:. after the last : 40 // disallow anything with ".." 41 bool ok = false; 42 for (const wchar_t* s = dir_name; *s; ++s) { 43 if (*s != L'\\' && *s != L'/' && *s != L':' && *s != L'.') 44 ok = true; 45 if (*s == L'.' && s > dir_name && *(s - 1) == L'.') 46 return false; 47 if (*s == L':') 48 ok = false; 49 } 50 return ok; 51} 52 53} // end namespace 54 55// Must only be called for regular files or directories that will be empty. 56bool ScheduleFileSystemEntityForDeletion(const wchar_t* path) { 57 // Check if the file exists, return false if not. 58 WIN32_FILE_ATTRIBUTE_DATA attrs = {0}; 59 if (!::GetFileAttributesEx(path, ::GetFileExInfoStandard, &attrs)) { 60 PLOG(WARNING) << path << " does not exist."; 61 return false; 62 } 63 64 DWORD flags = MOVEFILE_DELAY_UNTIL_REBOOT; 65 if (!file_util::DirectoryExists(base::FilePath::FromWStringHack(path))) { 66 // This flag valid only for files 67 flags |= MOVEFILE_REPLACE_EXISTING; 68 } 69 70 if (!::MoveFileEx(path, NULL, flags)) { 71 PLOG(ERROR) << "Could not schedule " << path << " for deletion."; 72 return false; 73 } 74 75#ifndef NDEBUG 76 // Useful debugging code to track down what files are in use. 77 if (flags & MOVEFILE_REPLACE_EXISTING) { 78 // Attempt to open the file exclusively. 79 HANDLE file = ::CreateFileW(path, GENERIC_READ | GENERIC_WRITE, 0, NULL, 80 OPEN_EXISTING, 0, NULL); 81 if (file != INVALID_HANDLE_VALUE) { 82 LOG(INFO) << " file not in use: " << path; 83 ::CloseHandle(file); 84 } else { 85 PLOG(INFO) << " file in use (or not found?): " << path; 86 } 87 } 88#endif 89 90 VLOG(1) << "Scheduled for deletion: " << path; 91 return true; 92} 93 94bool ScheduleDirectoryForDeletion(const wchar_t* dir_name) { 95 if (!IsSafeDirectoryNameForDeletion(dir_name)) { 96 LOG(ERROR) << "Unsafe directory name for deletion: " << dir_name; 97 return false; 98 } 99 100 // Make sure the directory exists (it is ok if it doesn't) 101 DWORD dir_attributes = ::GetFileAttributes(dir_name); 102 if (dir_attributes == INVALID_FILE_ATTRIBUTES) { 103 if (::GetLastError() == ERROR_FILE_NOT_FOUND) { 104 return true; // Ok if directory is missing 105 } else { 106 PLOG(ERROR) << "Could not GetFileAttributes for " << dir_name; 107 return false; 108 } 109 } 110 // Confirm it is a directory 111 if (!(dir_attributes & FILE_ATTRIBUTE_DIRECTORY)) { 112 LOG(ERROR) << "Scheduled directory is not a directory: " << dir_name; 113 return false; 114 } 115 116 // First schedule all the normal files for deletion. 117 { 118 bool success = true; 119 file_util::FileEnumerator file_enum(base::FilePath(dir_name), false, 120 file_util::FileEnumerator::FILES); 121 for (base::FilePath file = file_enum.Next(); !file.empty(); 122 file = file_enum.Next()) { 123 success = ScheduleFileSystemEntityForDeletion(file.value().c_str()); 124 if (!success) { 125 LOG(ERROR) << "Failed to schedule file for deletion: " << file.value(); 126 return false; 127 } 128 } 129 } 130 131 // Then recurse to all the subdirectories. 132 { 133 bool success = true; 134 file_util::FileEnumerator dir_enum(base::FilePath(dir_name), false, 135 file_util::FileEnumerator::DIRECTORIES); 136 for (base::FilePath sub_dir = dir_enum.Next(); !sub_dir.empty(); 137 sub_dir = dir_enum.Next()) { 138 success = ScheduleDirectoryForDeletion(sub_dir.value().c_str()); 139 if (!success) { 140 LOG(ERROR) << "Failed to schedule subdirectory for deletion: " 141 << sub_dir.value(); 142 return false; 143 } 144 } 145 } 146 147 // Now schedule the empty directory itself 148 if (!ScheduleFileSystemEntityForDeletion(dir_name)) 149 LOG(ERROR) << "Failed to schedule directory for deletion: " << dir_name; 150 151 return true; 152} 153 154// Converts the strings found in |buffer| to a list of wstrings that is returned 155// in |value|. 156// |buffer| points to a series of pairs of null-terminated wchar_t strings 157// followed by a terminating null character. 158// |byte_count| is the length of |buffer| in bytes. 159// |value| is a pointer to an empty vector of wstrings. On success, this vector 160// contains all of the strings extracted from |buffer|. 161// Returns S_OK on success, E_INVALIDARG if buffer does not meet tha above 162// specification. 163HRESULT MultiSZBytesToStringArray(const char* buffer, size_t byte_count, 164 std::vector<PendingMove>* value) { 165 DCHECK(buffer); 166 DCHECK(value); 167 DCHECK(value->empty()); 168 169 DWORD data_len = byte_count / sizeof(wchar_t); 170 const wchar_t* data = reinterpret_cast<const wchar_t*>(buffer); 171 const wchar_t* data_end = data + data_len; 172 if (data_len > 1) { 173 // must be terminated by two null characters 174 if (data[data_len - 1] != 0 || data[data_len - 2] != 0) { 175 DLOG(ERROR) << "Invalid MULTI_SZ found."; 176 return E_INVALIDARG; 177 } 178 179 // put null-terminated strings into arrays 180 while (data < data_end) { 181 std::wstring str_from(data); 182 data += str_from.length() + 1; 183 if (data < data_end) { 184 std::wstring str_to(data); 185 data += str_to.length() + 1; 186 value->push_back(std::make_pair(str_from, str_to)); 187 } 188 } 189 } 190 return S_OK; 191} 192 193void StringArrayToMultiSZBytes(const std::vector<PendingMove>& strings, 194 std::vector<char>* buffer) { 195 DCHECK(buffer); 196 buffer->clear(); 197 198 if (strings.empty()) { 199 // Leave buffer empty if we have no strings. 200 return; 201 } 202 203 size_t total_wchars = 0; 204 { 205 std::vector<PendingMove>::const_iterator iter(strings.begin()); 206 for (; iter != strings.end(); ++iter) { 207 total_wchars += iter->first.length(); 208 total_wchars++; // Space for the null char. 209 total_wchars += iter->second.length(); 210 total_wchars++; // Space for the null char. 211 } 212 total_wchars++; // Space for the extra terminating null char. 213 } 214 215 size_t total_length = total_wchars * sizeof(wchar_t); 216 buffer->resize(total_length); 217 wchar_t* write_pointer = reinterpret_cast<wchar_t*>(&((*buffer)[0])); 218 // Keep an end pointer around for sanity checking. 219 wchar_t* end_pointer = write_pointer + total_wchars; 220 221 std::vector<PendingMove>::const_iterator copy_iter(strings.begin()); 222 for (; copy_iter != strings.end() && write_pointer < end_pointer; 223 copy_iter++) { 224 // First copy the source string. 225 size_t string_length = copy_iter->first.length() + 1; 226 memcpy(write_pointer, copy_iter->first.c_str(), 227 string_length * sizeof(wchar_t)); 228 write_pointer += string_length; 229 // Now copy the destination string. 230 string_length = copy_iter->second.length() + 1; 231 memcpy(write_pointer, copy_iter->second.c_str(), 232 string_length * sizeof(wchar_t)); 233 write_pointer += string_length; 234 235 // We should never run off the end while in this loop. 236 DCHECK(write_pointer < end_pointer); 237 } 238 *write_pointer = L'\0'; // Explicitly set the final null char. 239 DCHECK(++write_pointer == end_pointer); 240} 241 242std::wstring GetShortPathName(const wchar_t* path) { 243 std::wstring short_path; 244 DWORD length = GetShortPathName(path, WriteInto(&short_path, MAX_PATH), 245 MAX_PATH); 246 DWORD last_error = ::GetLastError(); 247 DLOG_IF(WARNING, length == 0 && last_error != ERROR_PATH_NOT_FOUND) 248 << __FUNCTION__ << " gle=" << last_error; 249 if (length == 0) { 250 // GetShortPathName fails if the path is no longer present. Instead of 251 // returning an empty string, just return the original string. This will 252 // serve our purposes. 253 return path; 254 } 255 256 short_path.resize(length); 257 return short_path; 258} 259 260HRESULT GetPendingMovesValue( 261 std::vector<PendingMove>* pending_moves) { 262 DCHECK(pending_moves); 263 pending_moves->clear(); 264 265 // Get the current value of the key 266 // If the Key is missing, that's totally acceptable. 267 base::win::RegKey session_manager_key(HKEY_LOCAL_MACHINE, kSessionManagerKey, 268 KEY_QUERY_VALUE); 269 HKEY session_manager_handle = session_manager_key.Handle(); 270 if (!session_manager_handle) 271 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); 272 273 // The base::RegKey Read code squashes the return code from 274 // ReqQueryValueEx, we have to do things ourselves: 275 DWORD buffer_size = 0; 276 std::vector<char> buffer; 277 buffer.resize(1); 278 DWORD type; 279 DWORD result = RegQueryValueEx(session_manager_handle, kPendingFileRenameOps, 280 0, &type, reinterpret_cast<BYTE*>(&buffer[0]), 281 &buffer_size); 282 283 if (result == ERROR_FILE_NOT_FOUND) { 284 // No pending moves were found. 285 return HRESULT_FROM_WIN32(result); 286 } 287 if (result != ERROR_MORE_DATA) { 288 // That was unexpected. 289 DLOG(ERROR) << "Unexpected result from RegQueryValueEx: " << result; 290 return HRESULT_FROM_WIN32(result); 291 } 292 if (type != REG_MULTI_SZ) { 293 DLOG(ERROR) << "Found PendingRename value of unexpected type."; 294 return E_UNEXPECTED; 295 } 296 if (buffer_size % 2) { 297 // The buffer size should be an even number (since we expect wchar_ts). 298 // If this is not the case, fail here. 299 DLOG(ERROR) << "Corrupt PendingRename value."; 300 return E_UNEXPECTED; 301 } 302 303 // There are pending file renames. Read them in. 304 buffer.resize(buffer_size); 305 result = RegQueryValueEx(session_manager_handle, kPendingFileRenameOps, 306 0, &type, reinterpret_cast<LPBYTE>(&buffer[0]), 307 &buffer_size); 308 if (result != ERROR_SUCCESS) { 309 DLOG(ERROR) << "Failed to read from " << kPendingFileRenameOps; 310 return HRESULT_FROM_WIN32(result); 311 } 312 313 // We now have a buffer of bytes that is actually a sequence of 314 // null-terminated wchar_t strings terminated by an additional null character. 315 // Stick this into a vector of strings for clarity. 316 HRESULT hr = MultiSZBytesToStringArray(&buffer[0], buffer.size(), 317 pending_moves); 318 return hr; 319} 320 321bool MatchPendingDeletePath(const std::wstring& short_form_needle, 322 const std::wstring& reg_path) { 323 std::wstring match_path(reg_path); // Stores the path stored in each entry. 324 325 // First chomp the prefix since that will mess up GetShortPathName. 326 std::wstring prefix(L"\\??\\"); 327 if (StartsWith(match_path, prefix, false)) 328 match_path = match_path.substr(4); 329 330 // Get the short path name of the entry. 331 std::wstring short_match_path(GetShortPathName(match_path.c_str())); 332 333 // Now compare the paths. If it isn't one we're looking for, add it 334 // to the list to keep. 335 return StartsWith(short_match_path, short_form_needle, false); 336} 337 338// Removes all pending moves for the given |directory| and any contained 339// files or subdirectories. Returns true on success 340bool RemoveFromMovesPendingReboot(const wchar_t* directory) { 341 DCHECK(directory); 342 std::vector<PendingMove> pending_moves; 343 HRESULT hr = GetPendingMovesValue(&pending_moves); 344 if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) { 345 // No pending moves, nothing to do. 346 return true; 347 } 348 if (FAILED(hr)) { 349 // Couldn't read the key or the key was corrupt. 350 return false; 351 } 352 353 // Get the short form of |directory| and use that to match. 354 std::wstring short_directory(GetShortPathName(directory)); 355 356 std::vector<PendingMove> strings_to_keep; 357 for (std::vector<PendingMove>::const_iterator iter(pending_moves.begin()); 358 iter != pending_moves.end(); iter++) { 359 if (!MatchPendingDeletePath(short_directory, iter->first)) { 360 // This doesn't match the deletions we are looking for. Preserve 361 // this string pair, making sure that it is in fact a pair. 362 strings_to_keep.push_back(*iter); 363 } 364 } 365 366 if (strings_to_keep.size() == pending_moves.size()) { 367 // Nothing to remove, return true. 368 return true; 369 } 370 371 // Write the key back into a buffer. 372 base::win::RegKey session_manager_key(HKEY_LOCAL_MACHINE, kSessionManagerKey, 373 KEY_CREATE_SUB_KEY | KEY_SET_VALUE); 374 if (!session_manager_key.Handle()) { 375 // Couldn't open / create the key. 376 LOG(ERROR) << "Failed to open session manager key for writing."; 377 return false; 378 } 379 380 if (strings_to_keep.size() <= 1) { 381 // We have only the trailing NULL string. Don't bother writing that. 382 return (session_manager_key.DeleteValue(kPendingFileRenameOps) == 383 ERROR_SUCCESS); 384 } 385 std::vector<char> buffer; 386 StringArrayToMultiSZBytes(strings_to_keep, &buffer); 387 DCHECK_GT(buffer.size(), 0U); 388 if (buffer.empty()) 389 return false; 390 return (session_manager_key.WriteValue(kPendingFileRenameOps, &buffer[0], 391 buffer.size(), REG_MULTI_SZ) == ERROR_SUCCESS); 392} 393