blob: b731974bac49548ed20fa07687b58bcf68683353 [file] [log] [blame]
henrike@webrtc.orgf0488722014-05-13 18:00:26 +00001/*
2 * Copyright 2004 The WebRTC Project Authors. All rights reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11#include "webrtc/base/win32filesystem.h"
12
13#include "webrtc/base/win32.h"
14#include <shellapi.h>
15#include <shlobj.h>
16#include <tchar.h>
17
tfarina5237aaf2015-11-10 23:44:30 -080018#include "webrtc/base/arraysize.h"
henrike@webrtc.orgf0488722014-05-13 18:00:26 +000019#include "webrtc/base/fileutils.h"
20#include "webrtc/base/pathutils.h"
21#include "webrtc/base/scoped_ptr.h"
22#include "webrtc/base/stream.h"
23#include "webrtc/base/stringutils.h"
24
25// In several places in this file, we test the integrity level of the process
26// before calling GetLongPathName. We do this because calling GetLongPathName
27// when running under protected mode IE (a low integrity process) can result in
28// a virtualized path being returned, which is wrong if you only plan to read.
29// TODO: Waiting to hear back from IE team on whether this is the
30// best approach; IEIsProtectedModeProcess is another possible solution.
31
32namespace rtc {
33
34bool Win32Filesystem::CreateFolder(const Pathname &pathname) {
35 if (pathname.pathname().empty() || !pathname.filename().empty())
36 return false;
37
38 std::wstring path16;
39 if (!Utf8ToWindowsFilename(pathname.pathname(), &path16))
40 return false;
41
42 DWORD res = ::GetFileAttributes(path16.c_str());
43 if (res != INVALID_FILE_ATTRIBUTES) {
44 // Something exists at this location, check if it is a directory
45 return ((res & FILE_ATTRIBUTE_DIRECTORY) != 0);
46 } else if ((GetLastError() != ERROR_FILE_NOT_FOUND)
47 && (GetLastError() != ERROR_PATH_NOT_FOUND)) {
48 // Unexpected error
49 return false;
50 }
51
52 // Directory doesn't exist, look up one directory level
53 if (!pathname.parent_folder().empty()) {
54 Pathname parent(pathname);
55 parent.SetFolder(pathname.parent_folder());
56 if (!CreateFolder(parent)) {
57 return false;
58 }
59 }
60
61 return (::CreateDirectory(path16.c_str(), NULL) != 0);
62}
63
64FileStream *Win32Filesystem::OpenFile(const Pathname &filename,
65 const std::string &mode) {
66 FileStream *fs = new FileStream();
67 if (fs && !fs->Open(filename.pathname().c_str(), mode.c_str(), NULL)) {
68 delete fs;
69 fs = NULL;
70 }
71 return fs;
72}
73
74bool Win32Filesystem::CreatePrivateFile(const Pathname &filename) {
75 // To make the file private to the current user, we first must construct a
76 // SECURITY_DESCRIPTOR specifying an ACL. This code is mostly based upon
77 // http://msdn.microsoft.com/en-us/library/ms707085%28VS.85%29.aspx
78
79 // Get the current process token.
80 HANDLE process_token = INVALID_HANDLE_VALUE;
81 if (!::OpenProcessToken(::GetCurrentProcess(),
82 TOKEN_QUERY,
83 &process_token)) {
84 LOG_ERR(LS_ERROR) << "OpenProcessToken() failed";
85 return false;
86 }
87
88 // Get the size of its TOKEN_USER structure. Return value is not checked
89 // because we expect it to fail.
90 DWORD token_user_size = 0;
91 (void)::GetTokenInformation(process_token,
92 TokenUser,
93 NULL,
94 0,
95 &token_user_size);
96
97 // Get the TOKEN_USER structure.
98 scoped_ptr<char[]> token_user_bytes(new char[token_user_size]);
99 PTOKEN_USER token_user = reinterpret_cast<PTOKEN_USER>(
100 token_user_bytes.get());
101 memset(token_user, 0, token_user_size);
102 BOOL success = ::GetTokenInformation(process_token,
103 TokenUser,
104 token_user,
105 token_user_size,
106 &token_user_size);
107 // We're now done with this.
108 ::CloseHandle(process_token);
109 if (!success) {
110 LOG_ERR(LS_ERROR) << "GetTokenInformation() failed";
111 return false;
112 }
113
114 if (!IsValidSid(token_user->User.Sid)) {
115 LOG_ERR(LS_ERROR) << "Current process has invalid user SID";
116 return false;
117 }
118
119 // Compute size needed for an ACL that allows access to just this user.
120 int acl_size = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) +
121 GetLengthSid(token_user->User.Sid);
122
123 // Allocate it.
124 scoped_ptr<char[]> acl_bytes(new char[acl_size]);
125 PACL acl = reinterpret_cast<PACL>(acl_bytes.get());
126 memset(acl, 0, acl_size);
127 if (!::InitializeAcl(acl, acl_size, ACL_REVISION)) {
128 LOG_ERR(LS_ERROR) << "InitializeAcl() failed";
129 return false;
130 }
131
132 // Allow access to only the current user.
133 if (!::AddAccessAllowedAce(acl,
134 ACL_REVISION,
135 GENERIC_READ | GENERIC_WRITE | STANDARD_RIGHTS_ALL,
136 token_user->User.Sid)) {
137 LOG_ERR(LS_ERROR) << "AddAccessAllowedAce() failed";
138 return false;
139 }
140
141 // Now make the security descriptor.
142 SECURITY_DESCRIPTOR security_descriptor;
143 if (!::InitializeSecurityDescriptor(&security_descriptor,
144 SECURITY_DESCRIPTOR_REVISION)) {
145 LOG_ERR(LS_ERROR) << "InitializeSecurityDescriptor() failed";
146 return false;
147 }
148
149 // Put the ACL in it.
150 if (!::SetSecurityDescriptorDacl(&security_descriptor,
151 TRUE,
152 acl,
153 FALSE)) {
154 LOG_ERR(LS_ERROR) << "SetSecurityDescriptorDacl() failed";
155 return false;
156 }
157
158 // Finally create the file.
159 SECURITY_ATTRIBUTES security_attributes;
160 security_attributes.nLength = sizeof(security_attributes);
161 security_attributes.lpSecurityDescriptor = &security_descriptor;
162 security_attributes.bInheritHandle = FALSE;
163 HANDLE handle = ::CreateFile(
164 ToUtf16(filename.pathname()).c_str(),
165 GENERIC_READ | GENERIC_WRITE,
166 FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
167 &security_attributes,
168 CREATE_NEW,
169 0,
170 NULL);
171 if (INVALID_HANDLE_VALUE == handle) {
172 LOG_ERR(LS_ERROR) << "CreateFile() failed";
173 return false;
174 }
175 if (!::CloseHandle(handle)) {
176 LOG_ERR(LS_ERROR) << "CloseFile() failed";
177 // Continue.
178 }
179 return true;
180}
181
182bool Win32Filesystem::DeleteFile(const Pathname &filename) {
183 LOG(LS_INFO) << "Deleting file " << filename.pathname();
184 if (!IsFile(filename)) {
185 ASSERT(IsFile(filename));
186 return false;
187 }
188 return ::DeleteFile(ToUtf16(filename.pathname()).c_str()) != 0;
189}
190
191bool Win32Filesystem::DeleteEmptyFolder(const Pathname &folder) {
192 LOG(LS_INFO) << "Deleting folder " << folder.pathname();
193
194 std::string no_slash(folder.pathname(), 0, folder.pathname().length()-1);
195 return ::RemoveDirectory(ToUtf16(no_slash).c_str()) != 0;
196}
197
198bool Win32Filesystem::GetTemporaryFolder(Pathname &pathname, bool create,
199 const std::string *append) {
200 wchar_t buffer[MAX_PATH + 1];
tfarina5237aaf2015-11-10 23:44:30 -0800201 if (!::GetTempPath(arraysize(buffer), buffer))
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000202 return false;
203 if (!IsCurrentProcessLowIntegrity() &&
tfarina5237aaf2015-11-10 23:44:30 -0800204 !::GetLongPathName(buffer, buffer, arraysize(buffer)))
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000205 return false;
206 size_t len = strlen(buffer);
207 if ((len > 0) && (buffer[len-1] != '\\')) {
tfarina5237aaf2015-11-10 23:44:30 -0800208 len += strcpyn(buffer + len, arraysize(buffer) - len, L"\\");
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000209 }
tfarina5237aaf2015-11-10 23:44:30 -0800210 if (len >= arraysize(buffer) - 1)
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000211 return false;
212 pathname.clear();
213 pathname.SetFolder(ToUtf8(buffer));
214 if (append != NULL) {
215 ASSERT(!append->empty());
216 pathname.AppendFolder(*append);
217 }
218 return !create || CreateFolder(pathname);
219}
220
221std::string Win32Filesystem::TempFilename(const Pathname &dir,
222 const std::string &prefix) {
223 wchar_t filename[MAX_PATH];
224 if (::GetTempFileName(ToUtf16(dir.pathname()).c_str(),
225 ToUtf16(prefix).c_str(), 0, filename) != 0)
226 return ToUtf8(filename);
227 ASSERT(false);
228 return "";
229}
230
231bool Win32Filesystem::MoveFile(const Pathname &old_path,
232 const Pathname &new_path) {
233 if (!IsFile(old_path)) {
234 ASSERT(IsFile(old_path));
235 return false;
236 }
237 LOG(LS_INFO) << "Moving " << old_path.pathname()
238 << " to " << new_path.pathname();
239 return ::MoveFile(ToUtf16(old_path.pathname()).c_str(),
240 ToUtf16(new_path.pathname()).c_str()) != 0;
241}
242
243bool Win32Filesystem::MoveFolder(const Pathname &old_path,
244 const Pathname &new_path) {
245 if (!IsFolder(old_path)) {
246 ASSERT(IsFolder(old_path));
247 return false;
248 }
249 LOG(LS_INFO) << "Moving " << old_path.pathname()
250 << " to " << new_path.pathname();
251 if (::MoveFile(ToUtf16(old_path.pathname()).c_str(),
252 ToUtf16(new_path.pathname()).c_str()) == 0) {
253 if (::GetLastError() != ERROR_NOT_SAME_DEVICE) {
254 LOG_GLE(LS_ERROR) << "Failed to move file";
255 return false;
256 }
257 if (!CopyFolder(old_path, new_path))
258 return false;
259 if (!DeleteFolderAndContents(old_path))
260 return false;
261 }
262 return true;
263}
264
265bool Win32Filesystem::IsFolder(const Pathname &path) {
266 WIN32_FILE_ATTRIBUTE_DATA data = {0};
267 if (0 == ::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(),
268 GetFileExInfoStandard, &data))
269 return false;
270 return (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ==
271 FILE_ATTRIBUTE_DIRECTORY;
272}
273
274bool Win32Filesystem::IsFile(const Pathname &path) {
275 WIN32_FILE_ATTRIBUTE_DATA data = {0};
276 if (0 == ::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(),
277 GetFileExInfoStandard, &data))
278 return false;
279 return (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
280}
281
282bool Win32Filesystem::IsAbsent(const Pathname& path) {
283 WIN32_FILE_ATTRIBUTE_DATA data = {0};
284 if (0 != ::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(),
285 GetFileExInfoStandard, &data))
286 return false;
287 DWORD err = ::GetLastError();
288 return (ERROR_FILE_NOT_FOUND == err || ERROR_PATH_NOT_FOUND == err);
289}
290
291bool Win32Filesystem::CopyFile(const Pathname &old_path,
292 const Pathname &new_path) {
293 return ::CopyFile(ToUtf16(old_path.pathname()).c_str(),
294 ToUtf16(new_path.pathname()).c_str(), TRUE) != 0;
295}
296
297bool Win32Filesystem::IsTemporaryPath(const Pathname& pathname) {
298 TCHAR buffer[MAX_PATH + 1];
tfarina5237aaf2015-11-10 23:44:30 -0800299 if (!::GetTempPath(arraysize(buffer), buffer))
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000300 return false;
301 if (!IsCurrentProcessLowIntegrity() &&
tfarina5237aaf2015-11-10 23:44:30 -0800302 !::GetLongPathName(buffer, buffer, arraysize(buffer)))
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000303 return false;
304 return (::strnicmp(ToUtf16(pathname.pathname()).c_str(),
305 buffer, strlen(buffer)) == 0);
306}
307
308bool Win32Filesystem::GetFileSize(const Pathname &pathname, size_t *size) {
309 WIN32_FILE_ATTRIBUTE_DATA data = {0};
310 if (::GetFileAttributesEx(ToUtf16(pathname.pathname()).c_str(),
311 GetFileExInfoStandard, &data) == 0)
312 return false;
313 *size = data.nFileSizeLow;
314 return true;
315}
316
317bool Win32Filesystem::GetFileTime(const Pathname& path, FileTimeType which,
318 time_t* time) {
319 WIN32_FILE_ATTRIBUTE_DATA data = {0};
320 if (::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(),
321 GetFileExInfoStandard, &data) == 0)
322 return false;
323 switch (which) {
324 case FTT_CREATED:
325 FileTimeToUnixTime(data.ftCreationTime, time);
326 break;
327 case FTT_MODIFIED:
328 FileTimeToUnixTime(data.ftLastWriteTime, time);
329 break;
330 case FTT_ACCESSED:
331 FileTimeToUnixTime(data.ftLastAccessTime, time);
332 break;
333 default:
334 return false;
335 }
336 return true;
337}
338
339bool Win32Filesystem::GetAppPathname(Pathname* path) {
340 TCHAR buffer[MAX_PATH + 1];
tfarina5237aaf2015-11-10 23:44:30 -0800341 if (0 == ::GetModuleFileName(NULL, buffer, arraysize(buffer)))
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000342 return false;
343 path->SetPathname(ToUtf8(buffer));
344 return true;
345}
346
347bool Win32Filesystem::GetAppDataFolder(Pathname* path, bool per_user) {
348 ASSERT(!organization_name_.empty());
349 ASSERT(!application_name_.empty());
350 TCHAR buffer[MAX_PATH + 1];
351 int csidl = per_user ? CSIDL_LOCAL_APPDATA : CSIDL_COMMON_APPDATA;
352 if (!::SHGetSpecialFolderPath(NULL, buffer, csidl, TRUE))
353 return false;
354 if (!IsCurrentProcessLowIntegrity() &&
tfarina5237aaf2015-11-10 23:44:30 -0800355 !::GetLongPathName(buffer, buffer, arraysize(buffer)))
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000356 return false;
tfarina5237aaf2015-11-10 23:44:30 -0800357 size_t len = strcatn(buffer, arraysize(buffer), __T("\\"));
358 len += strcpyn(buffer + len, arraysize(buffer) - len,
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000359 ToUtf16(organization_name_).c_str());
360 if ((len > 0) && (buffer[len-1] != __T('\\'))) {
tfarina5237aaf2015-11-10 23:44:30 -0800361 len += strcpyn(buffer + len, arraysize(buffer) - len, __T("\\"));
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000362 }
tfarina5237aaf2015-11-10 23:44:30 -0800363 len += strcpyn(buffer + len, arraysize(buffer) - len,
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000364 ToUtf16(application_name_).c_str());
365 if ((len > 0) && (buffer[len-1] != __T('\\'))) {
tfarina5237aaf2015-11-10 23:44:30 -0800366 len += strcpyn(buffer + len, arraysize(buffer) - len, __T("\\"));
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000367 }
tfarina5237aaf2015-11-10 23:44:30 -0800368 if (len >= arraysize(buffer) - 1)
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000369 return false;
370 path->clear();
371 path->SetFolder(ToUtf8(buffer));
372 return CreateFolder(*path);
373}
374
375bool Win32Filesystem::GetAppTempFolder(Pathname* path) {
376 if (!GetAppPathname(path))
377 return false;
378 std::string filename(path->filename());
379 return GetTemporaryFolder(*path, true, &filename);
380}
381
jlmiller@webrtc.orgea1c8422015-01-22 17:44:19 +0000382bool Win32Filesystem::GetDiskFreeSpace(const Pathname& path,
Peter Boström0c4e06b2015-10-07 12:23:21 +0200383 int64_t* free_bytes) {
jlmiller@webrtc.orgea1c8422015-01-22 17:44:19 +0000384 if (!free_bytes) {
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000385 return false;
386 }
387 char drive[4];
388 std::wstring drive16;
389 const wchar_t* target_drive = NULL;
390 if (path.GetDrive(drive, sizeof(drive))) {
391 drive16 = ToUtf16(drive);
392 target_drive = drive16.c_str();
393 } else if (path.folder().substr(0, 2) == "\\\\") {
394 // UNC path, fail.
395 // TODO: Handle UNC paths.
396 return false;
397 } else {
398 // The path is probably relative. GetDriveType and GetDiskFreeSpaceEx
399 // use the current drive if NULL is passed as the drive name.
400 // TODO: Add method to Pathname to determine if the path is relative.
401 // TODO: Add method to Pathname to convert a path to absolute.
402 }
jlmiller@webrtc.orgea1c8422015-01-22 17:44:19 +0000403 UINT drive_type = ::GetDriveType(target_drive);
404 if ((drive_type == DRIVE_REMOTE) || (drive_type == DRIVE_UNKNOWN)) {
405 LOG(LS_VERBOSE) << "Remote or unknown drive: " << drive;
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000406 return false;
407 }
408
Peter Boström0c4e06b2015-10-07 12:23:21 +0200409 int64_t total_number_of_bytes; // receives the number of bytes on disk
410 int64_t total_number_of_free_bytes; // receives the free bytes on disk
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000411 // make sure things won't change in 64 bit machine
412 // TODO replace with compile time assert
Peter Boström0c4e06b2015-10-07 12:23:21 +0200413 ASSERT(sizeof(ULARGE_INTEGER) == sizeof(uint64_t)); // NOLINT
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000414 if (::GetDiskFreeSpaceEx(target_drive,
jlmiller@webrtc.orgea1c8422015-01-22 17:44:19 +0000415 (PULARGE_INTEGER)free_bytes,
416 (PULARGE_INTEGER)&total_number_of_bytes,
417 (PULARGE_INTEGER)&total_number_of_free_bytes)) {
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000418 return true;
419 } else {
jlmiller@webrtc.orgea1c8422015-01-22 17:44:19 +0000420 LOG(LS_VERBOSE) << "GetDiskFreeSpaceEx returns error.";
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000421 return false;
422 }
423}
424
425Pathname Win32Filesystem::GetCurrentDirectory() {
426 Pathname cwd;
427 int path_len = 0;
428 scoped_ptr<wchar_t[]> path;
429 do {
430 int needed = ::GetCurrentDirectory(path_len, path.get());
431 if (needed == 0) {
432 // Error.
433 LOG_GLE(LS_ERROR) << "::GetCurrentDirectory() failed";
434 return cwd; // returns empty pathname
435 }
436 if (needed <= path_len) {
437 // It wrote successfully.
438 break;
439 }
440 // Else need to re-alloc for "needed".
441 path.reset(new wchar_t[needed]);
442 path_len = needed;
443 } while (true);
444 cwd.SetFolder(ToUtf8(path.get()));
445 return cwd;
446}
447
448// TODO: Consider overriding DeleteFolderAndContents for speed and potentially
449// better OS integration (recycle bin?)
450/*
451 std::wstring temp_path16 = ToUtf16(temp_path.pathname());
452 temp_path16.append(1, '*');
453 temp_path16.append(1, '\0');
454
455 SHFILEOPSTRUCT file_op = { 0 };
456 file_op.wFunc = FO_DELETE;
457 file_op.pFrom = temp_path16.c_str();
458 file_op.fFlags = FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT;
459 return (0 == SHFileOperation(&file_op));
460*/
461
462} // namespace rtc