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