blob: da25b7dda08ddfc98dde5b5b64cfabaef1a6c4d9 [file] [log] [blame]
// Copyright 2003-2009 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========================================================================
// File handling routines
//
// Possible performance improvement: make a subclass or alternate class
// that reads an entire file into memory and fulfills read/write requests
// in memory (or equivalently, use a memory mapped file). This can greatly
// improve performance if there are files we do a lot of read/write requests on.
//
// We generally are dealing with files that can be very large and want to
// minimize memory usage, so this is not high priority
//
// this has the beginnings of asynchronous access support
//
// Unfortunately, doing asynchronous reads with FILE_FLAG_OVERLAPPED buys us
// nothing because the system cache manager enforces serial requests.
//
// Hence, we need to also use FILE_FLAG_NO_BUFFERING. this has a number of
// constraints:
// - file read/write position must be aligned on multiples of the disk sector
// size
// - file read/write length must be a multiple of the sector size
// - read/write buffer must be aligned on multiples of the disk sector size
//
// In particular, this means that we cannot write 8 bytes, for example, because
// we have to write an entire sector.
//
// Currently, the implementation only supports enough to do some simple read
// tests
//
// The general idea is code that wants to to a sequence of asynchronous actions
// will look like the following, for an example of reading multiple event
// records asynchronously:
//
// uint32 async_id = File::GetNextAsyncId()
// while (!done) {
// for (everything_to_do, e.g., for each event to read) {
// call File::Read to read items needed;
// returns TR_E_FILE_ASYNC_PENDING if queued; or returns data if done
// process the item (e.g., event) if desired
// }
// call some routine to process pending completions; initiate delayed action
// }
// call some cleanup routine
#include "omaha/common/file.h"
#include <algorithm>
#include "base/scoped_ptr.h"
#include "omaha/common/app_util.h"
#include "omaha/common/const_config.h"
#include "omaha/common/debug.h"
#include "omaha/common/error.h"
#include "omaha/common/logging.h"
#include "omaha/common/path.h"
#include "omaha/common/reg_key.h"
#include "omaha/common/scoped_any.h"
#include "omaha/common/scoped_ptr_address.h"
#include "omaha/common/string.h"
#include "omaha/common/system.h"
#include "omaha/common/timer.h"
#include "omaha/common/utils.h"
namespace omaha {
// Constants
const uint32 kZeroSize = 4096; // Buffer size used for clearing data in a file.
// The moves-pending-reboot is a MULTISZ registry key in the HKLM part of the
// registry.
static const TCHAR* kSessionManagerKey =
_T("HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager");
static const TCHAR* kPendingFileRenameOps = _T("PendingFileRenameOperations");
File::File()
: handle_(INVALID_HANDLE_VALUE), read_only_(false), sequence_id_(0) {
}
File::~File() {
if (handle_ != INVALID_HANDLE_VALUE) {
VERIFY1(SUCCEEDED(Close()));
}
}
// open for reading only if write == false, otherwise both reading and writing
// allow asynchronous operations if async == true. Use this function when you
// need exclusive access to the file.
HRESULT File::Open(const TCHAR* file_name, bool write, bool async) {
return OpenShareMode(file_name, write, async, 0);
}
// Allows specifying a sharing mode such as FILE_SHARE_READ. Otherwise,
// this is identical to File::Open().
HRESULT File::OpenShareMode(const TCHAR* file_name,
bool write,
bool async,
DWORD share_mode) {
ASSERT1(file_name && *file_name);
ASSERT1(handle_ == INVALID_HANDLE_VALUE);
VERIFY1(!async);
file_name_ = file_name;
// there are restrictions on what we can do if using FILE_FLAG_NO_BUFFERING
// if (!buffer) { flags |= FILE_FLAG_NO_BUFFERING; }
// FILE_FLAG_WRITE_THROUGH
// how efficient is NTFS encryption? FILE_ATTRIBUTE_ENCRYPTED
// FILE_ATTRIBUTE_TEMPORARY
// FILE_FLAG_RANDOM_ACCESS
// FILE_FLAG_SEQUENTIAL_SCAN
handle_ = ::CreateFile(file_name,
write ? (FILE_WRITE_DATA |
FILE_WRITE_ATTRIBUTES |
FILE_READ_DATA) : FILE_READ_DATA,
share_mode,
NULL,
write ? OPEN_ALWAYS : OPEN_EXISTING,
FILE_FLAG_RANDOM_ACCESS,
NULL);
if (handle_ == INVALID_HANDLE_VALUE) {
HRESULT hr = HRESULTFromLastError();
UTIL_LOG(LEVEL_ERROR,
(_T("[File::OpenShareMode - CreateFile failed][%s][%d][%d][0x%x]"),
file_name, write, async, hr));
return hr;
}
// This attribute is not supported directly by the CreateFile function.
if (!::SetFileAttributes(file_name, FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)) {
HRESULT hr = HRESULTFromLastError();
UTIL_LOG(LEVEL_ERROR,
(_T("[File::OpenShareMode - SetFileAttributes failed][0x%x]"), hr));
return hr;
}
read_only_ = !write;
pos_ = 0;
return S_OK;
}
bool File::Exists(const TCHAR* file_name) {
ASSERT1(file_name && *file_name);
ASSERT1(lstrlen(file_name) > 0);
// NOTE: This is the fastest implementation I found. The results were:
// CreateFile 1783739 avg ticks/call
// FindFirstFile 634148 avg ticks/call
// GetFileAttributes 428714 avg ticks/call
// GetFileAttributesEx 396324 avg ticks/call
WIN32_FILE_ATTRIBUTE_DATA attrs = {0};
return 0 != ::GetFileAttributesEx(file_name, ::GetFileExInfoStandard, &attrs);
}
bool File::IsDirectory(const TCHAR* file_name) {
ASSERT1(file_name && *file_name);
WIN32_FILE_ATTRIBUTE_DATA attrs;
SetZero(attrs);
if (!::GetFileAttributesEx(file_name, ::GetFileExInfoStandard, &attrs)) {
UTIL_LOG(LEVEL_ERROR,
(_T("[File::IsDirectory - GetFileAttributesEx failed][%s][0x%x]"),
file_name, HRESULTFromLastError()));
return false;
}
return (attrs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
}
HRESULT File::GetWildcards(const TCHAR* dir,
const TCHAR* wildcard,
std::vector<CString>* matching_paths) {
ASSERT1(dir && *dir);
ASSERT1(wildcard && *wildcard);
ASSERT1(matching_paths);
matching_paths->clear();
// Make sure directory name ends with "\"
CString directory = String_MakeEndWith(dir, _T("\\"), false);
WIN32_FIND_DATA find_data;
SetZero(find_data);
scoped_hfind hfind(::FindFirstFile(directory + wildcard, &find_data));
if (!hfind) {
HRESULT hr = HRESULTFromLastError();
UTIL_LOG(L5, (_T("[File::GetWildcards - FindFirstFile failed][0x%x]"), hr));
return hr;
}
do {
if (find_data.dwFileAttributes == FILE_ATTRIBUTE_NORMAL ||
!(find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
CString to_file(directory + find_data.cFileName);
matching_paths->push_back(to_file);
}
} while (::FindNextFile(get(hfind), &find_data));
HRESULT hr = HRESULTFromLastError();
if (hr != HRESULT_FROM_WIN32(ERROR_NO_MORE_FILES)) {
UTIL_LOG(LEVEL_ERROR,
(_T("[File::GetWildcards - FindNextFile failed][0x%x]"), hr));
return hr;
}
return S_OK;
}
// returns error if cannot remove
// returns success if removed or already removed
HRESULT File::Remove(const TCHAR* file_name) {
ASSERT1(file_name && *file_name);
if (!Exists(file_name)) {
return S_OK;
}
if (!::DeleteFile(file_name)) {
return HRESULTFromLastError();
}
return S_OK;
}
HRESULT File::CopyWildcards(const TCHAR* from_dir,
const TCHAR* to_dir,
const TCHAR* wildcard,
bool replace_existing_files) {
ASSERT1(from_dir && *from_dir);
ASSERT1(to_dir && *to_dir);
ASSERT1(wildcard && *wildcard);
// Make sure dir names end with a "\"
CString from_directory = String_MakeEndWith(from_dir, _T("\\"), false);
CString to_directory = String_MakeEndWith(to_dir, _T("\\"), false);
// Get full path to source files (which is a wildcard)
CString from_files(from_directory + wildcard);
// Run over all files that match wildcard
WIN32_FIND_DATA find_data;
SetZero(find_data);
scoped_hfind hfind(::FindFirstFile(from_files, &find_data));
if (!hfind) {
HRESULT hr = HRESULTFromLastError();
UTIL_LOG(LEVEL_ERROR,
(_T("[File::CopyWildcards - FindFirstFile failed][0x%x]"), hr));
return hr;
}
do {
// Copy files
if (find_data.dwFileAttributes == FILE_ATTRIBUTE_NORMAL ||
!(find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
CString from_file(from_directory + find_data.cFileName);
CString to_file(to_directory + find_data.cFileName);
if (!replace_existing_files && Exists(to_file)) {
// Continue, since the caller has explicitly asked us to not replace an
// existing file
continue;
}
RET_IF_FAILED(Copy(from_file, to_file, replace_existing_files));
}
} while (::FindNextFile(get(hfind), &find_data));
HRESULT hr = HRESULTFromLastError();
if (hr != HRESULT_FROM_WIN32(ERROR_NO_MORE_FILES)) {
UTIL_LOG(LEVEL_ERROR,
(_T("[File::CopyWildcards - FindNextFile failed][0x%x]"), hr));
return hr;
}
return S_OK;
}
HRESULT File::CopyTree(const TCHAR* from_dir,
const TCHAR* to_dir,
bool replace_existing_files) {
ASSERT1(from_dir && *from_dir);
ASSERT1(to_dir && *to_dir);
UTIL_LOG(L3, (L"[File::CopyTree][from_dir %s][to_dir %s][replace %d]",
from_dir, to_dir, replace_existing_files));
// Make sure dir names end with a "\"
CString from_directory(String_MakeEndWith(from_dir, L"\\", false));
CString to_directory(String_MakeEndWith(to_dir, L"\\", false));
RET_IF_FAILED(CreateDir(to_directory, NULL));
RET_IF_FAILED(CopyWildcards(from_directory,
to_directory,
L"*.*",
replace_existing_files));
// Run over all directories
WIN32_FIND_DATA find_data;
SetZero(find_data);
CString from_files(from_directory);
from_files += _T("*.*");
scoped_hfind hfind(::FindFirstFile(from_files, &find_data));
if (!hfind) {
HRESULT hr = HRESULTFromLastError();
UTIL_LOG(LEVEL_ERROR,
(_T("[File::CopyTree - FindFirstFile failed][0x%x]"), hr));
return hr;
}
do {
// Copy files
if ((find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 &&
String_StrNCmp(find_data.cFileName, L"..", 2, false) &&
String_StrNCmp(find_data.cFileName, L".", 2, false)) {
CString from_subdir(from_directory + find_data.cFileName);
CString to_subdir(to_directory + find_data.cFileName);
RET_IF_FAILED(CopyTree(from_subdir, to_subdir, replace_existing_files));
}
} while (::FindNextFile(get(hfind), &find_data));
HRESULT hr = HRESULTFromLastError();
if (hr != HRESULT_FROM_WIN32(ERROR_NO_MORE_FILES)) {
UTIL_LOG(LEVEL_ERROR,
(_T("[File::CopyTree - FindNextFile failed][0x%x]"), hr));
return hr;
}
return S_OK;
}
HRESULT File::Copy(const TCHAR* from,
const TCHAR* to,
bool replace_existing_file) {
ASSERT1(from && *from);
ASSERT1(to && *to);
if (!replace_existing_file && Exists(to)) {
// Return success, since the caller has explicitly asked us to not replace
// an existing file
return S_OK;
}
if (!::CopyFile(from, to, !replace_existing_file)) {
HRESULT hr = HRESULTFromLastError();
UTIL_LOG(LEVEL_ERROR, (_T("[File::Copy - CopyFile failed]")
_T("[from=%s][to=%s][replace=%u][0x%x]"),
from, to, replace_existing_file, hr));
return hr;
}
return S_OK;
}
// TODO(omaha): Combine common code in Move/MoveAfterReboot
HRESULT File::Move(const TCHAR* from,
const TCHAR* to,
bool replace_existing_file) {
ASSERT1(from && *from);
ASSERT1(to && *to);
DWORD flags = MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH;
if (replace_existing_file) {
flags |= MOVEFILE_REPLACE_EXISTING;
}
if (!::MoveFileEx(from, to, flags)) {
HRESULT hr = HRESULTFromLastError();
UTIL_LOG(LEVEL_ERROR,
(_T("[File::Move - MoveFileEx failed]")
_T("[from=%s][to=%s][replace=%u][0x%x]"),
from, to, replace_existing_file, hr));
return hr;
}
return S_OK;
}
// DeleteAfterReboot tries to delete the files by either moving them to the TEMP
// directory and deleting them on reboot, or if that fails, by trying to delete
// them in-place on reboot
HRESULT File::DeleteAfterReboot(const TCHAR* from) {
ASSERT1(from && *from);
if (File::Exists(from)) {
HRESULT hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
CString from_temp;
// No point in moving into TEMP if we're already there
if (!String_StartsWith(from, app_util::GetTempDir(), true)) {
// Try to move to the TEMP directory first
CString temp_dir(String_MakeEndWith(app_util::GetTempDir(),
_T("\\"),
false));
// Of the form "C:\\Windows\\Temp\\FROM.EXE1f4c0b7f"
from_temp.Format(_T("%s%s%x"),
temp_dir,
GetFileFromPath(from),
::GetTickCount());
hr = File::Move(from, from_temp, true);
UTIL_LOG(L2, (_T("[File::DeleteAfterReboot - move %s to %s][0x%x]"),
from, from_temp, hr));
}
if (SUCCEEDED(hr)) {
UTIL_LOG(L2, (_T("[File::DeleteAfterReboot - delete %s after reboot]"),
from_temp));
// Move temp file after reboot
if (FAILED(hr = File::MoveAfterReboot(from_temp, NULL))) {
UTIL_LOG(LEVEL_ERROR, (_T("[DeleteWildcardFiles]")
_T("[failed to delete after reboot %s][0x%x]"),
from_temp, hr));
}
} else {
// Move original file after reboot
if (FAILED(hr = File::MoveAfterReboot(from, NULL))) {
UTIL_LOG(LEVEL_ERROR, (_T("[DeleteWildcardFiles]")
_T("[failed to delete after reboot %s][0x%x]"),
from, hr));
}
}
return hr;
}
return S_OK;
}
HRESULT File::MoveAfterReboot(const TCHAR* from, const TCHAR* to) {
ASSERT1(from && *from);
if (!File::Exists(from)) {
// File/directory doesn't exist, should this return failure or success?
// Decision: Failure. Because the caller can decide if it is really
// failure or not in his specific case.
UTIL_LOG(LEVEL_WARNING, (_T("[File::MoveAfterReboot]")
_T("[file doesn't exist][from %s]"), from));
return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
}
DWORD flags = MOVEFILE_DELAY_UNTIL_REBOOT;
if (!File::IsDirectory(from)) {
// This flag valid only for files
flags |= MOVEFILE_REPLACE_EXISTING;
}
if (!::MoveFileEx(from, to, flags)) {
HRESULT hr = HRESULTFromLastError();
UTIL_LOG(LEVEL_ERROR, (_T("[File::MoveAfterReboot]")
_T("[failed to MoveFileEx from '%s' to '%s'][0x%x]"),
from, to, hr));
return hr;
}
return S_OK;
}
// See if we have any moves pending a reboot. Return SUCCESS if we do
// not encounter errors (not finding a move is not an error). We need to
// also check the value of *found_ptr for whether we actually found a move.
// On return, *value_multisz_ptr is the value within
// "PendingFileRenameOperations", but with any moves for in_directory removed
// from it.
// The prefix_match boolean controls whether we do an exact match on
// in_directory, or remove all entries with the in_directory prefix.
// NOTE: If the only values found were our own keys, the whole
// PendingFileRenameOperations MULTISZ needs to be deleted.
// This is signified by a returned *value_size_chars_ptr of 0.
HRESULT File::GetPendingRenamesValueMinusDir(const TCHAR* in_directory,
bool prefix_match,
TCHAR** value_multisz_ptr,
DWORD* value_size_chars_ptr,
bool* found_ptr) {
ASSERT1(in_directory && *in_directory);
// Convert to references for easier-to-read-code:
TCHAR*& value_multisz = *value_multisz_ptr;
DWORD& value_size_chars = *value_size_chars_ptr;
bool& found = *found_ptr;
// Initialize [out] parameters
value_multisz = NULL;
value_size_chars = 0;
found = false;
// Locals mirroring the [out] parameters.
// We will only set the corresponding [out] parameters when we have something
// meaningful to return to the caller
scoped_array<TCHAR> value_multisz_local;
DWORD value_size_chars_local = 0;
DWORD value_size_bytes = 0;
// Get the current value of the key
// If the Key is missing, that's totally acceptable.
RET_IF_FALSE(
RegKey::HasValue(kSessionManagerKey, kPendingFileRenameOps) &&
SUCCEEDED(RegKey::GetValue(kSessionManagerKey,
kPendingFileRenameOps,
reinterpret_cast<byte**>(&value_multisz_local),
&value_size_bytes)),
S_OK);
ASSERT1(value_multisz_local.get() || value_size_bytes == 0);
UTIL_LOG(L5, (_T("[File::GetPendingRenamesValueMinusDir]")
_T("[read multisz %d bytes]"),
value_size_bytes));
RET_IF_FALSE(value_size_bytes > 0, S_OK);
// The size should always be aligned to a TCHAR boundary, otherwise the key
// is corrupted.
ASSERT1((value_size_bytes % sizeof(TCHAR)) == 0);
RET_IF_FALSE((value_size_bytes % sizeof(TCHAR)) == 0,
HRESULT_FROM_WIN32(ERROR_BADKEY));
// Valid size, so convert to TCHARs:
value_size_chars_local = value_size_bytes / sizeof(TCHAR);
// Buffer must terminate with two nulls
ASSERT(value_size_chars_local >= 2 &&
!value_multisz_local[value_size_chars_local - 1] &&
!value_multisz_local[value_size_chars_local - 2],
(_T("buffer must terminate with two nulls")));
RET_IF_FALSE(value_size_chars_local >= 2 &&
!value_multisz_local[value_size_chars_local - 1] &&
!value_multisz_local[value_size_chars_local - 2],
HRESULT_FROM_WIN32(ERROR_BADKEY));
// Mark the end of the string.
// multisz_end will point at the character past end of buffer:
TCHAR* multisz_end = value_multisz_local.get() + value_size_chars_local;
// We're looking for \??\C:\... The \??\ was
// added by the OS to the directory name we specified.
CString from_dir(_T("\\??\\"));
from_dir += in_directory;
DWORD from_dir_len = from_dir.GetLength();
// A MULTISZ is a list of null terminated strings, terminated by a double
// null. We keep two pointers marching along the string in parallel.
TCHAR* str_read = value_multisz_local.get();
TCHAR* str_write = str_read;
while ((str_read < multisz_end) && *str_read) {
size_t str_len = ::lstrlen(str_read);
// A FALSE here indicates a corrupt PendingFileRenameOperations
RET_IF_FALSE((str_read + str_len + 1) < multisz_end,
HRESULT_FROM_WIN32(ERROR_BADKEY));
if (0 == String_StrNCmp(str_read,
from_dir,
from_dir_len + (prefix_match ? 0 : 1),
true)) {
// String matches, we want to remove this string, so advance only the
// read pointer - past this string and the replacement string.
UTIL_LOG(L5, (_T("[File::GetPendingRenamesValueMinusDir]")
_T("[skips past match '%s']"),
str_read));
str_read += str_len + 1;
str_read += ::lstrlen(str_read) + 1;
continue;
}
// String doesn't match, we want to keep it.
if (str_read != str_write) {
// Here we're not in sync in the buffer, we've got to move two
// strings down.
UTIL_LOG(L5, (_T("[File::GetPendingRenamesValueMinusDir]")
_T("[copying some other deletion][%s][%s]"),
str_read, str_read + ::lstrlen(str_read) + 1));
ASSERT1(str_write < str_read);
String_StrNCpy(str_write, str_read, str_len+1);
str_read += str_len + 1;
str_write += str_len + 1;
str_len = ::lstrlen(str_read);
String_StrNCpy(str_write, str_read, str_len+1);
str_read += str_len + 1;
str_write += str_len + 1;
} else {
// We're in sync in the buffer, advance both pointers past two strings
UTIL_LOG(L5, (_T("[File::GetPendingRenamesValueMinusDir]")
_T("[skipping past some other deletion][%s][%s]"),
str_read, str_read + ::lstrlen(str_read) + 1));
str_read += str_len + 1;
str_read += ::lstrlen(str_read) + 1;
str_write = str_read;
}
}
// A FALSE here indicates a corrupt PendingFileRenameOperations
RET_IF_FALSE(str_read < multisz_end,
HRESULT_FROM_WIN32(ERROR_BADKEY));
if (str_read != str_write) {
// We found some values
found = true;
if (str_write == value_multisz_local.get()) {
// The only values were our own keys,
// and the whole PendingFileRenameOperations
// value needs to be deleted. We do not populate
// value_size_chars or value_multisz in this case.
ASSERT1(!value_size_chars);
ASSERT1(!value_multisz);
} else {
// The last string should have a NULL terminator:
ASSERT1(str_write[-1] == '\0');
RET_IF_FALSE(str_write[-1] == '\0',
HRESULT_FROM_WIN32(ERROR_INVALID_DATA));
// a REG_MULTI_SZ needs to be terminated with an extra NULL.
*str_write = '\0';
++str_write;
// Populate value_size_chars and value_multisz in this case.
value_multisz = value_multisz_local.release();
value_size_chars = str_write - value_multisz;
}
}
return S_OK;
}
// Remove any moves pending a reboot from the PendingFileRenameOperations
// in the registry.
// The prefix_match boolean controls whether we do an exact match on
// in_directory, or remove all entries with the in_directory prefix.
HRESULT File::RemoveFromMovesPendingReboot(const TCHAR* in_directory,
bool prefix_match) {
ASSERT1(in_directory && *in_directory);
bool found = false;
// scoped_array will free the value_multisz buffer on stack unwind:
scoped_array<TCHAR> value_multisz;
DWORD value_size_chars = 0;
HRESULT hr = GetPendingRenamesValueMinusDir(in_directory,
prefix_match,
address(value_multisz),
&value_size_chars,
&found);
if (SUCCEEDED(hr) && found) {
if (value_multisz.get() == NULL) {
// There's no point in writing an empty value_multisz.
// Let's delete the PendingFileRenameOperations value
UTIL_LOG(L5, (_T("[File::RemoveFromMovesPendingReboot]")
_T("[deleting PendingFileRenameOperations value]")));
RET_IF_FAILED(RegKey::DeleteValue(kSessionManagerKey,
kPendingFileRenameOps));
} else {
// Let's write the modified value_multisz into the
// PendingFileRenameOperations value
UTIL_LOG(L5, (_T("[File::RemoveFromMovesPendingReboot]")
_T("[rewriting multisz %d bytes]"),
value_size_chars * sizeof(TCHAR)));
RET_IF_FAILED(RegKey::SetValueMultiSZ(
kSessionManagerKey,
kPendingFileRenameOps,
reinterpret_cast<byte*>(value_multisz.get()),
value_size_chars * sizeof(TCHAR)));
}
}
// Failure of GetPendingRenamesValueMinusDir() may indicate something
// seriously wrong with the system. Propogate error.
return hr;
}
// Did the user try to uninstall a previous install of the same version, and
// we couldn't clean up without a reboot?
// We check if there are any moves pending a reboot from the
// PendingFileRenameOperations in the registry.
// The prefix_match boolean controls whether we do an exact match on
// in_directory, or check all entries with the in_directory prefix.
bool File::AreMovesPendingReboot(const TCHAR* in_directory, bool prefix_match) {
ASSERT1(in_directory && *in_directory);
bool found = false;
// scoped_array will free the value_multisz buffer on stack unwind:
scoped_array<TCHAR> value_multisz;
DWORD value_size_chars = 0;
if (SUCCEEDED(GetPendingRenamesValueMinusDir(in_directory,
prefix_match,
address(value_multisz),
&value_size_chars,
&found)) && found) {
return true;
}
return false;
}
HRESULT File::GetFileTime(const TCHAR* file_name,
FILETIME* created,
FILETIME* accessed,
FILETIME* modified) {
ASSERT1(file_name && *file_name);
bool is_dir = IsDirectory(file_name);
// To obtain a handle to a directory, call the CreateFile function with
// the FILE_FLAG_BACKUP_SEMANTICS flag
scoped_hfile file_handle(
::CreateFile(file_name,
FILE_READ_DATA,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
is_dir ? FILE_FLAG_BACKUP_SEMANTICS : NULL,
NULL));
HRESULT hr = S_OK;
if (!file_handle) {
hr = HRESULTFromLastError();
UTIL_LOG(LE, (_T("[File::GetFileTime]")
_T("[failed to open file][%s][0x%x]"), file_name, hr));
} else {
if (!::GetFileTime(get(file_handle), created, accessed, modified)) {
hr = HRESULTFromLastError();
UTIL_LOG(LEVEL_ERROR, (_T("[File::GetFileTime]")
_T("[failed to get file time][%s][0x%x]"),
file_name, hr));
}
}
return hr;
}
HRESULT File::SetFileTime(const TCHAR* file_name,
const FILETIME* created,
const FILETIME* accessed,
const FILETIME* modified) {
ASSERT1(file_name && *file_name);
bool is_dir = IsDirectory(file_name);
// To obtain a handle to a directory, call the CreateFile function with
// the FILE_FLAG_BACKUP_SEMANTICS flag
scoped_hfile file_handle(
::CreateFile(file_name,
FILE_WRITE_ATTRIBUTES,
FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
is_dir ? FILE_FLAG_BACKUP_SEMANTICS : NULL,
NULL));
HRESULT hr = S_OK;
if (!file_handle) {
hr = HRESULTFromLastError();
UTIL_LOG(LEVEL_ERROR, (_T("[File::GetFileTime]")
_T("[failed to open file][%s][0x%x]"),
file_name, hr));
} else {
BOOL res = ::SetFileTime(get(file_handle), created, accessed, modified);
if (!res) {
hr = HRESULTFromLastError();
UTIL_LOG(LEVEL_ERROR, (_T("[File::SetFileTime]")
_T("[failed to set file time][%s][0x%x]"),
file_name, hr));
}
}
return hr;
}
HRESULT File::Sync() {
ASSERT1(handle_ != INVALID_HANDLE_VALUE);
if (!::FlushFileBuffers(handle_)) {
HRESULT hr = HRESULTFromLastError();
UTIL_LOG(LEVEL_ERROR, (_T("[File::Sync]")
_T("[FlushFileBuffers failed][%s][0x%x]"),
file_name_, hr));
return hr;
}
return S_OK;
}
HRESULT File::SeekToBegin() {
return SeekFromBegin(0);
}
HRESULT File::SeekFromBegin(uint32 n) {
ASSERT1(handle_ != INVALID_HANDLE_VALUE);
if (::SetFilePointer(handle_, n, NULL, FILE_BEGIN) ==
INVALID_SET_FILE_POINTER) {
HRESULT hr = HRESULTFromLastError();
UTIL_LOG(LEVEL_ERROR, (_T("[File::SeekFromBegin]")
_T("[SetFilePointer failed][%s][0x%x]"),
file_name_, hr));
return hr;
}
pos_ = n;
return S_OK;
}
// read nLen bytes starting at position n
// returns number of bytes read
//
// async operations:
//
// async_id - identifier of a sequence of async operations, 0 for synchronous
//
// if the async operation has not been initiated, we initiate it
// if it is in progress we do nothing
// if it has been completed we return the data
// does not delete async data entry
HRESULT File::ReadAt(const uint32 offset, byte* buf, const uint32 len,
const uint32, uint32* bytes_read) {
ASSERT1(handle_ != INVALID_HANDLE_VALUE);
ASSERT1(buf);
ASSERT1(len); // reading 0 bytes is not valid (differs from CRT API)
RET_IF_FAILED(SeekFromBegin(offset));
DWORD read = 0;
if (!::ReadFile(handle_, buf, len, &read, NULL)) {
HRESULT hr = HRESULTFromLastError();
UTIL_LOG(LEVEL_ERROR, (_T("[File::ReadAt]")
_T("[ReadFile failed][%s][0x%x]"), file_name_, hr));
return hr;
}
if (bytes_read) {
*bytes_read = read;
}
return (read == len) ? S_OK : E_FAIL;
}
// reads up to max_len bytes from the start of the file
// not considered an error if there are less than max_len bytes read
// returns number of bytes read
HRESULT File::ReadFromStartOfFile(const uint32 max_len,
byte* buf,
uint32* bytes_read) {
ASSERT1(handle_ != INVALID_HANDLE_VALUE);
ASSERT1(buf);
ASSERT1(max_len);
RET_IF_FAILED(SeekFromBegin(0));
uint32 file_len = 0;
RET_IF_FAILED(GetLength(&file_len));
if (!file_len) {
if (bytes_read) {
*bytes_read = 0;
}
return S_OK;
}
uint32 len = max_len;
if (len > file_len) {
len = static_cast<uint32>(file_len);
}
return Read(len, buf, bytes_read);
}
// this function handles lines terminated with LF or CRLF
// all CR characters are removed from each line, and LF is assumed
// to be the end of line and is removed
HRESULT File::ReadLineAnsi(uint32 max_len, char* line, uint32* len) {
ASSERT1(line);
ASSERT1(max_len);
char c = 0;
uint32 len_read = 0;
uint32 total_len = 0;
while (SUCCEEDED(Read(1, reinterpret_cast<byte *>(&c), &len_read)) &&
len_read &&
c != '\n') {
if (total_len < max_len - 1 && c != '\r') {
line[total_len++] = c;
}
}
ASSERT1(total_len < max_len);
line[total_len] = '\0';
if (len) {
*len = total_len;
}
return (len_read || total_len) ? S_OK : E_FAIL;
}
// used by ReadFromStartOfFile and ReadLineAnsi; not reading all requested bytes
// is not considered fatal
HRESULT File::Read(const uint32 len, byte* buf, uint32* bytes_read) {
ASSERT1(handle_ != INVALID_HANDLE_VALUE);
ASSERT1(buf);
ASSERT1(len);
DWORD read = 0;
if (!::ReadFile(handle_, buf, len, &read, NULL)) {
HRESULT hr = HRESULTFromLastError();
UTIL_LOG(LEVEL_ERROR, (_T("[File::ReadAt]")
_T("[ReadFile failed][%s][0x%x]"),
file_name_, hr));
return hr;
}
if (bytes_read) {
*bytes_read = read;
}
return S_OK;
}
// returns number of bytes written
HRESULT File::WriteAt(const uint32 offset,
const byte* buf,
const uint32 len,
uint32,
uint32* bytes_written) {
ASSERT1(handle_ != INVALID_HANDLE_VALUE);
ASSERT1(!read_only_);
ASSERT1(buf);
ASSERT1(len);
RET_IF_FAILED(SeekFromBegin(offset));
return Write(buf, len, bytes_written);
}
// write buffer n times
HRESULT File::WriteN(const byte* buf,
const uint32 len,
const uint32 n,
uint32* bytes_written) {
ASSERT1(handle_ != INVALID_HANDLE_VALUE);
ASSERT1(!read_only_);
ASSERT1(buf);
ASSERT1(len);
ASSERT1(n);
HRESULT hr = S_OK;
uint32 total_wrote = 0;
byte* temp_buf = const_cast<byte*>(buf);
scoped_array<byte> encrypt_buf;
uint32 to_go = n;
while (to_go) {
uint32 wrote = 0;
hr = Write(temp_buf, len, &wrote);
if (FAILED(hr)) {
if (bytes_written) {
*bytes_written = total_wrote;
}
return hr;
}
total_wrote += wrote;
to_go--;
}
if (bytes_written) {
*bytes_written = total_wrote;
}
return hr;
}
// returns number of bytes written
HRESULT File::Write(const byte* buf, const uint32 len, uint32* bytes_written) {
ASSERT1(handle_ != INVALID_HANDLE_VALUE);
ASSERT1(!read_only_);
ASSERT1(buf);
ASSERT1(len); // writing 0 bytes is not valid (differs from CRT API)
byte* b = const_cast<byte*>(buf);
scoped_array<byte> encrypt_buf;
DWORD wrote = 0;
if (!::WriteFile(handle_, b, len, &wrote, NULL)) {
HRESULT hr = HRESULTFromLastError();
UTIL_LOG(LEVEL_ERROR, (_T("[File::Write]")
_T("[WriteFile failed][%s][0x%x]"),
file_name_, hr));
return hr;
}
if (bytes_written) {
*bytes_written = wrote;
}
pos_ += wrote;
return (wrote == len) ? S_OK : E_FAIL;
}
HRESULT File::ClearAt(const uint32 offset,
const uint32 len,
uint32* bytes_written) {
ASSERT1(handle_ != INVALID_HANDLE_VALUE);
ASSERT1(!read_only_);
ASSERT1(len);
byte zero[kZeroSize] = {0};
uint32 to_go = len;
uint32 written = 0;
uint32 pos = offset;
while (to_go) {
uint32 wrote = 0;
uint32 write_len = std::min(to_go, kZeroSize);
RET_IF_FAILED(WriteAt(pos, zero, write_len, 0, &wrote));
if (wrote != write_len) {
return E_FAIL;
}
pos += wrote;
written += wrote;
to_go -= write_len;
}
if (bytes_written) {
*bytes_written = written;
}
return S_OK;
}
// returns true on failure
// zeros new data if zero_data == true
HRESULT File::SetLength(const uint32 n, bool zero_data) {
ASSERT1(handle_ != INVALID_HANDLE_VALUE);
ASSERT1(!read_only_);
ASSERT1(n <= kMaxFileSize);
HRESULT hr = S_OK;
uint32 len = 0;
VERIFY1(SUCCEEDED(GetLength(&len)));
if (len == n) {
return S_OK;
}
// according to the documentation, the
// new space will not be initialized
if (n > len) {
if (zero_data) {
uint32 bytes_written = 0;
RET_IF_FAILED(ClearAt(len, n - len, &bytes_written));
if (bytes_written != n - len) {
return E_FAIL;
}
} else {
byte zero = 0;
uint32 bytes_written = 0;
RET_IF_FAILED(WriteAt(n - 1, &zero, 1, 0, &bytes_written));
if (bytes_written != 1) {
return E_FAIL;
}
}
} else {
SeekFromBegin(n);
SetEndOfFile(handle_);
}
ASSERT1(SUCCEEDED(GetLength(&len)) && len == n);
return S_OK;
}
HRESULT File::ExtendInBlocks(const uint32 block_size, uint32 size_needed,
uint32* new_size, bool clear_new_space) {
ASSERT1(new_size);
*new_size = size_needed;
if (*new_size % block_size) {
*new_size += block_size - (*new_size % block_size);
}
// is zero_data needed? may reduce fragmentation by causing the block to
// be written
return SetLength(*new_size, clear_new_space);
}
// returns S_OK on success
HRESULT File::GetLength(uint32* length) {
ASSERT1(length);
ASSERT1(handle_ != INVALID_HANDLE_VALUE);
DWORD len = GetFileSize(handle_, NULL);
if (len == INVALID_FILE_SIZE) {
ASSERT(false, (_T("cannot get file length")));
return E_FAIL;
}
*length = len;
return S_OK;
}
HRESULT File::Touch() {
ASSERT1(handle_ != INVALID_HANDLE_VALUE);
FILETIME file_time;
SetZero(file_time);
::GetSystemTimeAsFileTime(&file_time);
if (!::SetFileTime(handle_, NULL, NULL, &file_time)) {
return HRESULTFromLastError();
}
return S_OK;
}
HRESULT File::Close() {
ASSERT1(handle_ != INVALID_HANDLE_VALUE);
HRESULT hr = S_OK;
if (!::CloseHandle(handle_)) {
hr = HRESULTFromLastError();
}
handle_ = INVALID_HANDLE_VALUE;
return hr;
}
// this is just for consistency with other classes; does not do anything
HRESULT File::Reload(uint32* number_errors) {
ASSERT1(number_errors);
ASSERT1(handle_ != INVALID_HANDLE_VALUE);
*number_errors = 0;
return S_OK;
}
// this is just for consistency with other classes; does not do anything
HRESULT File::Verify(uint32* number_errors) {
ASSERT1(number_errors);
ASSERT1(handle_ != INVALID_HANDLE_VALUE);
*number_errors = 0;
return S_OK;
}
// this is just for consistency with other classes; does not do anything
HRESULT File::Dump() {
ASSERT1(handle_ != INVALID_HANDLE_VALUE);
return S_OK;
}
// for consistency with other classes
HRESULT File::GetSizeOnDisk(uint64* size_on_disk) {
ASSERT1(size_on_disk);
ASSERT1(handle_ != INVALID_HANDLE_VALUE);
uint32 len = 0;
RET_IF_FAILED(GetLength(&len));
*size_on_disk = len;
return S_OK;
}
// for consistency with other classes
HRESULT File::GetReloadDiskSpaceNeeded(uint64* bytes_needed) {
ASSERT1(bytes_needed);
ASSERT1(handle_ != INVALID_HANDLE_VALUE);
uint32 len = 0;
RET_IF_FAILED(GetLength(&len));
*bytes_needed = len;
return S_OK;
}
// Get the file size
HRESULT File::GetFileSizeUnopen(const TCHAR* filename, uint32* out_size) {
ASSERT1(filename);
ASSERT1(out_size);
WIN32_FILE_ATTRIBUTE_DATA data;
SetZero(data);
if (!::GetFileAttributesEx(filename, ::GetFileExInfoStandard, &data)) {
return HRESULTFromLastError();
}
*out_size = data.nFileSizeLow;
return S_OK;
}
// Get the last time with a file was written to, and the size
HRESULT File::GetLastWriteTimeAndSize(const TCHAR* file_path,
SYSTEMTIME* out_time,
unsigned int* out_size) {
ASSERT1(file_path);
WIN32_FIND_DATA wfd;
SetZero(wfd);
HANDLE find = ::FindFirstFile(file_path, &wfd);
if (find == INVALID_HANDLE_VALUE) {
return HRESULTFromLastError();
}
::FindClose(find);
if (out_size) {
*out_size = wfd.nFileSizeLow;
}
if (out_time) {
// If created time is newer than write time, then use that instead
// [it tends to be more relevant when copying files around]
FILETIME* latest_time = NULL;
if (::CompareFileTime(&wfd.ftCreationTime, &wfd.ftLastWriteTime) > 0) {
latest_time = &wfd.ftCreationTime;
} else {
latest_time = &wfd.ftLastWriteTime;
}
if (!::FileTimeToSystemTime(latest_time, out_time)) {
return HRESULTFromLastError();
}
}
return S_OK;
}
FileLock::FileLock() {
}
FileLock::~FileLock() {
Unlock();
}
HRESULT FileLock::Lock(const TCHAR* file) {
std::vector<CString> files;
files.push_back(file);
return Lock(files);
}
HRESULT FileLock::Lock(const std::vector<CString>& files) {
ASSERT1(!files.empty());
// Try to lock all files
size_t curr_size = handles_.size();
for (size_t i = 0; i < files.size(); ++i) {
scoped_hfile handle(::CreateFile(files[i],
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL));
if (!handle) {
UTIL_LOG(LEVEL_ERROR,
(_T("[FileLock::Lock - failed to lock file][%s][0x%x]"),
files[i], HRESULTFromLastError()));
break;
}
handles_.push_back(release(handle));
}
// Cleanup if we fail to lock all the files
if (curr_size + files.size() < handles_.size()) {
for (size_t i = handles_.size() - 1; i >= curr_size; --i) {
VERIFY(::CloseHandle(handles_[i]), (_T("")));
handles_.pop_back();
}
return E_FAIL;
}
return S_OK;
}
HRESULT FileLock::Unlock() {
for (size_t i = 0; i < handles_.size(); ++i) {
VERIFY(::CloseHandle(handles_[i]), (_T("")));
}
handles_.clear();
return S_OK;
}
// path_name: the directory to watch
// watch_subtree: watch all subdirectory changes or
// only immediate child values
// notify_filter: See the documentation for FindFirstChangeNotification
FileWatcher::FileWatcher(const TCHAR* path_name, bool watch_subtree,
DWORD notify_filter)
: path_name_(path_name),
watch_subtree_(watch_subtree),
notify_filter_(notify_filter) {
ASSERT1(path_name && *path_name);
UTIL_LOG(L3, (_T("[FileWatcher::FileWatcher][%s]"), path_name));
}
// Get the event that is signaled on store changes.
HANDLE FileWatcher::change_event() const {
ASSERT(valid(change_event_), (_T("call FileWatcher::SetupEvent first")));
return get(change_event_);
}
// Called to create/reset the event that gets signaled
// any time the store changes. Access the created
// event using change_event().
HRESULT FileWatcher::EnsureEventSetup() {
UTIL_LOG(L3, (_T("[FileWatcher::EnsureEventSetup]")));
if (!valid(change_event_)) {
reset(change_event_, ::FindFirstChangeNotification(path_name_,
watch_subtree_,
notify_filter_));
if (!valid(change_event_)) {
ASSERT(false, (_T("unable to get file change notification")));
return E_FAIL;
}
// path name was only needed to set-up the event and now that is done....
path_name_.Empty();
return S_OK;
}
// if the event is set-up and no changes have occurred,
// then there is no need to re-setup the event.
if (valid(change_event_) && !HasChangeOccurred()) {
return NOERROR;
}
return ::FindNextChangeNotification(get(change_event_)) ? S_OK : E_FAIL;
}
} // namespace omaha