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