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