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.
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
10 // This implementation really is not fast, so do not use it where that will
13 #include "chrome/installer/util/delete_after_reboot_helper.h"
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"
23 // The moves-pending-reboot is a MULTISZ registry key in the HKLM part of the
25 const wchar_t kSessionManagerKey
[] =
26 L
"SYSTEM\\CurrentControlSet\\Control\\Session Manager";
27 const wchar_t kPendingFileRenameOps
[] = L
"PendingFileRenameOperations";
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
38 // require a character other than \/:. after the last :
39 // disallow anything with ".."
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
'.')
45 if (*s
== L
'.' && s
> dir_name_str
&& *(s
- 1) == L
'.')
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.";
65 DWORD flags
= MOVEFILE_DELAY_UNTIL_REBOOT
;
66 if (!base::DirectoryExists(path
)) {
67 // This flag valid only for files
68 flags
|= MOVEFILE_REPLACE_EXISTING
;
71 if (!::MoveFileEx(path
.value().c_str(), NULL
, flags
)) {
72 PLOG(ERROR
) << "Could not schedule " << path
.value() << " for deletion.";
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();
87 PLOG(WARNING
) << " file in use (or not found?): " << path
.value();
92 VLOG(1) << "Scheduled for deletion: " << path
.value();
96 bool ScheduleDirectoryForDeletion(const base::FilePath
& dir_name
) {
97 if (!IsSafeDirectoryNameForDeletion(dir_name
)) {
98 LOG(ERROR
) << "Unsafe directory name for deletion: " << dir_name
.value();
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
108 PLOG(ERROR
) << "Could not GetFileAttributes for " << dir_name
.value();
112 // Confirm it is a directory
113 if (!(dir_attributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
114 LOG(ERROR
) << "Scheduled directory is not a directory: "
119 // First schedule all the normal files for deletion.
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
);
128 LOG(ERROR
) << "Failed to schedule file for deletion: " << file
.value();
134 // Then recurse to all the subdirectories.
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
);
143 LOG(ERROR
) << "Failed to schedule subdirectory for deletion: "
150 // Now schedule the empty directory itself
151 if (!ScheduleFileSystemEntityForDeletion(dir_name
)) {
152 LOG(ERROR
) << "Failed to schedule directory for deletion: "
159 // Converts the strings found in |buffer| to a list of wstrings that is returned
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
168 HRESULT
MultiSZBytesToStringArray(const char* buffer
, size_t byte_count
,
169 std::vector
<PendingMove
>* value
) {
172 DCHECK(value
->empty());
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
;
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.";
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
));
198 void StringArrayToMultiSZBytes(const std::vector
<PendingMove
>& strings
,
199 std::vector
<char>* buffer
) {
203 if (strings
.empty()) {
204 // Leave buffer empty if we have no strings.
208 size_t total_wchars
= 0;
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.
217 total_wchars
++; // Space for the extra terminating null char.
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
;
226 std::vector
<PendingMove
>::const_iterator
copy_iter(strings
.begin());
227 for (; copy_iter
!= strings
.end() && write_pointer
< end_pointer
;
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
;
240 // We should never run off the end while in this loop.
241 DCHECK(write_pointer
< end_pointer
);
243 *write_pointer
= L
'\0'; // Explicitly set the final null char.
244 DCHECK(++write_pointer
== end_pointer
);
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
),
252 DWORD last_error
= ::GetLastError();
253 DLOG_IF(WARNING
, length
== 0 && last_error
!= ERROR_PATH_NOT_FOUND
)
254 << __FUNCTION__
<< " gle=" << last_error
;
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.
262 short_path
.resize(length
);
263 return base::FilePath(short_path
);
266 HRESULT
GetPendingMovesValue(std::vector
<PendingMove
>* pending_moves
) {
267 DCHECK(pending_moves
);
268 pending_moves
->clear();
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
,
274 HKEY session_manager_handle
= session_manager_key
.Handle();
275 if (!session_manager_handle
)
276 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND
);
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
;
284 DWORD result
= RegQueryValueEx(session_manager_handle
, kPendingFileRenameOps
,
285 0, &type
, reinterpret_cast<BYTE
*>(&buffer
[0]),
288 if (result
== ERROR_FILE_NOT_FOUND
) {
289 // No pending moves were found.
290 return HRESULT_FROM_WIN32(result
);
292 if (result
!= ERROR_MORE_DATA
) {
293 // That was unexpected.
294 DLOG(ERROR
) << "Unexpected result from RegQueryValueEx: " << result
;
295 return HRESULT_FROM_WIN32(result
);
297 if (type
!= REG_MULTI_SZ
) {
298 DLOG(ERROR
) << "Found PendingRename value of unexpected type.";
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.";
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]),
313 if (result
!= ERROR_SUCCESS
) {
314 DLOG(ERROR
) << "Failed to read from " << kPendingFileRenameOps
;
315 return HRESULT_FROM_WIN32(result
);
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(),
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());
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);
336 // Get the short path name of the entry.
337 base::FilePath
short_match_path(GetShortPathName(base::FilePath(match_path
)));
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);
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.
354 // Couldn't read the key or the key was corrupt.
358 // Get the short form of |directory| and use that to match.
359 base::FilePath
short_directory(GetShortPathName(directory
));
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
);
372 if (strings_to_keep
.size() == pending_moves
.size()) {
373 // Nothing to remove, return true.
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.";
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
) ==
391 std::vector
<char> buffer
;
392 StringArrayToMultiSZBytes(strings_to_keep
, &buffer
);
393 DCHECK_GT(buffer
.size(), 0U);
396 return (session_manager_key
.WriteValue(kPendingFileRenameOps
, &buffer
[0],
397 buffer
.size(), REG_MULTI_SZ
) == ERROR_SUCCESS
);