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.
25const wchar_t kSessionManagerKey[] =
26    L"SYSTEM\\CurrentControlSet\\Control\\Session Manager";
27const wchar_t kPendingFileRenameOps[] = L"PendingFileRenameOperations";
28
29namespace {
30
31// Returns true if this directory name is 'safe' for deletion (doesn't contain
32// "..", doesn't specify a drive root)
33bool 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.
56bool 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
96bool 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.
168HRESULT 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
198void 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
247base::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
266HRESULT 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
326bool 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
346bool 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