3 * Copyright 2004--2006, Google Inc.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * 3. The name of the author may not be used to endorse or promote products
14 * derived from this software without specific prior written permission.
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 #include "talk/base/win32filesystem.h"
30 #include "talk/base/win32.h"
35 #include "talk/base/fileutils.h"
36 #include "talk/base/pathutils.h"
37 #include "talk/base/scoped_ptr.h"
38 #include "talk/base/stream.h"
39 #include "talk/base/stringutils.h"
41 // In several places in this file, we test the integrity level of the process
42 // before calling GetLongPathName. We do this because calling GetLongPathName
43 // when running under protected mode IE (a low integrity process) can result in
44 // a virtualized path being returned, which is wrong if you only plan to read.
45 // TODO: Waiting to hear back from IE team on whether this is the
46 // best approach; IEIsProtectedModeProcess is another possible solution.
50 bool Win32Filesystem::CreateFolder(const Pathname &pathname) {
51 if (pathname.pathname().empty() || !pathname.filename().empty())
55 if (!Utf8ToWindowsFilename(pathname.pathname(), &path16))
58 DWORD res = ::GetFileAttributes(path16.c_str());
59 if (res != INVALID_FILE_ATTRIBUTES) {
60 // Something exists at this location, check if it is a directory
61 return ((res & FILE_ATTRIBUTE_DIRECTORY) != 0);
62 } else if ((GetLastError() != ERROR_FILE_NOT_FOUND)
63 && (GetLastError() != ERROR_PATH_NOT_FOUND)) {
68 // Directory doesn't exist, look up one directory level
69 if (!pathname.parent_folder().empty()) {
70 Pathname parent(pathname);
71 parent.SetFolder(pathname.parent_folder());
72 if (!CreateFolder(parent)) {
77 return (::CreateDirectory(path16.c_str(), NULL) != 0);
80 FileStream *Win32Filesystem::OpenFile(const Pathname &filename,
81 const std::string &mode) {
82 FileStream *fs = new FileStream();
83 if (fs && !fs->Open(filename.pathname().c_str(), mode.c_str(), NULL)) {
90 bool Win32Filesystem::CreatePrivateFile(const Pathname &filename) {
91 // To make the file private to the current user, we first must construct a
92 // SECURITY_DESCRIPTOR specifying an ACL. This code is mostly based upon
93 // http://msdn.microsoft.com/en-us/library/ms707085%28VS.85%29.aspx
95 // Get the current process token.
96 HANDLE process_token = INVALID_HANDLE_VALUE;
97 if (!::OpenProcessToken(::GetCurrentProcess(),
100 LOG_ERR(LS_ERROR) << "OpenProcessToken() failed";
104 // Get the size of its TOKEN_USER structure. Return value is not checked
105 // because we expect it to fail.
106 DWORD token_user_size = 0;
107 (void)::GetTokenInformation(process_token,
113 // Get the TOKEN_USER structure.
114 scoped_ptr<char[]> token_user_bytes(new char[token_user_size]);
115 PTOKEN_USER token_user = reinterpret_cast<PTOKEN_USER>(
116 token_user_bytes.get());
117 memset(token_user, 0, token_user_size);
118 BOOL success = ::GetTokenInformation(process_token,
123 // We're now done with this.
124 ::CloseHandle(process_token);
126 LOG_ERR(LS_ERROR) << "GetTokenInformation() failed";
130 if (!IsValidSid(token_user->User.Sid)) {
131 LOG_ERR(LS_ERROR) << "Current process has invalid user SID";
135 // Compute size needed for an ACL that allows access to just this user.
136 int acl_size = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) +
137 GetLengthSid(token_user->User.Sid);
140 scoped_ptr<char[]> acl_bytes(new char[acl_size]);
141 PACL acl = reinterpret_cast<PACL>(acl_bytes.get());
142 memset(acl, 0, acl_size);
143 if (!::InitializeAcl(acl, acl_size, ACL_REVISION)) {
144 LOG_ERR(LS_ERROR) << "InitializeAcl() failed";
148 // Allow access to only the current user.
149 if (!::AddAccessAllowedAce(acl,
151 GENERIC_READ | GENERIC_WRITE | STANDARD_RIGHTS_ALL,
152 token_user->User.Sid)) {
153 LOG_ERR(LS_ERROR) << "AddAccessAllowedAce() failed";
157 // Now make the security descriptor.
158 SECURITY_DESCRIPTOR security_descriptor;
159 if (!::InitializeSecurityDescriptor(&security_descriptor,
160 SECURITY_DESCRIPTOR_REVISION)) {
161 LOG_ERR(LS_ERROR) << "InitializeSecurityDescriptor() failed";
165 // Put the ACL in it.
166 if (!::SetSecurityDescriptorDacl(&security_descriptor,
170 LOG_ERR(LS_ERROR) << "SetSecurityDescriptorDacl() failed";
174 // Finally create the file.
175 SECURITY_ATTRIBUTES security_attributes;
176 security_attributes.nLength = sizeof(security_attributes);
177 security_attributes.lpSecurityDescriptor = &security_descriptor;
178 security_attributes.bInheritHandle = FALSE;
179 HANDLE handle = ::CreateFile(
180 ToUtf16(filename.pathname()).c_str(),
181 GENERIC_READ | GENERIC_WRITE,
182 FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
183 &security_attributes,
187 if (INVALID_HANDLE_VALUE == handle) {
188 LOG_ERR(LS_ERROR) << "CreateFile() failed";
191 if (!::CloseHandle(handle)) {
192 LOG_ERR(LS_ERROR) << "CloseFile() failed";
198 bool Win32Filesystem::DeleteFile(const Pathname &filename) {
199 LOG(LS_INFO) << "Deleting file " << filename.pathname();
200 if (!IsFile(filename)) {
201 ASSERT(IsFile(filename));
204 return ::DeleteFile(ToUtf16(filename.pathname()).c_str()) != 0;
207 bool Win32Filesystem::DeleteEmptyFolder(const Pathname &folder) {
208 LOG(LS_INFO) << "Deleting folder " << folder.pathname();
210 std::string no_slash(folder.pathname(), 0, folder.pathname().length()-1);
211 return ::RemoveDirectory(ToUtf16(no_slash).c_str()) != 0;
214 bool Win32Filesystem::GetTemporaryFolder(Pathname &pathname, bool create,
215 const std::string *append) {
216 wchar_t buffer[MAX_PATH + 1];
217 if (!::GetTempPath(ARRAY_SIZE(buffer), buffer))
219 if (!IsCurrentProcessLowIntegrity() &&
220 !::GetLongPathName(buffer, buffer, ARRAY_SIZE(buffer)))
222 size_t len = strlen(buffer);
223 if ((len > 0) && (buffer[len-1] != '\\')) {
224 len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, L"\\");
226 if (len >= ARRAY_SIZE(buffer) - 1)
229 pathname.SetFolder(ToUtf8(buffer));
230 if (append != NULL) {
231 ASSERT(!append->empty());
232 pathname.AppendFolder(*append);
234 return !create || CreateFolder(pathname);
237 std::string Win32Filesystem::TempFilename(const Pathname &dir,
238 const std::string &prefix) {
239 wchar_t filename[MAX_PATH];
240 if (::GetTempFileName(ToUtf16(dir.pathname()).c_str(),
241 ToUtf16(prefix).c_str(), 0, filename) != 0)
242 return ToUtf8(filename);
247 bool Win32Filesystem::MoveFile(const Pathname &old_path,
248 const Pathname &new_path) {
249 if (!IsFile(old_path)) {
250 ASSERT(IsFile(old_path));
253 LOG(LS_INFO) << "Moving " << old_path.pathname()
254 << " to " << new_path.pathname();
255 return ::MoveFile(ToUtf16(old_path.pathname()).c_str(),
256 ToUtf16(new_path.pathname()).c_str()) != 0;
259 bool Win32Filesystem::MoveFolder(const Pathname &old_path,
260 const Pathname &new_path) {
261 if (!IsFolder(old_path)) {
262 ASSERT(IsFolder(old_path));
265 LOG(LS_INFO) << "Moving " << old_path.pathname()
266 << " to " << new_path.pathname();
267 if (::MoveFile(ToUtf16(old_path.pathname()).c_str(),
268 ToUtf16(new_path.pathname()).c_str()) == 0) {
269 if (::GetLastError() != ERROR_NOT_SAME_DEVICE) {
270 LOG_GLE(LS_ERROR) << "Failed to move file";
273 if (!CopyFolder(old_path, new_path))
275 if (!DeleteFolderAndContents(old_path))
281 bool Win32Filesystem::IsFolder(const Pathname &path) {
282 WIN32_FILE_ATTRIBUTE_DATA data = {0};
283 if (0 == ::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(),
284 GetFileExInfoStandard, &data))
286 return (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ==
287 FILE_ATTRIBUTE_DIRECTORY;
290 bool Win32Filesystem::IsFile(const Pathname &path) {
291 WIN32_FILE_ATTRIBUTE_DATA data = {0};
292 if (0 == ::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(),
293 GetFileExInfoStandard, &data))
295 return (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
298 bool Win32Filesystem::IsAbsent(const Pathname& path) {
299 WIN32_FILE_ATTRIBUTE_DATA data = {0};
300 if (0 != ::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(),
301 GetFileExInfoStandard, &data))
303 DWORD err = ::GetLastError();
304 return (ERROR_FILE_NOT_FOUND == err || ERROR_PATH_NOT_FOUND == err);
307 bool Win32Filesystem::CopyFile(const Pathname &old_path,
308 const Pathname &new_path) {
309 return ::CopyFile(ToUtf16(old_path.pathname()).c_str(),
310 ToUtf16(new_path.pathname()).c_str(), TRUE) != 0;
313 bool Win32Filesystem::IsTemporaryPath(const Pathname& pathname) {
314 TCHAR buffer[MAX_PATH + 1];
315 if (!::GetTempPath(ARRAY_SIZE(buffer), buffer))
317 if (!IsCurrentProcessLowIntegrity() &&
318 !::GetLongPathName(buffer, buffer, ARRAY_SIZE(buffer)))
320 return (::strnicmp(ToUtf16(pathname.pathname()).c_str(),
321 buffer, strlen(buffer)) == 0);
324 bool Win32Filesystem::GetFileSize(const Pathname &pathname, size_t *size) {
325 WIN32_FILE_ATTRIBUTE_DATA data = {0};
326 if (::GetFileAttributesEx(ToUtf16(pathname.pathname()).c_str(),
327 GetFileExInfoStandard, &data) == 0)
329 *size = data.nFileSizeLow;
333 bool Win32Filesystem::GetFileTime(const Pathname& path, FileTimeType which,
335 WIN32_FILE_ATTRIBUTE_DATA data = {0};
336 if (::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(),
337 GetFileExInfoStandard, &data) == 0)
341 FileTimeToUnixTime(data.ftCreationTime, time);
344 FileTimeToUnixTime(data.ftLastWriteTime, time);
347 FileTimeToUnixTime(data.ftLastAccessTime, time);
355 bool Win32Filesystem::GetAppPathname(Pathname* path) {
356 TCHAR buffer[MAX_PATH + 1];
357 if (0 == ::GetModuleFileName(NULL, buffer, ARRAY_SIZE(buffer)))
359 path->SetPathname(ToUtf8(buffer));
363 bool Win32Filesystem::GetAppDataFolder(Pathname* path, bool per_user) {
364 ASSERT(!organization_name_.empty());
365 ASSERT(!application_name_.empty());
366 TCHAR buffer[MAX_PATH + 1];
367 int csidl = per_user ? CSIDL_LOCAL_APPDATA : CSIDL_COMMON_APPDATA;
368 if (!::SHGetSpecialFolderPath(NULL, buffer, csidl, TRUE))
370 if (!IsCurrentProcessLowIntegrity() &&
371 !::GetLongPathName(buffer, buffer, ARRAY_SIZE(buffer)))
373 size_t len = strcatn(buffer, ARRAY_SIZE(buffer), __T("\\"));
374 len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len,
375 ToUtf16(organization_name_).c_str());
376 if ((len > 0) && (buffer[len-1] != __T('\\'))) {
377 len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, __T("\\"));
379 len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len,
380 ToUtf16(application_name_).c_str());
381 if ((len > 0) && (buffer[len-1] != __T('\\'))) {
382 len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, __T("\\"));
384 if (len >= ARRAY_SIZE(buffer) - 1)
387 path->SetFolder(ToUtf8(buffer));
388 return CreateFolder(*path);
391 bool Win32Filesystem::GetAppTempFolder(Pathname* path) {
392 if (!GetAppPathname(path))
394 std::string filename(path->filename());
395 return GetTemporaryFolder(*path, true, &filename);
398 bool Win32Filesystem::GetDiskFreeSpace(const Pathname& path, int64 *freebytes) {
403 std::wstring drive16;
404 const wchar_t* target_drive = NULL;
405 if (path.GetDrive(drive, sizeof(drive))) {
406 drive16 = ToUtf16(drive);
407 target_drive = drive16.c_str();
408 } else if (path.folder().substr(0, 2) == "\\\\") {
410 // TODO: Handle UNC paths.
413 // The path is probably relative. GetDriveType and GetDiskFreeSpaceEx
414 // use the current drive if NULL is passed as the drive name.
415 // TODO: Add method to Pathname to determine if the path is relative.
416 // TODO: Add method to Pathname to convert a path to absolute.
418 UINT driveType = ::GetDriveType(target_drive);
419 if ( (driveType & DRIVE_REMOTE) || (driveType & DRIVE_UNKNOWN) ) {
420 LOG(LS_VERBOSE) << " remove or unknown drive " << drive;
424 int64 totalNumberOfBytes; // receives the number of bytes on disk
425 int64 totalNumberOfFreeBytes; // receives the free bytes on disk
426 // make sure things won't change in 64 bit machine
427 // TODO replace with compile time assert
428 ASSERT(sizeof(ULARGE_INTEGER) == sizeof(uint64)); //NOLINT
429 if (::GetDiskFreeSpaceEx(target_drive,
430 (PULARGE_INTEGER)freebytes,
431 (PULARGE_INTEGER)&totalNumberOfBytes,
432 (PULARGE_INTEGER)&totalNumberOfFreeBytes)) {
435 LOG(LS_VERBOSE) << " GetDiskFreeSpaceEx returns error ";
440 Pathname Win32Filesystem::GetCurrentDirectory() {
443 scoped_ptr<wchar_t[]> path;
445 int needed = ::GetCurrentDirectory(path_len, path.get());
448 LOG_GLE(LS_ERROR) << "::GetCurrentDirectory() failed";
449 return cwd; // returns empty pathname
451 if (needed <= path_len) {
452 // It wrote successfully.
455 // Else need to re-alloc for "needed".
456 path.reset(new wchar_t[needed]);
459 cwd.SetFolder(ToUtf8(path.get()));
463 // TODO: Consider overriding DeleteFolderAndContents for speed and potentially
464 // better OS integration (recycle bin?)
466 std::wstring temp_path16 = ToUtf16(temp_path.pathname());
467 temp_path16.append(1, '*');
468 temp_path16.append(1, '\0');
470 SHFILEOPSTRUCT file_op = { 0 };
471 file_op.wFunc = FO_DELETE;
472 file_op.pFrom = temp_path16.c_str();
473 file_op.fFlags = FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT;
474 return (0 == SHFileOperation(&file_op));
477 } // namespace talk_base