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
42namespace WTF {
43
44namespace FileSystemImpl {
45
46String 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
74CString 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
98bool 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.
105String 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
121bool 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
127bool deleteFile(const String& path)
128{
129 auto filename = fileSystemRepresentation(path);
130 return validRepresentation(filename) ? g_remove(filename.data()) != -1 : false;
131}
132
133bool deleteEmptyDirectory(const String& path)
134{
135 auto filename = fileSystemRepresentation(path);
136 return validRepresentation(filename) ? g_rmdir(filename.data()) != -1 : false;
137}
138
139static 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
148static 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
157bool 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
167bool 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
177Optional<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
183Optional<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
192static 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
201static 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
218Optional<FileMetadata> fileMetadata(const String& path)
219{
220 return fileMetadataUsingFunction(path, &getFileLStat);
221}
222
223Optional<FileMetadata> fileMetadataFollowingSymlinks(const String& path)
224{
225 return fileMetadataUsingFunction(path, &getFileStat);
226}
227
228String 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
235String 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
246bool 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
252String homeDirectoryPath()
253{
254 return stringFromFileSystemRepresentation(g_get_home_dir());
255}
256
257bool 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
270String 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
280bool 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
295String 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
305Vector<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
329String 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
341PlatformFileHandle 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
361void 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
371long 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
396int 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
404int 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
416bool 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
432bool 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
449bool 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
472Optional<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)
483bool 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
493bool 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