openMSX
LocalFile.cc
Go to the documentation of this file.
1#include "systemfuncs.hh"
2#include "unistdp.hh"
3#include <sys/types.h>
4#include <sys/stat.h>
5#if HAVE_MMAP
6#include <sys/mman.h>
7#endif
8#if defined _WIN32
9#include <io.h>
10#include <iostream>
11#endif
12#include "LocalFile.hh"
13#include "FileException.hh"
15#include "narrow.hh"
16#include "one_of.hh"
17#include <cstring> // for strchr, strerror
18#include <cerrno>
19#include <cassert>
20#include <memory>
21
22namespace openmsx {
23
24LocalFile::LocalFile(std::string filename_, File::OpenMode mode)
25 : filename(std::move(filename_))
26#if HAVE_MMAP || defined _WIN32
27 , mmem(nullptr)
28#endif
29#if defined _WIN32
30 , hMmap(nullptr)
31#endif
32 , readOnly(false)
33{
34 if (mode == File::SAVE_PERSISTENT) {
35 if (auto pos = filename.find_last_of('/'); pos != std::string::npos) {
36 FileOperations::mkdirp(filename.substr(0, pos));
37 }
38 }
39
40 const std::string& name = FileOperations::getNativePath(filename);
42 // open file read/write truncated
43 file = FileOperations::openFile(name, "wb+");
44 } else if (mode == File::CREATE) {
45 // open file read/write
46 file = FileOperations::openFile(name, "rb+");
47 if (!file) {
48 // create if it didn't exist yet
49 file = FileOperations::openFile(name, "wb+");
50 }
51 } else {
52 // open file read/write
53 file = FileOperations::openFile(name, "rb+");
54 if (!file) {
55 // if that fails try read only
56 file = FileOperations::openFile(name, "rb");
57 readOnly = true;
58 }
59 }
60 if (!file) {
61 int err = errno;
62 if (err == ENOENT) {
64 "File \"", filename, "\" not found");
65 } else {
66 throw FileException(
67 "Error opening file \"", filename, "\": ",
68 strerror(err));
69 }
70 }
71 (void)getSize(); // query filesize, but ignore result
72}
73
74LocalFile::LocalFile(std::string filename_, const char* mode)
75 : filename(std::move(filename_))
76#if HAVE_MMAP || defined _WIN32
77 , mmem(nullptr)
78#endif
79#if defined _WIN32
80 , hMmap(nullptr)
81#endif
82 , readOnly(false)
83{
84 assert(strchr(mode, 'b'));
85 const std::string name = FileOperations::getNativePath(filename);
86 file = FileOperations::openFile(name, mode);
87 if (!file) {
88 throw FileException("Error opening file \"", filename, '"');
89 }
90 (void)getSize(); // query filesize, but ignore result
91}
92
94{
95 munmap();
96}
97
99{
100 cache.emplace(FileOperations::getNativePath(filename));
101}
102
103void LocalFile::read(std::span<uint8_t> buffer)
104{
105 if (fread(buffer.data(), 1, buffer.size(), file.get()) != buffer.size()) {
106 if (ferror(file.get())) {
107 throw FileException("Error reading file");
108 }
109 if (feof(file.get())) {
110 throw FileException("Read beyond end of file");
111 }
112 }
113}
114
115void LocalFile::write(std::span<const uint8_t> buffer)
116{
117 if (fwrite(buffer.data(), 1, buffer.size(), file.get()) != buffer.size()) {
118 if (ferror(file.get())) {
119 throw FileException("Error writing file");
120 }
121 }
122}
123
124#if defined _WIN32
125std::span<const uint8_t> LocalFile::mmap()
126{
127 size_t size = getSize();
128 if (size == 0) return {static_cast<uint8_t*>(nullptr), size};
129
130 if (!mmem) {
131 int fd = _fileno(file.get());
132 if (fd == -1) {
133 throw FileException("_fileno failed");
134 }
135 auto hFile = reinterpret_cast<HANDLE>(_get_osfhandle(fd)); // No need to close
136 if (hFile == INVALID_HANDLE_VALUE) {
137 throw FileException("_get_osfhandle failed");
138 }
139 assert(!hMmap);
140 hMmap = CreateFileMapping(hFile, nullptr, PAGE_WRITECOPY, 0, 0, nullptr);
141 if (!hMmap) {
142 throw FileException(
143 "CreateFileMapping failed: ", GetLastError());
144 }
145 mmem = static_cast<uint8_t*>(MapViewOfFile(hMmap, FILE_MAP_COPY, 0, 0, 0));
146 if (!mmem) {
147 DWORD gle = GetLastError();
148 CloseHandle(hMmap);
149 hMmap = nullptr;
150 throw FileException("MapViewOfFile failed: ", gle);
151 }
152 }
153 return {mmem, size};
154}
155
157{
158 if (mmem) {
159 // TODO: make this a valid failure path
160 // When pages are dirty, UnmapViewOfFile is a save operation,
161 // and that can fail. However, munmap is called from
162 // the destructor, for which there is no expectation
163 // that it will fail. So this area needs some work.
164 // It is NOT an option to throw an exception (not even
165 // FatalError).
166 if (!UnmapViewOfFile(mmem)) {
167 std::cerr << "UnmapViewOfFile failed: "
168 << GetLastError()
169 << '\n';
170 }
171 mmem = nullptr;
172 }
173 if (hMmap) {
174 CloseHandle(hMmap);
175 hMmap = nullptr;
176 }
177}
178
179#elif HAVE_MMAP
180std::span<const uint8_t> LocalFile::mmap()
181{
182 size_t size = getSize();
183 if (size == 0) return {static_cast<uint8_t*>(nullptr), size};
184
185 if (!mmem) {
186 mmem = static_cast<uint8_t*>(
187 ::mmap(nullptr, size, PROT_READ | PROT_WRITE,
188 MAP_PRIVATE, fileno(file.get()), 0));
189 // MAP_FAILED is #define'd using an old-style cast, we
190 // have to redefine it ourselves to avoid a warning
191 auto* MY_MAP_FAILED = reinterpret_cast<void*>(-1);
192 if (mmem == MY_MAP_FAILED) {
193 throw FileException("Error mmapping file");
194 }
195 }
196 return {mmem, size};
197}
198
200{
201 if (mmem) {
202 try {
203 ::munmap(mmem, getSize());
204 } catch (FileException&) {
205 // In theory getSize() could throw. Does that ever
206 // happen in practice?
207 }
208 mmem = nullptr;
209 }
210}
211#endif
212
214{
215#if defined _WIN32
216 // Normal fstat compiles but doesn't seem to be working the same
217 // as on POSIX, regarding size support.
218 struct _stat64 st;
219 int ret = _fstat64(fileno(file.get()), &st);
220#else
221 struct stat st;
222 int ret = fstat(fileno(file.get()), &st);
223 if (ret && (errno == EOVERFLOW)) {
224 // on 32-bit systems, the fstat() call returns a EOVERFLOW
225 // error in case the file is bigger than (1<<31)-1 bytes
226 throw FileException("Files >= 2GB are not supported on "
227 "32-bit platforms: ", getURL());
228 }
229#endif
230 if (ret) {
231 throw FileException("Cannot get file size");
232 }
233 return st.st_size;
234}
235
236void LocalFile::seek(size_t pos)
237{
238#if defined _WIN32
239 int ret = _fseeki64(file.get(), pos, SEEK_SET);
240#else
241 int ret = fseek(file.get(), narrow_cast<long>(pos), SEEK_SET);
242#endif
243 if (ret != 0) {
244 throw FileException("Error seeking file");
245 }
246}
247
249{
250 return ftell(file.get());
251}
252
253#if HAVE_FTRUNCATE
255{
256 int fd = fileno(file.get());
257 if (ftruncate(fd, narrow_cast<off_t>(size))) {
258 throw FileException("Error truncating file");
259 }
260}
261#endif
262
264{
265 fflush(file.get());
266}
267
268const std::string& LocalFile::getURL() const
269{
270 return filename;
271}
272
274{
275 return filename;
276}
277
279{
280 return readOnly;
281}
282
284{
285#if defined _WIN32
286 // Normal fstat compiles but doesn't seem to be working the same
287 // as on POSIX, regarding size support.
288 struct _stat64 st;
289 int ret = _fstat64(fileno(file.get()), &st);
290#else
291 struct stat st;
292 int ret = fstat(fileno(file.get()), &st);
293#endif
294 if (ret) {
295 throw FileException("Cannot stat file");
296 }
297 return st.st_mtime;
298}
299
300} // namespace openmsx
Definition: one_of.hh:7
@ SAVE_PERSISTENT
Definition: File.hh:23
@ TRUNCATE
Definition: File.hh:20
time_t getModificationDate() override
Definition: LocalFile.cc:283
void write(std::span< const uint8_t > buffer) override
Definition: LocalFile.cc:115
void read(std::span< uint8_t > buffer) override
Definition: LocalFile.cc:103
void seek(size_t pos) override
Definition: LocalFile.cc:236
~LocalFile() override
Definition: LocalFile.cc:93
void munmap() override
Definition: LocalFile.cc:199
bool isReadOnly() const override
Definition: LocalFile.cc:278
LocalFile(std::string filename, File::OpenMode mode)
Definition: LocalFile.cc:24
void truncate(size_t size) override
Definition: LocalFile.cc:254
std::string getLocalReference() override
Definition: LocalFile.cc:273
size_t getPos() override
Definition: LocalFile.cc:248
std::span< const uint8_t > mmap() override
Definition: LocalFile.cc:180
void flush() override
Definition: LocalFile.cc:263
const std::string & getURL() const override
Definition: LocalFile.cc:268
size_t getSize() override
Definition: LocalFile.cc:213
const std::string & getNativePath(const std::string &path)
Returns the path in native path-delimiter.
FILE_t openFile(zstring_view filename, zstring_view mode)
Call fopen() in a platform-independent manner.
void mkdirp(string path)
Acts like the unix command "mkdir -p".
This file implemented 3 utility functions:
Definition: Autofire.cc:9
STL namespace.
size_t size(std::string_view utf8)
#define HAVE_MMAP
Definition: systemfuncs.hh:3