#include <BeOSBuildCompatibility.h>
#include <syscalls.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string>
#include <fs_attr.h>
#include "fs_impl.h"
#include "fs_descriptors.h"
#if HAIKU_HOST_USE_XATTR_REF
# if defined(HAIKU_HOST_PLATFORM_LINUX)
# include "fs_attr_xattr.h"
# elif defined(HAIKU_HOST_PLATFORM_FREEBSD)
# include "fs_attr_extattr.h"
# elif defined(HAIKU_HOST_PLATFORM_DARWIN)
# include "fs_attr_bsdxattr.h"
# else
# error No attribute support for this host platform!
# endif
#endif
using namespace std;
using namespace BPrivate;
static const char *sAttributeDirBasePath = HAIKU_BUILD_ATTRIBUTES_DIR;
#if HAIKU_HOST_USE_XATTR_REF
static const char* const kIDAttributeName = "id";
#endif
static status_t
init_attribute_dir_base_dir()
{
static bool initialized = false;
static status_t initError;
if (initialized)
return initError;
struct stat st;
initError = B_OK;
if (lstat(sAttributeDirBasePath, &st) == 0) {
if (!S_ISDIR(st.st_mode)) {
fprintf(stderr, "init_attribute_dir_base_dir(): The Attribute "
"directory base directory exists, but is no directory!\n");
initError = B_FILE_ERROR;
}
} else {
if (mkdir(sAttributeDirBasePath, S_IRWXU | S_IRWXG | S_IRWXO) < 0)
initError = errno;
}
initialized = true;
return initError;
}
static string
escape_attr_name(const char *name)
{
string escapedName("_");
while (*name != '\0') {
if (*name == '/')
escapedName += "_s";
else if (*name == '_')
escapedName += "__";
else
escapedName += *name;
name++;
}
return escapedName;
}
static string
deescape_attr_name(const char *name)
{
if (name[0] != '_') {
debugger("deescape_attr_name(): name doesn't start with '_'!\n");
return "___";
}
name++;
string deescapedName;
while (*name != '\0') {
if (*name == '_') {
name++;
if (*name == 's') {
deescapedName += '/';
} else if (*name == '_') {
deescapedName += '_';
} else {
debugger("deescape_attr_name(): name contains invalid escaped "
"sequence!\n");
name--;
}
} else
deescapedName += *name;
name++;
}
return deescapedName;
}
#if HAIKU_HOST_USE_XATTR_REF
static status_t
make_unique_node_id(string& _id)
{
int fd = open("/dev/urandom", O_RDONLY);
if (fd < 0) {
fd = open("/dev/random", O_RDONLY);
if (fd < 0)
return B_NOT_SUPPORTED;
}
uint8 buffer[16];
ssize_t bytesRead = read(fd, buffer, sizeof(buffer));
status_t error = B_OK;
if (bytesRead < 0)
error = errno;
close(fd);
if (error != B_OK)
return error;
if (bytesRead != (ssize_t)sizeof(buffer))
error = B_ERROR;
static const char* const kHexChars = "0123456789abcdef";
_id.clear();
for (size_t i = 0; i < sizeof(buffer); i++) {
_id += kHexChars[buffer[i] >> 4];
_id += kHexChars[buffer[i] & 0xf];
}
return B_OK;
}
static status_t
get_id_attribute(const char *path, int fd, string& _id)
{
(void)list_attributes;
(void)remove_attribute;
string attributeName(kAttributeNamespace);
attributeName += kIDAttributeName;
char buffer[64];
ssize_t bytesRead = get_attribute(fd, path, attributeName.c_str(), buffer,
sizeof(buffer));
if (bytesRead < 0) {
status_t error = errno;
struct stat st;
if (path == NULL || lstat(path, &st) < 0 || !S_ISLNK(st.st_mode))
return error;
char buffer[32];
snprintf(buffer, sizeof(buffer), "symlink-%" B_PRIdINO, st.st_ino);
_id = buffer;
return B_OK;
}
_id = string(buffer, bytesRead);
return B_OK;
}
static status_t
set_id_attribute(const char *path, int fd, const char* id)
{
string attributeName(kAttributeNamespace);
attributeName += kIDAttributeName;
if (set_attribute(fd, path, attributeName.c_str(), id, strlen(id)) < 0)
return errno;
return B_OK;
}
static string
get_attribute_dir_path(NodeRef ref, const char *path, int fd)
{
string id;
status_t error = get_id_attribute(path, fd, id);
if (error != B_OK)
id = "_no_attributes_";
string attrDirPath(sAttributeDirBasePath);
attrDirPath += '/';
attrDirPath += id;
return attrDirPath;
}
static status_t
get_attribute_dir_path_needed(NodeRef ref, const char *path, int fd,
string& _attrDirPath)
{
string id;
status_t error = get_id_attribute(path, fd, id);
if (error != B_OK) {
error = make_unique_node_id(id);
if (error != B_OK)
return error;
error = set_id_attribute(path, fd, id.c_str());
if (error != B_OK)
return error;
}
_attrDirPath = sAttributeDirBasePath;
_attrDirPath += '/';
_attrDirPath += id;
return B_OK;
}
#else
static string
get_attribute_dir_path(NodeRef ref, const char *path, int fd)
{
string attrDirPath(sAttributeDirBasePath);
char buffer[32];
sprintf(buffer, "/%" B_PRIdINO, ref.node);
attrDirPath += buffer;
return attrDirPath;
}
static status_t
get_attribute_dir_path_needed(NodeRef ref, const char *path, int fd,
string& _attrDirPath)
{
_attrDirPath = get_attribute_dir_path(ref, path, fd);
return B_OK;
}
#endif
static status_t
ensure_attribute_dir_exists(NodeRef ref, const char *path, int fd)
{
status_t error = init_attribute_dir_base_dir();
if (error != B_OK)
return error;
string attrDirPath;
error = get_attribute_dir_path_needed(ref, path, fd, attrDirPath);
if (error != B_OK)
return error;
struct stat st;
if (lstat(attrDirPath.c_str(), &st) == 0) {
if (!S_ISDIR(st.st_mode)) {
fprintf(stderr, "ensure_attribute_dir_exists(): Attribute "
"directory for node %lld exists, but is no directory!\n",
(long long)ref.node);
return B_FILE_ERROR;
}
return B_OK;
}
if (mkdir(attrDirPath.c_str(), S_IRWXU | S_IRWXG | S_IRWXO) < 0)
return errno;
return B_OK;
}
static DIR *
open_attr_dir(NodeRef ref, const char *path, int fd)
{
status_t error = ensure_attribute_dir_exists(ref, path, fd);
if (error != B_OK) {
errno = error;
return NULL;
}
string dirPath(get_attribute_dir_path(ref, path, fd));
return opendir(dirPath.c_str());
}
static status_t
get_attribute_path(NodeRef ref, const char *path, int fd,
const char *attribute, string &attrPath, string &typePath)
{
if (!attribute || strlen(attribute) == 0)
return B_BAD_VALUE;
status_t error = ensure_attribute_dir_exists(ref, path, fd);
if (error != B_OK) {
errno = error;
return -1;
}
attrPath = get_attribute_dir_path(ref, path, fd) + '/';
string attrName(escape_attr_name(attribute));
typePath = attrPath + "t" + attrName;
attrPath += attrName;
return B_OK;
}
static status_t
get_attribute_path_virtual_fd(int fd, const char *attribute, string &attrPath,
string &typePath)
{
struct stat st;
status_t error = _kern_read_stat(fd, NULL, false, &st, sizeof(st));
if (error != B_OK)
return error;
NodeRef ref(st);
string path;
bool pathValid = (get_path(fd, NULL, path) == B_OK);
return get_attribute_path(ref, (pathValid ? path.c_str() : NULL),
(pathValid ? -1 : fd), attribute, attrPath, typePath);
}
static status_t
get_attribute_path(int fd, const char *attribute, string &attrPath,
string &typePath)
{
if (get_descriptor(fd)) {
return get_attribute_path_virtual_fd(fd, attribute, attrPath,
typePath);
} else {
struct stat st;
if (fstat(fd, &st) < 0)
return errno;
NodeRef ref(st);
return get_attribute_path(ref, NULL, fd, attribute, attrPath, typePath);
}
}
DIR *
fs_open_attr_dir(const char *path)
{
struct stat st;
if (lstat(path, &st))
return NULL;
return open_attr_dir(NodeRef(st), path, -1);
}
DIR *
fs_fopen_attr_dir(int fd)
{
struct stat st;
status_t error = _kern_read_stat(fd, NULL, false, &st,
sizeof(struct stat));
if (error != B_OK) {
errno = error;
return NULL;
}
string path;
bool pathValid = (get_path(fd, NULL, path) == B_OK);
return open_attr_dir(NodeRef(st), (pathValid ? path.c_str() : NULL),
(pathValid ? -1 : fd));
}
int
fs_close_attr_dir(DIR *dir)
{
return closedir(dir);
}
struct dirent *
fs_read_attr_dir(DIR *dir)
{
struct dirent *entry = NULL;
while (true) {
entry = readdir(dir);
if (!entry)
return NULL;
if (entry->d_name[0] == '_') {
string attrName = deescape_attr_name(entry->d_name);
strcpy(entry->d_name, attrName.c_str());
return entry;
}
}
}
void
fs_rewind_attr_dir(DIR *dir)
{
rewinddir(dir);
}
int
fs_fopen_attr(int fd, const char *attribute, uint32 type, int openMode)
{
if (!attribute) {
errno = B_BAD_VALUE;
return -1;
}
string attrPath;
string typePath;
status_t error = get_attribute_path(fd, attribute, attrPath, typePath);
if (error != B_OK) {
errno = error;
return -1;
}
struct stat st;
bool exists = (lstat(attrPath.c_str(), &st) == 0);
int attrFD = open(attrPath.c_str(), openMode, S_IRWXU | S_IRWXG | S_IRWXO);
if (attrFD < 0)
return -1;
if (!exists) {
int typeFD = creat(typePath.c_str(), S_IRWXU | S_IRWXG | S_IRWXO);
if (typeFD >= 0) {
if (write(typeFD, &type, sizeof(type)) < 0)
error = errno;
close(typeFD);
} else
error = errno;
if (error != B_OK) {
if (typeFD > 0) {
unlink(typePath.c_str());
}
close(attrFD);
unlink(attrPath.c_str());
errno = error;
return -1;
}
}
return attrFD;
}
int
fs_close_attr(int fd)
{
return close(fd);
}
ssize_t
fs_read_attr(int fd, const char *attribute, uint32 type, off_t pos,
void *buffer, size_t readBytes)
{
int attrFD = fs_fopen_attr(fd, attribute, type, O_RDONLY);
if (attrFD < 0)
return attrFD;
ssize_t bytesRead = read_pos(attrFD, pos, buffer, readBytes);
status_t error = errno;
fs_close_attr(attrFD);
if (bytesRead < 0) {
errno = error;
return -1;
}
return bytesRead;
}
ssize_t
fs_write_attr(int fd, const char *attribute, uint32 type, off_t pos,
const void *buffer, size_t readBytes)
{
int attrFD = fs_fopen_attr(fd, attribute, type,
O_WRONLY | O_CREAT | O_TRUNC);
if (attrFD < 0) {
struct stat st;
if (strcmp(attribute, "BEOS:TYPE") == 0 && fstat(fd, &st) == 0
&& S_ISLNK(st.st_mode)) {
return readBytes;
}
return attrFD;
}
ssize_t bytesWritten = write_pos(attrFD, pos, buffer, readBytes);
status_t error = errno;
fs_close_attr(attrFD);
if (bytesWritten < 0) {
errno = error;
return -1;
}
return bytesWritten;
}
int
fs_remove_attr(int fd, const char *attribute)
{
if (!attribute) {
errno = B_BAD_VALUE;
return -1;
}
string attrPath;
string typePath;
status_t error = get_attribute_path(fd, attribute, attrPath, typePath);
if (error != B_OK) {
errno = error;
return -1;
}
if (unlink(attrPath.c_str()) < 0)
return -1;
unlink(typePath.c_str());
return B_OK;
}
int
fs_stat_attr(int fd, const char *attribute, struct attr_info *attrInfo)
{
if (!attribute || !attrInfo) {
errno = B_BAD_VALUE;
return -1;
}
string attrPath;
string typePath;
status_t error = get_attribute_path(fd, attribute, attrPath, typePath);
if (error != B_OK) {
errno = error;
return -1;
}
struct stat st;
if (lstat(attrPath.c_str(), &st) < 0)
return -1;
attrInfo->size = st.st_size;
int typeFD = open(typePath.c_str(), O_RDONLY);
if (typeFD < 0)
return -1;
ssize_t bytesRead = read(typeFD, &attrInfo->type, sizeof(attrInfo->type));
if (bytesRead < 0)
error = errno;
else if (bytesRead < (ssize_t)sizeof(attrInfo->type))
error = B_FILE_ERROR;
close(typeFD);
if (error != B_OK) {
errno = error;
return -1;
}
return 0;
}
int
_kern_open_attr_dir(int fd, const char *path)
{
struct stat st;
status_t error = _kern_read_stat(fd, path, false, &st,
sizeof(struct stat));
if (error != B_OK) {
errno = error;
return -1;
}
NodeRef ref(st);
string realPath;
if (path) {
error = get_path(fd, path, realPath);
if (error != B_OK)
return error;
}
DIR *dir = open_attr_dir(ref, (path ? realPath.c_str() : NULL),
(path ? -1 : fd));
if (!dir)
return errno;
AttrDirDescriptor *descriptor = new AttrDirDescriptor(dir, ref);
return add_descriptor(descriptor);
}
status_t
_kern_rename_attr(int fromFile, const char *fromName, int toFile,
const char *toName)
{
if (!fromName || !toName)
return B_BAD_VALUE;
string fromAttrPath;
string fromTypePath;
status_t error = get_attribute_path_virtual_fd(fromFile, fromName,
fromAttrPath, fromTypePath);
if (error != B_OK)
return error;
string toAttrPath;
string toTypePath;
error = get_attribute_path_virtual_fd(toFile, toName, toAttrPath,
toTypePath);
if (error != B_OK)
return error;
if (rename(fromAttrPath.c_str(), toAttrPath.c_str()) < 0)
return errno;
if (rename(fromTypePath.c_str(), toTypePath.c_str()) < 0) {
error = errno;
rename(toAttrPath.c_str(), fromAttrPath.c_str());
return error;
}
return B_OK;
}
status_t
_kern_remove_attr(int fd, const char *name)
{
if (!name)
return B_BAD_VALUE;
string attrPath;
string typePath;
status_t error = get_attribute_path_virtual_fd(fd, name, attrPath,
typePath);
if (error != B_OK)
return error;
if (unlink(attrPath.c_str()) < 0)
return errno;
unlink(typePath.c_str());
return B_OK;
}
extern "C" bool __get_attribute_dir_path(const struct stat* st,
const char* path, char* buffer);
bool
__get_attribute_dir_path(const struct stat* st, const char* path, char* buffer)
{
NodeRef ref(*st);
string dirPath = get_attribute_dir_path(ref, path, -1);
strcpy(buffer, dirPath.c_str());
return true;
}