⛏️ index : haiku.git


#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"


// Include the interface to the host platform attributes support, if it shall be
// used to tag files with unique IDs to identify their attribute directory.
#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


// init_attribute_dir_base_dir
static status_t
init_attribute_dir_base_dir()
{
	static bool initialized = false;
	static status_t initError;

	if (initialized)
		return initError;

	// stat the dir
	struct stat st;
	initError = B_OK;
	if (lstat(sAttributeDirBasePath, &st) == 0) {
		if (!S_ISDIR(st.st_mode)) {
			// the attribute dir base dir is no directory
			fprintf(stderr, "init_attribute_dir_base_dir(): The Attribute "
				"directory base directory exists, but is no directory!\n");
			initError = B_FILE_ERROR;
		}

	} else {
		// doesn't exist yet: create it
		if (mkdir(sAttributeDirBasePath, S_IRWXU | S_IRWXG | S_IRWXO) < 0)
			initError = errno;
	}

	initialized = true;
	return initError;
}

// escape_attr_name
static string
escape_attr_name(const char *name)
{
	string escapedName("_");
	while (*name != '\0') {
		// we replace '/' with "_s" and '_' with "__"
		if (*name == '/')
			escapedName += "_s";
		else if (*name == '_')
			escapedName += "__";
		else
			escapedName += *name;

		name++;
	}

	return escapedName;
}

// deescape_attr_name
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)
{
	// open random device
	int fd = open("/dev/urandom", O_RDONLY);
	if (fd < 0) {
		fd = open("/dev/random", O_RDONLY);
		if (fd < 0)
			return B_NOT_SUPPORTED;
	}

	// read bytes
	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;

	// convert to hex string
	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)
{
	// list_attributes() and remove_attribute() are unused here -- prevent the
	// warning
	(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) {
		// On Linux only priviledged users are allowed to set attributes on
		// symlinks. So, if this is a symlink, we don't even try and instead
		// construct a symlink specific node ID.
		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


// ensure_attribute_dir_exists
static status_t
ensure_attribute_dir_exists(NodeRef ref, const char *path, int fd)
{
	// init the base directory and get the attribute directory path
	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;

	// stat the dir
	struct stat st;
	if (lstat(attrDirPath.c_str(), &st) == 0) {
		if (!S_ISDIR(st.st_mode)) {
			// the attribute dir is no directory
			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;
	}

	// doesn't exist yet: create it
	if (mkdir(attrDirPath.c_str(), S_IRWXU | S_IRWXG | S_IRWXO) < 0)
		return errno;

	return B_OK;
}

// open_attr_dir
static DIR *
open_attr_dir(NodeRef ref, const char *path, int fd)
{
	// make sure the directory exists
	status_t error = ensure_attribute_dir_exists(ref, path, fd);
	if (error != B_OK) {
		errno = error;
		return NULL;
	}

	// open it
	string dirPath(get_attribute_dir_path(ref, path, fd));
	return opendir(dirPath.c_str());
}

// get_attribute_path
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;

	// make sure the attribute dir for the node exits
	status_t error = ensure_attribute_dir_exists(ref, path, fd);
	if (error != B_OK) {
		errno = error;
		return -1;
	}

	// construct the attribute path
	attrPath = get_attribute_dir_path(ref, path, fd) + '/';
	string attrName(escape_attr_name(attribute));
	typePath = attrPath + "t" + attrName;
	attrPath += attrName;

	return B_OK;
}


// get_attribute_path_virtual_fd
static status_t
get_attribute_path_virtual_fd(int fd, const char *attribute, string &attrPath,
	string &typePath)
{
	// stat the file to get a NodeRef
	struct stat st;
	status_t error = _kern_read_stat(fd, NULL, false, &st, sizeof(st));
	if (error != B_OK)
		return error;
	NodeRef ref(st);

	// Try to get a path. If we can't get a path, this is must be a "real"
	// (i.e. system) file descriptor, which is just as well.
	string path;
	bool pathValid = (get_path(fd, NULL, path) == B_OK);

	// get the attribute path
	return get_attribute_path(ref, (pathValid ? path.c_str() : NULL),
		(pathValid ? -1 : fd), attribute, attrPath, typePath);
}


// get_attribute_path
static status_t
get_attribute_path(int fd, const char *attribute, string &attrPath,
	string &typePath)
{
	if (get_descriptor(fd)) {
		// This is a virtual file descriptor -- we have a special function
		// for handling it.
		return  get_attribute_path_virtual_fd(fd, attribute, attrPath,
			typePath);
	} else {
		// This is a real (i.e. system) file descriptor -- fstat() it and
		// build the path.

		// stat the file to get a NodeRef
		struct stat st;
		if (fstat(fd, &st) < 0)
			return errno;
		NodeRef ref(st);

		return get_attribute_path(ref, NULL, fd, attribute, attrPath, typePath);
	}
}


// # pragma mark - Public API


// fs_open_attr_dir
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);
}

// fs_fopen_attr_dir
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;
	}

	// Try to get a path. If we can't get a path, this is must be a "real"
	// (i.e. system) file descriptor, which is just as well.
	string path;
	bool pathValid = (get_path(fd, NULL, path) == B_OK);

	// get the attribute path
	return open_attr_dir(NodeRef(st), (pathValid ? path.c_str() : NULL),
		(pathValid ? -1 : fd));
}

// fs_close_attr_dir
int
fs_close_attr_dir(DIR *dir)
{
	return closedir(dir);
}

// fs_read_attr_dir
struct dirent *
fs_read_attr_dir(DIR *dir)
{
	struct dirent *entry = NULL;
	while (true) {
		// read the next entry
		entry = readdir(dir);
		if (!entry)
			return NULL;

		// ignore administrative entries; the
		if (entry->d_name[0] == '_') {
			string attrName = deescape_attr_name(entry->d_name);
			strcpy(entry->d_name, attrName.c_str());
			return entry;
		}
	}
}

// fs_rewind_attr_dir
void
fs_rewind_attr_dir(DIR *dir)
{
	rewinddir(dir);
}

// fs_fopen_attr
int
fs_fopen_attr(int fd, const char *attribute, uint32 type, int openMode)
{
	if (!attribute) {
		errno = B_BAD_VALUE;
		return -1;
	}

	// get the attribute path
	string attrPath;
	string typePath;
	status_t error = get_attribute_path(fd, attribute, attrPath, typePath);
	if (error != B_OK) {
		errno = error;
		return -1;
	}

	// check, if the attribute already exists
	struct stat st;
	bool exists = (lstat(attrPath.c_str(), &st) == 0);

	// open the attribute
	int attrFD = open(attrPath.c_str(), openMode, S_IRWXU | S_IRWXG | S_IRWXO);
	if (attrFD < 0)
		return -1;

	// set the type, if the attribute didn't exist yet
	if (!exists) {
		// create a file prefixed "t"
		int typeFD = creat(typePath.c_str(), S_IRWXU | S_IRWXG | S_IRWXO);
		if (typeFD >= 0) {
			// write the type into the file
			if (write(typeFD, &type, sizeof(type)) < 0)
				error = errno;

			close(typeFD);

		} else
			error = errno;

		// remove type and attribute file, if something went wrong
		if (error != B_OK) {
			if (typeFD > 0) {
				unlink(typePath.c_str());
			}

			close(attrFD);
			unlink(attrPath.c_str());

			errno = error;
			return -1;
		}
	}

	return attrFD;
}

// fs_close_attr
int
fs_close_attr(int fd)
{
	return close(fd);
}

// fs_read_attr
ssize_t
fs_read_attr(int fd, const char *attribute, uint32 type, off_t pos,
	void *buffer, size_t readBytes)
{
	// open the attribute
	int attrFD = fs_fopen_attr(fd, attribute, type, O_RDONLY);
	if (attrFD < 0)
		return attrFD;

	// read
	ssize_t bytesRead = read_pos(attrFD, pos, buffer, readBytes);
	status_t error = errno;

	// close the attribute
	fs_close_attr(attrFD);

	if (bytesRead < 0) {
		errno = error;
		return -1;
	}

	return bytesRead;
}

// fs_write_attr
ssize_t
fs_write_attr(int fd, const char *attribute, uint32 type, off_t pos,
	const void *buffer, size_t readBytes)
{
	// open the attribute
	int attrFD = fs_fopen_attr(fd, attribute, type,
		O_WRONLY | O_CREAT | O_TRUNC);
	if (attrFD < 0) {
		// Setting user attributes on symlinks is not allowed (xattr). So, if
		// this is a symlink and we're only supposed to write a "BEOS:TYPE"
		// attribute we silently pretend to have succeeded.
		struct stat st;
		if (strcmp(attribute, "BEOS:TYPE") == 0 && fstat(fd, &st) == 0
			&& S_ISLNK(st.st_mode)) {
			return readBytes;
		}
		return attrFD;
	}

	// read
	ssize_t bytesWritten = write_pos(attrFD, pos, buffer, readBytes);
	status_t error = errno;

	// close the attribute
	fs_close_attr(attrFD);

	if (bytesWritten < 0) {
		errno = error;
		return -1;
	}

	return bytesWritten;
}

// fs_remove_attr
int
fs_remove_attr(int fd, const char *attribute)
{
	if (!attribute) {
		errno = B_BAD_VALUE;
		return -1;
	}

	// get the attribute path
	string attrPath;
	string typePath;
	status_t error = get_attribute_path(fd, attribute, attrPath, typePath);
	if (error != B_OK) {
		errno = error;
		return -1;
	}

	// remove the attribute
	if (unlink(attrPath.c_str()) < 0)
		return -1;

	unlink(typePath.c_str());

	return B_OK;
}

// fs_stat_attr
int
fs_stat_attr(int fd, const char *attribute, struct attr_info *attrInfo)
{
	if (!attribute || !attrInfo) {
		errno = B_BAD_VALUE;
		return -1;
	}

	// get the attribute path
	string attrPath;
	string typePath;
	status_t error = get_attribute_path(fd, attribute, attrPath, typePath);
	if (error != B_OK) {
		errno = error;
		return -1;
	}

	// stat the attribute file to get the size of the attribute
	struct stat st;
	if (lstat(attrPath.c_str(), &st) < 0)
		return -1;

	attrInfo->size = st.st_size;

	// now open the attribute type file and read the attribute's type
	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);

	// fail on error
	if (error != B_OK) {
		errno = error;
		return -1;
	}

	return 0;
}


// #pragma mark - Private Syscalls


// _kern_open_attr_dir
int
_kern_open_attr_dir(int fd, const char *path)
{
	// get node ref for the node
	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);

	// If a path was given, get a usable path.
	string realPath;
	if (path) {
		error = get_path(fd, path, realPath);
		if (error != B_OK)
			return error;
	}

	// open the attr dir
	DIR *dir = open_attr_dir(ref, (path ? realPath.c_str() : NULL),
		(path ? -1 : fd));
	if (!dir)
		return errno;

	// create descriptor
	AttrDirDescriptor *descriptor = new AttrDirDescriptor(dir, ref);
	return add_descriptor(descriptor);
}

// _kern_rename_attr
status_t
_kern_rename_attr(int fromFile, const char *fromName, int toFile,
	const char *toName)
{
	if (!fromName || !toName)
		return B_BAD_VALUE;

	// get the attribute paths
	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;

	// rename the attribute and type files
	if (rename(fromAttrPath.c_str(), toAttrPath.c_str()) < 0)
		return errno;

	if (rename(fromTypePath.c_str(), toTypePath.c_str()) < 0) {
		// renaming the type file failed: try to rename back the attribute file
		error = errno;

		rename(toAttrPath.c_str(), fromAttrPath.c_str());

		return error;
	}

	return B_OK;
}

// _kern_remove_attr
status_t
_kern_remove_attr(int fd, const char *name)
{
	if (!name)
		return B_BAD_VALUE;

	// get the attribute path
	string attrPath;
	string typePath;
	status_t error = get_attribute_path_virtual_fd(fd, name, attrPath,
		typePath);
	if (error != B_OK)
		return error;

	// remove the attribute
	if (unlink(attrPath.c_str()) < 0)
		return errno;

	unlink(typePath.c_str());

	return B_OK;
}


// __get_attribute_dir_path
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;
}