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