Cuberite
A lightweight, fast and extensible game server for Minecraft
File.cpp
Go to the documentation of this file.
1 
2 // cFile.cpp
3 
4 // Implements the cFile class providing an OS-independent abstraction of a file.
5 
6 #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
7 
8 #include "File.h"
9 #include <sys/stat.h>
10 #ifdef _WIN32
11  #include <share.h> // for _SH_DENYWRITE
12 #else
13  #include <dirent.h>
14 #endif // _WIN32
15 
16 
17 
18 
19 
20 cFile::cFile(void) :
21  m_File(nullptr)
22 {
23  // Nothing needed yet
24 }
25 
26 
27 
28 
29 
30 cFile::cFile(const AString & iFileName, eMode iMode) :
31  m_File(nullptr)
32 {
33  Open(iFileName, iMode);
34 }
35 
36 
37 
38 
39 
41 {
42  if (IsOpen())
43  {
44  Close();
45  }
46 }
47 
48 
49 
50 
51 
52 bool cFile::Open(const AString & iFileName, eMode iMode)
53 {
54  ASSERT(!IsOpen()); // You should close the file before opening another one
55 
56  if (IsOpen())
57  {
58  Close();
59  }
60 
61  const char * Mode = nullptr;
62  switch (iMode)
63  {
64  case fmRead: Mode = "rb"; break;
65  case fmWrite: Mode = "wb"; break;
66  case fmReadWrite: Mode = "rb+"; break;
67  case fmAppend: Mode = "a+"; break;
68  }
69  if (Mode == nullptr)
70  {
71  ASSERT(!"Unhandled file mode");
72  return false;
73  }
74 
75  #ifdef _WIN32
76  m_File = _fsopen((iFileName).c_str(), Mode, _SH_DENYWR);
77  #else
78  m_File = fopen((iFileName).c_str(), Mode);
79  #endif // _WIN32
80 
81  if ((m_File == nullptr) && (iMode == fmReadWrite))
82  {
83  // Fix for MS not following C spec, opening "a" mode files for writing at the end only
84  // The file open operation has been tried with "read update", fails if file not found
85  // So now we know either the file doesn't exist or we don't have rights, no need to worry about file contents.
86  // Simply re-open for read-writing, erasing existing contents:
87 
88  #ifdef _WIN32
89  m_File = _fsopen((iFileName).c_str(), "wb+", _SH_DENYWR);
90  #else
91  m_File = fopen((iFileName).c_str(), "wb+");
92  #endif // _WIN32
93 
94  }
95  return (m_File != nullptr);
96 }
97 
98 
99 
100 
101 
102 void cFile::Close(void)
103 {
104  if (!IsOpen())
105  {
106  // Closing an unopened file is a legal nop
107  return;
108  }
109 
110  fclose(m_File);
111  m_File = nullptr;
112 }
113 
114 
115 
116 
117 
118 bool cFile::IsOpen(void) const
119 {
120  return (m_File != nullptr);
121 }
122 
123 
124 
125 
126 
127 bool cFile::IsEOF(void) const
128 {
129  ASSERT(IsOpen());
130 
131  if (!IsOpen())
132  {
133  // Unopened files behave as at EOF
134  return true;
135  }
136 
137  return (feof(m_File) != 0);
138 }
139 
140 
141 
142 
143 
144 int cFile::Read (void * a_Buffer, size_t a_NumBytes)
145 {
146  ASSERT(IsOpen());
147 
148  if (!IsOpen())
149  {
150  return -1;
151  }
152 
153  return static_cast<int>(fread(a_Buffer, 1, a_NumBytes, m_File)); // fread() returns the portion of Count parameter actually read, so we need to send a_a_NumBytes as Count
154 }
155 
156 
157 
158 
159 
161 {
162  ASSERT(IsOpen());
163 
164  if (!IsOpen())
165  {
166  return {};
167  }
168 
170  res.resize(a_NumBytes); // TODO: investigate if worth hacking around std::string internals to avoid initialisation
171  auto newSize = fread(res.data(), sizeof(std::byte), a_NumBytes, m_File);
172  res.resize(newSize);
173  return res;
174 }
175 
176 
177 
178 
179 
180 int cFile::Write(const void * a_Buffer, size_t a_NumBytes)
181 {
182  ASSERT(IsOpen());
183 
184  if (!IsOpen())
185  {
186  return -1;
187  }
188 
189  int res = static_cast<int>(fwrite(a_Buffer, 1, a_NumBytes, m_File)); // fwrite() returns the portion of Count parameter actually written, so we need to send a_NumBytes as Count
190  return res;
191 }
192 
193 
194 
195 
196 
197 long cFile::Seek (int iPosition)
198 {
199  ASSERT(IsOpen());
200 
201  if (!IsOpen())
202  {
203  return -1;
204  }
205 
206  if (fseek(m_File, iPosition, SEEK_SET) != 0)
207  {
208  return -1;
209  }
210  return ftell(m_File);
211 }
212 
213 
214 
215 
216 
217 long cFile::Tell (void) const
218 {
219  ASSERT(IsOpen());
220 
221  if (!IsOpen())
222  {
223  return -1;
224  }
225 
226  return ftell(m_File);
227 }
228 
229 
230 
231 
232 
233 long cFile::GetSize(void) const
234 {
235  ASSERT(IsOpen());
236 
237  if (!IsOpen())
238  {
239  return -1;
240  }
241 
242  long CurPos = Tell();
243  if (CurPos < 0)
244  {
245  return -1;
246  }
247  if (fseek(m_File, 0, SEEK_END) != 0)
248  {
249  return -1;
250  }
251  long res = Tell();
252  if (fseek(m_File, static_cast<long>(CurPos), SEEK_SET) != 0)
253  {
254  return -1;
255  }
256  return res;
257 }
258 
259 
260 
261 
262 
264 {
265  ASSERT(IsOpen());
266 
267  if (!IsOpen())
268  {
269  return -1;
270  }
271 
272  long TotalSize = GetSize();
273  if (TotalSize < 0)
274  {
275  return -1;
276  }
277 
278  long Position = Tell();
279  if (Position < 0)
280  {
281  return -1;
282  }
283 
284  auto DataSize = static_cast<size_t>(TotalSize - Position);
285 
286  a_Contents.resize(DataSize); // TODO: investigate if worth hacking around std::string internals to avoid initialisation
287  return Read(a_Contents.data(), DataSize);
288 }
289 
290 
291 
292 
293 
294 bool cFile::Exists(const AString & a_FileName)
295 {
296  cFile test(a_FileName, fmRead);
297  return test.IsOpen();
298 }
299 
300 
301 
302 
303 
304 bool cFile::Delete(const AString & a_Path)
305 {
306  if (IsFolder(a_Path))
307  {
308  return DeleteFolder(a_Path);
309  }
310  else
311  {
312  return DeleteFile(a_Path);
313  }
314 }
315 
316 
317 
318 
319 
320 bool cFile::DeleteFolder(const AString & a_FolderName)
321 {
322  #ifdef _WIN32
323  return (RemoveDirectoryA(a_FolderName.c_str()) != 0);
324  #else // _WIN32
325  return (rmdir(a_FolderName.c_str()) == 0);
326  #endif // else _WIN32
327 }
328 
329 
330 
331 
332 
333 bool cFile::DeleteFolderContents(const AString & a_FolderName)
334 {
335  auto Contents = cFile::GetFolderContents(a_FolderName);
336  for (const auto & item: Contents)
337  {
338  // Remove the item:
339  auto WholePath = a_FolderName + GetPathSeparator() + item;
340  if (IsFolder(WholePath))
341  {
342  if (!DeleteFolderContents(WholePath))
343  {
344  return false;
345  }
346  if (!DeleteFolder(WholePath))
347  {
348  return false;
349  }
350  }
351  else
352  {
353  if (!DeleteFile(WholePath))
354  {
355  return false;
356  }
357  }
358  } // for item - Contents[]
359 
360  // All deletes succeeded
361  return true;
362 }
363 
364 
365 
366 
367 
368 bool cFile::DeleteFile(const AString & a_FileName)
369 {
370  return (remove(a_FileName.c_str()) == 0);
371 }
372 
373 
374 
375 
376 
377 bool cFile::Rename(const AString & a_OrigFileName, const AString & a_NewFileName)
378 {
379  return (rename(a_OrigFileName.c_str(), a_NewFileName.c_str()) == 0);
380 }
381 
382 
383 
384 
385 
386 bool cFile::Copy(const AString & a_SrcFileName, const AString & a_DstFileName)
387 {
388  #ifdef _WIN32
389  return (CopyFileA(a_SrcFileName.c_str(), a_DstFileName.c_str(), FALSE) != 0);
390  #else
391  // Other OSs don't have a direct CopyFile equivalent, do it the harder way:
392  std::ifstream src(a_SrcFileName.c_str(), std::ios::binary);
393  std::ofstream dst(a_DstFileName.c_str(), std::ios::binary);
394  if (dst.good())
395  {
396  dst << src.rdbuf();
397  return true;
398  }
399  else
400  {
401  return false;
402  }
403  #endif
404 }
405 
406 
407 
408 
409 
410 bool cFile::IsFolder(const AString & a_Path)
411 {
412  #ifdef _WIN32
413  DWORD FileAttrib = GetFileAttributesA(a_Path.c_str());
414  return ((FileAttrib != INVALID_FILE_ATTRIBUTES) && ((FileAttrib & FILE_ATTRIBUTE_DIRECTORY) != 0));
415  #else
416  struct stat st;
417  return ((stat(a_Path.c_str(), &st) == 0) && S_ISDIR(st.st_mode));
418  #endif
419 }
420 
421 
422 
423 
424 
425 bool cFile::IsFile(const AString & a_Path)
426 {
427  #ifdef _WIN32
428  DWORD FileAttrib = GetFileAttributesA(a_Path.c_str());
429  return ((FileAttrib != INVALID_FILE_ATTRIBUTES) && ((FileAttrib & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_DEVICE)) == 0));
430  #else
431  struct stat st;
432  return ((stat(a_Path.c_str(), &st) == 0) && S_ISREG(st.st_mode));
433  #endif
434 }
435 
436 
437 
438 
439 
440 long cFile::GetSize(const AString & a_FileName)
441 {
442  struct stat st;
443  if (stat(a_FileName.c_str(), &st) == 0)
444  {
445  return static_cast<int>(st.st_size);
446  }
447  return -1;
448 }
449 
450 
451 
452 
453 
454 bool cFile::CreateFolder(const AString & a_FolderPath)
455 {
456  #ifdef _WIN32
457  return (CreateDirectoryA(a_FolderPath.c_str(), nullptr) != 0);
458  #else
459  return (mkdir(a_FolderPath.c_str(), S_IRWXU | S_IRWXG | S_IRWXO) == 0);
460  #endif
461 }
462 
463 
464 
465 
466 
467 bool cFile::CreateFolderRecursive(const AString & a_FolderPath)
468 {
469  // Special case: Fail if the path is empty
470  if (a_FolderPath.empty())
471  {
472  return false;
473  }
474 
475  // Go through each path element and create the folder:
476  auto len = a_FolderPath.length();
477  for (decltype(len) i = 0; i < len; i++)
478  {
479  #ifdef _WIN32
480  if ((a_FolderPath[i] == '\\') || (a_FolderPath[i] == '/'))
481  #else
482  if (a_FolderPath[i] == '/')
483  #endif
484  {
485  CreateFolder(a_FolderPath.substr(0, i));
486  }
487  }
488  CreateFolder(a_FolderPath);
489 
490  // Check the result by querying whether the final path exists:
491  return IsFolder(a_FolderPath);
492 }
493 
494 
495 
496 
497 
499 {
500  AStringVector AllFiles;
501 
502  #ifdef _WIN32
503 
504  // If the folder name doesn't contain the terminating slash / backslash, add it:
505  AString FileFilter = a_Folder;
506  if (
507  !FileFilter.empty() &&
508  (FileFilter[FileFilter.length() - 1] != '\\') &&
509  (FileFilter[FileFilter.length() - 1] != '/')
510  )
511  {
512  FileFilter.push_back('\\');
513  }
514 
515  // Find all files / folders:
516  FileFilter.append("*.*");
517  HANDLE hFind;
518  WIN32_FIND_DATAA FindFileData;
519  if ((hFind = FindFirstFileA(FileFilter.c_str(), &FindFileData)) != INVALID_HANDLE_VALUE)
520  {
521  do
522  {
523  if (
524  (strcmp(FindFileData.cFileName, ".") == 0) ||
525  (strcmp(FindFileData.cFileName, "..") == 0)
526  )
527  {
528  continue;
529  }
530  AllFiles.push_back(FindFileData.cFileName);
531  } while (FindNextFileA(hFind, &FindFileData));
532  FindClose(hFind);
533  }
534 
535  #else // _WIN32
536 
537  DIR * dp;
538  AString Folder = a_Folder;
539  if (Folder.empty())
540  {
541  Folder = ".";
542  }
543  if ((dp = opendir(Folder.c_str())) == nullptr)
544  {
545  LOGERROR("Error (%i) opening directory \"%s\"\n", errno, Folder.c_str());
546  }
547  else
548  {
549  struct dirent *dirp;
550  while ((dirp = readdir(dp)) != nullptr)
551  {
552  if (
553  (strcmp(dirp->d_name, ".") == 0) ||
554  (strcmp(dirp->d_name, "..") == 0)
555  )
556  {
557  continue;
558  }
559  AllFiles.push_back(dirp->d_name);
560  }
561  closedir(dp);
562  }
563 
564  #endif // else _WIN32
565 
566  return AllFiles;
567 }
568 
569 
570 
571 
572 
574 {
575  cFile f;
576  if (!f.Open(a_FileName, fmRead))
577  {
578  return "";
579  }
580  AString Contents;
581  f.ReadRestOfFile(Contents);
582  return Contents;
583 }
584 
585 
586 
587 
588 
589 AString cFile::ChangeFileExt(const AString & a_FileName, const AString & a_NewExt)
590 {
591  auto res = a_FileName;
592 
593  // If the path separator is the last character of the string, return the string unmodified (refers to a folder):
594  #if defined(_MSC_VER)
595  // Find either path separator - MSVC CRT accepts slashes as separators, too
596  auto LastPathSep = res.find_last_of("/\\");
597  #elif defined(_WIN32)
598  // Windows with different CRTs support only the backslash separator
599  auto LastPathSep = res.rfind('\\');
600  #else
601  // Linux supports only the slash separator
602  auto LastPathSep = res.rfind('/');
603  #endif
604  if ((LastPathSep != AString::npos) && (LastPathSep + 1 == res.size()))
605  {
606  return res;
607  }
608 
609  // Append or replace the extension:
610  auto DotPos = res.rfind('.');
611  if (
612  (DotPos == AString::npos) || // No dot found
613  ((LastPathSep != AString::npos) && (LastPathSep > DotPos)) // Last dot is before the last path separator (-> in folder name)
614  )
615  {
616  // No extension, just append the new one:
617  if (!a_NewExt.empty() && (a_NewExt[0] != '.'))
618  {
619  // a_NewExt doesn't start with a dot, insert one:
620  res.push_back('.');
621  }
622  res.append(a_NewExt);
623  }
624  else
625  {
626  // Replace existing extension:
627  if (!a_NewExt.empty() && (a_NewExt[0] != '.'))
628  {
629  // a_NewExt doesn't start with a dot, keep the current one:
630  res.erase(DotPos + 1, AString::npos);
631  }
632  else
633  {
634  res.erase(DotPos, AString::npos);
635  }
636  res.append(a_NewExt);
637  }
638  return res;
639 }
640 
641 
642 
643 
644 
645 unsigned cFile::GetLastModificationTime(const AString & a_FileName)
646 {
647  struct stat st;
648  if (stat(a_FileName.c_str(), &st) < 0)
649  {
650  return 0;
651  }
652  #if defined(_WIN32)
653  // Windows returns times in local time already
654  return static_cast<unsigned>(st.st_mtime);
655  #elif defined(ANDROID)
656  // Identical to Linux below, but st_mtime is an unsigned long, so cast is needed:
657  auto Time = static_cast<time_t>(st.st_mtime);
658  return static_cast<unsigned>(mktime(localtime(&Time)));
659  #else
660  // Linux returns UTC time, convert to local timezone:
661  return static_cast<unsigned>(mktime(localtime(&st.st_mtime)));
662  #endif
663 }
664 
665 
666 
667 
668 
670 {
671  #ifdef _WIN32
672  return "\\";
673  #else
674  return "/";
675  #endif
676 }
677 
678 
679 
680 
681 
683 {
684  #ifdef _WIN32
685  return ".exe";
686  #else
687  return "";
688  #endif
689 }
690 
691 
692 
693 
694 
696 {
697  fflush(m_File);
698 }
699 
700 
701 
702 
703 
704 template <class StreamType>
705 FileStream<StreamType>::FileStream(const std::string & Path)
706 {
707  // Except on failbit, which is what open sets on failure:
708  FileStream::exceptions(FileStream::failbit | FileStream::badbit);
709 
710  // Open the file:
711  FileStream::open(Path);
712 
713  // Only subsequently except on serious errors, and not on conditions like EOF or malformed input:
714  FileStream::exceptions(FileStream::badbit);
715 }
716 
717 
718 
719 
720 
721 template <class StreamType>
722 FileStream<StreamType>::FileStream(const std::string & Path, const typename FileStream::openmode Mode)
723 {
724  // Except on failbit, which is what open sets on failure:
725  FileStream::exceptions(FileStream::failbit | FileStream::badbit);
726 
727  // Open the file:
728  FileStream::open(Path, Mode);
729 
730  // Only subsequently except on serious errors, and not on conditions like EOF or malformed input:
731  FileStream::exceptions(FileStream::badbit);
732 }
733 
734 
735 
736 
737 
738 #ifdef __clang__
739 #pragma clang diagnostic push
740 #pragma clang diagnostic ignored "-Wweak-template-vtables" // http://bugs.llvm.org/show_bug.cgi?id=18733
741 #endif
742 
743 // Instantiate the templated wrapper for input and output:
744 template class FileStream<std::ifstream>;
745 template class FileStream<std::ofstream>;
746 
747 #ifdef __clang__
748 #pragma clang diagnostic pop
749 #endif
#define ASSERT(x)
Definition: Globals.h:276
std::basic_string< std::byte > ContiguousByteBuffer
Definition: Globals.h:375
void LOGERROR(std::string_view a_Format, const Args &... args)
Definition: LoggerSimple.h:73
std::vector< AString > AStringVector
Definition: StringUtils.h:12
std::string AString
Definition: StringUtils.h:11
Definition: File.h:38
static bool CreateFolderRecursive(const AString &a_FolderPath)
Creates a new folder with the specified name, creating its parents if needed.
Definition: File.cpp:467
static AString GetExecutableExt()
Returns the customary executable extension used by the current platform.
Definition: File.cpp:682
static bool IsFolder(const AString &a_Path)
Returns true if the specified path is a folder.
Definition: File.cpp:410
int Read(void *a_Buffer, size_t a_NumBytes)
Reads up to a_NumBytes bytes into a_Buffer, returns the number of bytes actually read,...
Definition: File.cpp:144
static unsigned GetLastModificationTime(const AString &a_FileName)
Returns the last modification time (in current timezone) of the specified file.
Definition: File.cpp:645
long Seek(int iPosition)
Seeks to iPosition bytes from file start, returns old position or -1 for failure; asserts if not open...
Definition: File.cpp:197
static bool DeleteFile(const AString &a_FileName)
Deletes a file, returns true if successful.
Definition: File.cpp:368
static AString ChangeFileExt(const AString &a_FileName, const AString &a_NewExt)
Returns a_FileName with its extension changed to a_NewExt.
Definition: File.cpp:589
static bool Delete(const AString &a_Path)
Deletes a file or a folder, returns true if successful.
Definition: File.cpp:304
void Flush()
Flushes all the bufferef output into the file (only when writing)
Definition: File.cpp:695
static bool CreateFolder(const AString &a_FolderPath)
Creates a new folder with the specified name.
Definition: File.cpp:454
static bool Copy(const AString &a_SrcFileName, const AString &a_DstFileName)
Copies a file, returns true if successful.
Definition: File.cpp:386
static AString GetPathSeparator()
Returns the path separator used by the current platform.
Definition: File.cpp:669
static bool Rename(const AString &a_OrigPath, const AString &a_NewPath)
Renames a file or folder, returns true if successful.
Definition: File.cpp:377
eMode
The mode in which to open the file.
Definition: File.h:53
@ fmRead
Definition: File.h:54
@ fmWrite
Definition: File.h:55
@ fmAppend
Definition: File.h:57
@ fmReadWrite
Definition: File.h:56
cFile(void)
Simple constructor - creates an unopened file object, use Open() to open / create a real file.
Definition: File.cpp:20
static bool Exists(const AString &a_FileName)
Returns true if the file specified exists.
Definition: File.cpp:294
bool Open(const AString &iFileName, eMode iMode)
Definition: File.cpp:52
static AString ReadWholeFile(const AString &a_FileName)
Returns the entire contents of the specified file as a string.
Definition: File.cpp:573
static AStringVector GetFolderContents(const AString &a_Folder)
Returns the list of all items in the specified folder (files, folders, nix pipes, whatever's there).
Definition: File.cpp:498
~cFile()
Auto-closes the file, if open.
Definition: File.cpp:40
FILE * m_File
Definition: File.h:175
long GetSize(void) const
Returns the size of file, in bytes, or -1 for failure; asserts if not open.
Definition: File.cpp:233
static bool DeleteFolderContents(const AString &a_FolderName)
Deletes all content from the specified folder.
Definition: File.cpp:333
int ReadRestOfFile(AString &a_Contents)
Reads the file from current position till EOF into an AString; returns the number of bytes read or -1...
Definition: File.cpp:263
long Tell(void) const
Returns the current position (bytes from file start) or -1 for failure; asserts if not open.
Definition: File.cpp:217
bool IsOpen(void) const
Definition: File.cpp:118
int Write(const void *a_Buffer, size_t a_NumBytes)
Writes up to a_NumBytes bytes from a_Buffer, returns the number of bytes actually written,...
Definition: File.cpp:180
bool IsEOF(void) const
Definition: File.cpp:127
static bool IsFile(const AString &a_Path)
Returns true if the specified path is a regular file.
Definition: File.cpp:425
void Close(void)
Definition: File.cpp:102
static bool DeleteFolder(const AString &a_FolderName)
Deletes a folder, returns true if successful.
Definition: File.cpp:320
A wrapper for file streams that enables exceptions.
Definition: File.h:185
FileStream(const std::string &Path)
Definition: File.cpp:705