1 /*
2    Copyright (C) 2003 - 2018 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  * Declarations for File-IO.
18  */
19 
20 #pragma once
21 
22 #include <algorithm>
23 #include <ctime>
24 #include <functional>
25 #include <iosfwd>
26 #include <string>
27 #include <vector>
28 #include <memory>
29 
30 #include "exceptions.hpp"
31 #include "serialization/string_utils.hpp"
32 
33 class config;
34 
35 struct SDL_RWops;
36 
37 namespace filesystem {
38 
39 using scoped_istream = std::unique_ptr<std::istream>;
40 using scoped_ostream = std::unique_ptr<std::ostream>;
41 
42 typedef std::unique_ptr<SDL_RWops, void(*)(SDL_RWops*)> rwops_ptr;
43 
44 rwops_ptr make_read_RWops(const std::string &path);
45 rwops_ptr make_write_RWops(const std::string &path);
46 
47 /** An exception object used when an IO error occurs */
48 struct io_exception : public game::error {
io_exceptionfilesystem::io_exception49 	io_exception() : game::error("") {}
io_exceptionfilesystem::io_exception50 	io_exception(const std::string& msg) : game::error(msg) {}
51 };
52 
53 struct file_tree_checksum;
54 
55 enum file_name_option { ENTIRE_FILE_PATH, FILE_NAME_ONLY };
56 enum file_filter_option { NO_FILTER, SKIP_MEDIA_DIR, SKIP_PBL_FILES };
57 enum file_reorder_option { DONT_REORDER, DO_REORDER };
58 
59 // A list of file and directory blacklist patterns
60 class blacklist_pattern_list
61 {
62 public:
blacklist_pattern_list()63 	blacklist_pattern_list()
64 		: file_patterns_(), directory_patterns_()
65 	{}
blacklist_pattern_list(const std::vector<std::string> & file_patterns,const std::vector<std::string> & directory_patterns)66 	blacklist_pattern_list(const std::vector<std::string>& file_patterns, const std::vector<std::string>& directory_patterns)
67 		: file_patterns_(file_patterns), directory_patterns_(directory_patterns)
68 	{}
69 
match_file(const std::string & name) const70 	bool match_file(const std::string& name) const
71 	{
72 		return std::any_of(file_patterns_.begin(), file_patterns_.end(),
73 			std::bind(&utils::wildcard_string_match, std::ref(name), std::placeholders::_1));
74 	}
75 
match_dir(const std::string & name) const76 	bool match_dir(const std::string& name) const
77 	{
78 		return std::any_of(directory_patterns_.begin(), directory_patterns_.end(),
79 			std::bind(&utils::wildcard_string_match, std::ref(name), std::placeholders::_1));
80 	}
81 
add_file_pattern(const std::string & pattern)82 	void add_file_pattern(const std::string& pattern)
83 	{
84 		file_patterns_.push_back(pattern);
85 	}
86 
add_directory_pattern(const std::string & pattern)87 	void add_directory_pattern(const std::string& pattern)
88 	{
89 		directory_patterns_.push_back(pattern);
90 	}
91 
92 	void remove_blacklisted_files_and_dirs(std::vector<std::string>& files, std::vector<std::string>& directories) const;
93 
94 private:
95 	std::vector<std::string> file_patterns_;
96 	std::vector<std::string> directory_patterns_;
97 };
98 
99 static const blacklist_pattern_list default_blacklist{
100 	{
101 		/* Blacklist dot-files/dirs, which are hidden files in UNIX platforms */
102 		".+",
103 		"#*#",
104 		"*~",
105 		"*-bak",
106 		"*.swp",
107 		"*.pbl",
108 		"*.ign",
109 		"_info.cfg",
110 		"*.exe",
111 		"*.bat",
112 		"*.cmd",
113 		"*.com",
114 		"*.scr",
115 		"*.sh",
116 		"*.js",
117 		"*.vbs",
118 		"*.o",
119 		"*.ini",
120 		/* Remove junk created by certain file manager ;) */
121 		"Thumbs.db",
122 		/* Eclipse plugin */
123 		"*.wesnoth",
124 		"*.project",
125 	},
126 	{
127 		".+",
128 		/* macOS metadata-like cruft (http://floatingsun.net/2007/02/07/whats-with-__macosx-in-zip-files/) */
129 		"__MACOSX",
130 	}
131 };
132 
133 /** Some tasks to run on startup. */
134 void init();
135 
136 /**
137  * Populates 'files' with all the files and
138  * 'dirs' with all the directories in dir.
139  * If files or dirs are nullptr they will not be used.
140  *
141  * mode: determines whether the entire path or just the filename is retrieved.
142  * filter: determines if we skip images and sounds directories
143  * reorder: triggers the special handling of _main.cfg and _final.cfg
144  * checksum: can be used to store checksum info
145  */
146 void get_files_in_dir(const std::string &dir,
147                       std::vector<std::string>* files,
148                       std::vector<std::string>* dirs=nullptr,
149                       file_name_option mode = FILE_NAME_ONLY,
150                       file_filter_option filter = NO_FILTER,
151                       file_reorder_option reorder = DONT_REORDER,
152                       file_tree_checksum* checksum = nullptr);
153 
154 std::string get_dir(const std::string &dir);
155 
156 // The location of various important files:
157 std::string get_prefs_file();
158 std::string get_credentials_file();
159 std::string get_default_prefs_file();
160 std::string get_save_index_file();
161 std::string get_saves_dir();
162 std::string get_intl_dir();
163 std::string get_screenshot_dir();
164 std::string get_addons_dir();
165 
166 /**
167  * Get the next free filename using "name + number (3 digits) + extension"
168  * maximum 1000 files then start always giving 999
169  */
170 std::string get_next_filename(const std::string& name, const std::string& extension);
171 void set_user_config_dir(const std::string& path);
172 void set_user_data_dir(std::string path);
173 
174 std::string get_user_config_dir();
175 std::string get_user_data_dir();
176 std::string get_cache_dir();
177 
178 std::string get_cwd();
179 std::string get_exe_dir();
180 
181 bool make_directory(const std::string& dirname);
182 bool delete_directory(const std::string& dirname, const bool keep_pbl = false);
183 bool delete_file(const std::string &filename);
184 
185 bool looks_like_pbl(const std::string& file);
186 
187 // Basic disk I/O:
188 
189 /** Basic disk I/O - read file. */
190 std::string read_file(const std::string &fname);
191 filesystem::scoped_istream istream_file(const std::string& fname, bool treat_failure_as_error = true);
192 filesystem::scoped_ostream ostream_file(const std::string& fname, bool create_directory = true);
193 /** Throws io_exception if an error occurs. */
194 void write_file(const std::string& fname, const std::string& data);
195 
196 std::string read_map(const std::string& name);
197 
198 /**
199  * Creates a directory if it does not exist already.
200  *
201  * @param dirname                 Path to directory. All parents should exist.
202  * @returns                       True if the directory exists or could be
203  *                                successfully created; false otherwise.
204  */
205 bool create_directory_if_missing(const std::string& dirname);
206 /**
207  * Creates a recursive directory tree if it does not exist already
208  * @param dirname                 Full path of target directory. Non existing parents
209  *                                will be created
210  * @return                        True if the directory exists or could be
211  *                                successfully created; false otherwise.
212  */
213 bool create_directory_if_missing_recursive(const std::string& dirname);
214 
215 /** Returns true if the given file is a directory. */
216 bool is_directory(const std::string& fname);
217 
218 /** Returns true if a file or directory with such name already exists. */
219 bool file_exists(const std::string& name);
220 
221 /** Get the modification time of a file. */
222 time_t file_modified_time(const std::string& fname);
223 
224 /** Returns true if the file ends with '.gz'. */
225 bool is_gzip_file(const std::string& filename);
226 
227 /** Returns true if the file ends with '.bz2'. */
228 bool is_bzip2_file(const std::string& filename);
229 
is_compressed_file(const std::string & filename)230 inline bool is_compressed_file(const std::string& filename) {
231 	return is_gzip_file(filename) || is_bzip2_file(filename);
232 }
233 
234 struct file_tree_checksum
235 {
236 	file_tree_checksum();
237 	explicit file_tree_checksum(const config& cfg);
238 	void write(config& cfg) const;
resetfilesystem::file_tree_checksum239 	void reset() {nfiles = 0;modified = 0;sum_size=0;}
240 	// @todo make variables private!
241 	size_t nfiles, sum_size;
242 	time_t modified;
243 	bool operator==(const file_tree_checksum &rhs) const;
operator !=filesystem::file_tree_checksum244 	bool operator!=(const file_tree_checksum &rhs) const
245 	{ return !operator==(rhs); }
246 };
247 
248 /** Get the time at which the data/ tree was last modified at. */
249 const file_tree_checksum& data_tree_checksum(bool reset = false);
250 
251 /** Returns the size of a file, or -1 if the file doesn't exist. */
252 int file_size(const std::string& fname);
253 
254 /** Returns the sum of the sizes of the files contained in a directory. */
255 int dir_size(const std::string& path);
256 
257 bool ends_with(const std::string& str, const std::string& suffix);
258 
259 /**
260  * Returns the base filename of a file, with directory name stripped.
261  * Equivalent to a portable basename() function.
262  *
263  * If @a remove_extension is true, the filename extension will be stripped
264  * from the returned value.
265  */
266 std::string base_name(const std::string& file, const bool remove_extension = false);
267 
268 /**
269  * Returns the directory name of a file, with filename stripped.
270  * Equivalent to a portable dirname()
271  */
272 std::string directory_name(const std::string& file);
273 
274 /**
275  * Finds the nearest parent in existence for a file or directory.
276  *
277  * @note    The file's own existence is not checked.
278  *
279  * @returns An absolute path to the closest parent of the given path, or an
280  *          empty string if none could be found. While on POSIX platforms this
281  *          cannot happen (unless the original path was already empty), on
282  *          Windows it might be the case that the original path refers to a
283  *          drive letter or network share that does not exist.
284  */
285 std::string nearest_extant_parent(const std::string& file);
286 
287 /**
288  * Returns the absolute path of a file.
289  *
290  * @param path                 Original path.
291  * @param normalize_separators Whether to substitute path separators with the
292  *                             platform's preferred format.
293  * @param resolve_dot_entries  Whether to resolve . and .. directory entries.
294  *                             This requires @a path to refer to a valid
295  *                             existing object.
296  *
297  * @returns An absolute path -- that is, a path that is independent of the
298  *          current working directory for the process. If resolve_dot_entries
299  *          is set to true, the returned path has . and .. components resolved;
300  *          however, if resolution fails because a component does not exist, an
301  *          empty string is returned instead.
302  */
303 std::string normalize_path(const std::string& path,
304 						   bool normalize_separators = false,
305 						   bool resolve_dot_entries = false);
306 
307 /**
308  * Sanitizes a path to remove references to the user's name.
309  */
310 std::string sanitize_path(const std::string& path);
311 
312 /**
313  * Returns whether the path is the root of the file hierarchy.
314  *
315  * @note This function is unreliable for paths that do not exist -- it will
316  *       always return @a false for those.
317  */
318 bool is_root(const std::string& path);
319 
320 /**
321  * Returns the name of the root device if included in the given path.
322  *
323  * This only properly makes sense on Windows with paths containing a drive
324  * letter or UNC at the start -- otherwise, it will return the empty string. To
325  * ensure that a suitable root name can be found you might want to use
326  * normalize_path() first with @a resolve_dot_entries set to true.
327  */
328 std::string root_name(const std::string& path);
329 
330 /**
331  * Returns whether the path seems to be relative.
332  */
333 bool is_relative(const std::string& path);
334 
335 /**
336  * Returns whether @a c is a path separator.
337  *
338  * @note / is always a path separator. Additionally, on Windows \\ is a
339  *       path separator as well.
340  */
341 bool is_path_sep(char c);
342 
343 /**
344  * Returns the standard path separator for the current platform.
345  */
346 char path_separator();
347 
348 /**
349  *  The paths manager is responsible for recording the various paths
350  *  that binary files may be located at.
351  *  It should be passed a config object which holds binary path information.
352  *  This is in the format
353  *@verbatim
354  *    [binary_path]
355  *      path=<path>
356  *    [/binary_path]
357  *  Binaries will be searched for in [wesnoth-path]/data/<path>/images/
358  *@endverbatim
359  */
360 struct binary_paths_manager
361 {
362 	binary_paths_manager();
363 	binary_paths_manager(const config& cfg);
364 	~binary_paths_manager();
365 
366 	void set_paths(const config& cfg);
367 
368 private:
369 	binary_paths_manager(const binary_paths_manager& o);
370 	binary_paths_manager& operator=(const binary_paths_manager& o);
371 
372 	void cleanup();
373 
374 	std::vector<std::string> paths_;
375 };
376 
377 void clear_binary_paths_cache();
378 
379 /**
380  * Returns a vector with all possible paths to a given type of binary,
381  * e.g. 'images', 'sounds', etc,
382  */
383 const std::vector<std::string>& get_binary_paths(const std::string& type);
384 
385 /**
386  * Returns a complete path to the actual file of a given @a type
387  * or an empty string if the file isn't present.
388  */
389 std::string get_binary_file_location(const std::string& type, const std::string& filename);
390 
391 /**
392  * Returns a complete path to the actual directory of a given @a type
393  * or an empty string if the directory isn't present.
394  */
395 std::string get_binary_dir_location(const std::string &type, const std::string &filename);
396 
397 /**
398  * Returns a complete path to the actual WML file or directory
399  * or an empty string if the file isn't present.
400  */
401 std::string get_wml_location(const std::string &filename,
402 	const std::string &current_dir = std::string());
403 
404 /**
405  * Returns a short path to @a filename, skipping the (user) data directory.
406  */
407 std::string get_short_wml_path(const std::string &filename);
408 
409 /**
410  * Returns an image path to @a filename for binary path-independent use in saved games.
411  *
412  * Example:
413  *   units/konrad-fighter.png ->
414  *   data/campaigns/Heir_To_The_Throne/images/units/konrad-fighter.png
415  */
416 std::string get_independent_image_path(const std::string &filename);
417 
418 /**
419  * Returns the appropriate invocation for a Wesnoth-related binary, assuming
420  * that it is located in the same directory as the running wesnoth binary.
421  * This is just a string-transformation based on argv[0], so the returned
422  * program is not guaranteed to actually exist.  '-debug' variants are handled
423  * correctly.
424  */
425 std::string get_program_invocation(const std::string &program_name);
426 
427 
428 }
429