/*
Copyright (c) 2009 Jean-Francois Dockes

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/

/** \file pxattr.cpp 
    \brief Portable External Attributes API
 */

#if defined(__gnu_linux__) || \
    (defined(__FreeBSD_kernel__)&&defined(__GLIBC__)&&!defined(__FreeBSD__)) ||\
    defined(__CYGWIN32__)
#define PXALINUX
#endif

// If the platform is not supported, let this file be empty instead of
// breaking the compile, this will let the build work if the rest of
// the software is not actually calling us.
#if defined(__FreeBSD__) || defined(PXALINUX) || defined(__APPLE__)


#ifndef TEST_PXATTR
#include <sys/types.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>

#if defined(__FreeBSD__)
#include <sys/extattr.h>
#include <sys/uio.h>
#elif defined(PXALINUX)
#include <sys/xattr.h>
#elif defined(__APPLE__)
#include <sys/xattr.h>
#else
#error "Unknown system can't compile"
#endif

#include "pxattr.h"

namespace pxattr {

class AutoBuf {
public:
    char *buf;
    AutoBuf() : buf(0) {}
    ~AutoBuf() {if (buf) free(buf); buf = 0;}
    bool alloc(int n) 
    {
	if (buf) {
	    free(buf);
	    buf = 0;
	}
	buf = (char *)malloc(n); 
	return buf != 0;
    }
};

static bool 
get(int fd, const string& path, const string& _name, string *value,
    flags flags, nspace dom)
{
    string name;
    if (!sysname(dom, _name, &name)) 
	return false;

    ssize_t ret = -1;
    AutoBuf buf;

#if defined(__FreeBSD__)
    if (fd < 0) {
	if (flags & PXATTR_NOFOLLOW) {
	    ret = extattr_get_link(path.c_str(), EXTATTR_NAMESPACE_USER, 
				   name.c_str(), 0, 0);
	} else {
	    ret = extattr_get_file(path.c_str(), EXTATTR_NAMESPACE_USER, 
				   name.c_str(), 0, 0);
	}
    } else {
	ret = extattr_get_fd(fd, EXTATTR_NAMESPACE_USER, name.c_str(), 0, 0);
    }
    if (ret < 0)
	return false;
    if (!buf.alloc(ret+1)) // Don't want to deal with possible ret=0
	return false;
    if (fd < 0) {
	if (flags & PXATTR_NOFOLLOW) {
	    ret = extattr_get_link(path.c_str(), EXTATTR_NAMESPACE_USER, 
				   name.c_str(), buf.buf, ret);
	} else {
	    ret = extattr_get_file(path.c_str(), EXTATTR_NAMESPACE_USER, 
				   name.c_str(), buf.buf, ret);
	}
    } else {
	ret = extattr_get_fd(fd, EXTATTR_NAMESPACE_USER, 
			     name.c_str(), buf.buf, ret);
    }
#elif defined(PXALINUX)
    if (fd < 0) {
	if (flags & PXATTR_NOFOLLOW) {
	    ret = lgetxattr(path.c_str(), name.c_str(), 0, 0);
	} else {
	    ret = getxattr(path.c_str(), name.c_str(), 0, 0);
	}
    } else {
	ret = fgetxattr(fd, name.c_str(), 0, 0);
    }
    if (ret < 0)
	return false;
    if (!buf.alloc(ret+1)) // Don't want to deal with possible ret=0
	return false;
    if (fd < 0) {
	if (flags & PXATTR_NOFOLLOW) {
	    ret = lgetxattr(path.c_str(), name.c_str(), buf.buf, ret);
	} else {
	    ret = getxattr(path.c_str(), name.c_str(), buf.buf, ret);
	}
    } else {
	ret = fgetxattr(fd, name.c_str(), buf.buf, ret);
    }
#elif defined(__APPLE__)
    if (fd < 0) {
	if (flags & PXATTR_NOFOLLOW) {
	    ret = getxattr(path.c_str(), name.c_str(), 0, 0, 0, XATTR_NOFOLLOW);
	} else {
	    ret = getxattr(path.c_str(), name.c_str(), 0, 0, 0, 0);
	}
    } else {
	ret = fgetxattr(fd, name.c_str(), 0, 0, 0, 0);
    }
    if (ret < 0)
	return false;
    if (!buf.alloc(ret+1)) // Don't want to deal with possible ret=0
	return false;
    if (fd < 0) {
	if (flags & PXATTR_NOFOLLOW) {
	    ret = getxattr(path.c_str(), name.c_str(), buf.buf, ret, 0, 
			   XATTR_NOFOLLOW);
	} else {
	    ret = getxattr(path.c_str(), name.c_str(), buf.buf, ret, 0, 0);
	}
    } else {
	ret = fgetxattr(fd, name.c_str(), buf.buf, ret, 0, 0);
    }
#endif

    if (ret >= 0)
	value->assign(buf.buf, ret);
    return ret >= 0;
}

static bool 
set(int fd, const string& path, const string& _name, 
    const string& value, flags flags, nspace dom)
{
    string name;
    if (!sysname(dom, _name, &name)) 
	return false;

    ssize_t ret = -1;

#if defined(__FreeBSD__)
    
    if (flags & (PXATTR_CREATE|PXATTR_REPLACE)) {
	// Need to test existence
	bool exists = false;
	ssize_t eret;
	if (fd < 0) {
	    if (flags & PXATTR_NOFOLLOW) {
		eret = extattr_get_link(path.c_str(), EXTATTR_NAMESPACE_USER, 
				       name.c_str(), 0, 0);
	    } else {
		eret = extattr_get_file(path.c_str(), EXTATTR_NAMESPACE_USER, 
				       name.c_str(), 0, 0);
	    }
	} else {
	    eret = extattr_get_fd(fd, EXTATTR_NAMESPACE_USER, 
				  name.c_str(), 0, 0);
	}
	if (eret >= 0)
	    exists = true;
	if (eret < 0 && errno != ENOATTR)
	    return false;
	if ((flags & PXATTR_CREATE) && exists) {
	    errno = EEXIST;
	    return false;
	}
	if ((flags & PXATTR_REPLACE) && !exists) {
	    errno = ENOATTR;
	    return false;
	}
    }
    if (fd < 0) {
	if (flags & PXATTR_NOFOLLOW) {
	    ret = extattr_set_link(path.c_str(), EXTATTR_NAMESPACE_USER, 
				   name.c_str(), value.c_str(), value.length());
	} else {
	    ret = extattr_set_file(path.c_str(), EXTATTR_NAMESPACE_USER, 
				   name.c_str(), value.c_str(), value.length());
	}
    } else {
	ret = extattr_set_fd(fd, EXTATTR_NAMESPACE_USER, 
			     name.c_str(), value.c_str(), value.length());
    }
#elif defined(PXALINUX)
    int opts = 0;
    if (flags & PXATTR_CREATE)
	opts = XATTR_CREATE;
    else if (flags & PXATTR_REPLACE)
	opts = XATTR_REPLACE;
    if (fd < 0) {
	if (flags & PXATTR_NOFOLLOW) {
	    ret = lsetxattr(path.c_str(), name.c_str(), value.c_str(), 
			    value.length(), opts);
	} else {
	    ret = setxattr(path.c_str(), name.c_str(), value.c_str(), 
			   value.length(), opts);
	}
    } else {
	ret = fsetxattr(fd, name.c_str(), value.c_str(), value.length(), opts);
    }
#elif defined(__APPLE__)
    int opts = 0;
    if (flags & PXATTR_CREATE)
	opts = XATTR_CREATE;
    else if (flags & PXATTR_REPLACE)
	opts = XATTR_REPLACE;
    if (fd < 0) {
	if (flags & PXATTR_NOFOLLOW) {
	    ret = setxattr(path.c_str(), name.c_str(), value.c_str(), 
			   value.length(),  0, XATTR_NOFOLLOW|opts);
	} else {
	    ret = setxattr(path.c_str(), name.c_str(), value.c_str(), 
			   value.length(),  0, opts);
	}
    } else {
	ret = fsetxattr(fd, name.c_str(), value.c_str(), 
			value.length(), 0, opts);
    }
#endif
    return ret >= 0;
}

static bool 
del(int fd, const string& path, const string& _name, flags flags, nspace dom) 
{
    string name;
    if (!sysname(dom, _name, &name)) 
	return false;

    int ret = -1;

#if defined(__FreeBSD__)
    if (fd < 0) {
	if (flags & PXATTR_NOFOLLOW) {
	    ret = extattr_delete_link(path.c_str(), EXTATTR_NAMESPACE_USER,
				      name.c_str());
	} else {
	    ret = extattr_delete_file(path.c_str(), EXTATTR_NAMESPACE_USER,
				      name.c_str());
	}
    } else {
	ret = extattr_delete_fd(fd, EXTATTR_NAMESPACE_USER, name.c_str());
    }
#elif defined(PXALINUX)
    if (fd < 0) {
	if (flags & PXATTR_NOFOLLOW) {
	    ret = lremovexattr(path.c_str(), name.c_str());
	} else {
	    ret = removexattr(path.c_str(), name.c_str());
	}
    } else {
	ret = fremovexattr(fd, name.c_str());
    }
#elif defined(__APPLE__)
    if (fd < 0) {
	if (flags & PXATTR_NOFOLLOW) {
	    ret = removexattr(path.c_str(), name.c_str(), XATTR_NOFOLLOW);
	} else {
	    ret = removexattr(path.c_str(), name.c_str(), 0);
	}
    } else {
	ret = fremovexattr(fd, name.c_str(), 0);
    }
#endif
    return ret >= 0;
}

static bool 
list(int fd, const string& path, vector<string>* names, flags flags, nspace dom)
{
    ssize_t ret = -1;
    AutoBuf buf;

#if defined(__FreeBSD__)
    if (fd < 0) {
	if (flags & PXATTR_NOFOLLOW) {
	    ret = extattr_list_link(path.c_str(), EXTATTR_NAMESPACE_USER, 0, 0);
	} else {
	    ret = extattr_list_file(path.c_str(), EXTATTR_NAMESPACE_USER, 0, 0);
	}
    } else {
	ret = extattr_list_fd(fd, EXTATTR_NAMESPACE_USER, 0, 0);
    }
    if (ret < 0) 
	return false;
    if (!buf.alloc(ret+1)) // NEEDED on FreeBSD (no ending null)
	return false;
    buf.buf[ret] = 0;
    if (fd < 0) {
	if (flags & PXATTR_NOFOLLOW) {
	    ret = extattr_list_link(path.c_str(), EXTATTR_NAMESPACE_USER, 
				    buf.buf, ret);
	} else {
	    ret = extattr_list_file(path.c_str(), EXTATTR_NAMESPACE_USER, 
				    buf.buf, ret);
	}
    } else {
	ret = extattr_list_fd(fd, EXTATTR_NAMESPACE_USER, buf.buf, ret);
    }
#elif defined(PXALINUX)
    if (fd < 0) {
	if (flags & PXATTR_NOFOLLOW) {
	    ret = llistxattr(path.c_str(), 0, 0);
	} else {
	    ret = listxattr(path.c_str(), 0, 0);
	}
    } else {
	ret = flistxattr(fd, 0, 0);
    }
    if (ret < 0) 
	return false;
    if (!buf.alloc(ret+1)) // Don't want to deal with possible ret=0
	return false;
    if (fd < 0) {
	if (flags & PXATTR_NOFOLLOW) {
	    ret = llistxattr(path.c_str(), buf.buf, ret);
	} else {
	    ret = listxattr(path.c_str(), buf.buf, ret);
	}
    } else {
	ret = flistxattr(fd, buf.buf, ret);
    }
#elif defined(__APPLE__)
    if (fd < 0) {
	if (flags & PXATTR_NOFOLLOW) {
	    ret = listxattr(path.c_str(), 0, 0, XATTR_NOFOLLOW);
	} else {
	    ret = listxattr(path.c_str(), 0, 0, 0);
	}
    } else {
	ret = flistxattr(fd, 0, 0, 0);
    }
    if (ret < 0) 
	return false;
    if (!buf.alloc(ret+1)) // Don't want to deal with possible ret=0
	return false;
    if (fd < 0) {
	if (flags & PXATTR_NOFOLLOW) {
	    ret = listxattr(path.c_str(), buf.buf, ret, XATTR_NOFOLLOW);
	} else {
	    ret = listxattr(path.c_str(), buf.buf, ret, 0);
	}
    } else {
	ret = flistxattr(fd, buf.buf, ret, 0);
    }
#endif

    char *bufstart = buf.buf;

    // All systems return a 0-separated string list except FreeBSD
    // which has length, value pairs, length is a byte. 
#if defined(__FreeBSD__)
    char *cp = buf.buf;
    unsigned int len;
    while (cp < buf.buf + ret + 1) {
	len = *cp;
	*cp = 0;
	cp += len + 1;
    }
    bufstart = buf.buf + 1;
    *cp = 0; // don't forget, we allocated one more
#endif


    if (ret > 0) {
	int pos = 0;
	while (pos < ret) {
	    string n = string(bufstart + pos);
	    string n1;
	    if (pxname(PXATTR_USER, n, &n1)) {
		names->push_back(n1);
	    }
	    pos += n.length() + 1;
	}
    }
    return true;
}

static const string nullstring("");

bool get(const string& path, const string& _name, string *value,
	 flags flags, nspace dom)
{
    return get(-1, path, _name, value, flags, dom);
}
bool get(int fd, const string& _name, string *value, flags flags, nspace dom)
{
    return get(fd, nullstring, _name, value, flags, dom);
}
bool set(const string& path, const string& _name, const string& value,
	 flags flags, nspace dom)
{
    return set(-1, path, _name, value, flags, dom);
}
bool set(int fd, const string& _name, const string& value, 
	 flags flags, nspace dom)
{
    return set(fd, nullstring, _name, value, flags, dom);
}
bool del(const string& path, const string& _name, flags flags, nspace dom) 
{
    return del(-1, path, _name, flags, dom);
}
bool del(int fd, const string& _name, flags flags, nspace dom) 
{
    return del(fd, nullstring, _name, flags, dom);
}
bool list(const string& path, vector<string>* names, flags flags, nspace dom)
{
    return list(-1, path, names, flags, dom);
}
bool list(int fd, vector<string>* names, flags flags, nspace dom)
{
    return list(fd, nullstring, names, flags, dom);
}

#if defined(PXALINUX) || defined(COMPAT1)
static const string userstring("user.");
#else
static const string userstring("");
#endif
bool sysname(nspace dom, const string& pname, string* sname)
{
    if (dom != PXATTR_USER) {
	errno = EINVAL;
	return false;
     }
    *sname = userstring + pname;
    return true;
}

bool pxname(nspace dom, const string& sname, string* pname) 
{
    if (!userstring.empty() && sname.find(userstring) != 0) {
	errno = EINVAL;
	return false;
    }
    *pname = sname.substr(userstring.length());
    return true;
}

} // namespace pxattr

#else // TEST_PXATTR Testing / driver ->

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <ftw.h>

#include <iostream>
#include <fstream>
#include <map>
#include <algorithm>
#include <string>
using namespace std;

#include "pxattr.h"

static void dotests();

// \-quote character c in input \ -> \\, nl -> \n cr -> \rc -> \c
static void quote(const string& in, string& out, int c)
{
    out.clear();
    for (string::const_iterator it = in.begin(); it != in.end(); it++) {
	if (*it == '\\') {
	    out += "\\\\";
	} else if (*it == "\n"[0]) {
	    out += "\\n";
	} else if (*it == "\r"[0]) {
	    out += "\\r";
	} else if (*it == c) {
	    out += "\\";
	    out += c;
	} else {
	    out += *it;
	}
    }
}

// \-unquote input \n -> nl, \r -> cr, \c -> c
static void unquote(const string& in, string& out)
{
    out.clear();
    for (unsigned int i = 0; i < in.size(); i++) {
	if (in[i] == '\\') {
	    if (i == in.size() -1) {
		out += in[i];
	    } else {
		int c = in[++i];
		switch (c) {
		case 'n': out += "\n";break;
		case 'r': out += "\r";break;
		default: out += c;
		}
	    }
	} else {
	    out += in[i];
	}
    }
}

// Find first unquoted c in input: c preceded by odd number of backslashes
string::size_type find_first_unquoted(const string& in, int c)
{
    int q = 0;
    for (unsigned int i = 0;i < in.size(); i++) {
	if (in[i] == '\\') {
	    q++;
	} else if (in[i] == c) {
	    if (q&1) {
		// quoted
		q = 0;
	    } else {
		return i;
	    }
	} else {
	    q = 0;
	}
    }
    return string::npos;
}
static const string PATH_START("Path: ");
static void listattrs(const string& path)
{
    vector<string> names;
    if (!pxattr::list(path, &names)) {
	if (errno == ENOENT) {
	    return;
	}
	perror("pxattr::list");
	exit(1);
    }
    if (names.empty())
	return;

    // Sorting the names would not be necessary but it makes easier comparing
    // backups
    sort(names.begin(), names.end());

    string quoted;
    quote(path, quoted, 0);
    cout << PATH_START << quoted << endl;
    for (vector<string>::const_iterator it = names.begin(); 
	 it != names.end(); it++) {
	string value;
	if (!pxattr::get(path, *it, &value)) {
	    if (errno == ENOENT) {
		return;
	    }
	    perror("pxattr::get");
	    exit(1);
	}
	quote(*it, quoted, '=');
	cout << " " << quoted << "=";
	quote(value, quoted, 0);
	cout << quoted << endl;
    }
}

void setxattr(const string& path, const string& name, const string& value)
{
    if (!pxattr::set(path, name, value)) {
	perror("pxattr::set");
	exit(1);
    }
}

// Restore xattrs stored in file created by pxattr -lR output
static void restore(const char *backupnm)
{
    istream *input;
    ifstream fin;
    if (!strcmp(backupnm, "stdin")) {
	input = &cin;
    } else {
	fin.open(backupnm, ios::in);
	input = &fin;
    }

    bool done = false;
    int linenum = 0;
    string path;
    map<string, string> attrs;
    while (!done) {
	string line;
	getline(*input, line);
	if (!input->good()) {
	    if (input->bad()) {
                cerr << "Input I/O error" << endl;
		exit(1);
	    }
	    done = true;
	} else {
	    linenum++;
	}

	// cout << "Got line " << linenum << " : [" << line << "] done " << 
	// done << endl;

	if (line.find(PATH_START) == 0 || done) {
	    if (!path.empty() && !attrs.empty()) {
		for (map<string,string>::const_iterator it = attrs.begin();
		     it != attrs.end(); it++) {
		    setxattr(path, it->first, it->second);
		}
	    }
	    if (!done) {
		line = line.substr(PATH_START.size(), string::npos);
		unquote(line, path);
		attrs.clear();
	    }
	} else if (line.empty()) {
	    continue;
	} else {
	    // Should be attribute line
	    if (line[0] != ' ') {
		cerr << "Found bad line (no space) at " << linenum << endl;
		exit(1);
	    }
	    string::size_type pos = find_first_unquoted(line, '=');
	    if (pos == string::npos || pos < 2 || pos >= line.size()) {
		cerr << "Found bad line at " << linenum << endl;
		exit(1);
	    }
	    string qname = line.substr(1, pos-1);
	    pair<string,string> entry;
	    unquote(qname, entry.first);
	    unquote(line.substr(pos+1), entry.second);
	    attrs.insert(entry);
	}
    }
}

void  printxattr(const string &path, const string& name)
{
    cout << "Path: " << path << endl;
    string value;
    if (!pxattr::get(path, name, &value)) {
	if (errno == ENOENT) {
	    return;
	}
	perror("pxattr::get");
	exit(1);
    }
    cout << " " << name << " => " << value << endl;
}

void delxattr(const string &path, const string& name) 
{
    if (pxattr::del(path, name) < 0) {
	perror("pxattr::del");
	exit(1);
    }
}

static char *thisprog;
static char usage [] =
"pxattr [-h] -n name pathname [...] : show value for name\n"
"pxattr [-h] -n name -v value pathname [...] : add/replace attribute\n"
"pxattr [-h] -x name pathname [...] : delete attribute\n"
"pxattr [-h] [-l] [-R] pathname [...] : list attribute names and values\n"
" [-h] : don't follow symbolic links (act on link itself)\n"
" [-R] : recursive listing. Args should be directory(ies)\n"
"  For all the options above, if no pathname arguments are given, pxattr\n"
"  will read file names on stdin, one per line.\n"
"pxattr -S <backupfile> Restore xattrs from file created by pxattr -lR output\n"
"               if backupfile is 'stdin', reads from stdin\n"
"pxattr -T: run tests on temp file in current directory" 
"\n"
;
static void
Usage(void)
{
    fprintf(stderr, "%s: usage:\n%s", thisprog, usage);
    exit(1);
}

static int     op_flags;
#define OPT_MOINS 0x1
#define OPT_n	  0x2 
#define OPT_v	  0x4 
#define OPT_h     0x8
#define OPT_x     0x10
#define OPT_l     0x20
#define OPT_T     0x40
#define OPT_R     0x80
#define OPT_S     0x100

static string name, value;

int processfile(const char* fn, const struct stat *sb, int typeflag)
{
    //cout << "processfile " << fn << " opflags " << op_flags << endl;

    if (op_flags & OPT_l) {
	listattrs(fn);
    } else if (op_flags & OPT_n) {
	if (op_flags & OPT_v) {
	    setxattr(fn, name, value);
	} else {
	    printxattr(fn, name);
	} 
    } else if (op_flags & OPT_x) {
	delxattr(fn, name);
    } 
    return 0;
}

int main(int argc, char **argv)
{
    thisprog = argv[0];
    argc--; argv++;

    while (argc > 0 && **argv == '-') {
	(*argv)++;
	if (!(**argv))
	    /* Cas du "adb - core" */
	    Usage();
	while (**argv)
	    switch (*(*argv)++) {
	    case 'l':	op_flags |= OPT_l; break;
	    case 'n':	op_flags |= OPT_n; if (argc < 2)  Usage();
		name = *(++argv); argc--; 
		goto b1;
	    case 'R':	op_flags |= OPT_R; break;
	    case 'S':	op_flags |= OPT_S; break;
	    case 'T':	op_flags |= OPT_T; break;
	    case 'v':	op_flags |= OPT_v; if (argc < 2)  Usage();
		value = *(++argv); argc--; 
		goto b1;
	    case 'x':	op_flags |= OPT_x; if (argc < 2)  Usage();
		name = *(++argv); argc--; 
		goto b1;
	    default: Usage();	break;
	    }
    b1: argc--; argv++;
    }

    if (op_flags & OPT_T)  {
	if (argc > 0)
	    Usage();
	dotests();
	exit(0);
    }

    if (op_flags & OPT_S)  {
	if (argc != 1)
	    Usage();
	restore(argv[0]);
	exit(0);
    }

    // Default option is 'list'
    if ((op_flags&(OPT_l|OPT_n|OPT_x)) == 0)
	op_flags |= OPT_l;

    bool readstdin = false;
    if (argc == 0)
	readstdin = true;

    for (;;) {
	const char *fn = 0;
	if (argc > 0) {
	    fn = *argv++; 
	    argc--;
	} else if (readstdin) {
	    static char filename[1025];
	    if (!fgets(filename, 1024, stdin))
		break;
	    filename[strlen(filename)-1] = 0;
	    fn = filename;
	} else
	    break;

	if (op_flags & OPT_R) {
	    if (ftw(fn, processfile, 20))
		exit(1);
	} else {
	    processfile(fn, 0, 0);
	}
    } 

    exit(0);
}

static void fatal(const string& s)
{
    perror(s.c_str());
    exit(1);
}

static bool testbackups()
{
    static const char *top = "ttop";
    static const char *d1 = "d1";
    static const char *d2 = "d2";
    static const char *tfn1 = "tpxattr1.txt";
    static const char *tfn2 = "tpxattr2.txt";
    static const char *dump = "attrdump.txt";
    static const char *NAMES[] = {"ORG.PXATTR.NAME1", 
				  "ORG=PXATTR\"=\\=\n", 
				  "=", "Name4"};
    static const char *VALUES[] = 
	{"VALUE1", "VALUE2", "VALUE3=VALUE3equal",
	 "VALUE4\n is more like"
	 " normal text\n with new lines and \"\\\" \\\" backslashes"};

    static const int nattrs = sizeof(NAMES) / sizeof(char *);

    if (mkdir(top, 0777))
	fatal("Cant mkdir ttop");
    if (chdir(top))
	fatal("cant chdir ttop");
    if (mkdir(d1, 0777) || mkdir(d2, 0777))
	fatal("Can't mkdir ttdop/dx\n");
    if (chdir(d1))
	fatal("chdir d1");

    int fd;
    if ((fd = open(tfn1, O_RDWR|O_CREAT, 0755)) < 0)
	fatal("create d1/tpxattr1.txt");
    /* Set attrs */
    for (int i = 0; i < nattrs; i++) {
	if (!pxattr::set(fd, NAMES[i], VALUES[i]))
	    fatal("pxattr::set");
    }
    close(fd);
    if ((fd = open(tfn2, O_RDWR|O_CREAT, 0755)) < 0)
	fatal("create d1/tpxattr2.txt");
    /* Set attrs */
    for (int i = 0; i < nattrs; i++) {
	if (!pxattr::set(fd, NAMES[i], VALUES[i]))
	    fatal("pxattr::set");
    }
    close(fd);

    /* Create dump */
    string cmd;
    cmd = string("pxattr -lR . > " ) + dump;
    if (system(cmd.c_str()))
	fatal(cmd + " in d1");
    if (chdir("../d2"))
	fatal("chdir ../d2");
    if (close(open(tfn1, O_RDWR|O_CREAT, 0755)))
	fatal("create d2/tpxattr.txt");
    if (close(open(tfn2, O_RDWR|O_CREAT, 0755)))
	fatal("create d2/tpxattr.txt");
    cmd = string("pxattr -S ../d1/" ) + dump;
    if (system(cmd.c_str()))
	fatal(cmd);
    cmd = string("pxattr -lR . > " ) + dump;
    if (system(cmd.c_str()))
	fatal(cmd + " in d2");
    cmd = string("diff ../d1/") + dump + " " + dump;
    if (system(cmd.c_str()))
	fatal(cmd);
    cmd = string("cat ") + dump;
    system(cmd.c_str());

    if (1) {
	unlink(dump);
	unlink(tfn1);
	unlink(tfn2);
	if (chdir("../d1"))
	    fatal("chdir ../d1");
	unlink(dump);
	unlink(tfn1);
	unlink(tfn2);
	if (chdir("../"))
	    fatal("chdir .. 1");
	if (rmdir(d1))
	    fatal("rmdir d1");
	if (rmdir(d2))
	    fatal("rmdir d2");
	if (chdir("../"))
	    fatal("chdir .. 2");
	if (rmdir(top))
	    fatal("rmdir ttop");
    }
    return true;
}

static void dotests()
{
    static const char *tfn = "pxattr_testtmp.xyz";
    static const char *NAMES[] = {"ORG.PXATTR.NAME1", "ORG.PXATTR.N2", 
				  "ORG.PXATTR.LONGGGGGGGGisSSSHHHHHHHHHNAME3"};
    static const char *VALUES[] = {"VALUE1", "VALUE2", "VALUE3"};
    static bool verbose = true;

    /* Create test file if it doesn't exist, remove all attributes */
    int fd = open(tfn, O_RDWR|O_CREAT, 0755);
    if (fd < 0) {
	perror("open/create");
	exit(1);
    }

    if (verbose)
	fprintf(stdout, "Cleanup old attrs\n");
    vector<string> names;
    if (!pxattr::list(tfn, &names)) {
	perror("pxattr::list");
	exit(1);
    }
    for (vector<string>::const_iterator it = names.begin(); 
	 it != names.end(); it++) {
	string value;
	if (!pxattr::del(fd, *it)) {
	    perror("pxattr::del");
	    exit(1);
	}
    }
    /* Check that there are no attributes left */
    names.clear();
    if (!pxattr::list(tfn, &names)) {
	perror("pxattr::list");
	exit(1);
    }
    if (names.size() != 0) {
	fprintf(stderr, "Attributes remain after initial cleanup !\n");
	for (vector<string>::const_iterator it = names.begin();
	     it != names.end(); it++) {
	    fprintf(stderr, "%s\n", (*it).c_str());
	}
	exit(1);
    }

    /* Create attributes, check existence and value */
    if (verbose)
	fprintf(stdout, "Creating extended attributes\n");
    for (int i = 0; i < 3; i++) {
	if (!pxattr::set(fd, NAMES[i], VALUES[i])) {
	    perror("pxattr::set");
	    exit(1);
	}
    }
    if (verbose)
	fprintf(stdout, "Checking creation\n");
    for (int i = 0; i < 3; i++) {
	string value;
	if (!pxattr::get(tfn, NAMES[i], &value)) {
	    perror("pxattr::get");
	    exit(1);
	}
	if (value.compare(VALUES[i])) {
	    fprintf(stderr, "Wrong value after create !\n");
	    exit(1);
	}
    }

    /* Delete one, check list */
    if (verbose)
	fprintf(stdout, "Delete one\n");
    if (!pxattr::del(tfn, NAMES[1])) {
	perror("pxattr::del one name");
	exit(1);
    }
    if (verbose)
	fprintf(stdout, "Check list\n");
    for (int i = 0; i < 3; i++) {
	string value;
	if (!pxattr::get(fd, NAMES[i], &value)) {
	    if (i == 1)
		continue;
	    perror("pxattr::get");
	    exit(1);
	} else if (i == 1) {
	    fprintf(stderr, "Name at index 1 still exists after deletion\n");
	    exit(1);
	}
	if (value.compare(VALUES[i])) {
	    fprintf(stderr, "Wrong value after delete 1 !\n");
	    exit(1);
	}
    }

    /* Test the CREATE/REPLACE flags */
    // Set existing with flag CREATE should fail
    if (verbose)
	fprintf(stdout, "Testing CREATE/REPLACE flags use\n");
    if (pxattr::set(tfn, NAMES[0], VALUES[0], pxattr::PXATTR_CREATE)) {
	fprintf(stderr, "Create existing with flag CREATE succeeded !\n");
	exit(1);
    }
    // Set new with flag REPLACE should fail
    if (pxattr::set(tfn, NAMES[1], VALUES[1], pxattr::PXATTR_REPLACE)) {
	fprintf(stderr, "Create new with flag REPLACE succeeded !\n");
	exit(1);
    }
    // Set new with flag CREATE should succeed
    if (!pxattr::set(fd, NAMES[1], VALUES[1], pxattr::PXATTR_CREATE)) {
	fprintf(stderr, "Create new with flag CREATE failed !\n");
	exit(1);
    }
    // Set existing with flag REPLACE should succeed
    if (!pxattr::set(fd, NAMES[0], VALUES[0], pxattr::PXATTR_REPLACE)) {
	fprintf(stderr, "Create existing with flag REPLACE failed !\n");
	exit(1);
    }
    close(fd);
    unlink(tfn);

    if (testbackups())
	exit(0);
    exit(1);
}
#endif // Testing pxattr

#endif // Supported systems.
