Home | History | Annotate | Download | only in util
      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