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/files/file_enumerator.h"
     19 #include "base/files/file_util.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       VLOG(1) << " file not in use: " << path.value();
     85       ::CloseHandle(file);
     86     } else {
     87       PLOG(WARNING) << " 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