From: Mikko Rasa Date: Thu, 28 Jul 2011 11:19:27 +0000 (+0300) Subject: Merge branch 'fs-master' X-Git-Url: http://git.tdb.fi/?p=libs%2Fcore.git;a=commitdiff_plain;h=d16185720fa344263367dbd50c61bfc8183d99a4;hp=5dadd5ae8ff6618a94eb2c16e96b4e89df1694f5 Merge branch 'fs-master' Conflicts: .gitignore Build Changelog.txt --- diff --git a/Build b/Build index 740887b..a4758f4 100644 --- a/Build +++ b/Build @@ -59,6 +59,12 @@ package "mspcore" install true; }; + headers "msp/fs" + { + source "source/fs"; + install true; + }; + library "mspcore" { source "source/core"; @@ -67,6 +73,7 @@ package "mspcore" source "source/strings"; source "source/stringcodec"; source "source/io"; + source "source/fs"; install true; }; diff --git a/Changelog.txt b/Changelog.txt index 498e9ff..c1e8def 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -40,3 +40,18 @@ 1.0 * First released version + +== Changes from pre-2.0 mspfs == + +1.1 +* Always start relative paths with a dot +* Bugfixes + - Fix 64-bit compilation + +1.0.1 +* Bugfixes + - Make dirname behave sensibly for single-component paths + - Fix win32 compilation + +1.0 +* First release diff --git a/source/fs/dir.cpp b/source/fs/dir.cpp new file mode 100644 index 0000000..0832854 --- /dev/null +++ b/source/fs/dir.cpp @@ -0,0 +1,220 @@ +#include +#include +#include +#include +#ifdef WIN32 +#include +#endif +#include +#include +#include +#include "dir.h" +#include "path.h" +#include "stat.h" +#include "utils.h" + +using namespace std; + +namespace Msp { +namespace FS { + +namespace +{ + +/** Helper function to determine the location of the program's executable. +Caches the last result to cut down filesystem access with repeated calls. */ +const Path &get_bin_dir(const string &argv0) +{ + static string last_argv0; + static Path bin_dir; + + if(!(argv0==last_argv0)) + { + Path exe; + if(argv0.find('/')==string::npos) + { + const char *path = getenv("PATH"); + vector dirs = split(path, ':'); + for(vector::const_iterator i=dirs.begin(); i!=dirs.end(); ++i) + if(exists(Path(*i)/argv0)) + { + exe = realpath(Path(*i)/argv0); + break; + } + } + else + exe = realpath(argv0); + + last_argv0 = argv0; + bin_dir = dirname(exe); + } + + return bin_dir; +} + +} + +void mkdir(const Path &path, int mode) +{ + int err; +#ifdef WIN32 + // The win32 version of this function doesn't take the mode argument. Go figure. + (void)mode; + err = ::mkdir(path.str().c_str()); +#else + err = ::mkdir(path.str().c_str(), mode); +#endif + + if(err==-1) + throw SystemError("mkdir failed", errno); +} + +void mkpath(const Path &path, int mode) +{ + Path p; + for(Path::Iterator i=path.begin(); i!=path.end(); ++i) + { + p /= *i; +#ifdef WIN32 + if(p.size()==1 && p.is_absolute()) + continue; +#endif + struct stat st; + int err = stat(p, st); + if(err==0) + { + if(!S_ISDIR(st.st_mode)) + throw Exception("A component exists and is not a directory"); + continue; + } + else if(errno!=ENOENT) + throw SystemError("stat failed", errno); + else + mkdir(p, mode); + } +} + +void rmdir(const Path &path) +{ + if(::rmdir(path.str().c_str())==-1) + throw SystemError("rmdir failed", errno); +} + +void rmdirs(const Path &path) +{ + list files = list_files(path); + for(list::iterator i=files.begin(); i!=files.end(); ++i) + { + Path p = path / *i; + struct stat st = stat(p.str().c_str()); + if(S_ISDIR(st.st_mode)) + rmdirs(p); + else + unlink(p); + } + + rmdir(path); +} + +list list_files(const Path &path) +{ + return list_filtered(path, string()); +} + +list list_filtered(const Path &path, const string &filter) +{ + Regex r_filter(filter); + + list result; + DIR *dir = opendir(path.str().c_str()); + if(dir) + { + while(dirent *de = readdir(dir)) + { + const char *fn = de->d_name; + if(fn[0]=='.' && (fn[1]==0 || (fn[1]=='.' && fn[2]==0))) + continue; + if(r_filter.match(fn)) + result.push_back(fn); + } + closedir(dir); + } + + return result; +} + +Path getcwd() +{ + char buf[1024]; + return ::getcwd(buf, sizeof(buf)); +} + +Path get_home_dir() +{ +#ifdef WIN32 + char home[MAX_PATH]; + if(SHGetFolderPath(0, CSIDL_PERSONAL, 0, 0, home)==S_OK) + return home; +#else + const char *home = getenv("HOME"); + if(home) + return home; +#endif + return "."; +} + +Path get_user_data_dir(const string &appname) +{ +#ifdef WIN32 + char datadir[MAX_PATH]; + if(SHGetFolderPath(0, CSIDL_LOCAL_APPDATA, 0, 0, datadir)==S_OK) + return Path(datadir)/appname; + return "."; +#else + return get_home_dir()/("."+appname); +#endif +} + +Path get_sys_conf_dir(const string &argv0) +{ + Path dir = get_bin_dir(argv0); + + if(dir[-1]=="bin" || dir[-1]=="sbin") + { + dir /= ".."; + if(dir[-1]=="usr") + dir /= ".."; + return dir/"etc"; + } + else + return dir; +} + +Path get_sys_data_dir(const string &argv0, const string &appname) +{ + Path dir = get_bin_dir(argv0); + + if(dir[-1]=="bin" || dir[-1]=="sbin") + return dir/".."/"share"/appname; + else + return dir; +} + +Path get_sys_lib_dir(const string &argv0, const string &appname) +{ + Path dir = get_bin_dir(argv0); + + if(dir[-1]=="bin" || dir[-1]=="sbin") + return dir/".."/"lib"/appname; + else + return dir; +} + +void chdir(const Path &path) +{ + if(::chdir(path.str().c_str())==-1) + throw SystemError("chdir failed", errno); +} + +} // namespace FS +} // namespace Msp diff --git a/source/fs/dir.h b/source/fs/dir.h new file mode 100644 index 0000000..5544a31 --- /dev/null +++ b/source/fs/dir.h @@ -0,0 +1,53 @@ +#ifndef MSP_FS_DIR_H_ +#define MSP_FS_DIR_H_ + +#include +#include +#include "path.h" + +namespace Msp { +namespace FS { + +/// Creates a directory +void mkdir(const Path &path, int mode); + +/// Creates a directory and any required parent directories +void mkpath(const Path &path, int mode); + +/// Removes a directory, which must be empty +void rmdir(const Path &path); + +/// Removes a directory and anything it contains +void rmpath(const Path &path); + +/// Lists the contents of a directory +std::list list_files(const Path &path); + +/// Lists the contents of a directory, filtered with a regex +std::list list_filtered(const Path &path, const std::string &filter); + +/// Returns the current working directory +Path getcwd(); + +/// Returns the user's home directory +Path get_home_dir(); + +/// Returns a directory suitable for storing user-specific data +Path get_user_data_dir(const std::string &appname); + +/// Returns a directory containing system-wide configuration +Path get_sys_conf_dir(const std::string &argv0); + +/// Returns a directory containing immutable system-wide data +Path get_sys_data_dir(const std::string &argv0, const std::string &appname); + +/// Returns a directory containing system-wide architecture-specific files +Path get_sys_lib_dir(const std::string &argv0, const std::string &appname); + +/// Changes the current working directory +void chdir(const Path &); + +} // namespace FS +} // namespace Msp + +#endif diff --git a/source/fs/path.cpp b/source/fs/path.cpp new file mode 100644 index 0000000..52b1da2 --- /dev/null +++ b/source/fs/path.cpp @@ -0,0 +1,263 @@ +#include +#include +#include "path.h" +#include "utils.h" + +using namespace std; + +namespace { + +inline bool is_windows_drive(const std::string &p) +{ return (p.size()==2 && ((p[0]>='A' && p[0]<='Z') || (p[0]>='a' && p[0]<='z')) && p[1]==':'); } + +} + +namespace Msp { +namespace FS { + +Path::Path() +{ } + +Path::Path(const string &p) +{ + init(p); +} + +Path::Path(const char *p) +{ + init(p); +} + +unsigned Path::size() const +{ + if(path.empty()) + return 0; + if(path.size()==1 && path[0]==DIRSEP) + return 1; + + unsigned count = 1; + for(string::const_iterator i=path.begin(); i!=path.end(); ++i) + if(*i==DIRSEP) ++count; + return count; +} + +bool Path::is_absolute() const +{ +#ifdef WIN32 + if(is_windows_drive((*this)[0])) + return true; +#endif + if(path[0]==DIRSEP) + return true; + return false; +} + +Path Path::subpath(unsigned start, unsigned count) const +{ + Path result; + Iterator i = begin(); + for(unsigned j=0; (j=0) + { + for(Iterator i=begin(); i!=end(); ++i, --n) + if(!n) + return *i; + } + else + { + for(Iterator i=end(); i!=begin();) + { + --i; + if(!++n) + return *i; + } + } + + throw InvalidParameterValue("Path component index out of range"); +} + +bool Path::operator==(const Path &p) const +{ +#ifdef WIN32 + return !strcasecmp(path, p.path); +#else + return path==p.path; +#endif +} + +Path::Iterator Path::begin() const +{ + return Iterator(*this); +} + +Path::Iterator Path::end() const +{ + Iterator i(*this); + i.start=i.end = std::string::npos; + return i; +} + +void Path::init(const string &p) +{ + string::size_type start = 0; + if(p[0]=='/' || p[0]=='\\') + add_component(string(1, DIRSEP)); + while(1) + { + string::size_type slash = p.find_first_of("/\\", start); + if(slash>start) + add_component(p.substr(start, slash-start)); + if(slash==string::npos) + break; + start = slash+1; + } +} + +void Path::add_component(const string &comp) +{ + if(comp.empty()) + ; + else if(comp.size()==1 && comp[0]==DIRSEP) + { + // Replace the path with the root directory +#ifdef WIN32 + unsigned slash = path.find(DIRSEP); + if(is_windows_drive(path.substr(0, slash))) + path = path.substr(0, 2); + else +#endif + path = comp; + } +#ifdef WIN32 + else if(is_windows_drive(comp)) + path = comp; +#endif + else if(comp=="..") + { + if(path.empty() || path==".") + path = comp; + // .. in root directory is a no-op + else if(path.size()==1 && path[0]==DIRSEP) + ; +#ifdef WIN32 + else if(is_windows_drive(path)) + ; +#endif + else + { + string::size_type slash = path.rfind(DIRSEP); + string::size_type start = (slash==string::npos ? 0 : slash+1); + if(!path.compare(start, string::npos, "..")) + { + // If the last component already is a .., add another + path += DIRSEP; + path += comp; + } + else if(slash==string::npos) + path = "."; + else + { + if(slash==0) + slash = 1; + // Otherwise, erase the last component + path.erase(slash, string::npos); + } + } + } + else if(comp!="." || path.empty()) + { + if(comp!="." && path.empty()) + path = "."; + if(path.size()>1 || (path.size()==1 && path[0]!=DIRSEP)) + path += DIRSEP; + path += comp; + } +} + + +Path::Iterator::Iterator(const Path &p): + path(p), + start(0) +{ + if(path.path.empty()) + start=end = string::npos; + else if(path.path[0]==DIRSEP) + end = 1; +#ifdef WIN32 + else if(path.path.size()>2 && path.path[2]==DIRSEP && is_windows_drive(path.path.substr(0, 2))) + end = 2; +#endif + else + end = path.path.find(DIRSEP); +} + +Path::Iterator &Path::Iterator::operator++() +{ + start = end; + if(start>=path.path.size()) + return *this; + if(path.path[start]==DIRSEP) + ++start; + end = path.path.find(DIRSEP, start); + return *this; +} + +Path::Iterator &Path::Iterator::operator--() +{ + if(start==0) + return *this; + + end = start; + if(end>1 && end=path.path.size()) + return string(); + if(start==end) + return string(); + return path.path.substr(start, end-start); +} + +} // namespace FS +} // namespace Msp diff --git a/source/fs/path.h b/source/fs/path.h new file mode 100644 index 0000000..6cc14ad --- /dev/null +++ b/source/fs/path.h @@ -0,0 +1,89 @@ +#ifndef MSP_FS_PATH_H_ +#define MSP_FS_PATH_H_ + +#include +#include + +namespace Msp { +namespace FS { + +enum +{ +#ifdef WIN32 + DIRSEP = '\\' +#else + DIRSEP = '/' +#endif +}; + +/** +Stores a filesystem path. Paths are always stored in a normalized form; there +are never any "." or ".." components in the middle of the path, and relative +paths always begin with a single "." component or a sequence ".." components. +*/ +class Path +{ +public: + class Iterator + { + friend class Path; + + private: + const Path &path; + std::string::size_type start,end; + + Iterator(const Path &); + public: + Iterator &operator++(); + Iterator &operator--(); + std::string operator*() const; + bool operator==(const Iterator &i) const { return (start==i.start && end==i.end); } + bool operator!=(const Iterator &i) const { return !(*this==i); } + }; + +private: + std::string path; + +public: + Path(); + Path(const std::string &); + Path(const char *); + + const std::string &str() const { return path; } + + /// Returns the number of components in the path. + unsigned size() const; + + bool empty() const { return path.empty(); } + + /// Determines whether the path starts from the root directory + bool is_absolute() const; + + /// Extracts a range of components from the path. + Path subpath(unsigned start, unsigned count = static_cast(-1)) const; + + /// Concatenates this path with another one, with usual filesystem semantics + Path operator/(const Path &p) const; + Path &operator/=(const Path &); + + /** Extracts a single component from the path. Negative indices count from + the end of the path. */ + std::string operator[](int) const; + + bool operator==(const Path &) const; + Iterator begin() const; + Iterator end() const; +private: + void init(const std::string &); + + /** Adds a component to the path. It must not contain the directory + separator character. */ + void add_component(const std::string &); +}; + +inline std::ostream &operator<<(std::ostream &o, const Path &p) { o< +#ifdef WIN32 +#include +#endif +#include +#include "path.h" +#include "stat.h" + +namespace Msp { +namespace FS { + +int stat(const Path &fn, struct stat &st) +{ + return ::stat(fn.str().c_str(), &st); +} + +struct stat stat(const Path &fn) +{ + struct stat st; + if(stat(fn, st)==-1) + throw SystemError("stat failed", errno); + return st; +} + +int lstat(const Path &fn, struct stat &st) +{ +#ifdef WIN32 + return stat(fn, st); +#else + return ::lstat(fn.str().c_str(), &st); +#endif +} + +struct stat lstat(const Path &fn) +{ + struct stat st; + if(lstat(fn, st)==-1) + throw SystemError("lstat failed", errno); + return st; +} + +bool exists(const Path &path) +{ + return access(path.str().c_str(), F_OK)==0; +} + +bool is_reg(const Path &path) +{ + struct stat st; + if(stat(path, st)==0) + return S_ISREG(st.st_mode); + return false; +} + +bool is_dir(const Path &path) +{ + struct stat st; + if(stat(path, st)==0) + return S_ISDIR(st.st_mode); + return false; +} + +bool is_link(const Path &path) +{ +#ifdef WIN32 + (void)path; +#else + struct stat st; + if(lstat(path, st)==0) + return S_ISLNK(st.st_mode); +#endif + return false; +} + +} // namespace FS +} // namespace Msp diff --git a/source/fs/stat.h b/source/fs/stat.h new file mode 100644 index 0000000..9056c92 --- /dev/null +++ b/source/fs/stat.h @@ -0,0 +1,40 @@ +#ifndef MSP_FS_STAT_H_ +#define MSP_FS_STAT_H_ + +#include +#include "path.h" + +namespace Msp { +namespace FS { + +/** Gets information about a file. Returns 0 on success or -1 on error. This +version can be used to check for file existence and get information in one +call. */ +int stat(const Path &fn, struct stat &st); + +/** Returns information about a file. This version throws an exception if an +error occurs. */ +struct stat stat(const Path &fn); + +/// Gets information about a file, without following symbolic links +int lstat(const Path &fn, struct stat &st); + +/// Returns information about a file, without following symbolic links +struct stat lstat(const Path &fn); + +/// Tests for existence of a file +bool exists(const Path &path); + +/// Tests whether a path refers to an existing regular file +bool is_reg(const Path &path); + +/// Tests whether a path refers to an existing directory +bool is_dir(const Path &path); + +/// Tests whether a path refers to a symbolic link +bool is_link(const Path &path); + +} // namespace FS +} // namespace Msp + +#endif diff --git a/source/fs/utils.cpp b/source/fs/utils.cpp new file mode 100644 index 0000000..0c29a53 --- /dev/null +++ b/source/fs/utils.cpp @@ -0,0 +1,178 @@ +#include +#include +#include +#ifndef WIN32 +#include +#else +#include +#endif +#include +#include "dir.h" +#include "path.h" +#include "stat.h" +#include "utils.h" + +using namespace std; + +namespace Msp { +namespace FS { + +string basename(const Path &p) +{ + return p[-1]; +} + +Path dirname(const Path &p) +{ + if(p.size()==1) + { + if(p.is_absolute()) + return p; + return "."; + } + return p.subpath(0, p.size()-1); +} + +string basepart(const string &fn) +{ + unsigned dot = fn.rfind('.'); + return fn.substr(0, dot); +} + +string extpart(const string &fn) +{ + string::size_type dot = fn.rfind('.'); + if(dot==string::npos) + return string(); + return fn.substr(dot); +} + +Path fix_case(const Path &path) +{ + bool found = true; + Path result; + for(Path::Iterator i=path.begin(); i!=path.end(); ++i) + { + if(!found || *i=="/") + result /= *i; + else + { + list files; + if(result.size()) + files = list_files(result); + else + files = list_files("."); + + found = false; + for(list::iterator j=files.begin(); (j!=files.end() && !found); ++j) + if(!strcasecmp(*j,*i)) + { + result /= *j; + found = true; + } + + if(!found) + result /= *i; + } + } + + return result; +} + +Path readlink(const Path &link) +{ +#ifdef WIN32 + (void)link; + throw Exception("No symbolic links on win32"); +#else + char buf[4096]; + int len = ::readlink(link.str().c_str(), buf, sizeof(buf)); + if(len==-1) + throw SystemError("readlink failed", errno); + return string(buf, len); +#endif +} + +Path realpath(const Path &path) +{ +#ifdef WIN32 + if(path.is_absolute()) + return path; + else + return getcwd()/path; +#else + list queue(path.begin(), path.end()); + if(!path.is_absolute()) + { + Path cwd = getcwd(); + queue.insert(queue.begin(), cwd.begin(), cwd.end()); + } + + Path real; + unsigned n_links = 0; + while(!queue.empty()) + { + Path next = real/queue.front(); + queue.pop_front(); + + struct stat st = lstat(next); + if(S_ISLNK(st.st_mode)) + { + if(++n_links>64) + throw Exception("Ludicrous amount of symlinks detected in realpath, giving up"); + Path link = readlink(next); + queue.insert(queue.begin(), link.begin(), link.end()); + } + else + real = next; + } + + return real; +#endif +} + +void rename(const Path &from, const Path &to) +{ + if(::rename(from.str().c_str(), to.str().c_str())==-1) + throw SystemError("rename failed", errno); +} + +void unlink(const Path &path) +{ + if(::unlink(path.str().c_str())==-1) + throw SystemError("unlink failed", errno); +} + +Path relative(const Path &path, const Path &base) +{ + Path::Iterator i = path.begin(); + Path::Iterator j = base.begin(); + for(; (i!=path.end() && j!=base.end() && *i==*j); ++i, ++j) ; + + Path result; + for(; j!=base.end(); ++j) + result /= ".."; + for(; i!=path.end(); ++i) + result /= *i; + + return result; +} + +int descendant_depth(const Path &path, const Path &parent) +{ + Path::Iterator i = path.begin(); + Path::Iterator j = parent.begin(); + for(; (i!=path.end() && j!=parent.end() && *i==*j); ++i, ++j) ; + + if(j!=parent.end()) + return -1; + + int result = 0; + for(; i!=path.end(); ++i) + ++result; + + return result; +} + +} // namespace FS +} // namespace Msp diff --git a/source/fs/utils.h b/source/fs/utils.h new file mode 100644 index 0000000..df7bc76 --- /dev/null +++ b/source/fs/utils.h @@ -0,0 +1,48 @@ +#ifndef MSP_FS_UTILS_H_ +#define MSP_FS_UTILS_H_ + +#include "path.h" + +namespace Msp { +namespace FS { + +/// Extracts the last component of the path. +std::string basename(const Path &); + +/// Removes the last component from the path. +Path dirname(const Path &); + +/** Returns the base part of a filename. This includes everything up to the +last dot, but not the dot itself. */ +std::string basepart(const std::string &); + +/** Returns the extension part of a filename. This includes the last dot and +everything after it. */ +std::string extpart(const std::string &); + +/// Fixes the case of a path to match files / directories on the filesystem. +Path fix_case(const Path &path); + +/// Reads the contents of a symbolic link +Path readlink(const Path &path); + +/// Resolves all symlinks from a path. Will always return an absolute path. +Path realpath(const Path &path); + +/// Removes a file +void unlink(const Path &path); + +/// Renames a file. Existing file, if any, is overwritten. +void rename(const Path &from, const Path &to); + +/// Makes a path relative to some base path. That is, base/result==path. +Path relative(const Path &path, const Path &base); + +/** Determines how many levels a path is below another. Returns -1 if path is +not a descendant of parent. */ +int descendant_depth(const Path &path, const Path &parent); + +} // namespace FS +} // namespace Msp + +#endif