Merge branch 'fs-master'
authorMikko Rasa <tdb@tdb.fi>
Thu, 28 Jul 2011 11:19:27 +0000 (14:19 +0300)
committerMikko Rasa <tdb@tdb.fi>
Thu, 28 Jul 2011 11:19:27 +0000 (14:19 +0300)
Conflicts:
.gitignore
Build
Changelog.txt

Build
Changelog.txt
source/fs/dir.cpp [new file with mode: 0644]
source/fs/dir.h [new file with mode: 0644]
source/fs/path.cpp [new file with mode: 0644]
source/fs/path.h [new file with mode: 0644]
source/fs/stat.cpp [new file with mode: 0644]
source/fs/stat.h [new file with mode: 0644]
source/fs/utils.cpp [new file with mode: 0644]
source/fs/utils.h [new file with mode: 0644]

diff --git a/Build b/Build
index 740887bdfbda393f1e507a5b63fe14885cfab6be..a4758f420dd20e11cb253648648911079ee5acba 100644 (file)
--- 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;
        };
 
index 498e9ff7422a35c8b230cda27da1d413c2ef001a..c1e8def291266763f14f6071d560a2b8602081a4 100644 (file)
 
 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 (file)
index 0000000..0832854
--- /dev/null
@@ -0,0 +1,220 @@
+#include <cstdlib>
+#include <cerrno>
+#include <dirent.h>
+#include <sys/stat.h>
+#ifdef WIN32
+#include <shlobj.h>
+#endif
+#include <msp/core/except.h>
+#include <msp/strings/regex.h>
+#include <msp/strings/utils.h>
+#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<string> dirs = split(path, ':');
+                       for(vector<string>::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<string> files = list_files(path);
+       for(list<string>::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<string> list_files(const Path &path)
+{
+       return list_filtered(path, string());
+}
+
+list<string> list_filtered(const Path &path, const string &filter)
+{
+       Regex r_filter(filter);
+
+       list<string> 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 (file)
index 0000000..5544a31
--- /dev/null
@@ -0,0 +1,53 @@
+#ifndef MSP_FS_DIR_H_
+#define MSP_FS_DIR_H_
+
+#include <list>
+#include <string>
+#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<std::string> list_files(const Path &path);
+
+/// Lists the contents of a directory, filtered with a regex
+std::list<std::string> 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 (file)
index 0000000..52b1da2
--- /dev/null
@@ -0,0 +1,263 @@
+#include <msp/core/except.h>
+#include <msp/strings/utils.h>
+#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<start && i!=end()); ++j)
+               ++i;
+       for(unsigned j=0; (j<count && i!=end()); ++j)
+       {
+               result /= *i;
+               ++i;
+       }
+       return result;
+}
+
+Path Path::operator/(const Path &p) const
+{
+       Path a = *this;
+       a /= p;
+       return a;
+}
+
+Path &Path::operator/=(const Path &p)
+{
+       if(p.is_absolute())
+               path = p.path;
+       else
+       {
+               for(Iterator i=p.begin(); i!=p.end(); ++i)
+                       add_component(*i);
+       }
+       return *this;
+}
+
+string Path::operator[](int n) const
+{
+       if(n>=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() && path.path[end]!=DIRSEP)
+               --end;
+
+       start = path.path.rfind(DIRSEP, end-1);
+       if(start==string::npos)
+               start = 0;
+       else if(start<end-1)
+               ++start;
+
+       return *this;
+}
+
+string Path::Iterator::operator*() const
+{
+       if(start>=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 (file)
index 0000000..6cc14ad
--- /dev/null
@@ -0,0 +1,89 @@
+#ifndef MSP_FS_PATH_H_
+#define MSP_FS_PATH_H_
+
+#include <ostream>
+#include <string>
+
+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<unsigned>(-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<<p.str(); return o; }
+
+} // namespace FS
+} // namespace Msp
+
+#endif
diff --git a/source/fs/stat.cpp b/source/fs/stat.cpp
new file mode 100644 (file)
index 0000000..867256e
--- /dev/null
@@ -0,0 +1,76 @@
+#include <cerrno>
+#ifdef WIN32
+#include <io.h>
+#endif
+#include <msp/core/except.h>
+#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 (file)
index 0000000..9056c92
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef MSP_FS_STAT_H_
+#define MSP_FS_STAT_H_
+
+#include <sys/stat.h>
+#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 (file)
index 0000000..0c29a53
--- /dev/null
@@ -0,0 +1,178 @@
+#include <cerrno>
+#include <cstdio>
+#include <msp/core/except.h>
+#ifndef WIN32
+#include <fnmatch.h>
+#else
+#include <msp/strings/glob.h>
+#endif
+#include <msp/strings/utils.h>
+#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<string> files;
+                       if(result.size())
+                               files = list_files(result);
+                       else
+                               files = list_files(".");
+
+                       found = false;
+                       for(list<string>::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<string> 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 (file)
index 0000000..df7bc76
--- /dev/null
@@ -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