| // SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0 |
| /* |
| * Created by Li Guifu <blucerlee@gmail.com> |
| */ |
| #include <string.h> |
| #include <stdlib.h> |
| #include "erofs/print.h" |
| #include "erofs/xattr.h" |
| #include "erofs/cache.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(sbi)) |
| ondisk_extradevs = 0; |
| else |
| ondisk_extradevs = le16_to_cpu(dsb->extra_devices); |
| |
| if (sbi->extra_devices && |
| 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->extra_devices = ondisk_extradevs; |
| sbi->device_id_mask = roundup_pow_of_two(ondisk_extradevs + 1) - 1; |
| sbi->devs = calloc(ondisk_extradevs, sizeof(*sbi->devs)); |
| if (!sbi->devs) |
| return -ENOMEM; |
| 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(sbi, 0, &dis, pos, sizeof(dis)); |
| if (ret < 0) { |
| free(sbi->devs); |
| sbi->devs = NULL; |
| return ret; |
| } |
| |
| sbi->devs[i].mapped_blkaddr = le32_to_cpu(dis.mapped_blkaddr); |
| sbi->devs[i].blocks = le32_to_cpu(dis.blocks); |
| memcpy(sbi->devs[i].tag, dis.tag, sizeof(dis.tag)); |
| sbi->total_blocks += sbi->devs[i].blocks; |
| pos += EROFS_DEVT_SLOT_SIZE; |
| } |
| return 0; |
| } |
| |
| int erofs_read_superblock(struct erofs_sb_info *sbi) |
| { |
| u8 data[EROFS_MAX_BLOCK_SIZE]; |
| struct erofs_super_block *dsb; |
| int ret; |
| |
| sbi->blkszbits = ilog2(EROFS_MAX_BLOCK_SIZE); |
| ret = erofs_blk_read(sbi, 0, data, 0, erofs_blknr(sbi, sizeof(data))); |
| 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); |
| |
| sbi->blkszbits = dsb->blkszbits; |
| if (sbi->blkszbits < 9 || |
| sbi->blkszbits > ilog2(EROFS_MAX_BLOCK_SIZE)) { |
| erofs_err("blksize %llu isn't supported on this platform", |
| erofs_blksiz(sbi) | 0ULL); |
| return ret; |
| } else if (!check_layout_compatibility(sbi, dsb)) { |
| return ret; |
| } |
| |
| sbi->sb_size = 128 + dsb->sb_extslots * EROFS_SB_EXTSLOT_SIZE; |
| if (sbi->sb_size > (1 << sbi->blkszbits) - EROFS_SUPER_OFFSET) { |
| erofs_err("invalid sb_extslots %u (more than a fs block)", |
| dsb->sb_extslots); |
| return -EINVAL; |
| } |
| 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->xattr_prefix_start = le32_to_cpu(dsb->xattr_prefix_start); |
| sbi->xattr_prefix_count = dsb->xattr_prefix_count; |
| sbi->islotbits = EROFS_ISLOTBITS; |
| sbi->root_nid = le16_to_cpu(dsb->root_nid); |
| sbi->packed_nid = le64_to_cpu(dsb->packed_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)); |
| |
| ret = z_erofs_parse_cfgs(sbi, dsb); |
| if (ret) |
| return ret; |
| |
| ret = erofs_init_devices(sbi, dsb); |
| if (ret) |
| return ret; |
| |
| ret = erofs_xattr_prefixes_init(sbi); |
| if (ret && sbi->devs) { |
| free(sbi->devs); |
| sbi->devs = NULL; |
| } |
| return ret; |
| } |
| |
| void erofs_put_super(struct erofs_sb_info *sbi) |
| { |
| if (sbi->devs) { |
| free(sbi->devs); |
| sbi->devs = NULL; |
| } |
| erofs_xattr_prefixes_cleanup(sbi); |
| if (sbi->bmgr) { |
| erofs_buffer_exit(sbi->bmgr); |
| sbi->bmgr = NULL; |
| } |
| } |
| |
| int erofs_writesb(struct erofs_sb_info *sbi, struct erofs_buffer_head *sb_bh, |
| erofs_blk_t *blocks) |
| { |
| struct erofs_super_block sb = { |
| .magic = cpu_to_le32(EROFS_SUPER_MAGIC_V1), |
| .blkszbits = sbi->blkszbits, |
| .root_nid = cpu_to_le16(sbi->root_nid), |
| .inos = cpu_to_le64(sbi->inos), |
| .build_time = cpu_to_le64(sbi->build_time), |
| .build_time_nsec = cpu_to_le32(sbi->build_time_nsec), |
| .meta_blkaddr = cpu_to_le32(sbi->meta_blkaddr), |
| .xattr_blkaddr = cpu_to_le32(sbi->xattr_blkaddr), |
| .xattr_prefix_count = sbi->xattr_prefix_count, |
| .xattr_prefix_start = cpu_to_le32(sbi->xattr_prefix_start), |
| .feature_incompat = cpu_to_le32(sbi->feature_incompat), |
| .feature_compat = cpu_to_le32(sbi->feature_compat & |
| ~EROFS_FEATURE_COMPAT_SB_CHKSUM), |
| .extra_devices = cpu_to_le16(sbi->extra_devices), |
| .devt_slotoff = cpu_to_le16(sbi->devt_slotoff), |
| .packed_nid = cpu_to_le64(sbi->packed_nid), |
| }; |
| const u32 sb_blksize = round_up(EROFS_SUPER_END, erofs_blksiz(sbi)); |
| char *buf; |
| int ret; |
| |
| *blocks = erofs_mapbh(sbi->bmgr, NULL); |
| sb.blocks = cpu_to_le32(*blocks); |
| memcpy(sb.uuid, sbi->uuid, sizeof(sb.uuid)); |
| memcpy(sb.volume_name, sbi->volume_name, sizeof(sb.volume_name)); |
| |
| if (erofs_sb_has_compr_cfgs(sbi)) |
| sb.u1.available_compr_algs = cpu_to_le16(sbi->available_compr_algs); |
| else |
| sb.u1.lz4_max_distance = cpu_to_le16(sbi->lz4.max_distance); |
| |
| buf = calloc(sb_blksize, 1); |
| if (!buf) { |
| erofs_err("failed to allocate memory for sb: %s", |
| erofs_strerror(-errno)); |
| return -ENOMEM; |
| } |
| memcpy(buf + EROFS_SUPER_OFFSET, &sb, sizeof(sb)); |
| |
| ret = erofs_dev_write(sbi, buf, sb_bh ? erofs_btell(sb_bh, false) : 0, |
| EROFS_SUPER_END); |
| free(buf); |
| if (sb_bh) |
| erofs_bdrop(sb_bh, false); |
| return ret; |
| } |
| |
| struct erofs_buffer_head *erofs_reserve_sb(struct erofs_bufmgr *bmgr) |
| { |
| struct erofs_buffer_head *bh; |
| int err; |
| |
| bh = erofs_balloc(bmgr, META, 0, 0, 0); |
| if (IS_ERR(bh)) { |
| erofs_err("failed to allocate super: %s", |
| erofs_strerror(PTR_ERR(bh))); |
| return bh; |
| } |
| bh->op = &erofs_skip_write_bhops; |
| err = erofs_bh_balloon(bh, EROFS_SUPER_END); |
| if (err < 0) { |
| erofs_err("failed to balloon super: %s", erofs_strerror(err)); |
| goto err_bdrop; |
| } |
| |
| /* make sure that the super block should be the very first blocks */ |
| (void)erofs_mapbh(NULL, bh->block); |
| if (erofs_btell(bh, false) != 0) { |
| erofs_err("failed to pin super block @ 0"); |
| err = -EFAULT; |
| goto err_bdrop; |
| } |
| return bh; |
| err_bdrop: |
| erofs_bdrop(bh, true); |
| return ERR_PTR(err); |
| } |
| |
| int erofs_enable_sb_chksum(struct erofs_sb_info *sbi, u32 *crc) |
| { |
| int ret; |
| u8 buf[EROFS_MAX_BLOCK_SIZE]; |
| unsigned int len; |
| struct erofs_super_block *sb; |
| |
| ret = erofs_blk_read(sbi, 0, buf, 0, erofs_blknr(sbi, EROFS_SUPER_END) + 1); |
| if (ret) { |
| erofs_err("failed to read superblock to set checksum: %s", |
| erofs_strerror(ret)); |
| return ret; |
| } |
| |
| /* |
| * skip the first 1024 bytes, to allow for the installation |
| * of x86 boot sectors and other oddities. |
| */ |
| sb = (struct erofs_super_block *)(buf + EROFS_SUPER_OFFSET); |
| |
| if (le32_to_cpu(sb->magic) != EROFS_SUPER_MAGIC_V1) { |
| erofs_err("internal error: not an erofs valid image"); |
| return -EFAULT; |
| } |
| |
| /* turn on checksum feature */ |
| sb->feature_compat = cpu_to_le32(le32_to_cpu(sb->feature_compat) | |
| EROFS_FEATURE_COMPAT_SB_CHKSUM); |
| if (erofs_blksiz(sbi) > EROFS_SUPER_OFFSET) |
| len = erofs_blksiz(sbi) - EROFS_SUPER_OFFSET; |
| else |
| len = erofs_blksiz(sbi); |
| *crc = erofs_crc32c(~0, (u8 *)sb, len); |
| |
| /* set up checksum field to erofs_super_block */ |
| sb->checksum = cpu_to_le32(*crc); |
| |
| ret = erofs_blk_write(sbi, buf, 0, 1); |
| if (ret) { |
| erofs_err("failed to write checksummed superblock: %s", |
| erofs_strerror(ret)); |
| return ret; |
| } |
| |
| return 0; |
| } |