[GTK][WPE] Enable FILE_LOCK and implement lockFile and unlockFile
[WebKit-https.git] / Source / WebCore / platform / glib / FileSystemGlib.cpp
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 "FileSystem.h"
25
26 #include "FileMetadata.h"
27 #include "NotImplemented.h"
28 #include <gio/gfiledescriptorbased.h>
29 #include <gio/gio.h>
30 #include <glib.h>
31 #include <glib/gstdio.h>
32 #include <sys/file.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/CString.h>
38 #include <wtf/text/WTFString.h>
39
40 namespace WebCore {
41
42 /* On linux file names are just raw bytes, so also strings that cannot be encoded in any way
43  * are valid file names. This mean that we cannot just store a file name as-is in a String
44  * but we have to escape it.
45  * On Windows the GLib file name encoding is always UTF-8 so we can optimize this case. */
46 String stringFromFileSystemRepresentation(const char* fileSystemRepresentation)
47 {
48     if (!fileSystemRepresentation)
49         return String();
50
51 #if OS(WINDOWS)
52     return String::fromUTF8(fileSystemRepresentation);
53 #else
54     GUniquePtr<gchar> escapedString(g_uri_escape_string(fileSystemRepresentation, "/:", FALSE));
55     return escapedString.get();
56 #endif
57 }
58
59 static GUniquePtr<char> unescapedFilename(const String& path)
60 {
61     if (path.isEmpty())
62         return nullptr;
63 #if OS(WINDOWS)
64     return GUniquePtr<char>(g_strdup(path.utf8().data()));
65 #else
66     return GUniquePtr<char>(g_uri_unescape_string(path.utf8().data(), nullptr));
67 #endif
68 }
69
70 CString fileSystemRepresentation(const String& path)
71 {
72 #if OS(WINDOWS)
73     return path.utf8();
74 #else
75     GUniquePtr<gchar> filename = unescapedFilename(path);
76     return filename.get();
77 #endif
78 }
79
80 // Converts a string to something suitable to be displayed to the user.
81 String filenameForDisplay(const String& string)
82 {
83 #if OS(WINDOWS)
84     return string;
85 #else
86     GUniquePtr<gchar> filename = unescapedFilename(string);
87     if (!filename)
88         return string;
89
90     GUniquePtr<gchar> display(g_filename_to_utf8(filename.get(), -1, nullptr, nullptr, nullptr));
91     if (!display)
92         return string;
93
94     return String::fromUTF8(display.get());
95 #endif
96 }
97
98 bool fileExists(const String& path)
99 {
100     GUniquePtr<gchar> filename = unescapedFilename(path);
101     return filename ? g_file_test(filename.get(), G_FILE_TEST_EXISTS) : false;
102 }
103
104 bool deleteFile(const String& path)
105 {
106     GUniquePtr<gchar> filename = unescapedFilename(path);
107     return filename ? g_remove(filename.get()) != -1 : false;
108 }
109
110 bool deleteEmptyDirectory(const String& path)
111 {
112     GUniquePtr<gchar> filename = unescapedFilename(path);
113     return filename ? g_rmdir(filename.get()) != -1 : false;
114 }
115
116 static bool getFileStat(const String& path, GStatBuf* statBuffer)
117 {
118     GUniquePtr<gchar> filename = unescapedFilename(path);
119     if (!filename)
120         return false;
121
122     return g_stat(filename.get(), statBuffer) != -1;
123 }
124
125 bool getFileSize(const String& path, long long& resultSize)
126 {
127     GStatBuf statResult;
128     if (!getFileStat(path, &statResult))
129         return false;
130
131     resultSize = statResult.st_size;
132     return true;
133 }
134
135 bool getFileSize(PlatformFileHandle, long long&)
136 {
137     notImplemented();
138     return false;
139 }
140
141 bool getFileCreationTime(const String&, time_t&)
142 {
143     // FIXME: Is there a way to retrieve file creation time with Gtk on platforms that support it?
144     return false;
145 }
146
147 bool getFileModificationTime(const String& path, time_t& modifiedTime)
148 {
149     GStatBuf statResult;
150     if (!getFileStat(path, &statResult))
151         return false;
152
153     modifiedTime = statResult.st_mtime;
154     return true;
155 }
156
157 bool getFileMetadata(const String& path, FileMetadata& metadata)
158 {
159     GStatBuf statResult;
160     if (!getFileStat(path, &statResult))
161         return false;
162
163     metadata.modificationTime = statResult.st_mtime;
164     metadata.length = statResult.st_size;
165     metadata.type = S_ISDIR(statResult.st_mode) ? FileMetadata::TypeDirectory : FileMetadata::TypeFile;
166     return true;
167 }
168
169 String pathByAppendingComponent(const String& path, const String& component)
170 {
171     if (path.endsWith(G_DIR_SEPARATOR_S))
172         return path + component;
173     return path + G_DIR_SEPARATOR_S + component;
174 }
175
176 bool makeAllDirectories(const String& path)
177 {
178     GUniquePtr<gchar> filename = unescapedFilename(path);
179     return filename ? g_mkdir_with_parents(filename.get(), S_IRWXU) != -1 : false;
180 }
181
182 String homeDirectoryPath()
183 {
184     return stringFromFileSystemRepresentation(g_get_home_dir());
185 }
186
187 String pathGetFileName(const String& pathName)
188 {
189     GUniquePtr<gchar> tmpFilename = unescapedFilename(pathName);
190     if (!tmpFilename)
191         return pathName;
192
193     GUniquePtr<gchar> baseName(g_path_get_basename(tmpFilename.get()));
194     return String::fromUTF8(baseName.get());
195 }
196
197 CString applicationDirectoryPath()
198 {
199     CString path = getCurrentExecutablePath();
200     if (!path.isNull())
201         return path;
202
203     // If the above fails, check the PATH env variable.
204     GUniquePtr<char> currentExePath(g_find_program_in_path(g_get_prgname()));
205     if (!currentExePath.get())
206         return CString();
207
208     GUniquePtr<char> dirname(g_path_get_dirname(currentExePath.get()));
209     return dirname.get();
210 }
211
212 CString sharedResourcesPath()
213 {
214     static CString cachedPath;
215     if (!cachedPath.isNull())
216         return cachedPath;
217
218 #if PLATFORM(WPE)
219     GUniquePtr<gchar> dataPath(g_build_filename(DATA_DIR, "wpe", nullptr));
220 #elif PLATFORM(GTK)
221 #if OS(WINDOWS)
222     HMODULE hmodule = 0;
223     GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast<char*>(sharedResourcesPath), &hmodule);
224
225     GUniquePtr<gchar> runtimeDir(g_win32_get_package_installation_directory_of_module(hmodule));
226     GUniquePtr<gchar> dataPath(g_build_filename(runtimeDir.get(), "share", "webkitgtk-" WEBKITGTK_API_VERSION_STRING, NULL));
227 #else
228     GUniquePtr<gchar> dataPath(g_build_filename(DATA_DIR, "webkitgtk-" WEBKITGTK_API_VERSION_STRING, NULL));
229 #endif
230 #endif
231
232     cachedPath = dataPath.get();
233     return cachedPath;
234 }
235
236 bool getVolumeFreeSpace(const String& path, uint64_t& freeSpace)
237 {
238     GUniquePtr<gchar> filename = unescapedFilename(path);
239     if (!filename)
240         return false;
241
242     GRefPtr<GFile> file = adoptGRef(g_file_new_for_path(filename.get()));
243     GRefPtr<GFileInfo> fileInfo = adoptGRef(g_file_query_filesystem_info(file.get(), G_FILE_ATTRIBUTE_FILESYSTEM_FREE, 0, 0));
244     if (!fileInfo)
245         return false;
246
247     freeSpace = g_file_info_get_attribute_uint64(fileInfo.get(), G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
248     return !!freeSpace;
249 }
250
251 String directoryName(const String& path)
252 {
253     GUniquePtr<gchar> filename = unescapedFilename(path);
254     if (!filename)
255         return String();
256
257     GUniquePtr<char> dirname(g_path_get_dirname(filename.get()));
258     return String::fromUTF8(dirname.get());
259 }
260
261 Vector<String> listDirectory(const String& path, const String& filter)
262 {
263     Vector<String> entries;
264
265     GUniquePtr<gchar> filename = unescapedFilename(path);
266     if (!filename)
267         return entries;
268
269     GUniquePtr<GDir> dir(g_dir_open(filename.get(), 0, nullptr));
270     if (!dir)
271         return entries;
272
273     GUniquePtr<GPatternSpec> pspec(g_pattern_spec_new((filter.utf8()).data()));
274     while (const char* name = g_dir_read_name(dir.get())) {
275         if (!g_pattern_match_string(pspec.get(), name))
276             continue;
277
278         GUniquePtr<gchar> entry(g_build_filename(filename.get(), name, nullptr));
279         entries.append(stringFromFileSystemRepresentation(entry.get()));
280     }
281
282     return entries;
283 }
284
285 String openTemporaryFile(const String& prefix, PlatformFileHandle& handle)
286 {
287     GUniquePtr<gchar> filename(g_strdup_printf("%s%s", prefix.utf8().data(), createCanonicalUUIDString().utf8().data()));
288     GUniquePtr<gchar> tempPath(g_build_filename(g_get_tmp_dir(), filename.get(), NULL));
289     GRefPtr<GFile> file = adoptGRef(g_file_new_for_path(tempPath.get()));
290
291     handle = g_file_create_readwrite(file.get(), G_FILE_CREATE_NONE, 0, 0);
292     if (!isHandleValid(handle))
293         return String();
294     return String::fromUTF8(tempPath.get());
295 }
296
297 PlatformFileHandle openFile(const String& path, FileOpenMode mode)
298 {
299     GUniquePtr<gchar> filename = unescapedFilename(path);
300     if (!filename)
301         return invalidPlatformFileHandle;
302
303     GRefPtr<GFile> file = adoptGRef(g_file_new_for_path(filename.get()));
304     GFileIOStream* ioStream = 0;
305     if (mode == OpenForRead)
306         ioStream = g_file_open_readwrite(file.get(), 0, 0);
307     else if (mode == OpenForWrite) {
308         if (g_file_test(filename.get(), static_cast<GFileTest>(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)))
309             ioStream = g_file_open_readwrite(file.get(), 0, 0);
310         else
311             ioStream = g_file_create_readwrite(file.get(), G_FILE_CREATE_NONE, 0, 0);
312     }
313
314     return ioStream;
315 }
316
317 void closeFile(PlatformFileHandle& handle)
318 {
319     if (!isHandleValid(handle))
320         return;
321
322     g_io_stream_close(G_IO_STREAM(handle), 0, 0);
323     g_object_unref(handle);
324     handle = invalidPlatformFileHandle;
325 }
326
327 long long seekFile(PlatformFileHandle handle, long long offset, FileSeekOrigin origin)
328 {
329     GSeekType seekType = G_SEEK_SET;
330     switch (origin) {
331     case SeekFromBeginning:
332         seekType = G_SEEK_SET;
333         break;
334     case SeekFromCurrent:
335         seekType = G_SEEK_CUR;
336         break;
337     case SeekFromEnd:
338         seekType = G_SEEK_END;
339         break;
340     default:
341         ASSERT_NOT_REACHED();
342     }
343
344     if (!g_seekable_seek(G_SEEKABLE(g_io_stream_get_input_stream(G_IO_STREAM(handle))),
345         offset, seekType, 0, 0))
346     {
347         return -1;
348     }
349     return g_seekable_tell(G_SEEKABLE(g_io_stream_get_input_stream(G_IO_STREAM(handle))));
350 }
351
352 int writeToFile(PlatformFileHandle handle, const char* data, int length)
353 {
354     gsize bytesWritten;
355     g_output_stream_write_all(g_io_stream_get_output_stream(G_IO_STREAM(handle)),
356         data, length, &bytesWritten, 0, 0);
357     return bytesWritten;
358 }
359
360 int readFromFile(PlatformFileHandle handle, char* data, int length)
361 {
362     GUniqueOutPtr<GError> error;
363     do {
364         gssize bytesRead = g_input_stream_read(g_io_stream_get_input_stream(G_IO_STREAM(handle)),
365             data, length, 0, &error.outPtr());
366         if (bytesRead >= 0)
367             return bytesRead;
368     } while (error && error->code == G_FILE_ERROR_INTR);
369     return -1;
370 }
371
372 bool moveFile(const String& oldPath, const String& newPath)
373 {
374     GUniquePtr<gchar> oldFilename = unescapedFilename(oldPath);
375     if (!oldFilename)
376         return false;
377
378     GUniquePtr<gchar> newFilename = unescapedFilename(newPath);
379     if (!newFilename)
380         return false;
381
382     return g_rename(oldFilename.get(), newFilename.get()) != -1;
383 }
384
385 bool hardLinkOrCopyFile(const String& source, const String& destination)
386 {
387 #if OS(WINDOWS)
388     return !!::CopyFile(source.charactersWithNullTermination().data(), destination.charactersWithNullTermination().data(), TRUE);
389 #else
390     GUniquePtr<gchar> sourceFilename = unescapedFilename(source);
391     if (!sourceFilename)
392         return false;
393
394     GUniquePtr<gchar> destinationFilename = unescapedFilename(destination);
395     if (!destinationFilename)
396         return false;
397
398     if (!link(sourceFilename.get(), destinationFilename.get()))
399         return true;
400
401     // Hard link failed. Perform a copy instead.
402     GRefPtr<GFile> sourceFile = adoptGRef(g_file_new_for_path(sourceFilename.get()));
403     GRefPtr<GFile> destinationFile = adoptGRef(g_file_new_for_path(destinationFilename.get()));
404     return g_file_copy(sourceFile.get(), destinationFile.get(), G_FILE_COPY_NONE, nullptr, nullptr, nullptr, nullptr);
405 #endif
406 }
407
408 std::optional<int32_t> getFileDeviceId(const CString& fsFile)
409 {
410     GUniquePtr<gchar> filename = unescapedFilename(fsFile.data());
411     if (!filename)
412         return std::nullopt;
413
414     GRefPtr<GFile> file = adoptGRef(g_file_new_for_path(filename.get()));
415     GRefPtr<GFileInfo> fileInfo = adoptGRef(g_file_query_filesystem_info(file.get(), G_FILE_ATTRIBUTE_UNIX_DEVICE, nullptr, nullptr));
416     if (!fileInfo)
417         return std::nullopt;
418
419     return g_file_info_get_attribute_uint32(fileInfo.get(), G_FILE_ATTRIBUTE_UNIX_DEVICE);
420 }
421
422 #if USE(FILE_LOCK)
423 bool lockFile(PlatformFileHandle handle, FileLockMode lockMode)
424 {
425     COMPILE_ASSERT(LOCK_SH == LockShared, LockSharedEncodingIsAsExpected);
426     COMPILE_ASSERT(LOCK_EX == LockExclusive, LockExclusiveEncodingIsAsExpected);
427     COMPILE_ASSERT(LOCK_NB == LockNonBlocking, LockNonBlockingEncodingIsAsExpected);
428     auto* inputStream = g_io_stream_get_input_stream(G_IO_STREAM(handle));
429     int result = flock(g_file_descriptor_based_get_fd(G_FILE_DESCRIPTOR_BASED(inputStream)), lockMode);
430     return result != -1;
431 }
432
433 bool unlockFile(PlatformFileHandle handle)
434 {
435     auto* inputStream = g_io_stream_get_input_stream(G_IO_STREAM(handle));
436     int result = flock(g_file_descriptor_based_get_fd(G_FILE_DESCRIPTOR_BASED(inputStream)), LOCK_UN);
437     return result != -1;
438 }
439 #endif // USE(FILE_LOCK)
440
441 }