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