/* SPDX-License-Identifier: GPL-3.0-or-later */
#include "rdsquashfs.h"


static int should_skip(int type, int flags)
{
	switch (type) {
	case SQFS_INODE_BDEV:
	case SQFS_INODE_CDEV:
	case SQFS_INODE_EXT_CDEV:
	case SQFS_INODE_EXT_BDEV:
		return (flags & UNPACK_NO_DEVICES);
	case SQFS_INODE_SLINK:
	case SQFS_INODE_EXT_SLINK:
		return (flags & UNPACK_NO_SLINKS);
	case SQFS_INODE_SOCKET:
	case SQFS_INODE_EXT_SOCKET:
		return(flags & UNPACK_NO_SOCKETS);
	case SQFS_INODE_FIFO:
	case SQFS_INODE_EXT_FIFO:
		return (flags & UNPACK_NO_FIFO);
	}
	return 0;
}

static int fill_dir(meta_reader_t *ir, meta_reader_t *dr, tree_node_t *root,
		    sqfs_super_t *super, id_table_t *idtbl, int flags)
{
	sqfs_inode_generic_t *inode;
	sqfs_dir_header_t hdr;
	sqfs_dir_entry_t *ent;
	tree_node_t *n, *prev;
	uint64_t block_start;
	size_t size, diff;
	uint32_t i;

	block_start = root->data.dir->start_block;
	block_start += super->directory_table_start;

	if (meta_reader_seek(dr, block_start, root->data.dir->block_offset))
		return -1;

	size = root->data.dir->size;

	while (size != 0) {
		if (meta_reader_read_dir_header(dr, &hdr))
			return -1;

		size -= sizeof(hdr) > size ? size : sizeof(hdr);

		for (i = 0; i <= hdr.count; ++i) {
			ent = meta_reader_read_dir_ent(dr);
			if (ent == NULL)
				return -1;

			diff = sizeof(*ent) + strlen((char *)ent->name);
			size -= diff > size ? size : diff;

			if (should_skip(ent->type, flags)) {
				free(ent);
				continue;
			}

			inode = meta_reader_read_inode(ir, super,
						       hdr.start_block,
						       ent->offset);
			if (inode == NULL) {
				free(ent);
				return -1;
			}

			n = tree_node_from_inode(inode, idtbl,
						 (char *)ent->name,
						 super->block_size);
			free(ent);
			free(inode);

			if (n == NULL)
				return -1;

			n->parent = root;
			n->next = root->data.dir->children;
			root->data.dir->children = n;
		}
	}

	n = root->data.dir->children;
	prev = NULL;

	while (n != NULL) {
		if (S_ISDIR(n->mode)) {
			if (fill_dir(ir, dr, n, super, idtbl, flags))
				return -1;

			if (n->data.dir->children == NULL &&
			    (flags & UNPACK_NO_EMPTY)) {
				if (prev == NULL) {
					root->data.dir->children = n->next;
					free(n);
					n = root->data.dir->children;
				} else {
					prev->next = n->next;
					free(n);
					n = prev->next;
				}
				continue;
			}
		}

		prev = n;
		n = n->next;
	}

	return 0;
}

int read_fstree(fstree_t *out, sqfs_super_t *super, unsqfs_info_t *info)
{
	sqfs_inode_generic_t *root;
	meta_reader_t *ir, *dr;
	uint64_t block_start;
	id_table_t idtbl;
	int status = -1;
	size_t offset;

	ir = meta_reader_create(info->sqfsfd, info->cmp);
	if (ir == NULL)
		return -1;

	dr = meta_reader_create(info->sqfsfd, info->cmp);
	if (dr == NULL)
		goto out_ir;

	if (id_table_init(&idtbl))
		goto out_dr;

	if (id_table_read(&idtbl, info->sqfsfd, super, info->cmp))
		goto out_id;

	block_start = super->root_inode_ref >> 16;
	offset = super->root_inode_ref & 0xFFFF;
	root = meta_reader_read_inode(ir, super, block_start, offset);
	if (root == NULL)
		goto out_id;

	if (root->base.type != SQFS_INODE_DIR &&
	    root->base.type != SQFS_INODE_EXT_DIR) {
		free(root);
		fputs("File system root inode is not a directory inode!\n",
		      stderr);
		goto out_id;
	}

	memset(out, 0, sizeof(*out));
	out->block_size = super->block_size;
	out->default_uid = 0;
	out->default_gid = 0;
	out->default_mode = 0755;
	out->default_mtime = super->modification_time;

	out->root = tree_node_from_inode(root, &idtbl, "", super->block_size);
	free(root);
	root = NULL;

	if (out->root == NULL)
		goto out_id;

	if (fill_dir(ir, dr, out->root, super, &idtbl, info->flags))
		goto fail_fs;

	fstree_sort(out);

	status = 0;
out_id:
	id_table_cleanup(&idtbl);
out_dr:
	meta_reader_destroy(dr);
out_ir:
	meta_reader_destroy(ir);
	return status;
fail_fs:
	fstree_cleanup(out);
	goto out_id;
}