1 /******************************************************************************/
2 /* Mednafen - Multi-system Emulator                                           */
3 /******************************************************************************/
4 /* NativeVFS.cpp:
5 **  Copyright (C) 2018-2019 Mednafen Team
6 **
7 ** This program is free software; you can redistribute it and/or
8 ** modify it under the terms of the GNU General Public License
9 ** as published by the Free Software Foundation; either version 2
10 ** of the License, or (at your option) any later version.
11 **
12 ** This program is distributed in the hope that it will be useful,
13 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 ** GNU General Public License for more details.
16 **
17 ** You should have received a copy of the GNU General Public License
18 ** along with this program; if not, write to the Free Software Foundation, Inc.,
19 ** 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 */
21 
22 #include <mednafen/mednafen.h>
23 #include <mednafen/NativeVFS.h>
24 #include <mednafen/FileStream.h>
25 
26 #ifdef WIN32
27  #include <mednafen/win32-common.h>
28 #else
29  #include <unistd.h>
30  #include <dirent.h>
31 #endif
32 
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 
36 namespace Mednafen
37 {
38 
NativeVFS()39 NativeVFS::NativeVFS() : VirtualFS(MDFN_PS, (PSS_STYLE == 2) ? "\\/" : PSS)
40 {
41 
42 
43 }
44 
~NativeVFS()45 NativeVFS::~NativeVFS()
46 {
47 
48 
49 }
50 
open(const std::string & path,const uint32 mode,const int do_lock,const bool throw_on_noent,const CanaryType canary)51 Stream* NativeVFS::open(const std::string& path, const uint32 mode, const int do_lock, const bool throw_on_noent, const CanaryType canary)
52 {
53  if(canary != CanaryType::open)
54   _exit(-1);
55 
56  try
57  {
58   return new FileStream(path, mode, do_lock);
59  }
60  catch(MDFN_Error& e)
61  {
62   if(e.GetErrno() != ENOENT || throw_on_noent)
63    throw;
64 
65   return nullptr;
66  }
67 }
68 
mkdir(const std::string & path,const bool throw_on_exist)69 bool NativeVFS::mkdir(const std::string& path, const bool throw_on_exist)
70 {
71  if(path.find('\0') != std::string::npos)
72   throw MDFN_Error(EINVAL, _("Error creating directory \"%s\": %s"), path.c_str(), _("Null character in path."));
73 
74  #ifdef WIN32
75  bool invalid_utf8;
76  std::u16string u16path = UTF8_to_UTF16(path, &invalid_utf8, true);
77 
78  if(invalid_utf8)
79   throw MDFN_Error(EINVAL, _("Error creating directory \"%s\": %s"), path.c_str(), _("Invalid UTF-8"));
80 
81  if(::_wmkdir((const wchar_t*)u16path.c_str()))
82  #elif defined HAVE_MKDIR
83   #if MKDIR_TAKES_ONE_ARG
84    if(::mkdir(path.c_str()))
85   #else
86    if(::mkdir(path.c_str(), S_IRWXU))
87   #endif
88  #else
89   #error "mkdir() missing?!"
90  #endif
91  {
92   ErrnoHolder ene(errno);
93 
94   if(ene.Errno() != EEXIST || throw_on_exist)
95    throw MDFN_Error(ene.Errno(), _("Error creating directory \"%s\": %s"), path.c_str(), ene.StrError());
96 
97   return false;
98  }
99 
100  return true;
101 }
102 
103 
unlink(const std::string & path,const bool throw_on_noent,const CanaryType canary)104 bool NativeVFS::unlink(const std::string& path, const bool throw_on_noent, const CanaryType canary)
105 {
106  if(canary != CanaryType::unlink)
107   _exit(-1);
108 
109  if(path.find('\0') != std::string::npos)
110   throw MDFN_Error(EINVAL, _("Error unlinking \"%s\": %s"), path.c_str(), _("Null character in path."));
111 
112  #ifdef WIN32
113  bool invalid_utf8;
114  std::u16string u16path = UTF8_to_UTF16(path, &invalid_utf8, true);
115 
116  if(invalid_utf8)
117   throw MDFN_Error(EINVAL, _("Error unlinking \"%s\": %s"), path.c_str(), _("Invalid UTF-8"));
118 
119  if(::_wunlink((const wchar_t*)u16path.c_str()))
120  #else
121  if(::unlink(path.c_str()))
122  #endif
123  {
124   ErrnoHolder ene(errno);
125 
126   if(ene.Errno() == ENOENT && !throw_on_noent)
127    return false;
128   else
129    throw MDFN_Error(ene.Errno(), _("Error unlinking \"%s\": %s"), path.c_str(), ene.StrError());
130  }
131 
132  return true;
133 }
134 
rename(const std::string & oldpath,const std::string & newpath,const CanaryType canary)135 void NativeVFS::rename(const std::string& oldpath, const std::string& newpath, const CanaryType canary)
136 {
137  if(canary != CanaryType::rename)
138   _exit(-1);
139 
140  if(oldpath.find('\0') != std::string::npos || newpath.find('\0') != std::string::npos)
141   throw MDFN_Error(EINVAL, _("Error renaming \"%s\" to \"%s\": %s"), oldpath.c_str(), newpath.c_str(), _("Null character in path."));
142 
143  #ifdef WIN32
144  bool invalid_utf8_old;
145  bool invalid_utf8_new;
146  std::u16string u16oldpath = UTF8_to_UTF16(oldpath, &invalid_utf8_old, true);
147  std::u16string u16newpath = UTF8_to_UTF16(newpath, &invalid_utf8_new, true);
148 
149  if(invalid_utf8_old || invalid_utf8_new)
150   throw MDFN_Error(EINVAL, _("Error renaming \"%s\" to \"%s\": %s"), oldpath.c_str(), newpath.c_str(), _("Invalid UTF-8"));
151 
152  if(::_wrename((const wchar_t*)u16oldpath.c_str(), (const wchar_t*)u16newpath.c_str()))
153  {
154   ErrnoHolder ene(errno);
155 
156   // TODO/FIXME: Ensure oldpath and newpath don't refer to the same file via symbolic link.
157   if(oldpath != newpath && (ene.Errno() == EACCES || ene.Errno() == EEXIST))
158   {
159    if(::_wunlink((const wchar_t*)u16newpath.c_str()) || _wrename((const wchar_t*)u16oldpath.c_str(), (const wchar_t*)u16newpath.c_str()))
160     throw MDFN_Error(ene.Errno(), _("Error renaming \"%s\" to \"%s\": %s"), oldpath.c_str(), newpath.c_str(), ene.StrError());
161   }
162   else
163    throw MDFN_Error(ene.Errno(), _("Error renaming \"%s\" to \"%s\": %s"), oldpath.c_str(), newpath.c_str(), ene.StrError());
164  }
165  #else
166 
167  if(::rename(oldpath.c_str(), newpath.c_str()))
168  {
169   ErrnoHolder ene(errno);
170 
171   throw MDFN_Error(ene.Errno(), _("Error renaming \"%s\" to \"%s\": %s"), oldpath.c_str(), newpath.c_str(), ene.StrError());
172  }
173 
174  #endif
175 }
176 
finfo(const std::string & path,FileInfo * fi,const bool throw_on_noent)177 bool NativeVFS::finfo(const std::string& path, FileInfo* fi, const bool throw_on_noent)
178 {
179  if(path.find('\0') != std::string::npos)
180   throw MDFN_Error(EINVAL, _("Error getting file information for \"%s\": %s"), path.c_str(), _("Null character in path."));
181  //
182  //
183  #ifdef WIN32
184  bool invalid_utf8;
185  std::u16string u16path = UTF8_to_UTF16(path, &invalid_utf8, true);
186  struct _stati64 buf;
187 
188  if(invalid_utf8)
189   throw MDFN_Error(EINVAL, _("Error getting file information for \"%s\": %s"), path.c_str(), _("Invalid UTF-8"));
190 
191  if(::_wstati64((const wchar_t*)u16path.c_str(), &buf))
192  #else
193  struct stat buf;
194  if(::stat(path.c_str(), &buf))
195  #endif
196  {
197   ErrnoHolder ene(errno);
198 
199   if(ene.Errno() == ENOENT && !throw_on_noent)
200    return false;
201 
202   throw MDFN_Error(ene.Errno(), _("Error getting file information for \"%s\": %s"), path.c_str(), ene.StrError());
203  }
204 
205  if(fi)
206  {
207   FileInfo new_fi;
208 
209   new_fi.size = buf.st_size;
210   new_fi.mtime_us = (int64)buf.st_mtime * 1000 * 1000;
211 
212   new_fi.is_regular = S_ISREG(buf.st_mode);
213   new_fi.is_directory = S_ISDIR(buf.st_mode);
214 
215   *fi = new_fi;
216  }
217 
218  return true;
219 }
220 
readdirentries(const std::string & path,std::function<bool (const std::string &)> callb)221 void NativeVFS::readdirentries(const std::string& path, std::function<bool(const std::string&)> callb)
222 {
223  if(path.find('\0') != std::string::npos)
224   throw MDFN_Error(EINVAL, _("Error reading directory entries from \"%s\": %s"), path.c_str(), _("Null character in path."));
225  //
226  //
227 #ifdef WIN32
228  //
229  // TODO: drive-relative?  probably would need to change how we represent and compose paths in Mednafen...
230  //
231  HANDLE dp = nullptr;
232  bool invalid_utf8;
233  std::u16string u16path = UTF8_to_UTF16(path + preferred_path_separator + '*', &invalid_utf8, true);
234  WIN32_FIND_DATAW ded;
235 
236  if(invalid_utf8)
237   throw MDFN_Error(EINVAL, _("Error reading directory entries from \"%s\": %s"), path.c_str(), _("Invalid UTF-8"));
238 
239  try
240  {
241   if(!(dp = FindFirstFileW((const wchar_t*)u16path.c_str(), &ded)))
242   {
243    const uint32 ec = GetLastError();
244 
245    throw MDFN_Error(0, _("Error reading directory entries from \"%s\": %s"), path.c_str(), Win32Common::ErrCodeToString(ec).c_str());
246   }
247 
248   for(;;)
249   {
250    //printf("%s\n", UTF16_to_UTF8((const char16_t*)ded.cFileName, nullptr, true).c_str());
251    if(!callb(UTF16_to_UTF8((const char16_t*)ded.cFileName, nullptr, true)))
252     break;
253    //
254    if(!FindNextFileW(dp, &ded))
255    {
256     const uint32 ec = GetLastError();
257 
258     if(ec == ERROR_NO_MORE_FILES)
259      break;
260     else
261      throw MDFN_Error(0, _("Error reading directory entries from \"%s\": %s"), path.c_str(), Win32Common::ErrCodeToString(ec).c_str());
262    }
263   }
264   //
265   FindClose(dp);
266   dp = nullptr;
267  }
268  catch(...)
269  {
270   if(dp)
271   {
272    FindClose(dp);
273    dp = nullptr;
274   }
275   throw;
276  }
277 #else
278  DIR* dp = nullptr;
279  std::string fname;
280 
281  fname.reserve(512);
282 
283  try
284  {
285   if(!(dp = opendir(path.c_str())))
286   {
287    ErrnoHolder ene(errno);
288 
289    throw MDFN_Error(ene.Errno(), _("Error reading directory entries from \"%s\": %s"), path.c_str(), ene.StrError());
290   }
291   //
292   for(;;)
293   {
294    struct dirent* de;
295 
296    errno = 0;
297    if(!(de = readdir(dp)))
298    {
299     if(errno)
300     {
301      ErrnoHolder ene(errno);
302 
303      throw MDFN_Error(ene.Errno(), _("Error reading directory entries from \"%s\": %s"), path.c_str(), ene.StrError());
304     }
305     break;
306    }
307    //
308    fname.clear();
309    fname += de->d_name;
310    //
311    if(!callb(fname))
312     break;
313   }
314   //
315   closedir(dp);
316   dp = nullptr;
317  }
318  catch(...)
319  {
320   if(dp)
321   {
322    closedir(dp);
323    dp = nullptr;
324   }
325   throw;
326  }
327 #endif
328 }
329 
330 // Really dumb, maybe we should use boost?
is_absolute_path(const std::string & path)331 bool NativeVFS::is_absolute_path(const std::string& path)
332 {
333  if(!path.size())
334   return false;
335 
336  if(is_path_separator(path[0]))
337   return true;
338 
339  #if defined(WIN32) || defined(DOS)
340  if(path.size() >= 2 && path[1] == ':')
341  {
342   if((path[0] >= 'a' && path[0] <= 'z') || (path[0] >= 'A' && path[0] <= 'Z'))
343    return true;
344  }
345  #endif
346 
347  return false;
348 }
349 
check_firop_safe(const std::string & path)350 void NativeVFS::check_firop_safe(const std::string& path)
351 {
352  //
353  // First, check for any 8-bit characters, and print a warning about portability.
354  //
355  for(size_t x = 0; x < path.size(); x++)
356  {
357   if(path[x] & 0x80)
358   {
359    MDFN_printf(_("WARNING: Referenced path \"%s\" contains at least one 8-bit non-ASCII character; this may cause portability issues.\n"), path.c_str());
360    break;
361   }
362  }
363 
364  if(!MDFN_GetSettingB("filesys.untrusted_fip_check"))
365   return;
366 
367  // We could make this more OS-specific, but it shouldn't hurt to try to weed out usage of characters that are path
368  // separators in one OS but not in another, and we'd also run more of a risk of missing a special path separator case
369  // in some OS.
370  std::string unsafe_reason;
371 
372 #ifdef WIN32
373  if(!UTF8_validate(path, true))
374   unsafe_reason += _("Invalid UTF-8. ");
375 #endif
376 
377  if(path.find('\0') != std::string::npos)
378   unsafe_reason += _("Contains null(0). ");
379 
380  if(path.find(':') != std::string::npos)
381   unsafe_reason += _("Contains colon. ");
382 
383  if(path.find('\\') != std::string::npos)
384   unsafe_reason += _("Contains backslash. ");
385 
386  if(path.find('/') != std::string::npos)
387   unsafe_reason += _("Contains forward slash. ");
388 
389  if(path == "..")
390   unsafe_reason += _("Is parent directory. ");
391 
392 #if defined(DOS) || defined(WIN32)
393  //
394  // http://support.microsoft.com/kb/74496
395  // http://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html
396  //
397  {
398   static const char* dev_names[] =
399   {
400    "CON", "PRN", "AUX", "CLOCK$", "NUL", "CONIN$", "CONOUT$",
401    "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "COM¹", "COM²", "COM³",
402    "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", "LPT¹", "LPT²", "LPT³",
403    NULL
404   };
405   //
406   const char* pcs = path.c_str();
407   for(const char** ls = dev_names; *ls != NULL; ls++)
408   {
409    size_t lssl = strlen(*ls);
410 
411    if(!MDFN_strazicmp(*ls, pcs, lssl))
412    {
413     if(pcs[lssl] == 0 || pcs[lssl] == ':' || pcs[lssl] == '.' || pcs[lssl] == ' ')
414     {
415      unsafe_reason += _("Is (likely) a reserved device name. ");
416      break;
417     }
418    }
419   }
420  }
421 #endif
422 
423  if(unsafe_reason.size() > 0)
424   throw MDFN_Error(0, _("Referenced path \"%s\" (escaped) is potentially unsafe.  %s Refer to the documentation about the \"filesys.untrusted_fip_check\" setting.\n"), MDFN_strescape(path).c_str(), unsafe_reason.c_str());
425 }
426 
get_file_path_components(const std::string & file_path,std::string * dir_path_out,std::string * file_base_out,std::string * file_ext_out)427 void NativeVFS::get_file_path_components(const std::string &file_path, std::string* dir_path_out, std::string* file_base_out, std::string *file_ext_out)
428 {
429 #if defined(WIN32) || defined(DOS)
430  if(file_path.size() >= 3 && ((file_path[0] >= 'a' && file_path[0] <= 'z') || (file_path[0] >= 'A' && file_path[0] <= 'Z')) && file_path[1] == ':' && file_path.find_last_of(allowed_path_separators) == std::string::npos)
431  {
432   VirtualFS::get_file_path_components(file_path.substr(0, 2) + '.' + preferred_path_separator + file_path.substr(2), dir_path_out, file_base_out, file_ext_out);
433   return;
434  }
435 #endif
436 
437  VirtualFS::get_file_path_components(file_path, dir_path_out, file_base_out, file_ext_out);
438 }
439 
440 }
441