The Battle for Wesnoth  1.13.4+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 <[email protected]>
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 "global.hpp"
22 
23 #include "filesystem.hpp"
25 
26 #include <boost/filesystem.hpp>
27 #include <boost/filesystem/fstream.hpp>
28 #include <boost/system/windows_error.hpp>
29 #include <boost/iostreams/device/file_descriptor.hpp>
30 #include <boost/iostreams/stream.hpp>
31 #include <set>
32 
33 using boost::uintmax_t;
34 
35 #ifdef _WIN32
36 #include "log_windows.hpp"
37 
38 #include <boost/locale.hpp>
39 
40 #include <windows.h>
41 #include <shlobj.h>
42 #endif /* !_WIN32 */
43 
44 #include "config.hpp"
45 #include "game_config.hpp"
46 #include "log.hpp"
47 #include "util.hpp"
48 #include "version.hpp"
49 
50 static lg::log_domain log_filesystem("filesystem");
51 #define DBG_FS LOG_STREAM(debug, log_filesystem)
52 #define LOG_FS LOG_STREAM(info, log_filesystem)
53 #define WRN_FS LOG_STREAM(warn, log_filesystem)
54 #define ERR_FS LOG_STREAM(err, log_filesystem)
55 
56 namespace bfs = boost::filesystem;
58 using boost::system::error_code;
59 
60 namespace {
61  // These are the filenames that get special processing
62  const std::string maincfg_filename = "_main.cfg";
63  const std::string finalcfg_filename = "_final.cfg";
64  const std::string initialcfg_filename = "_initial.cfg";
65 }
66 namespace {
67  //only used by windows but put outside the ifdef to let it check by ci build.
68  class customcodecvt : public std::codecvt<wchar_t /*intern*/, char /*extern*/, std::mbstate_t>
69  {
70  private:
71  //private static helper things
72  template<typename char_t_to>
73  struct customcodecvt_do_conversion_writer
74  {
75  customcodecvt_do_conversion_writer(char_t_to*& _to_next, char_t_to* _to_end) :
76  to_next(_to_next),
77  to_end(_to_end)
78  {}
79  char_t_to*& to_next;
80  char_t_to* to_end;
81 
82  bool can_push(size_t count)
83  {
84  return static_cast<size_t>(to_end - to_next) > count;
85  }
86 
87  void push(char_t_to val)
88  {
89  assert(to_next != to_end);
90  *to_next++ = val;
91  }
92  };
93 
94  template<typename char_t_from , typename char_t_to>
95  static void customcodecvt_do_conversion( std::mbstate_t& /*state*/,
96  const char_t_from* from,
97  const char_t_from* from_end,
98  const char_t_from*& from_next,
99  char_t_to* to,
100  char_t_to* to_end,
101  char_t_to*& to_next )
102  {
103  typedef typename ucs4_convert_impl::convert_impl<char_t_from>::type impl_type_from;
104  typedef typename ucs4_convert_impl::convert_impl<char_t_to>::type impl_type_to;
105 
106  from_next = from;
107  to_next = to;
108  customcodecvt_do_conversion_writer<char_t_to> writer(to_next, to_end);
109  while(from_next != from_end)
110  {
111  impl_type_to::write(writer, impl_type_from::read(from_next, from_end));
112  }
113  }
114 
115  public:
116 
117  //Not used by boost filesystem
118  int do_encoding() const throw() { return 0; }
119  //Not used by boost filesystem
120  bool do_always_noconv() const throw() { return false; }
121  int do_length( std::mbstate_t& /*state*/,
122  const char* /*from*/,
123  const char* /*from_end*/,
124  std::size_t /*max*/ ) const
125  {
126  //Not used by boost filesystem
127  throw "Not supported";
128  }
129 
130  std::codecvt_base::result unshift( std::mbstate_t& /*state*/,
131  char* /*to*/,
132  char* /*to_end*/,
133  char*& /*to_next*/) const
134  {
135  //Not used by boost filesystem
136  throw "Not supported";
137  }
138 
139  //there are still some methods which could be implemented but arent because boost filesystem won't use them.
140  std::codecvt_base::result do_in( std::mbstate_t& state,
141  const char* from,
142  const char* from_end,
143  const char*& from_next,
144  wchar_t* to,
145  wchar_t* to_end,
146  wchar_t*& to_next ) const
147  {
148  try
149  {
150  customcodecvt_do_conversion<char, wchar_t>(state, from, from_end, from_next, to, to_end, to_next);
151  }
152  catch(...)
153  {
154  ERR_FS << "Invalid UTF-8 string'" << std::string(from, from_end) << "' " << std::endl;
156  }
157  return std::codecvt_base::ok;
158  }
159 
160  std::codecvt_base::result do_out( std::mbstate_t& state,
161  const wchar_t* from,
162  const wchar_t* from_end,
163  const wchar_t*& from_next,
164  char* to,
165  char* to_end,
166  char*& to_next ) const
167  {
168  try
169  {
170  customcodecvt_do_conversion<wchar_t, char>(state, from, from_end, from_next, to, to_end, to_next);
171  }
172  catch(...)
173  {
174  ERR_FS << "Invalid UTF-16 string" << std::endl;
176  }
177  return std::codecvt_base::ok;
178  }
179  };
180 
181 #ifdef _WIN32
182  class static_runner {
183  public:
184  static_runner() {
185  // Boost uses the current locale to generate a UTF-8 one
186  std::locale utf8_loc = boost::locale::generator().generate("");
187  // use a custom locale becasue we want to use out log.hpp functions in case of an invalid string.
188  utf8_loc = std::locale(utf8_loc, new customcodecvt());
189  boost::filesystem::path::imbue(utf8_loc);
190  }
191  };
192 
193  static static_runner static_bfs_path_imbuer;
194 #endif
195 }
196 
197 
198 namespace filesystem {
199 
200 static void push_if_exists(std::vector<std::string> *vec, const path &file, bool full) {
201  if (vec != nullptr) {
202  if (full)
203  vec->push_back(file.generic_string());
204  else
205  vec->push_back(file.filename().generic_string());
206  }
207 }
208 
209 static inline bool error_except_not_found(const error_code &ec)
210 {
211  return (ec
212  && ec.value() != boost::system::errc::no_such_file_or_directory
213 #ifdef _WIN32
214  && ec.value() != boost::system::windows_error::path_not_found
215 #endif /*_WIN32*/
216  );
217 }
218 
219 static bool is_directory_internal(const path &fpath)
220 {
221  error_code ec;
222  bool is_dir = bfs::is_directory(fpath, ec);
223  if (error_except_not_found(ec)) {
224  LOG_FS << "Failed to check if " << fpath.string() << " is a directory: " << ec.message() << '\n';
225  }
226  return is_dir;
227 }
228 
229 static bool file_exists(const path &fpath)
230 {
231  error_code ec;
232  bool exists = bfs::exists(fpath, ec);
233  if (error_except_not_found(ec)) {
234  ERR_FS << "Failed to check existence of file " << fpath.string() << ": " << ec.message() << '\n';
235  }
236  return exists;
237 }
238 static path get_dir(const path &dirpath)
239 {
240  bool is_dir = is_directory_internal(dirpath);
241  if (!is_dir) {
242  error_code ec;
243  bfs::create_directory(dirpath, ec);
244  if (ec) {
245  ERR_FS << "Failed to create directory " << dirpath.string() << ": " << ec.message() << '\n';
246  }
247  // This is probably redundant
248  is_dir = is_directory_internal(dirpath);
249  }
250  if (!is_dir) {
251  ERR_FS << "Could not open or create directory " << dirpath.string() << '\n';
252  return std::string();
253  }
254 
255  return dirpath;
256 }
257 static bool create_directory_if_missing(const path &dirpath)
258 {
259  error_code ec;
260  bfs::file_status fs = bfs::status(dirpath, ec);
261  if (error_except_not_found(ec)) {
262  ERR_FS << "Failed to retrieve file status for " << dirpath.string() << ": " << ec.message() << '\n';
263  return false;
264  } else if (bfs::is_directory(fs)) {
265  DBG_FS << "directory " << dirpath.string() << " exists, not creating\n";
266  return true;
267  } else if (bfs::exists(fs)) {
268  ERR_FS << "cannot create directory " << dirpath.string() << "; file exists\n";
269  return false;
270  }
271 
272  bool created = bfs::create_directory(dirpath, ec);
273  if (ec) {
274  ERR_FS << "Failed to create directory " << dirpath.string() << ": " << ec.message() << '\n';
275  }
276  return created;
277 }
278 static bool create_directory_if_missing_recursive(const path& dirpath)
279 {
280  DBG_FS << "creating recursive directory: " << dirpath.string() << '\n';
281 
282  if (dirpath.empty())
283  return false;
284  error_code ec;
285  bfs::file_status fs = bfs::status(dirpath);
286  if (error_except_not_found(ec)) {
287  ERR_FS << "Failed to retrieve file status for " << dirpath.string() << ": " << ec.message() << '\n';
288  return false;
289  } else if (bfs::is_directory(fs)) {
290  return true;
291  } else if (bfs::exists(fs)) {
292  return false;
293  }
294 
295  if (!dirpath.has_parent_path() || create_directory_if_missing_recursive(dirpath.parent_path())) {
296  return create_directory_if_missing(dirpath);
297  } else {
298  ERR_FS << "Could not create parents to " << dirpath.string() << '\n';
299  return false;
300  }
301 }
302 
304  std::vector<std::string>* files,
305  std::vector<std::string>* dirs,
308  file_reorder_option reorder,
309  file_tree_checksum* checksum) {
310  if(path(dir).is_relative() && !game_config::path.empty()) {
311  path absolute_dir(game_config::path);
312  absolute_dir /= dir;
313  if(is_directory_internal(absolute_dir)) {
314  get_files_in_dir(absolute_dir.string(), files, dirs, mode, filter, reorder, checksum);
315  return;
316  }
317  }
318 
319  const path dirpath(dir);
320 
321  if (reorder == DO_REORDER) {
322  LOG_FS << "searching for _main.cfg in directory " << dir << '\n';
323  const path maincfg = dirpath / maincfg_filename;
324 
325  if (file_exists(maincfg)) {
326  LOG_FS << "_main.cfg found : " << maincfg << '\n';
327  push_if_exists(files, maincfg, mode == ENTIRE_FILE_PATH);
328  return;
329  }
330  }
331 
332  error_code ec;
333  bfs::directory_iterator di(dirpath, ec);
334  bfs::directory_iterator end;
335  if (ec) {
336  // Probably not a directory, let the caller deal with it.
337  return;
338  }
339  for(; di != end; ++di) {
340  bfs::file_status st = di->status(ec);
341  if (ec) {
342  LOG_FS << "Failed to get file status of " << di->path().string() << ": " << ec.message() << '\n';
343  continue;
344  }
345  if (st.type() == bfs::regular_file) {
346  {
347  std::string basename = di->path().filename().string();
348  if (filter == SKIP_PBL_FILES && looks_like_pbl(basename))
349  continue;
350  if(!basename.empty() && basename[0] == '.' )
351  continue;
352  }
353  push_if_exists(files, di->path(), mode == ENTIRE_FILE_PATH);
354 
355  if (checksum != nullptr) {
356  std::time_t mtime = bfs::last_write_time(di->path(), ec);
357  if (ec) {
358  LOG_FS << "Failed to read modification time of " << di->path().string() << ": " << ec.message() << '\n';
359  } else if (mtime > checksum->modified) {
360  checksum->modified = mtime;
361  }
362 
363  uintmax_t size = bfs::file_size(di->path(), ec);
364  if (ec) {
365  LOG_FS << "Failed to read filesize of " << di->path().string() << ": " << ec.message() << '\n';
366  } else {
367  checksum->sum_size += size;
368  }
369  checksum->nfiles++;
370  }
371  } else if (st.type() == bfs::directory_file) {
372  std::string basename = di->path().filename().string();
373 
374  if(!basename.empty() && basename[0] == '.' )
375  continue;
376  if (filter == SKIP_MEDIA_DIR
377  && (basename == "images" || basename == "sounds"))
378  continue;
379 
380  const path inner_main(di->path() / maincfg_filename);
381  bfs::file_status main_st = bfs::status(inner_main, ec);
382  if (error_except_not_found(ec)) {
383  LOG_FS << "Failed to get file status of " << inner_main.string() << ": " << ec.message() << '\n';
384  } else if (reorder == DO_REORDER && main_st.type() == bfs::regular_file) {
385  LOG_FS << "_main.cfg found : " << (mode == ENTIRE_FILE_PATH ? inner_main.string() : inner_main.filename().string()) << '\n';
386  push_if_exists(files, inner_main, mode == ENTIRE_FILE_PATH);
387  } else {
388  push_if_exists(dirs, di->path(), mode == ENTIRE_FILE_PATH);
389  }
390  }
391  }
392 
393  if (files != nullptr)
394  std::sort(files->begin(),files->end());
395 
396  if (dirs != nullptr)
397  std::sort(dirs->begin(),dirs->end());
398 
399  if (files != nullptr && reorder == DO_REORDER) {
400  // move finalcfg_filename, if present, to the end of the vector
401  for (unsigned int i = 0; i < files->size(); i++) {
402  if (ends_with((*files)[i], "/" + finalcfg_filename)) {
403  files->push_back((*files)[i]);
404  files->erase(files->begin()+i);
405  break;
406  }
407  }
408  // move initialcfg_filename, if present, to the beginning of the vector
409  int foundit = -1;
410  for (unsigned int i = 0; i < files->size(); i++)
411  if (ends_with((*files)[i], "/" + initialcfg_filename)) {
412  foundit = i;
413  break;
414  }
415  if (foundit > 0) {
416  std::string initialcfg = (*files)[foundit];
417  for (unsigned int i = foundit; i > 0; i--)
418  (*files)[i] = (*files)[i-1];
419  (*files)[0] = initialcfg;
420  }
421  }
422 }
423 
425 {
426  return get_dir(path(dir)).string();
427 }
428 
430 {
431  std::string next_filename;
432  int counter = 0;
433 
434  do {
435  std::stringstream filename;
436 
437  filename << name;
438  filename.width(3);
439  filename.fill('0');
440  filename.setf(std::ios_base::right);
441  filename << counter << extension;
442  counter++;
443  next_filename = filename.str();
444  } while(file_exists(next_filename) && counter < 1000);
445  return next_filename;
446 }
447 
449 
451 {
452  static std::string suffix;
453 
454  // We only really need to generate this once since
455  // the version number cannot change during runtime.
456 
457  if(suffix.empty()) {
458  std::ostringstream s;
461  suffix = s.str();
462  }
463 
464  return suffix;
465 }
466 
467 static void setup_user_data_dir()
468 {
469  if (!create_directory_if_missing_recursive(user_data_dir)) {
470  ERR_FS << "could not open or create user data directory at " << user_data_dir.string() << '\n';
471  return;
472  }
473  // TODO: this may not print the error message if the directory exists but we don't have the proper permissions
474 
475  // Create user data and add-on directories
476  create_directory_if_missing(user_data_dir / "editor");
477  create_directory_if_missing(user_data_dir / "editor" / "maps");
478  create_directory_if_missing(user_data_dir / "editor" / "scenarios");
479  create_directory_if_missing(user_data_dir / "data");
480  create_directory_if_missing(user_data_dir / "data" / "add-ons");
481  create_directory_if_missing(user_data_dir / "saves");
482  create_directory_if_missing(user_data_dir / "persist");
483 
484 #ifdef _WIN32
486 #endif
487 }
488 
489 #ifdef _WIN32
490 // As a convenience for portable installs on Windows, relative paths with . or
491 // .. as the first component are considered relative to the current workdir
492 // instead of Documents/My Games.
493 static bool is_path_relative_to_cwd(const std::string& str)
494 {
495  const path p(str);
496 
497  if(p.empty()) {
498  return false;
499  }
500 
501  return *p.begin() == "." || *p.begin() == "..";
502 }
503 #endif
504 
506 {
507 #ifdef PREFERENCES_DIR
508  if (newprefdir.empty()) newprefdir = PREFERENCES_DIR;
509 #endif
510 
511 #ifdef _WIN32
512  if(newprefdir.size() > 2 && newprefdir[1] == ':') {
513  //allow absolute path override
514  user_data_dir = newprefdir;
515  } else if(is_path_relative_to_cwd(newprefdir)) {
516  // Custom directory relative to workdir (for portable installs, etc.)
517  user_data_dir = get_cwd() + "/" + newprefdir;
518  } else {
519  if(newprefdir.empty()) {
520  newprefdir = "Wesnoth" + get_version_path_suffix();
521  }
522 
523  wchar_t docs_path[MAX_PATH];
524 
525  HRESULT res = SHGetFolderPathW(nullptr,
526  CSIDL_PERSONAL | CSIDL_FLAG_CREATE, nullptr,
527  SHGFP_TYPE_CURRENT,
528  docs_path);
529  if(res != S_OK) {
530  //
531  // Crummy fallback path full of pain and suffering.
532  //
533  ERR_FS << "Could not determine path to user's Documents folder! ("
534  << std::hex << "0x" << res << std::dec << ") "
535  << "User config/data directories may be unavailable for "
536  << "this session. Please report this as a bug.\n";
537  user_data_dir = path(get_cwd()) / newprefdir;
538  } else {
539  path games_path = path(docs_path) / "My Games";
540  create_directory_if_missing(games_path);
541 
542  user_data_dir = games_path / newprefdir;
543  }
544  }
545 
546 #else /*_WIN32*/
547 
548  std::string backupprefdir = ".wesnoth" + get_version_path_suffix();
549 
550 #ifdef _X11
551  const char *home_str = getenv("HOME");
552 
553  if (newprefdir.empty()) {
554  char const *xdg_data = getenv("XDG_DATA_HOME");
555  if (!xdg_data || xdg_data[0] == '\0') {
556  if (!home_str) {
557  newprefdir = backupprefdir;
558  goto other;
559  }
560  user_data_dir = home_str;
561  user_data_dir /= ".local/share";
562  } else user_data_dir = xdg_data;
563  user_data_dir /= "wesnoth";
564  user_data_dir /= get_version_path_suffix();
565  } else {
566  other:
567  path home = home_str ? home_str : ".";
568 
569  if (newprefdir[0] == '/')
570  user_data_dir = newprefdir;
571  else
572  user_data_dir = home / newprefdir;
573  }
574 #else
575  if (newprefdir.empty()) newprefdir = backupprefdir;
576 
577  const char* home_str = getenv("HOME");
578  path home = home_str ? home_str : ".";
579 
580  if (newprefdir[0] == '/')
581  user_data_dir = newprefdir;
582  else
583  user_data_dir = home / newprefdir;
584 #endif
585 
586 #endif /*_WIN32*/
588 }
589 
590 static void set_user_config_path(path newconfig)
591 {
592  user_config_dir = newconfig;
593  if (!create_directory_if_missing_recursive(user_config_dir)) {
594  ERR_FS << "could not open or create user config directory at " << user_config_dir.string() << '\n';
595  }
596 }
597 
599 {
600  set_user_config_path(newconfigdir);
601 }
602 
603 static const path &get_user_data_path()
604 {
605  if (user_data_dir.empty())
606  {
608  }
609  return user_data_dir;
610 }
612 {
613  if (user_config_dir.empty())
614  {
615 #if defined(_X11) && !defined(PREFERENCES_DIR)
616  char const *xdg_config = getenv("XDG_CONFIG_HOME");
617  if (!xdg_config || xdg_config[0] == '\0') {
618  xdg_config = getenv("HOME");
619  if (!xdg_config) {
620  user_config_dir = get_user_data_path();
621  return user_config_dir.string();
622  }
623  user_config_dir = xdg_config;
624  user_config_dir /= ".config";
625  } else user_config_dir = xdg_config;
626  user_config_dir /= "wesnoth";
627  set_user_config_path(user_config_dir);
628 #else
629  user_config_dir = get_user_data_path();
630 #endif
631  }
632  return user_config_dir.string();
633 }
635 {
636  return get_user_data_path().string();
637 }
639 {
640  if (cache_dir.empty())
641  {
642 #if defined(_X11) && !defined(PREFERENCES_DIR)
643  char const *xdg_cache = getenv("XDG_CACHE_HOME");
644  if (!xdg_cache || xdg_cache[0] == '\0') {
645  xdg_cache = getenv("HOME");
646  if (!xdg_cache) {
647  cache_dir = get_dir(get_user_data_path() / "cache");
648  return cache_dir.string();
649  }
650  cache_dir = xdg_cache;
651  cache_dir /= ".cache";
652  } else cache_dir = xdg_cache;
653  cache_dir /= "wesnoth";
655 #else
656  cache_dir = get_dir(get_user_data_path() / "cache");
657 #endif
658  }
659  return cache_dir.string();
660 }
661 
663 {
664  error_code ec;
665  path cwd = bfs::current_path(ec);
666  if (ec) {
667  ERR_FS << "Failed to get current directory: " << ec.message() << '\n';
668  return "";
669  }
670  return cwd.generic_string();
671 }
673 {
674 #ifdef _WIN32
675  wchar_t process_path[MAX_PATH];
676  SetLastError(ERROR_SUCCESS);
677  GetModuleFileNameW(nullptr, process_path, MAX_PATH);
678  if (GetLastError() != ERROR_SUCCESS) {
679  return get_cwd();
680  }
681 
682  path exe(process_path);
683  return exe.parent_path().string();
684 #else
685  if (bfs::exists("/proc/")) {
686  path self_exe("/proc/self/exe");
687  error_code ec;
688  path exe = bfs::read_symlink(self_exe, ec);
689  if (ec) {
690  return std::string();
691  }
692 
693  return exe.parent_path().string();
694  } else {
695  return get_cwd();
696  }
697 #endif
698 }
699 
700 bool make_directory(const std::string& dirname)
701 {
702  error_code ec;
703  bool created = bfs::create_directory(path(dirname), ec);
704  if (ec) {
705  ERR_FS << "Failed to create directory " << dirname << ": " << ec.message() << '\n';
706  }
707  return created;
708 }
709 bool delete_directory(const std::string& dirname, const bool keep_pbl)
710 {
711  bool ret = true;
712  std::vector<std::string> files;
713  std::vector<std::string> dirs;
714  error_code ec;
715 
716  get_files_in_dir(dirname, &files, &dirs, ENTIRE_FILE_PATH, keep_pbl ? SKIP_PBL_FILES : NO_FILTER);
717 
718  if(!files.empty()) {
719  for(std::vector<std::string>::const_iterator i = files.begin(); i != files.end(); ++i) {
720  bfs::remove(path(*i), ec);
721  if (ec) {
722  LOG_FS << "remove(" << (*i) << "): " << ec.message() << '\n';
723  ret = false;
724  }
725  }
726  }
727 
728  if(!dirs.empty()) {
729  for(std::vector<std::string>::const_iterator j = dirs.begin(); j != dirs.end(); ++j) {
730  //TODO: this does not preserve any other PBL files
731  // filesystem.cpp does this too, so this might be intentional
732  if(!delete_directory(*j))
733  ret = false;
734  }
735  }
736 
737  if (ret) {
738  bfs::remove(path(dirname), ec);
739  if (ec) {
740  LOG_FS << "remove(" << dirname << "): " << ec.message() << '\n';
741  ret = false;
742  }
743  }
744  return ret;
745 }
746 
747 bool delete_file(const std::string &filename)
748 {
749  error_code ec;
750  bool ret = bfs::remove(path(filename), ec);
751  if (ec) {
752  ERR_FS << "Could not delete file " << filename << ": " << ec.message() << '\n';
753  }
754  return ret;
755 }
756 
758 {
759  scoped_istream is = istream_file(fname);
760  std::stringstream ss;
761  ss << is->rdbuf();
762  return ss.str();
763 }
764 
765 #if BOOST_VERSION < 104800
766 //boost iostream < 1.48 expects boost filesystem v2 paths. This is an adapter
768 {
769  template<typename stringtype>
770  iostream_path(const stringtype& s)
771  : path_(s)
772  {
773 
774  }
775  typedef bfs::path::string_type external_string_type;
776  external_string_type external_file_string() const
777  {
778  return path_.native();
779  }
781 };
782 
783 #else
784 typedef bfs::path iostream_path;
785 #endif
786 
787 std::istream *istream_file(const std::string &fname, bool treat_failure_as_error)
788 {
789  LOG_FS << "Streaming " << fname << " for reading.\n";
790  if (fname.empty()) {
791  ERR_FS << "Trying to open file with empty name.\n";
792  bfs::ifstream *s = new bfs::ifstream();
793  s->clear(std::ios_base::failbit);
794  return s;
795  }
796 
797  //mingw doesn't support std::basic_ifstream::basic_ifstream(const wchar_t* fname)
798  //that why boost::filesystem::fstream.hpp doesnt work with mingw.
799  try
800  {
801  boost::iostreams::file_descriptor_source fd(iostream_path(fname), std::ios_base::binary);
802  //TODO: has this still use ?
803  if (!fd.is_open() && treat_failure_as_error) {
804  ERR_FS << "Could not open '" << fname << "' for reading.\n";
805  }
806  return new boost::iostreams::stream<boost::iostreams::file_descriptor_source>(fd, 4096, 0);
807  }
808  catch(const std::exception ex)
809  {
810  if(treat_failure_as_error)
811  {
812  ERR_FS << "Could not open '" << fname << "' for reading.\n";
813  }
814  bfs::ifstream *s = new bfs::ifstream();
815  s->clear(std::ios_base::failbit);
816  return s;
817  }
818 }
819 
820 std::ostream *ostream_file(std::string const &fname, bool create_directory)
821 {
822  LOG_FS << "streaming " << fname << " for writing.\n";
823 #if 1
824  try
825  {
826  boost::iostreams::file_descriptor_sink fd(iostream_path(fname), std::ios_base::binary);
827  return new boost::iostreams::stream<boost::iostreams::file_descriptor_sink>(fd, 4096, 0);
828  }
829  catch(BOOST_IOSTREAMS_FAILURE& e)
830  {
831  // If this operation failed because the parent directoy didn't exist, create the parent directoy and retry.
832  error_code ec_unused;
833  if(create_directory && bfs::create_directories(bfs::path(fname).parent_path(), ec_unused))
834  {
835  return ostream_file(fname, false);
836  }
837  throw filesystem::io_exception(e.what());
838  }
839 #else
840  return new bfs::ofstream(path(fname), std::ios_base::binary);
841 #endif
842 }
843 // Throws io_exception if an error occurs
844 void write_file(const std::string& fname, const std::string& data)
845 {
846  scoped_ostream os = ostream_file(fname);
847  os->exceptions(std::ios_base::goodbit);
848 
849  const size_t block_size = 4096;
850  char buf[block_size];
851 
852  for(size_t i = 0; i < data.size(); i += block_size) {
853  const size_t bytes = std::min<size_t>(block_size,data.size() - i);
854  std::copy(data.begin() + i, data.begin() + i + bytes,buf);
855 
856  os->write(buf, bytes);
857  if (os->bad())
858  throw io_exception("Error writing to file: '" + fname + "'");
859  }
860 }
861 
863 {
864  return create_directory_if_missing(path(dirname));
865 }
867 {
869 }
870 
871 bool is_directory(const std::string& fname)
872 {
873  return is_directory_internal(path(fname));
874 }
875 
877 {
878  return file_exists(path(name));
879 }
880 
881 time_t file_modified_time(const std::string& fname)
882 {
883  error_code ec;
884  std::time_t mtime = bfs::last_write_time(path(fname), ec);
885  if (ec) {
886  LOG_FS << "Failed to read modification time of " << fname << ": " << ec.message() << '\n';
887  }
888  return mtime;
889 }
890 
891 bool is_gzip_file(const std::string& filename)
892 {
893  return path(filename).extension() == ".gz";
894 }
895 
896 bool is_bzip2_file(const std::string& filename)
897 {
898  return path(filename).extension() == ".bz2";
899 }
900 
901 int file_size(const std::string& fname)
902 {
903  error_code ec;
904  uintmax_t size = bfs::file_size(path(fname), ec);
905  if (ec) {
906  LOG_FS << "Failed to read filesize of " << fname << ": " << ec.message() << '\n';
907  return -1;
908  } else if (size > INT_MAX)
909  return INT_MAX;
910  else
911  return size;
912 }
913 
915 {
916  bfs::path p(pname);
917  uintmax_t size_sum = 0;
918  error_code ec;
919  for ( bfs::recursive_directory_iterator i(p), end; i != end && !ec; ++i ) {
920  if(bfs::is_regular_file(i->path())) {
921  size_sum += bfs::file_size(i->path(), ec);
922  }
923  }
924  if (ec) {
925  LOG_FS << "Failed to read directorysize of " << pname << ": " << ec.message() << '\n';
926  return -1;
927  }
928  else if (size_sum > INT_MAX)
929  return INT_MAX;
930  else
931  return size_sum;
932 }
933 
935 {
936  return path(file).filename().string();
937 }
938 
940 {
941  return path(file).parent_path().string();
942 }
943 bool is_path_sep(char c)
944 {
945  static const path sep = path("/").make_preferred();
946  const std::string s = std::string(1, c);
947  return sep == path(s).make_preferred();
948 }
950 {
951  if (fpath.empty()) {
952  return fpath;
953  }
954 
955  return bfs::absolute(fpath).string();
956 }
957 
958 /**
959  * The paths manager is responsible for recording the various paths
960  * that binary files may be located at.
961  * It should be passed a config object which holds binary path information.
962  * This is in the format
963  *@verbatim
964  * [binary_path]
965  * path=<path>
966  * [/binary_path]
967  * Binaries will be searched for in [wesnoth-path]/data/<path>/images/
968  *@endverbatim
969  */
970 namespace {
971 
972 std::set<std::string> binary_paths;
973 
974 typedef std::map<std::string,std::vector<std::string> > paths_map;
975 paths_map binary_paths_cache;
976 
977 }
978 
979 static void init_binary_paths()
980 {
981  if(binary_paths.empty()) {
982  binary_paths.insert("");
983  }
984 }
985 
987 {}
988 
990 {
991  set_paths(cfg);
992 }
993 
995 {
996  cleanup();
997 }
998 
1000 {
1001  cleanup();
1003 
1004  for (const config &bp : cfg.child_range("binary_path"))
1005  {
1006  std::string path = bp["path"].str();
1007  if (path.find("..") != std::string::npos) {
1008  ERR_FS << "Invalid binary path '" << path << "'\n";
1009  continue;
1010  }
1011  if (!path.empty() && path[path.size()-1] != '/') path += "/";
1012  if(binary_paths.count(path) == 0) {
1013  binary_paths.insert(path);
1014  paths_.push_back(path);
1015  }
1016  }
1017 }
1018 
1020 {
1021  binary_paths_cache.clear();
1022 
1023  for(std::vector<std::string>::const_iterator i = paths_.begin(); i != paths_.end(); ++i) {
1024  binary_paths.erase(*i);
1025  }
1026 }
1027 
1028 
1030 {
1031  binary_paths_cache.clear();
1032 }
1033 
1034 static bool is_legal_file(const std::string &filename)
1035 {
1036  DBG_FS << "Looking for '" << filename << "'.\n";
1037 
1038  if (filename.empty()) {
1039  LOG_FS << " invalid filename\n";
1040  return false;
1041  }
1042 
1043  if (filename.find("..") != std::string::npos) {
1044  ERR_FS << "Illegal path '" << filename << "' (\"..\" not allowed).\n";
1045  return false;
1046  }
1047 
1048  if (looks_like_pbl(filename)) {
1049  ERR_FS << "Illegal path '" << filename << "' (.pbl files are not allowed)." << std::endl;
1050  return false;
1051  }
1052 
1053  return true;
1054 }
1055 
1056 /**
1057  * Returns a vector with all possible paths to a given type of binary,
1058  * e.g. 'images', 'sounds', etc,
1059  */
1060 const std::vector<std::string>& get_binary_paths(const std::string& type)
1061 {
1062  const paths_map::const_iterator itor = binary_paths_cache.find(type);
1063  if(itor != binary_paths_cache.end()) {
1064  return itor->second;
1065  }
1066 
1067  if (type.find("..") != std::string::npos) {
1068  // Not an assertion, as language.cpp is passing user data as type.
1069  ERR_FS << "Invalid WML type '" << type << "' for binary paths\n";
1070  static std::vector<std::string> dummy;
1071  return dummy;
1072  }
1073 
1074  std::vector<std::string>& res = binary_paths_cache[type];
1075 
1077 
1078  for(const std::string &path : binary_paths)
1079  {
1080  res.push_back(get_user_data_dir() + "/" + path + type + "/");
1081 
1082  if(!game_config::path.empty()) {
1083  res.push_back(game_config::path + "/" + path + type + "/");
1084  }
1085  }
1086 
1087  // not found in "/type" directory, try main directory
1088  res.push_back(get_user_data_dir() + "/");
1089 
1090  if(!game_config::path.empty())
1091  res.push_back(game_config::path+"/");
1092 
1093  return res;
1094 }
1095 
1097 {
1098  // We define ".." as "remove everything before" this is needed becasue
1099  // on the one hand allowing ".." would be a security risk but
1100  // especialy for terrains the c++ engine puts a hardcoded "terrain/" before filename
1101  // and there would be no way to "escape" from "terrain/" otherwise. This is not the
1102  // best solution but we cannot remove it without another solution (subtypes maybe?).
1103 
1104  // using 'for' instead 'if' to allow putting delcaration and check into the brackets
1105  for(std::string::size_type pos = filename.rfind("../"); pos != std::string::npos;)
1106  return get_binary_file_location(type, filename.substr(pos + 3));
1107 
1108  if (!is_legal_file(filename))
1109  return std::string();
1110 
1111  for(const std::string &bp : get_binary_paths(type))
1112  {
1113  path bpath(bp);
1114  bpath /= filename;
1115  DBG_FS << " checking '" << bp << "'\n";
1116  if (file_exists(bpath)) {
1117  DBG_FS << " found at '" << bpath.string() << "'\n";
1118  return bpath.string();
1119  }
1120  }
1121 
1122  DBG_FS << " not found\n";
1123  return std::string();
1124 }
1125 
1127 {
1128  if (!is_legal_file(filename))
1129  return std::string();
1130 
1131  for (const std::string &bp : get_binary_paths(type))
1132  {
1133  path bpath(bp);
1134  bpath /= filename;
1135  DBG_FS << " checking '" << bp << "'\n";
1136  if (is_directory_internal(bpath)) {
1137  DBG_FS << " found at '" << bpath.string() << "'\n";
1138  return bpath.string();
1139  }
1140  }
1141 
1142  DBG_FS << " not found\n";
1143  return std::string();
1144 }
1145 
1146 std::string get_wml_location(const std::string &filename, const std::string &current_dir)
1147 {
1148  if (!is_legal_file(filename))
1149  return std::string();
1150 
1151  assert(game_config::path.empty() == false);
1152 
1153  path fpath(filename);
1154  path result;
1155 
1156  if (filename[0] == '~')
1157  {
1158  result /= get_user_data_path() / "data" / filename.substr(1);
1159  DBG_FS << " trying '" << result.string() << "'\n";
1160  } else if (*fpath.begin() == ".") {
1161  if (!current_dir.empty()) {
1162  result /= path(current_dir);
1163  } else {
1164  result /= path(game_config::path) / "data";
1165  }
1166 
1167  result /= filename;
1168  } else if (!game_config::path.empty()) {
1169  result /= path(game_config::path) / "data" / filename;
1170  }
1171  if (result.empty() || !file_exists(result)) {
1172  DBG_FS << " not found\n";
1173  result.clear();
1174  } else
1175  DBG_FS << " found: '" << result.string() << "'\n";
1176 
1177  return result.string();
1178 }
1179 
1180 static path subtract_path(const path &full, const path &prefix)
1181 {
1182  path::iterator fi = full.begin()
1183  , fe = full.end()
1184  , pi = prefix.begin()
1185  , pe = prefix.end();
1186  while (fi != fe && pi != pe && *fi == *pi) {
1187  ++fi;
1188  ++pi;
1189  }
1190  path rest;
1191  if (pi == pe)
1192  while (fi != fe) {
1193  rest /= *fi;
1194  ++fi;
1195  }
1196  return rest;
1197 }
1198 
1200 {
1201  path full_path(filename);
1202 
1203  path partial = subtract_path(full_path, get_user_data_path() / "data");
1204  if (!partial.empty())
1205  return "~" + partial.string();
1206 
1207  partial = subtract_path(full_path, path(game_config::path) / "data");
1208  if (!partial.empty())
1209  return partial.string();
1210 
1211  return filename;
1212 }
1213 
1215 {
1216  path full_path(get_binary_file_location("images", filename));
1217 
1218  if (full_path.empty())
1219  return full_path.string();
1220 
1221  path partial = subtract_path(full_path, get_user_data_path());
1222  if (!partial.empty())
1223  return partial.string();
1224 
1225  partial = subtract_path(full_path, game_config::path);
1226  if (!partial.empty())
1227  return partial.string();
1228 
1229  return full_path.string();
1230 }
1231 
1233 {
1234  const std::string real_program_name(program_name
1235 #ifdef DEBUG
1236  + "-debug"
1237 #endif
1238 #ifdef _WIN32
1239  + ".exe"
1240 #endif
1241  );
1242  return (path(game_config::wesnoth_program_dir) / real_program_name).string();
1243 }
1244 
1245 }
child_itors child_range(const std::string &key)
Definition: config.cpp:613
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...
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...
GLuint counter
Definition: glew.h:2584
void set_user_data_dir(std::string path)
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...
static bool is_directory_internal(const path &fpath)
bool delete_file(const std::string &filename)
bool looks_like_pbl(const std::string &file)
const GLfloat * c
Definition: glew.h:12741
int pos
Definition: formula.cpp:800
rng * generator
This generator is automatically synced during synced context.
Definition: random_new.cpp:52
GLuint GLuint GLsizei GLenum type
Definition: glew.h:1221
external_string_type external_file_string() const
static l_noret error(LoadState *S, const char *why)
Definition: lundump.cpp:29
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.
#define DBG_FS
static path cache_dir
static path subtract_path(const path &full, const path &prefix)
GLuint const GLfloat * val
Definition: glew.h:2614
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...
iostream_path(const stringtype &s)
GLint GLenum GLsizei GLint GLsizei const GLvoid * data
Definition: glew.h:1347
GLenum mode
Definition: glew.h:2390
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:1187
static bool is_legal_file(const std::string &filename)
GLsizei const char ** path
Definition: glew.h:4654
GLuint GLuint end
Definition: glew.h:1221
GLuint64EXT * result
Definition: glew.h:10727
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.
std::string get_user_data_dir()
const GLuint GLenum const GLvoid * binary
Definition: glew.h:3027
std::string normalize_path(const std::string &path)
Returns the absolute path of a file.
void write_file(const std::string &fname, const std::string &data)
Throws io_exception if an error occurs.
std::string base_name(const std::string &file)
Returns the base filename of a file, with directory name stripped.
std::istream * istream_file(const std::string &fname, bool treat_failure_as_error=true)
std::ostream * ostream_file(std::string const &fname, bool create_directory=true)
static void set_user_config_path(path newconfig)
bool is_directory(const std::string &fname)
Returns true if the given file is a directory.
static const path & get_user_data_path()
GLenum GLuint GLsizei const char * buf
Definition: glew.h:2498
std::string path
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. ...
GLfloat GLfloat p
Definition: glew.h:12766
Templates and utility-routines for strings and numbers.
std::string get_dir(const std::string &dir)
std::string read_file(const std::string &fname)
Basic disk I/O - read file.
GLuint GLuint GLsizei count
Definition: glew.h:1221
#define LOG_FS
lu_byte right
Definition: lparser.cpp:1020
bool is_path_sep(char c)
Returns whether c is a path separator.
static path user_config_dir
static void push_if_exists(std::vector< std::string > *vec, const path &file, bool full)
bool is_gzip_file(const std::string &filename)
Returns true if the file ends with '.gz'.
std::string get_cache_dir()
GLuint res
Definition: glew.h:9258
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()
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.
std::map< std::string, tfilter >::iterator itor
Definition: filter.cpp:199
int dir_size(const std::string &path)
Returns the sum of the sizes of the files contained in a directory.
void set_user_config_dir(std::string path)
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:224
An exception object used when an IO error occurs.
Definition: filesystem.hpp:40
unsigned int minor_version() const
Retrieves the minor version number (x2 in "x1.x2.x3").
Definition: version.cpp:130
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:126
size_t i
Definition: function.cpp:1057
Declarations for File-IO.
void read(config &cfg, std::istream &in, abstract_validator *validator)
Definition: parser.cpp:400
static int writer(lua_State *L, const void *b, size_t size, void *B)
Definition: lstrlib.cpp:166
static int sort(lua_State *L)
Definition: ltablib.cpp:246
#define ERR_FS
const version_info wesnoth_version(VERSION)
bool delete_directory(const std::string &dirname, const bool keep_pbl=false)
int file_size(const std::string &fname)
Returns the size of a file, or -1 if the file doesn't exist.
GLuint const GLchar * name
Definition: glew.h:1782
std::string get_user_config_dir()
GLsizeiptr size
Definition: glew.h:1649
GLint GLint GLint GLint GLint GLint GLint GLbitfield GLenum filter
Definition: glew.h:3448
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)
GLenum pname
Definition: glew.h:1656
Standard logging facilities (interface).
const std::string remove
remove directive
#define e
static lg::log_domain log_filesystem("filesystem")
std::string::const_iterator iterator
Definition: tokenizer.hpp:21
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:83
GLdouble s
Definition: glew.h:1358
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.
void write(std::ostream &out, configr_of const &cfg, unsigned int level)
Definition: parser.cpp:621
GLsizei const GLcharARB ** string
Definition: glew.h:4503
std::string directory_name(const std::string &file)
Returns the directory name of a file, with filename stripped.
bfs::path::string_type external_string_type
std::string wesnoth_program_dir
Definition: game_config.cpp:61