/**
* OHP3D
* Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string>
#include <cstring>
#include <stdexcept>
#include <vector>
#include <sys/xattr.h>
#include "XAttrs.h"
std::string xattrGet(
const std::string fileName,
const std::string name,
bool* exists = nullptr) {
std::string buffer;
ssize_t size = getxattr(fileName.c_str(), name.c_str(), nullptr, 0);
if (size > 0) {
buffer.resize(size);
getxattr(fileName.c_str(), name.c_str(), buffer.data(), buffer.size());
if (exists) *exists = true;
return buffer;
} else if (size == 0) {
if (exists) *exists = true;
return "";
} else if (errno == ENODATA) {
if (exists) *exists = false;
return "";
} else if (errno == ERANGE) {
// rare race condition - the value has changed between the two calls
return xattrGet(fileName, name, exists);
} else {
throw std::logic_error(
std::string("Unable to get extended attribute: ")
+ strerror(errno));
}
}
bool xattrSet(const std::string fileName,
const std::string name,
const std::string value,
bool exists = true) {
int result;
if (exists) {
result = setxattr(
fileName.c_str(),
name.c_str(),
value.c_str(),
value.size(),
0);
} else {
result = removexattr(fileName.c_str(), name.c_str());
if (result < 0 && errno == ENODATA) return false;
}
if (result < 0) throw std::logic_error(
std::string("Unable to set extended attribute: ")
+ strerror(errno));
else return true;
}
std::vector<std::string> xattrList(const std::string fileName) {
std::string buffer;
ssize_t size = listxattr(fileName.c_str(), nullptr, 0);
if (size >= 0) {
buffer.resize(size);
listxattr(fileName.c_str(), buffer.data(), buffer.size());
std::vector<std::string> result;
for (const char* k = buffer.c_str(); strlen(k); k += strlen(k) + 1) {
result.push_back(k);
}
return result;
} else if (errno == ERANGE) {
// rare race condition - the list has changed between the two calls
return xattrList(fileName);
} else {
throw std::logic_error(
std::string("Unable to list extended attributes: ")
+ strerror(errno));
}
}
class XAttrs::Attribute::Impl {
public:
Impl() {
}
virtual ~Impl() {
}
std::shared_ptr<XAttrs::Impl> xattrs;
std::string name;
std::string value;
bool exists = true;
bool loaded = false;
};
class XAttrs::Impl {
public:
std::string nameSpace;
std::string fileName;
std::vector<Attribute> attributes;
bool loaded = false;
const XAttrs::Attribute& save(const XAttrs::Attribute& attribute);
XAttrs::Attribute& load(XAttrs::Attribute& attribute);
};
XAttrs::XAttrs(const std::string& fileName, const std::string& nameSpace) :
impl(std::make_shared<Impl>()) {
impl->nameSpace = nameSpace;
impl->fileName = fileName;
}
XAttrs::~XAttrs() {
}
const std::string XAttrs::getFileName() const {
return impl->fileName;
}
size_t XAttrs::size(bool reload) {
if (reload || !impl->loaded) {
impl->attributes.clear();
std::vector<std::string> names = xattrList(impl->fileName);
const std::string prefix = impl->nameSpace + ".";
for (const std::string& name : names) {
if (name.starts_with(prefix)) {
Attribute a;
a.impl->xattrs = impl;
a.impl->name = name.substr(prefix.size());
a.impl->value = xattrGet(impl->fileName, name, &a.impl->exists);
impl->attributes.push_back(a);
}
}
impl->loaded = true;
}
return impl->attributes.size();
}
XAttrs::Attribute* XAttrs::begin() {
size();
return &impl->attributes[0];
}
XAttrs::Attribute* XAttrs::end() {
size();
return &impl->attributes[impl->attributes.size()];
}
const XAttrs::Attribute&
XAttrs::Impl::save(const XAttrs::Attribute& attribute) {
xattrSet(
fileName,
nameSpace + "." + attribute.impl->name,
attribute.impl->value,
attribute.impl->exists);
loaded &= attribute.impl->exists;
return attribute;
}
XAttrs::Attribute&
XAttrs::Impl::load(XAttrs::Attribute& attribute) {
attribute.impl->value = xattrGet(fileName,
nameSpace + "." + attribute.impl->name, &attribute.impl->exists);
return attribute;
}
XAttrs::Attribute& XAttrs::operator[](std::string name) {
for (XAttrs::Attribute& a : impl->attributes) {
if (a.getName() == name) return a; // impl->load(a);
}
XAttrs::Attribute a;
a.impl->xattrs = impl;
a.impl->name = name;
a.impl->exists = false;
// impl->load(a);
impl->attributes.push_back(a);
return impl->attributes.back();
}
XAttrs::Attribute& XAttrs::operator[](std::size_t index) {
if (impl->attributes.empty()) size();
return impl->attributes[index];
}
XAttrs::Attribute::Attribute() : impl(std::make_shared<Impl>()) {
}
XAttrs::Attribute::~Attribute() {
}
const std::string XAttrs::Attribute::getName() {
return impl->name;
}
const std::string XAttrs::Attribute::getValue(bool reload) {
if (reload || !impl->loaded) impl->xattrs->load(*this);
return impl->value;
}
bool XAttrs::Attribute::exists(bool reload) {
if (reload || !impl->loaded) impl->xattrs->load(*this);
return impl->exists;
}
bool XAttrs::Attribute::missing(bool reload) {
return !exists(reload);
}
XAttrs::Attribute& XAttrs::Attribute::operator=(const std::string& value) {
impl->value = value;
impl->exists = true;
impl->xattrs->save(*this);
return *this;
}
XAttrs::Attribute& XAttrs::Attribute::
operator=(const XAttrs::Attribute& value) {
impl->value = value.impl->value;
impl->exists = value.impl->exists;
impl->xattrs->save(*this);
return *this;
}
XAttrs::Attribute::operator std::string() {
impl->xattrs->load(*this);
return impl->value;
}
std::ostream& operator<<(std::ostream& s, XAttrs::Attribute& a) {
s << a.getValue().c_str();
return s;
}
std::ostream& operator>>(XAttrs::Attribute& a, std::ostream& s) {
s << a.getValue().c_str();
return s;
}
XAttrs::Null::Null() {
impl->exists = false;
}