| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * fs/partitions/aix.c | |
| * | |
| * Copyright (C) 2012-2013 Philippe De Muyter <phdm@macqel.be> | |
| */ | |
| #include "check.h" | |
| struct lvm_rec { | |
| char lvm_id[4]; /* "_LVM" */ | |
| char reserved4[16]; | |
| __be32 lvmarea_len; | |
| __be32 vgda_len; | |
| __be32 vgda_psn[2]; | |
| char reserved36[10]; | |
| __be16 pp_size; /* log2(pp_size) */ | |
| char reserved46[12]; | |
| __be16 version; | |
| }; | |
| struct vgda { | |
| __be32 secs; | |
| __be32 usec; | |
| char reserved8[16]; | |
| __be16 numlvs; | |
| __be16 maxlvs; | |
| __be16 pp_size; | |
| __be16 numpvs; | |
| __be16 total_vgdas; | |
| __be16 vgda_size; | |
| }; | |
| struct lvd { | |
| __be16 lv_ix; | |
| __be16 res2; | |
| __be16 res4; | |
| __be16 maxsize; | |
| __be16 lv_state; | |
| __be16 mirror; | |
| __be16 mirror_policy; | |
| __be16 num_lps; | |
| __be16 res10[8]; | |
| }; | |
| struct lvname { | |
| char name[64]; | |
| }; | |
| struct ppe { | |
| __be16 lv_ix; | |
| unsigned short res2; | |
| unsigned short res4; | |
| __be16 lp_ix; | |
| unsigned short res8[12]; | |
| }; | |
| struct pvd { | |
| char reserved0[16]; | |
| __be16 pp_count; | |
| char reserved18[2]; | |
| __be32 psn_part1; | |
| char reserved24[8]; | |
| struct ppe ppe[1016]; | |
| }; | |
| #define LVM_MAXLVS 256 | |
| /** | |
| * read_lba(): Read bytes from disk, starting at given LBA | |
| * @state | |
| * @lba | |
| * @buffer | |
| * @count | |
| * | |
| * Description: Reads @count bytes from @state->disk into @buffer. | |
| * Returns number of bytes read on success, 0 on error. | |
| */ | |
| static size_t read_lba(struct parsed_partitions *state, u64 lba, u8 *buffer, | |
| size_t count) | |
| { | |
| size_t totalreadcount = 0; | |
| if (!buffer || lba + count / 512 > get_capacity(state->disk) - 1ULL) | |
| return 0; | |
| while (count) { | |
| int copied = 512; | |
| Sector sect; | |
| unsigned char *data = read_part_sector(state, lba++, §); | |
| if (!data) | |
| break; | |
| if (copied > count) | |
| copied = count; | |
| memcpy(buffer, data, copied); | |
| put_dev_sector(sect); | |
| buffer += copied; | |
| totalreadcount += copied; | |
| count -= copied; | |
| } | |
| return totalreadcount; | |
| } | |
| /** | |
| * alloc_pvd(): reads physical volume descriptor | |
| * @state | |
| * @lba | |
| * | |
| * Description: Returns pvd on success, NULL on error. | |
| * Allocates space for pvd and fill it with disk blocks at @lba | |
| * Notes: remember to free pvd when you're done! | |
| */ | |
| static struct pvd *alloc_pvd(struct parsed_partitions *state, u32 lba) | |
| { | |
| size_t count = sizeof(struct pvd); | |
| struct pvd *p; | |
| p = kmalloc(count, GFP_KERNEL); | |
| if (!p) | |
| return NULL; | |
| if (read_lba(state, lba, (u8 *) p, count) < count) { | |
| kfree(p); | |
| return NULL; | |
| } | |
| return p; | |
| } | |
| /** | |
| * alloc_lvn(): reads logical volume names | |
| * @state | |
| * @lba | |
| * | |
| * Description: Returns lvn on success, NULL on error. | |
| * Allocates space for lvn and fill it with disk blocks at @lba | |
| * Notes: remember to free lvn when you're done! | |
| */ | |
| static struct lvname *alloc_lvn(struct parsed_partitions *state, u32 lba) | |
| { | |
| size_t count = sizeof(struct lvname) * LVM_MAXLVS; | |
| struct lvname *p; | |
| p = kmalloc(count, GFP_KERNEL); | |
| if (!p) | |
| return NULL; | |
| if (read_lba(state, lba, (u8 *) p, count) < count) { | |
| kfree(p); | |
| return NULL; | |
| } | |
| return p; | |
| } | |
| int aix_partition(struct parsed_partitions *state) | |
| { | |
| int ret = 0; | |
| Sector sect; | |
| unsigned char *d; | |
| u32 pp_bytes_size; | |
| u32 pp_blocks_size = 0; | |
| u32 vgda_sector = 0; | |
| u32 vgda_len = 0; | |
| int numlvs = 0; | |
| struct pvd *pvd = NULL; | |
| struct lv_info { | |
| unsigned short pps_per_lv; | |
| unsigned short pps_found; | |
| unsigned char lv_is_contiguous; | |
| } *lvip; | |
| struct lvname *n = NULL; | |
| d = read_part_sector(state, 7, §); | |
| if (d) { | |
| struct lvm_rec *p = (struct lvm_rec *)d; | |
| u16 lvm_version = be16_to_cpu(p->version); | |
| if (lvm_version == 1) { | |
| int pp_size_log2 = be16_to_cpu(p->pp_size); | |
| pp_bytes_size = 1 << pp_size_log2; | |
| pp_blocks_size = pp_bytes_size / 512; | |
| seq_buf_printf(&state->pp_buf, | |
| " AIX LVM header version %u found\n", | |
| lvm_version); | |
| vgda_len = be32_to_cpu(p->vgda_len); | |
| vgda_sector = be32_to_cpu(p->vgda_psn[0]); | |
| } else { | |
| seq_buf_printf(&state->pp_buf, | |
| " unsupported AIX LVM version %d found\n", | |
| lvm_version); | |
| } | |
| put_dev_sector(sect); | |
| } | |
| if (vgda_sector && (d = read_part_sector(state, vgda_sector, §))) { | |
| struct vgda *p = (struct vgda *)d; | |
| numlvs = be16_to_cpu(p->numlvs); | |
| put_dev_sector(sect); | |
| } | |
| lvip = kzalloc_objs(struct lv_info, state->limit); | |
| if (!lvip) | |
| return 0; | |
| if (numlvs && (d = read_part_sector(state, vgda_sector + 1, §))) { | |
| struct lvd *p = (struct lvd *)d; | |
| int i; | |
| n = alloc_lvn(state, vgda_sector + vgda_len - 33); | |
| if (n) { | |
| int foundlvs = 0; | |
| for (i = 0; foundlvs < numlvs && i < state->limit; i += 1) { | |
| lvip[i].pps_per_lv = be16_to_cpu(p[i].num_lps); | |
| if (lvip[i].pps_per_lv) | |
| foundlvs += 1; | |
| } | |
| /* pvd loops depend on n[].name and lvip[].pps_per_lv */ | |
| pvd = alloc_pvd(state, vgda_sector + 17); | |
| } | |
| put_dev_sector(sect); | |
| } | |
| if (pvd) { | |
| int numpps = be16_to_cpu(pvd->pp_count); | |
| int psn_part1 = be32_to_cpu(pvd->psn_part1); | |
| int i; | |
| int cur_lv_ix = -1; | |
| int next_lp_ix = 1; | |
| int lp_ix; | |
| /* | |
| * pvd was read into a fixed-size struct pvd whose ppe[] array | |
| * holds ARRAY_SIZE(pvd->ppe) entries. pp_count is an | |
| * unvalidated on-disk __be16, so clamp the scan to the array | |
| * size to avoid walking past the allocation. | |
| */ | |
| if (numpps > ARRAY_SIZE(pvd->ppe)) | |
| numpps = ARRAY_SIZE(pvd->ppe); | |
| for (i = 0; i < numpps; i += 1) { | |
| struct ppe *p = pvd->ppe + i; | |
| unsigned int lv_ix; | |
| lp_ix = be16_to_cpu(p->lp_ix); | |
| if (!lp_ix) { | |
| next_lp_ix = 1; | |
| continue; | |
| } | |
| lv_ix = be16_to_cpu(p->lv_ix) - 1; | |
| if (lv_ix >= state->limit) { | |
| cur_lv_ix = -1; | |
| continue; | |
| } | |
| lvip[lv_ix].pps_found += 1; | |
| if (lp_ix == 1) { | |
| cur_lv_ix = lv_ix; | |
| next_lp_ix = 1; | |
| } else if (lv_ix != cur_lv_ix || lp_ix != next_lp_ix) { | |
| next_lp_ix = 1; | |
| continue; | |
| } | |
| if (lp_ix == lvip[lv_ix].pps_per_lv) { | |
| put_partition(state, lv_ix + 1, | |
| (i + 1 - lp_ix) * pp_blocks_size + psn_part1, | |
| lvip[lv_ix].pps_per_lv * pp_blocks_size); | |
| seq_buf_printf(&state->pp_buf, " <%s>\n", | |
| n[lv_ix].name); | |
| lvip[lv_ix].lv_is_contiguous = 1; | |
| ret = 1; | |
| next_lp_ix = 1; | |
| } else | |
| next_lp_ix += 1; | |
| } | |
| for (i = 0; i < state->limit; i += 1) | |
| if (lvip[i].pps_found && !lvip[i].lv_is_contiguous) { | |
| char tmp[sizeof(n[i].name) + 1]; // null char | |
| snprintf(tmp, sizeof(tmp), "%s", n[i].name); | |
| pr_warn("partition %s (%u pp's found) is " | |
| "not contiguous\n", | |
| tmp, lvip[i].pps_found); | |
| } | |
| kfree(pvd); | |
| } | |
| kfree(n); | |
| kfree(lvip); | |
| return ret; | |
| } |