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