The Battle for Wesnoth  1.13.11+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
filesystem_boost.cpp
Go to the documentation of this file.
1 /* $Id$ */
2 /*
3  Copyright (C) 2003 - 2012 by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project http://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 /**
17  * @file
18  * File-IO
19  */
20 
21 #include "filesystem.hpp"
23 
24 #include <boost/filesystem.hpp>
25 #include <boost/filesystem/fstream.hpp>
26 #include <boost/system/windows_error.hpp>
27 #include <boost/iostreams/device/file_descriptor.hpp>
28 #include <boost/iostreams/stream.hpp>
29 #include <set>
30 
31 using boost::uintmax_t;
32 
33 #ifdef _WIN32
34 #include "log_windows.hpp"
35 
36 #include <boost/locale.hpp>
37 
38 #include <windows.h>
39 #include <shlobj.h>
40 #include <shlwapi.h>
41 
42 // Work around TDM-GCC not #defining this according to @newfrenchy83.
43 #define VOLUME_NAME_NONE 0x4
44 
45 #endif /* !_WIN32 */
46 
47 #include "config.hpp"
48 #include "game_config.hpp"
49 #include "log.hpp"
51 #include "version.hpp"
52 
53 static lg::log_domain log_filesystem("filesystem");
54 #define DBG_FS LOG_STREAM(debug, log_filesystem)
55 #define LOG_FS LOG_STREAM(info, log_filesystem)
56 #define WRN_FS LOG_STREAM(warn, log_filesystem)
57 #define ERR_FS LOG_STREAM(err, log_filesystem)
58 
59 namespace bfs = boost::filesystem;
61 using boost::system::error_code;
62 
63 namespace {
64  // These are the filenames that get special processing
65  const std::string maincfg_filename = "_main.cfg";
66  const std::string finalcfg_filename = "_final.cfg";
67  const std::string initialcfg_filename = "_initial.cfg";
68 }
69 namespace {
70  //only used by windows but put outside the ifdef to let it check by ci build.
71  class customcodecvt : public std::codecvt<wchar_t /*intern*/, char /*extern*/, std::mbstate_t>
72  {
73  private:
74  //private static helper things
75  template<typename char_t_to>
76  struct customcodecvt_do_conversion_writer
77  {
78  customcodecvt_do_conversion_writer(char_t_to*& _to_next, char_t_to* _to_end) :
79  to_next(_to_next),
80  to_end(_to_end)
81  {}
82  char_t_to*& to_next;
83  char_t_to* to_end;
84 
85  bool can_push(size_t count) const
86  {
87  return static_cast<size_t>(to_end - to_next) > count;
88  }
89 
90  void push(char_t_to val)
91  {
92  assert(to_next != to_end);
93  *to_next++ = val;
94  }
95  };
96 
97  template<typename char_t_from , typename char_t_to>
98  static void customcodecvt_do_conversion( std::mbstate_t& /*state*/,
99  const char_t_from* from,
100  const char_t_from* from_end,
101  const char_t_from*& from_next,
102  char_t_to* to,
103  char_t_to* to_end,
104  char_t_to*& to_next )
105  {
106  typedef typename ucs4_convert_impl::convert_impl<char_t_from>::type impl_type_from;
107  typedef typename ucs4_convert_impl::convert_impl<char_t_to>::type impl_type_to;
108 
109  from_next = from;
110  to_next = to;
111  customcodecvt_do_conversion_writer<char_t_to> writer(to_next, to_end);
112  while(from_next != from_end)
113  {
114  impl_type_to::write(writer, impl_type_from::read(from_next, from_end));
115  }
116  }
117 
118  public:
119 
120  //Not used by boost filesystem
121  int do_encoding() const NOEXCEPT { return 0; }
122  //Not used by boost filesystem
123  bool do_always_noconv() const NOEXCEPT { return false; }
124  int do_length( std::mbstate_t& /*state*/,
125  const char* /*from*/,
126  const char* /*from_end*/,
127  std::size_t /*max*/ ) const
128  {
129  //Not used by boost filesystem
130  throw "Not supported";
131  }
132 
133  std::codecvt_base::result unshift( std::mbstate_t& /*state*/,
134  char* /*to*/,
135  char* /*to_end*/,
136  char*& /*to_next*/) const
137  {
138  //Not used by boost filesystem
139  throw "Not supported";
140  }
141 
142  //there are still some methods which could be implemented but aren't because boost filesystem won't use them.
143  std::codecvt_base::result do_in( std::mbstate_t& state,
144  const char* from,
145  const char* from_end,
146  const char*& from_next,
147  wchar_t* to,
148  wchar_t* to_end,
149  wchar_t*& to_next ) const
150  {
151  try
152  {
153  customcodecvt_do_conversion<char, wchar_t>(state, from, from_end, from_next, to, to_end, to_next);
154  }
155  catch(...)
156  {
157  ERR_FS << "Invalid UTF-8 string'" << std::string(from, from_end) << "' " << std::endl;
159  }
160  return std::codecvt_base::ok;
161  }
162 
163  std::codecvt_base::result do_out( std::mbstate_t& state,
164  const wchar_t* from,
165  const wchar_t* from_end,
166  const wchar_t*& from_next,
167  char* to,
168  char* to_end,
169  char*& to_next ) const
170  {
171  try
172  {
173  customcodecvt_do_conversion<wchar_t, char>(state, from, from_end, from_next, to, to_end, to_next);
174  }
175  catch(...)
176  {
177  ERR_FS << "Invalid UTF-16 string" << std::endl;
179  }
180  return std::codecvt_base::ok;
181  }
182  };
183 
184 #ifdef _WIN32
185  class static_runner {
186  public:
187  static_runner() {
188  // Boost uses the current locale to generate a UTF-8 one
189  std::locale utf8_loc = boost::locale::generator().generate("");
190  // use a custom locale because we want to use out log.hpp functions in case of an invalid string.
191  utf8_loc = std::locale(utf8_loc, new customcodecvt());
192  boost::filesystem::path::imbue(utf8_loc);
193  }
194  };
195 
196  static static_runner static_bfs_path_imbuer;
197 
198  typedef DWORD(WINAPI *GetFinalPathNameByHandleWPtr)(HANDLE, LPWSTR, DWORD, DWORD);
199  static GetFinalPathNameByHandleWPtr dyn_GetFinalPathNameByHandle;
200 
201  bool is_filename_case_correct(const std::string& fname, const boost::iostreams::file_descriptor_source& fd)
202  {
203  if(dyn_GetFinalPathNameByHandle == nullptr) {
204  // Windows XP. Just assume that the case is correct.
205  return true;
206  }
207 
208  wchar_t real_path[MAX_PATH];
209  dyn_GetFinalPathNameByHandle(fd.handle(), real_path, MAX_PATH - 1, VOLUME_NAME_NONE);
210  std::string real_name = filesystem::base_name(unicode_cast<std::string>(std::wstring(real_path)));
211  return real_name == filesystem::base_name(fname);
212  }
213 
214 #else
215  bool is_filename_case_correct(const std::string& /*fname*/, const boost::iostreams::file_descriptor_source& /*fd*/)
216  {
217  return true;
218  }
219 #endif
220 }
221 
222 
223 namespace filesystem {
224 
225 void init()
226 {
227 #ifdef _WIN32
228  HMODULE kernel32 = GetModuleHandle(TEXT("Kernel32.dll"));
229  // Note that this returns a null pointer on Windows XP!
230  dyn_GetFinalPathNameByHandle = reinterpret_cast<GetFinalPathNameByHandleWPtr>(
231  GetProcAddress(kernel32, "GetFinalPathNameByHandleW"));
232 #endif
233 }
234 
235 static void push_if_exists(std::vector<std::string> *vec, const path &file, bool full) {
236  if (vec != nullptr) {
237  if (full)
238  vec->push_back(file.generic_string());
239  else
240  vec->push_back(file.filename().generic_string());
241  }
242 }
243 
244 static inline bool error_except_not_found(const error_code &ec)
245 {
246  return (ec
247  && ec.value() != boost::system::errc::no_such_file_or_directory
248 #ifdef _WIN32
249  && ec.value() != boost::system::windows_error::path_not_found
250 #endif /*_WIN32*/
251  );
252 }
253 
254 static bool is_directory_internal(const path &fpath)
255 {
256  error_code ec;
257  bool is_dir = bfs::is_directory(fpath, ec);
258  if (error_except_not_found(ec)) {
259  LOG_FS << "Failed to check if " << fpath.string() << " is a directory: " << ec.message() << '\n';
260  }
261  return is_dir;
262 }
263 
264 static bool file_exists(const path &fpath)
265 {
266  error_code ec;
267  bool exists = bfs::exists(fpath, ec);
268  if (error_except_not_found(ec)) {
269  ERR_FS << "Failed to check existence of file " << fpath.string() << ": " << ec.message() << '\n';
270  }
271  return exists;
272 }
273 static path get_dir(const path &dirpath)
274 {
275  bool is_dir = is_directory_internal(dirpath);
276  if (!is_dir) {
277  error_code ec;
278  bfs::create_directory(dirpath, ec);
279  if (ec) {
280  ERR_FS << "Failed to create directory " << dirpath.string() << ": " << ec.message() << '\n';
281  }
282  // This is probably redundant
283  is_dir = is_directory_internal(dirpath);
284  }
285  if (!is_dir) {
286  ERR_FS << "Could not open or create directory " << dirpath.string() << '\n';
287  return std::string();
288  }
289 
290  return dirpath;
291 }
292 static bool create_directory_if_missing(const path &dirpath)
293 {
294  error_code ec;
295  bfs::file_status fs = bfs::status(dirpath, ec);
296  if (error_except_not_found(ec)) {
297  ERR_FS << "Failed to retrieve file status for " << dirpath.string() << ": " << ec.message() << '\n';
298  return false;
299  } else if (bfs::is_directory(fs)) {
300  DBG_FS << "directory " << dirpath.string() << " exists, not creating\n";
301  return true;
302  } else if (bfs::exists(fs)) {
303  ERR_FS << "cannot create directory " << dirpath.string() << "; file exists\n";
304  return false;
305  }
306 
307  bool created = bfs::create_directory(dirpath, ec);
308  if (ec) {
309  ERR_FS << "Failed to create directory " << dirpath.string() << ": " << ec.message() << '\n';
310  }
311  return created;
312 }
313 static bool create_directory_if_missing_recursive(const path& dirpath)
314 {
315  DBG_FS << "creating recursive directory: " << dirpath.string() << '\n';
316 
317  if (dirpath.empty())
318  return false;
319  error_code ec;
320  bfs::file_status fs = bfs::status(dirpath);
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  return true;
326  } else if (bfs::exists(fs)) {
327  return false;
328  }
329 
330  if (!dirpath.has_parent_path() || create_directory_if_missing_recursive(dirpath.parent_path())) {
331  return create_directory_if_missing(dirpath);
332  } else {
333  ERR_FS << "Could not create parents to " << dirpath.string() << '\n';
334  return false;
335  }
336 }
337 
339  std::vector<std::string>* files,
340  std::vector<std::string>* dirs,
341  file_name_option mode,
342  file_filter_option filter,
343  file_reorder_option reorder,
344  file_tree_checksum* checksum) {
345  if(path(dir).is_relative() && !game_config::path.empty()) {
346  path absolute_dir(game_config::path);
347  absolute_dir /= dir;
348  if(is_directory_internal(absolute_dir)) {
349  get_files_in_dir(absolute_dir.string(), files, dirs, mode, filter, reorder, checksum);
350  return;
351  }
352  }
353 
354  const path dirpath(dir);
355 
356  if (reorder == DO_REORDER) {
357  LOG_FS << "searching for _main.cfg in directory " << dir << '\n';
358  const path maincfg = dirpath / maincfg_filename;
359 
360  if (file_exists(maincfg)) {
361  LOG_FS << "_main.cfg found : " << maincfg << '\n';
362  push_if_exists(files, maincfg, mode == ENTIRE_FILE_PATH);
363  return;
364  }
365  }
366 
367  error_code ec;
368  bfs::directory_iterator di(dirpath, ec);
369  bfs::directory_iterator end;
370  if (ec) {
371  // Probably not a directory, let the caller deal with it.
372  return;
373  }
374  for(; di != end; ++di) {
375  bfs::file_status st = di->status(ec);
376  if (ec) {
377  LOG_FS << "Failed to get file status of " << di->path().string() << ": " << ec.message() << '\n';
378  continue;
379  }
380  if (st.type() == bfs::regular_file) {
381  {
382  std::string basename = di->path().filename().string();
383  if (filter == SKIP_PBL_FILES && looks_like_pbl(basename))
384  continue;
385  if(!basename.empty() && basename[0] == '.' )
386  continue;
387  }
388  push_if_exists(files, di->path(), mode == ENTIRE_FILE_PATH);
389 
390  if (checksum != nullptr) {
391  std::time_t mtime = bfs::last_write_time(di->path(), ec);
392  if (ec) {
393  LOG_FS << "Failed to read modification time of " << di->path().string() << ": " << ec.message() << '\n';
394  } else if (mtime > checksum->modified) {
395  checksum->modified = mtime;
396  }
397 
398  uintmax_t size = bfs::file_size(di->path(), ec);
399  if (ec) {
400  LOG_FS << "Failed to read filesize of " << di->path().string() << ": " << ec.message() << '\n';
401  } else {
402  checksum->sum_size += size;
403  }
404  checksum->nfiles++;
405  }
406  } else if (st.type() == bfs::directory_file) {
407  std::string basename = di->path().filename().string();
408 
409  if(!basename.empty() && basename[0] == '.' )
410  continue;
411  if (filter == SKIP_MEDIA_DIR
412  && (basename == "images" || basename == "sounds"))
413  continue;
414 
415  const path inner_main(di->path() / maincfg_filename);
416  bfs::file_status main_st = bfs::status(inner_main, ec);
417  if (error_except_not_found(ec)) {
418  LOG_FS << "Failed to get file status of " << inner_main.string() << ": " << ec.message() << '\n';
419  } else if (reorder == DO_REORDER && main_st.type() == bfs::regular_file) {
420  LOG_FS << "_main.cfg found : " << (mode == ENTIRE_FILE_PATH ? inner_main.string() : inner_main.filename().string()) << '\n';
421  push_if_exists(files, inner_main, mode == ENTIRE_FILE_PATH);
422  } else {
423  push_if_exists(dirs, di->path(), mode == ENTIRE_FILE_PATH);
424  }
425  }
426  }
427 
428  if (files != nullptr)
429  std::sort(files->begin(),files->end());
430 
431  if (dirs != nullptr)
432  std::sort(dirs->begin(),dirs->end());
433 
434  if (files != nullptr && reorder == DO_REORDER) {
435  // move finalcfg_filename, if present, to the end of the vector
436  for (unsigned int i = 0; i < files->size(); i++) {
437  if (ends_with((*files)[i], "/" + finalcfg_filename)) {
438  files->push_back((*files)[i]);
439  files->erase(files->begin()+i);
440  break;
441  }
442  }
443  // move initialcfg_filename, if present, to the beginning of the vector
444  int foundit = -1;
445  for (unsigned int i = 0; i < files->size(); i++)
446  if (ends_with((*files)[i], "/" + initialcfg_filename)) {
447  foundit = i;
448  break;
449  }
450  if (foundit > 0) {
451  std::string initialcfg = (*files)[foundit];
452  for (unsigned int i = foundit; i > 0; i--)
453  (*files)[i] = (*files)[i-1];
454  (*files)[0] = initialcfg;
455  }
456  }
457 }
458 
460 {
461  return get_dir(path(dir)).string();
462 }
463 
465 {
466  std::string next_filename;
467  int counter = 0;
468 
469  do {
470  std::stringstream filename;
471 
472  filename << name;
473  filename.width(3);
474  filename.fill('0');
475  filename.setf(std::ios_base::right);
476  filename << counter << extension;
477  counter++;
478  next_filename = filename.str();
479  } while(file_exists(next_filename) && counter < 1000);
480  return next_filename;
481 }
482 
484 
486 {
487  static std::string suffix;
488 
489  // We only really need to generate this once since
490  // the version number cannot change during runtime.
491 
492  if(suffix.empty()) {
493  std::ostringstream s;
496  suffix = s.str();
497  }
498 
499  return suffix;
500 }
501 
502 static void setup_user_data_dir()
503 {
504  if (!create_directory_if_missing_recursive(user_data_dir)) {
505  ERR_FS << "could not open or create user data directory at " << user_data_dir.string() << '\n';
506  return;
507  }
508  // TODO: this may not print the error message if the directory exists but we don't have the proper permissions
509 
510  // Create user data and add-on directories
511  create_directory_if_missing(user_data_dir / "editor");
512  create_directory_if_missing(user_data_dir / "editor" / "maps");
513  create_directory_if_missing(user_data_dir / "editor" / "scenarios");
514  create_directory_if_missing(user_data_dir / "data");
515  create_directory_if_missing(user_data_dir / "data" / "add-ons");
516  create_directory_if_missing(user_data_dir / "saves");
517  create_directory_if_missing(user_data_dir / "persist");
518 
519 #ifdef _WIN32
521 #endif
522 }
523 
524 #ifdef _WIN32
525 // As a convenience for portable installs on Windows, relative paths with . or
526 // .. as the first component are considered relative to the current workdir
527 // instead of Documents/My Games.
528 static bool is_path_relative_to_cwd(const std::string& str)
529 {
530  const path p(str);
531 
532  if(p.empty()) {
533  return false;
534  }
535 
536  return *p.begin() == "." || *p.begin() == "..";
537 }
538 #endif
539 
541 {
542 #ifdef PREFERENCES_DIR
543  if (newprefdir.empty()) newprefdir = PREFERENCES_DIR;
544 #endif
545 
546 #ifdef _WIN32
547  if(newprefdir.size() > 2 && newprefdir[1] == ':') {
548  //allow absolute path override
549  user_data_dir = newprefdir;
550  } else if(is_path_relative_to_cwd(newprefdir)) {
551  // Custom directory relative to workdir (for portable installs, etc.)
552  user_data_dir = get_cwd() + "/" + newprefdir;
553  } else {
554  if(newprefdir.empty()) {
555  newprefdir = "Wesnoth" + get_version_path_suffix();
556  }
557 
558  wchar_t docs_path[MAX_PATH];
559 
560  HRESULT res = SHGetFolderPathW(nullptr,
561  CSIDL_PERSONAL | CSIDL_FLAG_CREATE, nullptr,
562  SHGFP_TYPE_CURRENT,
563  docs_path);
564  if(res != S_OK) {
565  //
566  // Crummy fallback path full of pain and suffering.
567  //
568  ERR_FS << "Could not determine path to user's Documents folder! ("
569  << std::hex << "0x" << res << std::dec << ") "
570  << "User config/data directories may be unavailable for "
571  << "this session. Please report this as a bug.\n";
572  user_data_dir = path(get_cwd()) / newprefdir;
573  } else {
574  path games_path = path(docs_path) / "My Games";
575  create_directory_if_missing(games_path);
576 
577  user_data_dir = games_path / newprefdir;
578  }
579  }
580 
581 #else /*_WIN32*/
582 
583  std::string backupprefdir = ".wesnoth" + get_version_path_suffix();
584 
585 #ifdef _X11
586  const char *home_str = getenv("HOME");
587 
588  if (newprefdir.empty()) {
589  char const *xdg_data = getenv("XDG_DATA_HOME");
590  if (!xdg_data || xdg_data[0] == '\0') {
591  if (!home_str) {
592  newprefdir = backupprefdir;
593  goto other;
594  }
595  user_data_dir = home_str;
596  user_data_dir /= ".local/share";
597  } else user_data_dir = xdg_data;
598  user_data_dir /= "wesnoth";
599  user_data_dir /= get_version_path_suffix();
600  } else {
601  other:
602  path home = home_str ? home_str : ".";
603 
604  if (newprefdir[0] == '/')
605  user_data_dir = newprefdir;
606  else
607  user_data_dir = home / newprefdir;
608  }
609 #else
610  if (newprefdir.empty()) newprefdir = backupprefdir;
611 
612  const char* home_str = getenv("HOME");
613  path home = home_str ? home_str : ".";
614 
615  if (newprefdir[0] == '/')
616  user_data_dir = newprefdir;
617  else
618  user_data_dir = home / newprefdir;
619 #endif
620 
621 #endif /*_WIN32*/
623  user_data_dir = normalize_path(user_data_dir.string(), true, true);
624 }
625 
626 static void set_user_config_path(path newconfig)
627 {
628  user_config_dir = newconfig;
629  if (!create_directory_if_missing_recursive(user_config_dir)) {
630  ERR_FS << "could not open or create user config directory at " << user_config_dir.string() << '\n';
631  }
632 }
633 
634 void set_user_config_dir(const std::string& newconfigdir)
635 {
636  set_user_config_path(newconfigdir);
637 }
638 
639 static const path &get_user_data_path()
640 {
641  if (user_data_dir.empty())
642  {
644  }
645  return user_data_dir;
646 }
648 {
649  if (user_config_dir.empty())
650  {
651 #if defined(_X11) && !defined(PREFERENCES_DIR)
652  char const *xdg_config = getenv("XDG_CONFIG_HOME");
653  if (!xdg_config || xdg_config[0] == '\0') {
654  xdg_config = getenv("HOME");
655  if (!xdg_config) {
656  user_config_dir = get_user_data_path();
657  return user_config_dir.string();
658  }
659  user_config_dir = xdg_config;
660  user_config_dir /= ".config";
661  } else user_config_dir = xdg_config;
662  user_config_dir /= "wesnoth";
663  set_user_config_path(user_config_dir);
664 #else
665  user_config_dir = get_user_data_path();
666 #endif
667  }
668  return user_config_dir.string();
669 }
671 {
672  return get_user_data_path().string();
673 }
675 {
676  if (cache_dir.empty())
677  {
678 #if defined(_X11) && !defined(PREFERENCES_DIR)
679  char const *xdg_cache = getenv("XDG_CACHE_HOME");
680  if (!xdg_cache || xdg_cache[0] == '\0') {
681  xdg_cache = getenv("HOME");
682  if (!xdg_cache) {
683  cache_dir = get_dir(get_user_data_path() / "cache");
684  return cache_dir.string();
685  }
686  cache_dir = xdg_cache;
687  cache_dir /= ".cache";
688  } else cache_dir = xdg_cache;
689  cache_dir /= "wesnoth";
691 #else
692  cache_dir = get_dir(get_user_data_path() / "cache");
693 #endif
694  }
695  return cache_dir.string();
696 }
697 
699 {
700  error_code ec;
701  path cwd = bfs::current_path(ec);
702  if (ec) {
703  ERR_FS << "Failed to get current directory: " << ec.message() << '\n';
704  return "";
705  }
706  return cwd.generic_string();
707 }
709 {
710 #ifdef _WIN32
711  wchar_t process_path[MAX_PATH];
712  SetLastError(ERROR_SUCCESS);
713  GetModuleFileNameW(nullptr, process_path, MAX_PATH);
714  if (GetLastError() != ERROR_SUCCESS) {
715  return get_cwd();
716  }
717 
718  path exe(process_path);
719  return exe.parent_path().string();
720 #else
721  if (bfs::exists("/proc/")) {
722  path self_exe("/proc/self/exe");
723  error_code ec;
724  path exe = bfs::read_symlink(self_exe, ec);
725  if (ec) {
726  return std::string();
727  }
728 
729  return exe.parent_path().string();
730  } else {
731  return get_cwd();
732  }
733 #endif
734 }
735 
736 bool make_directory(const std::string& dirname)
737 {
738  error_code ec;
739  bool created = bfs::create_directory(path(dirname), ec);
740  if (ec) {
741  ERR_FS << "Failed to create directory " << dirname << ": " << ec.message() << '\n';
742  }
743  return created;
744 }
745 bool delete_directory(const std::string& dirname, const bool keep_pbl)
746 {
747  bool ret = true;
748  std::vector<std::string> files;
749  std::vector<std::string> dirs;
750  error_code ec;
751 
752  get_files_in_dir(dirname, &files, &dirs, ENTIRE_FILE_PATH, keep_pbl ? SKIP_PBL_FILES : NO_FILTER);
753 
754  if(!files.empty()) {
755  for(std::vector<std::string>::const_iterator i = files.begin(); i != files.end(); ++i) {
756  bfs::remove(path(*i), ec);
757  if (ec) {
758  LOG_FS << "remove(" << (*i) << "): " << ec.message() << '\n';
759  ret = false;
760  }
761  }
762  }
763 
764  if(!dirs.empty()) {
765  for(std::vector<std::string>::const_iterator j = dirs.begin(); j != dirs.end(); ++j) {
766  //TODO: this does not preserve any other PBL files
767  // filesystem.cpp does this too, so this might be intentional
768  if(!delete_directory(*j))
769  ret = false;
770  }
771  }
772 
773  if (ret) {
774  bfs::remove(path(dirname), ec);
775  if (ec) {
776  LOG_FS << "remove(" << dirname << "): " << ec.message() << '\n';
777  ret = false;
778  }
779  }
780  return ret;
781 }
782 
783 bool delete_file(const std::string &filename)
784 {
785  error_code ec;
786  bool ret = bfs::remove(path(filename), ec);
787  if (ec) {
788  ERR_FS << "Could not delete file " << filename << ": " << ec.message() << '\n';
789  }
790  return ret;
791 }
792 
794 {
795  scoped_istream is = istream_file(fname);
796  std::stringstream ss;
797  ss << is->rdbuf();
798  return ss.str();
799 }
800 
801 filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error)
802 {
803  LOG_FS << "Streaming " << fname << " for reading.\n";
804  if (fname.empty()) {
805  ERR_FS << "Trying to open file with empty name.\n";
806  filesystem::scoped_istream s(new bfs::ifstream());
807  s->clear(std::ios_base::failbit);
808  return s;
809  }
810 
811  //mingw doesn't support std::basic_ifstream::basic_ifstream(const wchar_t* fname)
812  //that why boost::filesystem::fstream.hpp doesn't work with mingw.
813  try
814  {
815  boost::iostreams::file_descriptor_source fd(bfs::path(fname), std::ios_base::binary);
816  //TODO: has this still use ?
817  if (!fd.is_open() && treat_failure_as_error) {
818  ERR_FS << "Could not open '" << fname << "' for reading.\n";
819  } else if (!is_filename_case_correct(fname, fd)) {
820  ERR_FS << "Not opening '" << fname << "' due to case mismatch.\n";
821  filesystem::scoped_istream s(new bfs::ifstream());
822  s->clear(std::ios_base::failbit);
823  return s;
824  }
825  return filesystem::scoped_istream(new boost::iostreams::stream<boost::iostreams::file_descriptor_source>(fd, 4096, 0));
826  }
827  catch(const std::exception&)
828  {
829  if(treat_failure_as_error)
830  {
831  ERR_FS << "Could not open '" << fname << "' for reading.\n";
832  }
833  filesystem::scoped_istream s(new bfs::ifstream());
834  s->clear(std::ios_base::failbit);
835  return s;
836  }
837 }
838 
839 filesystem::scoped_ostream ostream_file(const std::string& fname, bool create_directory)
840 {
841  LOG_FS << "streaming " << fname << " for writing.\n";
842 #if 1
843  try
844  {
845  boost::iostreams::file_descriptor_sink fd(bfs::path(fname), std::ios_base::binary);
846  return filesystem::scoped_ostream(new boost::iostreams::stream<boost::iostreams::file_descriptor_sink>(fd, 4096, 0));
847  }
848  catch(BOOST_IOSTREAMS_FAILURE& e)
849  {
850  // If this operation failed because the parent directory didn't exist, create the parent directory and retry.
851  error_code ec_unused;
852  if(create_directory && bfs::create_directories(bfs::path(fname).parent_path(), ec_unused))
853  {
854  return ostream_file(fname, false);
855  }
856  throw filesystem::io_exception(e.what());
857  }
858 #else
859  return new bfs::ofstream(path(fname), std::ios_base::binary);
860 #endif
861 }
862 // Throws io_exception if an error occurs
863 void write_file(const std::string& fname, const std::string& data)
864 {
865  scoped_ostream os = ostream_file(fname);
866  os->exceptions(std::ios_base::goodbit);
867 
868  const size_t block_size = 4096;
869  char buf[block_size];
870 
871  for(size_t i = 0; i < data.size(); i += block_size) {
872  const size_t bytes = std::min<size_t>(block_size,data.size() - i);
873  std::copy(data.begin() + i, data.begin() + i + bytes,buf);
874 
875  os->write(buf, bytes);
876  if (os->bad())
877  throw io_exception("Error writing to file: '" + fname + "'");
878  }
879 }
880 
882 {
883  return create_directory_if_missing(path(dirname));
884 }
886 {
888 }
889 
890 bool is_directory(const std::string& fname)
891 {
892  return is_directory_internal(path(fname));
893 }
894 
896 {
897  return file_exists(path(name));
898 }
899 
900 time_t file_modified_time(const std::string& fname)
901 {
902  error_code ec;
903  std::time_t mtime = bfs::last_write_time(path(fname), ec);
904  if (ec) {
905  LOG_FS << "Failed to read modification time of " << fname << ": " << ec.message() << '\n';
906  }
907  return mtime;
908 }
909 
910 bool is_gzip_file(const std::string& filename)
911 {
912  return path(filename).extension() == ".gz";
913 }
914 
915 bool is_bzip2_file(const std::string& filename)
916 {
917  return path(filename).extension() == ".bz2";
918 }
919 
920 int file_size(const std::string& fname)
921 {
922  error_code ec;
923  uintmax_t size = bfs::file_size(path(fname), ec);
924  if (ec) {
925  LOG_FS << "Failed to read filesize of " << fname << ": " << ec.message() << '\n';
926  return -1;
927  } else if (size > INT_MAX)
928  return INT_MAX;
929  else
930  return size;
931 }
932 
933 int dir_size(const std::string& pname)
934 {
935  bfs::path p(pname);
936  uintmax_t size_sum = 0;
937  error_code ec;
938  for ( bfs::recursive_directory_iterator i(p), end; i != end && !ec; ++i ) {
939  if(bfs::is_regular_file(i->path())) {
940  size_sum += bfs::file_size(i->path(), ec);
941  }
942  }
943  if (ec) {
944  LOG_FS << "Failed to read directorysize of " << pname << ": " << ec.message() << '\n';
945  return -1;
946  }
947  else if (size_sum > INT_MAX)
948  return INT_MAX;
949  else
950  return size_sum;
951 }
952 
953 std::string base_name(const std::string& file, const bool remove_extension)
954 {
955  if(!remove_extension) {
956  return path(file).filename().string();
957  } else {
958  return path(file).stem().string();
959  }
960 }
961 
963 {
964  return path(file).parent_path().string();
965 }
966 
968 {
969  if(file.empty()) {
970  return "";
971  }
972 
973  bfs::path p{file};
974  error_code ec;
975 
976  do {
977  p = p.parent_path();
978  bfs::path q = canonical(p, ec);
979  if (!ec) {
980  p = q;
981  }
982  } while(ec && !is_root(p.string()));
983 
984  return ec ? "" : p.string();
985 }
986 
987 bool is_path_sep(char c)
988 {
989  static const path sep = path("/").make_preferred();
990  const std::string s = std::string(1, c);
991  return sep == path(s).make_preferred();
992 }
993 
995 {
996  return path::preferred_separator;
997 }
998 
1000 {
1001 #ifndef _WIN32
1002  error_code ec;
1003  const bfs::path& p = bfs::canonical(path, ec);
1004  return ec ? false : !p.has_parent_path();
1005 #else
1006  //
1007  // Boost.Filesystem is completely unreliable when it comes to detecting
1008  // whether a path refers to a drive's root directory on Windows, so we are
1009  // forced to take an alternative approach here. Instead of hand-parsing
1010  // strings we'll just call a graphical shell service.
1011  //
1012  // There are several poorly-documented ways to refer to a drive in Windows by
1013  // escaping the filesystem namespace using \\.\, \\?\, and \??\. We're just
1014  // going to ignore those here, which may yield unexpected results in places
1015  // such as the file dialog. This function really shouldn't be used for
1016  // security validation anyway, and there are virtually infinite ways to name
1017  // a drive's root using the NT object namespace so it's pretty pointless to
1018  // try to catch those there.
1019  //
1020  // (And no, shlwapi.dll's PathIsRoot() doesn't recognize \\.\C:\, \\?\C:\, or
1021  // \??\C:\ as roots either.)
1022  //
1023  // More generally, do NOT use this code in security-sensitive applications.
1024  //
1025  // See also: <https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html>
1026  //
1027  const std::wstring& wpath = bfs::path{path}.make_preferred().wstring();
1028  return PathIsRootW(wpath.c_str()) == TRUE;
1029 #endif
1030 }
1031 
1033 {
1034  return bfs::path{path}.root_name().string();
1035 }
1036 
1038 {
1039  return bfs::path{path}.is_relative();
1040 }
1041 
1042 std::string normalize_path(const std::string& fpath, bool normalize_separators, bool resolve_dot_entries)
1043 {
1044  if(fpath.empty()) {
1045  return fpath;
1046  }
1047 
1048  error_code ec;
1049  bfs::path p = resolve_dot_entries
1050  ? bfs::canonical(fpath, ec)
1051  : bfs::absolute(fpath);
1052 
1053  if(ec) {
1054  return "";
1055  }
1056 
1057  if(normalize_separators) {
1058  return p.make_preferred().string();
1059  } else {
1060  return p.string();
1061  }
1062 }
1063 
1064 /**
1065  * The paths manager is responsible for recording the various paths
1066  * that binary files may be located at.
1067  * It should be passed a config object which holds binary path information.
1068  * This is in the format
1069  *@verbatim
1070  * [binary_path]
1071  * path=<path>
1072  * [/binary_path]
1073  * Binaries will be searched for in [wesnoth-path]/data/<path>/images/
1074  *@endverbatim
1075  */
1076 namespace {
1077 
1078 std::set<std::string> binary_paths;
1079 
1080 typedef std::map<std::string,std::vector<std::string> > paths_map;
1081 paths_map binary_paths_cache;
1082 
1083 }
1084 
1085 static void init_binary_paths()
1086 {
1087  if(binary_paths.empty()) {
1088  binary_paths.insert("");
1089  }
1090 }
1091 
1093 {}
1094 
1096 {
1097  set_paths(cfg);
1098 }
1099 
1101 {
1102  cleanup();
1103 }
1104 
1106 {
1107  cleanup();
1109 
1110  for (const config &bp : cfg.child_range("binary_path"))
1111  {
1112  std::string path = bp["path"].str();
1113  if (path.find("..") != std::string::npos) {
1114  ERR_FS << "Invalid binary path '" << path << "'\n";
1115  continue;
1116  }
1117  if (!path.empty() && path[path.size()-1] != '/') path += "/";
1118  if(binary_paths.count(path) == 0) {
1119  binary_paths.insert(path);
1120  paths_.push_back(path);
1121  }
1122  }
1123 }
1124 
1126 {
1127  binary_paths_cache.clear();
1128 
1129  for(std::vector<std::string>::const_iterator i = paths_.begin(); i != paths_.end(); ++i) {
1130  binary_paths.erase(*i);
1131  }
1132 }
1133 
1134 
1136 {
1137  binary_paths_cache.clear();
1138 }
1139 
1140 static bool is_legal_file(const std::string &filename)
1141 {
1142  DBG_FS << "Looking for '" << filename << "'.\n";
1143 
1144  if (filename.empty()) {
1145  LOG_FS << " invalid filename\n";
1146  return false;
1147  }
1148 
1149  if (filename.find("..") != std::string::npos) {
1150  ERR_FS << "Illegal path '" << filename << "' (\"..\" not allowed).\n";
1151  return false;
1152  }
1153 
1154  if (filename.find('\\') != std::string::npos) {
1155  ERR_FS << "Illegal path '" << filename << R"end(' ("\" not allowed, for compatibility with GNU/Linux and macOS).)end" << std::endl;
1156  return false;
1157  }
1158 
1159  if (looks_like_pbl(filename)) {
1160  ERR_FS << "Illegal path '" << filename << "' (.pbl files are not allowed)." << std::endl;
1161  return false;
1162  }
1163 
1164  return true;
1165 }
1166 
1167 /**
1168  * Returns a vector with all possible paths to a given type of binary,
1169  * e.g. 'images', 'sounds', etc,
1170  */
1171 const std::vector<std::string>& get_binary_paths(const std::string& type)
1172 {
1173  const paths_map::const_iterator itor = binary_paths_cache.find(type);
1174  if(itor != binary_paths_cache.end()) {
1175  return itor->second;
1176  }
1177 
1178  if (type.find("..") != std::string::npos) {
1179  // Not an assertion, as language.cpp is passing user data as type.
1180  ERR_FS << "Invalid WML type '" << type << "' for binary paths\n";
1181  static std::vector<std::string> dummy;
1182  return dummy;
1183  }
1184 
1185  std::vector<std::string>& res = binary_paths_cache[type];
1186 
1188 
1189  for(const std::string &path : binary_paths)
1190  {
1191  res.push_back(get_user_data_dir() + "/" + path + type + "/");
1192 
1193  if(!game_config::path.empty()) {
1194  res.push_back(game_config::path + "/" + path + type + "/");
1195  }
1196  }
1197 
1198  // not found in "/type" directory, try main directory
1199  res.push_back(get_user_data_dir() + "/");
1200 
1201  if(!game_config::path.empty())
1202  res.push_back(game_config::path+"/");
1203 
1204  return res;
1205 }
1206 
1208 {
1209  // We define ".." as "remove everything before" this is needed because
1210  // on the one hand allowing ".." would be a security risk but
1211  // especially for terrains the c++ engine puts a hardcoded "terrain/" before filename
1212  // and there would be no way to "escape" from "terrain/" otherwise. This is not the
1213  // best solution but we cannot remove it without another solution (subtypes maybe?).
1214 
1215  {
1216  std::string::size_type pos = filename.rfind("../");
1217  if(pos != std::string::npos) {
1218  return get_binary_file_location(type, filename.substr(pos + 3));
1219  }
1220  }
1221 
1222  if (!is_legal_file(filename))
1223  return std::string();
1224 
1225  for(const std::string &bp : get_binary_paths(type))
1226  {
1227  path bpath(bp);
1228  bpath /= filename;
1229  DBG_FS << " checking '" << bp << "'\n";
1230  if (file_exists(bpath)) {
1231  DBG_FS << " found at '" << bpath.string() << "'\n";
1232  return bpath.string();
1233  }
1234  }
1235 
1236  DBG_FS << " not found\n";
1237  return std::string();
1238 }
1239 
1241 {
1242  if (!is_legal_file(filename))
1243  return std::string();
1244 
1245  for (const std::string &bp : get_binary_paths(type))
1246  {
1247  path bpath(bp);
1248  bpath /= filename;
1249  DBG_FS << " checking '" << bp << "'\n";
1250  if (is_directory_internal(bpath)) {
1251  DBG_FS << " found at '" << bpath.string() << "'\n";
1252  return bpath.string();
1253  }
1254  }
1255 
1256  DBG_FS << " not found\n";
1257  return std::string();
1258 }
1259 
1260 std::string get_wml_location(const std::string &filename, const std::string &current_dir)
1261 {
1262  if (!is_legal_file(filename))
1263  return std::string();
1264 
1265  assert(game_config::path.empty() == false);
1266 
1267  path fpath(filename);
1268  path result;
1269 
1270  if (filename[0] == '~')
1271  {
1272  result /= get_user_data_path() / "data" / filename.substr(1);
1273  DBG_FS << " trying '" << result.string() << "'\n";
1274  } else if (*fpath.begin() == ".") {
1275  if (!current_dir.empty()) {
1276  result /= path(current_dir);
1277  } else {
1278  result /= path(game_config::path) / "data";
1279  }
1280 
1281  result /= filename;
1282  } else if (!game_config::path.empty()) {
1283  result /= path(game_config::path) / "data" / filename;
1284  }
1285  if (result.empty() || !file_exists(result)) {
1286  DBG_FS << " not found\n";
1287  result.clear();
1288  } else
1289  DBG_FS << " found: '" << result.string() << "'\n";
1290 
1291  return result.string();
1292 }
1293 
1294 static path subtract_path(const path &full, const path &prefix)
1295 {
1296  path::iterator fi = full.begin()
1297  , fe = full.end()
1298  , pi = prefix.begin()
1299  , pe = prefix.end();
1300  while (fi != fe && pi != pe && *fi == *pi) {
1301  ++fi;
1302  ++pi;
1303  }
1304  path rest;
1305  if (pi == pe)
1306  while (fi != fe) {
1307  rest /= *fi;
1308  ++fi;
1309  }
1310  return rest;
1311 }
1312 
1314 {
1315  path full_path(filename);
1316 
1317  path partial = subtract_path(full_path, get_user_data_path() / "data");
1318  if (!partial.empty())
1319  return "~" + partial.generic_string();
1320 
1321  partial = subtract_path(full_path, path(game_config::path) / "data");
1322  if (!partial.empty())
1323  return partial.generic_string();
1324 
1325  return filename;
1326 }
1327 
1329 {
1330  path full_path(get_binary_file_location("images", filename));
1331 
1332  if (full_path.empty())
1333  return full_path.generic_string();
1334 
1335  path partial = subtract_path(full_path, get_user_data_path());
1336  if (!partial.empty())
1337  return partial.generic_string();
1338 
1339  partial = subtract_path(full_path, game_config::path);
1340  if (!partial.empty())
1341  return partial.generic_string();
1342 
1343  return full_path.generic_string();
1344 }
1345 
1347 {
1348  const std::string real_program_name(program_name
1349 #ifdef DEBUG
1350  + "-debug"
1351 #endif
1352 #ifdef _WIN32
1353  + ".exe"
1354 #endif
1355  );
1356  return (path(game_config::wesnoth_program_dir) / real_program_name).string();
1357 }
1358 
1359 }
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
static const std::string & get_version_path_suffix()
std::string get_program_invocation(const std::string &program_name)
Returns the appropriate invocation for a Wesnoth-related binary, assuming that it is located in the s...
std::vector< char_t > string
void set_user_data_dir(std::string path)
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...
static bool is_directory_internal(const path &fpath)
int dummy
Definition: lstrlib.cpp:1125
bool delete_file(const std::string &filename)
bool looks_like_pbl(const std::string &file)
static l_noret error(LoadState *S, const char *why)
Definition: lundump.cpp:39
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:360
#define DBG_FS
static path cache_dir
static path subtract_path(const path &full, const path &prefix)
void init()
Some tasks to run on startup.
static path user_data_dir
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't prese...
std::string normalize_path(const std::string &path, bool normalize_separators=false, bool resolve_dot_entries=false)
Returns the absolute path of a file.
Definitions for the interface to Wesnoth Markup Language (WML).
void clear_binary_paths_cache()
std::string get_cwd()
bool exists(const image::locator &i_locator)
returns true if the given image actually exists, without loading it.
Definition: image.cpp:1129
void write(std::ostream &out, const configr_of &cfg, unsigned int level)
Definition: parser.cpp:749
static bool is_legal_file(const std::string &filename)
filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error=true)
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.
void read(config &cfg, std::istream &in, abstract_validator *validator)
Definition: parser.cpp:612
std::string get_user_data_dir()
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.
static void set_user_config_path(path newconfig)
bool is_directory(const std::string &fname)
Returns true if the given file is a directory.
std::unique_ptr< std::istream > scoped_istream
Definition: filesystem.hpp:37
static const path & get_user_data_path()
std::string path
Definition: game_config.cpp:56
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. ...
std::string get_dir(const std::string &dir)
std::string read_file(const std::string &fname)
Basic disk I/O - read file.
#define LOG_FS
lu_byte right
Definition: lparser.cpp:1027
bool is_path_sep(char c)
Returns whether c is a path separator.
static path user_config_dir
std::unique_ptr< std::ostream > scoped_ostream
Definition: filesystem.hpp:38
static void push_if_exists(std::vector< std::string > *vec, const path &file, bool full)
size_t size(const utf8::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:86
bool is_gzip_file(const std::string &filename)
Returns true if the file ends with '.gz'.
std::string get_cache_dir()
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.
time_t file_modified_time(const std::string &fname)
Get the modification time of a file.
std::string get_exe_dir()
filesystem::scoped_ostream ostream_file(const std::string &fname, bool create_directory=true)
void get_files_in_dir(const std::string &dir, std::vector< std::string > *files, std::vector< std::string > *dirs=nullptr, file_name_option mode=FILE_NAME_ONLY, file_filter_option filter=NO_FILTER, file_reorder_option reorder=DONT_REORDER, file_tree_checksum *checksum=nullptr)
Populates 'files' with all the files and 'dirs' with all the directories in dir.
Log file control routines for Windows.
bool is_relative(const std::string &path)
Returns whether the path seems to be relative.
int dir_size(const std::string &path)
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=std::string())
Returns a complete path to the actual WML file or directory or an empty string if the file isn't pres...
std::vector< std::string > paths_
Definition: filesystem.hpp:293
mock_party p
An exception object used when an IO error occurs.
Definition: filesystem.hpp:46
static map_location::DIRECTION s
unsigned int minor_version() const
Retrieves the minor version number (x2 in "x1.x2.x3").
Definition: version.cpp:133
bool make_directory(const std::string &dirname)
unsigned int major_version() const
Retrieves the major version number (x1 in "x1.x2.x3").
Definition: version.cpp:129
size_t i
Definition: function.cpp:933
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
static int sort(lua_State *L)
Definition: ltablib.cpp:411
#define ERR_FS
const version_info wesnoth_version(VERSION)
rng * generator
This generator is automatically synced during synced context.
Definition: random.cpp:57
bool delete_directory(const std::string &dirname, const bool keep_pbl=false)
char path_separator()
Returns the standard path separator for the current platform.
std::string base_name(const std::string &file, const bool remove_extension=false)
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't exist.
std::string get_user_config_dir()
bool is_bzip2_file(const std::string &filename)
Returns true if the file ends with '.bz2'.
static bool error_except_not_found(const error_code &ec)
Standard logging facilities (interface).
void set_user_config_dir(const std::string &path)
static const char * name(const std::vector< SDL_Joystick * > &joysticks, const size_t index)
Definition: joystick.cpp:48
#define e
static lg::log_domain log_filesystem("filesystem")
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:93
mock_char c
static void setup_user_data_dir()
Interfaces for manipulating version numbers of engine, add-ons, etc.
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:65