1/*
2 * Copyright (C) 2007-2017 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Apple Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include "config.h"
30#include <wtf/FileSystem.h>
31
32#include <dirent.h>
33#include <errno.h>
34#include <fcntl.h>
35#include <fnmatch.h>
36#include <libgen.h>
37#include <stdio.h>
38#include <sys/stat.h>
39#include <sys/statvfs.h>
40#include <sys/types.h>
41#include <unistd.h>
42#include <wtf/EnumTraits.h>
43#include <wtf/FileMetadata.h>
44#include <wtf/text/CString.h>
45#include <wtf/text/StringBuilder.h>
46#include <wtf/text/WTFString.h>
47
48namespace WTF {
49
50namespace FileSystemImpl {
51
52bool fileExists(const String& path)
53{
54 if (path.isNull())
55 return false;
56
57 CString fsRep = fileSystemRepresentation(path);
58
59 if (!fsRep.data() || fsRep.data()[0] == '\0')
60 return false;
61
62 return access(fsRep.data(), F_OK) != -1;
63}
64
65bool deleteFile(const String& path)
66{
67 CString fsRep = fileSystemRepresentation(path);
68
69 if (!fsRep.data() || fsRep.data()[0] == '\0') {
70 LOG_ERROR("File failed to delete. Failed to get filesystem representation to create CString from cfString or filesystem representation is a null value");
71 return false;
72 }
73
74 // unlink(...) returns 0 on successful deletion of the path and non-zero in any other case (including invalid permissions or non-existent file)
75 bool unlinked = !unlink(fsRep.data());
76 if (!unlinked && errno != ENOENT)
77 LOG_ERROR("File failed to delete. Error message: %s", strerror(errno));
78
79 return unlinked;
80}
81
82PlatformFileHandle openFile(const String& path, FileOpenMode mode)
83{
84 CString fsRep = fileSystemRepresentation(path);
85
86 if (fsRep.isNull())
87 return invalidPlatformFileHandle;
88
89 int platformFlag = 0;
90 if (mode == FileOpenMode::Read)
91 platformFlag |= O_RDONLY;
92 else if (mode == FileOpenMode::Write)
93 platformFlag |= (O_WRONLY | O_CREAT | O_TRUNC);
94#if OS(DARWIN)
95 else if (mode == FileOpenMode::EventsOnly)
96 platformFlag |= O_EVTONLY;
97#endif
98
99 return open(fsRep.data(), platformFlag, 0666);
100}
101
102void closeFile(PlatformFileHandle& handle)
103{
104 if (isHandleValid(handle)) {
105 close(handle);
106 handle = invalidPlatformFileHandle;
107 }
108}
109
110long long seekFile(PlatformFileHandle handle, long long offset, FileSeekOrigin origin)
111{
112 int whence = SEEK_SET;
113 switch (origin) {
114 case FileSeekOrigin::Beginning:
115 whence = SEEK_SET;
116 break;
117 case FileSeekOrigin::Current:
118 whence = SEEK_CUR;
119 break;
120 case FileSeekOrigin::End:
121 whence = SEEK_END;
122 break;
123 default:
124 ASSERT_NOT_REACHED();
125 }
126 return static_cast<long long>(lseek(handle, offset, whence));
127}
128
129bool truncateFile(PlatformFileHandle handle, long long offset)
130{
131 // ftruncate returns 0 to indicate the success.
132 return !ftruncate(handle, offset);
133}
134
135int writeToFile(PlatformFileHandle handle, const char* data, int length)
136{
137 do {
138 int bytesWritten = write(handle, data, static_cast<size_t>(length));
139 if (bytesWritten >= 0)
140 return bytesWritten;
141 } while (errno == EINTR);
142 return -1;
143}
144
145int readFromFile(PlatformFileHandle handle, char* data, int length)
146{
147 do {
148 int bytesRead = read(handle, data, static_cast<size_t>(length));
149 if (bytesRead >= 0)
150 return bytesRead;
151 } while (errno == EINTR);
152 return -1;
153}
154
155#if USE(FILE_LOCK)
156bool lockFile(PlatformFileHandle handle, OptionSet<FileLockMode> lockMode)
157{
158 COMPILE_ASSERT(LOCK_SH == WTF::enumToUnderlyingType(FileLockMode::Shared), LockSharedEncodingIsAsExpected);
159 COMPILE_ASSERT(LOCK_EX == WTF::enumToUnderlyingType(FileLockMode::Exclusive), LockExclusiveEncodingIsAsExpected);
160 COMPILE_ASSERT(LOCK_NB == WTF::enumToUnderlyingType(FileLockMode::Nonblocking), LockNonblockingEncodingIsAsExpected);
161 int result = flock(handle, lockMode.toRaw());
162 return (result != -1);
163}
164
165bool unlockFile(PlatformFileHandle handle)
166{
167 int result = flock(handle, LOCK_UN);
168 return (result != -1);
169}
170#endif
171
172#if !PLATFORM(MAC)
173bool deleteEmptyDirectory(const String& path)
174{
175 CString fsRep = fileSystemRepresentation(path);
176
177 if (!fsRep.data() || fsRep.data()[0] == '\0')
178 return false;
179
180 // rmdir(...) returns 0 on successful deletion of the path and non-zero in any other case (including invalid permissions or non-existent file)
181 return !rmdir(fsRep.data());
182}
183#endif
184
185bool getFileSize(const String& path, long long& result)
186{
187 CString fsRep = fileSystemRepresentation(path);
188
189 if (!fsRep.data() || fsRep.data()[0] == '\0')
190 return false;
191
192 struct stat fileInfo;
193
194 if (stat(fsRep.data(), &fileInfo))
195 return false;
196
197 result = fileInfo.st_size;
198 return true;
199}
200
201bool getFileSize(PlatformFileHandle handle, long long& result)
202{
203 struct stat fileInfo;
204 if (fstat(handle, &fileInfo))
205 return false;
206
207 result = fileInfo.st_size;
208 return true;
209}
210
211Optional<WallTime> getFileCreationTime(const String& path)
212{
213#if OS(DARWIN) || OS(OPENBSD) || OS(NETBSD) || OS(FREEBSD)
214 CString fsRep = fileSystemRepresentation(path);
215
216 if (!fsRep.data() || fsRep.data()[0] == '\0')
217 return WTF::nullopt;
218
219 struct stat fileInfo;
220
221 if (stat(fsRep.data(), &fileInfo))
222 return WTF::nullopt;
223
224 return WallTime::fromRawSeconds(fileInfo.st_birthtime);
225#else
226 UNUSED_PARAM(path);
227 return WTF::nullopt;
228#endif
229}
230
231Optional<WallTime> getFileModificationTime(const String& path)
232{
233 CString fsRep = fileSystemRepresentation(path);
234
235 if (!fsRep.data() || fsRep.data()[0] == '\0')
236 return WTF::nullopt;
237
238 struct stat fileInfo;
239
240 if (stat(fsRep.data(), &fileInfo))
241 return WTF::nullopt;
242
243 return WallTime::fromRawSeconds(fileInfo.st_mtime);
244}
245
246static FileMetadata::Type toFileMetataType(struct stat fileInfo)
247{
248 if (S_ISDIR(fileInfo.st_mode))
249 return FileMetadata::Type::Directory;
250 if (S_ISLNK(fileInfo.st_mode))
251 return FileMetadata::Type::SymbolicLink;
252 return FileMetadata::Type::File;
253}
254
255static Optional<FileMetadata> fileMetadataUsingFunction(const String& path, int (*statFunc)(const char*, struct stat*))
256{
257 CString fsRep = fileSystemRepresentation(path);
258
259 if (!fsRep.data() || fsRep.data()[0] == '\0')
260 return WTF::nullopt;
261
262 struct stat fileInfo;
263 if (statFunc(fsRep.data(), &fileInfo))
264 return WTF::nullopt;
265
266 String filename = pathGetFileName(path);
267 bool isHidden = !filename.isEmpty() && filename[0] == '.';
268 return FileMetadata {
269 WallTime::fromRawSeconds(fileInfo.st_mtime),
270 fileInfo.st_size,
271 isHidden,
272 toFileMetataType(fileInfo)
273 };
274}
275
276Optional<FileMetadata> fileMetadata(const String& path)
277{
278 return fileMetadataUsingFunction(path, &lstat);
279}
280
281Optional<FileMetadata> fileMetadataFollowingSymlinks(const String& path)
282{
283 return fileMetadataUsingFunction(path, &stat);
284}
285
286bool createSymbolicLink(const String& targetPath, const String& symbolicLinkPath)
287{
288 CString targetPathFSRep = fileSystemRepresentation(targetPath);
289 if (!targetPathFSRep.data() || targetPathFSRep.data()[0] == '\0')
290 return false;
291
292 CString symbolicLinkPathFSRep = fileSystemRepresentation(symbolicLinkPath);
293 if (!symbolicLinkPathFSRep.data() || symbolicLinkPathFSRep.data()[0] == '\0')
294 return false;
295
296 return !symlink(targetPathFSRep.data(), symbolicLinkPathFSRep.data());
297}
298
299String pathByAppendingComponent(const String& path, const String& component)
300{
301 if (path.endsWith('/'))
302 return path + component;
303 return path + "/" + component;
304}
305
306String pathByAppendingComponents(StringView path, const Vector<StringView>& components)
307{
308 StringBuilder builder;
309 builder.append(path);
310 for (auto& component : components)
311 builder.append('/', component);
312 return builder.toString();
313}
314
315bool makeAllDirectories(const String& path)
316{
317 CString fullPath = fileSystemRepresentation(path);
318 if (!access(fullPath.data(), F_OK))
319 return true;
320
321 char* p = fullPath.mutableData() + 1;
322 int length = fullPath.length();
323
324 if (p[length - 1] == '/')
325 p[length - 1] = '\0';
326 for (; *p; ++p) {
327 if (*p == '/') {
328 *p = '\0';
329 if (access(fullPath.data(), F_OK)) {
330 if (mkdir(fullPath.data(), S_IRWXU))
331 return false;
332 }
333 *p = '/';
334 }
335 }
336 if (access(fullPath.data(), F_OK)) {
337 if (mkdir(fullPath.data(), S_IRWXU))
338 return false;
339 }
340
341 return true;
342}
343
344String pathGetFileName(const String& path)
345{
346 return path.substring(path.reverseFind('/') + 1);
347}
348
349String directoryName(const String& path)
350{
351 CString fsRep = fileSystemRepresentation(path);
352
353 if (!fsRep.data() || fsRep.data()[0] == '\0')
354 return String();
355
356 return String::fromUTF8(dirname(fsRep.mutableData()));
357}
358
359Vector<String> listDirectory(const String& path, const String& filter)
360{
361 Vector<String> entries;
362 CString cpath = fileSystemRepresentation(path);
363 CString cfilter = fileSystemRepresentation(filter);
364 DIR* dir = opendir(cpath.data());
365 if (dir) {
366 struct dirent* dp;
367 while ((dp = readdir(dir))) {
368 const char* name = dp->d_name;
369 if (!strcmp(name, ".") || !strcmp(name, ".."))
370 continue;
371 if (fnmatch(cfilter.data(), name, 0))
372 continue;
373 char filePath[PATH_MAX];
374 if (static_cast<int>(sizeof(filePath) - 1) < snprintf(filePath, sizeof(filePath), "%s/%s", cpath.data(), name))
375 continue; // buffer overflow
376
377 auto string = stringFromFileSystemRepresentation(filePath);
378
379 // Some file system representations cannot be represented as a UTF-16 string,
380 // so this string might be null.
381 if (!string.isNull())
382 entries.append(WTFMove(string));
383 }
384 closedir(dir);
385 }
386 return entries;
387}
388
389#if !USE(CF)
390String stringFromFileSystemRepresentation(const char* path)
391{
392 if (!path)
393 return String();
394
395 return String::fromUTF8(path);
396}
397
398CString fileSystemRepresentation(const String& path)
399{
400 return path.utf8();
401}
402#endif
403
404#if !PLATFORM(COCOA)
405bool moveFile(const String& oldPath, const String& newPath)
406{
407 auto oldFilename = fileSystemRepresentation(oldPath);
408 if (oldFilename.isNull())
409 return false;
410
411 auto newFilename = fileSystemRepresentation(newPath);
412 if (newFilename.isNull())
413 return false;
414
415 return rename(oldFilename.data(), newFilename.data()) != -1;
416}
417
418bool getVolumeFreeSpace(const String& path, uint64_t& freeSpace)
419{
420 struct statvfs fileSystemStat;
421 if (statvfs(fileSystemRepresentation(path).data(), &fileSystemStat)) {
422 freeSpace = fileSystemStat.f_bavail * fileSystemStat.f_frsize;
423 return true;
424 }
425 return false;
426}
427
428String openTemporaryFile(const String& prefix, PlatformFileHandle& handle)
429{
430 char buffer[PATH_MAX];
431 const char* tmpDir = getenv("TMPDIR");
432
433 if (!tmpDir)
434 tmpDir = "/tmp";
435
436 if (snprintf(buffer, PATH_MAX, "%s/%sXXXXXX", tmpDir, prefix.utf8().data()) >= PATH_MAX)
437 goto end;
438
439 handle = mkstemp(buffer);
440 if (handle < 0)
441 goto end;
442
443 return String::fromUTF8(buffer);
444
445end:
446 handle = invalidPlatformFileHandle;
447 return String();
448}
449#endif // !PLATFORM(COCOA)
450
451bool hardLink(const String& source, const String& destination)
452{
453 if (source.isEmpty() || destination.isEmpty())
454 return false;
455
456 auto fsSource = fileSystemRepresentation(source);
457 if (!fsSource.data())
458 return false;
459
460 auto fsDestination = fileSystemRepresentation(destination);
461 if (!fsDestination.data())
462 return false;
463
464 return !link(fsSource.data(), fsDestination.data());
465}
466
467bool hardLinkOrCopyFile(const String& source, const String& destination)
468{
469 if (hardLink(source, destination))
470 return true;
471
472 // Hard link failed. Perform a copy instead.
473 if (source.isEmpty() || destination.isEmpty())
474 return false;
475
476 auto fsSource = fileSystemRepresentation(source);
477 if (!fsSource.data())
478 return false;
479
480 auto fsDestination = fileSystemRepresentation(destination);
481 if (!fsDestination.data())
482 return false;
483
484 auto handle = open(fsDestination.data(), O_WRONLY | O_CREAT | O_EXCL, 0666);
485 if (handle == -1)
486 return false;
487
488 bool appendResult = appendFileContentsToFileHandle(source, handle);
489 close(handle);
490
491 // If the copy failed, delete the unusable file.
492 if (!appendResult)
493 unlink(fsDestination.data());
494
495 return appendResult;
496}
497
498Optional<int32_t> getFileDeviceId(const CString& fsFile)
499{
500 struct stat fileStat;
501 if (stat(fsFile.data(), &fileStat) == -1)
502 return WTF::nullopt;
503
504 return fileStat.st_dev;
505}
506
507String realPath(const String& filePath)
508{
509 CString fsRep = fileSystemRepresentation(filePath);
510 char resolvedName[PATH_MAX];
511 const char* result = realpath(fsRep.data(), resolvedName);
512 return result ? String::fromUTF8(result) : filePath;
513}
514
515} // namespace FileSystemImpl
516} // namespace WTF
517