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 return false;
71
72 // unlink(...) returns 0 on successful deletion of the path and non-zero in any other case (including invalid permissions or non-existent file)
73 return !unlink(fsRep.data());
74}
75
76PlatformFileHandle openFile(const String& path, FileOpenMode mode)
77{
78 CString fsRep = fileSystemRepresentation(path);
79
80 if (fsRep.isNull())
81 return invalidPlatformFileHandle;
82
83 int platformFlag = 0;
84 if (mode == FileOpenMode::Read)
85 platformFlag |= O_RDONLY;
86 else if (mode == FileOpenMode::Write)
87 platformFlag |= (O_WRONLY | O_CREAT | O_TRUNC);
88#if OS(DARWIN)
89 else if (mode == FileOpenMode::EventsOnly)
90 platformFlag |= O_EVTONLY;
91#endif
92
93 return open(fsRep.data(), platformFlag, 0666);
94}
95
96void closeFile(PlatformFileHandle& handle)
97{
98 if (isHandleValid(handle)) {
99 close(handle);
100 handle = invalidPlatformFileHandle;
101 }
102}
103
104long long seekFile(PlatformFileHandle handle, long long offset, FileSeekOrigin origin)
105{
106 int whence = SEEK_SET;
107 switch (origin) {
108 case FileSeekOrigin::Beginning:
109 whence = SEEK_SET;
110 break;
111 case FileSeekOrigin::Current:
112 whence = SEEK_CUR;
113 break;
114 case FileSeekOrigin::End:
115 whence = SEEK_END;
116 break;
117 default:
118 ASSERT_NOT_REACHED();
119 }
120 return static_cast<long long>(lseek(handle, offset, whence));
121}
122
123bool truncateFile(PlatformFileHandle handle, long long offset)
124{
125 // ftruncate returns 0 to indicate the success.
126 return !ftruncate(handle, offset);
127}
128
129int writeToFile(PlatformFileHandle handle, const char* data, int length)
130{
131 do {
132 int bytesWritten = write(handle, data, static_cast<size_t>(length));
133 if (bytesWritten >= 0)
134 return bytesWritten;
135 } while (errno == EINTR);
136 return -1;
137}
138
139int readFromFile(PlatformFileHandle handle, char* data, int length)
140{
141 do {
142 int bytesRead = read(handle, data, static_cast<size_t>(length));
143 if (bytesRead >= 0)
144 return bytesRead;
145 } while (errno == EINTR);
146 return -1;
147}
148
149#if USE(FILE_LOCK)
150bool lockFile(PlatformFileHandle handle, OptionSet<FileLockMode> lockMode)
151{
152 COMPILE_ASSERT(LOCK_SH == WTF::enumToUnderlyingType(FileLockMode::Shared), LockSharedEncodingIsAsExpected);
153 COMPILE_ASSERT(LOCK_EX == WTF::enumToUnderlyingType(FileLockMode::Exclusive), LockExclusiveEncodingIsAsExpected);
154 COMPILE_ASSERT(LOCK_NB == WTF::enumToUnderlyingType(FileLockMode::Nonblocking), LockNonblockingEncodingIsAsExpected);
155 int result = flock(handle, lockMode.toRaw());
156 return (result != -1);
157}
158
159bool unlockFile(PlatformFileHandle handle)
160{
161 int result = flock(handle, LOCK_UN);
162 return (result != -1);
163}
164#endif
165
166#if !PLATFORM(MAC)
167bool deleteEmptyDirectory(const String& path)
168{
169 CString fsRep = fileSystemRepresentation(path);
170
171 if (!fsRep.data() || fsRep.data()[0] == '\0')
172 return false;
173
174 // rmdir(...) returns 0 on successful deletion of the path and non-zero in any other case (including invalid permissions or non-existent file)
175 return !rmdir(fsRep.data());
176}
177#endif
178
179bool getFileSize(const String& path, long long& result)
180{
181 CString fsRep = fileSystemRepresentation(path);
182
183 if (!fsRep.data() || fsRep.data()[0] == '\0')
184 return false;
185
186 struct stat fileInfo;
187
188 if (stat(fsRep.data(), &fileInfo))
189 return false;
190
191 result = fileInfo.st_size;
192 return true;
193}
194
195bool getFileSize(PlatformFileHandle handle, long long& result)
196{
197 struct stat fileInfo;
198 if (fstat(handle, &fileInfo))
199 return false;
200
201 result = fileInfo.st_size;
202 return true;
203}
204
205Optional<WallTime> getFileCreationTime(const String& path)
206{
207#if OS(DARWIN) || OS(OPENBSD) || OS(NETBSD) || OS(FREEBSD)
208 CString fsRep = fileSystemRepresentation(path);
209
210 if (!fsRep.data() || fsRep.data()[0] == '\0')
211 return WTF::nullopt;
212
213 struct stat fileInfo;
214
215 if (stat(fsRep.data(), &fileInfo))
216 return WTF::nullopt;
217
218 return WallTime::fromRawSeconds(fileInfo.st_birthtime);
219#else
220 UNUSED_PARAM(path);
221 return WTF::nullopt;
222#endif
223}
224
225Optional<WallTime> getFileModificationTime(const String& path)
226{
227 CString fsRep = fileSystemRepresentation(path);
228
229 if (!fsRep.data() || fsRep.data()[0] == '\0')
230 return WTF::nullopt;
231
232 struct stat fileInfo;
233
234 if (stat(fsRep.data(), &fileInfo))
235 return WTF::nullopt;
236
237 return WallTime::fromRawSeconds(fileInfo.st_mtime);
238}
239
240static FileMetadata::Type toFileMetataType(struct stat fileInfo)
241{
242 if (S_ISDIR(fileInfo.st_mode))
243 return FileMetadata::Type::Directory;
244 if (S_ISLNK(fileInfo.st_mode))
245 return FileMetadata::Type::SymbolicLink;
246 return FileMetadata::Type::File;
247}
248
249static Optional<FileMetadata> fileMetadataUsingFunction(const String& path, int (*statFunc)(const char*, struct stat*))
250{
251 CString fsRep = fileSystemRepresentation(path);
252
253 if (!fsRep.data() || fsRep.data()[0] == '\0')
254 return WTF::nullopt;
255
256 struct stat fileInfo;
257 if (statFunc(fsRep.data(), &fileInfo))
258 return WTF::nullopt;
259
260 String filename = pathGetFileName(path);
261 bool isHidden = !filename.isEmpty() && filename[0] == '.';
262 return FileMetadata {
263 WallTime::fromRawSeconds(fileInfo.st_mtime),
264 fileInfo.st_size,
265 isHidden,
266 toFileMetataType(fileInfo)
267 };
268}
269
270Optional<FileMetadata> fileMetadata(const String& path)
271{
272 return fileMetadataUsingFunction(path, &lstat);
273}
274
275Optional<FileMetadata> fileMetadataFollowingSymlinks(const String& path)
276{
277 return fileMetadataUsingFunction(path, &stat);
278}
279
280bool createSymbolicLink(const String& targetPath, const String& symbolicLinkPath)
281{
282 CString targetPathFSRep = fileSystemRepresentation(targetPath);
283 if (!targetPathFSRep.data() || targetPathFSRep.data()[0] == '\0')
284 return false;
285
286 CString symbolicLinkPathFSRep = fileSystemRepresentation(symbolicLinkPath);
287 if (!symbolicLinkPathFSRep.data() || symbolicLinkPathFSRep.data()[0] == '\0')
288 return false;
289
290 return !symlink(targetPathFSRep.data(), symbolicLinkPathFSRep.data());
291}
292
293String pathByAppendingComponent(const String& path, const String& component)
294{
295 if (path.endsWith('/'))
296 return path + component;
297 return path + "/" + component;
298}
299
300String pathByAppendingComponents(StringView path, const Vector<StringView>& components)
301{
302 StringBuilder builder;
303 builder.append(path);
304 for (auto& component : components) {
305 builder.append('/');
306 builder.append(component);
307 }
308 return builder.toString();
309}
310
311bool makeAllDirectories(const String& path)
312{
313 CString fullPath = fileSystemRepresentation(path);
314 if (!access(fullPath.data(), F_OK))
315 return true;
316
317 char* p = fullPath.mutableData() + 1;
318 int length = fullPath.length();
319
320 if (p[length - 1] == '/')
321 p[length - 1] = '\0';
322 for (; *p; ++p) {
323 if (*p == '/') {
324 *p = '\0';
325 if (access(fullPath.data(), F_OK)) {
326 if (mkdir(fullPath.data(), S_IRWXU))
327 return false;
328 }
329 *p = '/';
330 }
331 }
332 if (access(fullPath.data(), F_OK)) {
333 if (mkdir(fullPath.data(), S_IRWXU))
334 return false;
335 }
336
337 return true;
338}
339
340String pathGetFileName(const String& path)
341{
342 return path.substring(path.reverseFind('/') + 1);
343}
344
345String directoryName(const String& path)
346{
347 CString fsRep = fileSystemRepresentation(path);
348
349 if (!fsRep.data() || fsRep.data()[0] == '\0')
350 return String();
351
352 return String::fromUTF8(dirname(fsRep.mutableData()));
353}
354
355Vector<String> listDirectory(const String& path, const String& filter)
356{
357 Vector<String> entries;
358 CString cpath = fileSystemRepresentation(path);
359 CString cfilter = fileSystemRepresentation(filter);
360 DIR* dir = opendir(cpath.data());
361 if (dir) {
362 struct dirent* dp;
363 while ((dp = readdir(dir))) {
364 const char* name = dp->d_name;
365 if (!strcmp(name, ".") || !strcmp(name, ".."))
366 continue;
367 if (fnmatch(cfilter.data(), name, 0))
368 continue;
369 char filePath[PATH_MAX];
370 if (static_cast<int>(sizeof(filePath) - 1) < snprintf(filePath, sizeof(filePath), "%s/%s", cpath.data(), name))
371 continue; // buffer overflow
372
373 auto string = stringFromFileSystemRepresentation(filePath);
374
375 // Some file system representations cannot be represented as a UTF-16 string,
376 // so this string might be null.
377 if (!string.isNull())
378 entries.append(WTFMove(string));
379 }
380 closedir(dir);
381 }
382 return entries;
383}
384
385#if !USE(CF)
386String stringFromFileSystemRepresentation(const char* path)
387{
388 if (!path)
389 return String();
390
391 return String::fromUTF8(path);
392}
393
394CString fileSystemRepresentation(const String& path)
395{
396 return path.utf8();
397}
398#endif
399
400#if !PLATFORM(COCOA)
401bool moveFile(const String& oldPath, const String& newPath)
402{
403 auto oldFilename = fileSystemRepresentation(oldPath);
404 if (oldFilename.isNull())
405 return false;
406
407 auto newFilename = fileSystemRepresentation(newPath);
408 if (newFilename.isNull())
409 return false;
410
411 return rename(oldFilename.data(), newFilename.data()) != -1;
412}
413
414bool getVolumeFreeSpace(const String& path, uint64_t& freeSpace)
415{
416 struct statvfs fileSystemStat;
417 if (statvfs(fileSystemRepresentation(path).data(), &fileSystemStat)) {
418 freeSpace = fileSystemStat.f_bavail * fileSystemStat.f_frsize;
419 return true;
420 }
421 return false;
422}
423
424String openTemporaryFile(const String& prefix, PlatformFileHandle& handle)
425{
426 char buffer[PATH_MAX];
427 const char* tmpDir = getenv("TMPDIR");
428
429 if (!tmpDir)
430 tmpDir = "/tmp";
431
432 if (snprintf(buffer, PATH_MAX, "%s/%sXXXXXX", tmpDir, prefix.utf8().data()) >= PATH_MAX)
433 goto end;
434
435 handle = mkstemp(buffer);
436 if (handle < 0)
437 goto end;
438
439 return String::fromUTF8(buffer);
440
441end:
442 handle = invalidPlatformFileHandle;
443 return String();
444}
445#endif // !PLATFORM(COCOA)
446
447bool hardLink(const String& source, const String& destination)
448{
449 if (source.isEmpty() || destination.isEmpty())
450 return false;
451
452 auto fsSource = fileSystemRepresentation(source);
453 if (!fsSource.data())
454 return false;
455
456 auto fsDestination = fileSystemRepresentation(destination);
457 if (!fsDestination.data())
458 return false;
459
460 return !link(fsSource.data(), fsDestination.data());
461}
462
463bool hardLinkOrCopyFile(const String& source, const String& destination)
464{
465 if (hardLink(source, destination))
466 return true;
467
468 // Hard link failed. Perform a copy instead.
469 if (source.isEmpty() || destination.isEmpty())
470 return false;
471
472 auto fsSource = fileSystemRepresentation(source);
473 if (!fsSource.data())
474 return false;
475
476 auto fsDestination = fileSystemRepresentation(destination);
477 if (!fsDestination.data())
478 return false;
479
480 auto handle = open(fsDestination.data(), O_WRONLY | O_CREAT | O_EXCL, 0666);
481 if (handle == -1)
482 return false;
483
484 bool appendResult = appendFileContentsToFileHandle(source, handle);
485 close(handle);
486
487 // If the copy failed, delete the unusable file.
488 if (!appendResult)
489 unlink(fsDestination.data());
490
491 return appendResult;
492}
493
494Optional<int32_t> getFileDeviceId(const CString& fsFile)
495{
496 struct stat fileStat;
497 if (stat(fsFile.data(), &fileStat) == -1)
498 return WTF::nullopt;
499
500 return fileStat.st_dev;
501}
502
503String realPath(const String& filePath)
504{
505 CString fsRep = fileSystemRepresentation(filePath);
506 char resolvedName[PATH_MAX];
507 const char* result = realpath(fsRep.data(), resolvedName);
508 return result ? String::fromUTF8(result) : filePath;
509}
510
511} // namespace FileSystemImpl
512} // namespace WTF
513