1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright (C) 2020 Bootlin
4 *
5 * Author: Joao Marcos Costa <joaomarcos.costa@bootlin.com>
6 */
7
8#include <asm/unaligned.h>
9#include <compiler.h>
10#include <errno.h>
11#include <stdint.h>
12#include <stdio.h>
13#include <stdlib.h>
14#include <string.h>
15
16#include "sqfs_decompressor.h"
17#include "sqfs_filesystem.h"
18#include "sqfs_utils.h"
19
20int sqfs_inode_size(struct squashfs_base_inode *inode, u32 blk_size)
21{
22	switch (get_unaligned_le16(&inode->inode_type)) {
23	case SQFS_DIR_TYPE:
24		return sizeof(struct squashfs_dir_inode);
25
26	case SQFS_REG_TYPE: {
27		struct squashfs_reg_inode *reg =
28			(struct squashfs_reg_inode *)inode;
29		u32 fragment = get_unaligned_le32(&reg->fragment);
30		u32 file_size = get_unaligned_le32(&reg->file_size);
31		unsigned int blk_list_size;
32
33		if (SQFS_IS_FRAGMENTED(fragment))
34			blk_list_size = file_size / blk_size;
35		else
36			blk_list_size = DIV_ROUND_UP(file_size, blk_size);
37
38		return sizeof(*reg) + blk_list_size * sizeof(u32);
39	}
40
41	case SQFS_LDIR_TYPE: {
42		struct squashfs_ldir_inode *ldir =
43			(struct squashfs_ldir_inode *)inode;
44		u16 i_count = get_unaligned_le16(&ldir->i_count);
45		unsigned int index_list_size = 0, l = 0;
46		struct squashfs_directory_index *di;
47		u32 sz;
48
49		if (i_count == 0)
50			return sizeof(*ldir);
51
52		di = ldir->index;
53		while (l < i_count) {
54			sz = get_unaligned_le32(&di->size) + 1;
55			index_list_size += sz;
56			di = (void *)di + sizeof(*di) + sz;
57			l++;
58		}
59
60		return sizeof(*ldir) + index_list_size +
61			i_count * SQFS_DIR_INDEX_BASE_LENGTH;
62	}
63
64	case SQFS_LREG_TYPE: {
65		struct squashfs_lreg_inode *lreg =
66			(struct squashfs_lreg_inode *)inode;
67		u32 fragment = get_unaligned_le32(&lreg->fragment);
68		u64 file_size = get_unaligned_le64(&lreg->file_size);
69		unsigned int blk_list_size;
70
71		if (fragment == 0xFFFFFFFF)
72			blk_list_size = DIV_ROUND_UP(file_size, blk_size);
73		else
74			blk_list_size = file_size / blk_size;
75
76		return sizeof(*lreg) + blk_list_size * sizeof(u32);
77	}
78
79	case SQFS_SYMLINK_TYPE:
80	case SQFS_LSYMLINK_TYPE: {
81		struct squashfs_symlink_inode *symlink =
82			(struct squashfs_symlink_inode *)inode;
83
84		return sizeof(*symlink) +
85			get_unaligned_le32(&symlink->symlink_size);
86	}
87
88	case SQFS_BLKDEV_TYPE:
89	case SQFS_CHRDEV_TYPE:
90		return sizeof(struct squashfs_dev_inode);
91	case SQFS_LBLKDEV_TYPE:
92	case SQFS_LCHRDEV_TYPE:
93		return sizeof(struct squashfs_ldev_inode);
94	case SQFS_FIFO_TYPE:
95	case SQFS_SOCKET_TYPE:
96		return sizeof(struct squashfs_ipc_inode);
97	case SQFS_LFIFO_TYPE:
98	case SQFS_LSOCKET_TYPE:
99		return sizeof(struct squashfs_lipc_inode);
100	default:
101		printf("Error while searching inode: unknown type.\n");
102		return -EINVAL;
103	}
104}
105
106/*
107 * Given the uncompressed inode table, the inode to be found and the number of
108 * inodes in the table, return inode position in case of success.
109 */
110void *sqfs_find_inode(void *inode_table, int inode_number, __le32 inode_count,
111		      __le32 block_size)
112{
113	struct squashfs_base_inode *base;
114	unsigned int offset = 0, k;
115	int sz;
116
117	if (!inode_table) {
118		printf("%s: Invalid pointer to inode table.\n", __func__);
119		return NULL;
120	}
121
122	for (k = 0; k < le32_to_cpu(inode_count); k++) {
123		base = inode_table + offset;
124		if (get_unaligned_le32(&base->inode_number) == inode_number)
125			return inode_table + offset;
126
127		sz = sqfs_inode_size(base, le32_to_cpu(block_size));
128		if (sz < 0)
129			return NULL;
130
131		offset += sz;
132	}
133
134	printf("Inode not found.\n");
135
136	return NULL;
137}
138
139int sqfs_read_metablock(unsigned char *file_mapping, int offset,
140			bool *compressed, u32 *data_size)
141{
142	const unsigned char *data;
143	u16 header;
144
145	if (!file_mapping)
146		return -EFAULT;
147	data = file_mapping + offset;
148
149	header = get_unaligned((u16 *)data);
150	if (!header)
151		return -EINVAL;
152
153	*compressed = SQFS_COMPRESSED_METADATA(header);
154	*data_size = SQFS_METADATA_SIZE(header);
155
156	if (*data_size > SQFS_METADATA_BLOCK_SIZE) {
157		printf("Invalid metatada block size: %d bytes.\n", *data_size);
158		return -EINVAL;
159	}
160
161	return 0;
162}
163