1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "flutter/fml/file.h"
6
7#include <dirent.h>
8#include <fcntl.h>
9#include <sys/mman.h>
10#include <sys/stat.h>
11#include <unistd.h>
12
13#include <cstring>
14#include <memory>
15#include <sstream>
16
17#include "flutter/fml/eintr_wrapper.h"
18#include "flutter/fml/logging.h"
19#include "flutter/fml/mapping.h"
20#include "flutter/fml/trace_event.h"
21#include "flutter/fml/unique_fd.h"
22
23namespace fml {
24
25std::string CreateTemporaryDirectory() {
26 char directory_name[] = "/tmp/flutter_XXXXXXXX";
27 auto* result = ::mkdtemp(template: directory_name);
28 if (result == nullptr) {
29 return "";
30 }
31 return {result};
32}
33
34static int ToPosixAccessFlags(FilePermission permission) {
35 int flags = 0;
36 switch (permission) {
37 case FilePermission::kRead:
38 flags |= O_RDONLY; // read only
39 break;
40 case FilePermission::kWrite:
41 flags |= O_WRONLY; // write only
42 break;
43 case FilePermission::kReadWrite:
44 flags |= O_RDWR; // read-write
45 break;
46 }
47 return flags;
48}
49
50static int ToPosixCreateModeFlags(FilePermission permission) {
51 int mode = 0;
52 switch (permission) {
53 case FilePermission::kRead:
54 mode |= S_IRUSR;
55 break;
56 case FilePermission::kWrite:
57 mode |= S_IWUSR;
58 break;
59 case FilePermission::kReadWrite:
60 mode |= S_IRUSR | S_IWUSR;
61 break;
62 }
63 return mode;
64}
65
66fml::UniqueFD OpenFile(const char* path,
67 bool create_if_necessary,
68 FilePermission permission) {
69 return OpenFile(base_directory: fml::UniqueFD{AT_FDCWD}, path, create_if_necessary,
70 permission);
71}
72
73fml::UniqueFD OpenFile(const fml::UniqueFD& base_directory,
74 const char* path,
75 bool create_if_necessary,
76 FilePermission permission) {
77 TRACE_EVENT0("flutter", "fml::OpenFile");
78 if (path == nullptr) {
79 return {};
80 }
81
82 int flags = 0;
83 int mode = 0;
84
85 if (create_if_necessary && !FileExists(base_directory, path)) {
86 flags = ToPosixAccessFlags(permission) | O_CREAT | O_TRUNC;
87 mode = ToPosixCreateModeFlags(permission);
88 } else {
89 flags = ToPosixAccessFlags(permission);
90 mode = 0; // Not creating since it already exists.
91 }
92
93 return fml::UniqueFD{
94 FML_HANDLE_EINTR(::openat(base_directory.get(), path, flags, mode))};
95}
96
97fml::UniqueFD OpenDirectory(const char* path,
98 bool create_if_necessary,
99 FilePermission permission) {
100 return OpenDirectory(base_directory: fml::UniqueFD{AT_FDCWD}, path, create_if_necessary,
101 permission);
102}
103
104fml::UniqueFD OpenDirectory(const fml::UniqueFD& base_directory,
105 const char* path,
106 bool create_if_necessary,
107 FilePermission permission) {
108 if (path == nullptr) {
109 return {};
110 }
111
112 if (create_if_necessary && !FileExists(base_directory, path)) {
113 if (::mkdirat(fd: base_directory.get(), path: path,
114 mode: ToPosixCreateModeFlags(permission) | S_IXUSR) != 0) {
115 return {};
116 }
117 }
118
119 return fml::UniqueFD{FML_HANDLE_EINTR(
120 ::openat(base_directory.get(), path, O_RDONLY | O_DIRECTORY))};
121}
122
123fml::UniqueFD Duplicate(fml::UniqueFD::element_type descriptor) {
124 return fml::UniqueFD{FML_HANDLE_EINTR(::dup(descriptor))};
125}
126
127bool IsDirectory(const fml::UniqueFD& directory) {
128 if (!directory.is_valid()) {
129 return false;
130 }
131
132 struct stat stat_result = {};
133
134 if (::fstat(fd: directory.get(), buf: &stat_result) != 0) {
135 return false;
136 }
137
138 return S_ISDIR(stat_result.st_mode);
139}
140
141bool IsDirectory(const fml::UniqueFD& base_directory, const char* path) {
142 UniqueFD file = OpenFileReadOnly(base_directory, path);
143 return (file.is_valid() && IsDirectory(directory: file));
144}
145
146bool IsFile(const std::string& path) {
147 struct stat buf;
148 if (stat(file: path.c_str(), buf: &buf) != 0) {
149 return false;
150 }
151
152 return S_ISREG(buf.st_mode);
153}
154
155bool TruncateFile(const fml::UniqueFD& file, size_t size) {
156 if (!file.is_valid()) {
157 return false;
158 }
159
160 return ::ftruncate(fd: file.get(), length: size) == 0;
161}
162
163bool UnlinkDirectory(const char* path) {
164 return UnlinkDirectory(base_directory: fml::UniqueFD{AT_FDCWD}, path);
165}
166
167bool UnlinkDirectory(const fml::UniqueFD& base_directory, const char* path) {
168 return ::unlinkat(fd: base_directory.get(), name: path, AT_REMOVEDIR) == 0;
169}
170
171bool UnlinkFile(const char* path) {
172 return UnlinkFile(base_directory: fml::UniqueFD{AT_FDCWD}, path);
173}
174
175bool UnlinkFile(const fml::UniqueFD& base_directory, const char* path) {
176 int code = ::unlinkat(fd: base_directory.get(), name: path, flag: 0);
177 if (code != 0) {
178 FML_DLOG(ERROR) << strerror(errno);
179 }
180 return code == 0;
181}
182
183bool FileExists(const fml::UniqueFD& base_directory, const char* path) {
184 if (!base_directory.is_valid()) {
185 return false;
186 }
187
188 return ::faccessat(fd: base_directory.get(), file: path, F_OK, flag: 0) == 0;
189}
190
191bool WriteAtomically(const fml::UniqueFD& base_directory,
192 const char* file_name,
193 const Mapping& data) {
194 if (file_name == nullptr || data.GetMapping() == nullptr) {
195 return false;
196 }
197
198 std::stringstream stream;
199 stream << file_name << ".temp";
200 const auto temp_file_name = stream.str();
201
202 auto temp_file = OpenFile(base_directory, path: temp_file_name.c_str(), create_if_necessary: true,
203 permission: FilePermission::kReadWrite);
204 if (!temp_file.is_valid()) {
205 return false;
206 }
207
208 if (!TruncateFile(file: temp_file, size: data.GetSize())) {
209 return false;
210 }
211
212 ssize_t remaining = data.GetSize();
213 ssize_t written = 0;
214 ssize_t offset = 0;
215
216 while (remaining > 0) {
217 written = FML_HANDLE_EINTR(
218 ::write(temp_file.get(), data.GetMapping() + offset, remaining));
219
220 if (written == -1) {
221 return false;
222 }
223
224 remaining -= written;
225 offset += written;
226 }
227
228 if (::fsync(fd: temp_file.get()) != 0) {
229 return false;
230 }
231
232 return ::renameat(oldfd: base_directory.get(), old: temp_file_name.c_str(),
233 newfd: base_directory.get(), new: file_name) == 0;
234}
235
236bool VisitFiles(const fml::UniqueFD& directory, const FileVisitor& visitor) {
237 fml::UniqueFD dup_fd(dup(fd: directory.get()));
238 if (!dup_fd.is_valid()) {
239 FML_DLOG(ERROR) << "Can't dup the directory fd. Error: " << strerror(errno);
240 return true; // continue to visit other files
241 }
242
243 fml::UniqueDir dir(::fdopendir(fd: dup_fd.get()));
244 if (!dir.is_valid()) {
245 FML_DLOG(ERROR) << "Can't open the directory. Error: " << strerror(errno);
246 return true; // continue to visit other files
247 }
248
249 // The directory fd will be closed by `closedir`.
250 (void)dup_fd.release();
251
252 // Without `rewinddir`, `readir` will directly return NULL (end of dir is
253 // reached) after a previuos `VisitFiles` call for the same `const
254 // fml::UniqueFd& directory`.
255 rewinddir(dirp: dir.get());
256 while (dirent* ent = readdir(dirp: dir.get())) {
257 std::string filename = ent->d_name;
258 if (filename != "." && filename != "..") {
259 if (!visitor(directory, filename)) {
260 return false;
261 }
262 }
263 }
264
265 return true;
266}
267
268} // namespace fml
269

source code of flutter_engine/flutter/fml/platform/posix/file_posix.cc