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