162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Squashfs - a compressed read only filesystem for Linux
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
662306a36Sopenharmony_ci * Phillip Lougher <phillip@squashfs.org.uk>
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * dir.c
962306a36Sopenharmony_ci */
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci/*
1262306a36Sopenharmony_ci * This file implements code to read directories from disk.
1362306a36Sopenharmony_ci *
1462306a36Sopenharmony_ci * See namei.c for a description of directory organisation on disk.
1562306a36Sopenharmony_ci */
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include <linux/fs.h>
1862306a36Sopenharmony_ci#include <linux/vfs.h>
1962306a36Sopenharmony_ci#include <linux/slab.h>
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#include "squashfs_fs.h"
2262306a36Sopenharmony_ci#include "squashfs_fs_sb.h"
2362306a36Sopenharmony_ci#include "squashfs_fs_i.h"
2462306a36Sopenharmony_ci#include "squashfs.h"
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistatic const unsigned char squashfs_filetype_table[] = {
2762306a36Sopenharmony_ci	DT_UNKNOWN, DT_DIR, DT_REG, DT_LNK, DT_BLK, DT_CHR, DT_FIFO, DT_SOCK
2862306a36Sopenharmony_ci};
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci/*
3162306a36Sopenharmony_ci * Lookup offset (f_pos) in the directory index, returning the
3262306a36Sopenharmony_ci * metadata block containing it.
3362306a36Sopenharmony_ci *
3462306a36Sopenharmony_ci * If we get an error reading the index then return the part of the index
3562306a36Sopenharmony_ci * (if any) we have managed to read - the index isn't essential, just
3662306a36Sopenharmony_ci * quicker.
3762306a36Sopenharmony_ci */
3862306a36Sopenharmony_cistatic int get_dir_index_using_offset(struct super_block *sb,
3962306a36Sopenharmony_ci	u64 *next_block, int *next_offset, u64 index_start, int index_offset,
4062306a36Sopenharmony_ci	int i_count, u64 f_pos)
4162306a36Sopenharmony_ci{
4262306a36Sopenharmony_ci	struct squashfs_sb_info *msblk = sb->s_fs_info;
4362306a36Sopenharmony_ci	int err, i, index, length = 0;
4462306a36Sopenharmony_ci	unsigned int size;
4562306a36Sopenharmony_ci	struct squashfs_dir_index dir_index;
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	TRACE("Entered get_dir_index_using_offset, i_count %d, f_pos %lld\n",
4862306a36Sopenharmony_ci					i_count, f_pos);
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	/*
5162306a36Sopenharmony_ci	 * Translate from external f_pos to the internal f_pos.  This
5262306a36Sopenharmony_ci	 * is offset by 3 because we invent "." and ".." entries which are
5362306a36Sopenharmony_ci	 * not actually stored in the directory.
5462306a36Sopenharmony_ci	 */
5562306a36Sopenharmony_ci	if (f_pos <= 3)
5662306a36Sopenharmony_ci		return f_pos;
5762306a36Sopenharmony_ci	f_pos -= 3;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	for (i = 0; i < i_count; i++) {
6062306a36Sopenharmony_ci		err = squashfs_read_metadata(sb, &dir_index, &index_start,
6162306a36Sopenharmony_ci				&index_offset, sizeof(dir_index));
6262306a36Sopenharmony_ci		if (err < 0)
6362306a36Sopenharmony_ci			break;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci		index = le32_to_cpu(dir_index.index);
6662306a36Sopenharmony_ci		if (index > f_pos)
6762306a36Sopenharmony_ci			/*
6862306a36Sopenharmony_ci			 * Found the index we're looking for.
6962306a36Sopenharmony_ci			 */
7062306a36Sopenharmony_ci			break;
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci		size = le32_to_cpu(dir_index.size) + 1;
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci		/* size should never be larger than SQUASHFS_NAME_LEN */
7562306a36Sopenharmony_ci		if (size > SQUASHFS_NAME_LEN)
7662306a36Sopenharmony_ci			break;
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci		err = squashfs_read_metadata(sb, NULL, &index_start,
7962306a36Sopenharmony_ci				&index_offset, size);
8062306a36Sopenharmony_ci		if (err < 0)
8162306a36Sopenharmony_ci			break;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci		length = index;
8462306a36Sopenharmony_ci		*next_block = le32_to_cpu(dir_index.start_block) +
8562306a36Sopenharmony_ci					msblk->directory_table;
8662306a36Sopenharmony_ci	}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	*next_offset = (length + *next_offset) % SQUASHFS_METADATA_SIZE;
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	/*
9162306a36Sopenharmony_ci	 * Translate back from internal f_pos to external f_pos.
9262306a36Sopenharmony_ci	 */
9362306a36Sopenharmony_ci	return length + 3;
9462306a36Sopenharmony_ci}
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_cistatic int squashfs_readdir(struct file *file, struct dir_context *ctx)
9862306a36Sopenharmony_ci{
9962306a36Sopenharmony_ci	struct inode *inode = file_inode(file);
10062306a36Sopenharmony_ci	struct squashfs_sb_info *msblk = inode->i_sb->s_fs_info;
10162306a36Sopenharmony_ci	u64 block = squashfs_i(inode)->start + msblk->directory_table;
10262306a36Sopenharmony_ci	int offset = squashfs_i(inode)->offset, length, err;
10362306a36Sopenharmony_ci	unsigned int inode_number, dir_count, size, type;
10462306a36Sopenharmony_ci	struct squashfs_dir_header dirh;
10562306a36Sopenharmony_ci	struct squashfs_dir_entry *dire;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	TRACE("Entered squashfs_readdir [%llx:%x]\n", block, offset);
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	dire = kmalloc(sizeof(*dire) + SQUASHFS_NAME_LEN + 1, GFP_KERNEL);
11062306a36Sopenharmony_ci	if (dire == NULL) {
11162306a36Sopenharmony_ci		ERROR("Failed to allocate squashfs_dir_entry\n");
11262306a36Sopenharmony_ci		goto finish;
11362306a36Sopenharmony_ci	}
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	/*
11662306a36Sopenharmony_ci	 * Return "." and  ".." entries as the first two filenames in the
11762306a36Sopenharmony_ci	 * directory.  To maximise compression these two entries are not
11862306a36Sopenharmony_ci	 * stored in the directory, and so we invent them here.
11962306a36Sopenharmony_ci	 *
12062306a36Sopenharmony_ci	 * It also means that the external f_pos is offset by 3 from the
12162306a36Sopenharmony_ci	 * on-disk directory f_pos.
12262306a36Sopenharmony_ci	 */
12362306a36Sopenharmony_ci	while (ctx->pos < 3) {
12462306a36Sopenharmony_ci		char *name;
12562306a36Sopenharmony_ci		int i_ino;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci		if (ctx->pos == 0) {
12862306a36Sopenharmony_ci			name = ".";
12962306a36Sopenharmony_ci			size = 1;
13062306a36Sopenharmony_ci			i_ino = inode->i_ino;
13162306a36Sopenharmony_ci		} else {
13262306a36Sopenharmony_ci			name = "..";
13362306a36Sopenharmony_ci			size = 2;
13462306a36Sopenharmony_ci			i_ino = squashfs_i(inode)->parent;
13562306a36Sopenharmony_ci		}
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci		if (!dir_emit(ctx, name, size, i_ino,
13862306a36Sopenharmony_ci				squashfs_filetype_table[1]))
13962306a36Sopenharmony_ci			goto finish;
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci		ctx->pos += size;
14262306a36Sopenharmony_ci	}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	length = get_dir_index_using_offset(inode->i_sb, &block, &offset,
14562306a36Sopenharmony_ci				squashfs_i(inode)->dir_idx_start,
14662306a36Sopenharmony_ci				squashfs_i(inode)->dir_idx_offset,
14762306a36Sopenharmony_ci				squashfs_i(inode)->dir_idx_cnt,
14862306a36Sopenharmony_ci				ctx->pos);
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	while (length < i_size_read(inode)) {
15162306a36Sopenharmony_ci		/*
15262306a36Sopenharmony_ci		 * Read directory header
15362306a36Sopenharmony_ci		 */
15462306a36Sopenharmony_ci		err = squashfs_read_metadata(inode->i_sb, &dirh, &block,
15562306a36Sopenharmony_ci					&offset, sizeof(dirh));
15662306a36Sopenharmony_ci		if (err < 0)
15762306a36Sopenharmony_ci			goto failed_read;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci		length += sizeof(dirh);
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci		dir_count = le32_to_cpu(dirh.count) + 1;
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci		if (dir_count > SQUASHFS_DIR_COUNT)
16462306a36Sopenharmony_ci			goto failed_read;
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci		while (dir_count--) {
16762306a36Sopenharmony_ci			/*
16862306a36Sopenharmony_ci			 * Read directory entry.
16962306a36Sopenharmony_ci			 */
17062306a36Sopenharmony_ci			err = squashfs_read_metadata(inode->i_sb, dire, &block,
17162306a36Sopenharmony_ci					&offset, sizeof(*dire));
17262306a36Sopenharmony_ci			if (err < 0)
17362306a36Sopenharmony_ci				goto failed_read;
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci			size = le16_to_cpu(dire->size) + 1;
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci			/* size should never be larger than SQUASHFS_NAME_LEN */
17862306a36Sopenharmony_ci			if (size > SQUASHFS_NAME_LEN)
17962306a36Sopenharmony_ci				goto failed_read;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci			err = squashfs_read_metadata(inode->i_sb, dire->name,
18262306a36Sopenharmony_ci					&block, &offset, size);
18362306a36Sopenharmony_ci			if (err < 0)
18462306a36Sopenharmony_ci				goto failed_read;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci			length += sizeof(*dire) + size;
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci			if (ctx->pos >= length)
18962306a36Sopenharmony_ci				continue;
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci			dire->name[size] = '\0';
19262306a36Sopenharmony_ci			inode_number = le32_to_cpu(dirh.inode_number) +
19362306a36Sopenharmony_ci				((short) le16_to_cpu(dire->inode_number));
19462306a36Sopenharmony_ci			type = le16_to_cpu(dire->type);
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci			if (type > SQUASHFS_MAX_DIR_TYPE)
19762306a36Sopenharmony_ci				goto failed_read;
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci			if (!dir_emit(ctx, dire->name, size,
20062306a36Sopenharmony_ci					inode_number,
20162306a36Sopenharmony_ci					squashfs_filetype_table[type]))
20262306a36Sopenharmony_ci				goto finish;
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci			ctx->pos = length;
20562306a36Sopenharmony_ci		}
20662306a36Sopenharmony_ci	}
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_cifinish:
20962306a36Sopenharmony_ci	kfree(dire);
21062306a36Sopenharmony_ci	return 0;
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_cifailed_read:
21362306a36Sopenharmony_ci	ERROR("Unable to read directory block [%llx:%x]\n", block, offset);
21462306a36Sopenharmony_ci	kfree(dire);
21562306a36Sopenharmony_ci	return 0;
21662306a36Sopenharmony_ci}
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ciconst struct file_operations squashfs_dir_ops = {
22062306a36Sopenharmony_ci	.read = generic_read_dir,
22162306a36Sopenharmony_ci	.iterate_shared = squashfs_readdir,
22262306a36Sopenharmony_ci	.llseek = generic_file_llseek,
22362306a36Sopenharmony_ci};
224