1055 lines
26 KiB
C++
1055 lines
26 KiB
C++
/*
|
|
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;
|
|
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.
|