[Android] Added UMA for search by image context menu.
[chromium-blink-merge.git] / chrome / installer / util / delete_after_reboot_helper.cc
blob5131b1d58becc3b8159272aeb1268fc105673ff4
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.
13 #include "chrome/installer/util/delete_after_reboot_helper.h"
15 #include <string>
16 #include <vector>
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"
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";
29 namespace {
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);
36 // empty name isn't allowed
37 if (!(dir_name && *dir_name))
38 return false;
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;
51 return ok;
54 } // end namespace
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;
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;
71 if (!::MoveFileEx(path, NULL, flags)) {
72 PLOG(ERROR) << "Could not schedule " << path << " for deletion.";
73 return false;
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;
89 #endif
91 VLOG(1) << "Scheduled for deletion: " << path;
92 return true;
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;
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;
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;
117 // First schedule all the normal files for deletion.
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;
132 // Then recurse to all the subdirectories.
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;
148 // Now schedule the empty directory itself
149 if (!ScheduleFileSystemEntityForDeletion(dir_name))
150 LOG(ERROR) << "Failed to schedule directory for deletion: " << dir_name;
152 return true;
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());
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;
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));
191 return S_OK;
194 void StringArrayToMultiSZBytes(const std::vector<PendingMove>& strings,
195 std::vector<char>* buffer) {
196 DCHECK(buffer);
197 buffer->clear();
199 if (strings.empty()) {
200 // Leave buffer empty if we have no strings.
201 return;
204 size_t total_wchars = 0;
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.
213 total_wchars++; // Space for the extra terminating null char.
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;
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;
236 // We should never run off the end while in this loop.
237 DCHECK(write_pointer < end_pointer);
239 *write_pointer = L'\0'; // Explicitly set the final null char.
240 DCHECK(++write_pointer == end_pointer);
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;
257 short_path.resize(length);
258 return short_path;
261 HRESULT GetPendingMovesValue(
262 std::vector<PendingMove>* pending_moves) {
263 DCHECK(pending_moves);
264 pending_moves->clear();
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);
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);
284 if (result == ERROR_FILE_NOT_FOUND) {
285 // No pending moves were found.
286 return HRESULT_FROM_WIN32(result);
288 if (result != ERROR_MORE_DATA) {
289 // That was unexpected.
290 DLOG(ERROR) << "Unexpected result from RegQueryValueEx: " << result;
291 return HRESULT_FROM_WIN32(result);
293 if (type != REG_MULTI_SZ) {
294 DLOG(ERROR) << "Found PendingRename value of unexpected type.";
295 return E_UNEXPECTED;
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;
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);
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;
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.
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);
331 // Get the short path name of the entry.
332 std::wstring short_match_path(GetShortPathName(match_path.c_str()));
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);
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;
349 if (FAILED(hr)) {
350 // Couldn't read the key or the key was corrupt.
351 return false;
354 // Get the short form of |directory| and use that to match.
355 std::wstring short_directory(GetShortPathName(directory));
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);
367 if (strings_to_keep.size() == pending_moves.size()) {
368 // Nothing to remove, return true.
369 return true;
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;
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);
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);