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