| 1 | /* |
| 2 | * Copyright (C) 2007, 2009 Holger Hans Peter Freyther |
| 3 | * Copyright (C) 2008 Collabora, Ltd. |
| 4 | * Copyright (C) 2008 Apple Inc. All rights reserved. |
| 5 | * Portions Copyright (c) 2010 Motorola Mobility, Inc. All rights reserved. |
| 6 | * |
| 7 | * This library is free software; you can redistribute it and/or |
| 8 | * modify it under the terms of the GNU Library General Public |
| 9 | * License as published by the Free Software Foundation; either |
| 10 | * version 2 of the License, or (at your option) any later version. |
| 11 | * |
| 12 | * This library is distributed in the hope that it will be useful, |
| 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 15 | * Library General Public License for more details. |
| 16 | * |
| 17 | * You should have received a copy of the GNU Library General Public License |
| 18 | * along with this library; see the file COPYING.LIB. If not, write to |
| 19 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| 20 | * Boston, MA 02110-1301, USA. |
| 21 | */ |
| 22 | |
| 23 | #include "config.h" |
| 24 | #include <wtf/FileSystem.h> |
| 25 | |
| 26 | #include <gio/gfiledescriptorbased.h> |
| 27 | #include <gio/gio.h> |
| 28 | #include <glib.h> |
| 29 | #include <glib/gstdio.h> |
| 30 | #include <sys/file.h> |
| 31 | #include <wtf/EnumTraits.h> |
| 32 | #include <wtf/FileMetadata.h> |
| 33 | #include <wtf/UUID.h> |
| 34 | #include <wtf/glib/GLibUtilities.h> |
| 35 | #include <wtf/glib/GRefPtr.h> |
| 36 | #include <wtf/glib/GUniquePtr.h> |
| 37 | #include <wtf/text/ASCIIFastPath.h> |
| 38 | #include <wtf/text/CString.h> |
| 39 | #include <wtf/text/StringBuilder.h> |
| 40 | #include <wtf/text/WTFString.h> |
| 41 | |
| 42 | namespace WTF { |
| 43 | |
| 44 | namespace FileSystemImpl { |
| 45 | |
| 46 | String stringFromFileSystemRepresentation(const char* representation) |
| 47 | { |
| 48 | if (!representation) |
| 49 | return { }; |
| 50 | |
| 51 | // Short-cut to String creation when only ASCII characters are used. |
| 52 | size_t representationLength = strlen(representation); |
| 53 | if (charactersAreAllASCII(reinterpret_cast<const LChar*>(representation), representationLength)) |
| 54 | return String(representation, representationLength); |
| 55 | |
| 56 | // If the returned charset is UTF-8 (i.e. g_get_filename_charsets() returns true), |
| 57 | // go directly to String creation. |
| 58 | const gchar** filenameCharsets = nullptr; |
| 59 | if (g_get_filename_charsets(&filenameCharsets)) |
| 60 | return String::fromUTF8(representation, representationLength); |
| 61 | |
| 62 | ASSERT(filenameCharsets); |
| 63 | // FIXME: If possible, we'd want to convert directly to UTF-16 and construct |
| 64 | // WTF::String object with resulting data. |
| 65 | size_t utf8Length = 0; |
| 66 | GUniquePtr<gchar> utf8(g_convert(representation, representationLength, |
| 67 | "UTF-8" , filenameCharsets[0], nullptr, &utf8Length, nullptr)); |
| 68 | if (!utf8) |
| 69 | return { }; |
| 70 | |
| 71 | return String::fromUTF8(utf8.get(), utf8Length); |
| 72 | } |
| 73 | |
| 74 | CString fileSystemRepresentation(const String& path) |
| 75 | { |
| 76 | if (path.isEmpty()) |
| 77 | return { }; |
| 78 | |
| 79 | CString utf8 = path.utf8(); |
| 80 | |
| 81 | // If the returned charset is UTF-8 (i.e. g_get_filename_charsets() returns true), |
| 82 | // simply return the CString object. |
| 83 | const gchar** filenameCharsets = nullptr; |
| 84 | if (g_get_filename_charsets(&filenameCharsets)) |
| 85 | return utf8; |
| 86 | |
| 87 | ASSERT(filenameCharsets); |
| 88 | // FIXME: If possible, we'd want to convert directly from WTF::String's UTF-16 data. |
| 89 | size_t representationLength = 0; |
| 90 | GUniquePtr<gchar> representation(g_convert(utf8.data(), utf8.length(), |
| 91 | filenameCharsets[0], "UTF-8" , nullptr, &representationLength, nullptr)); |
| 92 | if (!representation) |
| 93 | return { }; |
| 94 | |
| 95 | return CString(representation.get(), representationLength); |
| 96 | } |
| 97 | |
| 98 | bool validRepresentation(const CString& representation) |
| 99 | { |
| 100 | auto* data = representation.data(); |
| 101 | return !!data && data[0] != '\0'; |
| 102 | } |
| 103 | |
| 104 | // Converts a string to something suitable to be displayed to the user. |
| 105 | String filenameForDisplay(const String& string) |
| 106 | { |
| 107 | #if OS(WINDOWS) |
| 108 | return string; |
| 109 | #else |
| 110 | auto filename = fileSystemRepresentation(string); |
| 111 | if (!validRepresentation(filename)) |
| 112 | return string; |
| 113 | |
| 114 | GUniquePtr<gchar> display(g_filename_display_name(filename.data())); |
| 115 | if (!display) |
| 116 | return string; |
| 117 | return String::fromUTF8(display.get()); |
| 118 | #endif |
| 119 | } |
| 120 | |
| 121 | bool fileExists(const String& path) |
| 122 | { |
| 123 | auto filename = fileSystemRepresentation(path); |
| 124 | return validRepresentation(filename) ? g_file_test(filename.data(), G_FILE_TEST_EXISTS) : false; |
| 125 | } |
| 126 | |
| 127 | bool deleteFile(const String& path) |
| 128 | { |
| 129 | auto filename = fileSystemRepresentation(path); |
| 130 | return validRepresentation(filename) ? g_remove(filename.data()) != -1 : false; |
| 131 | } |
| 132 | |
| 133 | bool deleteEmptyDirectory(const String& path) |
| 134 | { |
| 135 | auto filename = fileSystemRepresentation(path); |
| 136 | return validRepresentation(filename) ? g_rmdir(filename.data()) != -1 : false; |
| 137 | } |
| 138 | |
| 139 | static bool getFileStat(const String& path, GStatBuf* statBuffer) |
| 140 | { |
| 141 | auto filename = fileSystemRepresentation(path); |
| 142 | if (!validRepresentation(filename)) |
| 143 | return false; |
| 144 | |
| 145 | return g_stat(filename.data(), statBuffer) != -1; |
| 146 | } |
| 147 | |
| 148 | static bool getFileLStat(const String& path, GStatBuf* statBuffer) |
| 149 | { |
| 150 | auto filename = fileSystemRepresentation(path); |
| 151 | if (!validRepresentation(filename)) |
| 152 | return false; |
| 153 | |
| 154 | return g_lstat(filename.data(), statBuffer) != -1; |
| 155 | } |
| 156 | |
| 157 | bool getFileSize(const String& path, long long& resultSize) |
| 158 | { |
| 159 | GStatBuf statResult; |
| 160 | if (!getFileStat(path, &statResult)) |
| 161 | return false; |
| 162 | |
| 163 | resultSize = statResult.st_size; |
| 164 | return true; |
| 165 | } |
| 166 | |
| 167 | bool getFileSize(PlatformFileHandle handle, long long& resultSize) |
| 168 | { |
| 169 | auto info = g_file_io_stream_query_info(handle, G_FILE_ATTRIBUTE_STANDARD_SIZE, nullptr, nullptr); |
| 170 | if (!info) |
| 171 | return false; |
| 172 | |
| 173 | resultSize = g_file_info_get_size(info); |
| 174 | return true; |
| 175 | } |
| 176 | |
| 177 | Optional<WallTime> getFileCreationTime(const String&) |
| 178 | { |
| 179 | // FIXME: Is there a way to retrieve file creation time with Gtk on platforms that support it? |
| 180 | return WTF::nullopt; |
| 181 | } |
| 182 | |
| 183 | Optional<WallTime> getFileModificationTime(const String& path) |
| 184 | { |
| 185 | GStatBuf statResult; |
| 186 | if (!getFileStat(path, &statResult)) |
| 187 | return WTF::nullopt; |
| 188 | |
| 189 | return WallTime::fromRawSeconds(statResult.st_mtime); |
| 190 | } |
| 191 | |
| 192 | static FileMetadata::Type toFileMetataType(GStatBuf statResult) |
| 193 | { |
| 194 | if (S_ISDIR(statResult.st_mode)) |
| 195 | return FileMetadata::Type::Directory; |
| 196 | if (S_ISLNK(statResult.st_mode)) |
| 197 | return FileMetadata::Type::SymbolicLink; |
| 198 | return FileMetadata::Type::File; |
| 199 | } |
| 200 | |
| 201 | static Optional<FileMetadata> fileMetadataUsingFunction(const String& path, bool (*statFunc)(const String&, GStatBuf*)) |
| 202 | { |
| 203 | GStatBuf statResult; |
| 204 | if (!statFunc(path, &statResult)) |
| 205 | return WTF::nullopt; |
| 206 | |
| 207 | String filename = pathGetFileName(path); |
| 208 | bool isHidden = !filename.isEmpty() && filename[0] == '.'; |
| 209 | |
| 210 | return FileMetadata { |
| 211 | WallTime::fromRawSeconds(statResult.st_mtime), |
| 212 | statResult.st_size, |
| 213 | isHidden, |
| 214 | toFileMetataType(statResult) |
| 215 | }; |
| 216 | } |
| 217 | |
| 218 | Optional<FileMetadata> fileMetadata(const String& path) |
| 219 | { |
| 220 | return fileMetadataUsingFunction(path, &getFileLStat); |
| 221 | } |
| 222 | |
| 223 | Optional<FileMetadata> fileMetadataFollowingSymlinks(const String& path) |
| 224 | { |
| 225 | return fileMetadataUsingFunction(path, &getFileStat); |
| 226 | } |
| 227 | |
| 228 | String pathByAppendingComponent(const String& path, const String& component) |
| 229 | { |
| 230 | if (path.endsWith(G_DIR_SEPARATOR_S)) |
| 231 | return path + component; |
| 232 | return path + G_DIR_SEPARATOR_S + component; |
| 233 | } |
| 234 | |
| 235 | String pathByAppendingComponents(StringView path, const Vector<StringView>& components) |
| 236 | { |
| 237 | StringBuilder builder; |
| 238 | builder.append(path); |
| 239 | for (auto& component : components) { |
| 240 | builder.append(G_DIR_SEPARATOR_S); |
| 241 | builder.append(component); |
| 242 | } |
| 243 | return builder.toString(); |
| 244 | } |
| 245 | |
| 246 | bool makeAllDirectories(const String& path) |
| 247 | { |
| 248 | auto filename = fileSystemRepresentation(path); |
| 249 | return validRepresentation(filename) ? g_mkdir_with_parents(filename.data(), S_IRWXU) != -1 : false; |
| 250 | } |
| 251 | |
| 252 | String homeDirectoryPath() |
| 253 | { |
| 254 | return stringFromFileSystemRepresentation(g_get_home_dir()); |
| 255 | } |
| 256 | |
| 257 | bool createSymbolicLink(const String& targetPath, const String& symbolicLinkPath) |
| 258 | { |
| 259 | CString targetPathFSRep = fileSystemRepresentation(targetPath); |
| 260 | if (!validRepresentation(targetPathFSRep)) |
| 261 | return false; |
| 262 | |
| 263 | CString symbolicLinkPathFSRep = fileSystemRepresentation(symbolicLinkPath); |
| 264 | if (!validRepresentation(symbolicLinkPathFSRep)) |
| 265 | return false; |
| 266 | |
| 267 | return !symlink(targetPathFSRep.data(), symbolicLinkPathFSRep.data()); |
| 268 | } |
| 269 | |
| 270 | String pathGetFileName(const String& path) |
| 271 | { |
| 272 | auto filename = fileSystemRepresentation(path); |
| 273 | if (!validRepresentation(filename)) |
| 274 | return path; |
| 275 | |
| 276 | GUniquePtr<gchar> baseName(g_path_get_basename(filename.data())); |
| 277 | return String::fromUTF8(baseName.get()); |
| 278 | } |
| 279 | |
| 280 | bool getVolumeFreeSpace(const String& path, uint64_t& freeSpace) |
| 281 | { |
| 282 | auto filename = fileSystemRepresentation(path); |
| 283 | if (!validRepresentation(filename)) |
| 284 | return false; |
| 285 | |
| 286 | GRefPtr<GFile> file = adoptGRef(g_file_new_for_path(filename.data())); |
| 287 | GRefPtr<GFileInfo> fileInfo = adoptGRef(g_file_query_filesystem_info(file.get(), G_FILE_ATTRIBUTE_FILESYSTEM_FREE, 0, 0)); |
| 288 | if (!fileInfo) |
| 289 | return false; |
| 290 | |
| 291 | freeSpace = g_file_info_get_attribute_uint64(fileInfo.get(), G_FILE_ATTRIBUTE_FILESYSTEM_FREE); |
| 292 | return !!freeSpace; |
| 293 | } |
| 294 | |
| 295 | String directoryName(const String& path) |
| 296 | { |
| 297 | auto filename = fileSystemRepresentation(path); |
| 298 | if (!validRepresentation(filename)) |
| 299 | return String(); |
| 300 | |
| 301 | GUniquePtr<char> dirname(g_path_get_dirname(filename.data())); |
| 302 | return String::fromUTF8(dirname.get()); |
| 303 | } |
| 304 | |
| 305 | Vector<String> listDirectory(const String& path, const String& filter) |
| 306 | { |
| 307 | Vector<String> entries; |
| 308 | |
| 309 | auto filename = fileSystemRepresentation(path); |
| 310 | if (!validRepresentation(filename)) |
| 311 | return entries; |
| 312 | |
| 313 | GUniquePtr<GDir> dir(g_dir_open(filename.data(), 0, nullptr)); |
| 314 | if (!dir) |
| 315 | return entries; |
| 316 | |
| 317 | GUniquePtr<GPatternSpec> pspec(g_pattern_spec_new((filter.utf8()).data())); |
| 318 | while (const char* name = g_dir_read_name(dir.get())) { |
| 319 | if (!g_pattern_match_string(pspec.get(), name)) |
| 320 | continue; |
| 321 | |
| 322 | GUniquePtr<gchar> entry(g_build_filename(filename.data(), name, nullptr)); |
| 323 | entries.append(stringFromFileSystemRepresentation(entry.get())); |
| 324 | } |
| 325 | |
| 326 | return entries; |
| 327 | } |
| 328 | |
| 329 | String openTemporaryFile(const String& prefix, PlatformFileHandle& handle) |
| 330 | { |
| 331 | GUniquePtr<gchar> filename(g_strdup_printf("%s%s" , prefix.utf8().data(), createCanonicalUUIDString().utf8().data())); |
| 332 | GUniquePtr<gchar> tempPath(g_build_filename(g_get_tmp_dir(), filename.get(), NULL)); |
| 333 | GRefPtr<GFile> file = adoptGRef(g_file_new_for_path(tempPath.get())); |
| 334 | |
| 335 | handle = g_file_create_readwrite(file.get(), G_FILE_CREATE_NONE, 0, 0); |
| 336 | if (!isHandleValid(handle)) |
| 337 | return String(); |
| 338 | return String::fromUTF8(tempPath.get()); |
| 339 | } |
| 340 | |
| 341 | PlatformFileHandle openFile(const String& path, FileOpenMode mode) |
| 342 | { |
| 343 | auto filename = fileSystemRepresentation(path); |
| 344 | if (!validRepresentation(filename)) |
| 345 | return invalidPlatformFileHandle; |
| 346 | |
| 347 | GRefPtr<GFile> file = adoptGRef(g_file_new_for_path(filename.data())); |
| 348 | GFileIOStream* ioStream = 0; |
| 349 | if (mode == FileOpenMode::Read) |
| 350 | ioStream = g_file_open_readwrite(file.get(), 0, 0); |
| 351 | else if (mode == FileOpenMode::Write) { |
| 352 | if (g_file_test(filename.data(), static_cast<GFileTest>(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))) |
| 353 | ioStream = g_file_open_readwrite(file.get(), 0, 0); |
| 354 | else |
| 355 | ioStream = g_file_create_readwrite(file.get(), G_FILE_CREATE_NONE, 0, 0); |
| 356 | } |
| 357 | |
| 358 | return ioStream; |
| 359 | } |
| 360 | |
| 361 | void closeFile(PlatformFileHandle& handle) |
| 362 | { |
| 363 | if (!isHandleValid(handle)) |
| 364 | return; |
| 365 | |
| 366 | g_io_stream_close(G_IO_STREAM(handle), 0, 0); |
| 367 | g_object_unref(handle); |
| 368 | handle = invalidPlatformFileHandle; |
| 369 | } |
| 370 | |
| 371 | long long seekFile(PlatformFileHandle handle, long long offset, FileSeekOrigin origin) |
| 372 | { |
| 373 | GSeekType seekType = G_SEEK_SET; |
| 374 | switch (origin) { |
| 375 | case FileSeekOrigin::Beginning: |
| 376 | seekType = G_SEEK_SET; |
| 377 | break; |
| 378 | case FileSeekOrigin::Current: |
| 379 | seekType = G_SEEK_CUR; |
| 380 | break; |
| 381 | case FileSeekOrigin::End: |
| 382 | seekType = G_SEEK_END; |
| 383 | break; |
| 384 | default: |
| 385 | ASSERT_NOT_REACHED(); |
| 386 | } |
| 387 | |
| 388 | if (!g_seekable_seek(G_SEEKABLE(g_io_stream_get_input_stream(G_IO_STREAM(handle))), |
| 389 | offset, seekType, 0, 0)) |
| 390 | { |
| 391 | return -1; |
| 392 | } |
| 393 | return g_seekable_tell(G_SEEKABLE(g_io_stream_get_input_stream(G_IO_STREAM(handle)))); |
| 394 | } |
| 395 | |
| 396 | int writeToFile(PlatformFileHandle handle, const char* data, int length) |
| 397 | { |
| 398 | gsize bytesWritten; |
| 399 | g_output_stream_write_all(g_io_stream_get_output_stream(G_IO_STREAM(handle)), |
| 400 | data, length, &bytesWritten, 0, 0); |
| 401 | return bytesWritten; |
| 402 | } |
| 403 | |
| 404 | int readFromFile(PlatformFileHandle handle, char* data, int length) |
| 405 | { |
| 406 | GUniqueOutPtr<GError> error; |
| 407 | do { |
| 408 | gssize bytesRead = g_input_stream_read(g_io_stream_get_input_stream(G_IO_STREAM(handle)), |
| 409 | data, length, 0, &error.outPtr()); |
| 410 | if (bytesRead >= 0) |
| 411 | return bytesRead; |
| 412 | } while (error && error->code == G_FILE_ERROR_INTR); |
| 413 | return -1; |
| 414 | } |
| 415 | |
| 416 | bool moveFile(const String& oldPath, const String& newPath) |
| 417 | { |
| 418 | auto oldFilename = fileSystemRepresentation(oldPath); |
| 419 | if (!validRepresentation(oldFilename)) |
| 420 | return false; |
| 421 | |
| 422 | auto newFilename = fileSystemRepresentation(newPath); |
| 423 | if (!validRepresentation(newFilename)) |
| 424 | return false; |
| 425 | |
| 426 | GRefPtr<GFile> oldFile = adoptGRef(g_file_new_for_path(oldFilename.data())); |
| 427 | GRefPtr<GFile> newFile = adoptGRef(g_file_new_for_path(newFilename.data())); |
| 428 | |
| 429 | return g_file_move(oldFile.get(), newFile.get(), G_FILE_COPY_OVERWRITE, nullptr, nullptr, nullptr, nullptr); |
| 430 | } |
| 431 | |
| 432 | bool hardLink(const String& source, const String& destination) |
| 433 | { |
| 434 | #if OS(WINDOWS) |
| 435 | return CreateHardLink(destination.wideCharacters().data(), source.wideCharacters().data(), nullptr); |
| 436 | #else |
| 437 | auto sourceFilename = fileSystemRepresentation(source); |
| 438 | if (!validRepresentation(sourceFilename)) |
| 439 | return false; |
| 440 | |
| 441 | auto destinationFilename = fileSystemRepresentation(destination); |
| 442 | if (!validRepresentation(destinationFilename)) |
| 443 | return false; |
| 444 | |
| 445 | return !link(sourceFilename.data(), destinationFilename.data()); |
| 446 | #endif |
| 447 | } |
| 448 | |
| 449 | bool hardLinkOrCopyFile(const String& source, const String& destination) |
| 450 | { |
| 451 | if (hardLink(source, destination)) |
| 452 | return true; |
| 453 | |
| 454 | // Hard link failed. Perform a copy instead. |
| 455 | #if OS(WINDOWS) |
| 456 | return !!::CopyFile(source.wideCharacters().data(), destination.wideCharacters().data(), TRUE); |
| 457 | #else |
| 458 | auto sourceFilename = fileSystemRepresentation(source); |
| 459 | if (!validRepresentation(sourceFilename)) |
| 460 | return false; |
| 461 | |
| 462 | auto destinationFilename = fileSystemRepresentation(destination); |
| 463 | if (!validRepresentation(destinationFilename)) |
| 464 | return false; |
| 465 | |
| 466 | GRefPtr<GFile> sourceFile = adoptGRef(g_file_new_for_path(sourceFilename.data())); |
| 467 | GRefPtr<GFile> destinationFile = adoptGRef(g_file_new_for_path(destinationFilename.data())); |
| 468 | return g_file_copy(sourceFile.get(), destinationFile.get(), G_FILE_COPY_NONE, nullptr, nullptr, nullptr, nullptr); |
| 469 | #endif |
| 470 | } |
| 471 | |
| 472 | Optional<int32_t> getFileDeviceId(const CString& fsFile) |
| 473 | { |
| 474 | GRefPtr<GFile> file = adoptGRef(g_file_new_for_path(fsFile.data())); |
| 475 | GRefPtr<GFileInfo> fileInfo = adoptGRef(g_file_query_filesystem_info(file.get(), G_FILE_ATTRIBUTE_UNIX_DEVICE, nullptr, nullptr)); |
| 476 | if (!fileInfo) |
| 477 | return WTF::nullopt; |
| 478 | |
| 479 | return g_file_info_get_attribute_uint32(fileInfo.get(), G_FILE_ATTRIBUTE_UNIX_DEVICE); |
| 480 | } |
| 481 | |
| 482 | #if USE(FILE_LOCK) |
| 483 | bool lockFile(PlatformFileHandle handle, OptionSet<FileLockMode> lockMode) |
| 484 | { |
| 485 | COMPILE_ASSERT(LOCK_SH == WTF::enumToUnderlyingType(FileLockMode::Shared), LockSharedEncodingIsAsExpected); |
| 486 | COMPILE_ASSERT(LOCK_EX == WTF::enumToUnderlyingType(FileLockMode::Exclusive), LockExclusiveEncodingIsAsExpected); |
| 487 | COMPILE_ASSERT(LOCK_NB == WTF::enumToUnderlyingType(FileLockMode::Nonblocking), LockNonblockingEncodingIsAsExpected); |
| 488 | auto* inputStream = g_io_stream_get_input_stream(G_IO_STREAM(handle)); |
| 489 | int result = flock(g_file_descriptor_based_get_fd(G_FILE_DESCRIPTOR_BASED(inputStream)), lockMode.toRaw()); |
| 490 | return result != -1; |
| 491 | } |
| 492 | |
| 493 | bool unlockFile(PlatformFileHandle handle) |
| 494 | { |
| 495 | auto* inputStream = g_io_stream_get_input_stream(G_IO_STREAM(handle)); |
| 496 | int result = flock(g_file_descriptor_based_get_fd(G_FILE_DESCRIPTOR_BASED(inputStream)), LOCK_UN); |
| 497 | return result != -1; |
| 498 | } |
| 499 | #endif // USE(FILE_LOCK) |
| 500 | |
| 501 | } // namespace FileSystemImpl |
| 502 | } // namespace WTF |
| 503 | |