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