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 "UUID.h"
28#include <gio/gio.h>
29#include <glib.h>
30#include <glib/gstdio.h>
31#include <wtf/gobject/GOwnPtr.h>
32#include <wtf/gobject/GRefPtr.h>
33#include <wtf/gobject/GlibUtilities.h>
34#include <wtf/text/CString.h>
35#include <wtf/text/WTFString.h>
36
37namespace WebCore {
38
39/* On linux file names are just raw bytes, so also strings that cannot be encoded in any way
40 * are valid file names. This mean that we cannot just store a file name as-is in a String
41 * but we have to escape it.
42 * On Windows the GLib file name encoding is always UTF-8 so we can optimize this case. */
43String filenameToString(const char* filename)
44{
45    if (!filename)
46        return String();
47
48#if OS(WINDOWS)
49    return String::fromUTF8(filename);
50#else
51    GOwnPtr<gchar> escapedString(g_uri_escape_string(filename, "/:", false));
52    return escapedString.get();
53#endif
54}
55
56CString fileSystemRepresentation(const String& path)
57{
58#if OS(WINDOWS)
59    return path.utf8();
60#else
61    GOwnPtr<gchar> filename(g_uri_unescape_string(path.utf8().data(), 0));
62    return filename.get();
63#endif
64}
65
66// Converts a string to something suitable to be displayed to the user.
67String filenameForDisplay(const String& string)
68{
69#if OS(WINDOWS)
70    return string;
71#else
72    CString filename = fileSystemRepresentation(string);
73    GOwnPtr<gchar> display(g_filename_to_utf8(filename.data(), 0, 0, 0, 0));
74    if (!display)
75        return string;
76
77    return String::fromUTF8(display.get());
78#endif
79}
80
81bool fileExists(const String& path)
82{
83    bool result = false;
84    CString filename = fileSystemRepresentation(path);
85
86    if (!filename.isNull())
87        result = g_file_test(filename.data(), G_FILE_TEST_EXISTS);
88
89    return result;
90}
91
92bool deleteFile(const String& path)
93{
94    bool result = false;
95    CString filename = fileSystemRepresentation(path);
96
97    if (!filename.isNull())
98        result = g_remove(filename.data()) == 0;
99
100    return result;
101}
102
103bool deleteEmptyDirectory(const String& path)
104{
105    bool result = false;
106    CString filename = fileSystemRepresentation(path);
107
108    if (!filename.isNull())
109        result = g_rmdir(filename.data()) == 0;
110
111    return result;
112}
113
114bool getFileSize(const String& path, long long& resultSize)
115{
116    CString filename = fileSystemRepresentation(path);
117    if (filename.isNull())
118        return false;
119
120    GStatBuf statResult;
121    gint result = g_stat(filename.data(), &statResult);
122    if (result != 0)
123        return false;
124
125    resultSize = statResult.st_size;
126    return true;
127}
128
129bool getFileModificationTime(const String& path, time_t& modifiedTime)
130{
131    CString filename = fileSystemRepresentation(path);
132    if (filename.isNull())
133        return false;
134
135    GStatBuf statResult;
136    gint result = g_stat(filename.data(), &statResult);
137    if (result != 0)
138        return false;
139
140    modifiedTime = statResult.st_mtime;
141    return true;
142
143}
144
145bool getFileMetadata(const String& path, FileMetadata& metadata)
146{
147    CString filename = fileSystemRepresentation(path);
148    if (filename.isNull())
149        return false;
150
151    struct stat statResult;
152    gint result = g_stat(filename.data(), &statResult);
153    if (result)
154        return false;
155
156    metadata.modificationTime = statResult.st_mtime;
157    metadata.length = statResult.st_size;
158    metadata.type = S_ISDIR(statResult.st_mode) ? FileMetadata::TypeDirectory : FileMetadata::TypeFile;
159    return true;
160
161}
162
163String pathByAppendingComponent(const String& path, const String& component)
164{
165    if (path.endsWith(G_DIR_SEPARATOR_S))
166        return path + component;
167    else
168        return path + G_DIR_SEPARATOR_S + component;
169}
170
171bool makeAllDirectories(const String& path)
172{
173    CString filename = fileSystemRepresentation(path);
174    if (filename.isNull())
175        return false;
176
177    gint result = g_mkdir_with_parents(filename.data(), S_IRWXU);
178
179    return result == 0;
180}
181
182String homeDirectoryPath()
183{
184    return filenameToString(g_get_home_dir());
185}
186
187String pathGetFileName(const String& pathName)
188{
189    if (pathName.isEmpty())
190        return pathName;
191
192    CString tmpFilename = fileSystemRepresentation(pathName);
193    GOwnPtr<gchar> baseName(g_path_get_basename(tmpFilename.data()));
194    return String::fromUTF8(baseName.get());
195}
196
197CString 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    GOwnPtr<char> currentExePath(g_find_program_in_path(g_get_prgname()));
205    if (!currentExePath.get())
206        return CString();
207
208    GOwnPtr<char> dirname(g_path_get_dirname(currentExePath.get()));
209    return dirname.get();
210}
211
212CString sharedResourcesPath()
213{
214    static CString cachedPath;
215    if (!cachedPath.isNull())
216        return cachedPath;
217
218#if OS(WINDOWS)
219    HMODULE hmodule = 0;
220    GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast<char*>(sharedResourcesPath), &hmodule);
221
222    GOwnPtr<gchar> runtimeDir(g_win32_get_package_installation_directory_of_module(hmodule));
223    GOwnPtr<gchar> dataPath(g_build_filename(runtimeDir.get(), "share", "webkitgtk-"WEBKITGTK_API_VERSION_STRING, NULL));
224#else
225    GOwnPtr<gchar> dataPath(g_build_filename(DATA_DIR, "webkitgtk-" WEBKITGTK_API_VERSION_STRING, NULL));
226#endif
227
228    cachedPath = dataPath.get();
229    return cachedPath;
230}
231
232uint64_t getVolumeFreeSizeForPath(const char* path)
233{
234    GRefPtr<GFile> file = adoptGRef(g_file_new_for_path(path));
235    GRefPtr<GFileInfo> fileInfo = adoptGRef(g_file_query_filesystem_info(file.get(), G_FILE_ATTRIBUTE_FILESYSTEM_FREE, 0, 0));
236    if (!fileInfo)
237        return 0;
238
239    return g_file_info_get_attribute_uint64(fileInfo.get(), G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
240}
241
242String directoryName(const String& path)
243{
244    /* No null checking needed */
245    GOwnPtr<char> dirname(g_path_get_dirname(fileSystemRepresentation(path).data()));
246    return String::fromUTF8(dirname.get());
247}
248
249Vector<String> listDirectory(const String& path, const String& filter)
250{
251    Vector<String> entries;
252
253    CString filename = fileSystemRepresentation(path);
254    GDir* dir = g_dir_open(filename.data(), 0, 0);
255    if (!dir)
256        return entries;
257
258    GPatternSpec *pspec = g_pattern_spec_new((filter.utf8()).data());
259    while (const char* name = g_dir_read_name(dir)) {
260        if (!g_pattern_match_string(pspec, name))
261            continue;
262
263        GOwnPtr<gchar> entry(g_build_filename(filename.data(), name, NULL));
264        entries.append(filenameToString(entry.get()));
265    }
266    g_pattern_spec_free(pspec);
267    g_dir_close(dir);
268
269    return entries;
270}
271
272String openTemporaryFile(const String& prefix, PlatformFileHandle& handle)
273{
274    GOwnPtr<gchar> filename(g_strdup_printf("%s%s", prefix.utf8().data(), createCanonicalUUIDString().utf8().data()));
275    GOwnPtr<gchar> tempPath(g_build_filename(g_get_tmp_dir(), filename.get(), NULL));
276    GRefPtr<GFile> file = adoptGRef(g_file_new_for_path(tempPath.get()));
277
278    handle = g_file_create_readwrite(file.get(), G_FILE_CREATE_NONE, 0, 0);
279    if (!isHandleValid(handle))
280        return String();
281    return String::fromUTF8(tempPath.get());
282}
283
284PlatformFileHandle openFile(const String& path, FileOpenMode mode)
285{
286    CString fsRep = fileSystemRepresentation(path);
287    if (fsRep.isNull())
288        return invalidPlatformFileHandle;
289
290    GRefPtr<GFile> file = adoptGRef(g_file_new_for_path(fsRep.data()));
291    GFileIOStream* ioStream = 0;
292    if (mode == OpenForRead)
293        ioStream = g_file_open_readwrite(file.get(), 0, 0);
294    else if (mode == OpenForWrite) {
295        if (g_file_test(fsRep.data(), static_cast<GFileTest>(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)))
296            ioStream = g_file_open_readwrite(file.get(), 0, 0);
297        else
298            ioStream = g_file_create_readwrite(file.get(), G_FILE_CREATE_NONE, 0, 0);
299    }
300
301    return ioStream;
302}
303
304void closeFile(PlatformFileHandle& handle)
305{
306    if (!isHandleValid(handle))
307        return;
308
309    g_io_stream_close(G_IO_STREAM(handle), 0, 0);
310    g_object_unref(handle);
311    handle = invalidPlatformFileHandle;
312}
313
314long long seekFile(PlatformFileHandle handle, long long offset, FileSeekOrigin origin)
315{
316    GSeekType seekType = G_SEEK_SET;
317    switch (origin) {
318    case SeekFromBeginning:
319        seekType = G_SEEK_SET;
320        break;
321    case SeekFromCurrent:
322        seekType = G_SEEK_CUR;
323        break;
324    case SeekFromEnd:
325        seekType = G_SEEK_END;
326        break;
327    default:
328        ASSERT_NOT_REACHED();
329    }
330
331    if (!g_seekable_seek(G_SEEKABLE(g_io_stream_get_input_stream(G_IO_STREAM(handle))),
332                         offset, seekType, 0, 0))
333        return -1;
334    return g_seekable_tell(G_SEEKABLE(g_io_stream_get_input_stream(G_IO_STREAM(handle))));
335}
336
337int writeToFile(PlatformFileHandle handle, const char* data, int length)
338{
339    gsize bytesWritten;
340    g_output_stream_write_all(g_io_stream_get_output_stream(G_IO_STREAM(handle)),
341                              data, length, &bytesWritten, 0, 0);
342    return bytesWritten;
343}
344
345int readFromFile(PlatformFileHandle handle, char* data, int length)
346{
347    GOwnPtr<GError> error;
348    do {
349        gssize bytesRead = g_input_stream_read(g_io_stream_get_input_stream(G_IO_STREAM(handle)),
350                                               data, length, 0, &error.outPtr());
351        if (bytesRead >= 0)
352            return bytesRead;
353    } while (error && error->code == G_FILE_ERROR_INTR);
354    return -1;
355}
356
357bool unloadModule(PlatformModule module)
358{
359#if OS(WINDOWS)
360    return ::FreeLibrary(module);
361#else
362    return g_module_close(module);
363#endif
364}
365}
366