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(), NULL)) { 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_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, 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_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"; 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_ptr<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