mirror of
				https://xff.cz/git/u-boot/
				synced 2025-10-31 10:26:10 +01:00 
			
		
		
		
	fs/erofs: add erofs filesystem support
This patch mainly deals with uncompressed files. Signed-off-by: Huang Jianan <jnhuang95@gmail.com>
This commit is contained in:
		| @@ -809,6 +809,13 @@ S:	Maintained | ||||
| F:	doc/usage/environment.rst | ||||
| F:	scripts/env2string.awk | ||||
|  | ||||
| EROFS | ||||
| M:	Huang Jianan <jnhuang95@gmail.com> | ||||
| L:	linux-erofs@lists.ozlabs.org | ||||
| S:	Maintained | ||||
| F:	fs/erofs/ | ||||
| F:	include/erofs.h | ||||
|  | ||||
| EVENTS | ||||
| M:	Simon Glass <sjg@chromium.org> | ||||
| S:	Maintained | ||||
|   | ||||
| @@ -24,4 +24,6 @@ source "fs/yaffs2/Kconfig" | ||||
|  | ||||
| source "fs/squashfs/Kconfig" | ||||
|  | ||||
| source "fs/erofs/Kconfig" | ||||
|  | ||||
| endmenu | ||||
|   | ||||
| @@ -25,5 +25,6 @@ obj-$(CONFIG_CMD_UBIFS) += ubifs/ | ||||
| obj-$(CONFIG_YAFFS2) += yaffs2/ | ||||
| obj-$(CONFIG_CMD_ZFS) += zfs/ | ||||
| obj-$(CONFIG_FS_SQUASHFS) += squashfs/ | ||||
| obj-$(CONFIG_FS_EROFS) += erofs/ | ||||
| endif | ||||
| obj-y += fs_internal.o | ||||
|   | ||||
							
								
								
									
										12
									
								
								fs/erofs/Kconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								fs/erofs/Kconfig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| config FS_EROFS | ||||
| 	bool "Enable EROFS filesystem support" | ||||
| 	help | ||||
| 	  This provides support for reading images from EROFS filesystem. | ||||
| 	  EROFS (Enhanced Read-Only File System) is a lightweight read-only | ||||
| 	  file system for scenarios which need high-performance read-only | ||||
| 	  requirements. | ||||
|  | ||||
| 	  It also provides fixed-sized output compression support, which | ||||
| 	  improves storage density, keeps relatively higher compression | ||||
| 	  ratios, which is more useful to achieve high performance for | ||||
| 	  embedded devices with limited memory. | ||||
							
								
								
									
										7
									
								
								fs/erofs/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								fs/erofs/Makefile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| # SPDX-License-Identifier: GPL-2.0+ | ||||
| # | ||||
|  | ||||
| obj-$(CONFIG_$(SPL_)FS_EROFS) = fs.o \ | ||||
| 				super.o \ | ||||
| 				namei.o \ | ||||
| 				data.o | ||||
							
								
								
									
										223
									
								
								fs/erofs/data.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										223
									
								
								fs/erofs/data.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,223 @@ | ||||
| // SPDX-License-Identifier: GPL-2.0+ | ||||
| #include "internal.h" | ||||
|  | ||||
| static int erofs_map_blocks_flatmode(struct erofs_inode *inode, | ||||
| 				     struct erofs_map_blocks *map, | ||||
| 				     int flags) | ||||
| { | ||||
| 	int err = 0; | ||||
| 	erofs_blk_t nblocks, lastblk; | ||||
| 	u64 offset = map->m_la; | ||||
| 	struct erofs_inode *vi = inode; | ||||
| 	bool tailendpacking = (vi->datalayout == EROFS_INODE_FLAT_INLINE); | ||||
|  | ||||
| 	nblocks = DIV_ROUND_UP(inode->i_size, EROFS_BLKSIZ); | ||||
| 	lastblk = nblocks - tailendpacking; | ||||
|  | ||||
| 	/* there is no hole in flatmode */ | ||||
| 	map->m_flags = EROFS_MAP_MAPPED; | ||||
|  | ||||
| 	if (offset < blknr_to_addr(lastblk)) { | ||||
| 		map->m_pa = blknr_to_addr(vi->u.i_blkaddr) + map->m_la; | ||||
| 		map->m_plen = blknr_to_addr(lastblk) - offset; | ||||
| 	} else if (tailendpacking) { | ||||
| 		/* 2 - inode inline B: inode, [xattrs], inline last blk... */ | ||||
| 		map->m_pa = iloc(vi->nid) + vi->inode_isize + | ||||
| 			vi->xattr_isize + erofs_blkoff(map->m_la); | ||||
| 		map->m_plen = inode->i_size - offset; | ||||
|  | ||||
| 		/* inline data should be located in one meta block */ | ||||
| 		if (erofs_blkoff(map->m_pa) + map->m_plen > PAGE_SIZE) { | ||||
| 			erofs_err("inline data cross block boundary @ nid %" PRIu64, | ||||
| 				  vi->nid); | ||||
| 			DBG_BUGON(1); | ||||
| 			err = -EFSCORRUPTED; | ||||
| 			goto err_out; | ||||
| 		} | ||||
|  | ||||
| 		map->m_flags |= EROFS_MAP_META; | ||||
| 	} else { | ||||
| 		erofs_err("internal error @ nid: %" PRIu64 " (size %llu), m_la 0x%" PRIx64, | ||||
| 			  vi->nid, (unsigned long long)inode->i_size, map->m_la); | ||||
| 		DBG_BUGON(1); | ||||
| 		err = -EIO; | ||||
| 		goto err_out; | ||||
| 	} | ||||
|  | ||||
| 	map->m_llen = map->m_plen; | ||||
| err_out: | ||||
| 	return err; | ||||
| } | ||||
|  | ||||
| int erofs_map_blocks(struct erofs_inode *inode, | ||||
| 		     struct erofs_map_blocks *map, int flags) | ||||
| { | ||||
| 	struct erofs_inode *vi = inode; | ||||
| 	struct erofs_inode_chunk_index *idx; | ||||
| 	u8 buf[EROFS_BLKSIZ]; | ||||
| 	u64 chunknr; | ||||
| 	unsigned int unit; | ||||
| 	erofs_off_t pos; | ||||
| 	int err = 0; | ||||
|  | ||||
| 	map->m_deviceid = 0; | ||||
| 	if (map->m_la >= inode->i_size) { | ||||
| 		/* leave out-of-bound access unmapped */ | ||||
| 		map->m_flags = 0; | ||||
| 		map->m_plen = 0; | ||||
| 		goto out; | ||||
| 	} | ||||
|  | ||||
| 	if (vi->datalayout != EROFS_INODE_CHUNK_BASED) | ||||
| 		return erofs_map_blocks_flatmode(inode, map, flags); | ||||
|  | ||||
| 	if (vi->u.chunkformat & EROFS_CHUNK_FORMAT_INDEXES) | ||||
| 		unit = sizeof(*idx);			/* chunk index */ | ||||
| 	else | ||||
| 		unit = EROFS_BLOCK_MAP_ENTRY_SIZE;	/* block map */ | ||||
|  | ||||
| 	chunknr = map->m_la >> vi->u.chunkbits; | ||||
| 	pos = roundup(iloc(vi->nid) + vi->inode_isize + | ||||
| 		      vi->xattr_isize, unit) + unit * chunknr; | ||||
|  | ||||
| 	err = erofs_blk_read(buf, erofs_blknr(pos), 1); | ||||
| 	if (err < 0) | ||||
| 		return -EIO; | ||||
|  | ||||
| 	map->m_la = chunknr << vi->u.chunkbits; | ||||
| 	map->m_plen = min_t(erofs_off_t, 1UL << vi->u.chunkbits, | ||||
| 			    roundup(inode->i_size - map->m_la, EROFS_BLKSIZ)); | ||||
|  | ||||
| 	/* handle block map */ | ||||
| 	if (!(vi->u.chunkformat & EROFS_CHUNK_FORMAT_INDEXES)) { | ||||
| 		__le32 *blkaddr = (void *)buf + erofs_blkoff(pos); | ||||
|  | ||||
| 		if (le32_to_cpu(*blkaddr) == EROFS_NULL_ADDR) { | ||||
| 			map->m_flags = 0; | ||||
| 		} else { | ||||
| 			map->m_pa = blknr_to_addr(le32_to_cpu(*blkaddr)); | ||||
| 			map->m_flags = EROFS_MAP_MAPPED; | ||||
| 		} | ||||
| 		goto out; | ||||
| 	} | ||||
| 	/* parse chunk indexes */ | ||||
| 	idx = (void *)buf + erofs_blkoff(pos); | ||||
| 	switch (le32_to_cpu(idx->blkaddr)) { | ||||
| 	case EROFS_NULL_ADDR: | ||||
| 		map->m_flags = 0; | ||||
| 		break; | ||||
| 	default: | ||||
| 		map->m_deviceid = le16_to_cpu(idx->device_id) & | ||||
| 			sbi.device_id_mask; | ||||
| 		map->m_pa = blknr_to_addr(le32_to_cpu(idx->blkaddr)); | ||||
| 		map->m_flags = EROFS_MAP_MAPPED; | ||||
| 		break; | ||||
| 	} | ||||
| out: | ||||
| 	map->m_llen = map->m_plen; | ||||
| 	return err; | ||||
| } | ||||
|  | ||||
| int erofs_map_dev(struct erofs_sb_info *sbi, struct erofs_map_dev *map) | ||||
| { | ||||
| 	struct erofs_device_info *dif; | ||||
| 	int id; | ||||
|  | ||||
| 	if (map->m_deviceid) { | ||||
| 		if (sbi->extra_devices < map->m_deviceid) | ||||
| 			return -ENODEV; | ||||
| 	} else if (sbi->extra_devices) { | ||||
| 		for (id = 0; id < sbi->extra_devices; ++id) { | ||||
| 			erofs_off_t startoff, length; | ||||
|  | ||||
| 			dif = sbi->devs + id; | ||||
| 			if (!dif->mapped_blkaddr) | ||||
| 				continue; | ||||
| 			startoff = blknr_to_addr(dif->mapped_blkaddr); | ||||
| 			length = blknr_to_addr(dif->blocks); | ||||
|  | ||||
| 			if (map->m_pa >= startoff && | ||||
| 			    map->m_pa < startoff + length) { | ||||
| 				map->m_pa -= startoff; | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| static int erofs_read_raw_data(struct erofs_inode *inode, char *buffer, | ||||
| 			       erofs_off_t size, erofs_off_t offset) | ||||
| { | ||||
| 	struct erofs_map_blocks map = { | ||||
| 		.index = UINT_MAX, | ||||
| 	}; | ||||
| 	struct erofs_map_dev mdev; | ||||
| 	int ret; | ||||
| 	erofs_off_t ptr = offset; | ||||
|  | ||||
| 	while (ptr < offset + size) { | ||||
| 		char *const estart = buffer + ptr - offset; | ||||
| 		erofs_off_t eend; | ||||
|  | ||||
| 		map.m_la = ptr; | ||||
| 		ret = erofs_map_blocks(inode, &map, 0); | ||||
| 		if (ret) | ||||
| 			return ret; | ||||
|  | ||||
| 		DBG_BUGON(map.m_plen != map.m_llen); | ||||
|  | ||||
| 		mdev = (struct erofs_map_dev) { | ||||
| 			.m_deviceid = map.m_deviceid, | ||||
| 			.m_pa = map.m_pa, | ||||
| 		}; | ||||
| 		ret = erofs_map_dev(&sbi, &mdev); | ||||
| 		if (ret) | ||||
| 			return ret; | ||||
|  | ||||
| 		/* trim extent */ | ||||
| 		eend = min(offset + size, map.m_la + map.m_llen); | ||||
| 		DBG_BUGON(ptr < map.m_la); | ||||
|  | ||||
| 		if (!(map.m_flags & EROFS_MAP_MAPPED)) { | ||||
| 			if (!map.m_llen) { | ||||
| 				/* reached EOF */ | ||||
| 				memset(estart, 0, offset + size - ptr); | ||||
| 				ptr = offset + size; | ||||
| 				continue; | ||||
| 			} | ||||
| 			memset(estart, 0, eend - ptr); | ||||
| 			ptr = eend; | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		if (ptr > map.m_la) { | ||||
| 			mdev.m_pa += ptr - map.m_la; | ||||
| 			map.m_la = ptr; | ||||
| 		} | ||||
|  | ||||
| 		ret = erofs_dev_read(mdev.m_deviceid, estart, mdev.m_pa, | ||||
| 				     eend - map.m_la); | ||||
| 		if (ret < 0) | ||||
| 			return -EIO; | ||||
| 		ptr = eend; | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int erofs_pread(struct erofs_inode *inode, char *buf, | ||||
| 		erofs_off_t count, erofs_off_t offset) | ||||
| { | ||||
| 	switch (inode->datalayout) { | ||||
| 	case EROFS_INODE_FLAT_PLAIN: | ||||
| 	case EROFS_INODE_FLAT_INLINE: | ||||
| 	case EROFS_INODE_CHUNK_BASED: | ||||
| 		return erofs_read_raw_data(inode, buf, count, offset); | ||||
| 	case EROFS_INODE_FLAT_COMPRESSION_LEGACY: | ||||
| 	case EROFS_INODE_FLAT_COMPRESSION: | ||||
| 		return -EOPNOTSUPP; | ||||
| 	default: | ||||
| 		break; | ||||
| 	} | ||||
| 	return -EINVAL; | ||||
| } | ||||
							
								
								
									
										436
									
								
								fs/erofs/erofs_fs.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										436
									
								
								fs/erofs/erofs_fs.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,436 @@ | ||||
| /* SPDX-License-Identifier: GPL-2.0-only OR Apache-2.0 */ | ||||
| /* | ||||
|  * EROFS (Enhanced ROM File System) on-disk format definition | ||||
|  * | ||||
|  * Copyright (C) 2017-2018 HUAWEI, Inc. | ||||
|  *             http://www.huawei.com/ | ||||
|  * Copyright (C) 2021, Alibaba Cloud | ||||
|  */ | ||||
| #ifndef __EROFS_FS_H | ||||
| #define __EROFS_FS_H | ||||
|  | ||||
| #include <asm/unaligned.h> | ||||
| #include <fs.h> | ||||
| #include <part.h> | ||||
| #include <stdint.h> | ||||
| #include <compiler.h> | ||||
|  | ||||
| #define EROFS_SUPER_MAGIC_V1    0xE0F5E1E2 | ||||
| #define EROFS_SUPER_OFFSET      1024 | ||||
|  | ||||
| #define EROFS_FEATURE_COMPAT_SB_CHKSUM		0x00000001 | ||||
|  | ||||
| /* | ||||
|  * Any bits that aren't in EROFS_ALL_FEATURE_INCOMPAT should | ||||
|  * be incompatible with this kernel version. | ||||
|  */ | ||||
| #define EROFS_FEATURE_INCOMPAT_LZ4_0PADDING	0x00000001 | ||||
| #define EROFS_FEATURE_INCOMPAT_COMPR_CFGS	0x00000002 | ||||
| #define EROFS_FEATURE_INCOMPAT_BIG_PCLUSTER	0x00000002 | ||||
| #define EROFS_FEATURE_INCOMPAT_CHUNKED_FILE	0x00000004 | ||||
| #define EROFS_FEATURE_INCOMPAT_DEVICE_TABLE	0x00000008 | ||||
| #define EROFS_ALL_FEATURE_INCOMPAT		\ | ||||
| 	(EROFS_FEATURE_INCOMPAT_LZ4_0PADDING | \ | ||||
| 	 EROFS_FEATURE_INCOMPAT_COMPR_CFGS | \ | ||||
| 	 EROFS_FEATURE_INCOMPAT_BIG_PCLUSTER | \ | ||||
| 	 EROFS_FEATURE_INCOMPAT_CHUNKED_FILE | \ | ||||
| 	 EROFS_FEATURE_INCOMPAT_DEVICE_TABLE) | ||||
|  | ||||
| #define EROFS_SB_EXTSLOT_SIZE	16 | ||||
|  | ||||
| struct erofs_deviceslot { | ||||
| 	union { | ||||
| 		u8 uuid[16];		/* used for device manager later */ | ||||
| 		u8 userdata[64];	/* digest(sha256), etc. */ | ||||
| 	} u; | ||||
| 	__le32 blocks;			/* total fs blocks of this device */ | ||||
| 	__le32 mapped_blkaddr;		/* map starting at mapped_blkaddr */ | ||||
| 	u8 reserved[56]; | ||||
| }; | ||||
|  | ||||
| #define EROFS_DEVT_SLOT_SIZE	sizeof(struct erofs_deviceslot) | ||||
|  | ||||
| /* erofs on-disk super block (currently 128 bytes) */ | ||||
| struct erofs_super_block { | ||||
| 	__le32 magic;           /* file system magic number */ | ||||
| 	__le32 checksum;        /* crc32c(super_block) */ | ||||
| 	__le32 feature_compat; | ||||
| 	__u8 blkszbits;         /* support block_size == PAGE_SIZE only */ | ||||
| 	__u8 sb_extslots;	/* superblock size = 128 + sb_extslots * 16 */ | ||||
|  | ||||
| 	__le16 root_nid;	/* nid of root directory */ | ||||
| 	__le64 inos;            /* total valid ino # (== f_files - f_favail) */ | ||||
|  | ||||
| 	__le64 build_time;      /* inode v1 time derivation */ | ||||
| 	__le32 build_time_nsec;	/* inode v1 time derivation in nano scale */ | ||||
| 	__le32 blocks;          /* used for statfs */ | ||||
| 	__le32 meta_blkaddr;	/* start block address of metadata area */ | ||||
| 	__le32 xattr_blkaddr;	/* start block address of shared xattr area */ | ||||
| 	__u8 uuid[16];          /* 128-bit uuid for volume */ | ||||
| 	__u8 volume_name[16];   /* volume name */ | ||||
| 	__le32 feature_incompat; | ||||
| 	union { | ||||
| 		/* bitmap for available compression algorithms */ | ||||
| 		__le16 available_compr_algs; | ||||
| 		/* customized sliding window size instead of 64k by default */ | ||||
| 		__le16 lz4_max_distance; | ||||
| 	} __packed u1; | ||||
| 	__le16 extra_devices;	/* # of devices besides the primary device */ | ||||
| 	__le16 devt_slotoff;	/* startoff = devt_slotoff * devt_slotsize */ | ||||
| 	__u8 reserved2[38]; | ||||
| }; | ||||
|  | ||||
| /* | ||||
|  * erofs inode datalayout (i_format in on-disk inode): | ||||
|  * 0 - inode plain without inline data A: | ||||
|  * inode, [xattrs], ... | ... | no-holed data | ||||
|  * 1 - inode VLE compression B (legacy): | ||||
|  * inode, [xattrs], extents ... | ... | ||||
|  * 2 - inode plain with inline data C: | ||||
|  * inode, [xattrs], last_inline_data, ... | ... | no-holed data | ||||
|  * 3 - inode compression D: | ||||
|  * inode, [xattrs], map_header, extents ... | ... | ||||
|  * 4 - inode chunk-based E: | ||||
|  * inode, [xattrs], chunk indexes ... | ... | ||||
|  * 5~7 - reserved | ||||
|  */ | ||||
| enum { | ||||
| 	EROFS_INODE_FLAT_PLAIN			= 0, | ||||
| 	EROFS_INODE_FLAT_COMPRESSION_LEGACY	= 1, | ||||
| 	EROFS_INODE_FLAT_INLINE			= 2, | ||||
| 	EROFS_INODE_FLAT_COMPRESSION		= 3, | ||||
| 	EROFS_INODE_CHUNK_BASED			= 4, | ||||
| 	EROFS_INODE_DATALAYOUT_MAX | ||||
| }; | ||||
|  | ||||
| static inline bool erofs_inode_is_data_compressed(unsigned int datamode) | ||||
| { | ||||
| 	return datamode == EROFS_INODE_FLAT_COMPRESSION || | ||||
| 		datamode == EROFS_INODE_FLAT_COMPRESSION_LEGACY; | ||||
| } | ||||
|  | ||||
| /* bit definitions of inode i_advise */ | ||||
| #define EROFS_I_VERSION_BITS            1 | ||||
| #define EROFS_I_DATALAYOUT_BITS         3 | ||||
|  | ||||
| #define EROFS_I_VERSION_BIT             0 | ||||
| #define EROFS_I_DATALAYOUT_BIT          1 | ||||
|  | ||||
| #define EROFS_I_ALL	\ | ||||
| 	((1 << (EROFS_I_DATALAYOUT_BIT + EROFS_I_DATALAYOUT_BITS)) - 1) | ||||
|  | ||||
| /* indicate chunk blkbits, thus 'chunksize = blocksize << chunk blkbits' */ | ||||
| #define EROFS_CHUNK_FORMAT_BLKBITS_MASK		0x001F | ||||
| /* with chunk indexes or just a 4-byte blkaddr array */ | ||||
| #define EROFS_CHUNK_FORMAT_INDEXES		0x0020 | ||||
|  | ||||
| #define EROFS_CHUNK_FORMAT_ALL	\ | ||||
| 	(EROFS_CHUNK_FORMAT_BLKBITS_MASK | EROFS_CHUNK_FORMAT_INDEXES) | ||||
|  | ||||
| struct erofs_inode_chunk_info { | ||||
| 	__le16 format;		/* chunk blkbits, etc. */ | ||||
| 	__le16 reserved; | ||||
| }; | ||||
|  | ||||
| /* 32-byte reduced form of an ondisk inode */ | ||||
| struct erofs_inode_compact { | ||||
| 	__le16 i_format;	/* inode format hints */ | ||||
|  | ||||
| /* 1 header + n-1 * 4 bytes inline xattr to keep continuity */ | ||||
| 	__le16 i_xattr_icount; | ||||
| 	__le16 i_mode; | ||||
| 	__le16 i_nlink; | ||||
| 	__le32 i_size; | ||||
| 	__le32 i_reserved; | ||||
| 	union { | ||||
| 		/* file total compressed blocks for data mapping 1 */ | ||||
| 		__le32 compressed_blocks; | ||||
| 		__le32 raw_blkaddr; | ||||
|  | ||||
| 		/* for device files, used to indicate old/new device # */ | ||||
| 		__le32 rdev; | ||||
|  | ||||
| 		/* for chunk-based files, it contains the summary info */ | ||||
| 		struct erofs_inode_chunk_info c; | ||||
| 	} i_u; | ||||
| 	__le32 i_ino;           /* only used for 32-bit stat compatibility */ | ||||
| 	__le16 i_uid; | ||||
| 	__le16 i_gid; | ||||
| 	__le32 i_reserved2; | ||||
| }; | ||||
|  | ||||
| /* 32 bytes on-disk inode */ | ||||
| #define EROFS_INODE_LAYOUT_COMPACT	0 | ||||
| /* 64 bytes on-disk inode */ | ||||
| #define EROFS_INODE_LAYOUT_EXTENDED	1 | ||||
|  | ||||
| /* 64-byte complete form of an ondisk inode */ | ||||
| struct erofs_inode_extended { | ||||
| 	__le16 i_format;	/* inode format hints */ | ||||
|  | ||||
| /* 1 header + n-1 * 4 bytes inline xattr to keep continuity */ | ||||
| 	__le16 i_xattr_icount; | ||||
| 	__le16 i_mode; | ||||
| 	__le16 i_reserved; | ||||
| 	__le64 i_size; | ||||
| 	union { | ||||
| 		/* file total compressed blocks for data mapping 1 */ | ||||
| 		__le32 compressed_blocks; | ||||
| 		__le32 raw_blkaddr; | ||||
|  | ||||
| 		/* for device files, used to indicate old/new device # */ | ||||
| 		__le32 rdev; | ||||
|  | ||||
| 		/* for chunk-based files, it contains the summary info */ | ||||
| 		struct erofs_inode_chunk_info c; | ||||
| 	} i_u; | ||||
|  | ||||
| 	/* only used for 32-bit stat compatibility */ | ||||
| 	__le32 i_ino; | ||||
|  | ||||
| 	__le32 i_uid; | ||||
| 	__le32 i_gid; | ||||
| 	__le64 i_ctime; | ||||
| 	__le32 i_ctime_nsec; | ||||
| 	__le32 i_nlink; | ||||
| 	__u8   i_reserved2[16]; | ||||
| }; | ||||
|  | ||||
| #define EROFS_MAX_SHARED_XATTRS         (128) | ||||
| /* h_shared_count between 129 ... 255 are special # */ | ||||
| #define EROFS_SHARED_XATTR_EXTENT       (255) | ||||
|  | ||||
| /* | ||||
|  * inline xattrs (n == i_xattr_icount): | ||||
|  * erofs_xattr_ibody_header(1) + (n - 1) * 4 bytes | ||||
|  *          12 bytes           /                   \ | ||||
|  *                            /                     \ | ||||
|  *                           /-----------------------\ | ||||
|  *                           |  erofs_xattr_entries+ | | ||||
|  *                           +-----------------------+ | ||||
|  * inline xattrs must starts in erofs_xattr_ibody_header, | ||||
|  * for read-only fs, no need to introduce h_refcount | ||||
|  */ | ||||
| struct erofs_xattr_ibody_header { | ||||
| 	__le32 h_reserved; | ||||
| 	__u8   h_shared_count; | ||||
| 	__u8   h_reserved2[7]; | ||||
| 	__le32 h_shared_xattrs[0];      /* shared xattr id array */ | ||||
| }; | ||||
|  | ||||
| /* Name indexes */ | ||||
| #define EROFS_XATTR_INDEX_USER              1 | ||||
| #define EROFS_XATTR_INDEX_POSIX_ACL_ACCESS  2 | ||||
| #define EROFS_XATTR_INDEX_POSIX_ACL_DEFAULT 3 | ||||
| #define EROFS_XATTR_INDEX_TRUSTED           4 | ||||
| #define EROFS_XATTR_INDEX_LUSTRE            5 | ||||
| #define EROFS_XATTR_INDEX_SECURITY          6 | ||||
|  | ||||
| /* xattr entry (for both inline & shared xattrs) */ | ||||
| struct erofs_xattr_entry { | ||||
| 	__u8   e_name_len;      /* length of name */ | ||||
| 	__u8   e_name_index;    /* attribute name index */ | ||||
| 	__le16 e_value_size;    /* size of attribute value */ | ||||
| 	/* followed by e_name and e_value */ | ||||
| 	char   e_name[0];       /* attribute name */ | ||||
| }; | ||||
|  | ||||
| static inline unsigned int erofs_xattr_ibody_size(__le16 i_xattr_icount) | ||||
| { | ||||
| 	if (!i_xattr_icount) | ||||
| 		return 0; | ||||
|  | ||||
| 	return sizeof(struct erofs_xattr_ibody_header) + | ||||
| 		sizeof(__u32) * (le16_to_cpu(i_xattr_icount) - 1); | ||||
| } | ||||
|  | ||||
| #define EROFS_XATTR_ALIGN(size) round_up(size, sizeof(struct erofs_xattr_entry)) | ||||
|  | ||||
| static inline unsigned int erofs_xattr_entry_size(struct erofs_xattr_entry *e) | ||||
| { | ||||
| 	return EROFS_XATTR_ALIGN(sizeof(struct erofs_xattr_entry) + | ||||
| 				 e->e_name_len + le16_to_cpu(e->e_value_size)); | ||||
| } | ||||
|  | ||||
| /* represent a zeroed chunk (hole) */ | ||||
| #define EROFS_NULL_ADDR			-1 | ||||
|  | ||||
| /* 4-byte block address array */ | ||||
| #define EROFS_BLOCK_MAP_ENTRY_SIZE	sizeof(__le32) | ||||
|  | ||||
| /* 8-byte inode chunk indexes */ | ||||
| struct erofs_inode_chunk_index { | ||||
| 	__le16 advise;		/* always 0, don't care for now */ | ||||
| 	__le16 device_id;	/* back-end storage id (with bits masked) */ | ||||
| 	__le32 blkaddr;		/* start block address of this inode chunk */ | ||||
| }; | ||||
|  | ||||
| /* maximum supported size of a physical compression cluster */ | ||||
| #define Z_EROFS_PCLUSTER_MAX_SIZE	(1024 * 1024) | ||||
|  | ||||
| /* available compression algorithm types (for h_algorithmtype) */ | ||||
| enum { | ||||
| 	Z_EROFS_COMPRESSION_LZ4		= 0, | ||||
| 	Z_EROFS_COMPRESSION_LZMA	= 1, | ||||
| 	Z_EROFS_COMPRESSION_MAX | ||||
| }; | ||||
|  | ||||
| #define Z_EROFS_ALL_COMPR_ALGS		(1 << (Z_EROFS_COMPRESSION_MAX - 1)) | ||||
|  | ||||
| /* 14 bytes (+ length field = 16 bytes) */ | ||||
| struct z_erofs_lz4_cfgs { | ||||
| 	__le16 max_distance; | ||||
| 	__le16 max_pclusterblks; | ||||
| 	u8 reserved[10]; | ||||
| } __packed; | ||||
|  | ||||
| /* 14 bytes (+ length field = 16 bytes) */ | ||||
| struct z_erofs_lzma_cfgs { | ||||
| 	__le32 dict_size; | ||||
| 	__le16 format; | ||||
| 	u8 reserved[8]; | ||||
| } __packed; | ||||
| #define Z_EROFS_LZMA_MAX_DICT_SIZE	(8 * Z_EROFS_PCLUSTER_MAX_SIZE) | ||||
|  | ||||
| /* | ||||
|  * bit 0 : COMPACTED_2B indexes (0 - off; 1 - on) | ||||
|  *  e.g. for 4k logical cluster size,      4B        if compacted 2B is off; | ||||
|  *                                  (4B) + 2B + (4B) if compacted 2B is on. | ||||
|  * bit 1 : HEAD1 big pcluster (0 - off; 1 - on) | ||||
|  * bit 2 : HEAD2 big pcluster (0 - off; 1 - on) | ||||
|  */ | ||||
| #define Z_EROFS_ADVISE_COMPACTED_2B		0x0001 | ||||
| #define Z_EROFS_ADVISE_BIG_PCLUSTER_1		0x0002 | ||||
| #define Z_EROFS_ADVISE_BIG_PCLUSTER_2		0x0004 | ||||
|  | ||||
| struct z_erofs_map_header { | ||||
| 	__le32	h_reserved1; | ||||
| 	__le16	h_advise; | ||||
| 	/* | ||||
| 	 * bit 0-3 : algorithm type of head 1 (logical cluster type 01); | ||||
| 	 * bit 4-7 : algorithm type of head 2 (logical cluster type 11). | ||||
| 	 */ | ||||
| 	__u8	h_algorithmtype; | ||||
| 	/* | ||||
| 	 * bit 0-2 : logical cluster bits - 12, e.g. 0 for 4096; | ||||
| 	 * bit 3-7 : reserved. | ||||
| 	 */ | ||||
| 	__u8	h_clusterbits; | ||||
| }; | ||||
|  | ||||
| #define Z_EROFS_VLE_LEGACY_HEADER_PADDING       8 | ||||
|  | ||||
| /* | ||||
|  * Fixed-sized output compression ondisk Logical Extent cluster type: | ||||
|  *    0 - literal (uncompressed) cluster | ||||
|  *    1 - compressed cluster (for the head logical cluster) | ||||
|  *    2 - compressed cluster (for the other logical clusters) | ||||
|  * | ||||
|  * In detail, | ||||
|  *    0 - literal (uncompressed) cluster, | ||||
|  *        di_advise = 0 | ||||
|  *        di_clusterofs = the literal data offset of the cluster | ||||
|  *        di_blkaddr = the blkaddr of the literal cluster | ||||
|  * | ||||
|  *    1 - compressed cluster (for the head logical cluster) | ||||
|  *        di_advise = 1 | ||||
|  *        di_clusterofs = the decompressed data offset of the cluster | ||||
|  *        di_blkaddr = the blkaddr of the compressed cluster | ||||
|  * | ||||
|  *    2 - compressed cluster (for the other logical clusters) | ||||
|  *        di_advise = 2 | ||||
|  *        di_clusterofs = | ||||
|  *           the decompressed data offset in its own head cluster | ||||
|  *        di_u.delta[0] = distance to its corresponding head cluster | ||||
|  *        di_u.delta[1] = distance to its corresponding tail cluster | ||||
|  *                (di_advise could be 0, 1 or 2) | ||||
|  */ | ||||
| enum { | ||||
| 	Z_EROFS_VLE_CLUSTER_TYPE_PLAIN		= 0, | ||||
| 	Z_EROFS_VLE_CLUSTER_TYPE_HEAD		= 1, | ||||
| 	Z_EROFS_VLE_CLUSTER_TYPE_NONHEAD	= 2, | ||||
| 	Z_EROFS_VLE_CLUSTER_TYPE_RESERVED	= 3, | ||||
| 	Z_EROFS_VLE_CLUSTER_TYPE_MAX | ||||
| }; | ||||
|  | ||||
| #define Z_EROFS_VLE_DI_CLUSTER_TYPE_BITS        2 | ||||
| #define Z_EROFS_VLE_DI_CLUSTER_TYPE_BIT         0 | ||||
|  | ||||
| /* | ||||
|  * D0_CBLKCNT will be marked _only_ at the 1st non-head lcluster to store the | ||||
|  * compressed block count of a compressed extent (in logical clusters, aka. | ||||
|  * block count of a pcluster). | ||||
|  */ | ||||
| #define Z_EROFS_VLE_DI_D0_CBLKCNT		(1 << 11) | ||||
|  | ||||
| struct z_erofs_vle_decompressed_index { | ||||
| 	__le16 di_advise; | ||||
| 	/* where to decompress in the head cluster */ | ||||
| 	__le16 di_clusterofs; | ||||
|  | ||||
| 	union { | ||||
| 		/* for the head cluster */ | ||||
| 		__le32 blkaddr; | ||||
| 		/* | ||||
| 		 * for the rest clusters | ||||
| 		 * eg. for 4k page-sized cluster, maximum 4K*64k = 256M) | ||||
| 		 * [0] - pointing to the head cluster | ||||
| 		 * [1] - pointing to the tail cluster | ||||
| 		 */ | ||||
| 		__le16 delta[2]; | ||||
| 	} di_u; | ||||
| }; | ||||
|  | ||||
| #define Z_EROFS_VLE_LEGACY_INDEX_ALIGN(size) \ | ||||
| 	(round_up(size, sizeof(struct z_erofs_vle_decompressed_index)) + \ | ||||
| 	 sizeof(struct z_erofs_map_header) + Z_EROFS_VLE_LEGACY_HEADER_PADDING) | ||||
|  | ||||
| #define Z_EROFS_VLE_EXTENT_ALIGN(size) round_up(size, \ | ||||
| 	sizeof(struct z_erofs_vle_decompressed_index)) | ||||
|  | ||||
| /* dirent sorts in alphabet order, thus we can do binary search */ | ||||
| struct erofs_dirent { | ||||
| 	__le64 nid;     /* node number */ | ||||
| 	__le16 nameoff; /* start offset of file name */ | ||||
| 	__u8 file_type; /* file type */ | ||||
| 	__u8 reserved;  /* reserved */ | ||||
| } __packed; | ||||
|  | ||||
| /* file types used in inode_info->flags */ | ||||
| enum { | ||||
| 	EROFS_FT_UNKNOWN, | ||||
| 	EROFS_FT_REG_FILE, | ||||
| 	EROFS_FT_DIR, | ||||
| 	EROFS_FT_CHRDEV, | ||||
| 	EROFS_FT_BLKDEV, | ||||
| 	EROFS_FT_FIFO, | ||||
| 	EROFS_FT_SOCK, | ||||
| 	EROFS_FT_SYMLINK, | ||||
| 	EROFS_FT_MAX | ||||
| }; | ||||
|  | ||||
| #define EROFS_NAME_LEN      255 | ||||
|  | ||||
| /* check the EROFS on-disk layout strictly at compile time */ | ||||
| static inline void erofs_check_ondisk_layout_definitions(void) | ||||
| { | ||||
| 	BUILD_BUG_ON(sizeof(struct erofs_super_block) != 128); | ||||
| 	BUILD_BUG_ON(sizeof(struct erofs_inode_compact) != 32); | ||||
| 	BUILD_BUG_ON(sizeof(struct erofs_inode_extended) != 64); | ||||
| 	BUILD_BUG_ON(sizeof(struct erofs_xattr_ibody_header) != 12); | ||||
| 	BUILD_BUG_ON(sizeof(struct erofs_xattr_entry) != 4); | ||||
| 	BUILD_BUG_ON(sizeof(struct erofs_inode_chunk_info) != 4); | ||||
| 	BUILD_BUG_ON(sizeof(struct erofs_inode_chunk_index) != 8); | ||||
| 	BUILD_BUG_ON(sizeof(struct z_erofs_map_header) != 8); | ||||
| 	BUILD_BUG_ON(sizeof(struct z_erofs_vle_decompressed_index) != 8); | ||||
| 	BUILD_BUG_ON(sizeof(struct erofs_dirent) != 12); | ||||
| 	/* keep in sync between 2 index structures for better extendibility */ | ||||
| 	BUILD_BUG_ON(sizeof(struct erofs_inode_chunk_index) != | ||||
| 		     sizeof(struct z_erofs_vle_decompressed_index)); | ||||
| 	BUILD_BUG_ON(sizeof(struct erofs_deviceslot) != 128); | ||||
|  | ||||
| 	BUILD_BUG_ON(BIT(Z_EROFS_VLE_DI_CLUSTER_TYPE_BITS) < | ||||
| 		     Z_EROFS_VLE_CLUSTER_TYPE_MAX - 1); | ||||
| } | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										267
									
								
								fs/erofs/fs.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										267
									
								
								fs/erofs/fs.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,267 @@ | ||||
| // SPDX-License-Identifier: GPL-2.0+ | ||||
| #include "internal.h" | ||||
| #include <fs_internal.h> | ||||
|  | ||||
| struct erofs_sb_info sbi; | ||||
|  | ||||
| static struct erofs_ctxt { | ||||
| 	struct disk_partition cur_part_info; | ||||
| 	struct blk_desc *cur_dev; | ||||
| } ctxt; | ||||
|  | ||||
| int erofs_dev_read(int device_id, void *buf, u64 offset, size_t len) | ||||
| { | ||||
| 	lbaint_t sect = offset >> ctxt.cur_dev->log2blksz; | ||||
| 	int off = offset & (ctxt.cur_dev->blksz - 1); | ||||
|  | ||||
| 	if (!ctxt.cur_dev) | ||||
| 		return -EIO; | ||||
|  | ||||
| 	if (fs_devread(ctxt.cur_dev, &ctxt.cur_part_info, sect, | ||||
| 		       off, len, buf)) | ||||
| 		return 0; | ||||
| 	return -EIO; | ||||
| } | ||||
|  | ||||
| int erofs_blk_read(void *buf, erofs_blk_t start, u32 nblocks) | ||||
| { | ||||
| 	return erofs_dev_read(0, buf, blknr_to_addr(start), | ||||
| 			 blknr_to_addr(nblocks)); | ||||
| } | ||||
|  | ||||
| int erofs_probe(struct blk_desc *fs_dev_desc, | ||||
| 		struct disk_partition *fs_partition) | ||||
| { | ||||
| 	int ret; | ||||
|  | ||||
| 	ctxt.cur_dev = fs_dev_desc; | ||||
| 	ctxt.cur_part_info = *fs_partition; | ||||
|  | ||||
| 	ret = erofs_read_superblock(); | ||||
| 	if (ret) | ||||
| 		goto error; | ||||
|  | ||||
| 	return 0; | ||||
| error: | ||||
| 	ctxt.cur_dev = NULL; | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
| struct erofs_dir_stream { | ||||
| 	struct fs_dir_stream fs_dirs; | ||||
| 	struct fs_dirent dirent; | ||||
|  | ||||
| 	struct erofs_inode inode; | ||||
| 	char dblk[EROFS_BLKSIZ]; | ||||
| 	unsigned int maxsize, de_end; | ||||
| 	erofs_off_t pos; | ||||
| }; | ||||
|  | ||||
| static int erofs_readlink(struct erofs_inode *vi) | ||||
| { | ||||
| 	size_t len = vi->i_size; | ||||
| 	char *target; | ||||
| 	int err; | ||||
|  | ||||
| 	target = malloc(len + 1); | ||||
| 	if (!target) | ||||
| 		return -ENOMEM; | ||||
| 	target[len] = '\0'; | ||||
|  | ||||
| 	err = erofs_pread(vi, target, len, 0); | ||||
| 	if (err) | ||||
| 		goto err_out; | ||||
|  | ||||
| 	err = erofs_ilookup(target, vi); | ||||
| 	if (err) | ||||
| 		goto err_out; | ||||
|  | ||||
| err_out: | ||||
| 	free(target); | ||||
| 	return err; | ||||
| } | ||||
|  | ||||
| int erofs_opendir(const char *filename, struct fs_dir_stream **dirsp) | ||||
| { | ||||
| 	struct erofs_dir_stream *dirs; | ||||
| 	int err; | ||||
|  | ||||
| 	dirs = calloc(1, sizeof(*dirs)); | ||||
| 	if (!dirs) | ||||
| 		return -ENOMEM; | ||||
|  | ||||
| 	err = erofs_ilookup(filename, &dirs->inode); | ||||
| 	if (err) | ||||
| 		goto err_out; | ||||
|  | ||||
| 	if (S_ISLNK(dirs->inode.i_mode)) { | ||||
| 		err = erofs_readlink(&dirs->inode); | ||||
| 		if (err) | ||||
| 			goto err_out; | ||||
| 	} | ||||
|  | ||||
| 	if (!S_ISDIR(dirs->inode.i_mode)) { | ||||
| 		err = -ENOTDIR; | ||||
| 		goto err_out; | ||||
| 	} | ||||
| 	*dirsp = (struct fs_dir_stream *)dirs; | ||||
| 	return 0; | ||||
| err_out: | ||||
| 	free(dirs); | ||||
| 	return err; | ||||
| } | ||||
|  | ||||
| int erofs_readdir(struct fs_dir_stream *fs_dirs, struct fs_dirent **dentp) | ||||
| { | ||||
| 	struct erofs_dir_stream *dirs = (struct erofs_dir_stream *)fs_dirs; | ||||
| 	struct fs_dirent *dent = &dirs->dirent; | ||||
| 	erofs_off_t pos = dirs->pos; | ||||
| 	unsigned int nameoff, de_namelen; | ||||
| 	struct erofs_dirent *de; | ||||
| 	char *de_name; | ||||
| 	int err; | ||||
|  | ||||
| 	if (pos >= dirs->inode.i_size) | ||||
| 		return 1; | ||||
|  | ||||
| 	if (!dirs->maxsize) { | ||||
| 		dirs->maxsize = min_t(unsigned int, EROFS_BLKSIZ, | ||||
| 				      dirs->inode.i_size - pos); | ||||
|  | ||||
| 		err = erofs_pread(&dirs->inode, dirs->dblk, | ||||
| 				  dirs->maxsize, pos); | ||||
| 		if (err) | ||||
| 			return err; | ||||
|  | ||||
| 		de = (struct erofs_dirent *)dirs->dblk; | ||||
| 		dirs->de_end = le16_to_cpu(de->nameoff); | ||||
| 		if (dirs->de_end < sizeof(struct erofs_dirent) || | ||||
| 		    dirs->de_end >= EROFS_BLKSIZ) { | ||||
| 			erofs_err("invalid de[0].nameoff %u @ nid %llu", | ||||
| 				  dirs->de_end, de->nid | 0ULL); | ||||
| 			return -EFSCORRUPTED; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	de = (struct erofs_dirent *)(dirs->dblk + erofs_blkoff(pos)); | ||||
| 	nameoff = le16_to_cpu(de->nameoff); | ||||
| 	de_name = (char *)dirs->dblk + nameoff; | ||||
|  | ||||
| 	/* the last dirent in the block? */ | ||||
| 	if (de + 1 >= (struct erofs_dirent *)(dirs->dblk + dirs->de_end)) | ||||
| 		de_namelen = strnlen(de_name, dirs->maxsize - nameoff); | ||||
| 	else | ||||
| 		de_namelen = le16_to_cpu(de[1].nameoff) - nameoff; | ||||
|  | ||||
| 	/* a corrupted entry is found */ | ||||
| 	if (nameoff + de_namelen > dirs->maxsize || | ||||
| 	    de_namelen > EROFS_NAME_LEN) { | ||||
| 		erofs_err("bogus dirent @ nid %llu", de->nid | 0ULL); | ||||
| 		DBG_BUGON(1); | ||||
| 		return -EFSCORRUPTED; | ||||
| 	} | ||||
|  | ||||
| 	memcpy(dent->name, de_name, de_namelen); | ||||
| 	dent->name[de_namelen] = '\0'; | ||||
|  | ||||
| 	if (de->file_type == EROFS_FT_DIR) { | ||||
| 		dent->type = FS_DT_DIR; | ||||
| 	} else if (de->file_type == EROFS_FT_SYMLINK) { | ||||
| 		dent->type = FS_DT_LNK; | ||||
| 	} else { | ||||
| 		struct erofs_inode vi; | ||||
|  | ||||
| 		dent->type = FS_DT_REG; | ||||
| 		vi.nid = de->nid; | ||||
|  | ||||
| 		err = erofs_read_inode_from_disk(&vi); | ||||
| 		if (err) | ||||
| 			return err; | ||||
| 		dent->size = vi.i_size; | ||||
| 	} | ||||
| 	*dentp = dent; | ||||
|  | ||||
| 	pos += sizeof(*de); | ||||
| 	if (erofs_blkoff(pos) >= dirs->de_end) { | ||||
| 		pos = blknr_to_addr(erofs_blknr(pos) + 1); | ||||
| 		dirs->maxsize = 0; | ||||
| 	} | ||||
| 	dirs->pos = pos; | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| void erofs_closedir(struct fs_dir_stream *fs_dirs) | ||||
| { | ||||
| 	free(fs_dirs); | ||||
| } | ||||
|  | ||||
| int erofs_exists(const char *filename) | ||||
| { | ||||
| 	struct erofs_inode vi; | ||||
| 	int err; | ||||
|  | ||||
| 	err = erofs_ilookup(filename, &vi); | ||||
| 	return err == 0; | ||||
| } | ||||
|  | ||||
| int erofs_size(const char *filename, loff_t *size) | ||||
| { | ||||
| 	struct erofs_inode vi; | ||||
| 	int err; | ||||
|  | ||||
| 	err = erofs_ilookup(filename, &vi); | ||||
| 	if (err) | ||||
| 		return err; | ||||
| 	*size = vi.i_size; | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int erofs_read(const char *filename, void *buf, loff_t offset, loff_t len, | ||||
| 	       loff_t *actread) | ||||
| { | ||||
| 	struct erofs_inode vi; | ||||
| 	int err; | ||||
|  | ||||
| 	err = erofs_ilookup(filename, &vi); | ||||
| 	if (err) | ||||
| 		return err; | ||||
|  | ||||
| 	if (S_ISLNK(vi.i_mode)) { | ||||
| 		err = erofs_readlink(&vi); | ||||
| 		if (err) | ||||
| 			return err; | ||||
| 	} | ||||
|  | ||||
| 	if (!len) | ||||
| 		len = vi.i_size; | ||||
|  | ||||
| 	err = erofs_pread(&vi, buf, len, offset); | ||||
| 	if (err) { | ||||
| 		*actread = 0; | ||||
| 		return err; | ||||
| 	} | ||||
|  | ||||
| 	if (offset >= vi.i_size) | ||||
| 		*actread = 0; | ||||
| 	else if (offset + len > vi.i_size) | ||||
| 		*actread = vi.i_size - offset; | ||||
| 	else | ||||
| 		*actread = len; | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| void erofs_close(void) | ||||
| { | ||||
| 	ctxt.cur_dev = NULL; | ||||
| } | ||||
|  | ||||
| int erofs_uuid(char *uuid_str) | ||||
| { | ||||
| 	if (IS_ENABLED(CONFIG_LIB_UUID)) { | ||||
| 		if (ctxt.cur_dev) | ||||
| 			uuid_bin_to_str(sbi.uuid, uuid_str, | ||||
| 					UUID_STR_FORMAT_STD); | ||||
| 		return 0; | ||||
| 	} | ||||
| 	return -ENOSYS; | ||||
| } | ||||
							
								
								
									
										313
									
								
								fs/erofs/internal.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										313
									
								
								fs/erofs/internal.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,313 @@ | ||||
| /* SPDX-License-Identifier: GPL-2.0+ */ | ||||
| #ifndef __EROFS_INTERNAL_H | ||||
| #define __EROFS_INTERNAL_H | ||||
|  | ||||
| #define __packed __attribute__((__packed__)) | ||||
|  | ||||
| #include <linux/stat.h> | ||||
| #include <linux/bug.h> | ||||
| #include <linux/err.h> | ||||
| #include <linux/printk.h> | ||||
| #include <linux/log2.h> | ||||
| #include <inttypes.h> | ||||
| #include "erofs_fs.h" | ||||
|  | ||||
| #define erofs_err(fmt, ...)	\ | ||||
| 	pr_err(fmt "\n", ##__VA_ARGS__) | ||||
|  | ||||
| #define erofs_info(fmt, ...)	\ | ||||
| 	pr_info(fmt "\n", ##__VA_ARGS__) | ||||
|  | ||||
| #define erofs_dbg(fmt, ...)	\ | ||||
| 	pr_debug(fmt "\n", ##__VA_ARGS__) | ||||
|  | ||||
| #define DBG_BUGON(condition)	BUG_ON(condition) | ||||
|  | ||||
| /* no obvious reason to support explicit PAGE_SIZE != 4096 for now */ | ||||
| #if PAGE_SIZE != 4096 | ||||
| #error incompatible PAGE_SIZE is already defined | ||||
| #endif | ||||
|  | ||||
| #define PAGE_MASK		(~(PAGE_SIZE - 1)) | ||||
|  | ||||
| #define LOG_BLOCK_SIZE          (12) | ||||
| #define EROFS_BLKSIZ            (1U << LOG_BLOCK_SIZE) | ||||
|  | ||||
| #define EROFS_ISLOTBITS		5 | ||||
| #define EROFS_SLOTSIZE		(1U << EROFS_ISLOTBITS) | ||||
|  | ||||
| typedef u64 erofs_off_t; | ||||
| typedef u64 erofs_nid_t; | ||||
| /* data type for filesystem-wide blocks number */ | ||||
| typedef u32 erofs_blk_t; | ||||
|  | ||||
| #define NULL_ADDR	((unsigned int)-1) | ||||
| #define NULL_ADDR_UL	((unsigned long)-1) | ||||
|  | ||||
| #define erofs_blknr(addr)       ((addr) / EROFS_BLKSIZ) | ||||
| #define erofs_blkoff(addr)      ((addr) % EROFS_BLKSIZ) | ||||
| #define blknr_to_addr(nr)       ((erofs_off_t)(nr) * EROFS_BLKSIZ) | ||||
|  | ||||
| #define BLK_ROUND_UP(addr)	DIV_ROUND_UP(addr, EROFS_BLKSIZ) | ||||
|  | ||||
| struct erofs_buffer_head; | ||||
|  | ||||
| struct erofs_device_info { | ||||
| 	u32 blocks; | ||||
| 	u32 mapped_blkaddr; | ||||
| }; | ||||
|  | ||||
| struct erofs_sb_info { | ||||
| 	struct erofs_device_info *devs; | ||||
|  | ||||
| 	u64 total_blocks; | ||||
| 	u64 primarydevice_blocks; | ||||
|  | ||||
| 	erofs_blk_t meta_blkaddr; | ||||
| 	erofs_blk_t xattr_blkaddr; | ||||
|  | ||||
| 	u32 feature_compat; | ||||
| 	u32 feature_incompat; | ||||
| 	u64 build_time; | ||||
| 	u32 build_time_nsec; | ||||
|  | ||||
| 	unsigned char islotbits; | ||||
|  | ||||
| 	/* what we really care is nid, rather than ino.. */ | ||||
| 	erofs_nid_t root_nid; | ||||
| 	/* used for statfs, f_files - f_favail */ | ||||
| 	u64 inos; | ||||
|  | ||||
| 	u8 uuid[16]; | ||||
|  | ||||
| 	u16 available_compr_algs; | ||||
| 	u16 lz4_max_distance; | ||||
| 	u32 checksum; | ||||
| 	u16 extra_devices; | ||||
| 	union { | ||||
| 		u16 devt_slotoff;		/* used for mkfs */ | ||||
| 		u16 device_id_mask;		/* used for others */ | ||||
| 	}; | ||||
| }; | ||||
|  | ||||
| /* global sbi */ | ||||
| extern struct erofs_sb_info sbi; | ||||
|  | ||||
| static inline erofs_off_t iloc(erofs_nid_t nid) | ||||
| { | ||||
| 	return blknr_to_addr(sbi.meta_blkaddr) + (nid << sbi.islotbits); | ||||
| } | ||||
|  | ||||
| #define EROFS_FEATURE_FUNCS(name, compat, feature) \ | ||||
| static inline bool erofs_sb_has_##name(void) \ | ||||
| { \ | ||||
| 	return sbi.feature_##compat & EROFS_FEATURE_##feature; \ | ||||
| } \ | ||||
| static inline void erofs_sb_set_##name(void) \ | ||||
| { \ | ||||
| 	sbi.feature_##compat |= EROFS_FEATURE_##feature; \ | ||||
| } \ | ||||
| static inline void erofs_sb_clear_##name(void) \ | ||||
| { \ | ||||
| 	sbi.feature_##compat &= ~EROFS_FEATURE_##feature; \ | ||||
| } | ||||
|  | ||||
| EROFS_FEATURE_FUNCS(lz4_0padding, incompat, INCOMPAT_LZ4_0PADDING) | ||||
| EROFS_FEATURE_FUNCS(compr_cfgs, incompat, INCOMPAT_COMPR_CFGS) | ||||
| EROFS_FEATURE_FUNCS(big_pcluster, incompat, INCOMPAT_BIG_PCLUSTER) | ||||
| EROFS_FEATURE_FUNCS(chunked_file, incompat, INCOMPAT_CHUNKED_FILE) | ||||
| EROFS_FEATURE_FUNCS(device_table, incompat, INCOMPAT_DEVICE_TABLE) | ||||
| EROFS_FEATURE_FUNCS(sb_chksum, compat, COMPAT_SB_CHKSUM) | ||||
|  | ||||
| #define EROFS_I_EA_INITED	(1 << 0) | ||||
| #define EROFS_I_Z_INITED	(1 << 1) | ||||
|  | ||||
| struct erofs_inode { | ||||
| 	struct list_head i_hash, i_subdirs, i_xattrs; | ||||
|  | ||||
| 	union { | ||||
| 		/* (erofsfuse) runtime flags */ | ||||
| 		unsigned int flags; | ||||
| 		/* (mkfs.erofs) device ID containing source file */ | ||||
| 		u32 dev; | ||||
| 	}; | ||||
| 	unsigned int i_count; | ||||
| 	struct erofs_inode *i_parent; | ||||
|  | ||||
| 	umode_t i_mode; | ||||
| 	erofs_off_t i_size; | ||||
|  | ||||
| 	u64 i_ino[2]; | ||||
| 	u32 i_uid; | ||||
| 	u32 i_gid; | ||||
| 	u64 i_ctime; | ||||
| 	u32 i_ctime_nsec; | ||||
| 	u32 i_nlink; | ||||
|  | ||||
| 	union { | ||||
| 		u32 i_blkaddr; | ||||
| 		u32 i_blocks; | ||||
| 		u32 i_rdev; | ||||
| 		struct { | ||||
| 			unsigned short	chunkformat; | ||||
| 			unsigned char	chunkbits; | ||||
| 		}; | ||||
| 	} u; | ||||
|  | ||||
| 	unsigned char datalayout; | ||||
| 	unsigned char inode_isize; | ||||
| 	/* inline tail-end packing size */ | ||||
| 	unsigned short idata_size; | ||||
|  | ||||
| 	unsigned int xattr_isize; | ||||
| 	unsigned int extent_isize; | ||||
|  | ||||
| 	erofs_nid_t nid; | ||||
| 	struct erofs_buffer_head *bh; | ||||
| 	struct erofs_buffer_head *bh_inline, *bh_data; | ||||
|  | ||||
| 	void *idata; | ||||
|  | ||||
| 	union { | ||||
| 		void *compressmeta; | ||||
| 		void *chunkindexes; | ||||
| 		struct { | ||||
| 			uint16_t z_advise; | ||||
| 			uint8_t  z_algorithmtype[2]; | ||||
| 			uint8_t  z_logical_clusterbits; | ||||
| 			uint8_t  z_physical_clusterblks; | ||||
| 		}; | ||||
| 	}; | ||||
| }; | ||||
|  | ||||
| static inline bool is_inode_layout_compression(struct erofs_inode *inode) | ||||
| { | ||||
| 	return erofs_inode_is_data_compressed(inode->datalayout); | ||||
| } | ||||
|  | ||||
| static inline unsigned int erofs_bitrange(unsigned int value, unsigned int bit, | ||||
| 					  unsigned int bits) | ||||
| { | ||||
| 	return (value >> bit) & ((1 << bits) - 1); | ||||
| } | ||||
|  | ||||
| static inline unsigned int erofs_inode_version(unsigned int value) | ||||
| { | ||||
| 	return erofs_bitrange(value, EROFS_I_VERSION_BIT, | ||||
| 			      EROFS_I_VERSION_BITS); | ||||
| } | ||||
|  | ||||
| static inline unsigned int erofs_inode_datalayout(unsigned int value) | ||||
| { | ||||
| 	return erofs_bitrange(value, EROFS_I_DATALAYOUT_BIT, | ||||
| 			      EROFS_I_DATALAYOUT_BITS); | ||||
| } | ||||
|  | ||||
| #define IS_ROOT(x)	((x) == (x)->i_parent) | ||||
|  | ||||
| struct erofs_dentry { | ||||
| 	struct list_head d_child;	/* child of parent list */ | ||||
|  | ||||
| 	unsigned int type; | ||||
| 	char name[EROFS_NAME_LEN]; | ||||
| 	union { | ||||
| 		struct erofs_inode *inode; | ||||
| 		erofs_nid_t nid; | ||||
| 	}; | ||||
| }; | ||||
|  | ||||
| static inline bool is_dot_dotdot(const char *name) | ||||
| { | ||||
| 	if (name[0] != '.') | ||||
| 		return false; | ||||
|  | ||||
| 	return name[1] == '\0' || (name[1] == '.' && name[2] == '\0'); | ||||
| } | ||||
|  | ||||
| enum { | ||||
| 	BH_Meta, | ||||
| 	BH_Mapped, | ||||
| 	BH_Encoded, | ||||
| 	BH_FullMapped, | ||||
| }; | ||||
|  | ||||
| /* Has a disk mapping */ | ||||
| #define EROFS_MAP_MAPPED	(1 << BH_Mapped) | ||||
| /* Located in metadata (could be copied from bd_inode) */ | ||||
| #define EROFS_MAP_META		(1 << BH_Meta) | ||||
| /* The extent is encoded */ | ||||
| #define EROFS_MAP_ENCODED	(1 << BH_Encoded) | ||||
| /* The length of extent is full */ | ||||
| #define EROFS_MAP_FULL_MAPPED	(1 << BH_FullMapped) | ||||
|  | ||||
| struct erofs_map_blocks { | ||||
| 	char mpage[EROFS_BLKSIZ]; | ||||
|  | ||||
| 	erofs_off_t m_pa, m_la; | ||||
| 	u64 m_plen, m_llen; | ||||
|  | ||||
| 	unsigned short m_deviceid; | ||||
| 	char m_algorithmformat; | ||||
| 	unsigned int m_flags; | ||||
| 	erofs_blk_t index; | ||||
| }; | ||||
|  | ||||
| /* | ||||
|  * Used to get the exact decompressed length, e.g. fiemap (consider lookback | ||||
|  * approach instead if possible since it's more metadata lightweight.) | ||||
|  */ | ||||
| #define EROFS_GET_BLOCKS_FIEMAP	0x0002 | ||||
|  | ||||
| enum { | ||||
| 	Z_EROFS_COMPRESSION_SHIFTED = Z_EROFS_COMPRESSION_MAX, | ||||
| 	Z_EROFS_COMPRESSION_RUNTIME_MAX | ||||
| }; | ||||
|  | ||||
| struct erofs_map_dev { | ||||
| 	erofs_off_t m_pa; | ||||
| 	unsigned int m_deviceid; | ||||
| }; | ||||
|  | ||||
| /* fs.c */ | ||||
| int erofs_blk_read(void *buf, erofs_blk_t start, u32 nblocks); | ||||
| int erofs_dev_read(int device_id, void *buf, u64 offset, size_t len); | ||||
|  | ||||
| /* super.c */ | ||||
| int erofs_read_superblock(void); | ||||
|  | ||||
| /* namei.c */ | ||||
| int erofs_read_inode_from_disk(struct erofs_inode *vi); | ||||
| int erofs_ilookup(const char *path, struct erofs_inode *vi); | ||||
| int erofs_read_inode_from_disk(struct erofs_inode *vi); | ||||
|  | ||||
| /* data.c */ | ||||
| int erofs_pread(struct erofs_inode *inode, char *buf, | ||||
| 		erofs_off_t count, erofs_off_t offset); | ||||
| int erofs_map_blocks(struct erofs_inode *inode, | ||||
| 		     struct erofs_map_blocks *map, int flags); | ||||
| int erofs_map_dev(struct erofs_sb_info *sbi, struct erofs_map_dev *map); | ||||
| /* zmap.c */ | ||||
| int z_erofs_fill_inode(struct erofs_inode *vi); | ||||
| int z_erofs_map_blocks_iter(struct erofs_inode *vi, | ||||
| 			    struct erofs_map_blocks *map, int flags); | ||||
|  | ||||
| #ifdef EUCLEAN | ||||
| #define EFSCORRUPTED	EUCLEAN		/* Filesystem is corrupted */ | ||||
| #else | ||||
| #define EFSCORRUPTED	EIO | ||||
| #endif | ||||
|  | ||||
| #define CRC32C_POLY_LE	0x82F63B78 | ||||
| static inline u32 erofs_crc32c(u32 crc, const u8 *in, size_t len) | ||||
| { | ||||
| 	int i; | ||||
|  | ||||
| 	while (len--) { | ||||
| 		crc ^= *in++; | ||||
| 		for (i = 0; i < 8; i++) | ||||
| 			crc = (crc >> 1) ^ ((crc & 1) ? CRC32C_POLY_LE : 0); | ||||
| 	} | ||||
| 	return crc; | ||||
| } | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										252
									
								
								fs/erofs/namei.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										252
									
								
								fs/erofs/namei.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,252 @@ | ||||
| // SPDX-License-Identifier: GPL-2.0+ | ||||
| #include "internal.h" | ||||
|  | ||||
| int erofs_read_inode_from_disk(struct erofs_inode *vi) | ||||
| { | ||||
| 	int ret, ifmt; | ||||
| 	char buf[sizeof(struct erofs_inode_extended)]; | ||||
| 	struct erofs_inode_compact *dic; | ||||
| 	struct erofs_inode_extended *die; | ||||
| 	const erofs_off_t inode_loc = iloc(vi->nid); | ||||
|  | ||||
| 	ret = erofs_dev_read(0, buf, inode_loc, sizeof(*dic)); | ||||
| 	if (ret < 0) | ||||
| 		return -EIO; | ||||
|  | ||||
| 	dic = (struct erofs_inode_compact *)buf; | ||||
| 	ifmt = le16_to_cpu(dic->i_format); | ||||
|  | ||||
| 	vi->datalayout = erofs_inode_datalayout(ifmt); | ||||
| 	if (vi->datalayout >= EROFS_INODE_DATALAYOUT_MAX) { | ||||
| 		erofs_err("unsupported datalayout %u of nid %llu", | ||||
| 			  vi->datalayout, vi->nid | 0ULL); | ||||
| 		return -EOPNOTSUPP; | ||||
| 	} | ||||
| 	switch (erofs_inode_version(ifmt)) { | ||||
| 	case EROFS_INODE_LAYOUT_EXTENDED: | ||||
| 		vi->inode_isize = sizeof(struct erofs_inode_extended); | ||||
|  | ||||
| 		ret = erofs_dev_read(0, buf + sizeof(*dic), inode_loc + sizeof(*dic), | ||||
| 				     sizeof(*die) - sizeof(*dic)); | ||||
| 		if (ret < 0) | ||||
| 			return -EIO; | ||||
|  | ||||
| 		die = (struct erofs_inode_extended *)buf; | ||||
| 		vi->xattr_isize = erofs_xattr_ibody_size(die->i_xattr_icount); | ||||
| 		vi->i_mode = le16_to_cpu(die->i_mode); | ||||
|  | ||||
| 		switch (vi->i_mode & S_IFMT) { | ||||
| 		case S_IFREG: | ||||
| 		case S_IFDIR: | ||||
| 		case S_IFLNK: | ||||
| 			vi->u.i_blkaddr = le32_to_cpu(die->i_u.raw_blkaddr); | ||||
| 			break; | ||||
| 		case S_IFCHR: | ||||
| 		case S_IFBLK: | ||||
| 			vi->u.i_rdev = 0; | ||||
| 			break; | ||||
| 		case S_IFIFO: | ||||
| 		case S_IFSOCK: | ||||
| 			vi->u.i_rdev = 0; | ||||
| 			break; | ||||
| 		default: | ||||
| 			goto bogusimode; | ||||
| 		} | ||||
|  | ||||
| 		vi->i_uid = le32_to_cpu(die->i_uid); | ||||
| 		vi->i_gid = le32_to_cpu(die->i_gid); | ||||
| 		vi->i_nlink = le32_to_cpu(die->i_nlink); | ||||
|  | ||||
| 		vi->i_ctime = le64_to_cpu(die->i_ctime); | ||||
| 		vi->i_ctime_nsec = le64_to_cpu(die->i_ctime_nsec); | ||||
| 		vi->i_size = le64_to_cpu(die->i_size); | ||||
| 		if (vi->datalayout == EROFS_INODE_CHUNK_BASED) | ||||
| 			/* fill chunked inode summary info */ | ||||
| 			vi->u.chunkformat = le16_to_cpu(die->i_u.c.format); | ||||
| 		break; | ||||
| 	case EROFS_INODE_LAYOUT_COMPACT: | ||||
| 		vi->inode_isize = sizeof(struct erofs_inode_compact); | ||||
| 		vi->xattr_isize = erofs_xattr_ibody_size(dic->i_xattr_icount); | ||||
| 		vi->i_mode = le16_to_cpu(dic->i_mode); | ||||
|  | ||||
| 		switch (vi->i_mode & S_IFMT) { | ||||
| 		case S_IFREG: | ||||
| 		case S_IFDIR: | ||||
| 		case S_IFLNK: | ||||
| 			vi->u.i_blkaddr = le32_to_cpu(dic->i_u.raw_blkaddr); | ||||
| 			break; | ||||
| 		case S_IFCHR: | ||||
| 		case S_IFBLK: | ||||
| 			vi->u.i_rdev = 0; | ||||
| 			break; | ||||
| 		case S_IFIFO: | ||||
| 		case S_IFSOCK: | ||||
| 			vi->u.i_rdev = 0; | ||||
| 			break; | ||||
| 		default: | ||||
| 			goto bogusimode; | ||||
| 		} | ||||
|  | ||||
| 		vi->i_uid = le16_to_cpu(dic->i_uid); | ||||
| 		vi->i_gid = le16_to_cpu(dic->i_gid); | ||||
| 		vi->i_nlink = le16_to_cpu(dic->i_nlink); | ||||
|  | ||||
| 		vi->i_ctime = sbi.build_time; | ||||
| 		vi->i_ctime_nsec = sbi.build_time_nsec; | ||||
|  | ||||
| 		vi->i_size = le32_to_cpu(dic->i_size); | ||||
| 		if (vi->datalayout == EROFS_INODE_CHUNK_BASED) | ||||
| 			vi->u.chunkformat = le16_to_cpu(dic->i_u.c.format); | ||||
| 		break; | ||||
| 	default: | ||||
| 		erofs_err("unsupported on-disk inode version %u of nid %llu", | ||||
| 			  erofs_inode_version(ifmt), vi->nid | 0ULL); | ||||
| 		return -EOPNOTSUPP; | ||||
| 	} | ||||
|  | ||||
| 	vi->flags = 0; | ||||
| 	if (vi->datalayout == EROFS_INODE_CHUNK_BASED) { | ||||
| 		if (vi->u.chunkformat & ~EROFS_CHUNK_FORMAT_ALL) { | ||||
| 			erofs_err("unsupported chunk format %x of nid %llu", | ||||
| 				  vi->u.chunkformat, vi->nid | 0ULL); | ||||
| 			return -EOPNOTSUPP; | ||||
| 		} | ||||
| 		vi->u.chunkbits = LOG_BLOCK_SIZE + | ||||
| 			(vi->u.chunkformat & EROFS_CHUNK_FORMAT_BLKBITS_MASK); | ||||
| 	} else if (erofs_inode_is_data_compressed(vi->datalayout)) | ||||
| 		return -EOPNOTSUPP; | ||||
| 	return 0; | ||||
| bogusimode: | ||||
| 	erofs_err("bogus i_mode (%o) @ nid %llu", vi->i_mode, vi->nid | 0ULL); | ||||
| 	return -EFSCORRUPTED; | ||||
| } | ||||
|  | ||||
| struct erofs_dirent *find_target_dirent(erofs_nid_t pnid, | ||||
| 					void *dentry_blk, | ||||
| 					const char *name, unsigned int len, | ||||
| 					unsigned int nameoff, | ||||
| 					unsigned int maxsize) | ||||
| { | ||||
| 	struct erofs_dirent *de = dentry_blk; | ||||
| 	const struct erofs_dirent *end = dentry_blk + nameoff; | ||||
|  | ||||
| 	while (de < end) { | ||||
| 		const char *de_name; | ||||
| 		unsigned int de_namelen; | ||||
|  | ||||
| 		nameoff = le16_to_cpu(de->nameoff); | ||||
| 		de_name = (char *)dentry_blk + nameoff; | ||||
|  | ||||
| 		/* the last dirent in the block? */ | ||||
| 		if (de + 1 >= end) | ||||
| 			de_namelen = strnlen(de_name, maxsize - nameoff); | ||||
| 		else | ||||
| 			de_namelen = le16_to_cpu(de[1].nameoff) - nameoff; | ||||
|  | ||||
| 		/* a corrupted entry is found */ | ||||
| 		if (nameoff + de_namelen > maxsize || | ||||
| 		    de_namelen > EROFS_NAME_LEN) { | ||||
| 			erofs_err("bogus dirent @ nid %llu", pnid | 0ULL); | ||||
| 			DBG_BUGON(1); | ||||
| 			return ERR_PTR(-EFSCORRUPTED); | ||||
| 		} | ||||
|  | ||||
| 		if (len == de_namelen && !memcmp(de_name, name, de_namelen)) | ||||
| 			return de; | ||||
| 		++de; | ||||
| 	} | ||||
| 	return NULL; | ||||
| } | ||||
|  | ||||
| struct nameidata { | ||||
| 	erofs_nid_t	nid; | ||||
| 	unsigned int	ftype; | ||||
| }; | ||||
|  | ||||
| int erofs_namei(struct nameidata *nd, | ||||
| 		const char *name, unsigned int len) | ||||
| { | ||||
| 	erofs_nid_t nid = nd->nid; | ||||
| 	int ret; | ||||
| 	char buf[EROFS_BLKSIZ]; | ||||
| 	struct erofs_inode vi = { .nid = nid }; | ||||
| 	erofs_off_t offset; | ||||
|  | ||||
| 	ret = erofs_read_inode_from_disk(&vi); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
|  | ||||
| 	offset = 0; | ||||
| 	while (offset < vi.i_size) { | ||||
| 		erofs_off_t maxsize = min_t(erofs_off_t, | ||||
| 					    vi.i_size - offset, EROFS_BLKSIZ); | ||||
| 		struct erofs_dirent *de = (void *)buf; | ||||
| 		unsigned int nameoff; | ||||
|  | ||||
| 		ret = erofs_pread(&vi, buf, maxsize, offset); | ||||
| 		if (ret) | ||||
| 			return ret; | ||||
|  | ||||
| 		nameoff = le16_to_cpu(de->nameoff); | ||||
| 		if (nameoff < sizeof(struct erofs_dirent) || | ||||
| 		    nameoff >= PAGE_SIZE) { | ||||
| 			erofs_err("invalid de[0].nameoff %u @ nid %llu", | ||||
| 				  nameoff, nid | 0ULL); | ||||
| 			return -EFSCORRUPTED; | ||||
| 		} | ||||
|  | ||||
| 		de = find_target_dirent(nid, buf, name, len, | ||||
| 					nameoff, maxsize); | ||||
| 		if (IS_ERR(de)) | ||||
| 			return PTR_ERR(de); | ||||
|  | ||||
| 		if (de) { | ||||
| 			nd->nid = le64_to_cpu(de->nid); | ||||
| 			return 0; | ||||
| 		} | ||||
| 		offset += maxsize; | ||||
| 	} | ||||
| 	return -ENOENT; | ||||
| } | ||||
|  | ||||
| static int link_path_walk(const char *name, struct nameidata *nd) | ||||
| { | ||||
| 	nd->nid = sbi.root_nid; | ||||
|  | ||||
| 	while (*name == '/') | ||||
| 		name++; | ||||
|  | ||||
| 	/* At this point we know we have a real path component. */ | ||||
| 	while (*name != '\0') { | ||||
| 		const char *p = name; | ||||
| 		int ret; | ||||
|  | ||||
| 		do { | ||||
| 			++p; | ||||
| 		} while (*p != '\0' && *p != '/'); | ||||
|  | ||||
| 		DBG_BUGON(p <= name); | ||||
| 		ret = erofs_namei(nd, name, p - name); | ||||
| 		if (ret) | ||||
| 			return ret; | ||||
|  | ||||
| 		name = p; | ||||
| 		/* Skip until no more slashes. */ | ||||
| 		for (name = p; *name == '/'; ++name) | ||||
| 			; | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int erofs_ilookup(const char *path, struct erofs_inode *vi) | ||||
| { | ||||
| 	int ret; | ||||
| 	struct nameidata nd; | ||||
|  | ||||
| 	ret = link_path_walk(path, &nd); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
|  | ||||
| 	vi->nid = nd.nid; | ||||
| 	return erofs_read_inode_from_disk(vi); | ||||
| } | ||||
							
								
								
									
										105
									
								
								fs/erofs/super.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								fs/erofs/super.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | ||||
| // SPDX-License-Identifier: GPL-2.0+ | ||||
| #include "internal.h" | ||||
|  | ||||
| static bool check_layout_compatibility(struct erofs_sb_info *sbi, | ||||
| 				       struct erofs_super_block *dsb) | ||||
| { | ||||
| 	const unsigned int feature = le32_to_cpu(dsb->feature_incompat); | ||||
|  | ||||
| 	sbi->feature_incompat = feature; | ||||
|  | ||||
| 	/* check if current kernel meets all mandatory requirements */ | ||||
| 	if (feature & (~EROFS_ALL_FEATURE_INCOMPAT)) { | ||||
| 		erofs_err("unidentified incompatible feature %x, please upgrade kernel version", | ||||
| 			  feature & ~EROFS_ALL_FEATURE_INCOMPAT); | ||||
| 		return false; | ||||
| 	} | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| static int erofs_init_devices(struct erofs_sb_info *sbi, | ||||
| 			      struct erofs_super_block *dsb) | ||||
| { | ||||
| 	unsigned int ondisk_extradevs, i; | ||||
| 	erofs_off_t pos; | ||||
|  | ||||
| 	sbi->total_blocks = sbi->primarydevice_blocks; | ||||
|  | ||||
| 	if (!erofs_sb_has_device_table()) | ||||
| 		ondisk_extradevs = 0; | ||||
| 	else | ||||
| 		ondisk_extradevs = le16_to_cpu(dsb->extra_devices); | ||||
|  | ||||
| 	if (ondisk_extradevs != sbi->extra_devices) { | ||||
| 		erofs_err("extra devices don't match (ondisk %u, given %u)", | ||||
| 			  ondisk_extradevs, sbi->extra_devices); | ||||
| 		return -EINVAL; | ||||
| 	} | ||||
| 	if (!ondisk_extradevs) | ||||
| 		return 0; | ||||
|  | ||||
| 	sbi->device_id_mask = roundup_pow_of_two(ondisk_extradevs + 1) - 1; | ||||
| 	sbi->devs = calloc(ondisk_extradevs, sizeof(*sbi->devs)); | ||||
| 	pos = le16_to_cpu(dsb->devt_slotoff) * EROFS_DEVT_SLOT_SIZE; | ||||
| 	for (i = 0; i < ondisk_extradevs; ++i) { | ||||
| 		struct erofs_deviceslot dis; | ||||
| 		int ret; | ||||
|  | ||||
| 		ret = erofs_dev_read(0, &dis, pos, sizeof(dis)); | ||||
| 		if (ret < 0) | ||||
| 			return ret; | ||||
|  | ||||
| 		sbi->devs[i].mapped_blkaddr = dis.mapped_blkaddr; | ||||
| 		sbi->total_blocks += dis.blocks; | ||||
| 		pos += EROFS_DEVT_SLOT_SIZE; | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int erofs_read_superblock(void) | ||||
| { | ||||
| 	char data[EROFS_BLKSIZ]; | ||||
| 	struct erofs_super_block *dsb; | ||||
| 	unsigned int blkszbits; | ||||
| 	int ret; | ||||
|  | ||||
| 	ret = erofs_blk_read(data, 0, 1); | ||||
| 	if (ret < 0) { | ||||
| 		erofs_err("cannot read erofs superblock: %d", ret); | ||||
| 		return -EIO; | ||||
| 	} | ||||
| 	dsb = (struct erofs_super_block *)(data + EROFS_SUPER_OFFSET); | ||||
|  | ||||
| 	ret = -EINVAL; | ||||
| 	if (le32_to_cpu(dsb->magic) != EROFS_SUPER_MAGIC_V1) { | ||||
| 		erofs_err("cannot find valid erofs superblock"); | ||||
| 		return ret; | ||||
| 	} | ||||
|  | ||||
| 	sbi.feature_compat = le32_to_cpu(dsb->feature_compat); | ||||
|  | ||||
| 	blkszbits = dsb->blkszbits; | ||||
| 	/* 9(512 bytes) + LOG_SECTORS_PER_BLOCK == LOG_BLOCK_SIZE */ | ||||
| 	if (blkszbits != LOG_BLOCK_SIZE) { | ||||
| 		erofs_err("blksize %u isn't supported on this platform", | ||||
| 			  1 << blkszbits); | ||||
| 		return ret; | ||||
| 	} | ||||
|  | ||||
| 	if (!check_layout_compatibility(&sbi, dsb)) | ||||
| 		return ret; | ||||
|  | ||||
| 	sbi.primarydevice_blocks = le32_to_cpu(dsb->blocks); | ||||
| 	sbi.meta_blkaddr = le32_to_cpu(dsb->meta_blkaddr); | ||||
| 	sbi.xattr_blkaddr = le32_to_cpu(dsb->xattr_blkaddr); | ||||
| 	sbi.islotbits = EROFS_ISLOTBITS; | ||||
| 	sbi.root_nid = le16_to_cpu(dsb->root_nid); | ||||
| 	sbi.inos = le64_to_cpu(dsb->inos); | ||||
| 	sbi.checksum = le32_to_cpu(dsb->checksum); | ||||
|  | ||||
| 	sbi.build_time = le64_to_cpu(dsb->build_time); | ||||
| 	sbi.build_time_nsec = le32_to_cpu(dsb->build_time_nsec); | ||||
|  | ||||
| 	memcpy(&sbi.uuid, dsb->uuid, sizeof(dsb->uuid)); | ||||
| 	return erofs_init_devices(&sbi, dsb); | ||||
| } | ||||
							
								
								
									
										22
									
								
								fs/fs.c
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								fs/fs.c
									
									
									
									
									
								
							| @@ -26,6 +26,7 @@ | ||||
| #include <linux/math64.h> | ||||
| #include <efi_loader.h> | ||||
| #include <squashfs.h> | ||||
| #include <erofs.h> | ||||
|  | ||||
| DECLARE_GLOBAL_DATA_PTR; | ||||
|  | ||||
| @@ -304,6 +305,27 @@ static struct fstype_info fstypes[] = { | ||||
| 		.unlink = fs_unlink_unsupported, | ||||
| 		.mkdir = fs_mkdir_unsupported, | ||||
| 	}, | ||||
| #endif | ||||
| #if IS_ENABLED(CONFIG_FS_EROFS) | ||||
| 	{ | ||||
| 		.fstype = FS_TYPE_EROFS, | ||||
| 		.name = "erofs", | ||||
| 		.null_dev_desc_ok = false, | ||||
| 		.probe = erofs_probe, | ||||
| 		.opendir = erofs_opendir, | ||||
| 		.readdir = erofs_readdir, | ||||
| 		.ls = fs_ls_generic, | ||||
| 		.read = erofs_read, | ||||
| 		.size = erofs_size, | ||||
| 		.close = erofs_close, | ||||
| 		.closedir = erofs_closedir, | ||||
| 		.exists = erofs_exists, | ||||
| 		.uuid = fs_uuid_unsupported, | ||||
| 		.write = fs_write_unsupported, | ||||
| 		.ln = fs_ln_unsupported, | ||||
| 		.unlink = fs_unlink_unsupported, | ||||
| 		.mkdir = fs_mkdir_unsupported, | ||||
| 	}, | ||||
| #endif | ||||
| 	{ | ||||
| 		.fstype = FS_TYPE_ANY, | ||||
|   | ||||
							
								
								
									
										19
									
								
								include/erofs.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								include/erofs.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| /* SPDX-License-Identifier: GPL-2.0 */ | ||||
| #ifndef _EROFS_H_ | ||||
| #define _EROFS_H_ | ||||
|  | ||||
| struct disk_partition; | ||||
|  | ||||
| int erofs_opendir(const char *filename, struct fs_dir_stream **dirsp); | ||||
| int erofs_readdir(struct fs_dir_stream *dirs, struct fs_dirent **dentp); | ||||
| int erofs_probe(struct blk_desc *fs_dev_desc, | ||||
| 		struct disk_partition *fs_partition); | ||||
| int erofs_read(const char *filename, void *buf, loff_t offset, | ||||
| 	       loff_t len, loff_t *actread); | ||||
| int erofs_size(const char *filename, loff_t *size); | ||||
| int erofs_exists(const char *filename); | ||||
| void erofs_close(void); | ||||
| void erofs_closedir(struct fs_dir_stream *dirs); | ||||
| int erofs_uuid(char *uuid_str); | ||||
|  | ||||
| #endif /* _EROFS_H */ | ||||
| @@ -17,6 +17,7 @@ struct cmd_tbl; | ||||
| #define FS_TYPE_UBIFS	4 | ||||
| #define FS_TYPE_BTRFS	5 | ||||
| #define FS_TYPE_SQUASHFS 6 | ||||
| #define FS_TYPE_EROFS   7 | ||||
|  | ||||
| struct blk_desc; | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user