1 // sg_path.cxx -- routines to abstract out path separator differences
2 // between MacOS and the rest of the world
4 // Written by Curtis L. Olson, started April 1999.
6 // Copyright (C) 1999 Curtis L. Olson - http://www.flightgear.org/~curt
8 // This library is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU Library General Public
10 // License as published by the Free Software Foundation; either
11 // version 2 of the License, or (at your option) any later version.
13 // This library is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 // Library General Public License for more details.
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25 #include <simgear/compiler.h>
27 #include <simgear_config.h>
28 #include <simgear/debug/logstream.hxx>
29 #include <simgear/misc/strutils.hxx>
30 #include <simgear/misc/sgstream.hxx>
41 #include "sg_path.hxx"
43 #include <boost/algorithm/string/case_conv.hpp>
46 using simgear::strutils::starts_with;
49 * define directory path separators
52 static const char sgDirPathSep = '/';
53 static const char sgDirPathSepBad = '\\';
56 static const char sgSearchPathSep = ';';
58 static const char sgSearchPathSep = ':';
62 #include <ShlObj.h> // for CSIDL
63 // TODO: replace this include file with the official <versionhelpers.h> header
64 // included in the Windows 8.1 SDK
65 #include "sgversionhelpers.hxx"
67 #define ENABLE_OLD_PATH_API 1
69 static SGPath pathForCSIDL(int csidl, const SGPath& def)
71 typedef BOOL (WINAPI*GetSpecialFolderPath)(HWND, LPSTR, int, BOOL);
72 static GetSpecialFolderPath SHGetSpecialFolderPath = NULL;
74 // lazy open+resolve of shell32
75 if (!SHGetSpecialFolderPath) {
76 HINSTANCE shellDll = ::LoadLibrary("shell32");
77 SHGetSpecialFolderPath = (GetSpecialFolderPath) GetProcAddress(shellDll, "SHGetSpecialFolderPathA");
80 if (!SHGetSpecialFolderPath){
85 if (SHGetSpecialFolderPath(0, path, csidl, false)) {
86 return SGPath(path, def.getPermissionChecker());
92 static SGPath pathForKnownFolder(REFKNOWNFOLDERID folderId, const SGPath& def)
94 typedef HRESULT (WINAPI*PSHGKFP)(REFKNOWNFOLDERID, DWORD, HANDLE, PWSTR*);
96 HINSTANCE shellDll = LoadLibrary(TEXT("shell32"));
97 if (shellDll != NULL) {
98 PSHGKFP pSHGetKnownFolderPath = (PSHGKFP) GetProcAddress(shellDll, "SHGetKnownFolderPath");
99 if (pSHGetKnownFolderPath != NULL) {
100 // system call will allocate dynamic memory... which we must release when done
101 wchar_t* localFolder = 0;
103 if (pSHGetKnownFolderPath(folderId, KF_FLAG_DEFAULT_PATH, NULL, &localFolder) == S_OK) {
104 // copy into local memory
107 if (wcstombs_s(&len, path, localFolder, MAX_PATH) != S_OK) {
109 SG_LOG(SG_GENERAL, SG_WARN, "WCS to MBS failed");
112 SGPath folder_path = SGPath(path, def.getPermissionChecker());
114 // release dynamic memory
115 CoTaskMemFree(static_cast<void*>(localFolder));
121 FreeLibrary(shellDll);
129 // defined in CocoaHelpers.mm
130 SGPath appleSpecialFolder(int dirType, int domainMask, const SGPath& def);
133 static SGPath getXDGDir( const std::string& name,
135 const std::string& fallback )
137 // http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
139 // $XDG_CONFIG_HOME defines the base directory relative to which user specific
140 // configuration files should be stored. If $XDG_CONFIG_HOME is either not set
141 // or empty, a default equal to $HOME/.config should be used.
142 const SGPath user_dirs = SGPath::fromEnv( "XDG_CONFIG_HOME",
143 SGPath::home() / ".config")
146 // Format is XDG_xxx_DIR="$HOME/yyy", where yyy is a shell-escaped
147 // homedir-relative path, or XDG_xxx_DIR="/yyy", where /yyy is an absolute
148 // path. No other format is supported.
149 const std::string XDG_ID = "XDG_" + name + "_DIR=\"";
151 sg_ifstream user_dirs_file( user_dirs );
153 while( std::getline(user_dirs_file, line).good() )
155 if( !starts_with(line, XDG_ID) || *line.rbegin() != '"' )
158 // Extract dir from XDG_<name>_DIR="<dir>"
159 line = line.substr(XDG_ID.length(), line.length() - XDG_ID.length() - 1 );
161 const std::string HOME = "$HOME";
162 if( starts_with(line, HOME) )
163 return SGPath::home(def)
164 / simgear::strutils::unescape(line.substr(HOME.length()));
166 return SGPath(line, def.getPermissionChecker());
170 return SGPath::home(def) / fallback;
176 // For windows, replace "\" by "/".
180 string::size_type sz = path.size();
181 for ( string::size_type i = 0; i < sz; ++i ) {
182 if ( path[i] == sgDirPathSepBad ) {
183 path[i] = sgDirPathSep;
187 while ((sz>1)&&(path[sz-1]==sgDirPathSep))
194 // default constructor
195 SGPath::SGPath(PermissionChecker validator)
197 _permission_checker(validator),
205 // create a path based on "path"
206 SGPath::SGPath( const std::string& p, PermissionChecker validator )
208 _permission_checker(validator),
216 // create a path based on "path" and a "subpath"
217 SGPath::SGPath( const SGPath& p,
218 const std::string& r,
219 PermissionChecker validator )
221 _permission_checker(validator),
224 _cacheEnabled(p._cacheEnabled)
230 SGPath SGPath::fromLocal8Bit(const char *name)
232 return SGPath(simgear::strutils::convertWindowsLocal8BitToUtf8(name));
235 SGPath SGPath::fromUtf8(const std::string& bytes, PermissionChecker p)
237 return SGPath(bytes, p);
241 SGPath::SGPath(const SGPath& p) :
243 _permission_checker(p._permission_checker),
245 _rwCached(p._rwCached),
246 _cacheEnabled(p._cacheEnabled),
247 _canRead(p._canRead),
248 _canWrite(p._canWrite),
252 _modTime(p._modTime),
257 SGPath& SGPath::operator=(const SGPath& p)
260 _permission_checker = p._permission_checker,
262 _rwCached = p._rwCached;
263 _cacheEnabled = p._cacheEnabled;
264 _canRead = p._canRead;
265 _canWrite = p._canWrite;
269 _modTime = p._modTime;
278 #if defined(ENABLE_OLD_PATH_API)
280 void SGPath::set( const string& p ) {
288 //------------------------------------------------------------------------------
289 void SGPath::setPermissionChecker(PermissionChecker validator)
291 _permission_checker = validator;
295 //------------------------------------------------------------------------------
296 SGPath::PermissionChecker SGPath::getPermissionChecker() const
298 return _permission_checker;
301 //------------------------------------------------------------------------------
302 void SGPath::set_cached(bool cached)
304 _cacheEnabled = cached;
308 // append another piece to the existing path
309 void SGPath::append( const string& p ) {
310 if ( path.empty() ) {
313 if ( p[0] != sgDirPathSep ) {
314 path += sgDirPathSep;
323 //------------------------------------------------------------------------------
324 SGPath SGPath::operator/( const std::string& p ) const
331 #if defined(ENABLE_OLD_PATH_API)
332 //add a new path component to the existing path string
333 void SGPath::add( const string& p ) {
334 append( sgSearchPathSep+p );
338 // concatenate a string to the end of the path without inserting a
340 void SGPath::concat( const string& p ) {
341 if ( path.empty() ) {
351 std::string SGPath::local8BitStr() const
353 return simgear::strutils::convertUtf8ToWindowsLocal8Bit(path);
356 // Get the file part of the path (everything after the last path sep)
357 string SGPath::file() const
359 string::size_type index = path.rfind(sgDirPathSep);
360 if (index != string::npos) {
361 return path.substr(index + 1);
368 // get the directory part of the path.
369 string SGPath::dir() const {
370 int index = path.rfind(sgDirPathSep);
372 return path.substr(0, index);
378 SGPath SGPath::dirPath() const
380 return SGPath::fromUtf8(dir());
383 // get the base part of the path (everything but the extension.)
384 string SGPath::base() const
386 string::size_type index = path.rfind(".");
387 string::size_type lastSep = path.rfind(sgDirPathSep);
389 // tolerate dots inside directory names
390 if ((lastSep != string::npos) && (index < lastSep)) {
394 if (index != string::npos) {
395 return path.substr(0, index);
401 string SGPath::file_base() const
403 string::size_type index = path.rfind(sgDirPathSep);
404 if (index == string::npos) {
405 index = 0; // no separator in the name
407 ++index; // skip past the separator
410 string::size_type firstDot = path.find(".", index);
411 if (firstDot == string::npos) {
412 return path.substr(index); // no extensions
415 return path.substr(index, firstDot - index);
418 // get the extension (everything after the final ".")
419 // but make sure no "/" follows the "." character (otherwise it
420 // is has to be a directory name containing a ".").
421 string SGPath::extension() const {
422 int index = path.rfind(".");
423 if ((index >= 0) && (path.find("/", index) == string::npos)) {
424 return path.substr(index + 1);
430 string SGPath::lower_extension() const {
431 return boost::to_lower_copy(extension());
434 string SGPath::complete_lower_extension() const
436 string::size_type index = path.rfind(sgDirPathSep);
437 if (index == string::npos) {
438 index = 0; // no separator in the name
440 ++index; // skip past the separator
443 string::size_type firstDot = path.find(".", index);
444 if ((firstDot != string::npos) && (path.find(sgDirPathSep, firstDot) == string::npos)) {
445 return boost::to_lower_copy(path.substr(firstDot + 1));
451 //------------------------------------------------------------------------------
452 void SGPath::validate() const
454 if (_cached && _cacheEnabled) {
465 bool remove_trailing = false;
466 string statPath(local8BitStr());
467 if ((path.length() > 1) && (path.back() == '/')) {
471 if (_stat(statPath.c_str(), &buf ) < 0) {
475 _isFile = ((S_IFREG & buf.st_mode ) !=0);
476 _isDir = ((S_IFDIR & buf.st_mode ) !=0);
477 _modTime = buf.st_mtime;
484 if (stat(path.c_str(), &buf ) < 0) {
488 _isFile = ((S_ISREG(buf.st_mode )) != 0);
489 _isDir = ((S_ISDIR(buf.st_mode )) != 0);
490 _modTime = buf.st_mtime;
498 //------------------------------------------------------------------------------
499 void SGPath::checkAccess() const
501 if( _rwCached && _cacheEnabled )
504 if( _permission_checker )
506 Permissions p = _permission_checker(*this);
519 bool SGPath::exists() const
525 //------------------------------------------------------------------------------
526 bool SGPath::canRead() const
532 //------------------------------------------------------------------------------
533 bool SGPath::canWrite() const
539 bool SGPath::isDir() const
542 return _exists && _isDir;
545 bool SGPath::isFile() const
548 return _exists && _isFile;
551 //------------------------------------------------------------------------------
553 # define sgMkDir(d,m) _mkdir(d)
555 # define sgMkDir(d,m) mkdir(d,m)
558 int SGPath::create_dir(mode_t mode)
563 SG_WARN, "Error creating directory for '" << *this << "'"
564 " reason: access denied" );
568 string_list dirlist = sgPathSplit(dir());
569 if ( dirlist.empty() )
571 string path = dirlist[0];
572 string_list path_elements = sgPathBranchSplit(path);
573 bool absolute = !path.empty() && path[0] == sgDirPathSep;
576 SGPath dir(absolute ? string( 1, sgDirPathSep ) : "", _permission_checker);
577 dir.concat( path_elements[0] );
578 std::string ds = dir.local8BitStr();
580 if ( ds.find(':') != string::npos && path_elements.size() >= 2 ) {
581 dir.append( path_elements[1] );
583 ds = dir.local8BitStr();
588 for(; (r = stat(dir.c_str(), &info)) == 0 && i < path_elements.size(); ++i) {
589 dir.append(path_elements[i]);
592 return 0; // Directory already exists
596 if( sgMkDir(dir.c_str(), mode) )
599 SG_ALERT, "Error creating directory: (" << dir << ")" );
603 SG_LOG(SG_IO, SG_DEBUG, "Directory created: " << dir);
605 if( i >= path_elements.size() )
608 dir.append(path_elements[i++]);
614 string_list sgPathBranchSplit( const string &dirpath ) {
615 string_list path_elements;
616 string element, path = dirpath;
617 while ( ! path.empty() ) {
618 size_t p = path.find( sgDirPathSep );
619 if ( p != string::npos ) {
620 element = path.substr( 0, p );
621 path.erase( 0, p + 1 );
626 if ( ! element.empty() )
627 path_elements.push_back( element );
629 return path_elements;
633 string_list sgPathSplit( const string &search_path ) {
634 string tmp = search_path;
641 int index = tmp.find(sgSearchPathSep);
643 result.push_back( tmp.substr(0, index) );
644 tmp = tmp.substr( index + 1 );
647 result.push_back( tmp );
655 bool SGPath::isAbsolute() const
662 // detect '[A-Za-z]:/'
663 if (path.size() > 2) {
664 if (isalpha(path[0]) && (path[1] == ':') && (path[2] == sgDirPathSep)) {
670 return (path[0] == sgDirPathSep);
673 bool SGPath::isNull() const
678 #if defined(ENABLE_OLD_PATH_API)
679 std::string SGPath::str_native() const
682 std::string s = local8BitStr();
683 std::string::size_type pos;
684 std::string nativeSeparator;
685 nativeSeparator = sgDirPathSepBad;
687 while( (pos=s.find( sgDirPathSep )) != std::string::npos ) {
688 s.replace( pos, 1, nativeSeparator );
697 //------------------------------------------------------------------------------
698 bool SGPath::remove()
702 SG_LOG( SG_IO, SG_WARN, "file remove failed: (" << *this << ")"
703 " reason: access denied" );
707 std::string ps = local8BitStr();
708 int err = ::unlink(ps.c_str());
711 SG_LOG( SG_IO, SG_WARN, "file remove failed: (" << *this << ") "
712 " reason: " << strerror(errno) );
713 // TODO check if failed unlink can really change any of the cached values
716 _cached = false; // stat again if required
721 time_t SGPath::modTime() const
727 size_t SGPath::sizeInBytes() const
733 bool SGPath::operator==(const SGPath& other) const
735 return (path == other.path);
738 bool SGPath::operator!=(const SGPath& other) const
740 return (path != other.path);
743 //------------------------------------------------------------------------------
744 bool SGPath::rename(const SGPath& newName)
746 if( !canRead() || !canWrite() || !newName.canWrite() )
748 SG_LOG( SG_IO, SG_WARN, "rename failed: from " << *this <<
750 " reason: access denied" );
755 if (newName.exists()) {
762 std::string p = local8BitStr();
763 std::string np = newName.local8BitStr();
765 if( ::rename(p.c_str(), np.c_str()) != 0 )
767 SG_LOG( SG_IO, SG_WARN, "rename failed: from " << *this <<
769 " reason: " << strerror(errno) );
775 // Do not remove permission checker (could happen for example if just using
776 // a std::string as new name)
777 if( newName._permission_checker )
778 _permission_checker = newName._permission_checker;
786 //------------------------------------------------------------------------------
787 SGPath SGPath::standardLocation(StandardLocation type, const SGPath& def)
796 if (IsWindowsVistaOrGreater())
797 return pathForKnownFolder(FOLDERID_Desktop, def);
799 return pathForCSIDL(CSIDL_DESKTOPDIRECTORY, def);
802 if (IsWindowsVistaOrGreater())
803 return pathForKnownFolder(FOLDERID_Downloads, def);
808 return pathForCSIDL(CSIDL_DESKTOPDIRECTORY, def);
811 if (IsWindowsVistaOrGreater())
812 return pathForKnownFolder(FOLDERID_Documents, def);
814 return pathForCSIDL(CSIDL_MYDOCUMENTS, def);
817 if (IsWindowsVistaOrGreater())
818 return pathForKnownFolder(FOLDERID_Pictures, def);
820 return pathForCSIDL(CSIDL_MYPICTURES, def);
823 // since this is C++, we can't include NSPathUtilities.h to access the enum
824 // values, so hard-coding them here (they are stable, don't worry)
826 return appleSpecialFolder(15, 1, def);
828 return appleSpecialFolder(12, 1, def);
830 return appleSpecialFolder(9, 1, def);
832 return appleSpecialFolder(19, 1, def);
835 return getXDGDir("DESKTOP", def, "Desktop");
837 return getXDGDir("DOWNLOADS", def, "Downloads");
839 return getXDGDir("DOCUMENTS", def, "Documents");
841 return getXDGDir("PICTURES", def, "Pictures");
846 "SGPath::standardLocation() unhandled type: " << type );
851 //------------------------------------------------------------------------------
852 SGPath SGPath::fromEnv(const char* name, const SGPath& def)
854 const char* val = getenv(name);
856 return SGPath(val, def._permission_checker);
860 //------------------------------------------------------------------------------
862 std::vector<SGPath> SGPath::pathsFromEnv(const char *name)
864 std::vector<SGPath> r;
865 const char* val = getenv(name);
870 string_list items = sgPathSplit(val);
871 string_list_iterator it;
872 for (it = items.begin(); it != items.end(); ++it) {
873 r.push_back(SGPath::fromLocal8Bit(it->c_str()));
879 //------------------------------------------------------------------------------
881 std::vector<SGPath> SGPath::pathsFromUtf8(const std::string& paths)
883 std::vector<SGPath> r;
884 string_list items = sgPathSplit(paths);
885 string_list_iterator it;
886 for (it = items.begin(); it != items.end(); ++it) {
887 r.push_back(SGPath::fromUtf8(it->c_str()));
893 //------------------------------------------------------------------------------
895 std::vector<SGPath> SGPath::pathsFromLocal8Bit(const std::string& paths)
897 std::vector<SGPath> r;
898 string_list items = sgPathSplit(paths);
899 string_list_iterator it;
900 for (it = items.begin(); it != items.end(); ++it) {
901 r.push_back(SGPath::fromLocal8Bit(it->c_str()));
907 //------------------------------------------------------------------------------
908 SGPath SGPath::home(const SGPath& def)
911 return fromEnv("USERPROFILE", def);
913 return fromEnv("HOME", def);
917 //------------------------------------------------------------------------------
918 SGPath SGPath::desktop(const SGPath& def)
920 return standardLocation(DESKTOP, def);
923 //------------------------------------------------------------------------------
924 SGPath SGPath::documents(const SGPath& def)
926 return standardLocation(DOCUMENTS, def);
929 //------------------------------------------------------------------------------
930 std::string SGPath::realpath() const
932 #if defined(_MSC_VER) /*for MS compilers */ || defined(_WIN32) /*needed for non MS windows compilers like MingW*/
933 // with absPath NULL, will allocate, and ignore length
934 char *buf = _fullpath( NULL, path.c_str(), _MAX_PATH );
937 char* buf = ::realpath(path.c_str(), NULL);
939 if (!buf) // File does not exist: return the realpath it would have if created now
940 // (needed for fgValidatePath security)
943 return SGPath(".").realpath(); // current directory
945 std::string this_dir = dir();
946 if (isAbsolute() && this_dir.empty()) { // top level
949 if (file() == "..") {
950 this_dir = SGPath(SGPath(this_dir).realpath()).dir();
951 if (this_dir.empty()) { // invalid path: .. above root
954 return SGPath(this_dir).realpath(); // use native path separator,
955 // and handle 'existing/nonexisting/../symlink' paths
957 return SGPath(this_dir).realpath() +
958 #if defined(_MSC_VER) || defined(_WIN32)
969 //------------------------------------------------------------------------------
971 std::string SGPath::join(const std::vector<SGPath>& paths, const std::string& joinWith)
978 r = paths[0].utf8Str();
979 for (size_t i=1; i<paths.size(); ++i) {
980 r += joinWith + paths[i].utf8Str();
986 //------------------------------------------------------------------------------
987 std::wstring SGPath::wstr() const
990 size_t buflen = mbstowcs(NULL, path.c_str(), 0)+1;
991 wchar_t wideBuf = malloc(buflen * sizeof(int));
993 size_t count = mbstowcs(wideBuf, path.c_str(), buflen);
994 if (count == (size_t)-1) {
995 return std::wstring();
998 std::wstring rv(wideBuf, count);
1002 SG_LOG( SG_GENERAL, SG_ALERT, "SGPath::wstr: unable to allocate enough memory for " << *this );