Unreviewed, rolling out r241559 and r241566.
[WebKit-https.git] / Source / WTF / wtf / posix / FileSystemPOSIX.cpp
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
48 namespace WTF {
49
50 namespace FileSystemImpl {
51
52 bool 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
65 bool 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
76 PlatformFileHandle 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
96 void closeFile(PlatformFileHandle& handle)
97 {
98     if (isHandleValid(handle)) {
99         close(handle);
100         handle = invalidPlatformFileHandle;
101     }
102 }
103
104 long 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
123 bool truncateFile(PlatformFileHandle handle, long long offset)
124 {
125     // ftruncate returns 0 to indicate the success.
126     return !ftruncate(handle, offset);
127 }
128
129 int 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
139 int 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)
150 bool 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
159 bool unlockFile(PlatformFileHandle handle)
160 {
161     int result = flock(handle, LOCK_UN);
162     return (result != -1);
163 }
164 #endif
165
166 #if !PLATFORM(MAC)
167 bool 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
179 bool 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
195 bool 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
205 Optional<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
225 Optional<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
240 static 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
249 static 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
270 Optional<FileMetadata> fileMetadata(const String& path)
271 {
272     return fileMetadataUsingFunction(path, &lstat);
273 }
274
275 Optional<FileMetadata> fileMetadataFollowingSymlinks(const String& path)
276 {
277     return fileMetadataUsingFunction(path, &stat);
278 }
279
280 bool 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
293 String pathByAppendingComponent(const String& path, const String& component)
294 {
295     if (path.endsWith('/'))
296         return path + component;
297     return path + "/" + component;
298 }
299
300 String 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
311 bool 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
340 String pathGetFileName(const String& path)
341 {
342     return path.substring(path.reverseFind('/') + 1);
343 }
344
345 String 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
355 Vector<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)
386 String stringFromFileSystemRepresentation(const char* path)
387 {
388     if (!path)
389         return String();
390
391     return String::fromUTF8(path);
392 }
393
394 CString fileSystemRepresentation(const String& path)
395 {
396     return path.utf8();
397 }
398 #endif
399
400 #if !PLATFORM(COCOA)
401 bool 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
414 bool 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
424 String 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
441 end:
442     handle = invalidPlatformFileHandle;
443     return String();
444 }
445 #endif // !PLATFORM(COCOA)
446
447 bool hardLinkOrCopyFile(const String& source, const String& destination)
448 {
449     if (source.isEmpty() || destination.isEmpty())
450         return false;
451
452     CString fsSource = fileSystemRepresentation(source);
453     if (!fsSource.data())
454         return false;
455
456     CString fsDestination = fileSystemRepresentation(destination);
457     if (!fsDestination.data())
458         return false;
459
460     if (!link(fsSource.data(), fsDestination.data()))
461         return true;
462
463     // Hard link failed. Perform a copy instead.
464     auto handle = open(fsDestination.data(), O_WRONLY | O_CREAT | O_EXCL, 0666);
465     if (handle == -1)
466         return false;
467
468     bool appendResult = appendFileContentsToFileHandle(source, handle);
469     close(handle);
470
471     // If the copy failed, delete the unusable file.
472     if (!appendResult)
473         unlink(fsDestination.data());
474
475     return appendResult;
476 }
477
478 Optional<int32_t> getFileDeviceId(const CString& fsFile)
479 {
480     struct stat fileStat;
481     if (stat(fsFile.data(), &fileStat) == -1)
482         return WTF::nullopt;
483
484     return fileStat.st_dev;
485 }
486
487 String realPath(const String& filePath)
488 {
489     CString fsRep = fileSystemRepresentation(filePath);
490     char resolvedName[PATH_MAX];
491     const char* result = realpath(fsRep.data(), resolvedName);
492     return result ? String::fromUTF8(result) : filePath;
493 }
494
495 } // namespace FileSystemImpl
496 } // namespace WTF