+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include "fspath.h"
+
+
+char *make_abs_path(const char *s)
+{
+ char wd[1024];
+ int slen = s ? strlen(s) : 0;
+ int len = slen;
+ char *res;
+
+ if(!s || *s != '/') {
+ getcwd(wd, sizeof wd);
+ int wdlen = strlen(wd);
+ len = wdlen + 1 + slen;
+
+ res = new char[len + 1];
+ if(s) {
+ sprintf(res, "%s/%s", wd, s);
+ } else {
+ memcpy(res, wd, len + 1);
+ }
+ } else {
+ res = new char[len + 1];
+ memcpy(res, s, len + 1);
+ }
+
+ return res;
+}
+
+FSPath::FSPath()
+{
+ abs_path = name = suffix = parent = 0;
+}
+
+FSPath::FSPath(const char *s)
+{
+ abs_path = name = suffix = parent = 0;
+ set_path(s);
+}
+
+FSPath::~FSPath()
+{
+ delete [] abs_path;
+ delete [] parent;
+}
+
+FSPath::FSPath(const FSPath &p)
+{
+ abs_path = name = suffix = parent = 0;
+
+ if(abs_path) {
+ int len = strlen(p.abs_path);
+ abs_path = new char[len + 1];
+ memcpy(abs_path, p.abs_path, len + 1);
+
+ if(p.name) {
+ name = abs_path + (p.name - p.abs_path);
+ }
+ if(p.suffix) {
+ suffix = abs_path + (p.suffix - p.abs_path);
+ }
+ }
+ if(p.parent) {
+ int len = strlen(p.parent);
+ parent = new char[len + 1];
+ memcpy(parent, p.parent, len + 1);
+ }
+}
+
+FSPath &FSPath::operator =(const FSPath &p)
+{
+ if(&p != this) {
+ delete [] abs_path;
+ delete [] parent;
+ abs_path = name = suffix = parent = 0;
+
+ if(p.abs_path) {
+ int len = strlen(p.abs_path);
+ abs_path = new char[len + 1];
+ memcpy(abs_path, p.abs_path, len + 1);
+
+ if(p.name) {
+ name = abs_path + (p.name - p.abs_path);
+ }
+ if(p.suffix) {
+ suffix = abs_path + (p.suffix - p.abs_path);
+ }
+ }
+ if(p.parent) {
+ int len = strlen(p.parent);
+ parent = new char[len + 1];
+ memcpy(parent, p.parent, len + 1);
+ }
+ }
+ return *this;
+}
+
+FSPath::FSPath(FSPath &&p)
+{
+ abs_path = p.abs_path;
+ name = p.name;
+ suffix = p.suffix;
+ parent = p.parent;
+
+ p.abs_path = p.name = p.suffix = p.parent = 0;
+}
+
+FSPath &FSPath::operator =(FSPath &&p)
+{
+ if(&p != this) {
+ delete [] abs_path;
+ delete [] parent;
+
+ abs_path = p.abs_path;
+ name = p.name;
+ suffix = p.suffix;
+ parent = p.parent;
+
+ p.abs_path = p.name = p.suffix = p.parent = 0;
+ }
+ return *this;
+}
+
+void FSPath::sanitize()
+{
+ // first pass, convert and backslashes to slashes
+ char *s = abs_path;
+ while(*s) {
+ if(*s == '\\') {
+ *s = '/';
+ }
+ ++s;
+ }
+
+ // second pass, remove any "./" and "foo/.." parts
+ s = abs_path;
+ int len = strlen(s);
+ while((s = strstr(s, "./"))) {
+ len -= 2;
+ memmove(s, s + 2, len + 1);
+ }
+ assert(len == (int)strlen(abs_path));
+
+ s = abs_path;
+ while((s = strstr(s, "/.."))) {
+ char *start = s;
+ while(start > abs_path && *--start != '/');
+
+ memmove(start, s + 3, len - 2);
+ len -= (s + 3) - start;
+ s = start;
+ }
+ assert(len == (int)strlen(abs_path));
+
+ // remove double slashes
+ while((s = strstr(abs_path, "//"))) {
+ memmove(s, s + 1, len-- - (s - abs_path));
+ }
+ assert(len == (int)strlen(abs_path));
+
+ // remove trailing slash
+ if(abs_path[len - 1] == '/') {
+ abs_path[--len] = 0;
+ }
+ assert(len == (int)strlen(abs_path));
+
+ // setup name and prefix pointers as necessary
+ name = suffix = 0;
+ s = abs_path;
+ while(*s) {
+ if(*s == '/') name = s + 1;
+ if(*s == '.') suffix = s;
+ ++s;
+ }
+
+ if(!name) {
+ name = abs_path; // shouldn't happen, all paths start with a slash
+ fprintf(stderr, "FSPath::sanitize: absolute path \"%s\" isn't absolute!\n", abs_path);
+ }
+
+ if(suffix == abs_path || suffix <= name) {
+ suffix = 0;
+ }
+
+ // create parent name string
+ if(abs_path[0] == '/' && abs_path[1] == 0) {
+ parent = 0; // root dir has no parent
+ } else {
+ int plen = name - 1 - abs_path;
+ assert(plen > 0);
+
+ parent = new char[plen + 1];
+ memcpy(parent, abs_path, plen);
+ parent[plen] = 0;
+ }
+}
+
+bool FSPath::set_path(const char *s)
+{
+ char *tmp = make_abs_path(s);
+ if(!tmp) {
+ return false;
+ }
+
+ delete [] abs_path;
+ delete [] parent;
+ abs_path = tmp;
+ parent = 0;
+
+ sanitize();
+ return true;
+}
+
+const char *FSPath::get_path() const
+{
+ return abs_path;
+}
+
+const char *FSPath::get_name() const
+{
+ return name;
+}
+
+const char *FSPath::get_suffix() const
+{
+ return suffix;
+}
+
+const char *FSPath::get_parent() const
+{
+ return parent;
+}
+
+bool FSPath::append_path(const char *s)
+{
+ if(!s || !*s) return false;
+
+ if(!abs_path) {
+ return set_path(s);
+ }
+
+ if(*s == '/') {
+ return false; // can't append an absolute path
+ }
+
+ int len = strlen(abs_path) + 1 + strlen(s);
+ try {
+ char *tmp = new char[len + 1];
+
+ delete [] abs_path;
+ abs_path = tmp;
+ delete [] parent;
+ parent = 0;
+ }
+ catch(...) {
+ return false;
+ }
+
+ sanitize();
+ return true;
+}
+
+bool FSPath::exists() const
+{
+ struct stat st;
+ return abs_path && stat(abs_path, &st) != -1;
+}
+
+bool FSPath::is_file() const
+{
+ struct stat st;
+ return abs_path && stat(abs_path, &st) != -1 && S_ISREG(st.st_mode);
+}
+
+bool FSPath::is_dir() const
+{
+ struct stat st;
+ return abs_path && stat(abs_path, &st) != -1 && S_ISDIR(st.st_mode);
+}
+
+bool FSPath::is_dev() const
+{
+ struct stat st;
+ unsigned int devmask = S_IFBLK | S_IFCHR;
+ return abs_path && stat(abs_path, &st) != -1 && (st.st_mode & devmask);
+}
+
+FSPath::operator const char* () const
+{
+ return abs_path;
+}