pkg-utils/sqfs/vfs.c

323 lines
6.5 KiB
C

/* SPDX-License-Identifier: ISC */
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "pkg2sqfs.h"
static int id_to_index(vfs_t *fs, uint32_t id, uint16_t *out)
{
size_t i, new_sz;
void *new;
for (i = 0; i < fs->num_ids; ++i) {
if (fs->id_tbl[i] == id) {
*out = i;
return 0;
}
}
if (fs->num_ids > 0xFFFF) {
fputs("too many unique UIDs/GIDs (more than 64k)!\n", stderr);
return -1;
}
if (fs->num_ids == fs->max_ids) {
new_sz = fs->max_ids + 10;
new = realloc(fs->id_tbl, new_sz * sizeof(fs->id_tbl[0]));
if (new == NULL) {
perror("expanding ID table");
return -1;
}
fs->max_ids = new_sz;
fs->id_tbl = new;
}
*out = fs->num_ids;
fs->id_tbl[fs->num_ids++] = id;
return 0;
}
static node_t *node_create(vfs_t *fs, node_t *parent, size_t extra,
const char *name, size_t name_len,
uint16_t mode, uint32_t uid, uint32_t gid)
{
node_t *n = calloc(1, sizeof(*n) + extra + name_len + 1);
if (n == NULL) {
perror("allocating vfs node");
return NULL;
}
if (parent != NULL) {
n->parent = parent;
n->next = parent->data.dir->children;
parent->data.dir->children = n;
}
if (id_to_index(fs, uid, &n->uid_idx))
return NULL;
if (id_to_index(fs, gid, &n->gid_idx))
return NULL;
n->mode = mode;
n->name = (char *)n->extra;
memcpy(n->extra, name, name_len);
return n;
}
static node_t *node_from_ent(vfs_t *fs, sqfs_super_t *s, image_entry_t *ent)
{
node_t *n, *parent = fs->root;
const char *path = ent->name;
size_t len, extra;
char *end;
next:
end = strchrnul(path, '/');
len = end - path;
for (n = parent->data.dir->children; n != NULL; n = n->next) {
if (!strncmp(n->name, path, len) && strlen(n->name) == len) {
if (path[len] != '/')
goto fail_exists;
if (!S_ISDIR(n->mode))
goto fail_not_dir;
parent = n;
path += len + 1;
goto next;
}
}
if (path[len] == '/') {
if (S_ISDIR(ent->mode)) {
n = node_create(fs, parent, sizeof(dir_info_t),
path, len, S_IFDIR | fs->default_mode,
fs->default_uid, fs->default_gid);
if (n == NULL)
return NULL;
n->data.dir =
(dir_info_t *)(n->name + strlen(n->name) + 1);
parent = n;
path += len + 1;
goto next;
}
goto fail_no_ent;
}
switch (ent->mode & S_IFMT) {
case S_IFREG:
extra = sizeof(file_info_t);
extra += (ent->data.file.size / s->block_size) *
sizeof(uint32_t);
break;
case S_IFLNK:
extra = strlen(ent->data.symlink.target) + 1;
break;
case S_IFDIR:
extra = sizeof(dir_info_t);
break;
default:
extra = 0;
break;
}
n = node_create(fs, parent, extra, path, len,
ent->mode, ent->uid, ent->gid);
if (n == NULL)
return NULL;
switch (ent->mode & S_IFMT) {
case S_IFREG:
n->data.file = (file_info_t *)(n->name + strlen(n->name) + 1);
n->data.file->size = ent->data.file.size;
n->data.file->id = ent->data.file.id;
n->data.file->startblock = 0xFFFFFFFFFFFFFFFFUL;
n->data.file->fragment = 0xFFFFFFFFL;
break;
case S_IFLNK:
n->data.symlink = n->name + strlen(n->name) + 1;
strcpy((char *)n->data.symlink, ent->data.symlink.target);
break;
case S_IFBLK:
case S_IFCHR:
n->data.device = ent->data.device.devno;
break;
case S_IFDIR:
n->data.dir = (dir_info_t *)(n->name + strlen(n->name) + 1);
break;
default:
break;
}
return n;
fail_no_ent:
fprintf(stderr, "cannot create /%s: '%.*s' does not exist\n",
ent->name, (int)len, path);
return NULL;
fail_exists:
fprintf(stderr, "cannot create /%s: already exists\n", ent->name);
return NULL;
fail_not_dir:
fprintf(stderr, "cannot create /%s: %.*s is not a directory\n",
ent->name, (int)len, path);
return NULL;
}
static void node_recursive_delete(node_t *n)
{
node_t *child;
if (n != NULL && S_ISDIR(n->mode)) {
while (n->data.dir->children != NULL) {
child = n->data.dir->children;
n->data.dir->children = child->next;
node_recursive_delete(child);
}
}
free(n);
}
typedef int(*node_cb)(vfs_t *fs, node_t *n, void *user);
static int foreach_node(vfs_t *fs, node_t *root, void *user, node_cb cb)
{
bool has_subdirs = false;
node_t *it;
for (it = root->data.dir->children; it != NULL; it = it->next) {
if (S_ISDIR(it->mode)) {
has_subdirs = true;
break;
}
}
if (has_subdirs) {
for (it = root->data.dir->children; it != NULL; it = it->next) {
if (!S_ISDIR(it->mode))
continue;
if (foreach_node(fs, it, user, cb) != 0)
return -1;
}
}
for (it = root->data.dir->children; it != NULL; it = it->next) {
if (cb(fs, it, user))
return -1;
}
if (root->parent == NULL) {
if (cb(fs, root, user))
return -1;
}
return 0;
}
static int alloc_inum(vfs_t *fs, node_t *n, void *user)
{
uint32_t *counter = user;
(void)fs;
if (*counter == 0xFFFFFFFF) {
fputs("too many inodes (more than 2^32 - 2)!\n", stderr);
return -1;
}
n->inode_num = (*counter)++;
return 0;
}
static int link_node_to_tbl(vfs_t *fs, node_t *n, void *user)
{
(void)user;
fs->node_tbl[n->inode_num] = n;
return 0;
}
int create_vfs_tree(sqfs_info_t *info)
{
image_entry_t *ent, *list;
uint32_t counter = 2;
vfs_t *fs = &info->fs;
node_t *n;
size_t i;
if (image_entry_list_from_package(info->rd, &list))
return -1;
fs->root = node_create(fs, NULL, sizeof(dir_info_t),
"", 0, S_IFDIR | 0755, 0, 0);
if (fs->root == NULL)
goto fail;
fs->root->data.dir = (dir_info_t *)(fs->root->name + strlen(fs->root->name) + 1);
for (ent = list; ent != NULL; ent = ent->next) {
n = node_from_ent(fs, &info->super, ent);
if (n == NULL)
goto fail;
}
if (foreach_node(fs, fs->root, &counter, alloc_inum))
goto fail;
fs->num_inodes = counter;
fs->node_tbl = calloc(fs->num_inodes, sizeof(fs->node_tbl[0]));
if (fs->node_tbl == NULL)
goto fail_oom;
if (foreach_node(fs, fs->root, NULL, link_node_to_tbl))
goto fail;
info->super.inode_count = fs->num_inodes - 2;
info->super.id_count = fs->num_ids;
image_entry_free_list(list);
for (i = 0; i < fs->num_ids; ++i)
fs->id_tbl[i] = htole32(fs->id_tbl[i]);
return 0;
fail_oom:
fputs("out of memory\n", stderr);
fail:
image_entry_free_list(list);
free(fs->node_tbl);
free(fs->id_tbl);
node_recursive_delete(fs->root);
return -1;
}
void destroy_vfs_tree(vfs_t *fs)
{
free(fs->node_tbl);
free(fs->id_tbl);
node_recursive_delete(fs->root);
}
node_t *vfs_node_from_file_id(vfs_t *fs, uint32_t id)
{
size_t i;
for (i = 0; i < fs->num_inodes; ++i) {
if (fs->node_tbl[i] == NULL)
continue;
if (!S_ISREG(fs->node_tbl[i]->mode))
continue;
if (fs->node_tbl[i]->data.file->id == id)
return fs->node_tbl[i];
}
return NULL;
}