The Battle for Wesnoth  1.17.0-dev
filesystem.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2021
3  by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 /**
17  * @file
18  * File-IO
19  */
20 #define GETTEXT_DOMAIN "wesnoth-lib"
21 
22 #include "filesystem.hpp"
23 
24 #include "config.hpp"
25 #include "deprecation.hpp"
26 #include "game_config.hpp"
27 #include "gettext.hpp"
28 #include "log.hpp"
32 
33 #include <boost/algorithm/string.hpp>
34 #include <boost/filesystem.hpp>
35 #include <boost/filesystem/fstream.hpp>
36 #include <boost/iostreams/device/file_descriptor.hpp>
37 #include <boost/iostreams/stream.hpp>
38 #include "game_config_view.hpp"
39 
40 #ifdef _WIN32
41 #include "log_windows.hpp"
42 
43 #include <boost/locale.hpp>
44 
45 #include <windows.h>
46 #include <shlobj.h>
47 #include <shlwapi.h>
48 
49 // Work around TDM-GCC not #defining this according to @newfrenchy83.
50 #ifndef VOLUME_NAME_NONE
51 #define VOLUME_NAME_NONE 0x4
52 #endif
53 
54 #endif /* !_WIN32 */
55 
56 #include <algorithm>
57 #include <set>
58 
59 // Copied from boost::predef, as it's there only since 1.55.
60 #if defined(__APPLE__) && defined(__MACH__) && defined(__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__)
61 
62 #define WESNOTH_BOOST_OS_IOS (__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__*1000)
63 #include <SDL2/SDL_filesystem.h>
64 
65 #endif
66 
67 
68 static lg::log_domain log_filesystem("filesystem");
69 #define DBG_FS LOG_STREAM(debug, log_filesystem)
70 #define LOG_FS LOG_STREAM(info, log_filesystem)
71 #define WRN_FS LOG_STREAM(warn, log_filesystem)
72 #define ERR_FS LOG_STREAM(err, log_filesystem)
73 
74 namespace bfs = boost::filesystem;
75 using boost::system::error_code;
76 
77 namespace
78 {
79 // These are the filenames that get special processing
80 const std::string maincfg_filename = "_main.cfg";
81 const std::string finalcfg_filename = "_final.cfg";
82 const std::string initialcfg_filename = "_initial.cfg";
83 
84 // only used by windows but put outside the ifdef to let it check by ci build.
85 class customcodecvt : public std::codecvt<wchar_t /*intern*/, char /*extern*/, std::mbstate_t>
86 {
87 private:
88  // private static helper things
89  template<typename char_t_to>
90  struct customcodecvt_do_conversion_writer
91  {
92  customcodecvt_do_conversion_writer(char_t_to*& _to_next, char_t_to* _to_end)
93  : to_next(_to_next)
94  , to_end(_to_end)
95  {
96  }
97 
98  char_t_to*& to_next;
99  char_t_to* to_end;
100 
101  bool can_push(std::size_t count) const
102  {
103  return static_cast<std::size_t>(to_end - to_next) > count;
104  }
105 
106  void push(char_t_to val)
107  {
108  assert(to_next != to_end);
109  *to_next++ = val;
110  }
111  };
112 
113  template<typename char_t_from, typename char_t_to>
114  static void customcodecvt_do_conversion(std::mbstate_t& /*state*/,
115  const char_t_from* from,
116  const char_t_from* from_end,
117  const char_t_from*& from_next,
118  char_t_to* to,
119  char_t_to* to_end,
120  char_t_to*& to_next)
121  {
122  typedef typename ucs4_convert_impl::convert_impl<char_t_from>::type impl_type_from;
123  typedef typename ucs4_convert_impl::convert_impl<char_t_to>::type impl_type_to;
124 
125  from_next = from;
126  to_next = to;
127  customcodecvt_do_conversion_writer<char_t_to> writer(to_next, to_end);
128 
129  while(from_next != from_end) {
130  impl_type_to::write(writer, impl_type_from::read(from_next, from_end));
131  }
132  }
133 
134 public:
135  // Not used by boost filesystem
136  int do_encoding() const noexcept
137  {
138  return 0;
139  }
140 
141  // Not used by boost filesystem
142  bool do_always_noconv() const noexcept
143  {
144  return false;
145  }
146 
147  int do_length(std::mbstate_t& /*state*/, const char* /*from*/, const char* /*from_end*/, std::size_t /*max*/) const
148  {
149  // Not used by boost filesystem
150  throw "Not supported";
151  }
152 
153  std::codecvt_base::result unshift(
154  std::mbstate_t& /*state*/, char* /*to*/, char* /*to_end*/, char*& /*to_next*/) const
155  {
156  // Not used by boost filesystem
157  throw "Not supported";
158  }
159 
160  // there are still some methods which could be implemented but aren't because boost filesystem won't use them.
161  std::codecvt_base::result do_in(std::mbstate_t& state,
162  const char* from,
163  const char* from_end,
164  const char*& from_next,
165  wchar_t* to,
166  wchar_t* to_end,
167  wchar_t*& to_next) const
168  {
169  try {
170  customcodecvt_do_conversion<char, wchar_t>(state, from, from_end, from_next, to, to_end, to_next);
171  } catch(...) {
172  ERR_FS << "Invalid UTF-8 string'" << std::string(from, from_end) << "' " << std::endl;
174  }
175 
176  return std::codecvt_base::ok;
177  }
178 
179  std::codecvt_base::result do_out(std::mbstate_t& state,
180  const wchar_t* from,
181  const wchar_t* from_end,
182  const wchar_t*& from_next,
183  char* to,
184  char* to_end,
185  char*& to_next) const
186  {
187  try {
188  customcodecvt_do_conversion<wchar_t, char>(state, from, from_end, from_next, to, to_end, to_next);
189  } catch(...) {
190  ERR_FS << "Invalid UTF-16 string" << std::endl;
192  }
193 
194  return std::codecvt_base::ok;
195  }
196 };
197 
198 #ifdef _WIN32
199 class static_runner
200 {
201 public:
202  static_runner()
203  {
204  // Boost uses the current locale to generate a UTF-8 one
205  std::locale utf8_loc = boost::locale::generator().generate("");
206 
207  // use a custom locale because we want to use out log.hpp functions in case of an invalid string.
208  utf8_loc = std::locale(utf8_loc, new customcodecvt());
209 
210  boost::filesystem::path::imbue(utf8_loc);
211  }
212 };
213 
214 static static_runner static_bfs_path_imbuer;
215 
216 bool is_filename_case_correct(const std::string& fname, const boost::iostreams::file_descriptor_source& fd)
217 {
218  wchar_t real_path[MAX_PATH];
219  GetFinalPathNameByHandleW(fd.handle(), real_path, MAX_PATH - 1, VOLUME_NAME_NONE);
220 
221  std::string real_name = filesystem::base_name(unicode_cast<std::string>(std::wstring(real_path)));
222  return real_name == filesystem::base_name(fname);
223 }
224 
225 #else
226 bool is_filename_case_correct(const std::string& /*fname*/, const boost::iostreams::file_descriptor_source& /*fd*/)
227 {
228  return true;
229 }
230 #endif
231 } // namespace
232 
233 namespace filesystem
234 {
235 
236 static void push_if_exists(std::vector<std::string>* vec, const bfs::path& file, bool full)
237 {
238  if(vec != nullptr) {
239  if(full) {
240  vec->push_back(file.generic_string());
241  } else {
242  vec->push_back(file.filename().generic_string());
243  }
244  }
245 }
246 
247 static inline bool error_except_not_found(const error_code& ec)
248 {
249  return ec && ec != boost::system::errc::no_such_file_or_directory;
250 }
251 
252 static bool is_directory_internal(const bfs::path& fpath)
253 {
254  error_code ec;
255  bool is_dir = bfs::is_directory(fpath, ec);
256  if(error_except_not_found(ec)) {
257  LOG_FS << "Failed to check if " << fpath.string() << " is a directory: " << ec.message() << '\n';
258  }
259 
260  return is_dir;
261 }
262 
263 static bool file_exists(const bfs::path& fpath)
264 {
265  error_code ec;
266  bool exists = bfs::exists(fpath, ec);
267  if(error_except_not_found(ec)) {
268  ERR_FS << "Failed to check existence of file " << fpath.string() << ": " << ec.message() << '\n';
269  }
270 
271  return exists;
272 }
273 
274 static bfs::path get_dir(const bfs::path& dirpath)
275 {
276  bool is_dir = is_directory_internal(dirpath);
277  if(!is_dir) {
278  error_code ec;
279  bfs::create_directory(dirpath, ec);
280 
281  if(ec) {
282  ERR_FS << "Failed to create directory " << dirpath.string() << ": " << ec.message() << '\n';
283  }
284 
285  // This is probably redundant
286  is_dir = is_directory_internal(dirpath);
287  }
288 
289  if(!is_dir) {
290  ERR_FS << "Could not open or create directory " << dirpath.string() << '\n';
291  return std::string();
292  }
293 
294  return dirpath;
295 }
296 
297 static bool create_directory_if_missing(const bfs::path& dirpath)
298 {
299  error_code ec;
300  bfs::file_status fs = bfs::status(dirpath, ec);
301 
302  if(error_except_not_found(ec)) {
303  ERR_FS << "Failed to retrieve file status for " << dirpath.string() << ": " << ec.message() << '\n';
304  return false;
305  } else if(bfs::is_directory(fs)) {
306  DBG_FS << "directory " << dirpath.string() << " exists, not creating\n";
307  return true;
308  } else if(bfs::exists(fs)) {
309  ERR_FS << "cannot create directory " << dirpath.string() << "; file exists\n";
310  return false;
311  }
312 
313  bool created = bfs::create_directory(dirpath, ec);
314  if(ec) {
315  ERR_FS << "Failed to create directory " << dirpath.string() << ": " << ec.message() << '\n';
316  }
317 
318  return created;
319 }
320 
322 {
323  DBG_FS << "creating recursive directory: " << dirpath.string() << '\n';
324 
325  if(dirpath.empty()) {
326  return false;
327  }
328 
329  error_code ec;
330  bfs::file_status fs = bfs::status(dirpath);
331 
332  if(error_except_not_found(ec)) {
333  ERR_FS << "Failed to retrieve file status for " << dirpath.string() << ": " << ec.message() << '\n';
334  return false;
335  } else if(bfs::is_directory(fs)) {
336  return true;
337  } else if(bfs::exists(fs)) {
338  return false;
339  }
340 
341  if(!dirpath.has_parent_path() || create_directory_if_missing_recursive(dirpath.parent_path())) {
342  return create_directory_if_missing(dirpath);
343  } else {
344  ERR_FS << "Could not create parents to " << dirpath.string() << '\n';
345  return false;
346  }
347 }
348 
349 void get_files_in_dir(const std::string& dir,
350  std::vector<std::string>* files,
351  std::vector<std::string>* dirs,
352  name_mode mode,
353  filter_mode filter,
354  reorder_mode reorder,
355  file_tree_checksum* checksum)
356 {
357  if(bfs::path(dir).is_relative() && !game_config::path.empty()) {
358  bfs::path absolute_dir(game_config::path);
359  absolute_dir /= dir;
360 
361  if(is_directory_internal(absolute_dir)) {
362  get_files_in_dir(absolute_dir.string(), files, dirs, mode, filter, reorder, checksum);
363  return;
364  }
365  }
366 
367  const bfs::path dirpath(dir);
368 
369  if(reorder == reorder_mode::DO_REORDER) {
370  LOG_FS << "searching for _main.cfg in directory " << dir << '\n';
371  const bfs::path maincfg = dirpath / maincfg_filename;
372 
373  if(file_exists(maincfg)) {
374  LOG_FS << "_main.cfg found : " << maincfg << '\n';
375  push_if_exists(files, maincfg, mode == name_mode::ENTIRE_FILE_PATH);
376  return;
377  }
378  }
379 
380  error_code ec;
381  bfs::directory_iterator di(dirpath, ec);
382  bfs::directory_iterator end;
383 
384  // Probably not a directory, let the caller deal with it.
385  if(ec) {
386  return;
387  }
388 
389  for(; di != end; ++di) {
390  bfs::file_status st = di->status(ec);
391  if(ec) {
392  LOG_FS << "Failed to get file status of " << di->path().string() << ": " << ec.message() << '\n';
393  continue;
394  }
395 
396  if(st.type() == bfs::regular_file) {
397  {
398  std::string basename = di->path().filename().string();
399  if(filter == filter_mode::SKIP_PBL_FILES && looks_like_pbl(basename))
400  continue;
401  if(!basename.empty() && basename[0] == '.')
402  continue;
403  }
404 
405  push_if_exists(files, di->path(), mode == name_mode::ENTIRE_FILE_PATH);
406 
407  if(checksum != nullptr) {
408  std::time_t mtime = bfs::last_write_time(di->path(), ec);
409  if(ec) {
410  LOG_FS << "Failed to read modification time of " << di->path().string() << ": " << ec.message()
411  << '\n';
412  } else if(mtime > checksum->modified) {
413  checksum->modified = mtime;
414  }
415 
416  uintmax_t size = bfs::file_size(di->path(), ec);
417  if(ec) {
418  LOG_FS << "Failed to read filesize of " << di->path().string() << ": " << ec.message() << '\n';
419  } else {
420  checksum->sum_size += size;
421  }
422 
423  checksum->nfiles++;
424  }
425  } else if(st.type() == bfs::directory_file) {
426  std::string basename = di->path().filename().string();
427 
428  if(!basename.empty() && basename[0] == '.') {
429  continue;
430  }
431 
432  if(filter == filter_mode::SKIP_MEDIA_DIR && (basename == "images" || basename == "sounds")) {
433  continue;
434  }
435 
436  const bfs::path inner_main(di->path() / maincfg_filename);
437  bfs::file_status main_st = bfs::status(inner_main, ec);
438 
439  if(error_except_not_found(ec)) {
440  LOG_FS << "Failed to get file status of " << inner_main.string() << ": " << ec.message() << '\n';
441  } else if(reorder == reorder_mode::DO_REORDER && main_st.type() == bfs::regular_file) {
442  LOG_FS << "_main.cfg found : "
443  << (mode == name_mode::ENTIRE_FILE_PATH ? inner_main.string() : inner_main.filename().string()) << '\n';
444  push_if_exists(files, inner_main, mode == name_mode::ENTIRE_FILE_PATH);
445  } else {
446  push_if_exists(dirs, di->path(), mode == name_mode::ENTIRE_FILE_PATH);
447  }
448  }
449  }
450 
451  if(files != nullptr) {
452  std::sort(files->begin(), files->end());
453  }
454 
455  if(dirs != nullptr) {
456  std::sort(dirs->begin(), dirs->end());
457  }
458 
459  if(files != nullptr && reorder == reorder_mode::DO_REORDER) {
460  // move finalcfg_filename, if present, to the end of the vector
461  for(unsigned int i = 0; i < files->size(); i++) {
462  if(ends_with((*files)[i], "/" + finalcfg_filename)) {
463  files->push_back((*files)[i]);
464  files->erase(files->begin() + i);
465  break;
466  }
467  }
468 
469  // move initialcfg_filename, if present, to the beginning of the vector
470  int foundit = -1;
471  for(unsigned int i = 0; i < files->size(); i++)
472  if(ends_with((*files)[i], "/" + initialcfg_filename)) {
473  foundit = i;
474  break;
475  }
476  if(foundit > 0) {
477  std::string initialcfg = (*files)[foundit];
478  for(unsigned int i = foundit; i > 0; i--)
479  (*files)[i] = (*files)[i - 1];
480  (*files)[0] = initialcfg;
481  }
482  }
483 }
484 
485 std::string get_dir(const std::string& dir)
486 {
487  return get_dir(bfs::path(dir)).string();
488 }
489 
490 std::string get_next_filename(const std::string& name, const std::string& extension)
491 {
492  std::string next_filename;
493  int counter = 0;
494 
495  do {
496  std::stringstream filename;
497 
498  filename << name;
499  filename.width(3);
500  filename.fill('0');
501  filename.setf(std::ios_base::right);
502  filename << counter << extension;
503 
504  counter++;
505  next_filename = filename.str();
506  } while(file_exists(next_filename) && counter < 1000);
507 
508  return next_filename;
509 }
510 
512 
513 const std::string get_version_path_suffix(const version_info& version)
514 {
515  std::ostringstream s;
516  s << version.major_version() << '.' << version.minor_version();
517  return s.str();
518 }
519 
520 const std::string& get_version_path_suffix()
521 {
522  static std::string suffix;
523 
524  // We only really need to generate this once since
525  // the version number cannot change during runtime.
526 
527  if(suffix.empty()) {
529  }
530 
531  return suffix;
532 }
533 
534 #if defined(__APPLE__) && !defined(__IPHONEOS__)
535  // Starting from Wesnoth 1.14.6, we have to use sandboxing function on macOS
536  // The problem is, that only signed builds can use sandbox. Unsigned builds
537  // would use other config directory then signed ones. So if we don't want
538  // to have two separate config dirs, we have to create symlink to new config
539  // location if exists. This part of code is only required on macOS.
540  static void migrate_apple_config_directory_for_unsandboxed_builds()
541  {
542  const char* home_str = getenv("HOME");
543  bfs::path home = home_str ? home_str : ".";
544 
545  // We don't know which of the two is in PREFERENCES_DIR now.
546  boost::filesystem::path old_saves_dir = home / "Library/Application Support/Wesnoth_";
547  old_saves_dir += get_version_path_suffix();
548  boost::filesystem::path new_saves_dir = home / "Library/Containers/org.wesnoth.Wesnoth/Data/Library/Application Support/Wesnoth_";
549  new_saves_dir += get_version_path_suffix();
550 
551  if(bfs::is_directory(new_saves_dir)) {
552  if(!bfs::exists(old_saves_dir)) {
553  std::cout << "Apple developer's userdata migration: ";
554  std::cout << "symlinking " << old_saves_dir << " to " << new_saves_dir << "\n";
555  bfs::create_symlink(new_saves_dir, old_saves_dir);
556  } else if(!bfs::symbolic_link_exists(old_saves_dir)) {
557  std::cout << "Apple developer's userdata migration: ";
558  std::cout << "Problem! Old (non-containerized) directory " << old_saves_dir << " is not a symlink. Your savegames are scattered around 2 locations.\n";
559  }
560  return;
561  }
562  }
563 #endif
564 
565 
566 static void setup_user_data_dir()
567 {
568 #if defined(__APPLE__) && !defined(__IPHONEOS__)
569  migrate_apple_config_directory_for_unsandboxed_builds();
570 #endif
571  if(!file_exists(user_data_dir)) {
573  }
574 
575  if(!create_directory_if_missing_recursive(user_data_dir)) {
576  ERR_FS << "could not open or create user data directory at " << user_data_dir.string() << '\n';
577  return;
578  }
579  // TODO: this may not print the error message if the directory exists but we don't have the proper permissions
580 
581  // Create user data and add-on directories
582  create_directory_if_missing(user_data_dir / "editor");
583  create_directory_if_missing(user_data_dir / "editor" / "maps");
584  create_directory_if_missing(user_data_dir / "editor" / "scenarios");
585  create_directory_if_missing(user_data_dir / "data");
586  create_directory_if_missing(user_data_dir / "data" / "add-ons");
587  create_directory_if_missing(user_data_dir / "saves");
588  create_directory_if_missing(user_data_dir / "persist");
589 
590 #ifdef _WIN32
592 #endif
593 }
594 
595 #ifdef _WIN32
596 // As a convenience for portable installs on Windows, relative paths with . or
597 // .. as the first component are considered relative to the current workdir
598 // instead of Documents/My Games.
599 static bool is_path_relative_to_cwd(const std::string& str)
600 {
601  const bfs::path p(str);
602 
603  if(p.empty()) {
604  return false;
605  }
606 
607  return *p.begin() == "." || *p.begin() == "..";
608 }
609 #endif
610 
611 void set_user_data_dir(std::string newprefdir)
612 {
613  [[maybe_unused]] bool relative_ok = false;
614 
615 #ifdef PREFERENCES_DIR
616  if(newprefdir.empty()) {
617  newprefdir = PREFERENCES_DIR;
618  relative_ok = true;
619  }
620 #endif
621 
622 #ifdef _WIN32
623  if(newprefdir.size() > 2 && newprefdir[1] == ':') {
624  // allow absolute path override
625  user_data_dir = newprefdir;
626  } else if(is_path_relative_to_cwd(newprefdir)) {
627  // Custom directory relative to workdir (for portable installs, etc.)
628  user_data_dir = get_cwd() + "/" + newprefdir;
629  } else {
630  if(newprefdir.empty()) {
631  newprefdir = "Wesnoth" + get_version_path_suffix();
632  } else {
633 #ifdef PREFERENCES_DIR
634  if (newprefdir != PREFERENCES_DIR)
635 #endif
636  {
637  // TRANSLATORS: translate the part inside <...> only
638  deprecated_message(_("--userdata-dir=<relative path that doesn't start with a period>"),
640  {1, 17, 0},
641  _("Use an absolute path, or a relative path that starts with a period and a backslash"));
642  }
643  }
644 
645  PWSTR docs_path = nullptr;
646  HRESULT res = SHGetKnownFolderPath(FOLDERID_Documents, KF_FLAG_CREATE, nullptr, &docs_path);
647 
648  if(res != S_OK) {
649  //
650  // Crummy fallback path full of pain and suffering.
651  //
652  ERR_FS << "Could not determine path to user's Documents folder! (" << std::hex << "0x" << res << std::dec << ") "
653  << "User config/data directories may be unavailable for "
654  << "this session. Please report this as a bug.\n";
655  user_data_dir = bfs::path(get_cwd()) / newprefdir;
656  } else {
657  bfs::path games_path = bfs::path(docs_path) / "My Games";
658  create_directory_if_missing(games_path);
659 
660  user_data_dir = games_path / newprefdir;
661  }
662 
663  CoTaskMemFree(docs_path);
664  }
665 
666 #else /*_WIN32*/
667 
668  std::string backupprefdir = ".wesnoth" + get_version_path_suffix();
669 
670 #ifdef WESNOTH_BOOST_OS_IOS
671  char *sdl_pref_path = SDL_GetPrefPath("wesnoth.org", "iWesnoth");
672  if(sdl_pref_path) {
673  backupprefdir = std::string(sdl_pref_path) + backupprefdir;
674  SDL_free(sdl_pref_path);
675  }
676 #endif
677 
678 #ifdef _X11
679  const char* home_str = getenv("HOME");
680 
681  if(newprefdir.empty()) {
682  char const* xdg_data = getenv("XDG_DATA_HOME");
683  if(!xdg_data || xdg_data[0] == '\0') {
684  if(!home_str) {
685  newprefdir = backupprefdir;
686  goto other;
687  }
688 
689  user_data_dir = home_str;
690  user_data_dir /= ".local/share";
691  } else {
692  user_data_dir = xdg_data;
693  }
694 
695  user_data_dir /= "wesnoth";
696  user_data_dir /= get_version_path_suffix();
697  } else {
698  other:
699  bfs::path home = home_str ? home_str : ".";
700 
701  if(newprefdir[0] == '/') {
702  user_data_dir = newprefdir;
703  } else {
704  if(!relative_ok) {
705  // TRANSLATORS: translate the part inside <...> only
706  deprecated_message(_("--userdata-dir=<relative path>"),
708  {1, 17, 0},
709  _("Use absolute paths. Relative paths are deprecated because they are interpreted relative to $HOME"));
710  }
711  user_data_dir = home / newprefdir;
712  }
713  }
714 #else
715  if(newprefdir.empty()) {
716  newprefdir = backupprefdir;
717  }
718 
719  const char* home_str = getenv("HOME");
720  bfs::path home = home_str ? home_str : ".";
721 
722  if(newprefdir[0] == '/') {
723  user_data_dir = newprefdir;
724  } else {
725  if(!relative_ok) {
726  // TRANSLATORS: translate the part inside <...> only
727  deprecated_message(_("--userdata-dir=<relative path>"),
729  {1, 17, 0},
730  _("Use absolute paths. Relative paths are deprecated because they are interpreted relative to $HOME"));
731  }
732  user_data_dir = home / newprefdir;
733  }
734 #endif
735 
736 #endif /*_WIN32*/
738  user_data_dir = normalize_path(user_data_dir.string(), true, true);
739 }
740 
741 static void set_user_config_path(bfs::path newconfig)
742 {
743  user_config_dir = newconfig;
744  if(!create_directory_if_missing_recursive(user_config_dir)) {
745  ERR_FS << "could not open or create user config directory at " << user_config_dir.string() << '\n';
746  }
747 }
748 
749 void set_user_config_dir(const std::string& newconfigdir)
750 {
751  set_user_config_path(newconfigdir);
752 }
753 
755 {
756  if(user_data_dir.empty()) {
757  set_user_data_dir(std::string());
758  }
759 
760  return user_data_dir;
761 }
762 
763 std::string get_user_config_dir()
764 {
765  if(user_config_dir.empty()) {
766 #if defined(_X11) && !defined(PREFERENCES_DIR)
767  char const* xdg_config = getenv("XDG_CONFIG_HOME");
768 
769  if(!xdg_config || xdg_config[0] == '\0') {
770  xdg_config = getenv("HOME");
771  if(!xdg_config) {
772  user_config_dir = get_user_data_path();
773  return user_config_dir.string();
774  }
775 
776  user_config_dir = xdg_config;
777  user_config_dir /= ".config";
778  } else {
779  user_config_dir = xdg_config;
780  }
781 
782  user_config_dir /= "wesnoth";
783  set_user_config_path(user_config_dir);
784 #else
785  user_config_dir = get_user_data_path();
786 #endif
787  }
788 
789  return user_config_dir.string();
790 }
791 
792 std::string get_user_data_dir()
793 {
794  return get_user_data_path().string();
795 }
796 
797 std::string get_cache_dir()
798 {
799  if(cache_dir.empty()) {
800 #if defined(_X11) && !defined(PREFERENCES_DIR)
801  char const* xdg_cache = getenv("XDG_CACHE_HOME");
802 
803  if(!xdg_cache || xdg_cache[0] == '\0') {
804  xdg_cache = getenv("HOME");
805  if(!xdg_cache) {
806  cache_dir = get_dir(get_user_data_path() / "cache");
807  return cache_dir.string();
808  }
809 
810  cache_dir = xdg_cache;
811  cache_dir /= ".cache";
812  } else {
813  cache_dir = xdg_cache;
814  }
815 
816  cache_dir /= "wesnoth";
818 #else
819  cache_dir = get_dir(get_user_data_path() / "cache");
820 #endif
821  }
822 
823  return cache_dir.string();
824 }
825 
826 std::vector<other_version_dir> find_other_version_saves_dirs()
827 {
828 #if !defined(_WIN32) && !defined(_X11) && !defined(__APPLE__)
829  // By all means, this situation doesn't make sense
830  return {};
831 #else
832  const auto& w_ver = game_config::wesnoth_version;
833  const auto& ms_ver = game_config::min_savegame_version;
834 
835  if(w_ver.major_version() != 1 || ms_ver.major_version() != 1) {
836  // Unimplemented, assuming that version 2 won't use WML-based saves
837  return {};
838  }
839 
840  std::vector<other_version_dir> result;
841 
842  // For 1.16, check for saves from all versions up to 1.20.
843  for(auto minor = w_ver.minor_version() + 4; minor >= ms_ver.minor_version(); --minor) {
844  if(minor == w_ver.minor_version())
845  continue;
846 
847  auto version = version_info{};
848  version.set_major_version(w_ver.major_version());
849  version.set_minor_version(minor);
850  auto suffix = get_version_path_suffix(version);
851 
852  bfs::path path;
853 
854  //
855  // NOTE:
856  // This is a bit of a naive approach. We assume on all platforms that
857  // get_user_data_path() will return something resembling the default
858  // configuration and that --user-data-dir wasn't used. We will get
859  // false negatives when any of these conditions don't hold true.
860  //
861 
862 #if defined(_WIN32)
863  path = get_user_data_path().parent_path() / ("Wesnoth" + suffix) / "saves";
864 #elif defined(_X11)
865  path = get_user_data_path().parent_path() / suffix / "saves";
866 #elif defined(__APPLE__)
867  path = get_user_data_path().parent_path() / ("Wesnoth_" + suffix) / "saves";
868 #endif
869 
870  if(bfs::exists(path)) {
871  result.emplace_back(suffix, path.string());
872  }
873  }
874 
875  return result;
876 #endif
877 }
878 
879 std::string get_cwd()
880 {
881  error_code ec;
882  bfs::path cwd = bfs::current_path(ec);
883 
884  if(ec) {
885  ERR_FS << "Failed to get current directory: " << ec.message() << '\n';
886  return "";
887  }
888 
889  return cwd.generic_string();
890 }
891 
892 bool set_cwd(const std::string& dir)
893 {
894  error_code ec;
895  bfs::current_path(bfs::path{dir}, ec);
896 
897  if(ec) {
898  ERR_FS << "Failed to set current directory: " << ec.message() << '\n';
899  return false;
900  } else {
901  LOG_FS << "Process working directory set to " << dir << '\n';
902  }
903 
904  return true;
905 }
906 
907 std::string get_exe_dir()
908 {
909 #ifdef _WIN32
910  wchar_t process_path[MAX_PATH];
911  SetLastError(ERROR_SUCCESS);
912 
913  GetModuleFileNameW(nullptr, process_path, MAX_PATH);
914 
915  if(GetLastError() != ERROR_SUCCESS) {
916  return get_cwd();
917  }
918 
919  bfs::path exe(process_path);
920  return exe.parent_path().string();
921 #else
922  if(bfs::exists("/proc/")) {
923  bfs::path self_exe("/proc/self/exe");
924  error_code ec;
925  bfs::path exe = bfs::read_symlink(self_exe, ec);
926  if(ec) {
927  return std::string();
928  }
929 
930  return exe.parent_path().string();
931  } else {
932  return get_cwd();
933  }
934 #endif
935 }
936 
937 bool make_directory(const std::string& dirname)
938 {
939  error_code ec;
940  bool created = bfs::create_directory(bfs::path(dirname), ec);
941  if(ec) {
942  ERR_FS << "Failed to create directory " << dirname << ": " << ec.message() << '\n';
943  }
944 
945  return created;
946 }
947 
948 bool delete_directory(const std::string& dirname, const bool keep_pbl)
949 {
950  bool ret = true;
951  std::vector<std::string> files;
952  std::vector<std::string> dirs;
953  error_code ec;
954 
955  get_files_in_dir(dirname, &files, &dirs, name_mode::ENTIRE_FILE_PATH, keep_pbl ? filter_mode::SKIP_PBL_FILES : filter_mode::NO_FILTER);
956 
957  if(!files.empty()) {
958  for(const std::string& f : files) {
959  bfs::remove(bfs::path(f), ec);
960  if(ec) {
961  LOG_FS << "remove(" << f << "): " << ec.message() << '\n';
962  ret = false;
963  }
964  }
965  }
966 
967  if(!dirs.empty()) {
968  for(const std::string& d : dirs) {
969  // TODO: this does not preserve any other PBL files
970  // filesystem.cpp does this too, so this might be intentional
971  if(!delete_directory(d))
972  ret = false;
973  }
974  }
975 
976  if(ret) {
977  bfs::remove(bfs::path(dirname), ec);
978  if(ec) {
979  LOG_FS << "remove(" << dirname << "): " << ec.message() << '\n';
980  ret = false;
981  }
982  }
983 
984  return ret;
985 }
986 
987 bool delete_file(const std::string& filename)
988 {
989  error_code ec;
990  bool ret = bfs::remove(bfs::path(filename), ec);
991  if(ec) {
992  ERR_FS << "Could not delete file " << filename << ": " << ec.message() << '\n';
993  }
994 
995  return ret;
996 }
997 
998 std::string read_file(const std::string& fname)
999 {
1000  scoped_istream is = istream_file(fname);
1001  std::stringstream ss;
1002  ss << is->rdbuf();
1003  return ss.str();
1004 }
1005 
1006 filesystem::scoped_istream istream_file(const std::string& fname, bool treat_failure_as_error)
1007 {
1008  LOG_FS << "Streaming " << fname << " for reading.\n";
1009 
1010  if(fname.empty()) {
1011  ERR_FS << "Trying to open file with empty name.\n";
1012  filesystem::scoped_istream s(new bfs::ifstream());
1013  s->clear(std::ios_base::failbit);
1014  return s;
1015  }
1016 
1017  // mingw doesn't support std::basic_ifstream::basic_ifstream(const wchar_t* fname)
1018  // that why boost::filesystem::fstream.hpp doesn't work with mingw.
1019  try {
1020  boost::iostreams::file_descriptor_source fd(bfs::path(fname), std::ios_base::binary);
1021 
1022  // TODO: has this still use ?
1023  if(!fd.is_open() && treat_failure_as_error) {
1024  ERR_FS << "Could not open '" << fname << "' for reading.\n";
1025  } else if(!is_filename_case_correct(fname, fd)) {
1026  ERR_FS << "Not opening '" << fname << "' due to case mismatch.\n";
1027  filesystem::scoped_istream s(new bfs::ifstream());
1028  s->clear(std::ios_base::failbit);
1029  return s;
1030  }
1031 
1032  return std::make_unique<boost::iostreams::stream<boost::iostreams::file_descriptor_source>>(fd, 4096, 0);
1033  } catch(const std::exception&) {
1034  if(treat_failure_as_error) {
1035  ERR_FS << "Could not open '" << fname << "' for reading.\n";
1036  }
1037 
1038  filesystem::scoped_istream s(new bfs::ifstream());
1039  s->clear(std::ios_base::failbit);
1040  return s;
1041  }
1042 }
1043 
1044 filesystem::scoped_ostream ostream_file(const std::string& fname, std::ios_base::openmode mode, bool create_directory)
1045 {
1046  LOG_FS << "streaming " << fname << " for writing.\n";
1047 #if 1
1048  try {
1049  boost::iostreams::file_descriptor_sink fd(bfs::path(fname), mode);
1050  return std::make_unique<boost::iostreams::stream<boost::iostreams::file_descriptor_sink>>(fd, 4096, 0);
1051  } catch(const BOOST_IOSTREAMS_FAILURE& e) {
1052  // If this operation failed because the parent directory didn't exist, create the parent directory and
1053  // retry.
1054  error_code ec_unused;
1055  if(create_directory && bfs::create_directories(bfs::path(fname).parent_path(), ec_unused)) {
1056  return ostream_file(fname, mode, false);
1057  }
1058 
1059  throw filesystem::io_exception(e.what());
1060  }
1061 #else
1062  return new bfs::ofstream(bfs::path(fname), mode);
1063 #endif
1064 }
1065 
1066 // Throws io_exception if an error occurs
1067 void write_file(const std::string& fname, const std::string& data)
1068 {
1069  scoped_ostream os = ostream_file(fname);
1070  os->exceptions(std::ios_base::goodbit);
1071 
1072  const std::size_t block_size = 4096;
1073  char buf[block_size];
1074 
1075  for(std::size_t i = 0; i < data.size(); i += block_size) {
1076  const std::size_t bytes = std::min<std::size_t>(block_size, data.size() - i);
1077  std::copy(data.begin() + i, data.begin() + i + bytes, buf);
1078 
1079  os->write(buf, bytes);
1080  if(os->bad()) {
1081  throw io_exception("Error writing to file: '" + fname + "'");
1082  }
1083  }
1084 }
1085 
1086 void copy_file(const std::string& src, const std::string& dest)
1087 {
1088  write_file(dest, read_file(src));
1089 }
1090 
1091 bool create_directory_if_missing(const std::string& dirname)
1092 {
1093  return create_directory_if_missing(bfs::path(dirname));
1094 }
1095 
1096 bool create_directory_if_missing_recursive(const std::string& dirname)
1097 {
1099 }
1100 
1101 bool is_directory(const std::string& fname)
1102 {
1103  return is_directory_internal(bfs::path(fname));
1104 }
1105 
1106 bool file_exists(const std::string& name)
1107 {
1108  return file_exists(bfs::path(name));
1109 }
1110 
1111 std::time_t file_modified_time(const std::string& fname)
1112 {
1113  error_code ec;
1114  std::time_t mtime = bfs::last_write_time(bfs::path(fname), ec);
1115  if(ec) {
1116  LOG_FS << "Failed to read modification time of " << fname << ": " << ec.message() << '\n';
1117  }
1118 
1119  return mtime;
1120 }
1121 
1122 bool is_gzip_file(const std::string& filename)
1123 {
1124  return bfs::path(filename).extension() == ".gz";
1125 }
1126 
1127 bool is_bzip2_file(const std::string& filename)
1128 {
1129  return bfs::path(filename).extension() == ".bz2";
1130 }
1131 
1132 int file_size(const std::string& fname)
1133 {
1134  error_code ec;
1135  uintmax_t size = bfs::file_size(bfs::path(fname), ec);
1136  if(ec) {
1137  LOG_FS << "Failed to read filesize of " << fname << ": " << ec.message() << '\n';
1138  return -1;
1139  } else if(size > INT_MAX) {
1140  return INT_MAX;
1141  } else {
1142  return size;
1143  }
1144 }
1145 
1146 int dir_size(const std::string& pname)
1147 {
1148  bfs::path p(pname);
1149  uintmax_t size_sum = 0;
1150  error_code ec;
1151  for(bfs::recursive_directory_iterator i(p), end; i != end && !ec; ++i) {
1152  if(bfs::is_regular_file(i->path())) {
1153  size_sum += bfs::file_size(i->path(), ec);
1154  }
1155  }
1156 
1157  if(ec) {
1158  LOG_FS << "Failed to read directorysize of " << pname << ": " << ec.message() << '\n';
1159  return -1;
1160  } else if(size_sum > INT_MAX) {
1161  return INT_MAX;
1162  } else {
1163  return size_sum;
1164  }
1165 }
1166 
1167 std::string base_name(const std::string& file, const bool remove_extension)
1168 {
1169  if(!remove_extension) {
1170  return bfs::path(file).filename().string();
1171  } else {
1172  return bfs::path(file).stem().string();
1173  }
1174 }
1175 
1176 std::string directory_name(const std::string& file)
1177 {
1178  return bfs::path(file).parent_path().string();
1179 }
1180 
1181 std::string nearest_extant_parent(const std::string& file)
1182 {
1183  if(file.empty()) {
1184  return "";
1185  }
1186 
1187  bfs::path p{file};
1188  error_code ec;
1189 
1190  do {
1191  p = p.parent_path();
1192  bfs::path q = canonical(p, ec);
1193  if(!ec) {
1194  p = q;
1195  }
1196  } while(ec && !is_root(p.string()));
1197 
1198  return ec ? "" : p.string();
1199 }
1200 
1201 bool is_path_sep(char c)
1202 {
1203  static const bfs::path sep = bfs::path("/").make_preferred();
1204  const std::string s = std::string(1, c);
1205  return sep == bfs::path(s).make_preferred();
1206 }
1207 
1209 {
1210  return bfs::path::preferred_separator;
1211 }
1212 
1213 bool is_root(const std::string& path)
1214 {
1215 #ifndef _WIN32
1216  error_code ec;
1217  const bfs::path& p = bfs::canonical(path, ec);
1218  return ec ? false : !p.has_parent_path();
1219 #else
1220  //
1221  // Boost.Filesystem is completely unreliable when it comes to detecting
1222  // whether a path refers to a drive's root directory on Windows, so we are
1223  // forced to take an alternative approach here. Instead of hand-parsing
1224  // strings we'll just call a graphical shell service.
1225  //
1226  // There are several poorly-documented ways to refer to a drive in Windows by
1227  // escaping the filesystem namespace using \\.\, \\?\, and \??\. We're just
1228  // going to ignore those here, which may yield unexpected results in places
1229  // such as the file dialog. This function really shouldn't be used for
1230  // security validation anyway, and there are virtually infinite ways to name
1231  // a drive's root using the NT object namespace so it's pretty pointless to
1232  // try to catch those there.
1233  //
1234  // (And no, shlwapi.dll's PathIsRoot() doesn't recognize \\.\C:\, \\?\C:\, or
1235  // \??\C:\ as roots either.)
1236  //
1237  // More generally, do NOT use this code in security-sensitive applications.
1238  //
1239  // See also: <https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html>
1240  //
1241  const std::wstring& wpath = bfs::path{path}.make_preferred().wstring();
1242  return PathIsRootW(wpath.c_str()) == TRUE;
1243 #endif
1244 }
1245 
1246 std::string root_name(const std::string& path)
1247 {
1248  return bfs::path{path}.root_name().string();
1249 }
1250 
1251 bool is_relative(const std::string& path)
1252 {
1253  return bfs::path{path}.is_relative();
1254 }
1255 
1256 std::string normalize_path(const std::string& fpath, bool normalize_separators, bool resolve_dot_entries)
1257 {
1258  if(fpath.empty()) {
1259  return fpath;
1260  }
1261 
1262  error_code ec;
1263  bfs::path p = resolve_dot_entries ? bfs::canonical(fpath, ec) : bfs::absolute(fpath);
1264 
1265  if(ec) {
1266  return "";
1267  }
1268 
1269  if(normalize_separators) {
1270  return p.make_preferred().string();
1271  } else {
1272  return p.string();
1273  }
1274 }
1275 
1276 /**
1277  * The paths manager is responsible for recording the various paths
1278  * that binary files may be located at.
1279  * It should be passed a config object which holds binary path information.
1280  * This is in the format
1281  *@verbatim
1282  * [binary_path]
1283  * path=<path>
1284  * [/binary_path]
1285  * Binaries will be searched for in [wesnoth-path]/data/<path>/images/
1286  *@endverbatim
1287  */
1288 namespace
1289 {
1290 std::set<std::string> binary_paths;
1291 
1292 typedef std::map<std::string, std::vector<std::string>> paths_map;
1293 paths_map binary_paths_cache;
1294 
1295 } // namespace
1296 
1297 static void init_binary_paths()
1298 {
1299  if(binary_paths.empty()) {
1300  binary_paths.insert("");
1301  }
1302 }
1303 
1304 binary_paths_manager::binary_paths_manager()
1305  : paths_()
1306 {
1307 }
1308 
1310  : paths_()
1311 {
1312  set_paths(cfg);
1313 }
1314 
1316 {
1317  cleanup();
1318 }
1319 
1321 {
1322  cleanup();
1324 
1325  for(const config& bp : cfg.child_range("binary_path")) {
1326  std::string path = bp["path"].str();
1327  if(path.find("..") != std::string::npos) {
1328  ERR_FS << "Invalid binary path '" << path << "'\n";
1329  continue;
1330  }
1331 
1332  if(!path.empty() && path.back() != '/')
1333  path += "/";
1334  if(binary_paths.count(path) == 0) {
1335  binary_paths.insert(path);
1336  paths_.push_back(path);
1337  }
1338  }
1339 }
1340 
1342 {
1343  binary_paths_cache.clear();
1344 
1345  for(const std::string& p : paths_) {
1346  binary_paths.erase(p);
1347  }
1348 }
1349 
1351 {
1352  binary_paths_cache.clear();
1353 }
1354 
1355 static bool is_legal_file(const std::string& filename_str)
1356 {
1357  DBG_FS << "Looking for '" << filename_str << "'.\n";
1358 
1359  if(filename_str.empty()) {
1360  LOG_FS << " invalid filename\n";
1361  return false;
1362  }
1363 
1364  if(filename_str.find("..") != std::string::npos) {
1365  ERR_FS << "Illegal path '" << filename_str << "' (\"..\" not allowed).\n";
1366  return false;
1367  }
1368 
1369  if(filename_str.find('\\') != std::string::npos) {
1370  ERR_FS << "Illegal path '" << filename_str
1371  << R"end(' ("\" not allowed, for compatibility with GNU/Linux and macOS).)end" << std::endl;
1372  return false;
1373  }
1374 
1375  bfs::path filepath(filename_str);
1376 
1377  if(default_blacklist.match_file(filepath.filename().string())) {
1378  ERR_FS << "Illegal path '" << filename_str << "' (blacklisted filename)." << std::endl;
1379  return false;
1380  }
1381 
1382  if(std::any_of(filepath.begin(), filepath.end(),
1383  [](const bfs::path& dirname) { return default_blacklist.match_dir(dirname.string()); })) {
1384  ERR_FS << "Illegal path '" << filename_str << "' (blacklisted directory name)." << std::endl;
1385  return false;
1386  }
1387 
1388  return true;
1389 }
1390 
1391 /**
1392  * Returns a vector with all possible paths to a given type of binary,
1393  * e.g. 'images', 'sounds', etc,
1394  */
1395 const std::vector<std::string>& get_binary_paths(const std::string& type)
1396 {
1397  const paths_map::const_iterator itor = binary_paths_cache.find(type);
1398  if(itor != binary_paths_cache.end()) {
1399  return itor->second;
1400  }
1401 
1402  if(type.find("..") != std::string::npos) {
1403  // Not an assertion, as language.cpp is passing user data as type.
1404  ERR_FS << "Invalid WML type '" << type << "' for binary paths\n";
1405  static std::vector<std::string> dummy;
1406  return dummy;
1407  }
1408 
1409  std::vector<std::string>& res = binary_paths_cache[type];
1410 
1412 
1413  for(const std::string& path : binary_paths) {
1414  res.push_back(get_user_data_dir() + "/" + path + type + "/");
1415 
1416  if(!game_config::path.empty()) {
1417  res.push_back(game_config::path + "/" + path + type + "/");
1418  }
1419  }
1420 
1421  // not found in "/type" directory, try main directory
1422  res.push_back(get_user_data_dir() + "/");
1423 
1424  if(!game_config::path.empty()) {
1425  res.push_back(game_config::path + "/");
1426  }
1427 
1428  return res;
1429 }
1430 
1431 std::string get_binary_file_location(const std::string& type, const std::string& filename)
1432 {
1433  // We define ".." as "remove everything before" this is needed because
1434  // on the one hand allowing ".." would be a security risk but
1435  // especially for terrains the c++ engine puts a hardcoded "terrain/" before filename
1436  // and there would be no way to "escape" from "terrain/" otherwise. This is not the
1437  // best solution but we cannot remove it without another solution (subtypes maybe?).
1438 
1439  {
1440  std::string::size_type pos = filename.rfind("../");
1441  if(pos != std::string::npos) {
1442  return get_binary_file_location(type, filename.substr(pos + 3));
1443  }
1444  }
1445 
1446  if(!is_legal_file(filename)) {
1447  return std::string();
1448  }
1449 
1450  std::string result;
1451  for(const std::string& bp : get_binary_paths(type)) {
1452  bfs::path bpath(bp);
1453  bpath /= filename;
1454 
1455  DBG_FS << " checking '" << bp << "'\n";
1456 
1457  if(file_exists(bpath)) {
1458  DBG_FS << " found at '" << bpath.string() << "'\n";
1459  if(result.empty()) {
1460  result = bpath.string();
1461  } else {
1462  WRN_FS << "Conflicting files in binary_path: '" << sanitize_path(result)
1463  << "' and '" << sanitize_path(bpath.string()) << "'\n";
1464  }
1465  }
1466  }
1467 
1468  DBG_FS << " not found\n";
1469  return result;
1470 }
1471 
1472 std::string get_binary_dir_location(const std::string& type, const std::string& filename)
1473 {
1474  if(!is_legal_file(filename)) {
1475  return std::string();
1476  }
1477 
1478  for(const std::string& bp : get_binary_paths(type)) {
1479  bfs::path bpath(bp);
1480  bpath /= filename;
1481  DBG_FS << " checking '" << bp << "'\n";
1482  if(is_directory_internal(bpath)) {
1483  DBG_FS << " found at '" << bpath.string() << "'\n";
1484  return bpath.string();
1485  }
1486  }
1487 
1488  DBG_FS << " not found\n";
1489  return std::string();
1490 }
1491 
1492 std::string get_wml_location(const std::string& filename, const std::string& current_dir)
1493 {
1494  if(!is_legal_file(filename)) {
1495  return std::string();
1496  }
1497 
1498  assert(game_config::path.empty() == false);
1499 
1500  bfs::path fpath(filename);
1501  bfs::path result;
1502 
1503  if(filename[0] == '~') {
1504  result /= get_user_data_path() / "data" / filename.substr(1);
1505  DBG_FS << " trying '" << result.string() << "'\n";
1506  } else if(*fpath.begin() == ".") {
1507  if(!current_dir.empty()) {
1508  result /= bfs::path(current_dir);
1509  } else {
1510  result /= bfs::path(game_config::path) / "data";
1511  }
1512 
1513  result /= filename;
1514  } else if(!game_config::path.empty()) {
1515  result /= bfs::path(game_config::path) / "data" / filename;
1516  }
1517 
1518  if(result.empty() || !file_exists(result)) {
1519  DBG_FS << " not found\n";
1520  result.clear();
1521  } else {
1522  DBG_FS << " found: '" << result.string() << "'\n";
1523  }
1524 
1525  return result.string();
1526 }
1527 
1528 static bfs::path subtract_path(const bfs::path& full, const bfs::path& prefix)
1529 {
1530  bfs::path::iterator fi = full.begin(), fe = full.end(), pi = prefix.begin(), pe = prefix.end();
1531  while(fi != fe && pi != pe && *fi == *pi) {
1532  ++fi;
1533  ++pi;
1534  }
1535 
1536  bfs::path rest;
1537  if(pi == pe) {
1538  while(fi != fe) {
1539  rest /= *fi;
1540  ++fi;
1541  }
1542  }
1543 
1544  return rest;
1545 }
1546 
1547 std::string get_short_wml_path(const std::string& filename)
1548 {
1549  bfs::path full_path(filename);
1550 
1551  bfs::path partial = subtract_path(full_path, get_user_data_path() / "data");
1552  if(!partial.empty()) {
1553  return "~" + partial.generic_string();
1554  }
1555 
1556  partial = subtract_path(full_path, bfs::path(game_config::path) / "data");
1557  if(!partial.empty()) {
1558  return partial.generic_string();
1559  }
1560 
1561  return filename;
1562 }
1563 
1564 std::string get_independent_binary_file_path(const std::string& type, const std::string& filename)
1565 {
1566  bfs::path full_path(get_binary_file_location(type, filename));
1567 
1568  if(full_path.empty()) {
1569  return full_path.generic_string();
1570  }
1571 
1573  if(!partial.empty()) {
1574  return partial.generic_string();
1575  }
1576 
1577  partial = subtract_path(full_path, game_config::path);
1578  if(!partial.empty()) {
1579  return partial.generic_string();
1580  }
1581 
1582  return full_path.generic_string();
1583 }
1584 
1585 std::string get_program_invocation(const std::string& program_name)
1586 {
1587  const std::string real_program_name(program_name
1588 #ifdef DEBUG
1589  + "-debug"
1590 #endif
1591 #ifdef _WIN32
1592  + ".exe"
1593 #endif
1594  );
1595 
1596  return (bfs::path(game_config::wesnoth_program_dir) / real_program_name).string();
1597 }
1598 
1599 std::string sanitize_path(const std::string& path)
1600 {
1601 #ifdef _WIN32
1602  const char* user_name = getenv("USERNAME");
1603 #else
1604  const char* user_name = getenv("USER");
1605 #endif
1606 
1607  std::string canonicalized = filesystem::normalize_path(path, true, false);
1608  if(user_name != nullptr) {
1609  boost::replace_all(canonicalized, user_name, "USER");
1610  }
1611 
1612  return canonicalized;
1613 }
1614 
1615 // Return path to localized counterpart of the given file, if any, or empty string.
1616 // Localized counterpart may also be requested to have a suffix to base name.
1617 std::string get_localized_path(const std::string& file, const std::string& suff)
1618 {
1619  std::string dir = filesystem::directory_name(file);
1620  std::string base = filesystem::base_name(file);
1621 
1622  const std::size_t pos_ext = base.rfind(".");
1623 
1624  std::string loc_base;
1625  if(pos_ext != std::string::npos) {
1626  loc_base = base.substr(0, pos_ext) + suff + base.substr(pos_ext);
1627  } else {
1628  loc_base = base + suff;
1629  }
1630 
1631  // TRANSLATORS: This is the language code which will be used
1632  // to store and fetch localized non-textual resources, such as images,
1633  // when they exist. Normally it is just the code of the PO file itself,
1634  // e.g. "de" of de.po for German. But it can also be a comma-separated
1635  // list of language codes by priority, when the localized resource
1636  // found for first of those languages will be used. This is useful when
1637  // two languages share sufficient commonality, that they can use each
1638  // other's resources rather than duplicating them. For example,
1639  // Swedish (sv) and Danish (da) are such, so Swedish translator could
1640  // translate this message as "sv,da", while Danish as "da,sv".
1641  std::vector<std::string> langs = utils::split(_("language code for localized resources^en_US"));
1642 
1643  // In case even the original image is split into base and overlay,
1644  // add en_US with lowest priority, since the message above will
1645  // not have it when translated.
1646  langs.push_back("en_US");
1647  for(const std::string& lang : langs) {
1648  std::string loc_file = dir + "/" + "l10n" + "/" + lang + "/" + loc_base;
1649  if(filesystem::file_exists(loc_file)) {
1650  return loc_file;
1651  }
1652  }
1653 
1654  return "";
1655 }
1656 
1657 } // namespace filesystem
std::string get_binary_dir_location(const std::string &type, const std::string &filename)
Returns a complete path to the actual directory of a given type or an empty string if the directory i...
void remove()
Removes a tip.
Definition: tooltip.cpp:175
bool delete_directory(const std::string &dirname, const bool keep_pbl)
Definition: filesystem.cpp:948
static void push_if_exists(std::vector< std::string > *vec, const bfs::path &file, bool full)
Definition: filesystem.cpp:236
std::string get_program_invocation(const std::string &program_name)
Returns the appropriate invocation for a Wesnoth-related binary, assuming that it is located in the s...
std::string get_next_filename(const std::string &name, const std::string &extension)
Get the next free filename using "name + number (3 digits) + extension" maximum 1000 files then start...
Definition: filesystem.cpp:490
int dummy
Definition: lstrlib.cpp:1347
bool check_migration
Definition: game_config.cpp:47
bool delete_file(const std::string &filename)
Definition: filesystem.cpp:987
bool looks_like_pbl(const std::string &file)
static l_noret error(LoadState *S, const char *why)
Definition: lundump.cpp:40
void set_user_data_dir(std::string newprefdir)
Definition: filesystem.cpp:611
#define ERR_FS
Definition: filesystem.cpp:72
config_array_view child_range(config_key_type key) const
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:263
bool ends_with(const std::string &str, const std::string &suffix)
void finish_log_file_setup()
Relocates the stdout+stderr log file to the user data directory.
void set_major_version(unsigned int)
Sets the major version number.
filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error)
#define DBG_FS
Definition: filesystem.cpp:69
static void init_binary_paths()
std::string get_binary_file_location(const std::string &type, const std::string &filename)
Returns a complete path to the actual file of a given type or an empty string if the file isn&#39;t prese...
std::string normalize_path(const std::string &fpath, bool normalize_separators, bool resolve_dot_entries)
Returns the absolute path of a file.
#define d
static std::string _(const char *str)
Definition: gettext.hpp:93
Definitions for the interface to Wesnoth Markup Language (WML).
filesystem::scoped_ostream ostream_file(const std::string &fname, std::ios_base::openmode mode, bool create_directory)
std::string get_independent_binary_file_path(const std::string &type, const std::string &filename)
Returns an asset path to filename for binary path-independent use in saved games. ...
void clear_binary_paths_cache()
std::string get_cwd()
Definition: filesystem.cpp:879
bool exists(const image::locator &i_locator)
Returns true if the given image actually exists, without loading it.
Definition: picture.cpp:1011
void write(std::ostream &out, const configr_of &cfg, unsigned int level)
Definition: parser.cpp:764
#define LOG_FS
Definition: filesystem.cpp:70
static bool is_legal_file(const std::string &filename_str)
bool match_file(const std::string &name) const
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:87
#define WRN_FS
Definition: filesystem.cpp:71
std::string deprecated_message(const std::string &elem_name, DEP_LEVEL level, const version_info &version, const std::string &detail)
Definition: deprecation.cpp:30
bool create_directory_if_missing_recursive(const std::string &dirname)
Creates a recursive directory tree if it does not exist already.
bool create_directory_if_missing(const std::string &dirname)
Creates a directory if it does not exist already.
unsigned int major_version() const
Retrieves the major version number (x1 in "x1.x2.x3").
static bfs::path user_config_dir
Definition: filesystem.cpp:511
void read(config &cfg, std::istream &in, abstract_validator *validator)
Definition: parser.cpp:627
static bool is_directory_internal(const bfs::path &fpath)
Definition: filesystem.cpp:252
std::string get_user_data_dir()
Definition: filesystem.cpp:792
void write_file(const std::string &fname, const std::string &data)
Throws io_exception if an error occurs.
void get_files_in_dir(const std::string &dir, std::vector< std::string > *files, std::vector< std::string > *dirs, name_mode mode, filter_mode filter, reorder_mode reorder, file_tree_checksum *checksum)
Get a list of all files and/or directories in a given directory.
Definition: filesystem.cpp:349
std::string root_name(const std::string &path)
Returns the name of the root device if included in the given path.
std::string nearest_extant_parent(const std::string &file)
Finds the nearest parent in existence for a file or directory.
unsigned int minor_version() const
Retrieves the minor version number (x2 in "x1.x2.x3").
bool is_directory(const std::string &fname)
Returns true if the given file is a directory.
std::unique_ptr< std::istream > scoped_istream
Definition: filesystem.hpp:39
std::string path
Definition: game_config.cpp:39
std::string get_short_wml_path(const std::string &filename)
Returns a short path to filename, skipping the (user) data directory.
const std::string & get_version_path_suffix()
Definition: filesystem.cpp:520
static bfs::path user_data_dir
Definition: filesystem.cpp:511
std::string sanitize_path(const std::string &path)
Sanitizes a path to remove references to the user&#39;s name.
std::string get_dir(const std::string &dir)
Definition: filesystem.cpp:485
std::string read_file(const std::string &fname)
Basic disk I/O - read file.
Definition: filesystem.cpp:998
static int writer(lua_State *L, const void *b, size_t size, void *ud)
Definition: lstrlib.cpp:221
lu_byte right
Definition: lparser.cpp:1227
bool is_path_sep(char c)
Returns whether c is a path separator.
std::unique_ptr< std::ostream > scoped_ostream
Definition: filesystem.hpp:40
bool is_gzip_file(const std::string &filename)
Returns true if the file ends with &#39;.gz&#39;.
std::string get_cache_dir()
Definition: filesystem.cpp:797
const std::vector< std::string > & get_binary_paths(const std::string &type)
Returns a vector with all possible paths to a given type of binary, e.g.
std::string get_exe_dir()
Definition: filesystem.cpp:907
Log file control routines for Windows.
std::size_t i
Definition: function.cpp:967
void set_user_config_dir(const std::string &newconfigdir)
Definition: filesystem.cpp:749
bool is_relative(const std::string &path)
Returns whether the path seems to be relative.
int dir_size(const std::string &pname)
Returns the sum of the sizes of the files contained in a directory.
std::string get_wml_location(const std::string &filename, const std::string &current_dir)
Returns a complete path to the actual WML file or directory or an empty string if the file isn&#39;t pres...
std::vector< std::string > paths_
Definition: filesystem.hpp:414
mock_party p
void copy_file(const std::string &src, const std::string &dest)
Read a file and then writes it back out.
An exception object used when an IO error occurs.
Definition: filesystem.hpp:48
static map_location::DIRECTION s
bool make_directory(const std::string &dirname)
Definition: filesystem.cpp:937
bool is_root(const std::string &path)
Returns whether the path is the root of the file hierarchy.
Declarations for File-IO.
std::string get_localized_path(const std::string &file, const std::string &suff)
Returns the localized version of the given filename, if it exists.
static int sort(lua_State *L)
Definition: ltablib.cpp:397
const version_info wesnoth_version(VERSION)
rng * generator
This generator is automatically synced during synced context.
Definition: random.cpp:61
Represents version numbers.
char path_separator()
Returns the standard path separator for the current platform.
static bfs::path cache_dir
Definition: filesystem.cpp:511
std::string base_name(const std::string &file, const bool remove_extension)
Returns the base filename of a file, with directory name stripped.
int file_size(const std::string &fname)
Returns the size of a file, or -1 if the file doesn&#39;t exist.
bool set_cwd(const std::string &dir)
Definition: filesystem.cpp:892
std::string get_user_config_dir()
Definition: filesystem.cpp:763
void set_paths(const game_config_view &cfg)
bool is_bzip2_file(const std::string &filename)
Returns true if the file ends with &#39;.bz2&#39;.
#define f
std::time_t file_modified_time(const std::string &fname)
Get the modification time of a file.
std::vector< std::string > split(const config_attribute_value &val)
static bool error_except_not_found(const error_code &ec)
Definition: filesystem.cpp:247
static void set_user_config_path(bfs::path newconfig)
Definition: filesystem.cpp:741
static bfs::path subtract_path(const bfs::path &full, const bfs::path &prefix)
Standard logging facilities (interface).
#define e
std::vector< other_version_dir > find_other_version_saves_dirs()
Searches for directories containing saves created by other versions of Wesnoth.
Definition: filesystem.cpp:826
There are still moves and/or attacks possible, but the unit doesn&#39;t fit in the "unmoved" status...
bool match_dir(const std::string &name) const
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:61
mock_char c
static lg::log_domain log_filesystem("filesystem")
const version_info min_savegame_version(MIN_SAVEGAME_VERSION)
static void setup_user_data_dir()
Definition: filesystem.cpp:566
bool file_exists(const std::string &name)
Returns true if a file or directory with such name already exists.
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
std::string directory_name(const std::string &file)
Returns the directory name of a file, with filename stripped.
std::string wesnoth_program_dir
Definition: game_config.cpp:49
static const bfs::path & get_user_data_path()
Definition: filesystem.cpp:754
static const blacklist_pattern_list default_blacklist
Definition: filesystem.hpp:91