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