Home | History | Annotate | Download | only in base
      1 /*
      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 
     48 namespace talk_base {
     49 
     50 bool 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 
     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())) {
     84     delete fs;
     85     fs = NULL;
     86   }
     87   return fs;
     88 }
     89 
     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
     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.
    114   scoped_array<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,
    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.
    140   scoped_array<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";
    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 
    198 bool 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 
    207 bool 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 
    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))
    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 
    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);
    243   ASSERT(false);
    244   return "";
    245 }
    246 
    247 bool 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 
    259 bool 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 
    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))
    285     return false;
    286   return (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ==
    287       FILE_ATTRIBUTE_DIRECTORY;
    288 }
    289 
    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))
    294     return false;
    295   return (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
    296 }
    297 
    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))
    302     return false;
    303   DWORD err = ::GetLastError();
    304   return (ERROR_FILE_NOT_FOUND == err || ERROR_PATH_NOT_FOUND == err);
    305 }
    306 
    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;
    311 }
    312 
    313 bool 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 
    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)
    328   return false;
    329   *size = data.nFileSizeLow;
    330   return true;
    331 }
    332 
    333 bool 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 
    355 bool 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 
    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))
    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 
    391 bool 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 
    398 bool 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 
    440 Pathname Win32Filesystem::GetCurrentDirectory() {
    441   Pathname cwd;
    442   int path_len = 0;
    443   scoped_array<wchar_t> path;
    444   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
    478