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