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