filesystem.cpp

Go to the documentation of this file.
00001 /* $Id: filesystem.cpp 54062 2012-05-01 20:18:57Z j_daniel $ */
00002 /*
00003    Copyright (C) 2003 - 2012 by David White <dave@whitevine.net>
00004    Part of the Battle for Wesnoth Project http://www.wesnoth.org/
00005 
00006    This program is free software; you can redistribute it and/or modify
00007    it under the terms of the GNU General Public License as published by
00008    the Free Software Foundation; either version 2 of the License, or
00009    (at your option) any later version.
00010    This program is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY.
00012 
00013    See the COPYING file for more details.
00014 */
00015 
00016 /**
00017  * @file
00018  * File-IO
00019  */
00020 
00021 #include "global.hpp"
00022 
00023 // Include files for opendir(3), readdir(3), etc.
00024 // These files may vary from platform to platform,
00025 // since these functions are NOT ANSI-conforming functions.
00026 // They may have to be altered to port to new platforms
00027 
00028 //for mkdir
00029 #include <sys/stat.h>
00030 
00031 #ifdef _WIN32
00032 #include "filesystem_win32.ii"
00033 #include <cctype>
00034 #else /* !_WIN32 */
00035 #include <unistd.h>
00036 #include <dirent.h>
00037 #include <libgen.h>
00038 #endif /* !_WIN32 */
00039 
00040 #ifdef __BEOS__
00041 #include <Directory.h>
00042 #include <FindDirectory.h>
00043 #include <Path.h>
00044 BPath be_path;
00045 #endif
00046 
00047 // for getenv
00048 #include <cerrno>
00049 #include <fstream>
00050 #include <iomanip>
00051 #include <set>
00052 #include <boost/algorithm/string.hpp>
00053 
00054 // for strerror
00055 #include <cstring>
00056 
00057 #include "config.hpp"
00058 #include "filesystem.hpp"
00059 #include "foreach.hpp"
00060 #include "game_config.hpp"
00061 #include "game_preferences.hpp"
00062 #include "log.hpp"
00063 #include "loadscreen.hpp"
00064 #include "scoped_resource.hpp"
00065 #include "serialization/string_utils.hpp"
00066 #include "version.hpp"
00067 
00068 static lg::log_domain log_filesystem("filesystem");
00069 #define DBG_FS LOG_STREAM(debug, log_filesystem)
00070 #define LOG_FS LOG_STREAM(info, log_filesystem)
00071 #define WRN_FS LOG_STREAM(warn, log_filesystem)
00072 #define ERR_FS LOG_STREAM(err, log_filesystem)
00073 
00074 namespace {
00075     const mode_t AccessMode = 00770;
00076 
00077     // These are the filenames that get special processing
00078     const std::string maincfg_filename = "_main.cfg";
00079     const std::string finalcfg_filename = "_final.cfg";
00080     const std::string initialcfg_filename = "_initial.cfg";
00081 
00082 }
00083 
00084 #ifdef __APPLE__
00085 #include <CoreFoundation/CoreFoundation.h>
00086 #include <CoreFoundation/CFString.h>
00087 #include <CoreFoundation/CFBase.h>
00088 #endif
00089 
00090 bool ends_with(const std::string& str, const std::string& suffix)
00091 {
00092     return str.size() >= suffix.size() && std::equal(suffix.begin(),suffix.end(),str.end()-suffix.size());
00093 }
00094 
00095 void get_files_in_dir(const std::string &directory,
00096                       std::vector<std::string>* files,
00097                       std::vector<std::string>* dirs,
00098                       file_name_option mode,
00099                       file_filter_option filter,
00100                       file_reorder_option reorder,
00101                       file_tree_checksum* checksum)
00102 {
00103     // If we have a path to find directories in,
00104     // then convert relative pathnames to be rooted
00105     // on the wesnoth path
00106 #ifndef __AMIGAOS4__
00107     if(!directory.empty() && directory[0] != '/' && !game_config::path.empty()){
00108         std::string dir = game_config::path + "/" + directory;
00109         if(is_directory(dir)) {
00110             get_files_in_dir(dir,files,dirs,mode,filter,reorder,checksum);
00111             return;
00112         }
00113     }
00114 #endif /* __AMIGAOS4__ */
00115 
00116     struct stat st;
00117 
00118     if (reorder == DO_REORDER) {
00119         LOG_FS << "searching for _main.cfg in directory " << directory << '\n';
00120         std::string maincfg;
00121         if (directory.empty() || directory[directory.size()-1] == '/'
00122 #ifdef __AMIGAOS4__
00123             || (directory[directory.size()-1]==':')
00124 #endif /* __AMIGAOS4__ */
00125         )
00126             maincfg = directory + maincfg_filename;
00127         else
00128             maincfg = (directory + "/") + maincfg_filename;
00129 
00130         if (::stat(maincfg.c_str(), &st) != -1) {
00131             LOG_FS << "_main.cfg found : " << maincfg << '\n';
00132             if (files != NULL) {
00133                 if (mode == ENTIRE_FILE_PATH)
00134                     files->push_back(maincfg);
00135                 else
00136                     files->push_back(maincfg_filename);
00137             }
00138             return;
00139         }
00140     }
00141 
00142     DIR* dir = opendir(directory.c_str());
00143 
00144     if(dir == NULL) {
00145         return;
00146     }
00147 
00148     struct dirent* entry;
00149     while((entry = readdir(dir)) != NULL) {
00150         if(entry->d_name[0] == '.')
00151             continue;
00152 #ifdef __APPLE__
00153         // HFS Mac OS X decomposes filenames using combining unicode characters.
00154         // Try to get the precomposed form.
00155         char macname[MAXNAMLEN+1];
00156         CFStringRef cstr = CFStringCreateWithCString(NULL,
00157                              entry->d_name,
00158                              kCFStringEncodingUTF8);
00159         CFMutableStringRef mut_str = CFStringCreateMutableCopy(NULL,
00160                              0, cstr);
00161         CFStringNormalize(mut_str, kCFStringNormalizationFormC);
00162         CFStringGetCString(mut_str,
00163                 macname,sizeof(macname)-1,
00164                 kCFStringEncodingUTF8);
00165         CFRelease(cstr);
00166         CFRelease(mut_str);
00167         const std::string basename = macname;
00168 #else
00169         // generic Unix
00170         const std::string basename = entry->d_name;
00171 #endif /* !APPLE */
00172 
00173         std::string fullname;
00174         if (directory.empty() || directory[directory.size()-1] == '/'
00175 #ifdef __AMIGAOS4__
00176             || (directory[directory.size()-1]==':')
00177 #endif /* __AMIGAOS4__ */
00178         )
00179             fullname = directory + basename;
00180         else
00181             fullname = directory + "/" + basename;
00182 
00183         if (::stat(fullname.c_str(), &st) != -1) {
00184             if (S_ISREG(st.st_mode)) {
00185                 if(filter == SKIP_PBL_FILES && looks_like_pbl(basename)) {
00186                     continue;
00187                 }
00188                 if (files != NULL) {
00189                     if (mode == ENTIRE_FILE_PATH)
00190                         files->push_back(fullname);
00191                     else
00192                         files->push_back(basename);
00193                 }
00194                 if (checksum != NULL) {
00195                     if(st.st_mtime > checksum->modified) {
00196                         checksum->modified = st.st_mtime;
00197                     }
00198                     checksum->sum_size += st.st_size;
00199                     checksum->nfiles++;
00200                 }
00201             } else if (S_ISDIR(st.st_mode)) {
00202                 if (filter == SKIP_MEDIA_DIR
00203                         && (basename == "images"|| basename == "sounds"))
00204                     continue;
00205 
00206                 if (reorder == DO_REORDER &&
00207                         ::stat((fullname+"/"+maincfg_filename).c_str(), &st)!=-1 &&
00208                         S_ISREG(st.st_mode)) {
00209                     LOG_FS << "_main.cfg found : ";
00210                     if (files != NULL) {
00211                         if (mode == ENTIRE_FILE_PATH) {
00212                             files->push_back(fullname + "/" + maincfg_filename);
00213                             LOG_FS << fullname << "/" << maincfg_filename << '\n';
00214                         } else {
00215                             files->push_back(basename + "/" + maincfg_filename);
00216                             LOG_FS << basename << "/" << maincfg_filename << '\n';
00217                     }
00218                     } else {
00219                     // Show what I consider strange
00220                         LOG_FS << fullname << "/" << maincfg_filename << " not used now but skip the directory \n";
00221                     }
00222                 } else if (dirs != NULL) {
00223                     if (mode == ENTIRE_FILE_PATH)
00224                         dirs->push_back(fullname);
00225                     else
00226                         dirs->push_back(basename);
00227                 }
00228             }
00229         }
00230     }
00231 
00232     closedir(dir);
00233 
00234     if(files != NULL)
00235         std::sort(files->begin(),files->end());
00236 
00237     if (dirs != NULL)
00238         std::sort(dirs->begin(),dirs->end());
00239 
00240     if (files != NULL && reorder == DO_REORDER) {
00241         // move finalcfg_filename, if present, to the end of the vector
00242         for (unsigned int i = 0; i < files->size(); i++) {
00243             if (ends_with((*files)[i], "/" + finalcfg_filename)) {
00244                 files->push_back((*files)[i]);
00245                 files->erase(files->begin()+i);
00246                 break;
00247             }
00248         }
00249         // move initialcfg_filename, if present, to the beginning of the vector
00250         int foundit = -1;
00251         for (unsigned int i = 0; i < files->size(); i++)
00252             if (ends_with((*files)[i], "/" + initialcfg_filename)) {
00253                 foundit = i;
00254                 break;
00255             }
00256         if (foundit > 0) {
00257             std::string initialcfg = (*files)[foundit];
00258             for (unsigned int i = foundit; i > 0; i--)
00259                 (*files)[i] = (*files)[i-1];
00260             (*files)[0] = initialcfg;
00261         }
00262     }
00263 }
00264 
00265 #ifdef __native_client__
00266 // For performance reasons, on NaCl we only keep preferences and saves in persistent storage.
00267 std::string get_prefs_file()
00268 {
00269     return "/wesnoth-userdata/preferences";
00270 }
00271 
00272 std::string get_save_index_file()
00273 {
00274     return "/wesnoth-userdata/save_index";
00275 }
00276 
00277 std::string get_saves_dir()
00278 {
00279     const std::string dir_path = "/wesnoth-userdata/saves";
00280     return get_dir(dir_path);
00281 }
00282 
00283 #else
00284 
00285 std::string get_prefs_file()
00286 {
00287     return get_user_config_dir() + "/preferences";
00288 }
00289 
00290 std::string get_default_prefs_file()
00291 {
00292 #ifdef HAS_RELATIVE_DEFPREF
00293     return game_config::path + "/" + game_config::default_preferences_path;
00294 #else
00295     return game_config::default_preferences_path;
00296 #endif
00297 }
00298 
00299 std::string get_save_index_file()
00300 {
00301     return get_user_data_dir() + "/save_index";
00302 }
00303 
00304 std::string get_saves_dir()
00305 {
00306     const std::string dir_path = get_user_data_dir() + "/saves";
00307     return get_dir(dir_path);
00308 }
00309 #endif
00310 
00311 std::string get_addon_campaigns_dir()
00312 {
00313     const std::string dir_path = get_user_data_dir() + "/data/add-ons";
00314     return get_dir(dir_path);
00315 }
00316 
00317 std::string get_intl_dir()
00318 {
00319 #ifdef _WIN32
00320     return get_cwd() + "/translations";
00321 #else
00322 
00323 #ifdef USE_INTERNAL_DATA
00324     return get_cwd() + "/" LOCALEDIR;
00325 #endif
00326 
00327 #if HAS_RELATIVE_LOCALEDIR
00328     std::string res = game_config::path + "/" LOCALEDIR;
00329 #else
00330     std::string res = LOCALEDIR;
00331 #endif
00332 
00333     return res;
00334 #endif
00335 }
00336 
00337 std::string get_screenshot_dir()
00338 {
00339     const std::string dir_path = get_user_data_dir() + "/screenshots";
00340     return get_dir(dir_path);
00341 }
00342 
00343 std::string get_next_filename(const std::string& name, const std::string& extension)
00344 {
00345     std::string next_filename;
00346     int counter = 0;
00347 
00348     do {
00349         std::stringstream filename;
00350 
00351         filename << name;
00352         filename.width(3);
00353         filename.fill('0');
00354         filename.setf(std::ios_base::right);
00355         filename << counter << extension;
00356         counter++;
00357         next_filename = filename.str();
00358     } while(file_exists(next_filename) && counter < 1000);
00359     return next_filename;
00360 }
00361 
00362 
00363 std::string get_dir(const std::string& dir_path)
00364 {
00365     DIR* dir = opendir(dir_path.c_str());
00366     if(dir == NULL) {
00367         const int res = mkdir(dir_path.c_str(),AccessMode);
00368         if(res == 0) {
00369             dir = opendir(dir_path.c_str());
00370         } else {
00371             ERR_FS << "could not open or create directory: " << dir_path << '\n';
00372         }
00373     }
00374 
00375     if(dir == NULL)
00376         return "";
00377 
00378     closedir(dir);
00379 
00380     return dir_path;
00381 }
00382 
00383 bool make_directory(const std::string& path)
00384 {
00385     return (mkdir(path.c_str(),AccessMode) == 0);
00386 }
00387 
00388 bool looks_like_pbl(const std::string& file)
00389 {
00390     return utils::wildcard_string_match(utils::lowercase(file), "*.pbl");
00391 }
00392 
00393 // This deletes a directory with no hidden files and subdirectories.
00394 // Also deletes a single file.
00395 bool delete_directory(const std::string& path, const bool keep_pbl)
00396 {
00397     bool ret = true;
00398     std::vector<std::string> files;
00399     std::vector<std::string> dirs;
00400 
00401     get_files_in_dir(path, &files, &dirs, ENTIRE_FILE_PATH, keep_pbl ? SKIP_PBL_FILES : NO_FILTER);
00402 
00403     if(!files.empty()) {
00404         for(std::vector<std::string>::const_iterator i = files.begin(); i != files.end(); ++i) {
00405             errno = 0;
00406             if(remove((*i).c_str()) != 0) {
00407                 LOG_FS << "remove(" << (*i) << "): " << strerror(errno) << "\n";
00408                 ret = false;
00409             }
00410         }
00411     }
00412 
00413     if(!dirs.empty()) {
00414         for(std::vector<std::string>::const_iterator j = dirs.begin(); j != dirs.end(); ++j) {
00415             if(!delete_directory(*j))
00416                 ret = false;
00417         }
00418     }
00419 
00420     errno = 0;
00421 #ifdef _WIN32
00422     // remove() doesn't delete directories on windows.
00423     int (*remove)(const char*);
00424     if(is_directory(path))
00425         remove = rmdir;
00426     else
00427         remove = ::remove;
00428 #endif
00429     if(remove(path.c_str()) != 0) {
00430         LOG_FS << "remove(" << path << "): " << strerror(errno) << "\n";
00431         ret = false;
00432     }
00433     return ret;
00434 }
00435 
00436 std::string get_cwd()
00437 {
00438     char buf[1024];
00439     const char* const res = getcwd(buf,sizeof(buf));
00440     if(res != NULL) {
00441         std::string str(res);
00442 
00443 #ifdef _WIN32
00444         std::replace(str.begin(),str.end(),'\\','/');
00445 #endif
00446 
00447         return str;
00448     } else {
00449         return "";
00450     }
00451 }
00452 
00453 std::string get_exe_dir()
00454 {
00455 #ifndef _WIN32
00456     char buf[1024];
00457     size_t path_size = readlink("/proc/self/exe", buf, 1024);
00458     if(path_size == static_cast<size_t>(-1))
00459         return std::string();
00460     buf[path_size] = 0;
00461     return std::string(dirname(buf));
00462 #else
00463     return get_cwd();
00464 #endif
00465 }
00466 
00467 bool create_directory_if_missing(const std::string& dirname)
00468 {
00469     if(is_directory(dirname)) {
00470         DBG_FS << "directory " << dirname << " exists, not creating\n";
00471         return true;
00472     } else if(file_exists(dirname)) {
00473         ERR_FS << "cannot create directory " << dirname << "; file exists\n";
00474         return false;
00475     }
00476     DBG_FS << "creating missing directory " << dirname << '\n';
00477     return make_directory(dirname);
00478 }
00479 bool create_directory_if_missing_recursive(const std::string& dirname)
00480 {
00481     DBG_FS<<"creating recursive directory: "<<dirname<<'\n';
00482     if (is_directory(dirname) == false && dirname.empty() == false)
00483     {
00484         std::string tmp_dirname = dirname;
00485         // remove trailing slashes or backslashes
00486         while ((tmp_dirname[tmp_dirname.size()-1] == '/' ||
00487               tmp_dirname[tmp_dirname.size()-1] == '\\') &&
00488               !tmp_dirname.empty())
00489         {
00490             tmp_dirname.erase(tmp_dirname.size()-1);
00491         }
00492 
00493         // create the first non-existing directory
00494         size_t pos = tmp_dirname.rfind("/");
00495 
00496         // we get the most right directory and *skip* it
00497         // we are creating it when we get back here
00498         if (tmp_dirname.rfind('\\') != std::string::npos &&
00499             tmp_dirname.rfind('\\') > pos )
00500             pos = tmp_dirname.rfind('\\');
00501 
00502         if (pos != std::string::npos)
00503             create_directory_if_missing_recursive(tmp_dirname.substr(0,pos));
00504 
00505         return create_directory_if_missing(tmp_dirname);
00506     }
00507     return create_directory_if_missing(dirname);
00508 }
00509 
00510 static std::string user_data_dir, user_config_dir, cache_dir;
00511 
00512 static void setup_user_data_dir();
00513 
00514 #ifndef _WIN32
00515 static const std::string& get_version_path_suffix()
00516 {
00517     static std::string suffix;
00518 
00519     // We only really need to generate this once since
00520     // the version number cannot change during runtime.
00521 
00522     if(suffix.empty()) {
00523         std::ostringstream s;
00524         s << game_config::wesnoth_version.major_version() << '.'
00525           << game_config::wesnoth_version.minor_version();
00526         suffix = s.str();
00527     }
00528 
00529     return suffix;
00530 }
00531 #endif
00532 
00533 void set_preferences_dir(std::string path)
00534 {
00535 #ifdef _WIN32
00536     if(path.empty()) {
00537         game_config::preferences_dir = get_cwd() + "/userdata";
00538     } else if (path.size() > 2 && path[1] == ':') {
00539         //allow absolute path override
00540         game_config::preferences_dir = path;
00541     } else {
00542         BOOL (*SHGetSpecialFolderPath)(HWND, LPTSTR, int, BOOL);
00543         HMODULE module = LoadLibrary("shell32");
00544         SHGetSpecialFolderPath = (BOOL (*)(HWND, LPTSTR, int, BOOL))GetProcAddress(module, "SHGetSpecialFolderPathA");
00545         if(SHGetSpecialFolderPath) {
00546             LOG_FS << "Using SHGetSpecialFolderPath to find My Documents\n";
00547             char my_documents_path[MAX_PATH];
00548             if(SHGetSpecialFolderPath(NULL, my_documents_path, 5, 1)) {
00549                 std::string mygames_path = std::string(my_documents_path) + "/" + "My Games";
00550                 boost::algorithm::replace_all(mygames_path, std::string("\\"), std::string("/"));
00551                 create_directory_if_missing(mygames_path);
00552                 game_config::preferences_dir = mygames_path + "/" + path;
00553             } else {
00554                 WRN_FS << "SHGetSpecialFolderPath failed\n";
00555                 game_config::preferences_dir = get_cwd() + "/" + path;
00556             }
00557         } else {
00558             LOG_FS << "Failed to load SHGetSpecialFolderPath function\n";
00559             game_config::preferences_dir = get_cwd() + "/" + path;
00560         }
00561     }
00562 
00563 #else /*_WIN32*/
00564 
00565 #ifdef PREFERENCES_DIR
00566     if (path.empty()) path = PREFERENCES_DIR;
00567 #endif
00568 
00569     std::string path2 = ".wesnoth" + get_version_path_suffix();
00570 
00571 #ifdef _X11
00572     const char *home_str = getenv("HOME");
00573 
00574     if (path.empty()) {
00575         char const *xdg_data = getenv("XDG_DATA_HOME");
00576         if (!xdg_data || xdg_data[0] == '\0') {
00577             if (!home_str) {
00578                 path = path2;
00579                 goto other;
00580             }
00581             user_data_dir = home_str;
00582             user_data_dir += "/.local/share";
00583         } else user_data_dir = xdg_data;
00584         user_data_dir += "/wesnoth/";
00585         user_data_dir += get_version_path_suffix();
00586         create_directory_if_missing_recursive(user_data_dir);
00587         game_config::preferences_dir = user_data_dir;
00588     } else {
00589         other:
00590         std::string home = home_str ? home_str : ".";
00591 
00592         if (path[0] == '/')
00593             game_config::preferences_dir = path;
00594         else
00595             game_config::preferences_dir = home + "/" + path;
00596     }
00597 #else
00598     if (path.empty()) path = path2;
00599 
00600 #ifdef __AMIGAOS4__
00601     game_config::preferences_dir = "PROGDIR:" + path;
00602 #elif defined(__BEOS__)
00603     if (be_path.InitCheck() != B_OK) {
00604         BPath tpath;
00605         if (find_directory(B_USER_SETTINGS_DIRECTORY, &be_path, true) == B_OK) {
00606             be_path.Append("wesnoth");
00607         } else {
00608             be_path.SetTo("/boot/home/config/settings/wesnoth");
00609         }
00610         game_config::preferences_dir = be_path.Path();
00611     }
00612 #else
00613     const char* home_str = getenv("HOME");
00614     std::string home = home_str ? home_str : ".";
00615 
00616     if (path[0] == '/')
00617         game_config::preferences_dir = path;
00618     else
00619         game_config::preferences_dir = home + std::string("/") + path;
00620 #endif
00621 #endif
00622 
00623 #endif /*_WIN32*/
00624     user_data_dir = game_config::preferences_dir;
00625     setup_user_data_dir();
00626 }
00627 
00628 static void setup_user_data_dir()
00629 {
00630 #ifdef _WIN32
00631     _mkdir(user_data_dir.c_str());
00632     _mkdir((user_data_dir + "/editor").c_str());
00633     _mkdir((user_data_dir + "/editor/maps").c_str());
00634     _mkdir((user_data_dir + "/data").c_str());
00635     _mkdir((user_data_dir + "/data/add-ons").c_str());
00636     _mkdir((user_data_dir + "/saves").c_str());
00637     _mkdir((user_data_dir + "/persist").c_str());
00638 #elif defined(__BEOS__)
00639     BPath tpath;
00640     #define BEOS_CREATE_PREFERENCES_SUBDIR(subdir) \
00641         tpath = be_path;                       \
00642         tpath.Append(subdir);                  \
00643         create_directory(tpath.Path(), 0775);
00644 
00645     BEOS_CREATE_PREFERENCES_SUBDIR("editor");
00646     BEOS_CREATE_PREFERENCES_SUBDIR("editor/maps");
00647     BEOS_CREATE_PREFERENCES_SUBDIR("data");
00648     BEOS_CREATE_PREFERENCES_SUBDIR("data/add-ons");
00649     BEOS_CREATE_PREFERENCES_SUBDIR("saves");
00650     BEOS_CREATE_PREFERENCES_SUBDIR("persist");
00651     #undef BEOS_CREATE_PREFERENCES_SUBDIR
00652 #else
00653     const std::string& dir_path = user_data_dir;
00654 
00655     const bool res = create_directory_if_missing(dir_path);
00656     // probe read permissions (if we could make the directory)
00657     DIR* const dir = res ? opendir(dir_path.c_str()) : NULL;
00658     if(dir == NULL) {
00659         ERR_FS << "could not open or create preferences directory at " << dir_path << '\n';
00660         return;
00661     }
00662     closedir(dir);
00663 
00664     // Create user data and add-on directories
00665     create_directory_if_missing(dir_path + "/editor");
00666     create_directory_if_missing(dir_path + "/editor/maps");
00667     create_directory_if_missing(dir_path + "/data");
00668     create_directory_if_missing(dir_path + "/data/add-ons");
00669     create_directory_if_missing(dir_path + "/saves");
00670     create_directory_if_missing(dir_path + "/persist");
00671 #endif
00672 }
00673 
00674 const std::string& get_user_data_dir()
00675 {
00676     // ensure setup gets called only once per session
00677     // FIXME: this is okay and optimized, but how should we react
00678     // if the user deletes a dir while we are running?
00679     if (user_data_dir.empty())
00680     {
00681         if (game_config::preferences_dir.empty())
00682             set_preferences_dir(std::string());
00683         else {
00684             user_data_dir = game_config::preferences_dir;
00685             setup_user_data_dir();
00686         }
00687     }
00688     return user_data_dir;
00689 }
00690 
00691 const std::string &get_user_config_dir()
00692 {
00693     if (user_config_dir.empty())
00694     {
00695 #if defined(_X11) && !defined(PREFERENCES_DIR)
00696         char const *xdg_config = getenv("XDG_CONFIG_HOME");
00697         if (!xdg_config || xdg_config[0] == '\0') {
00698             xdg_config = getenv("HOME");
00699             if (!xdg_config) {
00700                 user_config_dir = get_user_data_dir();
00701                 return user_config_dir;
00702             }
00703             user_config_dir = xdg_config;
00704             user_config_dir += "/.config";
00705         } else user_config_dir = xdg_config;
00706         user_config_dir += "/wesnoth";
00707         create_directory_if_missing_recursive(user_config_dir);
00708 #else
00709         user_config_dir = get_user_data_dir();
00710 #endif
00711     }
00712     return user_config_dir;
00713 }
00714 
00715 const std::string &get_cache_dir()
00716 {
00717     if (cache_dir.empty())
00718     {
00719 #if defined(_X11) && !defined(PREFERENCES_DIR)
00720         char const *xdg_cache = getenv("XDG_CACHE_HOME");
00721         if (!xdg_cache || xdg_cache[0] == '\0') {
00722             xdg_cache = getenv("HOME");
00723             if (!xdg_cache) {
00724                 cache_dir = get_dir(get_user_data_dir() + "/cache");
00725                 return cache_dir;
00726             }
00727             cache_dir = xdg_cache;
00728             cache_dir += "/.cache";
00729         } else cache_dir = xdg_cache;
00730         cache_dir += "/wesnoth";
00731         create_directory_if_missing_recursive(cache_dir);
00732 #else
00733         cache_dir = get_dir(get_user_data_dir() + "/cache");
00734 #endif
00735     }
00736     return cache_dir;
00737 }
00738 
00739 static std::string read_stream(std::istream& s)
00740 {
00741     std::stringstream ss;
00742     ss << s.rdbuf();
00743     return ss.str();
00744 }
00745 
00746 std::istream *istream_file(const std::string &fname)
00747 {
00748     LOG_FS << "Streaming " << fname << " for reading.\n";
00749     if (fname.empty())
00750     {
00751         ERR_FS << "Trying to open file with empty name.\n";
00752         std::ifstream *s = new std::ifstream();
00753         s->clear(std::ios_base::failbit);
00754         return s;
00755     }
00756 
00757     std::ifstream *s = new std::ifstream(fname.c_str(),std::ios_base::binary);
00758     if (s->is_open())
00759         return s;
00760     ERR_FS << "Could not open '" << fname << "' for reading.\n";
00761     return s;
00762 
00763 }
00764 
00765 std::string read_file(const std::string &fname)
00766 {
00767     scoped_istream s = istream_file(fname);
00768     return read_stream(*s);
00769 }
00770 
00771 std::ostream *ostream_file(std::string const &fname)
00772 {
00773     LOG_FS << "streaming " << fname << " for writing.\n";
00774     return new std::ofstream(fname.c_str(), std::ios_base::binary);
00775 }
00776 
00777 // Throws io_exception if an error occurs
00778 void write_file(const std::string& fname, const std::string& data)
00779 {
00780     //const util::scoped_resource<FILE*,close_FILE> file(fopen(fname.c_str(),"wb"));
00781     const util::scoped_FILE file(fopen(fname.c_str(),"wb"));
00782     if(file.get() == NULL) {
00783         throw io_exception("Could not open file for writing: '" + fname + "'");
00784     }
00785 
00786     const size_t block_size = 4096;
00787     char buf[block_size];
00788 
00789     for(size_t i = 0; i < data.size(); i += block_size) {
00790         const size_t bytes = std::min<size_t>(block_size,data.size() - i);
00791         std::copy(data.begin() + i, data.begin() + i + bytes,buf);
00792         const size_t res = fwrite(buf,1,bytes,file.get());
00793         if(res != bytes) {
00794             throw io_exception("Error writing to file: '" + fname + "'");
00795         }
00796     }
00797 }
00798 
00799 
00800 std::string read_map(const std::string& name)
00801 {
00802     std::string res;
00803     std::string map_location = get_wml_location("maps/" + name);
00804     if(!map_location.empty()) {
00805         res = read_file(map_location);
00806     }
00807 
00808     if (res.empty()) {
00809         res = read_file(get_user_data_dir() + "/editor/maps/" + name);
00810     }
00811 
00812     return res;
00813 }
00814 
00815 static bool is_directory_internal(const std::string& fname)
00816 {
00817 #ifdef _WIN32
00818     _finddata_t info;
00819     const long handle = _findfirst((fname + "/*").c_str(),&info);
00820     if(handle >= 0) {
00821         _findclose(handle);
00822         return true;
00823     } else {
00824         return false;
00825     }
00826 
00827 #else
00828     struct stat dir_stat;
00829     if(::stat(fname.c_str(), &dir_stat) == -1) {
00830         return false;
00831     }
00832     return S_ISDIR(dir_stat.st_mode);
00833 #endif
00834 }
00835 
00836 bool is_directory(const std::string& fname)
00837 {
00838     if(fname.empty()) {
00839         return false;
00840     }
00841     if(fname[0] != '/' && !game_config::path.empty()) {
00842         if(is_directory_internal(game_config::path + "/" + fname))
00843             return true;
00844     }
00845 
00846     return is_directory_internal(fname);
00847 }
00848 
00849 bool file_exists(const std::string& name)
00850 {
00851 #ifdef _WIN32
00852        struct stat st;
00853        return (::stat(name.c_str(), &st) == 0);
00854 #else
00855     struct stat st;
00856     return (::stat(name.c_str(), &st) != -1);
00857 #endif
00858 }
00859 
00860 time_t file_create_time(const std::string& fname)
00861 {
00862     struct stat buf;
00863     if(::stat(fname.c_str(),&buf) == -1)
00864         return 0;
00865 
00866     return buf.st_mtime;
00867 }
00868 
00869 /**
00870  * Returns true if the file ends with '.gz'.
00871  *
00872  * @param filename                The name to test.
00873  */
00874 bool is_gzip_file(const std::string& filename)
00875 {
00876     return (filename.length() > 3
00877         && filename.substr(filename.length() - 3) == ".gz");
00878 }
00879 
00880 file_tree_checksum::file_tree_checksum()
00881     : nfiles(0), sum_size(0), modified(0)
00882 {}
00883 
00884 file_tree_checksum::file_tree_checksum(const config& cfg) :
00885     nfiles  (lexical_cast_default<size_t>(cfg["nfiles"])),
00886     sum_size(lexical_cast_default<size_t>(cfg["size"])),
00887     modified(lexical_cast_default<time_t>(cfg["modified"]))
00888 {
00889 }
00890 
00891 void file_tree_checksum::write(config& cfg) const
00892 {
00893     cfg["nfiles"] = lexical_cast<std::string>(nfiles);
00894     cfg["size"] = lexical_cast<std::string>(sum_size);
00895     cfg["modified"] = lexical_cast<std::string>(modified);
00896 }
00897 
00898 bool file_tree_checksum::operator==(const file_tree_checksum &rhs) const
00899 {
00900     return nfiles == rhs.nfiles && sum_size == rhs.sum_size &&
00901         modified == rhs.modified;
00902 }
00903 
00904 static void get_file_tree_checksum_internal(const std::string& path, file_tree_checksum& res)
00905 {
00906 
00907     std::vector<std::string> dirs;
00908     get_files_in_dir(path,NULL,&dirs, ENTIRE_FILE_PATH, SKIP_MEDIA_DIR, DONT_REORDER, &res);
00909     loadscreen::increment_progress();
00910 
00911     for(std::vector<std::string>::const_iterator j = dirs.begin(); j != dirs.end(); ++j) {
00912         get_file_tree_checksum_internal(*j,res);
00913     }
00914 }
00915 
00916 const file_tree_checksum& data_tree_checksum(bool reset)
00917 {
00918     static file_tree_checksum checksum;
00919     if (reset)
00920         checksum.reset();
00921     if(checksum.nfiles == 0) {
00922         get_file_tree_checksum_internal("data/",checksum);
00923         get_file_tree_checksum_internal(get_user_data_dir() + "/data/",checksum);
00924         LOG_FS << "calculated data tree checksum: "
00925                << checksum.nfiles << " files; "
00926                << checksum.sum_size << " bytes\n";
00927     }
00928 
00929     return checksum;
00930 }
00931 
00932 int file_size(const std::string& fname)
00933 {
00934     struct stat buf;
00935     if(::stat(fname.c_str(),&buf) == -1)
00936         return -1;
00937 
00938     return buf.st_size;
00939 }
00940 
00941 std::string file_name(const std::string& file)
00942 // Analogous to POSIX basename(3), but for C++ string-object pathnames
00943 {
00944 #ifdef _WIN32
00945     static const std::string dir_separators = "\\/:";
00946 #else
00947     static const std::string dir_separators = "/";
00948 #endif
00949 
00950     std::string::size_type pos = file.find_last_of(dir_separators);
00951 
00952     if(pos == std::string::npos)
00953         return file;
00954     if(pos >= file.size()-1)
00955         return "";
00956 
00957     return file.substr(pos+1);
00958 }
00959 
00960 std::string directory_name(const std::string& file)
00961 // Analogous to POSIX dirname(3), but for C++ string-object pathnames
00962 {
00963 #ifdef _WIN32
00964     static const std::string dir_separators = "\\/:";
00965 #else
00966     static const std::string dir_separators = "/";
00967 #endif
00968 
00969     std::string::size_type pos = file.find_last_of(dir_separators);
00970 
00971     if(pos == std::string::npos)
00972         return "";
00973 
00974     return file.substr(0,pos+1);
00975 }
00976 
00977 namespace {
00978 
00979 std::set<std::string> binary_paths;
00980 
00981 typedef std::map<std::string,std::vector<std::string> > paths_map;
00982 paths_map binary_paths_cache;
00983 
00984 }
00985 
00986 static void init_binary_paths()
00987 {
00988     if(binary_paths.empty()) {
00989         binary_paths.insert("");
00990     }
00991 }
00992 
00993 binary_paths_manager::binary_paths_manager() : paths_()
00994 {}
00995 
00996 binary_paths_manager::binary_paths_manager(const config& cfg) : paths_()
00997 {
00998     set_paths(cfg);
00999 }
01000 
01001 binary_paths_manager::~binary_paths_manager()
01002 {
01003     cleanup();
01004 }
01005 
01006 void binary_paths_manager::set_paths(const config& cfg)
01007 {
01008     cleanup();
01009     init_binary_paths();
01010 
01011     foreach (const config &bp, cfg.child_range("binary_path"))
01012     {
01013         std::string path = bp["path"].str();
01014         if (path.find("..") != std::string::npos) {
01015             ERR_FS << "Invalid binary path '" << path << "'\n";
01016             continue;
01017         }
01018         if (!path.empty() && path[path.size()-1] != '/') path += "/";
01019         if(binary_paths.count(path) == 0) {
01020             binary_paths.insert(path);
01021             paths_.push_back(path);
01022         }
01023     }
01024 }
01025 
01026 void binary_paths_manager::cleanup()
01027 {
01028     binary_paths_cache.clear();
01029 
01030     for(std::vector<std::string>::const_iterator i = paths_.begin(); i != paths_.end(); ++i) {
01031         binary_paths.erase(*i);
01032     }
01033 }
01034 
01035 void clear_binary_paths_cache()
01036 {
01037     binary_paths_cache.clear();
01038 }
01039 
01040 const std::vector<std::string>& get_binary_paths(const std::string& type)
01041 {
01042     const paths_map::const_iterator itor = binary_paths_cache.find(type);
01043     if(itor != binary_paths_cache.end()) {
01044         return itor->second;
01045     }
01046 
01047     if (type.find("..") != std::string::npos) {
01048         // Not an assertion, as language.cpp is passing user data as type.
01049         ERR_FS << "Invalid WML type '" << type << "' for binary paths\n";
01050         static std::vector<std::string> dummy;
01051         return dummy;
01052     }
01053 
01054     std::vector<std::string>& res = binary_paths_cache[type];
01055 
01056     init_binary_paths();
01057 
01058     foreach (const std::string &path, binary_paths)
01059     {
01060         res.push_back(get_user_data_dir() + "/" + path + type + "/");
01061 
01062         if(!game_config::path.empty()) {
01063             res.push_back(game_config::path + "/" + path + type + "/");
01064         }
01065     }
01066 
01067     // not found in "/type" directory, try main directory
01068     res.push_back(get_user_data_dir() + "/");
01069 
01070     if(!game_config::path.empty())
01071         res.push_back(game_config::path+"/");
01072 
01073     return res;
01074 }
01075 
01076 std::string get_binary_file_location(const std::string& type, const std::string& filename)
01077 {
01078     DBG_FS << "Looking for '" << filename << "'.\n";
01079 
01080     if (filename.empty()) {
01081         LOG_FS << "  invalid filename (type: " << type <<")\n";
01082         return std::string();
01083     }
01084 
01085     // Some parts of Wesnoth enjoy putting ".." inside filenames. This is
01086     // bad and should be fixed. But in the meantime, deal with them in a dumb way.
01087     std::string::size_type pos = filename.rfind("../");
01088     if (pos != std::string::npos) {
01089         std::string nf = filename.substr(pos + 3);
01090         LOG_FS << "Illegal path '" << filename << "' replaced by '" << nf << "'\n";
01091         return get_binary_file_location(type, nf);
01092     }
01093 
01094     if (filename.find("..") != std::string::npos) {
01095         ERR_FS << "Illegal path '" << filename << "' (\"..\" not allowed).\n";
01096         return std::string();
01097     }
01098 
01099     foreach (const std::string &path, get_binary_paths(type))
01100     {
01101         const std::string file = path + filename;
01102         DBG_FS << "  checking '" << path << "'\n";
01103         if(file_exists(file)) {
01104             DBG_FS << "  found at '" << file << "'\n";
01105             return file;
01106         }
01107     }
01108 
01109     DBG_FS << "  not found\n";
01110     return std::string();
01111 }
01112 
01113 std::string get_binary_dir_location(const std::string &type, const std::string &filename)
01114 {
01115     DBG_FS << "Looking for '" << filename << "'.\n";
01116 
01117     if (filename.empty()) {
01118         LOG_FS << "  invalid filename (type: " << type <<")\n";
01119         return std::string();
01120     }
01121 
01122     if (filename.find("..") != std::string::npos) {
01123         ERR_FS << "Illegal path '" << filename << "' (\"..\" not allowed).\n";
01124         return std::string();
01125     }
01126 
01127     foreach (const std::string &path, get_binary_paths(type))
01128     {
01129         const std::string file = path + filename;
01130         DBG_FS << "  checking '" << path << "'\n";
01131         if (is_directory(file)) {
01132             DBG_FS << "  found at '" << file << "'\n";
01133             return file;
01134         }
01135     }
01136 
01137     DBG_FS << "  not found\n";
01138     return std::string();
01139 }
01140 
01141 std::string get_wml_location(const std::string &filename, const std::string &current_dir)
01142 {
01143     DBG_FS << "Looking for '" << filename << "'.\n";
01144 
01145     std::string result;
01146 
01147     if (filename.empty()) {
01148         LOG_FS << "  invalid filename\n";
01149         return result;
01150     }
01151 
01152     if (filename.find("..") != std::string::npos) {
01153         ERR_FS << "Illegal path '" << filename << "' (\"..\" not allowed).\n";
01154         return result;
01155     }
01156 
01157     bool already_found = false;
01158 
01159     if (filename[0] == '~')
01160     {
01161         // If the filename starts with '~', look in the user data directory.
01162         result = get_user_data_dir() + "/data/" + filename.substr(1);
01163         DBG_FS << "  trying '" << result << "'\n";
01164 
01165         already_found = file_exists(result) || is_directory(result);
01166     }
01167     else if (filename.size() >= 2 && filename[0] == '.' && filename[1] == '/')
01168     {
01169         // If the filename begins with a "./", look in the same directory
01170         // as the file currrently being preprocessed.
01171         result = current_dir + filename.substr(2);
01172     }
01173     else if (!game_config::path.empty())
01174         result = game_config::path + "/data/" + filename;
01175 
01176     DBG_FS << "  trying '" << result << "'\n";
01177 
01178     if (result.empty() ||
01179         (!already_found && !file_exists(result) && !is_directory(result)))
01180     {
01181         DBG_FS << "  not found\n";
01182         result.clear();
01183     }
01184     else
01185         DBG_FS << "  found: '" << result << "'\n";
01186 
01187     return result;
01188 }
01189 
01190 std::string get_short_wml_path(const std::string &filename)
01191 {
01192     std::string match = get_user_data_dir() + "/data/";
01193     if (filename.find(match) == 0) {
01194         return "~" + filename.substr(match.size());
01195     }
01196     match = game_config::path + "/data/";
01197     if (filename.find(match) == 0) {
01198         return filename.substr(match.size());
01199     }
01200     return filename;
01201 }
01202 
01203 std::string get_independent_image_path(const std::string &filename)
01204 {
01205     std::string full_path = get_binary_file_location("images", filename);
01206 
01207     if(!full_path.empty()) {
01208         std::string match = get_user_data_dir() + "/";
01209         if(full_path.find(match) == 0) {
01210             return full_path.substr(match.size());
01211         }
01212         match = game_config::path + "/";
01213         if(full_path.find(match) == 0) {
01214             return full_path.substr(match.size());
01215         }
01216     }
01217 
01218     return full_path;
01219 }
01220 
01221 std::string get_program_invocation(const std::string& program_name) {
01222 #ifdef DEBUG
01223   #ifdef _WIN32
01224     const char *program_suffix = "-debug.exe";
01225   #else
01226     const char *program_suffix = "-debug";
01227   #endif
01228 #else
01229   #ifdef _WIN32
01230     const char *program_suffix = ".exe";
01231   #else
01232     const char *program_suffix = "";
01233   #endif
01234 #endif
01235 
01236     const std::string real_program_name(program_name + program_suffix);
01237     if(game_config::wesnoth_program_dir.empty()) return real_program_name;
01238 #ifdef _WIN32
01239     return game_config::wesnoth_program_dir + "\\" + real_program_name;
01240 #else
01241     return game_config::wesnoth_program_dir + "/" + real_program_name;
01242 #endif
01243 }
01244 
01245 static bool is_path_sep(char c)
01246 {
01247 #ifdef _WIN32
01248     if (c == '/' || c == '\\') return true;
01249 #else
01250     if (c == '/') return true;
01251 #endif
01252     return false;
01253 }
01254 
01255 std::string normalize_path(const std::string &p1)
01256 {
01257     if (p1.empty()) return p1;
01258 
01259     std::string p2;
01260 #ifdef _WIN32
01261     if (p1.size() >= 2 && p1[1] == ':')
01262         // Windows relative paths with explicit drive name are not handled.
01263         p2 = p1;
01264     else
01265 #endif
01266     if (!is_path_sep(p1[0]))
01267         p2 = get_cwd() + "/" + p1;
01268     else
01269         p2 = p1;
01270 
01271 #ifdef _WIN32
01272     std::string drive;
01273     if (p2.size() >= 2 && p2[1] == ':') {
01274         drive = p2.substr(0, 2);
01275         p2.erase(0, 2);
01276     }
01277 #endif
01278 
01279     std::vector<std::string> components(1);
01280     for (int i = 0, i_end = p2.size(); i <= i_end; ++i)
01281     {
01282         std::string &last = components[components.size() - 1];
01283         char c = p2.c_str()[i];
01284         if (is_path_sep(c) || c == 0)
01285         {
01286             if (last == ".")
01287                 last.clear();
01288             else if (last == "..")
01289             {
01290                 if (components.size() >= 2) {
01291                     components.pop_back();
01292                     components[components.size() - 1].clear();
01293                 } else
01294                     last.clear();
01295             }
01296             else if (!last.empty())
01297                 components.push_back(std::string());
01298         }
01299         else
01300             last += c;
01301     }
01302 
01303     std::ostringstream p4;
01304     components.pop_back();
01305 
01306 #ifdef _WIN32
01307     p4 << drive;
01308 #endif
01309 
01310     foreach (const std::string &s, components)
01311     {
01312         p4 << '/' << s;
01313     }
01314 
01315     DBG_FS << "Normalizing '" << p2 << "' to '" << p4.str() << "'\n";
01316 
01317     return p4.str();
01318 }
01319 
01320 scoped_istream& scoped_istream::operator=(std::istream *s)
01321 {
01322     delete stream;
01323     stream = s;
01324     return *this;
01325 }
01326 
01327 scoped_istream::~scoped_istream()
01328 {
01329     delete stream;
01330 }
01331 
01332 scoped_ostream& scoped_ostream::operator=(std::ostream *s)
01333 {
01334     delete stream;
01335     stream = s;
01336     return *this;
01337 }
01338 
01339 scoped_ostream::~scoped_ostream()
01340 {
01341     delete stream;
01342 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines

Generated by doxygen 1.7.1 on Thu May 24 2012 01:02:36 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs