blob: c23c55a63c013f3f244c61a2ffa9c50f229ccac6 [file] [log] [blame] [raw]
/* Copyright 2015-2022 Rivoreo
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#define _FILE_OFFSET_BITS 64
#define FUSE_USE_VERSION 26
#include <fuse/fuse.h>
#include <fuse/fuse_lowlevel.h> /* For fuse_req_t and fuse_ino_t */
#include "nbt.h"
#include <arpa/inet.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include "syncrw.h"
#include <syslog.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <assert.h>
#define NBT_IS_DIRECTORY(NODE) ((NODE)->type == NORMAL_NODE && ((NODE)->node->type == TAG_COMPOUND || (NODE)->node->type == TAG_LIST || (NODE)->node->type == TAG_INT_ARRAY || (NODE)->node->type == TAG_LONG_ARRAY))
#define SET_MODIFIED(NODE) do { if((NODE)->chunk) (NODE)->chunk->is_modified = 1; else is_modified = 1; } while(0)
struct wrapped_nbt_node {
struct wrapped_nbt_node *self;
enum {
NORMAL_NODE, REGION_ROOT_NODE, LIST_TYPE_NODE, ARRAY_ELEMENT_NODE
} type;
struct nbt_node *node;
union {
struct list_head *head;
int index;
} pos;
struct chunk_info *chunk;
};
static uid_t myuid;
static gid_t mygid;
static int read_only = 0;
static mode_t node_umask = 0;
static int use_type_prefix = 0;
static int is_region = 0;
static const char *write_file_path = NULL;
static int compression = -1;
static struct wrapped_nbt_node root_node = { .self = &root_node };
static int need_full_write;
static int region_fd = -1;
static size_t region_file_size = 0;
static void *region_map = NULL;
static struct chunk_info {
uint32_t raw_offset_and_size;
uint32_t raw_mtime;
off_t file_offset;
size_t file_size;
void *map_begin;
size_t length;
struct nbt_node *nbt_node;
int is_modified;
} region_chunks[1024];
static FILE *nbt_file = NULL;
static int is_modified = 0;
static nbt_type get_nbt_type_by_name_prefix(const char *name, size_t len) {
switch(len) {
case 4:
if(strncmp(name, "byte", 4) == 0) return TAG_BYTE;
else if(strncmp(name, "int8", 4) == 0) return TAG_BYTE;
else if(strncmp(name, "list", 4) == 0) return TAG_LIST;
else break;
case 5:
if(strncmp(name, "int16", 5) == 0) return TAG_SHORT;
else if(strncmp(name, "int32", 5) == 0) return TAG_INT;
else if(strncmp(name, "int64", 5) == 0) return TAG_LONG;
else if(strncmp(name, "float", 5) == 0) return TAG_FLOAT;
else break;
case 6:
if(strncmp(name, "string", 6) == 0) return TAG_STRING;
else if(strncmp(name, "single", 6) == 0) return TAG_FLOAT;
else if(strncmp(name, "double", 6) == 0) return TAG_DOUBLE;
else break;
case 7:
if(strncmp(name, "float32", 7) == 0) return TAG_FLOAT;
else if(strncmp(name, "float64", 7) == 0) return TAG_DOUBLE;
else break;
case 8:
if(strncmp(name, "compound", 8) == 0) return TAG_COMPOUND;
else break;
case 9:
if(strncmp(name, "bytearray", 9) == 0) return TAG_BYTE_ARRAY;
else if(strncmp(name, "int8array", 9) == 0) return TAG_BYTE_ARRAY;
else break;
case 10:
if(strncmp(name, "int32array", 10) == 0) return TAG_INT_ARRAY;
else if(strncmp(name, "int64array", 10) == 0) return TAG_LONG_ARRAY;
else break;
}
return TAG_INVALID;
}
static const char *get_node_type_name(const struct nbt_node *node) {
switch(node->type) {
case TAG_BYTE:
return "int8";
case TAG_SHORT:
return "int16";
case TAG_INT:
return "int32";
case TAG_LONG:
return "int64";
case TAG_FLOAT:
return "float32";
case TAG_DOUBLE:
return "float64";
case TAG_STRING:
return "string";
case TAG_LIST:
return "list";
case TAG_COMPOUND:
return "compound";
case TAG_BYTE_ARRAY:
return "int8array";
case TAG_INT_ARRAY:
return "int32array";
case TAG_LONG_ARRAY:
return "int64array";
default:
return NULL;
}
}
static struct wrapped_nbt_node *get_child_node_by_name(struct wrapped_nbt_node *parent, const char *name) {
if(is_region && parent->type == REGION_ROOT_NODE) {
char *end_p;
unsigned int i = strtoul(name, &end_p, 0);
if(*end_p) return NULL;
if(i >= 1024) return NULL;
struct chunk_info *info = region_chunks + i;
if(!info->nbt_node) {
if(!info->map_begin) return NULL;
info->nbt_node = nbt_parse_compressed((char *)info->map_begin + 1, info->length - 1);
if(!info->nbt_node) return NULL;
}
struct wrapped_nbt_node *r = malloc(sizeof(struct wrapped_nbt_node));
if(!r) return NULL;
r->self = r;
r->type = NORMAL_NODE;
r->node = info->nbt_node;
r->pos.head = NULL;
r->chunk = info;
return r;
}
if(parent->type != NORMAL_NODE) return NULL;
nbt_type type = TAG_INVALID;
const char *colon = strchr(name, ':');
if(colon) {
type = get_nbt_type_by_name_prefix(name, colon - name);
if(type == TAG_INVALID) return NULL;
name = colon + 1;
}
switch(parent->node->type) {
long int i, j;
char *end_p;
struct list_head *pos;
case TAG_LIST:
if(type != TAG_INVALID) return NULL;
if(strcmp(name, ".type") == 0) {
struct nbt_node *type_name_node = malloc(sizeof(struct nbt_node));
if(!type_name_node) return NULL;
type_name_node->type = 128;
type_name_node->payload.tag_list = parent->node->payload.tag_list;
struct wrapped_nbt_node *r = malloc(sizeof(struct wrapped_nbt_node));
if(!r) {
free(type_name_node);
errno = ENOMEM;
return NULL;
}
r->self = r;
r->type = LIST_TYPE_NODE;
r->node = type_name_node;
r->pos.head = NULL;
r->chunk = parent->chunk;
return r;
}
i = strtol(name, &end_p, 0);
if(*end_p) return NULL;
j = 0;
list_for_each(pos, &parent->node->payload.tag_list->entry) {
if(j++ == i) {
struct wrapped_nbt_node *r = malloc(sizeof(struct wrapped_nbt_node));
if(!r) return NULL;
r->self = r;
r->type = NORMAL_NODE;
r->node = list_entry(pos, struct nbt_list, entry)->data;
r->pos.head = pos;
r->chunk = parent->chunk;
return r;
}
}
break;
case TAG_COMPOUND:
list_for_each(pos, &parent->node->payload.tag_compound->entry) {
struct nbt_node *entry = list_entry(pos, struct nbt_list, entry)->data;
if(type != TAG_INVALID && entry->type != type) continue;
if(entry->name && strcmp(entry->name, name) == 0) {
struct wrapped_nbt_node *r = malloc(sizeof(struct wrapped_nbt_node));
if(!r) return NULL;
r->self = r;
r->type = NORMAL_NODE;
r->node = entry;
r->pos.head = pos;
r->chunk = parent->chunk;
return r;
}
}
break;
case TAG_INT_ARRAY:
j = parent->node->payload.tag_int_array.length;
goto array;
case TAG_LONG_ARRAY:
j = parent->node->payload.tag_long_array.length;
array:
i = strtol(name, &end_p, 0);
if(*end_p) return NULL;
if(i < 0 || i >= j) return NULL;
struct wrapped_nbt_node *r = malloc(sizeof(struct wrapped_nbt_node));
if(!r) return NULL;
r->self = r;
r->type = ARRAY_ELEMENT_NODE;
r->node = parent->node;
r->pos.index = i;
r->chunk = parent->chunk;
return r;
}
return NULL;
}
static struct wrapped_nbt_node *get_node(struct wrapped_nbt_node *parent, const char *path) {
if(*path == '/') path++;
if(!*path) return parent;
size_t name_len = 1;
while(path[name_len] && path[name_len] != '/') name_len++;
char name[name_len + 1];
memcpy(name, path, name_len);
name[name_len] = 0;
struct wrapped_nbt_node *node = get_child_node_by_name(parent, name);
if(!node) return NULL;
struct wrapped_nbt_node *r = get_node(node, path + name_len);
if(node != r) free(node);
return r;
}
static int init_node(struct nbt_node *node) {
switch(node->type) {
case TAG_BYTE:
node->payload.tag_byte = 0;
break;
case TAG_SHORT:
node->payload.tag_short = 0;
break;
case TAG_INT:
node->payload.tag_int = 0;
break;
case TAG_LONG:
node->payload.tag_long = 0;
break;
case TAG_FLOAT:
node->payload.tag_float = 0;
break;
case TAG_DOUBLE:
node->payload.tag_double = 0;
break;
case TAG_STRING:
node->payload.tag_string = malloc(1);
if(!node->payload.tag_string) return -1;
*node->payload.tag_string = 0;
break;
case TAG_LIST:
node->payload.tag_list = malloc(sizeof(struct nbt_list));
if(!node->payload.tag_compound) return -1;
node->payload.tag_compound->data = malloc(sizeof(struct nbt_node));
if(!node->payload.tag_compound->data) {
free(node->payload.tag_compound);
return -1;
}
memset(node->payload.tag_compound->data, 0, sizeof(struct nbt_node));
//node->payload.tag_compound->data->type = TAG_INVALID;
INIT_LIST_HEAD(&node->payload.tag_compound->entry);
break;
case TAG_COMPOUND:
node->payload.tag_compound = malloc(sizeof(struct nbt_list));
if(!node->payload.tag_compound) return -1;
node->payload.tag_compound->data = NULL;
INIT_LIST_HEAD(&node->payload.tag_compound->entry);
break;
case TAG_BYTE_ARRAY:
node->payload.tag_byte_array.data = NULL;
node->payload.tag_byte_array.length = 0;
break;
case TAG_INT_ARRAY:
node->payload.tag_int_array.data = NULL;
node->payload.tag_int_array.length = 0;
break;
case TAG_LONG_ARRAY:
node->payload.tag_long_array.data = NULL;
node->payload.tag_long_array.length = 0;
break;
default:
errno = EINVAL;
return -1;
}
return 0;
}
static struct wrapped_nbt_node *create_node(struct wrapped_nbt_node *parent, const char *path) {
struct wrapped_nbt_node *orig_parent_node = parent;
if(*path == '/') path++;
const char *p = strrchr(path, '/');
if(p) {
size_t node_path_len = ++p - path;
char node_path[node_path_len + 1];
memcpy(node_path, path, node_path_len);
node_path[node_path_len] = 0;
errno = ENOENT;
parent = get_node(parent, node_path);
if(!parent) return NULL;
path = p;
} else if(is_region && parent->type == REGION_ROOT_NODE) {
errno = EINVAL;
return NULL;
}
if(parent->type != NORMAL_NODE) {
if(parent != orig_parent_node) free(parent);
errno = ENOTDIR;
return NULL;
}
nbt_type type;
const char *name;
struct list_head *parent_list_head;
switch(parent->node->type) {
unsigned long int i;
char *end_p;
struct list_head *pos;
case TAG_LIST:
i = strtoul(path, &end_p, 0);
if(*end_p) {
if(parent != orig_parent_node) free(parent);
errno = EINVAL;
return NULL;
}
type = parent->node->payload.tag_list->data->type;
if(type == TAG_INVALID) {
if(parent != orig_parent_node) free(parent);
errno = EPERM;
return NULL;
}
name = NULL;
parent_list_head = &parent->node->payload.tag_list->entry;
if(i != list_length(parent_list_head)) {
if(parent != orig_parent_node) free(parent);
errno = EPERM;
return NULL;
}
break;
case TAG_COMPOUND:
p = strchr(path, ':');
if(!p) {
if(parent != orig_parent_node) free(parent);
errno = EINVAL;
return NULL;
}
type = get_nbt_type_by_name_prefix(path, p - path);
if(type == TAG_INVALID) {
if(parent != orig_parent_node) free(parent);
errno = EINVAL;
return NULL;
}
name = p + 1;
parent_list_head = &parent->node->payload.tag_compound->entry;
list_for_each(pos, parent_list_head) {
struct nbt_node *entry = list_entry(pos, struct nbt_list, entry)->data;
if(entry->name && strcmp(entry->name, name) == 0) {
if(parent != orig_parent_node) free(parent);
errno = EEXIST;
return NULL;
}
}
break;
#define EXTEND_ARRAY_AND_RETURN_LAST_ELEMENT(NODE_PAYLOAD) do { \
long int i = strtol(path, &end_p, 0); \
if(*end_p) { \
if(parent != orig_parent_node) free(parent); \
errno = EINVAL; \
return NULL; \
} \
if(i < (NODE_PAYLOAD).length) { \
if(parent != orig_parent_node) free(parent); \
errno = EEXIST; \
return NULL; \
} \
struct wrapped_nbt_node *r = malloc(sizeof(struct wrapped_nbt_node)); \
if(!r) { \
if(parent != orig_parent_node) free(parent); \
errno = ENOMEM; \
return NULL; \
} \
void *p = realloc((NODE_PAYLOAD).data, (i + 1) * sizeof *(NODE_PAYLOAD).data); \
if(!p) { \
if(parent != orig_parent_node) free(parent); \
free(r); \
errno = ENOMEM; \
return NULL; \
} \
(NODE_PAYLOAD).data = p; \
size_t extended_size = (i + 1 - (NODE_PAYLOAD).length) * sizeof *(NODE_PAYLOAD).data; \
memset((NODE_PAYLOAD).data + (NODE_PAYLOAD).length, 0, extended_size); \
(NODE_PAYLOAD).length = i + 1; \
r->self = r; \
r->type = ARRAY_ELEMENT_NODE; \
r->node = parent->node; \
r->pos.index = i; \
r->chunk = parent->chunk; \
return r; \
} while(0)
case TAG_INT_ARRAY:
EXTEND_ARRAY_AND_RETURN_LAST_ELEMENT(parent->node->payload.tag_int_array);
case TAG_LONG_ARRAY:
EXTEND_ARRAY_AND_RETURN_LAST_ELEMENT(parent->node->payload.tag_long_array);
#undef EXTEND_ARRAY_AND_RETURN_LAST_ELEMENT
default:
if(parent != orig_parent_node) free(parent);
errno = ENOTDIR;
return NULL;
}
struct nbt_node *node = malloc(sizeof(struct nbt_node));
if(!node) return NULL;
node->type = type;
if(name) {
node->name = strdup(name);
if(!node->name) {
free(node);
if(parent != orig_parent_node) free(parent);
errno = ENOMEM;
return NULL;
}
} else {
node->name = NULL;
}
if(init_node(node) < 0) {
free(node);
if(parent != orig_parent_node) free(parent);
return NULL;
}
struct wrapped_nbt_node *r = malloc(sizeof(struct wrapped_nbt_node));
if(!r) {
nbt_free(node);
if(parent != orig_parent_node) free(parent);
errno = ENOMEM;
return NULL;
}
r->self = r;
r->type = NORMAL_NODE;
r->node = node;
r->chunk = parent->chunk;
if(parent != orig_parent_node) free(parent);
struct nbt_list *new_list = malloc(sizeof(struct nbt_list));
if(!new_list) {
nbt_free(node);
free(r);
errno = ENOMEM;
return NULL;
}
new_list->data = node;
list_add_tail(&new_list->entry, parent_list_head);
r->pos.head = &new_list->entry;
SET_MODIFIED(r);
return r;
}
static struct wrapped_nbt_node *file_info_to_nbt_node(const struct fuse_file_info *fi) {
struct wrapped_nbt_node *node = (struct wrapped_nbt_node *)fi->fh;
if(node->self == node) return node;
struct fuse_dir_handle {
pthread_mutex_t lock;
struct fuse *fuse;
fuse_req_t req;
char *contents;
int allocated;
unsigned int len;
unsigned int size;
unsigned int needlen;
int filled;
uint64_t fh;
int error;
fuse_ino_t nodeid;
} *dirh = (struct fuse_dir_handle *)fi->fh;
node = (struct wrapped_nbt_node *)dirh->fh;
assert(node->self == node);
return node;
}
static size_t get_size(struct nbt_node *node) {
switch(node->type) {
const char *s;
case TAG_BYTE:
return snprintf(NULL, 0, "%hhd\n", (char)node->payload.tag_byte);
case TAG_SHORT:
return snprintf(NULL, 0, "%d\n", (int)node->payload.tag_short);
case TAG_INT:
return snprintf(NULL, 0, "%d\n", (int)node->payload.tag_int);
case TAG_LONG:
return snprintf(NULL, 0, "%lld\n", (long long int)node->payload.tag_long);
case TAG_FLOAT:
return snprintf(NULL, 0, "%f\n", (double)node->payload.tag_float);
case TAG_DOUBLE:
return snprintf(NULL, 0, "%f\n", node->payload.tag_double);
case TAG_STRING:
if(!node->payload.tag_string) return 0;
return strlen(node->payload.tag_string) + 1;
case 128:
s = get_node_type_name(node->payload.tag_list->data);
return s ? strlen(s) + 1 : 8;
case TAG_LIST:
case TAG_COMPOUND:
return nbt_size(node);
case TAG_BYTE_ARRAY:
return node->payload.tag_byte_array.length;
case TAG_INT_ARRAY:
return node->payload.tag_int_array.length * 4;
case TAG_LONG_ARRAY:
return node->payload.tag_long_array.length * 8;
default:
return 0;
}
}
static int nbt_fgetattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi) {
struct wrapped_nbt_node *node = file_info_to_nbt_node(fi);
memset(stbuf, 0, sizeof *stbuf);
stbuf->st_uid = myuid;
stbuf->st_gid = mygid;
stbuf->st_nlink = 1;
if(is_region && node->type == REGION_ROOT_NODE) {
stbuf->st_ino = 1;
stbuf->st_mode = (0777 | S_IFDIR) & ~node_umask;
} else {
stbuf->st_ino = (ino_t)node;
stbuf->st_mode = NBT_IS_DIRECTORY(node) ? (0777 | S_IFDIR) : (0666 | S_IFREG);
stbuf->st_mode &= ~node_umask;
stbuf->st_size = get_size(node->node);
}
return 0;
}
static int nbt_getattr(const char *path, struct stat *stbuf) {
errno = ENOENT;
struct wrapped_nbt_node *node = get_node(&root_node, path);
if(!node) return -errno;
struct fuse_file_info fi = { .fh = (uint64_t)node };
int ne = nbt_fgetattr(path, stbuf, &fi);
if(node != &root_node) free(node);
return ne;
}
static int nbt_ftruncate(const char *path, off_t length, struct fuse_file_info *fi) {
if(read_only) return -EROFS;
struct wrapped_nbt_node *node = file_info_to_nbt_node(fi);
switch(node->type) {
case NORMAL_NODE:
break;
case REGION_ROOT_NODE:
if(!is_region) {
syslog(LOG_ERR, "REGION_ROOT_NODE (%s) appears in non-region mount", path);
return -EIO;
}
return -EISDIR;
case LIST_TYPE_NODE:
if(&node->node->payload.tag_list->entry != node->node->payload.tag_list->entry.flink) {
return -ENOTEMPTY;
}
// Silently ignore
return 0;
case ARRAY_ELEMENT_NODE:
if(length == (node->node->type == TAG_INT_ARRAY ? 4 : 8)) return 0;
if(length) return -EINVAL;
switch(node->node->type) {
case TAG_INT_ARRAY:
node->node->payload.tag_int_array.data[node->pos.index] = 0;
break;
case TAG_LONG_ARRAY:
node->node->payload.tag_long_array.data[node->pos.index] =
0;
break;
default:
return -EIO;
}
SET_MODIFIED(node);
return 0;
default:
return -EIO;
}
switch(node->node->type) {
void *p;
case TAG_BYTE:
if(length == 1) return 0;
if(length) return -EINVAL;
node->node->payload.tag_byte = 0;
break;
case TAG_SHORT:
if(length == 2) return 0;
if(length) return -EINVAL;
node->node->payload.tag_short = 0;
break;
case TAG_INT:
if(length == 4) return 0;
if(length) return -EINVAL;
node->node->payload.tag_int = 0;
break;
case TAG_LONG:
if(length == 8) return 0;
if(length) return -EINVAL;
node->node->payload.tag_long = 0;
break;
case TAG_FLOAT:
if(length == 4) return 0;
if(length) return -EINVAL;
node->node->payload.tag_float = 0;
break;
case TAG_DOUBLE:
if(length == 8) return 0;
if(length) return -EINVAL;
node->node->payload.tag_double = 0;
break;
case TAG_STRING:
p = realloc(node->node->payload.tag_string, length + 1);
if(!p) return -ENOMEM;
node->node->payload.tag_string = p;
node->node->payload.tag_string[length] = 0;
break;
case TAG_BYTE_ARRAY:
p = realloc(node->node->payload.tag_byte_array.data, length);
if(length && !p) return -ENOMEM;
node->node->payload.tag_byte_array.data = p;
node->node->payload.tag_byte_array.length = length;
break;
case TAG_INT_ARRAY:
if(length % 4) return -EINVAL;
p = realloc(node->node->payload.tag_int_array.data, length);
if(length && !p) return -ENOMEM;
node->node->payload.tag_int_array.data = p;
node->node->payload.tag_int_array.length = length / 4;
break;
case TAG_LONG_ARRAY:
if(length % 8) return -EINVAL;
p = realloc(node->node->payload.tag_long_array.data, length);
if(length && !p) return -ENOMEM;
node->node->payload.tag_long_array.data = p;
node->node->payload.tag_long_array.length = length / 8;
break;
case TAG_LIST:
case TAG_COMPOUND:
return -EISDIR;
default:
return -EIO;
}
SET_MODIFIED(node);
return 0;
}
static int nbt_truncate(const char *path, off_t length) {
if(read_only) return -EROFS;
struct wrapped_nbt_node *node = get_node(&root_node, path);
if(!node) return -ENOENT;
struct fuse_file_info fi = { .fh = (uint64_t)node };
int ne = nbt_ftruncate(path, length, &fi);
if(node != &root_node) free(node);
return ne;
}
static int nbt_open(const char *path, struct fuse_file_info *fi) {
if((fi->flags & (O_RDONLY|O_WRONLY|O_RDWR)) == O_RDONLY) {
if(fi->flags & O_TRUNC) return -EINVAL;
} else if(read_only) return -EROFS;
struct wrapped_nbt_node *node = get_node(&root_node, path);
if(!node) return -ENOENT;
fi->fh = (uint64_t)node;
if(fi->flags & O_TRUNC) {
int ne = nbt_ftruncate(path, 0, fi);
if(ne) {
free(node);
return ne;
}
}
return 0;
}
static int nbt_create(const char *path, mode_t mode, struct fuse_file_info *fi) {
if(read_only) return -EROFS;
errno = ENOENT;
struct wrapped_nbt_node *node = create_node(&root_node, path);
if(!node) return -errno;
fi->fh = (uint64_t)node;
return 0;
}
static int nbt_mkdir(const char *path, mode_t mode) {
if(read_only) return -EROFS;
errno = ENOENT;
struct wrapped_nbt_node *node = create_node(&root_node, path);
if(!node) return -errno;
free(node);
return 0;
}
static int nbt_remove_node(const char *path, int dir_only) {
int ne;
struct wrapped_nbt_node *parent_node;
if(*path == '/') path++;
const char *p = strrchr(path, '/');
if(p) {
size_t node_path_len = ++p - path;
char node_path[node_path_len + 1];
memcpy(node_path, path, node_path_len);
node_path[node_path_len] = 0;
parent_node = get_node(&root_node, node_path);
if(!parent_node) return -ENOENT;
} else if(is_region) {
// TODO
return -EPERM;
} else {
parent_node = &root_node;
p = path;
}
if(!NBT_IS_DIRECTORY(parent_node)) {
ne = -ENOTDIR;
goto cleanup;
}
if(read_only) {
ne = -EROFS;
goto cleanup;
}
errno = ENOENT;
struct wrapped_nbt_node *node = get_child_node_by_name(parent_node, p);
if(!node) {
ne = -errno;
goto cleanup;
}
if(node == &root_node) {
ne = -EINVAL;
goto cleanup;
}
switch(node->type) {
void **p;
int32_t *len;
unsigned int element_size;
case NORMAL_NODE:
if(!node->pos.head) goto missing_link_head;
break;
case LIST_TYPE_NODE:
missing_link_head:
free(node);
ne = -EPERM;
goto cleanup;
case ARRAY_ELEMENT_NODE:
if(dir_only) {
free(node);
ne = -ENOTDIR;
goto cleanup;
}
switch(node->node->type) {
case TAG_INT_ARRAY:
p = (void **)&node->node->payload.tag_int_array.data;
len = &node->node->payload.tag_int_array.length;
element_size = 4;
break;
case TAG_LONG_ARRAY:
p = (void **)&node->node->payload.tag_long_array.data;
len = &node->node->payload.tag_long_array.length;
element_size = 8;
break;
default:
goto eio;
}
if(node->pos.index + 1 < *len) {
free(node);
ne = -EBUSY;
goto cleanup;
}
if(node->pos.index >= *len) {
free(node);
ne = -ENOENT;
goto cleanup;
}
*p = realloc(*p, node->pos.index * element_size);
(*len)--;
SET_MODIFIED(node);
free(node);
ne = 0;
goto cleanup;
default:
eio:
free(node);
ne = -EIO;
goto cleanup;
}
if(dir_only) switch(node->node->type) {
case TAG_LIST:
if(&node->node->payload.tag_list->entry != node->node->payload.tag_list->entry.flink) {
free(node);
ne = -ENOTEMPTY;
goto cleanup;
}
break;
case TAG_COMPOUND:
if(&node->node->payload.tag_compound->entry != node->node->payload.tag_compound->entry.flink) {
free(node);
ne = -ENOTEMPTY;
goto cleanup;
}
break;
default:
free(node);
ne = -ENOTDIR;
goto cleanup;
}
list_del(node->pos.head);
nbt_free(node->node);
SET_MODIFIED(node);
free(node);
ne = 0;
cleanup:
if(parent_node != &root_node) free(parent_node);
return ne;
}
static int nbt_unlink(const char *path) {
return nbt_remove_node(path, 0);
}
static int nbt_rmdir(const char *path) {
return nbt_remove_node(path, 1);
}
static int nbt_release(const char *path, struct fuse_file_info *fi) {
struct wrapped_nbt_node *node = (struct wrapped_nbt_node *)fi->fh;
if(node == &root_node) return 0;
if(node->type == LIST_TYPE_NODE) free(node->node);
free(node);
return 0;
}
static int nbt_read(const char *path, char *out_buf, size_t size, off_t offset, struct fuse_file_info *fi) {
char buffer[4096];
size_t length = 0;
struct wrapped_nbt_node *node = file_info_to_nbt_node(fi);
switch(node->type) {
case NORMAL_NODE:
break;
case REGION_ROOT_NODE:
if(!is_region) {
syslog(LOG_ERR, "REGION_ROOT_NODE (%s) appears in non-region mount", path);
return -EIO;
}
return -EISDIR;
case LIST_TYPE_NODE:
goto copy_list_type;
case ARRAY_ELEMENT_NODE:
switch(node->node->type) {
case TAG_INT_ARRAY:
length = sprintf(buffer, "%d\n",
(int)node->node->payload.tag_int_array.data[node->pos.index]);
break;
case TAG_LONG_ARRAY:
length = sprintf(buffer, "%lld\n",
(long long int)node->node->payload.tag_long_array.data[node->pos.index]);
break;
default:
return -EIO;
}
break;
default:
return -EIO;
}
if(!length) switch(node->node->type) {
const char *p;
case TAG_BYTE:
length = sprintf(buffer, "%hhd\n", (char)node->node->payload.tag_byte);
break;
case TAG_SHORT:
length = sprintf(buffer, "%d\n", (int)node->node->payload.tag_short);
break;
case TAG_INT:
length = sprintf(buffer, "%d\n", (int)node->node->payload.tag_int);
break;
case TAG_LONG:
length = sprintf(buffer, "%lld\n", (long long int)node->node->payload.tag_long);
break;
case TAG_FLOAT:
length = sprintf(buffer, "%f\n", (double)node->node->payload.tag_float);
break;
case TAG_DOUBLE:
length = sprintf(buffer, "%f\n", node->node->payload.tag_double);
break;
case TAG_STRING:
p = node->node->payload.tag_string;
if(!p) return 0;
goto copy_string;
//case 128:
copy_list_type:
p = get_node_type_name(node->node->payload.tag_list->data);
if(!p) p = "invalid";
copy_string:
length = strlen(p);
if(length < offset) return 0;
length -= offset;
if(length > sizeof buffer) length = sizeof buffer;
memcpy(buffer, p + offset, length);
if(length < sizeof buffer) buffer[length++] = '\n';
offset = 0;
break;
case TAG_LIST:
case TAG_COMPOUND:
return -EISDIR;
case TAG_BYTE_ARRAY:
p = (const char *)node->node->payload.tag_byte_array.data;
length = node->node->payload.tag_byte_array.length;
goto copy_array;
case TAG_INT_ARRAY:
if(offset % 4 || size % 4) return -EINVAL;
p = (const char *)node->node->payload.tag_int_array.data;
length = node->node->payload.tag_int_array.length * 4;
goto copy_array;
case TAG_LONG_ARRAY:
if(offset % 8 || size % 8) return -EINVAL;
p = (const char *)node->node->payload.tag_long_array.data;
length = node->node->payload.tag_long_array.length * 8;
copy_array:
if(length <= offset) return 0;
length -= offset;
if(length > sizeof buffer) length = sizeof buffer;
memcpy(buffer, p + offset, length);
offset = 0;
break;
default:
return -EIO;
}
if(offset + size > length) {
if((size_t)offset >= length) return 0;
size = length - offset;
}
memcpy(out_buf, buffer + offset, size);
return size;
}
static int nbt_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) {
unsigned int i;
struct wrapped_nbt_node *node = (struct wrapped_nbt_node *)fi->fh;
switch(node->type) {
case NORMAL_NODE:
break;
case REGION_ROOT_NODE:
if(!is_region) {
syslog(LOG_ERR, "REGION_ROOT_NODE (%s) appears in non-region mount", path);
return -EIO;
}
for(i = 0; i < 1024; i++) {
const struct chunk_info *info = region_chunks + i;
if(!info->map_begin) continue;
char text_buffer[5];
sprintf(text_buffer, "%u", i);
filler(buf, text_buffer, NULL, 0);
}
return 0;
default:
return -ENOTDIR;
}
switch(node->node->type) {
char text_buffer[32];
struct list_head *pos;
case TAG_LIST:
filler(buf, ".type", NULL, 0);
i = 0;
list_for_each(pos, &node->node->payload.tag_compound->entry) {
sprintf(text_buffer, "%u", i++);
filler(buf, text_buffer, NULL, 0);
}
break;
case TAG_COMPOUND:
list_for_each(pos, &node->node->payload.tag_compound->entry) {
struct nbt_node *entry = list_entry(pos, struct nbt_list, entry)->data;
if(!entry->name) continue;
if(use_type_prefix) {
const char *prefix = get_node_type_name(entry);
if(prefix) {
size_t prefix_len = strlen(prefix);
size_t name_len = strlen(entry->name);
char text_buffer[prefix_len + 1 + name_len + 1];
memcpy(text_buffer, prefix, prefix_len);
text_buffer[prefix_len] = ':';
memcpy(text_buffer + prefix_len + 1, entry->name, name_len);
text_buffer[prefix_len + 1 + name_len] = 0;
filler(buf, text_buffer, NULL, 0);
continue;
}
}
filler(buf, entry->name, NULL, 0);
}
break;
case TAG_INT_ARRAY:
case TAG_LONG_ARRAY:
i = node->node->type == TAG_INT_ARRAY ?
node->node->payload.tag_int_array.length : node->node->payload.tag_long_array.length;
while(i > 0) {
sprintf(text_buffer, "%u", --i);
filler(buf, text_buffer, NULL, 0);
}
break;
default:
return -ENOTDIR;
}
return 0;
}
static int nbt_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi) {
if(read_only) return -EROFS;
if(!size) return 0;
struct wrapped_nbt_node *node = file_info_to_nbt_node(fi);
switch(node->type) {
case NORMAL_NODE:
break;
case REGION_ROOT_NODE:
if(!is_region) {
syslog(LOG_ERR, "REGION_ROOT_NODE (%s) appears in non-region mount", path);
return -EIO;
}
return -EISDIR;
case LIST_TYPE_NODE:
if(offset) return -EINVAL;
if(&node->node->payload.tag_list->entry != node->node->payload.tag_list->entry.flink) {
return -ENOTEMPTY;
}
size_t copy_len = buf[size - 1] == '\n' ? size - 1 : size;
nbt_type type = get_nbt_type_by_name_prefix(buf, copy_len);
if(type == TAG_INVALID) return -EINVAL;
node->node->payload.tag_list->data->type = type;
SET_MODIFIED(node);
return size;
case ARRAY_ELEMENT_NODE:
goto parse_number;
default:
return -EIO;
}
switch(node->node->type) {
static const void *parse_number_labels[] = {
[TAG_BYTE] = &&parse_byte,
[TAG_SHORT] = &&parse_short,
[TAG_INT] = &&parse_int,
[TAG_LONG] = &&parse_long,
[TAG_FLOAT] = &&parse_float,
[TAG_DOUBLE] = &&parse_double,
[TAG_INT_ARRAY] = &&parse_int_for_array,
[TAG_LONG_ARRAY] = &&parse_long_for_array
};
char *end_p;
size_t orig_len, copy_len;
int need_end_byte;
case TAG_BYTE:
case TAG_SHORT:
case TAG_INT:
case TAG_LONG:
case TAG_FLOAT:
case TAG_DOUBLE:
parse_number:
if(offset) return -EINVAL;
{
char text_buffer[size + 1];
memcpy(text_buffer, buf, size);
text_buffer[size] = 0;
goto *parse_number_labels[node->node->type];
parse_byte:
node->node->payload.tag_byte = strtol(text_buffer, &end_p, 0);
goto parse_number_end;
parse_short:
node->node->payload.tag_short = strtol(text_buffer, &end_p, 0);
goto parse_number_end;
parse_int:
node->node->payload.tag_int = strtol(text_buffer, &end_p, 0);
goto parse_number_end;
parse_long:
node->node->payload.tag_long = strtoll(text_buffer, &end_p, 0);
goto parse_number_end;
parse_float:
node->node->payload.tag_float = strtof(text_buffer, &end_p);
goto parse_number_end;
parse_double:
node->node->payload.tag_double = strtod(text_buffer, &end_p);
goto parse_number_end;
parse_int_for_array:
node->node->payload.tag_int_array.data[node->pos.index] =
strtol(text_buffer, &end_p, 0);
goto parse_number_end;
parse_long_for_array:
node->node->payload.tag_long_array.data[node->pos.index] =
strtoll(text_buffer, &end_p, 0);
parse_number_end:
if(*end_p && !isspace(*end_p)) return -EINVAL;
SET_MODIFIED(node);
return *end_p ? end_p - text_buffer + 1 : (int)size;
}
case TAG_STRING:
orig_len = strlen(node->node->payload.tag_string) + 1;
copy_len = buf[size - 1] == '\n' ? size - 1 : size;
need_end_byte = 1;
goto copy_bytes;
case TAG_BYTE_ARRAY:
orig_len = node->node->payload.tag_byte_array.length;
copy_len = size;
need_end_byte = 0;
copy_bytes:
{
void **target_p = node->node->type == TAG_STRING ?
(void **)&node->node->payload.tag_string : (void **)&node->node->payload.tag_byte_array.data;
if(offset + copy_len > orig_len) {
void *p = realloc(*target_p, offset + copy_len + need_end_byte);
if(!p) return -ENOMEM;
*target_p = p;
if(node->node->type == TAG_BYTE_ARRAY) {
node->node->payload.tag_byte_array.length = offset + copy_len;
}
}
memcpy((char *)*target_p + offset, buf, copy_len);
if(need_end_byte) ((char *)*target_p)[offset + copy_len] = 0;
}
break;
case TAG_LIST:
case TAG_COMPOUND:
return -EISDIR;
case TAG_INT_ARRAY:
case TAG_LONG_ARRAY:
return -EPERM;
default:
return -EIO;
}
SET_MODIFIED(node);
return size;
}
static void handle_file_error(const char *func, int *fd) {
syslog(LOG_ERR, "%s on fd %d failed, %s; data will not be saved", func, *fd, strerror(errno));
close(*fd);
*fd = -1;
}
static void nbt_destroy(void *a) {
if(is_region) {
unsigned int i;
if(!read_only && region_fd != -1 && need_full_write) {
if(lseek(region_fd, 0, SEEK_SET) < 0) {
handle_file_error("lseek", &region_fd);
} else if(sync_write(region_fd, region_map, 8192) < 0) {
handle_file_error("write", &region_fd);
}
}
for(i = 0; i < 1024; i++) {
struct chunk_info *info = region_chunks + i;
if(!info->map_begin) continue;
if(!read_only && region_fd != -1 && (info->is_modified || need_full_write)) {
struct buffer buffer;
if(lseek(region_fd, info->file_offset, SEEK_SET) < 0) {
handle_file_error("lseek", &region_fd);
}
if(info->is_modified) {
syslog(LOG_DEBUG, "Chunk %u has been modified", i);
buffer = nbt_dump_compressed(info->nbt_node, compression);
if(!buffer.data) {
syslog(LOG_ERR, "Failed to compress chunk %u, %s",
i, nbt_error_to_string(errno));
}
if(1 + buffer.len> info->file_size) {
// TODO: reallocate more space
syslog(LOG_ERR, "Chunk %u is too big to store, need %zu bytes but only %zu bytes available",
i, 1 + buffer.len, info->file_size);
free(buffer.data);
buffer.data = NULL;
}
} else {
buffer.data = NULL;
}
int32_t len = htonl(buffer.data ? buffer.len + 1 : info->length);
if(sync_write(region_fd, &len, 4) < 0) {
handle_file_error("write", &region_fd);
} else if(buffer.data) {
uint8_t v = compression == STRAT_GZIP ? 1 : 2;
if(sync_write(region_fd, &v, 1) < 0) {
handle_file_error("write", &region_fd);
} else if(sync_write(region_fd, buffer.data, buffer.len) < 0) {
handle_file_error("write", &region_fd);
}
} else if(sync_write(region_fd, info->map_begin, info->length) < 0) {
handle_file_error("write", &region_fd);
}
free(buffer.data);
}
nbt_free(info->nbt_node);
}
munmap(region_map, region_file_size);
if(!read_only && region_fd != -1 && close(region_fd) < 0) {
syslog(LOG_ERR, "Failed to close fd %d, %s; data may not be saved",
region_fd, strerror(errno));
}
} else {
if(!read_only && nbt_file && (is_modified || write_file_path)) {
fseek(nbt_file, 0, SEEK_SET);
nbt_status status = nbt_dump_file(root_node.node, nbt_file, compression);
if(status != NBT_OK) {
syslog(LOG_ERR, "Failed to save NBT file, %s", nbt_error_to_string(status));
}
if(fclose(nbt_file) == EOF) {
syslog(LOG_ERR, "Failed to save NBT file, %s", strerror(errno));
}
}
nbt_free(root_node.node);
}
}
static char *parse_extended_options(char *o) {
char *fuse_opt = NULL;
size_t fuse_opt_len = 0;
char *comma;
do {
comma = strchr(o, ',');
if(comma) *comma++ = 0;
if(strcmp(o, "ro") == 0) read_only = 1;
else if(strcmp(o, "rw") == 0) read_only = 0;
else if(strncmp(o, "umask=", 6) == 0) node_umask = strtol(o + 6, NULL, 8) & 0777;
else if(strcmp(o, "typeprefix") == 0) use_type_prefix = 1;
else if(strcmp(o, "region") == 0) is_region = 1;
else if(strncmp(o, "writefile=", 10) == 0) write_file_path = o + 10;
else if(strncmp(o, "compression=", 12) == 0) {
const char *a = o + 12;
if(strcmp(a, "gzip") == 0) compression = STRAT_GZIP;
else if(strcmp(a, "zlib") == 0) compression = STRAT_INFLATE;
else {
fprintf(stderr, "Compression type %s is not supported\n", a);
exit(-1);
}
} else {
if(fuse_opt_len) {
fuse_opt[fuse_opt_len++] = ',';
}
size_t i = fuse_opt_len;
size_t len = strlen(o);
fuse_opt_len += len;
fuse_opt = realloc(fuse_opt, fuse_opt_len + 1);
if(!fuse_opt) {
perror("parse_extended_options: realloc");
exit(1);
}
memcpy(fuse_opt + i, o, len + 1);
}
} while(comma && *(o = comma));
return fuse_opt;
}
static void print_usage(const char *name) {
fprintf(stderr, "Usage: %s [-o <fs-options>] [-fnrvw] <nbt-file> <mount-point>\n",
name);
}
static int read_region_header(int fd) {
unsigned int i;
off_t len = lseek(fd, 0, SEEK_END);
if(len < 0) {
perror("lseek");
return -1;
}
if(len < 8192) {
fputs("File is too small to be a valid region file\n", stderr);
return -1;
}
region_file_size = len;
lseek(fd, 0, SEEK_SET);
region_map = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
if(!region_map) {
perror("mmap");
return -1;
}
uint8_t *byte_p = region_map;
int32_t *int_p = region_map;
for(i = 0; i < 1024; i++) {
struct chunk_info *info = region_chunks + i;
info->raw_offset_and_size = int_p[i];
size_t chunk_size = byte_p[i * 4 + 3] * 4 * 1024;
if(!chunk_size) continue;
off_t chunk_offset = (ntohl(int_p[i]) >> 8) & 0xffffff;
if(!chunk_offset) continue;
off_t file_offset = chunk_offset * 4 * 1024;
if(file_offset > len) {
fprintf(stderr, "Chunk %u has invalid offset %ld that's out of file length\n",
i, (long int)file_offset);
if(read_only) continue;
fputs("Cannot continue in read-write mode\n", stderr);
return -1;
}
info->raw_mtime = int_p[1024 + i];
/*
time_t chunk_mtime = ntohl(info->raw_mtime);
struct tm *chunk_tm = localtime(&chunk_mtime);
char time_buffer[24];
if(!strftime(time_buffer, sizeof time_buffer, "%F %T", chunk_tm)) {
sprintf(time_buffer, "%d", (int)chunk_mtime);
}
*/
uint8_t *chunk = (uint8_t *)region_map + file_offset;
int32_t used_space;
memcpy(&used_space, chunk, 4);
used_space = ntohl(used_space);
if(used_space < 0 || (size_t)used_space > chunk_size) {
fprintf(stderr, "Chunk %u has invalid size %d\n", i, (int)used_space);
if(read_only) continue;
fputs("Cannot continue in read-write mode\n", stderr);
return -1;
}
uint8_t compression_type = chunk[4];
if(compression_type != 1 && compression_type != 2) {
fprintf(stderr, "Chunk %u has unsupported compression type %hhu\n",
i, compression_type);
if(read_only) continue;
fputs("Cannot continue in read-write mode\n", stderr);
return -1;
}
info->file_offset = file_offset;
info->file_size = chunk_size;
info->map_begin = chunk + 4;
info->length = used_space;
}
return 0;
}
static struct fuse_operations operations = {
.fgetattr = nbt_fgetattr,
.getattr = nbt_getattr,
.create = nbt_create,
.open = nbt_open,
.opendir = nbt_open,
.release = nbt_release,
.releasedir = nbt_release,
.read = nbt_read,
.readdir = nbt_readdir,
.write = nbt_write,
.ftruncate = nbt_ftruncate,
.truncate = nbt_truncate,
.unlink = nbt_unlink,
.mkdir = nbt_mkdir,
.rmdir = nbt_rmdir,
.destroy = nbt_destroy
};
int main(int argc, char **argv) {
int fuse_argc = 1;
char **fuse_argv = malloc(2 * sizeof(char *));
if(!fuse_argv) {
perror("malloc");
return 1;
}
char *fuse_extended_options;
int foreground = 0;
int verbose = 0;
while(1) {
int c = getopt(argc, argv, "fo:nrvwF:t:h");
if(c == -1) break;
switch(c) {
case 'f':
fuse_argc++;
fuse_argv = realloc(fuse_argv, (fuse_argc + 1) * sizeof(char *));
if(!fuse_argv) {
perror("realloc");
return 1;
}
fuse_argv[fuse_argc - 1] = "-f";
foreground = 1;
break;
case 'o':
fuse_extended_options = parse_extended_options(optarg);
if(fuse_extended_options) {
fuse_argc += 2;
fuse_argv = realloc(fuse_argv, (fuse_argc + 1) * sizeof(char *));
if(!fuse_argv) {
perror("realloc");
return 1;
}
fuse_argv[fuse_argc - 2] = "-o";
fuse_argv[fuse_argc - 1] = fuse_extended_options;
}
break;
case 'n':
break;
case 'r':
read_only = 1;
break;
case 'v':
verbose++;
break;
case 'w':
read_only = 0;
break;
case 'F':
case 't':
if(strcmp(optarg, "nbt")) {
fprintf(stderr, "%s: The file system type may only be specified as 'nbt'\n",
argv[0]);
return -1;
}
break;
case 'h':
print_usage(argv[0]);
return 0;
case '?':
print_usage(argv[0]);
return -1;
}
}
if(argc - optind != 2) {
print_usage(argv[0]);
return -1;
}
int logopt = LOG_PID;
if(foreground || verbose) logopt |= LOG_PERROR;
openlog("mount.nbt", logopt, LOG_DAEMON);
myuid = getuid();
mygid = getgid();
if(is_region) {
int fd = open(argv[optind], read_only || write_file_path ? O_RDONLY : O_RDWR);
if(fd == -1) {
perror(argv[optind]);
return 1;
}
if(!read_only) {
if(write_file_path) {
region_fd = open(write_file_path, O_WRONLY | O_CREAT, 0666);
if(region_fd == -1) {
perror(write_file_path);
return 1;
}
need_full_write = 1;
} else {
region_fd = fd;
need_full_write = 0;
}
}
if(read_region_header(fd) < 0) return 1;
root_node.type = REGION_ROOT_NODE;
root_node.node = NULL;
root_node.pos.head = NULL;
root_node.chunk = NULL;
if(fd != region_fd) close(fd);
if(compression == -1) compression = STRAT_INFLATE;
syslog(LOG_DEBUG, "Region %s loaded successfully", argv[optind]);
} else {
FILE *f = fopen(argv[optind], read_only || write_file_path ? "rb" : "r+b");
if(!f) {
perror(argv[optind]);
return 1;
}
if(!read_only) {
if(write_file_path) {
nbt_file = fopen(write_file_path, "wb");
if(!nbt_file) {
perror(write_file_path);
return 1;
}
} else {
nbt_file = f;
}
}
root_node.type = NORMAL_NODE;
root_node.node = nbt_parse_file(f);
if(!root_node.node) {
fprintf(stderr, "%s: Failed to mount %s, %s\n", argv[0], argv[optind], nbt_error_to_string(errno));
return 1;
}
root_node.pos.head = NULL;
root_node.chunk = NULL;
if(f != nbt_file) fclose(f);
if(compression == -1) compression = STRAT_GZIP;
syslog(LOG_DEBUG, "NBT %s loaded successfully", argv[optind]);
}
fuse_argc += argc - optind - 1;
fuse_argv = realloc(fuse_argv, (fuse_argc + 1) * sizeof(char *));
if(!fuse_argv) {
perror("realloc");
return 1;
}
fuse_argv[0] = argv[optind];
memcpy(fuse_argv + (fuse_argc - (argc - optind - 1)), argv + optind + 1, (argc - optind) * sizeof(char *));
return fuse_main(fuse_argc, fuse_argv, &operations, NULL);
}