blob: 5caeac2a32db7e2cc8fc93a186d0cbe732efd5f0 [file] [log] [blame]
henrike@webrtc.org0e118e72013-07-10 00:45:36 +00001/*
2 * libjingle
3 * Copyright 2004--2006, Google Inc.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
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.
15 *
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.
26 */
27
28#include "talk/base/win32filesystem.h"
29
30#include "talk/base/win32.h"
31#include <shellapi.h>
32#include <shlobj.h>
33#include <tchar.h>
34
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"
40
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.
47
48namespace talk_base {
49
50bool Win32Filesystem::CreateFolder(const Pathname &pathname) {
51 if (pathname.pathname().empty() || !pathname.filename().empty())
52 return false;
53
54 std::wstring path16;
55 if (!Utf8ToWindowsFilename(pathname.pathname(), &path16))
56 return false;
57
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)) {
64 // Unexpected error
65 return false;
66 }
67
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)) {
73 return false;
74 }
75 }
76
77 return (::CreateDirectory(path16.c_str(), NULL) != 0);
78}
79
80FileStream *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)) {
84 delete fs;
85 fs = NULL;
86 }
87 return fs;
88}
89
90bool 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
94
95 // Get the current process token.
96 HANDLE process_token = INVALID_HANDLE_VALUE;
97 if (!::OpenProcessToken(::GetCurrentProcess(),
98 TOKEN_QUERY,
99 &process_token)) {
100 LOG_ERR(LS_ERROR) << "OpenProcessToken() failed";
101 return false;
102 }
103
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,
108 TokenUser,
109 NULL,
110 0,
111 &token_user_size);
112
113 // Get the TOKEN_USER structure.
wu@webrtc.org5c9dd592013-10-25 21:18:33 +0000114 scoped_ptr<char[]> token_user_bytes(new char[token_user_size]);
henrike@webrtc.org0e118e72013-07-10 00:45:36 +0000115 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,
119 TokenUser,
120 token_user,
121 token_user_size,
122 &token_user_size);
123 // We're now done with this.
124 ::CloseHandle(process_token);
125 if (!success) {
126 LOG_ERR(LS_ERROR) << "GetTokenInformation() failed";
127 return false;
128 }
129
130 if (!IsValidSid(token_user->User.Sid)) {
131 LOG_ERR(LS_ERROR) << "Current process has invalid user SID";
132 return false;
133 }
134
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);
138
139 // Allocate it.
wu@webrtc.org5c9dd592013-10-25 21:18:33 +0000140 scoped_ptr<char[]> acl_bytes(new char[acl_size]);
henrike@webrtc.org0e118e72013-07-10 00:45:36 +0000141 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";
145 return false;
146 }
147
148 // Allow access to only the current user.
149 if (!::AddAccessAllowedAce(acl,
150 ACL_REVISION,
151 GENERIC_READ | GENERIC_WRITE | STANDARD_RIGHTS_ALL,
152 token_user->User.Sid)) {
153 LOG_ERR(LS_ERROR) << "AddAccessAllowedAce() failed";
154 return false;
155 }
156
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";
162 return false;
163 }
164
165 // Put the ACL in it.
166 if (!::SetSecurityDescriptorDacl(&security_descriptor,
167 TRUE,
168 acl,
169 FALSE)) {
170 LOG_ERR(LS_ERROR) << "SetSecurityDescriptorDacl() failed";
171 return false;
172 }
173
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,
184 CREATE_NEW,
185 0,
186 NULL);
187 if (INVALID_HANDLE_VALUE == handle) {
188 LOG_ERR(LS_ERROR) << "CreateFile() failed";
189 return false;
190 }
191 if (!::CloseHandle(handle)) {
192 LOG_ERR(LS_ERROR) << "CloseFile() failed";
193 // Continue.
194 }
195 return true;
196}
197
198bool Win32Filesystem::DeleteFile(const Pathname &filename) {
199 LOG(LS_INFO) << "Deleting file " << filename.pathname();
200 if (!IsFile(filename)) {
201 ASSERT(IsFile(filename));
202 return false;
203 }
204 return ::DeleteFile(ToUtf16(filename.pathname()).c_str()) != 0;
205}
206
207bool Win32Filesystem::DeleteEmptyFolder(const Pathname &folder) {
208 LOG(LS_INFO) << "Deleting folder " << folder.pathname();
209
210 std::string no_slash(folder.pathname(), 0, folder.pathname().length()-1);
211 return ::RemoveDirectory(ToUtf16(no_slash).c_str()) != 0;
212}
213
214bool 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))
218 return false;
219 if (!IsCurrentProcessLowIntegrity() &&
220 !::GetLongPathName(buffer, buffer, ARRAY_SIZE(buffer)))
221 return false;
222 size_t len = strlen(buffer);
223 if ((len > 0) && (buffer[len-1] != '\\')) {
224 len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, L"\\");
225 }
226 if (len >= ARRAY_SIZE(buffer) - 1)
227 return false;
228 pathname.clear();
229 pathname.SetFolder(ToUtf8(buffer));
230 if (append != NULL) {
231 ASSERT(!append->empty());
232 pathname.AppendFolder(*append);
233 }
234 return !create || CreateFolder(pathname);
235}
236
237std::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);
243 ASSERT(false);
244 return "";
245}
246
247bool Win32Filesystem::MoveFile(const Pathname &old_path,
248 const Pathname &new_path) {
249 if (!IsFile(old_path)) {
250 ASSERT(IsFile(old_path));
251 return false;
252 }
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;
257}
258
259bool Win32Filesystem::MoveFolder(const Pathname &old_path,
260 const Pathname &new_path) {
261 if (!IsFolder(old_path)) {
262 ASSERT(IsFolder(old_path));
263 return false;
264 }
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";
271 return false;
272 }
273 if (!CopyFolder(old_path, new_path))
274 return false;
275 if (!DeleteFolderAndContents(old_path))
276 return false;
277 }
278 return true;
279}
280
281bool Win32Filesystem::IsFolder(const Pathname &path) {
282 WIN32_FILE_ATTRIBUTE_DATA data = {0};
283 if (0 == ::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(),
284 GetFileExInfoStandard, &data))
285 return false;
286 return (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ==
287 FILE_ATTRIBUTE_DIRECTORY;
288}
289
290bool Win32Filesystem::IsFile(const Pathname &path) {
291 WIN32_FILE_ATTRIBUTE_DATA data = {0};
292 if (0 == ::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(),
293 GetFileExInfoStandard, &data))
294 return false;
295 return (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
296}
297
298bool Win32Filesystem::IsAbsent(const Pathname& path) {
299 WIN32_FILE_ATTRIBUTE_DATA data = {0};
300 if (0 != ::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(),
301 GetFileExInfoStandard, &data))
302 return false;
303 DWORD err = ::GetLastError();
304 return (ERROR_FILE_NOT_FOUND == err || ERROR_PATH_NOT_FOUND == err);
305}
306
307bool 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;
311}
312
313bool Win32Filesystem::IsTemporaryPath(const Pathname& pathname) {
314 TCHAR buffer[MAX_PATH + 1];
315 if (!::GetTempPath(ARRAY_SIZE(buffer), buffer))
316 return false;
317 if (!IsCurrentProcessLowIntegrity() &&
318 !::GetLongPathName(buffer, buffer, ARRAY_SIZE(buffer)))
319 return false;
320 return (::strnicmp(ToUtf16(pathname.pathname()).c_str(),
321 buffer, strlen(buffer)) == 0);
322}
323
324bool 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)
328 return false;
329 *size = data.nFileSizeLow;
330 return true;
331}
332
333bool Win32Filesystem::GetFileTime(const Pathname& path, FileTimeType which,
334 time_t* time) {
335 WIN32_FILE_ATTRIBUTE_DATA data = {0};
336 if (::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(),
337 GetFileExInfoStandard, &data) == 0)
338 return false;
339 switch (which) {
340 case FTT_CREATED:
341 FileTimeToUnixTime(data.ftCreationTime, time);
342 break;
343 case FTT_MODIFIED:
344 FileTimeToUnixTime(data.ftLastWriteTime, time);
345 break;
346 case FTT_ACCESSED:
347 FileTimeToUnixTime(data.ftLastAccessTime, time);
348 break;
349 default:
350 return false;
351 }
352 return true;
353}
354
355bool Win32Filesystem::GetAppPathname(Pathname* path) {
356 TCHAR buffer[MAX_PATH + 1];
357 if (0 == ::GetModuleFileName(NULL, buffer, ARRAY_SIZE(buffer)))
358 return false;
359 path->SetPathname(ToUtf8(buffer));
360 return true;
361}
362
363bool 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))
369 return false;
370 if (!IsCurrentProcessLowIntegrity() &&
371 !::GetLongPathName(buffer, buffer, ARRAY_SIZE(buffer)))
372 return false;
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("\\"));
378 }
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("\\"));
383 }
384 if (len >= ARRAY_SIZE(buffer) - 1)
385 return false;
386 path->clear();
387 path->SetFolder(ToUtf8(buffer));
388 return CreateFolder(*path);
389}
390
391bool Win32Filesystem::GetAppTempFolder(Pathname* path) {
392 if (!GetAppPathname(path))
393 return false;
394 std::string filename(path->filename());
395 return GetTemporaryFolder(*path, true, &filename);
396}
397
398bool Win32Filesystem::GetDiskFreeSpace(const Pathname& path, int64 *freebytes) {
399 if (!freebytes) {
400 return false;
401 }
402 char drive[4];
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) == "\\\\") {
409 // UNC path, fail.
410 // TODO: Handle UNC paths.
411 return false;
412 } else {
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.
417 }
418 UINT driveType = ::GetDriveType(target_drive);
419 if ( (driveType & DRIVE_REMOTE) || (driveType & DRIVE_UNKNOWN) ) {
420 LOG(LS_VERBOSE) << " remove or unknown drive " << drive;
421 return false;
422 }
423
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)) {
433 return true;
434 } else {
435 LOG(LS_VERBOSE) << " GetDiskFreeSpaceEx returns error ";
436 return false;
437 }
438}
439
440Pathname Win32Filesystem::GetCurrentDirectory() {
441 Pathname cwd;
442 int path_len = 0;
wu@webrtc.org5c9dd592013-10-25 21:18:33 +0000443 scoped_ptr<wchar_t[]> path;
henrike@webrtc.org0e118e72013-07-10 00:45:36 +0000444 do {
445 int needed = ::GetCurrentDirectory(path_len, path.get());
446 if (needed == 0) {
447 // Error.
448 LOG_GLE(LS_ERROR) << "::GetCurrentDirectory() failed";
449 return cwd; // returns empty pathname
450 }
451 if (needed <= path_len) {
452 // It wrote successfully.
453 break;
454 }
455 // Else need to re-alloc for "needed".
456 path.reset(new wchar_t[needed]);
457 path_len = needed;
458 } while (true);
459 cwd.SetFolder(ToUtf8(path.get()));
460 return cwd;
461}
462
463// TODO: Consider overriding DeleteFolderAndContents for speed and potentially
464// better OS integration (recycle bin?)
465/*
466 std::wstring temp_path16 = ToUtf16(temp_path.pathname());
467 temp_path16.append(1, '*');
468 temp_path16.append(1, '\0');
469
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));
475*/
476
477} // namespace talk_base