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