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