| /* |
| * GRUB -- GRand Unified Bootloader |
| * Copyright (C) 1999,2000,2001,2002,2003,2004,2009,2010,2011 Free Software Foundation, Inc. |
| * Copyright 2010 Sun Microsystems, Inc. |
| * Copyright (c) 2012 by Delphix. All rights reserved. |
| * |
| * GRUB is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * GRUB is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with GRUB. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| /* |
| * The zfs plug-in routines for GRUB are: |
| * |
| * zfs_mount() - locates a valid uberblock of the root pool and reads |
| * in its MOS at the memory address MOS. |
| * |
| * zfs_open() - locates a plain file object by following the MOS |
| * and places its dnode at the memory address DNODE. |
| * |
| * zfs_read() - read in the data blocks pointed by the DNODE. |
| * |
| */ |
| |
| #include <grub/err.h> |
| #include <grub/file.h> |
| #include <grub/mm.h> |
| #include <grub/misc.h> |
| #include <grub/disk.h> |
| #include <grub/partition.h> |
| #include <grub/dl.h> |
| #include <grub/types.h> |
| #include <grub/zfs/zfs.h> |
| #include <grub/zfs/zio.h> |
| #include <grub/zfs/dnode.h> |
| #include <grub/zfs/uberblock_impl.h> |
| #include <grub/zfs/vdev_impl.h> |
| #include <grub/zfs/zio_checksum.h> |
| #include <grub/zfs/zap_impl.h> |
| #include <grub/zfs/zap_leaf.h> |
| #include <grub/zfs/zfs_znode.h> |
| #include <grub/zfs/dmu.h> |
| #include <grub/zfs/dmu_objset.h> |
| #include <grub/zfs/sa_impl.h> |
| #include <grub/zfs/dsl_dir.h> |
| #include <grub/zfs/dsl_dataset.h> |
| #include <grub/deflate.h> |
| #include <grub/crypto.h> |
| #include <grub/i18n.h> |
| |
| GRUB_MOD_LICENSE ("GPLv3+"); |
| |
| #define ZPOOL_PROP_BOOTFS "bootfs" |
| |
| /* |
| * For nvlist manipulation. (from nvpair.h) |
| */ |
| #define NV_ENCODE_NATIVE 0 |
| #define NV_ENCODE_XDR 1 |
| #define NV_BIG_ENDIAN 0 |
| #define NV_LITTLE_ENDIAN 1 |
| #define DATA_TYPE_UINT64 8 |
| #define DATA_TYPE_STRING 9 |
| #define DATA_TYPE_NVLIST 19 |
| #define DATA_TYPE_NVLIST_ARRAY 20 |
| |
| #ifndef GRUB_UTIL |
| static grub_dl_t my_mod; |
| #endif |
| |
| #define P2PHASE(x, align) ((x) & ((align) - 1)) |
| |
| static inline grub_disk_addr_t |
| DVA_OFFSET_TO_PHYS_SECTOR (grub_disk_addr_t offset) |
| { |
| return ((offset + VDEV_LABEL_START_SIZE) >> SPA_MINBLOCKSHIFT); |
| } |
| |
| /* |
| * FAT ZAP data structures |
| */ |
| #define ZFS_CRC64_POLY 0xC96C5795D7870F42ULL /* ECMA-182, reflected form */ |
| static inline grub_uint64_t |
| ZAP_HASH_IDX (grub_uint64_t hash, grub_uint64_t n) |
| { |
| return (((n) == 0) ? 0 : ((hash) >> (64 - (n)))); |
| } |
| |
| #define CHAIN_END 0xffff /* end of the chunk chain */ |
| |
| /* |
| * The amount of space within the chunk available for the array is: |
| * chunk size - space for type (1) - space for next pointer (2) |
| */ |
| #define ZAP_LEAF_ARRAY_BYTES (ZAP_LEAF_CHUNKSIZE - 3) |
| |
| static inline int |
| ZAP_LEAF_HASH_SHIFT (int bs) |
| { |
| return bs - 5; |
| } |
| |
| static inline int |
| ZAP_LEAF_HASH_NUMENTRIES (int bs) |
| { |
| return 1 << ZAP_LEAF_HASH_SHIFT(bs); |
| } |
| |
| static inline grub_size_t |
| LEAF_HASH (int bs, grub_uint64_t h, zap_leaf_phys_t *l) |
| { |
| return ((ZAP_LEAF_HASH_NUMENTRIES (bs)-1) |
| & ((h) >> (64 - ZAP_LEAF_HASH_SHIFT (bs) - l->l_hdr.lh_prefix_len))); |
| } |
| |
| /* |
| * The amount of space available for chunks is: |
| * block size shift - hash entry size (2) * number of hash |
| * entries - header space (2*chunksize) |
| */ |
| static inline int |
| ZAP_LEAF_NUMCHUNKS (int bs) |
| { |
| return (((1U << bs) - 2 * ZAP_LEAF_HASH_NUMENTRIES (bs)) / |
| ZAP_LEAF_CHUNKSIZE - 2); |
| } |
| |
| /* |
| * The chunks start immediately after the hash table. The end of the |
| * hash table is at l_hash + HASH_NUMENTRIES, which we simply cast to a |
| * chunk_t. |
| */ |
| static inline zap_leaf_chunk_t * |
| ZAP_LEAF_CHUNK (zap_leaf_phys_t *l, int bs, int idx) |
| { |
| return &((zap_leaf_chunk_t *) (l->l_entries |
| + (ZAP_LEAF_HASH_NUMENTRIES(bs) * 2) |
| / sizeof (grub_properly_aligned_t)))[idx]; |
| } |
| |
| static inline struct zap_leaf_entry * |
| ZAP_LEAF_ENTRY(zap_leaf_phys_t *l, int bs, int idx) |
| { |
| return &ZAP_LEAF_CHUNK(l, bs, idx)->l_entry; |
| } |
| |
| |
| /* |
| * Decompression Entry - lzjb & lz4 |
| */ |
| |
| extern grub_err_t lzjb_decompress (void *, void *, grub_size_t, grub_size_t); |
| |
| extern grub_err_t lz4_decompress (void *, void *, grub_size_t, grub_size_t); |
| |
| typedef grub_err_t zfs_decomp_func_t (void *s_start, void *d_start, |
| grub_size_t s_len, grub_size_t d_len); |
| typedef struct decomp_entry |
| { |
| const char *name; |
| zfs_decomp_func_t *decomp_func; |
| } decomp_entry_t; |
| |
| /* |
| * Signature for checksum functions. |
| */ |
| typedef void zio_checksum_t(const void *data, grub_uint64_t size, |
| grub_zfs_endian_t endian, zio_cksum_t *zcp); |
| |
| /* |
| * Information about each checksum function. |
| */ |
| typedef struct zio_checksum_info { |
| zio_checksum_t *ci_func; /* checksum function for each byteorder */ |
| int ci_correctable; /* number of correctable bits */ |
| int ci_eck; /* uses zio embedded checksum? */ |
| const char *ci_name; /* descriptive name */ |
| } zio_checksum_info_t; |
| |
| typedef struct dnode_end |
| { |
| dnode_phys_t dn; |
| grub_zfs_endian_t endian; |
| } dnode_end_t; |
| |
| struct grub_zfs_device_desc |
| { |
| enum { DEVICE_LEAF, DEVICE_MIRROR, DEVICE_RAIDZ } type; |
| grub_uint64_t id; |
| grub_uint64_t guid; |
| unsigned ashift; |
| unsigned max_children_ashift; |
| |
| /* Valid only for non-leafs. */ |
| unsigned n_children; |
| struct grub_zfs_device_desc *children; |
| |
| /* Valid only for RAIDZ. */ |
| unsigned nparity; |
| |
| /* Valid only for leaf devices. */ |
| grub_device_t dev; |
| grub_disk_addr_t vdev_phys_sector; |
| uberblock_t current_uberblock; |
| int original; |
| }; |
| |
| struct subvolume |
| { |
| dnode_end_t mdn; |
| grub_uint64_t obj; |
| grub_uint64_t case_insensitive; |
| grub_size_t nkeys; |
| struct |
| { |
| grub_crypto_cipher_handle_t cipher; |
| grub_uint64_t txg; |
| grub_uint64_t algo; |
| } *keyring; |
| }; |
| |
| struct grub_zfs_data |
| { |
| /* cache for a file block of the currently zfs_open()-ed file */ |
| char *file_buf; |
| grub_uint64_t file_start; |
| grub_uint64_t file_end; |
| |
| /* cache for a dnode block */ |
| dnode_phys_t *dnode_buf; |
| dnode_phys_t *dnode_mdn; |
| grub_uint64_t dnode_start; |
| grub_uint64_t dnode_end; |
| grub_zfs_endian_t dnode_endian; |
| |
| dnode_end_t mos; |
| dnode_end_t dnode; |
| struct subvolume subvol; |
| |
| struct grub_zfs_device_desc *devices_attached; |
| unsigned n_devices_attached; |
| unsigned n_devices_allocated; |
| struct grub_zfs_device_desc *device_original; |
| |
| uberblock_t current_uberblock; |
| |
| grub_uint64_t guid; |
| }; |
| |
| /* Context for grub_zfs_dir. */ |
| struct grub_zfs_dir_ctx |
| { |
| grub_fs_dir_hook_t hook; |
| void *hook_data; |
| struct grub_zfs_data *data; |
| }; |
| |
| grub_err_t (*grub_zfs_decrypt) (grub_crypto_cipher_handle_t cipher, |
| grub_uint64_t algo, |
| void *nonce, |
| char *buf, grub_size_t size, |
| const grub_uint32_t *expected_mac, |
| grub_zfs_endian_t endian) = NULL; |
| grub_crypto_cipher_handle_t (*grub_zfs_load_key) (const struct grub_zfs_key *key, |
| grub_size_t keysize, |
| grub_uint64_t salt, |
| grub_uint64_t algo) = NULL; |
| /* |
| * List of pool features that the grub implementation of ZFS supports for |
| * read. Note that features that are only required for write do not need |
| * to be listed here since grub opens pools in read-only mode. |
| */ |
| #define MAX_SUPPORTED_FEATURE_STRLEN 50 |
| static const char *spa_feature_names[] = { |
| "org.illumos:lz4_compress", |
| "com.delphix:hole_birth", |
| "com.delphix:embedded_data", |
| "com.delphix:extensible_dataset", |
| "org.open-zfs:large_blocks", |
| NULL |
| }; |
| |
| static int |
| check_feature(const char *name, grub_uint64_t val, struct grub_zfs_dir_ctx *ctx); |
| static grub_err_t |
| check_mos_features(dnode_phys_t *mosmdn_phys,grub_zfs_endian_t endian,struct grub_zfs_data* data ); |
| |
| static grub_err_t |
| zlib_decompress (void *s, void *d, |
| grub_size_t slen, grub_size_t dlen) |
| { |
| if (grub_zlib_decompress (s, slen, 0, d, dlen) == (grub_ssize_t) dlen) |
| return GRUB_ERR_NONE; |
| |
| if (!grub_errno) |
| grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, |
| "premature end of compressed"); |
| return grub_errno; |
| } |
| |
| static grub_err_t |
| zle_decompress (void *s, void *d, |
| grub_size_t slen, grub_size_t dlen) |
| { |
| grub_uint8_t *iptr, *optr; |
| grub_size_t clen; |
| for (iptr = s, optr = d; iptr < (grub_uint8_t *) s + slen |
| && optr < (grub_uint8_t *) d + dlen;) |
| { |
| if (*iptr & 0x80) |
| clen = ((*iptr) & 0x7f) + 0x41; |
| else |
| clen = ((*iptr) & 0x3f) + 1; |
| if ((grub_ssize_t) clen > (grub_uint8_t *) d + dlen - optr) |
| clen = (grub_uint8_t *) d + dlen - optr; |
| if (*iptr & 0x40 || *iptr & 0x80) |
| { |
| grub_memset (optr, 0, clen); |
| iptr++; |
| optr += clen; |
| continue; |
| } |
| if ((grub_ssize_t) clen > (grub_uint8_t *) s + slen - iptr - 1) |
| clen = (grub_uint8_t *) s + slen - iptr - 1; |
| grub_memcpy (optr, iptr + 1, clen); |
| optr += clen; |
| iptr += clen + 1; |
| } |
| if (optr < (grub_uint8_t *) d + dlen) |
| grub_memset (optr, 0, (grub_uint8_t *) d + dlen - optr); |
| return GRUB_ERR_NONE; |
| } |
| |
| static decomp_entry_t decomp_table[ZIO_COMPRESS_FUNCTIONS] = { |
| {"inherit", NULL}, /* ZIO_COMPRESS_INHERIT */ |
| {"on", lzjb_decompress}, /* ZIO_COMPRESS_ON */ |
| {"off", NULL}, /* ZIO_COMPRESS_OFF */ |
| {"lzjb", lzjb_decompress}, /* ZIO_COMPRESS_LZJB */ |
| {"empty", NULL}, /* ZIO_COMPRESS_EMPTY */ |
| {"gzip-1", zlib_decompress}, /* ZIO_COMPRESS_GZIP1 */ |
| {"gzip-2", zlib_decompress}, /* ZIO_COMPRESS_GZIP2 */ |
| {"gzip-3", zlib_decompress}, /* ZIO_COMPRESS_GZIP3 */ |
| {"gzip-4", zlib_decompress}, /* ZIO_COMPRESS_GZIP4 */ |
| {"gzip-5", zlib_decompress}, /* ZIO_COMPRESS_GZIP5 */ |
| {"gzip-6", zlib_decompress}, /* ZIO_COMPRESS_GZIP6 */ |
| {"gzip-7", zlib_decompress}, /* ZIO_COMPRESS_GZIP7 */ |
| {"gzip-8", zlib_decompress}, /* ZIO_COMPRESS_GZIP8 */ |
| {"gzip-9", zlib_decompress}, /* ZIO_COMPRESS_GZIP9 */ |
| {"zle", zle_decompress}, /* ZIO_COMPRESS_ZLE */ |
| {"lz4", lz4_decompress}, /* ZIO_COMPRESS_LZ4 */ |
| }; |
| |
| static grub_err_t zio_read_data (blkptr_t * bp, grub_zfs_endian_t endian, |
| void *buf, struct grub_zfs_data *data); |
| |
| /* |
| * Our own version of log2(). Same thing as highbit()-1. |
| */ |
| static int |
| zfs_log2 (grub_uint64_t num) |
| { |
| int i = 0; |
| |
| while (num > 1) |
| { |
| i++; |
| num = num >> 1; |
| } |
| |
| return i; |
| } |
| |
| /* Checksum Functions */ |
| static void |
| zio_checksum_off (const void *buf __attribute__ ((unused)), |
| grub_uint64_t size __attribute__ ((unused)), |
| grub_zfs_endian_t endian __attribute__ ((unused)), |
| zio_cksum_t * zcp) |
| { |
| ZIO_SET_CHECKSUM (zcp, 0, 0, 0, 0); |
| } |
| |
| /* Checksum Table and Values */ |
| static zio_checksum_info_t zio_checksum_table[ZIO_CHECKSUM_FUNCTIONS] = { |
| {NULL, 0, 0, "inherit"}, |
| {NULL, 0, 0, "on"}, |
| {zio_checksum_off, 0, 0, "off"}, |
| {zio_checksum_SHA256, 1, 1, "label"}, |
| {zio_checksum_SHA256, 1, 1, "gang_header"}, |
| {NULL, 0, 0, "zilog"}, |
| {fletcher_2, 0, 0, "fletcher2"}, |
| {fletcher_4, 1, 0, "fletcher4"}, |
| {zio_checksum_SHA256, 1, 0, "SHA256"}, |
| {NULL, 0, 0, "zilog2"}, |
| {zio_checksum_SHA256, 1, 0, "SHA256+MAC"}, |
| }; |
| |
| /* |
| * zio_checksum_verify: Provides support for checksum verification. |
| * |
| * Fletcher2, Fletcher4, and SHA256 are supported. |
| * |
| */ |
| static grub_err_t |
| zio_checksum_verify (zio_cksum_t zc, grub_uint32_t checksum, |
| grub_zfs_endian_t endian, |
| char *buf, grub_size_t size) |
| { |
| zio_eck_t *zec = (zio_eck_t *) (buf + size) - 1; |
| zio_checksum_info_t *ci = &zio_checksum_table[checksum]; |
| zio_cksum_t actual_cksum, expected_cksum; |
| |
| if (checksum >= ZIO_CHECKSUM_FUNCTIONS || ci->ci_func == NULL) |
| { |
| grub_dprintf ("zfs", "unknown checksum function %d\n", checksum); |
| return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, |
| "unknown checksum function %d", checksum); |
| } |
| |
| if (ci->ci_eck) |
| { |
| expected_cksum = zec->zec_cksum; |
| zec->zec_cksum = zc; |
| ci->ci_func (buf, size, endian, &actual_cksum); |
| zec->zec_cksum = expected_cksum; |
| zc = expected_cksum; |
| } |
| else |
| ci->ci_func (buf, size, endian, &actual_cksum); |
| |
| if (grub_memcmp (&actual_cksum, &zc, |
| checksum != ZIO_CHECKSUM_SHA256_MAC ? 32 : 20) != 0) |
| { |
| grub_dprintf ("zfs", "checksum %s verification failed\n", ci->ci_name); |
| grub_dprintf ("zfs", "actual checksum %016llx %016llx %016llx %016llx\n", |
| (unsigned long long) actual_cksum.zc_word[0], |
| (unsigned long long) actual_cksum.zc_word[1], |
| (unsigned long long) actual_cksum.zc_word[2], |
| (unsigned long long) actual_cksum.zc_word[3]); |
| grub_dprintf ("zfs", "expected checksum %016llx %016llx %016llx %016llx\n", |
| (unsigned long long) zc.zc_word[0], |
| (unsigned long long) zc.zc_word[1], |
| (unsigned long long) zc.zc_word[2], |
| (unsigned long long) zc.zc_word[3]); |
| return grub_error (GRUB_ERR_BAD_FS, N_("checksum verification failed")); |
| } |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| /* |
| * vdev_uberblock_compare takes two uberblock structures and returns an integer |
| * indicating the more recent of the two. |
| * Return Value = 1 if ub2 is more recent |
| * Return Value = -1 if ub1 is more recent |
| * The most recent uberblock is determined using its transaction number and |
| * timestamp. The uberblock with the highest transaction number is |
| * considered "newer". If the transaction numbers of the two blocks match, the |
| * timestamps are compared to determine the "newer" of the two. |
| */ |
| static int |
| vdev_uberblock_compare (uberblock_t * ub1, uberblock_t * ub2) |
| { |
| grub_zfs_endian_t ub1_endian, ub2_endian; |
| if (grub_zfs_to_cpu64 (ub1->ub_magic, GRUB_ZFS_LITTLE_ENDIAN) |
| == UBERBLOCK_MAGIC) |
| ub1_endian = GRUB_ZFS_LITTLE_ENDIAN; |
| else |
| ub1_endian = GRUB_ZFS_BIG_ENDIAN; |
| if (grub_zfs_to_cpu64 (ub2->ub_magic, GRUB_ZFS_LITTLE_ENDIAN) |
| == UBERBLOCK_MAGIC) |
| ub2_endian = GRUB_ZFS_LITTLE_ENDIAN; |
| else |
| ub2_endian = GRUB_ZFS_BIG_ENDIAN; |
| |
| if (grub_zfs_to_cpu64 (ub1->ub_txg, ub1_endian) |
| < grub_zfs_to_cpu64 (ub2->ub_txg, ub2_endian)) |
| return -1; |
| if (grub_zfs_to_cpu64 (ub1->ub_txg, ub1_endian) |
| > grub_zfs_to_cpu64 (ub2->ub_txg, ub2_endian)) |
| return 1; |
| |
| if (grub_zfs_to_cpu64 (ub1->ub_timestamp, ub1_endian) |
| < grub_zfs_to_cpu64 (ub2->ub_timestamp, ub2_endian)) |
| return -1; |
| if (grub_zfs_to_cpu64 (ub1->ub_timestamp, ub1_endian) |
| > grub_zfs_to_cpu64 (ub2->ub_timestamp, ub2_endian)) |
| return 1; |
| |
| return 0; |
| } |
| |
| /* |
| * Three pieces of information are needed to verify an uberblock: the magic |
| * number, the version number, and the checksum. |
| * |
| * Currently Implemented: version number, magic number, checksum |
| * |
| */ |
| static grub_err_t |
| uberblock_verify (uberblock_phys_t * ub, grub_uint64_t offset, |
| grub_size_t s) |
| { |
| uberblock_t *uber = &ub->ubp_uberblock; |
| grub_err_t err; |
| grub_zfs_endian_t endian = GRUB_ZFS_UNKNOWN_ENDIAN; |
| zio_cksum_t zc; |
| |
| if (grub_zfs_to_cpu64 (uber->ub_magic, GRUB_ZFS_LITTLE_ENDIAN) |
| == UBERBLOCK_MAGIC |
| && SPA_VERSION_IS_SUPPORTED(grub_zfs_to_cpu64 (uber->ub_version, GRUB_ZFS_LITTLE_ENDIAN))) |
| endian = GRUB_ZFS_LITTLE_ENDIAN; |
| |
| if (grub_zfs_to_cpu64 (uber->ub_magic, GRUB_ZFS_BIG_ENDIAN) == UBERBLOCK_MAGIC |
| && SPA_VERSION_IS_SUPPORTED(grub_zfs_to_cpu64 (uber->ub_version, GRUB_ZFS_BIG_ENDIAN))) |
| endian = GRUB_ZFS_BIG_ENDIAN; |
| |
| if (endian == GRUB_ZFS_UNKNOWN_ENDIAN) |
| return grub_error (GRUB_ERR_BAD_FS, "invalid uberblock magic"); |
| |
| grub_memset (&zc, 0, sizeof (zc)); |
| |
| zc.zc_word[0] = grub_cpu_to_zfs64 (offset, endian); |
| err = zio_checksum_verify (zc, ZIO_CHECKSUM_LABEL, endian, |
| (char *) ub, s); |
| |
| return err; |
| } |
| |
| /* |
| * Find the best uberblock. |
| * Return: |
| * Success - Pointer to the best uberblock. |
| * Failure - NULL |
| */ |
| static uberblock_phys_t * |
| find_bestub (uberblock_phys_t * ub_array, |
| const struct grub_zfs_device_desc *desc) |
| { |
| uberblock_phys_t *ubbest = NULL, *ubptr; |
| int i; |
| grub_disk_addr_t offset; |
| grub_err_t err = GRUB_ERR_NONE; |
| int ub_shift; |
| |
| ub_shift = desc->ashift; |
| if (ub_shift < VDEV_UBERBLOCK_SHIFT) |
| ub_shift = VDEV_UBERBLOCK_SHIFT; |
| |
| for (i = 0; i < (VDEV_UBERBLOCK_RING >> ub_shift); i++) |
| { |
| offset = (desc->vdev_phys_sector << SPA_MINBLOCKSHIFT) + VDEV_PHYS_SIZE |
| + (i << ub_shift); |
| |
| ubptr = (uberblock_phys_t *) ((grub_properly_aligned_t *) ub_array |
| + ((i << ub_shift) |
| / sizeof (grub_properly_aligned_t))); |
| err = uberblock_verify (ubptr, offset, 1 << ub_shift); |
| if (err) |
| { |
| grub_errno = GRUB_ERR_NONE; |
| continue; |
| } |
| if (ubbest == NULL |
| || vdev_uberblock_compare (&(ubptr->ubp_uberblock), |
| &(ubbest->ubp_uberblock)) > 0) |
| ubbest = ubptr; |
| } |
| if (!ubbest) |
| grub_errno = err; |
| |
| return ubbest; |
| } |
| |
| static inline grub_size_t |
| get_psize (blkptr_t * bp, grub_zfs_endian_t endian) |
| { |
| return ((((grub_zfs_to_cpu64 ((bp)->blk_prop, endian) >> 16) & 0xffff) + 1) |
| << SPA_MINBLOCKSHIFT); |
| } |
| |
| static grub_uint64_t |
| dva_get_offset (const dva_t *dva, grub_zfs_endian_t endian) |
| { |
| grub_dprintf ("zfs", "dva=%llx, %llx\n", |
| (unsigned long long) dva->dva_word[0], |
| (unsigned long long) dva->dva_word[1]); |
| return grub_zfs_to_cpu64 ((dva)->dva_word[1], |
| endian) << SPA_MINBLOCKSHIFT; |
| } |
| |
| static grub_err_t |
| zfs_fetch_nvlist (struct grub_zfs_device_desc *diskdesc, char **nvlist) |
| { |
| grub_err_t err; |
| |
| *nvlist = 0; |
| |
| if (!diskdesc->dev) |
| return grub_error (GRUB_ERR_BUG, "member drive unknown"); |
| |
| *nvlist = grub_malloc (VDEV_PHYS_SIZE); |
| |
| /* Read in the vdev name-value pair list (112K). */ |
| err = grub_disk_read (diskdesc->dev->disk, diskdesc->vdev_phys_sector, 0, |
| VDEV_PHYS_SIZE, *nvlist); |
| if (err) |
| { |
| grub_free (*nvlist); |
| *nvlist = 0; |
| return err; |
| } |
| return GRUB_ERR_NONE; |
| } |
| |
| static grub_err_t |
| fill_vdev_info_real (struct grub_zfs_data *data, |
| const char *nvlist, |
| struct grub_zfs_device_desc *fill, |
| struct grub_zfs_device_desc *insert, |
| int *inserted, |
| unsigned ashift) |
| { |
| char *type; |
| |
| type = grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_TYPE); |
| |
| if (!type) |
| return grub_errno; |
| |
| if (!grub_zfs_nvlist_lookup_uint64 (nvlist, "id", &(fill->id))) |
| { |
| grub_free (type); |
| return grub_error (GRUB_ERR_BAD_FS, "couldn't find vdev id"); |
| } |
| |
| if (!grub_zfs_nvlist_lookup_uint64 (nvlist, "guid", &(fill->guid))) |
| { |
| grub_free (type); |
| return grub_error (GRUB_ERR_BAD_FS, "couldn't find vdev id"); |
| } |
| |
| { |
| grub_uint64_t par; |
| if (grub_zfs_nvlist_lookup_uint64 (nvlist, "ashift", &par)) |
| fill->ashift = par; |
| else if (ashift != 0xffffffff) |
| fill->ashift = ashift; |
| else |
| { |
| grub_free (type); |
| return grub_error (GRUB_ERR_BAD_FS, "couldn't find ashift"); |
| } |
| } |
| |
| fill->max_children_ashift = 0; |
| |
| if (grub_strcmp (type, VDEV_TYPE_DISK) == 0 |
| || grub_strcmp (type, VDEV_TYPE_FILE) == 0) |
| { |
| fill->type = DEVICE_LEAF; |
| |
| if (!fill->dev && fill->guid == insert->guid) |
| { |
| fill->dev = insert->dev; |
| fill->vdev_phys_sector = insert->vdev_phys_sector; |
| fill->current_uberblock = insert->current_uberblock; |
| fill->original = insert->original; |
| if (!data->device_original) |
| data->device_original = fill; |
| insert->ashift = fill->ashift; |
| *inserted = 1; |
| } |
| |
| grub_free (type); |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| if (grub_strcmp (type, VDEV_TYPE_MIRROR) == 0 |
| || grub_strcmp (type, VDEV_TYPE_RAIDZ) == 0) |
| { |
| int nelm, i; |
| |
| if (grub_strcmp (type, VDEV_TYPE_MIRROR) == 0) |
| fill->type = DEVICE_MIRROR; |
| else |
| { |
| grub_uint64_t par; |
| fill->type = DEVICE_RAIDZ; |
| if (!grub_zfs_nvlist_lookup_uint64 (nvlist, "nparity", &par)) |
| { |
| grub_free (type); |
| return grub_error (GRUB_ERR_BAD_FS, "couldn't find raidz parity"); |
| } |
| fill->nparity = par; |
| } |
| |
| nelm = grub_zfs_nvlist_lookup_nvlist_array_get_nelm (nvlist, |
| ZPOOL_CONFIG_CHILDREN); |
| |
| if (nelm <= 0) |
| { |
| grub_free (type); |
| return grub_error (GRUB_ERR_BAD_FS, "incorrect mirror VDEV"); |
| } |
| |
| if (!fill->children) |
| { |
| fill->n_children = nelm; |
| |
| fill->children = grub_zalloc (fill->n_children |
| * sizeof (fill->children[0])); |
| } |
| |
| for (i = 0; i < nelm; i++) |
| { |
| char *child; |
| grub_err_t err; |
| |
| child = grub_zfs_nvlist_lookup_nvlist_array |
| (nvlist, ZPOOL_CONFIG_CHILDREN, i); |
| |
| err = fill_vdev_info_real (data, child, &fill->children[i], insert, |
| inserted, fill->ashift); |
| |
| grub_free (child); |
| |
| if (err) |
| { |
| grub_free (type); |
| return err; |
| } |
| if (fill->children[i].ashift > fill->max_children_ashift) |
| fill->max_children_ashift = fill->children[i].ashift; |
| } |
| grub_free (type); |
| return GRUB_ERR_NONE; |
| } |
| |
| grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "vdev %s isn't supported", type); |
| grub_free (type); |
| return grub_errno; |
| } |
| |
| static grub_err_t |
| fill_vdev_info (struct grub_zfs_data *data, |
| char *nvlist, struct grub_zfs_device_desc *diskdesc, |
| int *inserted) |
| { |
| grub_uint64_t id; |
| unsigned i; |
| |
| *inserted = 0; |
| |
| if (!grub_zfs_nvlist_lookup_uint64 (nvlist, "id", &id)) |
| return grub_error (GRUB_ERR_BAD_FS, "couldn't find vdev id"); |
| |
| for (i = 0; i < data->n_devices_attached; i++) |
| if (data->devices_attached[i].id == id) |
| return fill_vdev_info_real (data, nvlist, &data->devices_attached[i], |
| diskdesc, inserted, 0xffffffff); |
| |
| data->n_devices_attached++; |
| if (data->n_devices_attached > data->n_devices_allocated) |
| { |
| void *tmp; |
| data->n_devices_allocated = 2 * data->n_devices_attached + 1; |
| data->devices_attached |
| = grub_realloc (tmp = data->devices_attached, |
| data->n_devices_allocated |
| * sizeof (data->devices_attached[0])); |
| if (!data->devices_attached) |
| { |
| data->devices_attached = tmp; |
| return grub_errno; |
| } |
| } |
| |
| grub_memset (&data->devices_attached[data->n_devices_attached - 1], |
| 0, sizeof (data->devices_attached[data->n_devices_attached - 1])); |
| |
| return fill_vdev_info_real (data, nvlist, |
| &data->devices_attached[data->n_devices_attached - 1], |
| diskdesc, inserted, 0xffffffff); |
| } |
| |
| /* |
| * For a given XDR packed nvlist, verify the first 4 bytes and move on. |
| * |
| * An XDR packed nvlist is encoded as (comments from nvs_xdr_create) : |
| * |
| * encoding method/host endian (4 bytes) |
| * nvl_version (4 bytes) |
| * nvl_nvflag (4 bytes) |
| * encoded nvpairs: |
| * encoded size of the nvpair (4 bytes) |
| * decoded size of the nvpair (4 bytes) |
| * name string size (4 bytes) |
| * name string data (sizeof(NV_ALIGN4(string)) |
| * data type (4 bytes) |
| * # of elements in the nvpair (4 bytes) |
| * data |
| * 2 zero's for the last nvpair |
| * (end of the entire list) (8 bytes) |
| * |
| */ |
| |
| /* |
| * The nvlist_next_nvpair() function returns a handle to the next nvpair in the |
| * list following nvpair. If nvpair is NULL, the first pair is returned. If |
| * nvpair is the last pair in the nvlist, NULL is returned. |
| */ |
| static const char * |
| nvlist_next_nvpair (const char *nvl, const char *nvpair) |
| { |
| const char *nvp; |
| int encode_size; |
| int name_len; |
| if (nvl == NULL) |
| return NULL; |
| |
| if (nvpair == NULL) |
| { |
| /* skip over header, nvl_version and nvl_nvflag */ |
| nvpair = nvl + 4 * 3; |
| } |
| else |
| { |
| /* skip to the next nvpair */ |
| encode_size = grub_be_to_cpu32 (grub_get_unaligned32(nvpair)); |
| nvpair += encode_size; |
| /*If encode_size equals 0 nvlist_next_nvpair would return |
| * the same pair received in input, leading to an infinite loop. |
| * If encode_size is less than 0, this will move the pointer |
| * backwards, *possibly* examinining two times the same nvpair |
| * and potentially getting into an infinite loop. */ |
| if(encode_size <= 0) |
| { |
| grub_dprintf ("zfs", "nvpair with size <= 0\n"); |
| grub_error (GRUB_ERR_BAD_FS, "incorrect nvlist"); |
| return NULL; |
| } |
| } |
| /* 8 bytes of 0 marks the end of the list */ |
| if (grub_get_unaligned64 (nvpair) == 0) |
| return NULL; |
| /*consistency checks*/ |
| if (nvpair + 4 * 3 >= nvl + VDEV_PHYS_SIZE) |
| { |
| grub_dprintf ("zfs", "nvlist overflow\n"); |
| grub_error (GRUB_ERR_BAD_FS, "incorrect nvlist"); |
| return NULL; |
| } |
| encode_size = grub_be_to_cpu32 (grub_get_unaligned32(nvpair)); |
| |
| nvp = nvpair + 4*2; |
| name_len = grub_be_to_cpu32 (grub_get_unaligned32 (nvp)); |
| nvp += 4; |
| |
| nvp = nvp + ((name_len + 3) & ~3); // align |
| if (nvp + 4 >= nvl + VDEV_PHYS_SIZE |
| || encode_size < 0 |
| || nvp + 4 + encode_size > nvl + VDEV_PHYS_SIZE) |
| { |
| grub_dprintf ("zfs", "nvlist overflow\n"); |
| grub_error (GRUB_ERR_BAD_FS, "incorrect nvlist"); |
| return NULL; |
| } |
| /* end consistency checks */ |
| |
| return nvpair; |
| } |
| |
| /* |
| * This function returns 0 on success and 1 on failure. On success, a string |
| * containing the name of nvpair is saved in buf. |
| */ |
| static int |
| nvpair_name (const char *nvp, char **buf, grub_size_t *buflen) |
| { |
| /* skip over encode/decode size */ |
| nvp += 4 * 2; |
| |
| *buf = (char *) (nvp + 4); |
| *buflen = grub_be_to_cpu32 (grub_get_unaligned32 (nvp)); |
| |
| return 0; |
| } |
| |
| /* |
| * This function retrieves the value of the nvpair in the form of enumerated |
| * type data_type_t. |
| */ |
| static int |
| nvpair_type (const char *nvp) |
| { |
| int name_len, type; |
| |
| /* skip over encode/decode size */ |
| nvp += 4 * 2; |
| |
| /* skip over name_len */ |
| name_len = grub_be_to_cpu32 (grub_get_unaligned32 (nvp)); |
| nvp += 4; |
| |
| /* skip over name */ |
| nvp = nvp + ((name_len + 3) & ~3); /* align */ |
| |
| type = grub_be_to_cpu32 (grub_get_unaligned32 (nvp)); |
| |
| return type; |
| } |
| |
| static int |
| nvpair_value (const char *nvp,char **val, |
| grub_size_t *size_out, grub_size_t *nelm_out) |
| { |
| int name_len,nelm,encode_size; |
| |
| /* skip over encode/decode size */ |
| encode_size = grub_be_to_cpu32 (grub_get_unaligned32(nvp)); |
| nvp += 8; |
| |
| /* skip over name_len */ |
| name_len = grub_be_to_cpu32 (grub_get_unaligned32 (nvp)); |
| nvp += 4; |
| |
| /* skip over name */ |
| nvp = nvp + ((name_len + 3) & ~3); /* align */ |
| |
| /* skip over type */ |
| nvp += 4; |
| nelm = grub_be_to_cpu32 (grub_get_unaligned32 (nvp)); |
| nvp +=4; |
| if (nelm < 1) |
| { |
| grub_error (GRUB_ERR_BAD_FS, "empty nvpair"); |
| return 0; |
| } |
| *val = (char *) nvp; |
| *size_out = encode_size; |
| if (nelm_out) |
| *nelm_out = nelm; |
| |
| return 1; |
| } |
| |
| /* |
| * Check the disk label information and retrieve needed vdev name-value pairs. |
| * |
| */ |
| static grub_err_t |
| check_pool_label (struct grub_zfs_data *data, |
| struct grub_zfs_device_desc *diskdesc, |
| int *inserted, int original) |
| { |
| grub_uint64_t pool_state, txg = 0; |
| char *nvlist,*features; |
| #if 0 |
| char *nv; |
| #endif |
| grub_uint64_t poolguid; |
| grub_uint64_t version; |
| int found; |
| grub_err_t err; |
| grub_zfs_endian_t endian; |
| vdev_phys_t *phys; |
| zio_cksum_t emptycksum; |
| |
| *inserted = 0; |
| |
| err = zfs_fetch_nvlist (diskdesc, &nvlist); |
| if (err) |
| return err; |
| |
| phys = (vdev_phys_t*) nvlist; |
| if (grub_zfs_to_cpu64 (phys->vp_zbt.zec_magic, |
| GRUB_ZFS_LITTLE_ENDIAN) |
| == ZEC_MAGIC) |
| endian = GRUB_ZFS_LITTLE_ENDIAN; |
| else if (grub_zfs_to_cpu64 (phys->vp_zbt.zec_magic, |
| GRUB_ZFS_BIG_ENDIAN) |
| == ZEC_MAGIC) |
| endian = GRUB_ZFS_BIG_ENDIAN; |
| else |
| { |
| grub_free (nvlist); |
| return grub_error (GRUB_ERR_BAD_FS, |
| "bad vdev_phys_t.vp_zbt.zec_magic number"); |
| } |
| /* Now check the integrity of the vdev_phys_t structure though checksum. */ |
| ZIO_SET_CHECKSUM(&emptycksum, diskdesc->vdev_phys_sector << 9, 0, 0, 0); |
| err = zio_checksum_verify (emptycksum, ZIO_CHECKSUM_LABEL, endian, |
| nvlist, VDEV_PHYS_SIZE); |
| if (err) |
| return err; |
| |
| grub_dprintf ("zfs", "check 2 passed\n"); |
| |
| found = grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_POOL_STATE, |
| &pool_state); |
| if (! found) |
| { |
| grub_free (nvlist); |
| if (! grub_errno) |
| grub_error (GRUB_ERR_BAD_FS, ZPOOL_CONFIG_POOL_STATE " not found"); |
| return grub_errno; |
| } |
| grub_dprintf ("zfs", "check 3 passed\n"); |
| |
| if (pool_state == POOL_STATE_DESTROYED) |
| { |
| grub_free (nvlist); |
| return grub_error (GRUB_ERR_BAD_FS, "zpool is marked as destroyed"); |
| } |
| grub_dprintf ("zfs", "check 4 passed\n"); |
| |
| found = grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_POOL_TXG, &txg); |
| if (!found) |
| { |
| grub_free (nvlist); |
| if (! grub_errno) |
| grub_error (GRUB_ERR_BAD_FS, ZPOOL_CONFIG_POOL_TXG " not found"); |
| return grub_errno; |
| } |
| grub_dprintf ("zfs", "check 6 passed\n"); |
| |
| /* not an active device */ |
| if (txg == 0) |
| { |
| grub_free (nvlist); |
| return grub_error (GRUB_ERR_BAD_FS, "zpool isn't active"); |
| } |
| grub_dprintf ("zfs", "check 7 passed\n"); |
| |
| found = grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_VERSION, |
| &version); |
| if (! found) |
| { |
| grub_free (nvlist); |
| if (! grub_errno) |
| grub_error (GRUB_ERR_BAD_FS, ZPOOL_CONFIG_VERSION " not found"); |
| return grub_errno; |
| } |
| grub_dprintf ("zfs", "check 8 passed\n"); |
| |
| if (!SPA_VERSION_IS_SUPPORTED(version)) |
| { |
| grub_free (nvlist); |
| return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, |
| "too new version %llu > %llu", |
| (unsigned long long) version, |
| (unsigned long long) SPA_VERSION_BEFORE_FEATURES); |
| } |
| grub_dprintf ("zfs", "check 9 passed\n"); |
| |
| found = grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_GUID, |
| &(diskdesc->guid)); |
| if (! found) |
| { |
| grub_free (nvlist); |
| if (! grub_errno) |
| grub_error (GRUB_ERR_BAD_FS, ZPOOL_CONFIG_GUID " not found"); |
| return grub_errno; |
| } |
| |
| found = grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_POOL_GUID, |
| &poolguid); |
| if (! found) |
| { |
| grub_free (nvlist); |
| if (! grub_errno) |
| grub_error (GRUB_ERR_BAD_FS, ZPOOL_CONFIG_POOL_GUID " not found"); |
| return grub_errno; |
| } |
| |
| grub_dprintf ("zfs", "check 11 passed\n"); |
| |
| if (original) |
| data->guid = poolguid; |
| |
| if (data->guid != poolguid) |
| return grub_error (GRUB_ERR_BAD_FS, "another zpool"); |
| |
| { |
| char *nv; |
| nv = grub_zfs_nvlist_lookup_nvlist (nvlist, ZPOOL_CONFIG_VDEV_TREE); |
| |
| if (!nv) |
| { |
| grub_free (nvlist); |
| return grub_error (GRUB_ERR_BAD_FS, "couldn't find vdev tree"); |
| } |
| err = fill_vdev_info (data, nv, diskdesc, inserted); |
| if (err) |
| { |
| grub_free (nv); |
| grub_free (nvlist); |
| return err; |
| } |
| grub_free (nv); |
| } |
| grub_dprintf ("zfs", "check 10 passed\n"); |
| features = grub_zfs_nvlist_lookup_nvlist(nvlist, |
| ZPOOL_CONFIG_FEATURES_FOR_READ); |
| if (features) |
| { |
| const char *nvp=NULL; |
| char name[MAX_SUPPORTED_FEATURE_STRLEN + 1]; |
| char *nameptr; |
| grub_size_t namelen; |
| while ((nvp = nvlist_next_nvpair(features, nvp)) != NULL) |
| { |
| nvpair_name (nvp, &nameptr, &namelen); |
| if(namelen > MAX_SUPPORTED_FEATURE_STRLEN) |
| namelen = MAX_SUPPORTED_FEATURE_STRLEN; |
| grub_memcpy (name, nameptr, namelen); |
| name[namelen] = '\0'; |
| grub_dprintf("zfs","str=%s\n",name); |
| if (check_feature(name,1, NULL) != 0) |
| { |
| grub_dprintf("zfs","feature missing in check_pool_label:%s\n",name); |
| err= grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET," check_pool_label missing feature '%s' for read",name); |
| return err; |
| } |
| } |
| } |
| grub_dprintf ("zfs", "check 12 passed (feature flags)\n"); |
| grub_free (nvlist); |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| static grub_err_t |
| scan_disk (grub_device_t dev, struct grub_zfs_data *data, |
| int original, int *inserted) |
| { |
| int label = 0; |
| uberblock_phys_t *ub_array, *ubbest = NULL; |
| vdev_boot_header_t *bh; |
| grub_err_t err; |
| int vdevnum; |
| struct grub_zfs_device_desc desc; |
| |
| ub_array = grub_malloc (VDEV_UBERBLOCK_RING); |
| if (!ub_array) |
| return grub_errno; |
| |
| bh = grub_malloc (VDEV_BOOT_HEADER_SIZE); |
| if (!bh) |
| { |
| grub_free (ub_array); |
| return grub_errno; |
| } |
| |
| vdevnum = VDEV_LABELS; |
| |
| desc.dev = dev; |
| desc.original = original; |
| |
| /* Don't check back labels on CDROM. */ |
| if (grub_disk_get_size (dev->disk) == GRUB_DISK_SIZE_UNKNOWN) |
| vdevnum = VDEV_LABELS / 2; |
| |
| for (label = 0; ubbest == NULL && label < vdevnum; label++) |
| { |
| desc.vdev_phys_sector |
| = label * (sizeof (vdev_label_t) >> SPA_MINBLOCKSHIFT) |
| + ((VDEV_SKIP_SIZE + VDEV_BOOT_HEADER_SIZE) >> SPA_MINBLOCKSHIFT) |
| + (label < VDEV_LABELS / 2 ? 0 : |
| ALIGN_DOWN (grub_disk_get_size (dev->disk), sizeof (vdev_label_t)) |
| - VDEV_LABELS * (sizeof (vdev_label_t) >> SPA_MINBLOCKSHIFT)); |
| |
| /* Read in the uberblock ring (128K). */ |
| err = grub_disk_read (dev->disk, desc.vdev_phys_sector |
| + (VDEV_PHYS_SIZE >> SPA_MINBLOCKSHIFT), |
| 0, VDEV_UBERBLOCK_RING, (char *) ub_array); |
| if (err) |
| { |
| grub_errno = GRUB_ERR_NONE; |
| continue; |
| } |
| grub_dprintf ("zfs", "label ok %d\n", label); |
| |
| err = check_pool_label (data, &desc, inserted, original); |
| if (err || !*inserted) |
| { |
| grub_errno = GRUB_ERR_NONE; |
| continue; |
| } |
| |
| ubbest = find_bestub (ub_array, &desc); |
| if (!ubbest) |
| { |
| grub_dprintf ("zfs", "No uberblock found\n"); |
| grub_errno = GRUB_ERR_NONE; |
| continue; |
| } |
| |
| grub_memmove (&(desc.current_uberblock), |
| &ubbest->ubp_uberblock, sizeof (uberblock_t)); |
| if (original) |
| grub_memmove (&(data->current_uberblock), |
| &ubbest->ubp_uberblock, sizeof (uberblock_t)); |
| |
| #if 0 |
| if (find_best_root && |
| vdev_uberblock_compare (&ubbest->ubp_uberblock, |
| &(current_uberblock)) <= 0) |
| continue; |
| #endif |
| grub_free (ub_array); |
| grub_free (bh); |
| return GRUB_ERR_NONE; |
| } |
| |
| grub_free (ub_array); |
| grub_free (bh); |
| |
| return grub_error (GRUB_ERR_BAD_FS, "couldn't find a valid label"); |
| } |
| |
| /* Helper for scan_devices. */ |
| static int |
| scan_devices_iter (const char *name, void *hook_data) |
| { |
| struct grub_zfs_data *data = hook_data; |
| grub_device_t dev; |
| grub_err_t err; |
| int inserted; |
| |
| dev = grub_device_open (name); |
| if (!dev) |
| return 0; |
| if (!dev->disk) |
| { |
| grub_device_close (dev); |
| return 0; |
| } |
| err = scan_disk (dev, data, 0, &inserted); |
| if (err == GRUB_ERR_BAD_FS) |
| { |
| grub_device_close (dev); |
| grub_errno = GRUB_ERR_NONE; |
| return 0; |
| } |
| if (err) |
| { |
| grub_device_close (dev); |
| grub_print_error (); |
| return 0; |
| } |
| |
| if (!inserted) |
| grub_device_close (dev); |
| |
| return 0; |
| } |
| |
| static grub_err_t |
| scan_devices (struct grub_zfs_data *data) |
| { |
| grub_device_iterate (scan_devices_iter, data); |
| return GRUB_ERR_NONE; |
| } |
| |
| /* x**y. */ |
| static grub_uint8_t powx[255 * 2]; |
| /* Such an s that x**s = y */ |
| static int powx_inv[256]; |
| static const grub_uint8_t poly = 0x1d; |
| |
| /* perform the operation a ^= b * (x ** (known_idx * recovery_pow) ) */ |
| static inline void |
| xor_out (grub_uint8_t *a, const grub_uint8_t *b, grub_size_t s, |
| unsigned known_idx, unsigned recovery_pow) |
| { |
| unsigned add; |
| |
| /* Simple xor. */ |
| if (known_idx == 0 || recovery_pow == 0) |
| { |
| grub_crypto_xor (a, a, b, s); |
| return; |
| } |
| add = (known_idx * recovery_pow) % 255; |
| for (;s--; b++, a++) |
| if (*b) |
| *a ^= powx[powx_inv[*b] + add]; |
| } |
| |
| static inline grub_uint8_t |
| gf_mul (grub_uint8_t a, grub_uint8_t b) |
| { |
| if (a == 0 || b == 0) |
| return 0; |
| return powx[powx_inv[a] + powx_inv[b]]; |
| } |
| |
| #define MAX_NBUFS 4 |
| |
| static grub_err_t |
| recovery (grub_uint8_t *bufs[4], grub_size_t s, const int nbufs, |
| const unsigned *powers, |
| const unsigned *idx) |
| { |
| grub_dprintf ("zfs", "recovering %u buffers\n", nbufs); |
| /* Now we have */ |
| /* b_i = sum (r_j* (x ** (powers[i] * idx[j])))*/ |
| /* Let's invert the matrix in question. */ |
| switch (nbufs) |
| { |
| /* Easy: r_0 = bufs[0] / (x << (powers[i] * idx[j])). */ |
| case 1: |
| { |
| int add; |
| grub_uint8_t *a; |
| if (powers[0] == 0 || idx[0] == 0) |
| return GRUB_ERR_NONE; |
| add = 255 - ((powers[0] * idx[0]) % 255); |
| for (a = bufs[0]; s--; a++) |
| if (*a) |
| *a = powx[powx_inv[*a] + add]; |
| return GRUB_ERR_NONE; |
| } |
| /* Case 2x2: Let's use the determinant formula. */ |
| case 2: |
| { |
| grub_uint8_t det, det_inv; |
| grub_uint8_t matrixinv[2][2]; |
| unsigned i; |
| /* The determinant is: */ |
| det = (powx[(powers[0] * idx[0] + powers[1] * idx[1]) % 255] |
| ^ powx[(powers[0] * idx[1] + powers[1] * idx[0]) % 255]); |
| if (det == 0) |
| return grub_error (GRUB_ERR_BAD_FS, "singular recovery matrix"); |
| det_inv = powx[255 - powx_inv[det]]; |
| matrixinv[0][0] = gf_mul (powx[(powers[1] * idx[1]) % 255], det_inv); |
| matrixinv[1][1] = gf_mul (powx[(powers[0] * idx[0]) % 255], det_inv); |
| matrixinv[0][1] = gf_mul (powx[(powers[0] * idx[1]) % 255], det_inv); |
| matrixinv[1][0] = gf_mul (powx[(powers[1] * idx[0]) % 255], det_inv); |
| for (i = 0; i < s; i++) |
| { |
| grub_uint8_t b0, b1; |
| b0 = bufs[0][i]; |
| b1 = bufs[1][i]; |
| |
| bufs[0][i] = (gf_mul (b0, matrixinv[0][0]) |
| ^ gf_mul (b1, matrixinv[0][1])); |
| bufs[1][i] = (gf_mul (b0, matrixinv[1][0]) |
| ^ gf_mul (b1, matrixinv[1][1])); |
| } |
| return GRUB_ERR_NONE; |
| } |
| /* Otherwise use Gauss. */ |
| case 3: |
| { |
| grub_uint8_t matrix1[MAX_NBUFS][MAX_NBUFS], matrix2[MAX_NBUFS][MAX_NBUFS]; |
| int i, j, k; |
| |
| for (i = 0; i < nbufs; i++) |
| for (j = 0; j < nbufs; j++) |
| matrix1[i][j] = powx[(powers[i] * idx[j]) % 255]; |
| for (i = 0; i < nbufs; i++) |
| for (j = 0; j < nbufs; j++) |
| matrix2[i][j] = 0; |
| for (i = 0; i < nbufs; i++) |
| matrix2[i][i] = 1; |
| |
| for (i = 0; i < nbufs; i++) |
| { |
| grub_uint8_t mul; |
| for (j = i; j < nbufs; j++) |
| if (matrix1[i][j]) |
| break; |
| if (j == nbufs) |
| return grub_error (GRUB_ERR_BAD_FS, "singular recovery matrix"); |
| if (j != i) |
| { |
| int xchng; |
| xchng = j; |
| for (j = 0; j < nbufs; j++) |
| { |
| grub_uint8_t t; |
| t = matrix1[xchng][j]; |
| matrix1[xchng][j] = matrix1[i][j]; |
| matrix1[i][j] = t; |
| } |
| for (j = 0; j < nbufs; j++) |
| { |
| grub_uint8_t t; |
| t = matrix2[xchng][j]; |
| matrix2[xchng][j] = matrix2[i][j]; |
| matrix2[i][j] = t; |
| } |
| } |
| mul = powx[255 - powx_inv[matrix1[i][i]]]; |
| for (j = 0; j < nbufs; j++) |
| matrix1[i][j] = gf_mul (matrix1[i][j], mul); |
| for (j = 0; j < nbufs; j++) |
| matrix2[i][j] = gf_mul (matrix2[i][j], mul); |
| for (j = i + 1; j < nbufs; j++) |
| { |
| mul = matrix1[j][i]; |
| for (k = 0; k < nbufs; k++) |
| matrix1[j][k] ^= gf_mul (matrix1[i][k], mul); |
| for (k = 0; k < nbufs; k++) |
| matrix2[j][k] ^= gf_mul (matrix2[i][k], mul); |
| } |
| } |
| for (i = nbufs - 1; i >= 0; i--) |
| { |
| for (j = 0; j < i; j++) |
| { |
| grub_uint8_t mul; |
| mul = matrix1[j][i]; |
| for (k = 0; k < nbufs; k++) |
| matrix1[j][k] ^= gf_mul (matrix1[i][k], mul); |
| for (k = 0; k < nbufs; k++) |
| matrix2[j][k] ^= gf_mul (matrix2[i][k], mul); |
| } |
| } |
| |
| for (i = 0; i < (int) s; i++) |
| { |
| grub_uint8_t b[MAX_NBUFS]; |
| for (j = 0; j < nbufs; j++) |
| b[j] = bufs[j][i]; |
| for (j = 0; j < nbufs; j++) |
| { |
| bufs[j][i] = 0; |
| for (k = 0; k < nbufs; k++) |
| bufs[j][i] ^= gf_mul (matrix2[j][k], b[k]); |
| } |
| } |
| return GRUB_ERR_NONE; |
| } |
| default: |
| return grub_error (GRUB_ERR_BUG, "too big matrix"); |
| } |
| } |
| |
| static grub_err_t |
| read_device (grub_uint64_t offset, struct grub_zfs_device_desc *desc, |
| grub_size_t len, void *buf) |
| { |
| switch (desc->type) |
| { |
| case DEVICE_LEAF: |
| { |
| grub_uint64_t sector; |
| sector = DVA_OFFSET_TO_PHYS_SECTOR (offset); |
| if (!desc->dev) |
| { |
| return grub_error (GRUB_ERR_BAD_FS, |
| N_("couldn't find a necessary member device " |
| "of multi-device filesystem")); |
| } |
| /* read in a data block */ |
| return grub_disk_read (desc->dev->disk, sector, 0, len, buf); |
| } |
| case DEVICE_MIRROR: |
| { |
| grub_err_t err = GRUB_ERR_NONE; |
| unsigned i; |
| if (desc->n_children <= 0) |
| return grub_error (GRUB_ERR_BAD_FS, |
| "non-positive number of mirror children"); |
| for (i = 0; i < desc->n_children; i++) |
| { |
| err = read_device (offset, &desc->children[i], |
| len, buf); |
| if (!err) |
| break; |
| grub_errno = GRUB_ERR_NONE; |
| } |
| grub_errno = err; |
| |
| return err; |
| } |
| case DEVICE_RAIDZ: |
| { |
| unsigned c = 0; |
| grub_uint64_t high; |
| grub_uint64_t devn; |
| grub_uint64_t m; |
| grub_uint32_t s, orig_s; |
| void *orig_buf = buf; |
| grub_size_t orig_len = len; |
| grub_uint8_t *recovery_buf[4]; |
| grub_size_t recovery_len[4]; |
| unsigned recovery_idx[4]; |
| unsigned failed_devices = 0; |
| int idx, orig_idx; |
| |
| if (desc->nparity < 1 || desc->nparity > 3) |
| return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, |
| "raidz%d is not supported", desc->nparity); |
| |
| if (desc->n_children <= desc->nparity || desc->n_children < 1) |
| return grub_error(GRUB_ERR_BAD_FS, "too little devices for given parity"); |
| |
| orig_s = (((len + (1 << desc->ashift) - 1) >> desc->ashift) |
| + (desc->n_children - desc->nparity) - 1); |
| s = orig_s; |
| |
| high = grub_divmod64 ((offset >> desc->ashift), |
| desc->n_children, &m); |
| if (desc->nparity == 2) |
| c = 2; |
| if (desc->nparity == 3) |
| c = 3; |
| if (((len + (1 << desc->ashift) - 1) >> desc->ashift) |
| >= (desc->n_children - desc->nparity)) |
| idx = (desc->n_children - desc->nparity - 1); |
| else |
| idx = ((len + (1 << desc->ashift) - 1) >> desc->ashift) - 1; |
| orig_idx = idx; |
| while (len > 0) |
| { |
| grub_size_t csize; |
| grub_uint32_t bsize; |
| grub_err_t err; |
| bsize = s / (desc->n_children - desc->nparity); |
| |
| if (desc->nparity == 1 |
| && ((offset >> (desc->ashift + 20 - desc->max_children_ashift)) |
| & 1) == c) |
| c++; |
| |
| high = grub_divmod64 ((offset >> desc->ashift) + c, |
| desc->n_children, &devn); |
| csize = bsize << desc->ashift; |
| if (csize > len) |
| csize = len; |
| |
| grub_dprintf ("zfs", "RAIDZ mapping 0x%" PRIxGRUB_UINT64_T |
| "+%u (%" PRIxGRUB_SIZE ", %" PRIxGRUB_UINT32_T |
| ") -> (0x%" PRIxGRUB_UINT64_T ", 0x%" |
| PRIxGRUB_UINT64_T ")\n", |
| offset >> desc->ashift, c, len, bsize, high, |
| devn); |
| err = read_device ((high << desc->ashift) |
| | (offset & ((1 << desc->ashift) - 1)), |
| &desc->children[devn], |
| csize, buf); |
| if (err && failed_devices < desc->nparity) |
| { |
| recovery_buf[failed_devices] = buf; |
| recovery_len[failed_devices] = csize; |
| recovery_idx[failed_devices] = idx; |
| failed_devices++; |
| grub_errno = err = 0; |
| } |
| if (err) |
| return err; |
| |
| c++; |
| idx--; |
| s--; |
| buf = (char *) buf + csize; |
| len -= csize; |
| } |
| if (failed_devices) |
| { |
| unsigned redundancy_pow[4]; |
| unsigned cur_redundancy_pow = 0; |
| unsigned n_redundancy = 0; |
| unsigned i, j; |
| grub_err_t err; |
| |
| /* Compute mul. x**s has a period of 255. */ |
| if (powx[0] == 0) |
| { |
| grub_uint8_t cur = 1; |
| for (i = 0; i < 255; i++) |
| { |
| powx[i] = cur; |
| powx[i + 255] = cur; |
| powx_inv[cur] = i; |
| if (cur & 0x80) |
| cur = (cur << 1) ^ poly; |
| else |
| cur <<= 1; |
| } |
| } |
| |
| /* Read redundancy data. */ |
| for (n_redundancy = 0, cur_redundancy_pow = 0; |
| n_redundancy < failed_devices; |
| cur_redundancy_pow++) |
| { |
| high = grub_divmod64 ((offset >> desc->ashift) |
| + cur_redundancy_pow |
| + ((desc->nparity == 1) |
| && ((offset >> (desc->ashift + 20 |
| - desc->max_children_ashift)) |
| & 1)), |
| desc->n_children, &devn); |
| err = read_device ((high << desc->ashift) |
| | (offset & ((1 << desc->ashift) - 1)), |
| &desc->children[devn], |
| recovery_len[n_redundancy], |
| recovery_buf[n_redundancy]); |
| /* Ignore error if we may still have enough devices. */ |
| if (err && n_redundancy + desc->nparity - cur_redundancy_pow - 1 |
| >= failed_devices) |
| { |
| grub_errno = GRUB_ERR_NONE; |
| continue; |
| } |
| if (err) |
| return err; |
| redundancy_pow[n_redundancy] = cur_redundancy_pow; |
| n_redundancy++; |
| } |
| /* Now xor-our the parts we already know. */ |
| buf = orig_buf; |
| len = orig_len; |
| s = orig_s; |
| idx = orig_idx; |
| |
| while (len > 0) |
| { |
| grub_size_t csize; |
| csize = ((s / (desc->n_children - desc->nparity)) |
| << desc->ashift); |
| if (csize > len) |
| csize = len; |
| |
| for (j = 0; j < failed_devices; j++) |
| if (buf == recovery_buf[j]) |
| break; |
| |
| if (j == failed_devices) |
| for (j = 0; j < failed_devices; j++) |
| xor_out (recovery_buf[j], buf, |
| csize < recovery_len[j] ? csize : recovery_len[j], |
| idx, redundancy_pow[j]); |
| |
| s--; |
| buf = (char *) buf + csize; |
| len -= csize; |
| idx--; |
| } |
| for (i = 0; i < failed_devices |
| && recovery_len[i] == recovery_len[0]; |
| i++); |
| /* Since the chunks have variable length handle the last block |
| separately. */ |
| if (i != failed_devices) |
| { |
| grub_uint8_t *tmp_recovery_buf[4]; |
| for (j = 0; j < i; j++) |
| tmp_recovery_buf[j] = recovery_buf[j] + recovery_len[failed_devices - 1]; |
| err = recovery (tmp_recovery_buf, recovery_len[0] - recovery_len[failed_devices - 1], i, redundancy_pow, |
| recovery_idx); |
| if (err) |
| return err; |
| } |
| err = recovery (recovery_buf, recovery_len[failed_devices - 1], |
| failed_devices, redundancy_pow, recovery_idx); |
| if (err) |
| return err; |
| } |
| return GRUB_ERR_NONE; |
| } |
| } |
| return grub_error (GRUB_ERR_BAD_FS, "unsupported device type"); |
| } |
| |
| static grub_err_t |
| read_dva (const dva_t *dva, |
| grub_zfs_endian_t endian, struct grub_zfs_data *data, |
| void *buf, grub_size_t len) |
| { |
| grub_uint64_t offset; |
| unsigned i; |
| grub_err_t err = 0; |
| int try = 0; |
| offset = dva_get_offset (dva, endian); |
| |
| for (try = 0; try < 2; try++) |
| { |
| for (i = 0; i < data->n_devices_attached; i++) |
| if (data->devices_attached[i].id == DVA_GET_VDEV (dva)) |
| { |
| err = read_device (offset, &data->devices_attached[i], len, buf); |
| if (!err) |
| return GRUB_ERR_NONE; |
| break; |
| } |
| if (try == 1) |
| break; |
| err = scan_devices (data); |
| if (err) |
| return err; |
| } |
| if (!err) |
| return grub_error (GRUB_ERR_BAD_FS, "unknown device %d", |
| (int) DVA_GET_VDEV (dva)); |
| return err; |
| } |
| |
| /* |
| * Read a block of data based on the gang block address dva, |
| * and put its data in buf. |
| * |
| */ |
| static grub_err_t |
| zio_read_gang (blkptr_t * bp, grub_zfs_endian_t endian, dva_t * dva, void *buf, |
| struct grub_zfs_data *data) |
| { |
| zio_gbh_phys_t *zio_gb; |
| unsigned i; |
| grub_err_t err; |
| zio_cksum_t zc; |
| |
| grub_memset (&zc, 0, sizeof (zc)); |
| |
| zio_gb = grub_malloc (SPA_GANGBLOCKSIZE); |
| if (!zio_gb) |
| return grub_errno; |
| grub_dprintf ("zfs", endian == GRUB_ZFS_LITTLE_ENDIAN ? "little-endian gang\n" |
| :"big-endian gang\n"); |
| |
| err = read_dva (dva, endian, data, zio_gb, SPA_GANGBLOCKSIZE); |
| if (err) |
| { |
| grub_free (zio_gb); |
| return err; |
| } |
| |
| /* XXX */ |
| /* self checksuming the gang block header */ |
| ZIO_SET_CHECKSUM (&zc, DVA_GET_VDEV (dva), |
| dva_get_offset (dva, endian), bp->blk_birth, 0); |
| err = zio_checksum_verify (zc, ZIO_CHECKSUM_GANG_HEADER, endian, |
| (char *) zio_gb, SPA_GANGBLOCKSIZE); |
| if (err) |
| { |
| grub_free (zio_gb); |
| return err; |
| } |
| |
| endian = (grub_zfs_to_cpu64 (bp->blk_prop, endian) >> 63) & 1; |
| |
| for (i = 0; i < SPA_GBH_NBLKPTRS; i++) |
| { |
| if (BP_IS_HOLE(&zio_gb->zg_blkptr[i])) |
| continue; |
| |
| err = zio_read_data (&zio_gb->zg_blkptr[i], endian, buf, data); |
| if (err) |
| { |
| grub_free (zio_gb); |
| return err; |
| } |
| buf = (char *) buf + get_psize (&zio_gb->zg_blkptr[i], endian); |
| } |
| grub_free (zio_gb); |
| return GRUB_ERR_NONE; |
| } |
| |
| /* |
| * Read in a block of raw data to buf. |
| */ |
| static grub_err_t |
| zio_read_data (blkptr_t * bp, grub_zfs_endian_t endian, void *buf, |
| struct grub_zfs_data *data) |
| { |
| int i, psize; |
| grub_err_t err = GRUB_ERR_NONE; |
| |
| psize = get_psize (bp, endian); |
| |
| /* pick a good dva from the block pointer */ |
| for (i = 0; i < SPA_DVAS_PER_BP; i++) |
| { |
| if (bp->blk_dva[i].dva_word[0] == 0 && bp->blk_dva[i].dva_word[1] == 0) |
| continue; |
| |
| if ((grub_zfs_to_cpu64 (bp->blk_dva[i].dva_word[1], endian)>>63) & 1) |
| err = zio_read_gang (bp, endian, &bp->blk_dva[i], buf, data); |
| else |
| err = read_dva (&bp->blk_dva[i], endian, data, buf, psize); |
| if (!err) |
| return GRUB_ERR_NONE; |
| grub_errno = GRUB_ERR_NONE; |
| } |
| |
| if (!err) |
| err = grub_error (GRUB_ERR_BAD_FS, "couldn't find a valid DVA"); |
| grub_errno = err; |
| |
| return err; |
| } |
| |
| /* |
| * buf must be at least BPE_GET_PSIZE(bp) bytes long (which will never be |
| * more than BPE_PAYLOAD_SIZE bytes). |
| */ |
| static grub_err_t |
| decode_embedded_bp_compressed(const blkptr_t *bp, void *buf) |
| { |
| grub_size_t psize, i; |
| grub_uint8_t *buf8 = buf; |
| grub_uint64_t w = 0; |
| const grub_uint64_t *bp64 = (const grub_uint64_t *)bp; |
| |
| psize = BPE_GET_PSIZE(bp); |
| |
| /* |
| * Decode the words of the block pointer into the byte array. |
| * Low bits of first word are the first byte (little endian). |
| */ |
| for (i = 0; i < psize; i++) |
| { |
| if (i % sizeof (w) == 0) |
| { |
| /* beginning of a word */ |
| w = *bp64; |
| bp64++; |
| if (!BPE_IS_PAYLOADWORD(bp, bp64)) |
| bp64++; |
| } |
| buf8[i] = BF64_GET(w, (i % sizeof (w)) * 8, 8); |
| } |
| return GRUB_ERR_NONE; |
| } |
| |
| /* |
| * Read in a block of data, verify its checksum, decompress if needed, |
| * and put the uncompressed data in buf. |
| */ |
| static grub_err_t |
| zio_read (blkptr_t *bp, grub_zfs_endian_t endian, void **buf, |
| grub_size_t *size, struct grub_zfs_data *data) |
| { |
| grub_size_t lsize, psize; |
| unsigned int comp, encrypted; |
| char *compbuf = NULL; |
| grub_err_t err; |
| zio_cksum_t zc = bp->blk_cksum; |
| grub_uint32_t checksum; |
| |
| *buf = NULL; |
| |
| checksum = (grub_zfs_to_cpu64((bp)->blk_prop, endian) >> 40) & 0xff; |
| comp = (grub_zfs_to_cpu64((bp)->blk_prop, endian)>>32) & 0x7f; |
| encrypted = ((grub_zfs_to_cpu64((bp)->blk_prop, endian) >> 60) & 3); |
| if (BP_IS_EMBEDDED(bp)) |
| { |
| if (BPE_GET_ETYPE(bp) != BP_EMBEDDED_TYPE_DATA) |
| return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, |
| "unsupported embedded BP (type=%u)\n", |
| BPE_GET_ETYPE(bp)); |
| lsize = BPE_GET_LSIZE(bp); |
| psize = BF64_GET_SB(grub_zfs_to_cpu64 ((bp)->blk_prop, endian), 25, 7, 0, 1); |
| } |
| else |
| { |
| lsize = (BP_IS_HOLE(bp) ? 0 : |
| (((grub_zfs_to_cpu64 ((bp)->blk_prop, endian) & 0xffff) + 1) |
| << SPA_MINBLOCKSHIFT)); |
| psize = get_psize (bp, endian); |
| } |
| grub_dprintf("zfs", "zio_read: E %d: size %" PRIdGRUB_SSIZE "/%" |
| PRIdGRUB_SSIZE "\n", (int)BP_IS_EMBEDDED(bp), lsize, psize); |
| |
| if (size) |
| *size = lsize; |
| |
| if (comp >= ZIO_COMPRESS_FUNCTIONS) |
| return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, |
| "compression algorithm %u not supported\n", (unsigned int) comp); |
| |
| if (comp != ZIO_COMPRESS_OFF && decomp_table[comp].decomp_func == NULL) |
| return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, |
| "compression algorithm %s not supported\n", decomp_table[comp].name); |
| |
| if (comp != ZIO_COMPRESS_OFF) |
| /* It's not really necessary to align to 16, just for safety. */ |
| compbuf = grub_malloc (ALIGN_UP (psize, 16)); |
| else |
| compbuf = *buf = grub_malloc (lsize); |
| if (! compbuf) |
| return grub_errno; |
| |
| grub_dprintf ("zfs", "endian = %d\n", endian); |
| if (BP_IS_EMBEDDED(bp)) |
| err = decode_embedded_bp_compressed(bp, compbuf); |
| else |
| { |
| err = zio_read_data (bp, endian, compbuf, data); |
| /* FIXME is it really necessary? */ |
| if (comp != ZIO_COMPRESS_OFF) |
| grub_memset (compbuf + psize, 0, ALIGN_UP (psize, 16) - psize); |
| } |
| if (err) |
| { |
| grub_free (compbuf); |
| *buf = NULL; |
| return err; |
| } |
| |
| if (!BP_IS_EMBEDDED(bp)) |
| { |
| err = zio_checksum_verify (zc, checksum, endian, |
| compbuf, psize); |
| if (err) |
| { |
| grub_dprintf ("zfs", "incorrect checksum\n"); |
| grub_free (compbuf); |
| *buf = NULL; |
| return err; |
| } |
| } |
| |
| if (encrypted) |
| { |
| if (!grub_zfs_decrypt) |
| err = grub_error (GRUB_ERR_BAD_FS, |
| N_("module `%s' isn't loaded"), |
| "zfscrypt"); |
| else |
| { |
| unsigned i, besti = 0; |
| grub_uint64_t bestval = 0; |
| for (i = 0; i < data->subvol.nkeys; i++) |
| if (data->subvol.keyring[i].txg <= grub_zfs_to_cpu64 (bp->blk_birth, |
| endian) |
| && data->subvol.keyring[i].txg > bestval) |
| { |
| besti = i; |
| bestval = data->subvol.keyring[i].txg; |
| } |
| if (bestval == 0) |
| { |
| grub_free (compbuf); |
| *buf = NULL; |
| grub_dprintf ("zfs", "no key for txg %" PRIxGRUB_UINT64_T "\n", |
| grub_zfs_to_cpu64 (bp->blk_birth, |
| endian)); |
| return grub_error (GRUB_ERR_BAD_FS, "no key found in keychain"); |
| } |
| grub_dprintf ("zfs", "using key %u (%" PRIxGRUB_UINT64_T |
| ", %p) for txg %" PRIxGRUB_UINT64_T "\n", |
| besti, data->subvol.keyring[besti].txg, |
| data->subvol.keyring[besti].cipher, |
| grub_zfs_to_cpu64 (bp->blk_birth, |
| endian)); |
| err = grub_zfs_decrypt (data->subvol.keyring[besti].cipher, |
| data->subvol.keyring[besti].algo, |
| &(bp)->blk_dva[encrypted], |
| compbuf, psize, zc.zc_mac, |
| endian); |
| } |
| if (err) |
| { |
| grub_free (compbuf); |
| *buf = NULL; |
| return err; |
| } |
| } |
| |
| if (comp != ZIO_COMPRESS_OFF) |
| { |
| *buf = grub_malloc (lsize); |
| if (!*buf) |
| { |
| grub_free (compbuf); |
| return grub_errno; |
| } |
| |
| err = decomp_table[comp].decomp_func (compbuf, *buf, psize, lsize); |
| grub_free (compbuf); |
| if (err) |
| { |
| grub_free (*buf); |
| *buf = NULL; |
| return err; |
| } |
| } |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| /* |
| * Get the block from a block id. |
| * push the block onto the stack. |
| * |
| */ |
| static grub_err_t |
| dmu_read (dnode_end_t * dn, grub_uint64_t blkid, void **buf, |
| grub_zfs_endian_t *endian_out, struct grub_zfs_data *data) |
| { |
| int level; |
| grub_off_t idx; |
| blkptr_t *bp_array = dn->dn.dn_blkptr; |
| int epbs = dn->dn.dn_indblkshift - SPA_BLKPTRSHIFT; |
| blkptr_t *bp; |
| void *tmpbuf = 0; |
| grub_zfs_endian_t endian; |
| grub_err_t err = GRUB_ERR_NONE; |
| |
| bp = grub_malloc (sizeof (blkptr_t)); |
| if (!bp) |
| return grub_errno; |
| |
| endian = dn->endian; |
| for (level = dn->dn.dn_nlevels - 1; level >= 0; level--) |
| { |
| grub_dprintf ("zfs", "endian = %d\n", endian); |
| idx = (blkid >> (epbs * level)) & ((1 << epbs) - 1); |
| *bp = bp_array[idx]; |
| if (bp_array != dn->dn.dn_blkptr) |
| { |
| grub_free (bp_array); |
| bp_array = 0; |
| } |
| |
| if (BP_IS_HOLE (bp)) |
| { |
| grub_size_t size = grub_zfs_to_cpu16 (dn->dn.dn_datablkszsec, |
| dn->endian) |
| << SPA_MINBLOCKSHIFT; |
| *buf = grub_malloc (size); |
| if (!*buf) |
| { |
| err = grub_errno; |
| break; |
| } |
| grub_memset (*buf, 0, size); |
| endian = (grub_zfs_to_cpu64 (bp->blk_prop, endian) >> 63) & 1; |
| break; |
| } |
| if (level == 0) |
| { |
| grub_dprintf ("zfs", "endian = %d\n", endian); |
| err = zio_read (bp, endian, buf, 0, data); |
| endian = (grub_zfs_to_cpu64 (bp->blk_prop, endian) >> 63) & 1; |
| break; |
| } |
| grub_dprintf ("zfs", "endian = %d\n", endian); |
| err = zio_read (bp, endian, &tmpbuf, 0, data); |
| endian = (grub_zfs_to_cpu64 (bp->blk_prop, endian) >> 63) & 1; |
| if (err) |
| break; |
| bp_array = tmpbuf; |
| } |
| if (bp_array != dn->dn.dn_blkptr) |
| grub_free (bp_array); |
| if (endian_out) |
| *endian_out = endian; |
| |
| grub_free (bp); |
| return err; |
| } |
| |
| /* |
| * mzap_lookup: Looks up property described by "name" and returns the value |
| * in "value". |
| */ |
| static grub_err_t |
| mzap_lookup (mzap_phys_t * zapobj, grub_zfs_endian_t endian, |
| grub_uint32_t objsize, const char *name, grub_uint64_t * value, |
| int case_insensitive) |
| { |
| grub_uint32_t i, chunks; |
| mzap_ent_phys_t *mzap_ent = zapobj->mz_chunk; |
| |
| if (objsize < MZAP_ENT_LEN) |
| return grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), name); |
| chunks = objsize / MZAP_ENT_LEN - 1; |
| for (i = 0; i < chunks; i++) |
| { |
| if (case_insensitive ? (grub_strcasecmp (mzap_ent[i].mze_name, name) == 0) |
| : (grub_strcmp (mzap_ent[i].mze_name, name) == 0)) |
| { |
| *value = grub_zfs_to_cpu64 (mzap_ent[i].mze_value, endian); |
| return GRUB_ERR_NONE; |
| } |
| } |
| |
| return grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), name); |
| } |
| |
| static int |
| mzap_iterate (mzap_phys_t * zapobj, grub_zfs_endian_t endian, int objsize, |
| int (*hook) (const char *name, grub_uint64_t val, |
| struct grub_zfs_dir_ctx *ctx), |
| struct grub_zfs_dir_ctx *ctx) |
| { |
| int i, chunks; |
| mzap_ent_phys_t *mzap_ent = zapobj->mz_chunk; |
| |
| chunks = objsize / MZAP_ENT_LEN - 1; |
| for (i = 0; i < chunks; i++) |
| { |
| grub_dprintf ("zfs", "zap: name = %s, value = %llx, cd = %x\n", |
| mzap_ent[i].mze_name, (long long)mzap_ent[i].mze_value, |
| (int)mzap_ent[i].mze_cd); |
| if (hook (mzap_ent[i].mze_name, |
| grub_zfs_to_cpu64 (mzap_ent[i].mze_value, endian), ctx)) |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static grub_uint64_t |
| zap_hash (grub_uint64_t salt, const char *name, |
| int case_insensitive) |
| { |
| static grub_uint64_t table[256]; |
| const grub_uint8_t *cp; |
| grub_uint8_t c; |
| grub_uint64_t crc = salt; |
| |
| if (table[128] == 0) |
| { |
| grub_uint64_t *ct; |
| int i, j; |
| for (i = 0; i < 256; i++) |
| { |
| for (ct = table + i, *ct = i, j = 8; j > 0; j--) |
| *ct = (*ct >> 1) ^ (-(*ct & 1) & ZFS_CRC64_POLY); |
| } |
| } |
| |
| if (case_insensitive) |
| for (cp = (const grub_uint8_t *) name; (c = *cp) != '\0'; cp++) |
| crc = (crc >> 8) ^ table[(crc ^ grub_toupper (c)) & 0xFF]; |
| else |
| for (cp = (const grub_uint8_t *) name; (c = *cp) != '\0'; cp++) |
| crc = (crc >> 8) ^ table[(crc ^ c) & 0xFF]; |
| |
| /* |
| * Only use 28 bits, since we need 4 bits in the cookie for the |
| * collision differentiator. We MUST use the high bits, since |
| * those are the onces that we first pay attention to when |
| * chosing the bucket. |
| */ |
| crc &= ~((1ULL << (64 - ZAP_HASHBITS)) - 1); |
| |
| return crc; |
| } |
| |
| /* |
| * Only to be used on 8-bit arrays. |
| * array_len is actual len in bytes (not encoded le_value_length). |
| * buf is null-terminated. |
| */ |
| |
| static inline int |
| name_cmp (const char *s1, const char *s2, grub_size_t n, |
| int case_insensitive) |
| { |
| const char *t1 = (const char *) s1; |
| const char *t2 = (const char *) s2; |
| |
| if (!case_insensitive) |
| return grub_memcmp (t1, t2, n); |
| |
| while (n--) |
| { |
| if (grub_toupper (*t1) != grub_toupper (*t2)) |
| return (int) grub_toupper (*t1) - (int) grub_toupper (*t2); |
| |
| t1++; |
| t2++; |
| } |
| |
| return 0; |
| } |
| |
| /* XXX */ |
| static int |
| zap_leaf_array_equal (zap_leaf_phys_t * l, grub_zfs_endian_t endian, |
| int blksft, int chunk, grub_size_t array_len, |
| const char *buf, int case_insensitive) |
| { |
| grub_size_t bseen = 0; |
| |
| while (bseen < array_len) |
| { |
| struct zap_leaf_array *la = &ZAP_LEAF_CHUNK (l, blksft, chunk)->l_array; |
| grub_size_t toread = array_len - bseen; |
| |
| if (toread > ZAP_LEAF_ARRAY_BYTES) |
| toread = ZAP_LEAF_ARRAY_BYTES; |
| |
| if (chunk >= ZAP_LEAF_NUMCHUNKS (blksft)) |
| return 0; |
| |
| if (name_cmp ((char *) la->la_array, buf + bseen, toread, |
| case_insensitive) != 0) |
| break; |
| chunk = grub_zfs_to_cpu16 (la->la_next, endian); |
| bseen += toread; |
| } |
| return (bseen == array_len); |
| } |
| |
| /* XXX */ |
| static grub_err_t |
| zap_leaf_array_get (zap_leaf_phys_t * l, grub_zfs_endian_t endian, int blksft, |
| int chunk, grub_size_t array_len, char *buf) |
| { |
| grub_size_t bseen = 0; |
| |
| while (bseen < array_len) |
| { |
| struct zap_leaf_array *la = &ZAP_LEAF_CHUNK (l, blksft, chunk)->l_array; |
| grub_size_t toread = array_len - bseen; |
| |
| if (toread > ZAP_LEAF_ARRAY_BYTES) |
| toread = ZAP_LEAF_ARRAY_BYTES; |
| |
| if (chunk >= ZAP_LEAF_NUMCHUNKS (blksft)) |
| /* Don't use grub_error because this error is to be ignored. */ |
| return GRUB_ERR_BAD_FS; |
| |
| grub_memcpy (buf + bseen,la->la_array, toread); |
| chunk = grub_zfs_to_cpu16 (la->la_next, endian); |
| bseen += toread; |
| } |
| return GRUB_ERR_NONE; |
| } |
| |
| |
| /* |
| * Given a zap_leaf_phys_t, walk thru the zap leaf chunks to get the |
| * value for the property "name". |
| * |
| */ |
| /* XXX */ |
| static grub_err_t |
| zap_leaf_lookup (zap_leaf_phys_t * l, grub_zfs_endian_t endian, |
| int blksft, grub_uint64_t h, |
| const char *name, grub_uint64_t * value, |
| int case_insensitive) |
| { |
| grub_uint16_t chunk; |
| struct zap_leaf_entry *le; |
| |
| /* Verify if this is a valid leaf block */ |
| if (grub_zfs_to_cpu64 (l->l_hdr.lh_block_type, endian) != ZBT_LEAF) |
| return grub_error (GRUB_ERR_BAD_FS, "invalid leaf type"); |
| if (grub_zfs_to_cpu32 (l->l_hdr.lh_magic, endian) != ZAP_LEAF_MAGIC) |
| return grub_error (GRUB_ERR_BAD_FS, "invalid leaf magic"); |
| |
| for (chunk = grub_zfs_to_cpu16 (l->l_hash[LEAF_HASH (blksft, h, l)], endian); |
| chunk != CHAIN_END; chunk = grub_zfs_to_cpu16 (le->le_next, endian)) |
| { |
| |
| if (chunk >= ZAP_LEAF_NUMCHUNKS (blksft)) |
| return grub_error (GRUB_ERR_BAD_FS, "invalid chunk number"); |
| |
| le = ZAP_LEAF_ENTRY (l, blksft, chunk); |
| |
| /* Verify the chunk entry */ |
| if (le->le_type != ZAP_CHUNK_ENTRY) |
| return grub_error (GRUB_ERR_BAD_FS, "invalid chunk entry"); |
| |
| if (grub_zfs_to_cpu64 (le->le_hash,endian) != h) |
| continue; |
| |
| grub_dprintf ("zfs", "fzap: length %d\n", (int) le->le_name_length); |
| |
| if (zap_leaf_array_equal (l, endian, blksft, |
| grub_zfs_to_cpu16 (le->le_name_chunk,endian), |
| grub_zfs_to_cpu16 (le->le_name_length, endian), |
| name, case_insensitive)) |
| { |
| struct zap_leaf_array *la; |
| |
| if (le->le_int_size != 8 || grub_zfs_to_cpu16 (le->le_value_length, |
| endian) != 1) |
| return grub_error (GRUB_ERR_BAD_FS, "invalid leaf chunk entry"); |
| |
| /* get the uint64_t property value */ |
| la = &ZAP_LEAF_CHUNK (l, blksft, le->le_value_chunk)->l_array; |
| |
| *value = grub_be_to_cpu64 (la->la_array64); |
| |
| return GRUB_ERR_NONE; |
| } |
| } |
| |
| return grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("file `%s' not found"), name); |
| } |
| |
| |
| /* Verify if this is a fat zap header block */ |
| static grub_err_t |
| zap_verify (zap_phys_t *zap, grub_zfs_endian_t endian) |
| { |
| if (grub_zfs_to_cpu64 (zap->zap_magic, endian) != (grub_uint64_t) ZAP_MAGIC) |
| return grub_error (GRUB_ERR_BAD_FS, "bad ZAP magic"); |
| |
| if (zap->zap_salt == 0) |
| return grub_error (GRUB_ERR_BAD_FS, "bad ZAP salt"); |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| /* |
| * Fat ZAP lookup |
| * |
| */ |
| /* XXX */ |
| static grub_err_t |
| fzap_lookup (dnode_end_t * zap_dnode, zap_phys_t * zap, |
| const char *name, grub_uint64_t * value, |
| struct grub_zfs_data *data, int case_insensitive) |
| { |
| void *l; |
| grub_uint64_t hash, idx, blkid; |
| int blksft = zfs_log2 (grub_zfs_to_cpu16 (zap_dnode->dn.dn_datablkszsec, |
| zap_dnode->endian) << DNODE_SHIFT); |
| grub_err_t err; |
| grub_zfs_endian_t leafendian; |
| |
| err = zap_verify (zap, zap_dnode->endian); |
| if (err) |
| return err; |
| |
| hash = zap_hash (zap->zap_salt, name, case_insensitive); |
| |
| /* get block id from index */ |
| if (zap->zap_ptrtbl.zt_numblks != 0) |
| return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, |
| "external pointer tables not supported"); |
| idx = ZAP_HASH_IDX (hash, zap->zap_ptrtbl.zt_shift); |
| blkid = grub_zfs_to_cpu64 (((grub_uint64_t *) zap)[idx + (1 << (blksft - 3 - 1))], zap_dnode->endian); |
| |
| /* Get the leaf block */ |
| if ((1U << blksft) < sizeof (zap_leaf_phys_t)) |
| return grub_error (GRUB_ERR_BAD_FS, "ZAP leaf is too small"); |
| err = dmu_read (zap_dnode, blkid, &l, &leafendian, data); |
| if (err) |
| return err; |
| |
| err = zap_leaf_lookup (l, leafendian, blksft, hash, name, value, |
| case_insensitive); |
| grub_free (l); |
| return err; |
| } |
| |
| /* XXX */ |
| static int |
| fzap_iterate (dnode_end_t * zap_dnode, zap_phys_t * zap, |
| grub_size_t name_elem_length, |
| int (*hook) (const void *name, grub_size_t name_length, |
| const void *val_in, |
| grub_size_t nelem, grub_size_t elemsize, |
| void *data), |
| void *hook_data, struct grub_zfs_data *data) |
| { |
| zap_leaf_phys_t *l; |
| void *l_in; |
| grub_uint64_t idx, idx2, blkid; |
| grub_uint16_t chunk; |
| int blksft = zfs_log2 (grub_zfs_to_cpu16 (zap_dnode->dn.dn_datablkszsec, |
| zap_dnode->endian) << DNODE_SHIFT); |
| grub_err_t err; |
| grub_zfs_endian_t endian; |
| |
| if (zap_verify (zap, zap_dnode->endian)) |
| return 0; |
| |
| /* get block id from index */ |
| if (zap->zap_ptrtbl.zt_numblks != 0) |
| { |
| grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, |
| "external pointer tables not supported"); |
| return 0; |
| } |
| /* Get the leaf block */ |
| if ((1U << blksft) < sizeof (zap_leaf_phys_t)) |
| { |
| grub_error (GRUB_ERR_BAD_FS, "ZAP leaf is too small"); |
| return 0; |
| } |
| for (idx = 0; idx < (1ULL << zap->zap_ptrtbl.zt_shift); idx++) |
| { |
| blkid = grub_zfs_to_cpu64 (((grub_uint64_t *) zap)[idx + (1 << (blksft - 3 - 1))], |
| zap_dnode->endian); |
| |
| for (idx2 = 0; idx2 < idx; idx2++) |
| if (blkid == grub_zfs_to_cpu64 (((grub_uint64_t *) zap)[idx2 + (1 << (blksft - 3 - 1))], |
| zap_dnode->endian)) |
| break; |
| if (idx2 != idx) |
| continue; |
| |
| err = dmu_read (zap_dnode, blkid, &l_in, &endian, data); |
| l = l_in; |
| if (err) |
| { |
| grub_errno = GRUB_ERR_NONE; |
| continue; |
| } |
| |
| /* Verify if this is a valid leaf block */ |
| if (grub_zfs_to_cpu64 (l->l_hdr.lh_block_type, endian) != ZBT_LEAF) |
| { |
| grub_free (l); |
| continue; |
| } |
| if (grub_zfs_to_cpu32 (l->l_hdr.lh_magic, endian) != ZAP_LEAF_MAGIC) |
| { |
| grub_free (l); |
| continue; |
| } |
| |
| for (chunk = 0; chunk < ZAP_LEAF_NUMCHUNKS (blksft); chunk++) |
| { |
| char *buf; |
| struct zap_leaf_entry *le; |
| char *val; |
| grub_size_t val_length; |
| le = ZAP_LEAF_ENTRY (l, blksft, chunk); |
| |
| /* Verify the chunk entry */ |
| if (le->le_type != ZAP_CHUNK_ENTRY) |
| continue; |
| |
| buf = grub_malloc (grub_zfs_to_cpu16 (le->le_name_length, endian) |
| * name_elem_length + 1); |
| if (zap_leaf_array_get (l, endian, blksft, |
| grub_zfs_to_cpu16 (le->le_name_chunk, |
| endian), |
| grub_zfs_to_cpu16 (le->le_name_length, |
| endian) |
| * name_elem_length, buf)) |
| { |
| grub_free (buf); |
| continue; |
| } |
| buf[le->le_name_length * name_elem_length] = 0; |
| |
| val_length = ((int) le->le_value_length |
| * (int) le->le_int_size); |
| val = grub_malloc (grub_zfs_to_cpu16 (val_length, endian)); |
| if (zap_leaf_array_get (l, endian, blksft, |
| grub_zfs_to_cpu16 (le->le_value_chunk, |
| endian), |
| val_length, val)) |
| { |
| grub_free (buf); |
| grub_free (val); |
| continue; |
| } |
| |
| if (hook (buf, le->le_name_length, |
| val, le->le_value_length, le->le_int_size, hook_data)) |
| { |
| grub_free (l); |
| return 1; |
| } |
| grub_free (buf); |
| grub_free (val); |
| } |
| grub_free (l); |
| } |
| return 0; |
| } |
| |
| /* |
| * Read in the data of a zap object and find the value for a matching |
| * property name. |
| * |
| */ |
| static grub_err_t |
| zap_lookup (dnode_end_t * zap_dnode, const char *name, grub_uint64_t *val, |
| struct grub_zfs_data *data, int case_insensitive) |
| { |
| grub_uint64_t block_type; |
| grub_uint32_t size; |
| void *zapbuf; |
| grub_err_t err; |
| grub_zfs_endian_t endian; |
| |
| grub_dprintf ("zfs", "looking for '%s'\n", name); |
| |
| /* Read in the first block of the zap object data. */ |
| size = (grub_uint32_t) grub_zfs_to_cpu16 (zap_dnode->dn.dn_datablkszsec, |
| zap_dnode->endian) << SPA_MINBLOCKSHIFT; |
| err = dmu_read (zap_dnode, 0, &zapbuf, &endian, data); |
| if (err) |
| return err; |
| block_type = grub_zfs_to_cpu64 (*((grub_uint64_t *) zapbuf), endian); |
| |
| grub_dprintf ("zfs", "zap read\n"); |
| |
| if (block_type == ZBT_MICRO) |
| { |
| grub_dprintf ("zfs", "micro zap\n"); |
| err = mzap_lookup (zapbuf, endian, size, name, val, |
| case_insensitive); |
| grub_dprintf ("zfs", "returned %d\n", err); |
| grub_free (zapbuf); |
| return err; |
| } |
| else if (block_type == ZBT_HEADER) |
| { |
| grub_dprintf ("zfs", "fat zap\n"); |
| /* this is a fat zap */ |
| err = fzap_lookup (zap_dnode, zapbuf, name, val, data, |
| case_insensitive); |
| grub_dprintf ("zfs", "returned %d\n", err); |
| grub_free (zapbuf); |
| return err; |
| } |
| |
| return grub_error (GRUB_ERR_BAD_FS, "unknown ZAP type"); |
| } |
| |
| /* Context for zap_iterate_u64. */ |
| struct zap_iterate_u64_ctx |
| { |
| int (*hook) (const char *, grub_uint64_t, struct grub_zfs_dir_ctx *); |
| struct grub_zfs_dir_ctx *dir_ctx; |
| }; |
| |
| /* Helper for zap_iterate_u64. */ |
| static int |
| zap_iterate_u64_transform (const void *name, |
| grub_size_t namelen __attribute__ ((unused)), |
| const void *val_in, |
| grub_size_t nelem, |
| grub_size_t elemsize, |
| void *data) |
| { |
| struct zap_iterate_u64_ctx *ctx = data; |
| |
| if (elemsize != sizeof (grub_uint64_t) || nelem != 1) |
| return 0; |
| return ctx->hook (name, grub_be_to_cpu64 (*(const grub_uint64_t *) val_in), |
| ctx->dir_ctx); |
| } |
| |
| static int |
| zap_iterate_u64 (dnode_end_t * zap_dnode, |
| int (*hook) (const char *name, grub_uint64_t val, |
| struct grub_zfs_dir_ctx *ctx), |
| struct grub_zfs_data *data, struct grub_zfs_dir_ctx *ctx) |
| { |
| grub_uint64_t block_type; |
| int size; |
| void *zapbuf; |
| grub_err_t err; |
| int ret; |
| grub_zfs_endian_t endian; |
| |
| /* Read in the first block of the zap object data. */ |
| size = grub_zfs_to_cpu16 (zap_dnode->dn.dn_datablkszsec, zap_dnode->endian) << SPA_MINBLOCKSHIFT; |
| err = dmu_read (zap_dnode, 0, &zapbuf, &endian, data); |
| if (err) |
| return 0; |
| block_type = grub_zfs_to_cpu64 (*((grub_uint64_t *) zapbuf), endian); |
| |
| grub_dprintf ("zfs", "zap iterate\n"); |
| |
| if (block_type == ZBT_MICRO) |
| { |
| grub_dprintf ("zfs", "micro zap\n"); |
| ret = mzap_iterate (zapbuf, endian, size, hook, ctx); |
| grub_free (zapbuf); |
| return ret; |
| } |
| else if (block_type == ZBT_HEADER) |
| { |
| struct zap_iterate_u64_ctx transform_ctx = { |
| .hook = hook, |
| .dir_ctx = ctx |
| }; |
| |
| grub_dprintf ("zfs", "fat zap\n"); |
| /* this is a fat zap */ |
| ret = fzap_iterate (zap_dnode, zapbuf, 1, |
| zap_iterate_u64_transform, &transform_ctx, data); |
| grub_free (zapbuf); |
| return ret; |
| } |
| grub_error (GRUB_ERR_BAD_FS, "unknown ZAP type"); |
| return 0; |
| } |
| |
| static int |
| zap_iterate (dnode_end_t * zap_dnode, |
| grub_size_t nameelemlen, |
| int (*hook) (const void *name, grub_size_t namelen, |
| const void *val_in, |
| grub_size_t nelem, grub_size_t elemsize, |
| void *data), |
| void *hook_data, struct grub_zfs_data *data) |
| { |
| grub_uint64_t block_type; |
| void *zapbuf; |
| grub_err_t err; |
| int ret; |
| grub_zfs_endian_t endian; |
| |
| /* Read in the first block of the zap object data. */ |
| err = dmu_read (zap_dnode, 0, &zapbuf, &endian, data); |
| if (err) |
| return 0; |
| block_type = grub_zfs_to_cpu64 (*((grub_uint64_t *) zapbuf), endian); |
| |
| grub_dprintf ("zfs", "zap iterate\n"); |
| |
| if (block_type == ZBT_MICRO) |
| { |
| grub_error (GRUB_ERR_BAD_FS, "micro ZAP where FAT ZAP expected"); |
| return 0; |
| } |
| if (block_type == ZBT_HEADER) |
| { |
| grub_dprintf ("zfs", "fat zap\n"); |
| /* this is a fat zap */ |
| ret = fzap_iterate (zap_dnode, zapbuf, nameelemlen, hook, hook_data, |
| data); |
| grub_free (zapbuf); |
| return ret; |
| } |
| grub_error (GRUB_ERR_BAD_FS, "unknown ZAP type"); |
| return 0; |
| } |
| |
| |
| /* |
| * Get the dnode of an object number from the metadnode of an object set. |
| * |
| * Input |
| * mdn - metadnode to get the object dnode |
| * objnum - object number for the object dnode |
| * buf - data buffer that holds the returning dnode |
| */ |
| static grub_err_t |
| dnode_get (dnode_end_t * mdn, grub_uint64_t objnum, grub_uint8_t type, |
| dnode_end_t * buf, struct grub_zfs_data *data) |
| { |
| grub_uint64_t blkid, blksz; /* the block id this object dnode is in */ |
| int epbs; /* shift of number of dnodes in a block */ |
| int idx; /* index within a block */ |
| void *dnbuf; |
| grub_err_t err; |
| grub_zfs_endian_t endian; |
| |
| blksz = grub_zfs_to_cpu16 (mdn->dn.dn_datablkszsec, |
| mdn->endian) << SPA_MINBLOCKSHIFT; |
| epbs = zfs_log2 (blksz) - DNODE_SHIFT; |
| blkid = objnum >> epbs; |
| idx = objnum & ((1 << epbs) - 1); |
| |
| if (data->dnode_buf != NULL && grub_memcmp (data->dnode_mdn, mdn, |
| sizeof (*mdn)) == 0 |
| && objnum >= data->dnode_start && objnum < data->dnode_end) |
| { |
| grub_memmove (&(buf->dn), &(data->dnode_buf)[idx], DNODE_SIZE); |
| buf->endian = data->dnode_endian; |
| if (type && buf->dn.dn_type != type) |
| return grub_error(GRUB_ERR_BAD_FS, "incorrect dnode type"); |
| return GRUB_ERR_NONE; |
| } |
| |
| grub_dprintf ("zfs", "endian = %d, blkid=%llx\n", mdn->endian, |
| (unsigned long long) blkid); |
| err = dmu_read (mdn, blkid, &dnbuf, &endian, data); |
| if (err) |
| return err; |
| grub_dprintf ("zfs", "alive\n"); |
| |
| grub_free (data->dnode_buf); |
| grub_free (data->dnode_mdn); |
| data->dnode_mdn = grub_malloc (sizeof (*mdn)); |
| if (! data->dnode_mdn) |
| { |
| grub_errno = GRUB_ERR_NONE; |
| data->dnode_buf = 0; |
| } |
| else |
| { |
| grub_memcpy (data->dnode_mdn, mdn, sizeof (*mdn)); |
| data->dnode_buf = dnbuf; |
| data->dnode_start = blkid << epbs; |
| data->dnode_end = (blkid + 1) << epbs; |
| data->dnode_endian = endian; |
| } |
| |
| grub_memmove (&(buf->dn), (dnode_phys_t *) dnbuf + idx, DNODE_SIZE); |
| buf->endian = endian; |
| if (type && buf->dn.dn_type != type) |
| return grub_error(GRUB_ERR_BAD_FS, "incorrect dnode type"); |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| #pragma GCC diagnostic ignored "-Wstrict-aliasing" |
| |
| /* |
| * Get the file dnode for a given file name where mdn is the meta dnode |
| * for this ZFS object set. When found, place the file dnode in dn. |
| * The 'path' argument will be mangled. |
| * |
| */ |
| static grub_err_t |
| dnode_get_path (struct subvolume *subvol, const char *path_in, dnode_end_t *dn, |
| struct grub_zfs_data *data) |
| { |
| grub_uint64_t objnum, version; |
| char *cname, ch; |
| grub_err_t err = GRUB_ERR_NONE; |
| char *path, *path_buf; |
| struct dnode_chain |
| { |
| struct dnode_chain *next; |
| dnode_end_t dn; |
| }; |
| struct dnode_chain *dnode_path = 0, *dn_new, *root; |
| |
| dn_new = grub_malloc (sizeof (*dn_new)); |
| if (! dn_new) |
| return grub_errno; |
| dn_new->next = 0; |
| dnode_path = root = dn_new; |
| |
| err = dnode_get (&subvol->mdn, MASTER_NODE_OBJ, DMU_OT_MASTER_NODE, |
| &(dnode_path->dn), data); |
| if (err) |
| { |
| grub_free (dn_new); |
| return err; |
| } |
| |
| err = zap_lookup (&(dnode_path->dn), ZPL_VERSION_STR, &version, |
| data, 0); |
| if (err) |
| { |
| grub_free (dn_new); |
| return err; |
| } |
| |
| if (version > ZPL_VERSION) |
| { |
| grub_free (dn_new); |
| return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "too new ZPL version"); |
| } |
| |
| err = zap_lookup (&(dnode_path->dn), "casesensitivity", |
| &subvol->case_insensitive, |
| data, 0); |
| if (err == GRUB_ERR_FILE_NOT_FOUND) |
| { |
| grub_errno = GRUB_ERR_NONE; |
| subvol->case_insensitive = 0; |
| } |
| |
| err = zap_lookup (&(dnode_path->dn), ZFS_ROOT_OBJ, &objnum, data, 0); |
| if (err) |
| { |
| grub_free (dn_new); |
| return err; |
| } |
| |
| err = dnode_get (&subvol->mdn, objnum, 0, &(dnode_path->dn), data); |
| if (err) |
| { |
| grub_free (dn_new); |
| return err; |
| } |
| |
| path = path_buf = grub_strdup (path_in); |
| if (!path_buf) |
| { |
| grub_free (dn_new); |
| return grub_errno; |
| } |
| |
| while (1) |
| { |
| /* skip leading slashes */ |
| while (*path == '/') |
| path++; |
| if (!*path) |
| break; |
| /* get the next component name */ |
| cname = path; |
| while (*path && *path != '/') |
| path++; |
| /* Skip dot. */ |
| if (cname + 1 == path && cname[0] == '.') |
| continue; |
| /* Handle double dot. */ |
| if (cname + 2 == path && cname[0] == '.' && cname[1] == '.') |
| { |
| if (dn_new->next) |
| { |
| dn_new = dnode_path; |
| dnode_path = dn_new->next; |
| grub_free (dn_new); |
| } |
| else |
| { |
| err = grub_error (GRUB_ERR_FILE_NOT_FOUND, |
| "can't resolve .."); |
| break; |
| } |
| continue; |
| } |
| |
| ch = *path; |
| *path = 0; /* ensure null termination */ |
| |
| if (dnode_path->dn.dn.dn_type != DMU_OT_DIRECTORY_CONTENTS) |
| { |
| grub_free (path_buf); |
| return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a directory")); |
| } |
| err = zap_lookup (&(dnode_path->dn), cname, &objnum, |
| data, subvol->case_insensitive); |
| if (err) |
| break; |
| |
| dn_new = grub_malloc (sizeof (*dn_new)); |
| if (! dn_new) |
| { |
| err = grub_errno; |
| break; |
| } |
| dn_new->next = dnode_path; |
| dnode_path = dn_new; |
| |
| objnum = ZFS_DIRENT_OBJ (objnum); |
| err = dnode_get (&subvol->mdn, objnum, 0, &(dnode_path->dn), data); |
| if (err) |
| break; |
| |
| *path = ch; |
| if (dnode_path->dn.dn.dn_bonustype == DMU_OT_ZNODE |
| && ((grub_zfs_to_cpu64(((znode_phys_t *) DN_BONUS (&dnode_path->dn.dn))->zp_mode, dnode_path->dn.endian) >> 12) & 0xf) == 0xa) |
| { |
| char *sym_value; |
| grub_size_t sym_sz; |
| int free_symval = 0; |
| char *oldpath = path, *oldpathbuf = path_buf; |
| sym_value = ((char *) DN_BONUS (&dnode_path->dn.dn) + sizeof (struct znode_phys)); |
| |
| sym_sz = grub_zfs_to_cpu64 (((znode_phys_t *) DN_BONUS (&dnode_path->dn.dn))->zp_size, dnode_path->dn.endian); |
| |
| if (dnode_path->dn.dn.dn_flags & 1) |
| { |
| grub_size_t block; |
| grub_size_t blksz; |
| blksz = (grub_zfs_to_cpu16 (dnode_path->dn.dn.dn_datablkszsec, |
| dnode_path->dn.endian) |
| << SPA_MINBLOCKSHIFT); |
| |
| if (blksz == 0) |
| return grub_error(GRUB_ERR_BAD_FS, "0-sized block"); |
| |
| sym_value = grub_malloc (sym_sz); |
| if (!sym_value) |
| return grub_errno; |
| for (block = 0; block < (sym_sz + blksz - 1) / blksz; block++) |
| { |
| void *t; |
| grub_size_t movesize; |
| |
| err = dmu_read (&(dnode_path->dn), block, &t, 0, data); |
| if (err) |
| { |
| grub_free (sym_value); |
| return err; |
| } |
| |
| movesize = sym_sz - block * blksz; |
| if (movesize > blksz) |
| movesize = blksz; |
| |
| grub_memcpy (sym_value + block * blksz, t, movesize); |
| grub_free (t); |
| } |
| free_symval = 1; |
| } |
| path = path_buf = grub_malloc (sym_sz + grub_strlen (oldpath) + 1); |
| if (!path_buf) |
| { |
| grub_free (oldpathbuf); |
| if (free_symval) |
| grub_free (sym_value); |
| return grub_errno; |
| } |
| grub_memcpy (path, sym_value, sym_sz); |
| if (free_symval) |
| grub_free (sym_value); |
| path [sym_sz] = 0; |
| grub_memcpy (path + grub_strlen (path), oldpath, |
| grub_strlen (oldpath) + 1); |
| |
| grub_free (oldpathbuf); |
| if (path[0] != '/') |
| { |
| dn_new = dnode_path; |
| dnode_path = dn_new->next; |
| grub_free (dn_new); |
| } |
| else while (dnode_path != root) |
| { |
| dn_new = dnode_path; |
| dnode_path = dn_new->next; |
| grub_free (dn_new); |
| } |
| } |
| if (dnode_path->dn.dn.dn_bonustype == DMU_OT_SA) |
| { |
| void *sahdrp; |
| int hdrsize; |
| |
| if (dnode_path->dn.dn.dn_bonuslen != 0) |
| { |
| sahdrp = DN_BONUS (&dnode_path->dn.dn); |
| } |
| else if (dnode_path->dn.dn.dn_flags & DNODE_FLAG_SPILL_BLKPTR) |
| { |
| blkptr_t *bp = &dnode_path->dn.dn.dn_spill; |
| |
| err = zio_read (bp, dnode_path->dn.endian, &sahdrp, NULL, data); |
| if (err) |
| return err; |
| } |
| else |
| { |
| return grub_error (GRUB_ERR_BAD_FS, "filesystem is corrupt"); |
| } |
| |
| hdrsize = SA_HDR_SIZE (((sa_hdr_phys_t *) sahdrp)); |
| |
| if (((grub_zfs_to_cpu64 (grub_get_unaligned64 ((char *) sahdrp |
| + hdrsize |
| + SA_TYPE_OFFSET), |
| dnode_path->dn.endian) >> 12) & 0xf) == 0xa) |
| { |
| char *sym_value = (char *) sahdrp + hdrsize + SA_SYMLINK_OFFSET; |
| grub_size_t sym_sz = |
| grub_zfs_to_cpu64 (grub_get_unaligned64 ((char *) sahdrp |
| + hdrsize |
| + SA_SIZE_OFFSET), |
| dnode_path->dn.endian); |
| char *oldpath = path, *oldpathbuf = path_buf; |
| path = path_buf = grub_malloc (sym_sz + grub_strlen (oldpath) + 1); |
| if (!path_buf) |
| { |
| grub_free (oldpathbuf); |
| return grub_errno; |
| } |
| grub_memcpy (path, sym_value, sym_sz); |
| path [sym_sz] = 0; |
| grub_memcpy (path + grub_strlen (path), oldpath, |
| grub_strlen (oldpath) + 1); |
| |
| grub_free (oldpathbuf); |
| if (path[0] != '/') |
| { |
| dn_new = dnode_path; |
| dnode_path = dn_new->next; |
| grub_free (dn_new); |
| } |
| else while (dnode_path != root) |
| { |
| dn_new = dnode_path; |
| dnode_path = dn_new->next; |
| grub_free (dn_new); |
| } |
| } |
| } |
| } |
| |
| if (!err) |
| grub_memcpy (dn, &(dnode_path->dn), sizeof (*dn)); |
| |
| while (dnode_path) |
| { |
| dn_new = dnode_path->next; |
| grub_free (dnode_path); |
| dnode_path = dn_new; |
| } |
| grub_free (path_buf); |
| return err; |
| } |
| |
| #if 0 |
| /* |
| * Get the default 'bootfs' property value from the rootpool. |
| * |
| */ |
| static grub_err_t |
| get_default_bootfsobj (dnode_phys_t * mosmdn, grub_uint64_t * obj, |
| struct grub_zfs_data *data) |
| { |
| grub_uint64_t objnum = 0; |
| dnode_phys_t *dn; |
| if (!dn) |
| return grub_errno; |
| |
| if ((grub_errno = dnode_get (mosmdn, DMU_POOL_DIRECTORY_OBJECT, |
| DMU_OT_OBJECT_DIRECTORY, dn, data))) |
| { |
| grub_free (dn); |
| return (grub_errno); |
| } |
| |
| /* |
| * find the object number for 'pool_props', and get the dnode |
| * of the 'pool_props'. |
| */ |
| if (zap_lookup (dn, DMU_POOL_PROPS, &objnum, data)) |
| { |
| grub_free (dn); |
| return (GRUB_ERR_BAD_FS); |
| } |
| if ((grub_errno = dnode_get (mosmdn, objnum, DMU_OT_POOL_PROPS, dn, data))) |
| { |
| grub_free (dn); |
| return (grub_errno); |
| } |
| if (zap_lookup (dn, ZPOOL_PROP_BOOTFS, &objnum, data)) |
| { |
| grub_free (dn); |
| return (GRUB_ERR_BAD_FS); |
| } |
| |
| if (!objnum) |
| { |
| grub_free (dn); |
| return (GRUB_ERR_BAD_FS); |
| } |
| |
| *obj = objnum; |
| return (0); |
| } |
| #endif |
| /* |
| * Given a MOS metadnode, get the metadnode of a given filesystem name (fsname), |
| * e.g. pool/rootfs, or a given object number (obj), e.g. the object number |
| * of pool/rootfs. |
| * |
| * If no fsname and no obj are given, return the DSL_DIR metadnode. |
| * If fsname is given, return its metadnode and its matching object number. |
| * If only obj is given, return the metadnode for this object number. |
| * |
| */ |
| static grub_err_t |
| get_filesystem_dnode (dnode_end_t * mosmdn, char *fsname, |
| dnode_end_t * mdn, struct grub_zfs_data *data) |
| { |
| grub_uint64_t objnum; |
| grub_err_t err; |
| |
| grub_dprintf ("zfs", "endian = %d\n", mosmdn->endian); |
| |
| err = dnode_get (mosmdn, DMU_POOL_DIRECTORY_OBJECT, |
| DMU_OT_OBJECT_DIRECTORY, mdn, data); |
| if (err) |
| return err; |
| |
| grub_dprintf ("zfs", "alive\n"); |
| |
| err = zap_lookup (mdn, DMU_POOL_ROOT_DATASET, &objnum, data, 0); |
| if (err) |
| return err; |
| |
| grub_dprintf ("zfs", "alive\n"); |
| |
| err = dnode_get (mosmdn, objnum, 0, mdn, data); |
| if (err) |
| return err; |
| |
| grub_dprintf ("zfs", "alive\n"); |
| |
| while (*fsname) |
| { |
| grub_uint64_t childobj; |
| char *cname, ch; |
| |
| while (*fsname == '/') |
| fsname++; |
| |
| if (! *fsname || *fsname == '@') |
| break; |
| |
| cname = fsname; |
| while (*fsname && *fsname != '/') |
| fsname++; |
| ch = *fsname; |
| *fsname = 0; |
| |
| childobj = grub_zfs_to_cpu64 ((((dsl_dir_phys_t *) DN_BONUS (&mdn->dn)))->dd_child_dir_zapobj, mdn->endian); |
| err = dnode_get (mosmdn, childobj, |
| DMU_OT_DSL_DIR_CHILD_MAP, mdn, data); |
| if (err) |
| return err; |
| |
| err = zap_lookup (mdn, cname, &objnum, data, 0); |
| if (err) |
| return err; |
| |
| err = dnode_get (mosmdn, objnum, 0, mdn, data); |
| if (err) |
| return err; |
| |
| *fsname = ch; |
| } |
| return GRUB_ERR_NONE; |
| } |
| |
| static grub_err_t |
| make_mdn (dnode_end_t * mdn, struct grub_zfs_data *data) |
| { |
| void *osp; |
| blkptr_t *bp; |
| grub_size_t ospsize = 0; |
| grub_err_t err; |
| |
| grub_dprintf ("zfs", "endian = %d\n", mdn->endian); |
| |
| bp = &(((dsl_dataset_phys_t *) DN_BONUS (&mdn->dn))->ds_bp); |
| err = zio_read (bp, mdn->endian, &osp, &ospsize, data); |
| if (err) |
| return err; |
| if (ospsize < OBJSET_PHYS_SIZE_V14) |
| { |
| grub_free (osp); |
| return grub_error (GRUB_ERR_BAD_FS, "too small osp"); |
| } |
| |
| mdn->endian = (grub_zfs_to_cpu64 (bp->blk_prop, mdn->endian)>>63) & 1; |
| grub_memmove ((char *) &(mdn->dn), |
| (char *) &((objset_phys_t *) osp)->os_meta_dnode, DNODE_SIZE); |
| grub_free (osp); |
| return GRUB_ERR_NONE; |
| } |
| |
| /* Context for dnode_get_fullpath. */ |
| struct dnode_get_fullpath_ctx |
| { |
| struct subvolume *subvol; |
| grub_uint64_t salt; |
| int keyn; |
| }; |
| |
| /* Helper for dnode_get_fullpath. */ |
| static int |
| count_zap_keys (const void *name __attribute__ ((unused)), |
| grub_size_t namelen __attribute__ ((unused)), |
| const void *val_in __attribute__ ((unused)), |
| grub_size_t nelem __attribute__ ((unused)), |
| grub_size_t elemsize __attribute__ ((unused)), |
| void *data) |
| { |
| struct dnode_get_fullpath_ctx *ctx = data; |
| |
| ctx->subvol->nkeys++; |
| return 0; |
| } |
| |
| /* Helper for dnode_get_fullpath. */ |
| static int |
| load_zap_key (const void *name, grub_size_t namelen, const void *val_in, |
| grub_size_t nelem, grub_size_t elemsize, void *data) |
| { |
| struct dnode_get_fullpath_ctx *ctx = data; |
| |
| if (namelen != 1) |
| { |
| grub_dprintf ("zfs", "Unexpected key index size %" PRIuGRUB_SIZE "\n", |
| namelen); |
| return 0; |
| } |
| |
| if (elemsize != 1) |
| { |
| grub_dprintf ("zfs", "Unexpected key element size %" PRIuGRUB_SIZE "\n", |
| elemsize); |
| return 0; |
| } |
| |
| ctx->subvol->keyring[ctx->keyn].txg = |
| grub_be_to_cpu64 (*(grub_uint64_t *) name); |
| ctx->subvol->keyring[ctx->keyn].algo = |
| grub_le_to_cpu64 (*(grub_uint64_t *) val_in); |
| ctx->subvol->keyring[ctx->keyn].cipher = |
| grub_zfs_load_key (val_in, nelem, ctx->salt, |
| ctx->subvol->keyring[ctx->keyn].algo); |
| ctx->keyn++; |
| return 0; |
| } |
| |
| static grub_err_t |
| dnode_get_fullpath (const char *fullpath, struct subvolume *subvol, |
| dnode_end_t * dn, int *isfs, |
| struct grub_zfs_data *data) |
| { |
| char *fsname, *snapname; |
| const char *ptr_at, *filename; |
| grub_uint64_t headobj; |
| grub_uint64_t keychainobj; |
| grub_err_t err; |
| |
| ptr_at = grub_strchr (fullpath, '@'); |
| if (! ptr_at) |
| { |
| *isfs = 1; |
| filename = 0; |
| snapname = 0; |
| fsname = grub_strdup (fullpath); |
| } |
| else |
| { |
| const char *ptr_slash = grub_strchr (ptr_at, '/'); |
| |
| *isfs = 0; |
| fsname = grub_malloc (ptr_at - fullpath + 1); |
| if (!fsname) |
| return grub_errno; |
| grub_memcpy (fsname, fullpath, ptr_at - fullpath); |
| fsname[ptr_at - fullpath] = 0; |
| if (ptr_at[1] && ptr_at[1] != '/') |
| { |
| snapname = grub_malloc (ptr_slash - ptr_at); |
| if (!snapname) |
| { |
| grub_free (fsname); |
| return grub_errno; |
| } |
| grub_memcpy (snapname, ptr_at + 1, ptr_slash - ptr_at - 1); |
| snapname[ptr_slash - ptr_at - 1] = 0; |
| } |
| else |
| snapname = 0; |
| if (ptr_slash) |
| filename = ptr_slash; |
| else |
| filename = "/"; |
| grub_dprintf ("zfs", "fsname = '%s' snapname='%s' filename = '%s'\n", |
| fsname, snapname, filename); |
| } |
| grub_dprintf ("zfs", "alive\n"); |
| err = get_filesystem_dnode (&(data->mos), fsname, dn, data); |
| if (err) |
| { |
| grub_free (fsname); |
| grub_free (snapname); |
| return err; |
| } |
| |
| grub_dprintf ("zfs", "alive\n"); |
| |
| headobj = grub_zfs_to_cpu64 (((dsl_dir_phys_t *) DN_BONUS (&dn->dn))->dd_head_dataset_obj, dn->endian); |
| |
| grub_dprintf ("zfs", "endian = %d\n", subvol->mdn.endian); |
| |
| err = dnode_get (&(data->mos), headobj, 0, &subvol->mdn, data); |
| if (err) |
| { |
| grub_free (fsname); |
| grub_free (snapname); |
| return err; |
| } |
| grub_dprintf ("zfs", "endian = %d\n", subvol->mdn.endian); |
| |
| keychainobj = grub_zfs_to_cpu64 (((dsl_dir_phys_t *) DN_BONUS (&dn->dn))->keychain, dn->endian); |
| if (grub_zfs_load_key && keychainobj) |
| { |
| struct dnode_get_fullpath_ctx ctx = { |
| .subvol = subvol, |
| .keyn = 0 |
| }; |
| dnode_end_t keychain_dn, props_dn; |
| grub_uint64_t propsobj; |
| propsobj = grub_zfs_to_cpu64 (((dsl_dir_phys_t *) DN_BONUS (&dn->dn))->dd_props_zapobj, dn->endian); |
| |
| err = dnode_get (&(data->mos), propsobj, DMU_OT_DSL_PROPS, |
| &props_dn, data); |
| if (err) |
| { |
| grub_free (fsname); |
| grub_free (snapname); |
| return err; |
| } |
| |
| err = zap_lookup (&props_dn, "salt", &ctx.salt, data, 0); |
| if (err == GRUB_ERR_FILE_NOT_FOUND) |
| { |
| err = 0; |
| grub_errno = 0; |
| ctx.salt = 0; |
| } |
| if (err) |
| { |
| grub_dprintf ("zfs", "failed here\n"); |
| return err; |
| } |
| |
| err = dnode_get (&(data->mos), keychainobj, DMU_OT_DSL_KEYCHAIN, |
| &keychain_dn, data); |
| if (err) |
| { |
| grub_free (fsname); |
| grub_free (snapname); |
| return err; |
| } |
| subvol->nkeys = 0; |
| zap_iterate (&keychain_dn, 8, count_zap_keys, &ctx, data); |
| subvol->keyring = grub_zalloc (subvol->nkeys * sizeof (subvol->keyring[0])); |
| if (!subvol->keyring) |
| { |
| grub_free (fsname); |
| grub_free (snapname); |
| return err; |
| } |
| zap_iterate (&keychain_dn, 8, load_zap_key, &ctx, data); |
| } |
| |
| if (snapname) |
| { |
| grub_uint64_t snapobj; |
| |
| snapobj = grub_zfs_to_cpu64 (((dsl_dataset_phys_t *) DN_BONUS (&subvol->mdn.dn))->ds_snapnames_zapobj, subvol->mdn.endian); |
| |
| err = dnode_get (&(data->mos), snapobj, |
| DMU_OT_DSL_DS_SNAP_MAP, &subvol->mdn, data); |
| if (!err) |
| err = zap_lookup (&subvol->mdn, snapname, &headobj, data, 0); |
| if (!err) |
| err = dnode_get (&(data->mos), headobj, DMU_OT_DSL_DATASET, |
| &subvol->mdn, data); |
| if (err) |
| { |
| grub_free (fsname); |
| grub_free (snapname); |
| return err; |
| } |
| } |
| |
| subvol->obj = headobj; |
| |
| make_mdn (&subvol->mdn, data); |
| |
| grub_dprintf ("zfs", "endian = %d\n", subvol->mdn.endian); |
| |
| if (*isfs) |
| { |
| grub_free (fsname); |
| grub_free (snapname); |
| return GRUB_ERR_NONE; |
| } |
| err = dnode_get_path (subvol, filename, dn, data); |
| grub_free (fsname); |
| grub_free (snapname); |
| return err; |
| } |
| |
| static int |
| nvlist_find_value (const char *nvlist_in, const char *name, |
| int valtype, char **val, |
| grub_size_t *size_out, grub_size_t *nelm_out) |
| { |
| grub_size_t nvp_name_len, name_len = grub_strlen(name); |
| int type; |
| const char *nvpair=NULL,*nvlist=nvlist_in; |
| char *nvp_name; |
| |
| /* Verify if the 1st and 2nd byte in the nvlist are valid. */ |
| /* NOTE: independently of what endianness header announces all |
| subsequent values are big-endian. */ |
| if (nvlist[0] != NV_ENCODE_XDR || (nvlist[1] != NV_LITTLE_ENDIAN |
| && nvlist[1] != NV_BIG_ENDIAN)) |
| { |
| grub_dprintf ("zfs", "incorrect nvlist header\n"); |
| grub_error (GRUB_ERR_BAD_FS, "incorrect nvlist"); |
| return 0; |
| } |
| |
| /* |
| * Loop thru the nvpair list |
| * The XDR representation of an integer is in big-endian byte order. |
| */ |
| while ((nvpair=nvlist_next_nvpair(nvlist,nvpair))) |
| { |
| nvpair_name(nvpair,&nvp_name, &nvp_name_len); |
| type = nvpair_type(nvpair); |
| if (type == valtype |
| && (nvp_name_len == name_len |
| || (nvp_name_len > name_len && nvp_name[name_len] == '\0')) |
| && grub_memcmp (nvp_name, name, name_len) == 0) |
| { |
| return nvpair_value(nvpair,val,size_out,nelm_out); |
| } |
| } |
| return 0; |
| } |
| |
| int |
| grub_zfs_nvlist_lookup_uint64 (const char *nvlist, const char *name, |
| grub_uint64_t * out) |
| { |
| char *nvpair; |
| grub_size_t size; |
| int found; |
| |
| found = nvlist_find_value (nvlist, name, DATA_TYPE_UINT64, &nvpair, &size, 0); |
| if (!found) |
| return 0; |
| if (size < sizeof (grub_uint64_t)) |
| { |
| grub_error (GRUB_ERR_BAD_FS, "invalid uint64"); |
| return 0; |
| } |
| |
| *out = grub_be_to_cpu64 (grub_get_unaligned64 (nvpair)); |
| return 1; |
| } |
| |
| char * |
| grub_zfs_nvlist_lookup_string (const char *nvlist, const char *name) |
| { |
| char *nvpair; |
| char *ret; |
| grub_size_t slen; |
| grub_size_t size; |
| int found; |
| |
| found = nvlist_find_value (nvlist, name, DATA_TYPE_STRING, &nvpair, &size, 0); |
| if (!found) |
| return 0; |
| if (size < 4) |
| { |
| grub_error (GRUB_ERR_BAD_FS, "invalid string"); |
| return 0; |
| } |
| slen = grub_be_to_cpu32 (grub_get_unaligned32 (nvpair)); |
| if (slen > size - 4) |
| slen = size - 4; |
| ret = grub_malloc (slen + 1); |
| if (!ret) |
| return 0; |
| grub_memcpy (ret, nvpair + 4, slen); |
| ret[slen] = 0; |
| return ret; |
| } |
| |
| char * |
| grub_zfs_nvlist_lookup_nvlist (const char *nvlist, const char *name) |
| { |
| char *nvpair; |
| char *ret; |
| grub_size_t size; |
| int found; |
| |
| found = nvlist_find_value (nvlist, name, DATA_TYPE_NVLIST, &nvpair, |
| &size, 0); |
| if (!found) |
| return 0; |
| ret = grub_zalloc (size + 3 * sizeof (grub_uint32_t)); |
| if (!ret) |
| return 0; |
| grub_memcpy (ret, nvlist, sizeof (grub_uint32_t)); |
| |
| grub_memcpy (ret + sizeof (grub_uint32_t), nvpair, size); |
| return ret; |
| } |
| |
| int |
| grub_zfs_nvlist_lookup_nvlist_array_get_nelm (const char *nvlist, |
| const char *name) |
| { |
| char *nvpair; |
| grub_size_t nelm, size; |
| int found; |
| |
| found = nvlist_find_value (nvlist, name, DATA_TYPE_NVLIST_ARRAY, &nvpair, |
| &size, &nelm); |
| if (! found) |
| return -1; |
| return nelm; |
| } |
| |
| static int |
| get_nvlist_size (const char *beg, const char *limit) |
| { |
| const char *ptr; |
| grub_uint32_t encode_size; |
| |
| ptr = beg + 8; |
| |
| while (ptr < limit |
| && (encode_size = grub_be_to_cpu32 (grub_get_unaligned32 (ptr)))) |
| ptr += encode_size; /* goto the next nvpair */ |
| ptr += 8; |
| return (ptr > limit) ? -1 : (ptr - beg); |
| } |
| |
| char * |
| grub_zfs_nvlist_lookup_nvlist_array (const char *nvlist, const char *name, |
| grub_size_t index) |
| { |
| char *nvpair, *nvpairptr; |
| int found; |
| char *ret; |
| grub_size_t size; |
| unsigned i; |
| grub_size_t nelm; |
| int elemsize = 0; |
| |
| found = nvlist_find_value (nvlist, name, DATA_TYPE_NVLIST_ARRAY, &nvpair, |
| &size, &nelm); |
| if (!found) |
| return 0; |
| if (index >= nelm) |
| { |
| grub_error (GRUB_ERR_OUT_OF_RANGE, "trying to lookup past nvlist array"); |
| return 0; |
| } |
| |
| nvpairptr = nvpair; |
| |
| for (i = 0; i < index; i++) |
| { |
| int r; |
| r = get_nvlist_size (nvpairptr, nvpair + size); |
| |
| if (r < 0) |
| { |
| grub_error (GRUB_ERR_BAD_FS, "incorrect nvlist array"); |
| return NULL; |
| } |
| nvpairptr += r; |
| } |
| |
| elemsize = get_nvlist_size (nvpairptr, nvpair + size); |
| |
| if (elemsize < 0) |
| { |
| grub_error (GRUB_ERR_BAD_FS, "incorrect nvlist array"); |
| return 0; |
| } |
| |
| ret = grub_zalloc (elemsize + sizeof (grub_uint32_t)); |
| if (!ret) |
| return 0; |
| grub_memcpy (ret, nvlist, sizeof (grub_uint32_t)); |
| |
| grub_memcpy (ret + sizeof (grub_uint32_t), nvpairptr, elemsize); |
| return ret; |
| } |
| |
| static void |
| unmount_device (struct grub_zfs_device_desc *desc) |
| { |
| unsigned i; |
| switch (desc->type) |
| { |
| case DEVICE_LEAF: |
| if (!desc->original && desc->dev) |
| grub_device_close (desc->dev); |
| return; |
| case DEVICE_RAIDZ: |
| case DEVICE_MIRROR: |
| for (i = 0; i < desc->n_children; i++) |
| unmount_device (&desc->children[i]); |
| grub_free (desc->children); |
| return; |
| } |
| } |
| |
| static void |
| zfs_unmount (struct grub_zfs_data *data) |
| { |
| unsigned i; |
| for (i = 0; i < data->n_devices_attached; i++) |
| unmount_device (&data->devices_attached[i]); |
| grub_free (data->devices_attached); |
| grub_free (data->dnode_buf); |
| grub_free (data->dnode_mdn); |
| grub_free (data->file_buf); |
| for (i = 0; i < data->subvol.nkeys; i++) |
| grub_crypto_cipher_close (data->subvol.keyring[i].cipher); |
| grub_free (data->subvol.keyring); |
| grub_free (data); |
| } |
| |
| /* |
| * zfs_mount() locates a valid uberblock of the root pool and read in its MOS |
| * to the memory address MOS. |
| * |
| */ |
| static struct grub_zfs_data * |
| zfs_mount (grub_device_t dev) |
| { |
| struct grub_zfs_data *data = 0; |
| grub_err_t err; |
| void *osp = 0; |
| grub_size_t ospsize; |
| grub_zfs_endian_t ub_endian = GRUB_ZFS_UNKNOWN_ENDIAN; |
| uberblock_t *ub; |
| int inserted; |
| |
| if (! dev->disk) |
| { |
| grub_error (GRUB_ERR_BAD_DEVICE, "not a disk"); |
| return 0; |
| } |
| |
| data = grub_zalloc (sizeof (*data)); |
| if (!data) |
| return 0; |
| #if 0 |
| /* if it's our first time here, zero the best uberblock out */ |
| if (data->best_drive == 0 && data->best_part == 0 && find_best_root) |
| grub_memset (¤t_uberblock, 0, sizeof (uberblock_t)); |
| #endif |
| |
| data->n_devices_allocated = 16; |
| data->devices_attached = grub_malloc (sizeof (data->devices_attached[0]) |
| * data->n_devices_allocated); |
| data->n_devices_attached = 0; |
| err = scan_disk (dev, data, 1, &inserted); |
| if (err) |
| { |
| zfs_unmount (data); |
| return NULL; |
| } |
| |
| ub = &(data->current_uberblock); |
| ub_endian = (grub_zfs_to_cpu64 (ub->ub_magic, |
| GRUB_ZFS_LITTLE_ENDIAN) == UBERBLOCK_MAGIC |
| ? GRUB_ZFS_LITTLE_ENDIAN : GRUB_ZFS_BIG_ENDIAN); |
| |
| err = zio_read (&ub->ub_rootbp, ub_endian, |
| &osp, &ospsize, data); |
| if (err) |
| { |
| zfs_unmount (data); |
| return NULL; |
| } |
| |
| if (ospsize < OBJSET_PHYS_SIZE_V14) |
| { |
| grub_error (GRUB_ERR_BAD_FS, "OSP too small"); |
| grub_free (osp); |
| zfs_unmount (data); |
| return NULL; |
| } |
| |
| if (ub->ub_version >= SPA_VERSION_FEATURES && |
| check_mos_features(&((objset_phys_t *) osp)->os_meta_dnode,ub_endian, |
| data) != 0) |
| { |
| grub_error (GRUB_ERR_BAD_FS, "Unsupported features in pool"); |
| grub_free (osp); |
| zfs_unmount (data); |
| return NULL; |
| } |
| |
| /* Got the MOS. Save it at the memory addr MOS. */ |
| grub_memmove (&(data->mos.dn), &((objset_phys_t *) osp)->os_meta_dnode, |
| DNODE_SIZE); |
| data->mos.endian = (grub_zfs_to_cpu64 (ub->ub_rootbp.blk_prop, |
| ub_endian) >> 63) & 1; |
| grub_free (osp); |
| |
| return data; |
| } |
| |
| grub_err_t |
| grub_zfs_fetch_nvlist (grub_device_t dev, char **nvlist) |
| { |
| struct grub_zfs_data *zfs; |
| grub_err_t err; |
| |
| zfs = zfs_mount (dev); |
| if (!zfs) |
| return grub_errno; |
| err = zfs_fetch_nvlist (zfs->device_original, nvlist); |
| zfs_unmount (zfs); |
| return err; |
| } |
| |
| static grub_err_t |
| zfs_label (grub_device_t device, char **label) |
| { |
| char *nvlist; |
| grub_err_t err; |
| struct grub_zfs_data *data; |
| |
| data = zfs_mount (device); |
| if (! data) |
| return grub_errno; |
| |
| err = zfs_fetch_nvlist (data->device_original, &nvlist); |
| if (err) |
| { |
| zfs_unmount (data); |
| return err; |
| } |
| |
| *label = grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_POOL_NAME); |
| grub_free (nvlist); |
| zfs_unmount (data); |
| return grub_errno; |
| } |
| |
| static grub_err_t |
| zfs_uuid (grub_device_t device, char **uuid) |
| { |
| struct grub_zfs_data *data; |
| |
| *uuid = 0; |
| |
| data = zfs_mount (device); |
| if (! data) |
| return grub_errno; |
| |
| *uuid = grub_xasprintf ("%016llx", (long long unsigned) data->guid); |
| zfs_unmount (data); |
| if (! *uuid) |
| return grub_errno; |
| return GRUB_ERR_NONE; |
| } |
| |
| static grub_err_t |
| zfs_mtime (grub_device_t device, grub_int32_t *mt) |
| { |
| struct grub_zfs_data *data; |
| grub_zfs_endian_t ub_endian = GRUB_ZFS_UNKNOWN_ENDIAN; |
| uberblock_t *ub; |
| |
| *mt = 0; |
| |
| data = zfs_mount (device); |
| if (! data) |
| return grub_errno; |
| |
| ub = &(data->current_uberblock); |
| ub_endian = (grub_zfs_to_cpu64 (ub->ub_magic, |
| GRUB_ZFS_LITTLE_ENDIAN) == UBERBLOCK_MAGIC |
| ? GRUB_ZFS_LITTLE_ENDIAN : GRUB_ZFS_BIG_ENDIAN); |
| |
| *mt = grub_zfs_to_cpu64 (ub->ub_timestamp, ub_endian); |
| zfs_unmount (data); |
| return GRUB_ERR_NONE; |
| } |
| |
| /* |
| * zfs_open() locates a file in the rootpool by following the |
| * MOS and places the dnode of the file in the memory address DNODE. |
| */ |
| static grub_err_t |
| grub_zfs_open (struct grub_file *file, const char *fsfilename) |
| { |
| struct grub_zfs_data *data; |
| grub_err_t err; |
| int isfs; |
| |
| data = zfs_mount (file->device); |
| if (! data) |
| return grub_errno; |
| |
| err = dnode_get_fullpath (fsfilename, &(data->subvol), |
| &(data->dnode), &isfs, data); |
| if (err) |
| { |
| zfs_unmount (data); |
| return err; |
| } |
| |
| if (isfs) |
| { |
| zfs_unmount (data); |
| return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("missing `%c' symbol"), '@'); |
| } |
| |
| /* We found the dnode for this file. Verify if it is a plain file. */ |
| if (data->dnode.dn.dn_type != DMU_OT_PLAIN_FILE_CONTENTS) |
| { |
| zfs_unmount (data); |
| return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a regular file")); |
| } |
| |
| /* get the file size and set the file position to 0 */ |
| |
| /* |
| * For DMU_OT_SA we will need to locate the SIZE attribute |
| * attribute, which could be either in the bonus buffer |
| * or the "spill" block. |
| */ |
| if (data->dnode.dn.dn_bonustype == DMU_OT_SA) |
| { |
| void *sahdrp; |
| int hdrsize; |
| |
| if (data->dnode.dn.dn_bonuslen != 0) |
| { |
| sahdrp = (sa_hdr_phys_t *) DN_BONUS (&data->dnode.dn); |
| } |
| else if (data->dnode.dn.dn_flags & DNODE_FLAG_SPILL_BLKPTR) |
| { |
| blkptr_t *bp = &data->dnode.dn.dn_spill; |
| |
| err = zio_read (bp, data->dnode.endian, &sahdrp, NULL, data); |
| if (err) |
| return err; |
| } |
| else |
| { |
| return grub_error (GRUB_ERR_BAD_FS, "filesystem is corrupt"); |
| } |
| |
| hdrsize = SA_HDR_SIZE (((sa_hdr_phys_t *) sahdrp)); |
| file->size = grub_zfs_to_cpu64 (grub_get_unaligned64 ((char *) sahdrp + hdrsize + SA_SIZE_OFFSET), data->dnode.endian); |
| } |
| else if (data->dnode.dn.dn_bonustype == DMU_OT_ZNODE) |
| { |
| file->size = grub_zfs_to_cpu64 (((znode_phys_t *) DN_BONUS (&data->dnode.dn))->zp_size, data->dnode.endian); |
| } |
| else |
| return grub_error (GRUB_ERR_BAD_FS, "bad bonus type"); |
| |
| file->data = data; |
| file->offset = 0; |
| |
| #ifndef GRUB_UTIL |
| grub_dl_ref (my_mod); |
| #endif |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| static grub_ssize_t |
| grub_zfs_read (grub_file_t file, char *buf, grub_size_t len) |
| { |
| struct grub_zfs_data *data = (struct grub_zfs_data *) file->data; |
| grub_size_t blksz, movesize; |
| grub_size_t length; |
| grub_size_t read; |
| grub_err_t err; |
| |
| /* |
| * If offset is in memory, move it into the buffer provided and return. |
| */ |
| if (file->offset >= data->file_start |
| && file->offset + len <= data->file_end) |
| { |
| grub_memmove (buf, data->file_buf + file->offset - data->file_start, |
| len); |
| return len; |
| } |
| |
| blksz = grub_zfs_to_cpu16 (data->dnode.dn.dn_datablkszsec, |
| data->dnode.endian) << SPA_MINBLOCKSHIFT; |
| |
| if (blksz == 0) |
| { |
| grub_error (GRUB_ERR_BAD_FS, "0-sized block"); |
| return -1; |
| } |
| |
| /* |
| * Entire Dnode is too big to fit into the space available. We |
| * will need to read it in chunks. This could be optimized to |
| * read in as large a chunk as there is space available, but for |
| * now, this only reads in one data block at a time. |
| */ |
| length = len; |
| read = 0; |
| while (length) |
| { |
| void *t; |
| /* |
| * Find requested blkid and the offset within that block. |
| */ |
| grub_uint64_t blkid = grub_divmod64 (file->offset + read, blksz, 0); |
| grub_free (data->file_buf); |
| data->file_buf = 0; |
| |
| err = dmu_read (&(data->dnode), blkid, &t, |
| 0, data); |
| data->file_buf = t; |
| if (err) |
| { |
| data->file_buf = NULL; |
| data->file_start = data->file_end = 0; |
| return -1; |
| } |
| |
| data->file_start = blkid * blksz; |
| data->file_end = data->file_start + blksz; |
| |
| movesize = data->file_end - file->offset - read; |
| if (movesize > length) |
| movesize = length; |
| |
| grub_memmove (buf, data->file_buf + file->offset + read |
| - data->file_start, movesize); |
| buf += movesize; |
| length -= movesize; |
| read += movesize; |
| } |
| |
| return len; |
| } |
| |
| static grub_err_t |
| grub_zfs_close (grub_file_t file) |
| { |
| zfs_unmount ((struct grub_zfs_data *) file->data); |
| |
| #ifndef GRUB_UTIL |
| grub_dl_unref (my_mod); |
| #endif |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| grub_err_t |
| grub_zfs_getmdnobj (grub_device_t dev, const char *fsfilename, |
| grub_uint64_t *mdnobj) |
| { |
| struct grub_zfs_data *data; |
| grub_err_t err; |
| int isfs; |
| |
| data = zfs_mount (dev); |
| if (! data) |
| return grub_errno; |
| |
| err = dnode_get_fullpath (fsfilename, &(data->subvol), |
| &(data->dnode), &isfs, data); |
| *mdnobj = data->subvol.obj; |
| zfs_unmount (data); |
| return err; |
| } |
| |
| static grub_err_t |
| fill_fs_info (struct grub_dirhook_info *info, |
| dnode_end_t mdn, struct grub_zfs_data *data) |
| { |
| grub_err_t err; |
| dnode_end_t dn; |
| grub_uint64_t objnum; |
| grub_uint64_t headobj; |
| |
| grub_memset (info, 0, sizeof (*info)); |
| |
| info->dir = 1; |
| |
| if (mdn.dn.dn_type == DMU_OT_DSL_DIR) |
| { |
| headobj = grub_zfs_to_cpu64 (((dsl_dir_phys_t *) DN_BONUS (&mdn.dn))->dd_head_dataset_obj, mdn.endian); |
| |
| err = dnode_get (&(data->mos), headobj, 0, &mdn, data); |
| if (err) |
| { |
| grub_dprintf ("zfs", "failed here\n"); |
| return err; |
| } |
| } |
| err = make_mdn (&mdn, data); |
| if (err) |
| return err; |
| err = dnode_get (&mdn, MASTER_NODE_OBJ, DMU_OT_MASTER_NODE, |
| &dn, data); |
| if (err) |
| { |
| grub_dprintf ("zfs", "failed here\n"); |
| return err; |
| } |
| |
| err = zap_lookup (&dn, ZFS_ROOT_OBJ, &objnum, data, 0); |
| if (err) |
| { |
| grub_dprintf ("zfs", "failed here\n"); |
| return err; |
| } |
| |
| err = dnode_get (&mdn, objnum, 0, &dn, data); |
| if (err) |
| { |
| grub_dprintf ("zfs", "failed here\n"); |
| return err; |
| } |
| |
| if (dn.dn.dn_bonustype == DMU_OT_SA) |
| { |
| void *sahdrp; |
| int hdrsize; |
| |
| if (dn.dn.dn_bonuslen != 0) |
| { |
| sahdrp = (sa_hdr_phys_t *) DN_BONUS (&dn.dn); |
| } |
| else if (dn.dn.dn_flags & DNODE_FLAG_SPILL_BLKPTR) |
| { |
| blkptr_t *bp = &dn.dn.dn_spill; |
| |
| err = zio_read (bp, dn.endian, &sahdrp, NULL, data); |
| if (err) |
| return err; |
| } |
| else |
| { |
| grub_error (GRUB_ERR_BAD_FS, "filesystem is corrupt"); |
| return grub_errno; |
| } |
| |
| hdrsize = SA_HDR_SIZE (((sa_hdr_phys_t *) sahdrp)); |
| info->mtimeset = 1; |
| info->mtime = grub_zfs_to_cpu64 (grub_get_unaligned64 ((char *) sahdrp + hdrsize + SA_MTIME_OFFSET), dn.endian); |
| } |
| |
| if (dn.dn.dn_bonustype == DMU_OT_ZNODE) |
| { |
| info->mtimeset = 1; |
| info->mtime = grub_zfs_to_cpu64 (((znode_phys_t *) DN_BONUS (&dn.dn))->zp_mtime[0], dn.endian); |
| } |
| return 0; |
| } |
| |
| /* Helper for grub_zfs_dir. */ |
| static int |
| iterate_zap (const char *name, grub_uint64_t val, struct grub_zfs_dir_ctx *ctx) |
| { |
| grub_err_t err; |
| struct grub_dirhook_info info; |
| |
| dnode_end_t dn; |
| grub_memset (&info, 0, sizeof (info)); |
| |
| err = dnode_get (&(ctx->data->subvol.mdn), val, 0, &dn, ctx->data); |
| if (err) |
| { |
| grub_print_error (); |
| return 0; |
| } |
| |
| if (dn.dn.dn_bonustype == DMU_OT_SA) |
| { |
| void *sahdrp; |
| int hdrsize; |
| |
| if (dn.dn.dn_bonuslen != 0) |
| { |
| sahdrp = (sa_hdr_phys_t *) DN_BONUS (&ctx->data->dnode.dn); |
| } |
| else if (dn.dn.dn_flags & DNODE_FLAG_SPILL_BLKPTR) |
| { |
| blkptr_t *bp = &dn.dn.dn_spill; |
| |
| err = zio_read (bp, dn.endian, &sahdrp, NULL, ctx->data); |
| if (err) |
| { |
| grub_print_error (); |
| return 0; |
| } |
| } |
| else |
| { |
| grub_error (GRUB_ERR_BAD_FS, "filesystem is corrupt"); |
| grub_print_error (); |
| return 0; |
| } |
| |
| hdrsize = SA_HDR_SIZE (((sa_hdr_phys_t *) sahdrp)); |
| info.mtimeset = 1; |
| info.mtime = grub_zfs_to_cpu64 (grub_get_unaligned64 ((char *) sahdrp + hdrsize + SA_MTIME_OFFSET), dn.endian); |
| info.case_insensitive = ctx->data->subvol.case_insensitive; |
| } |
| |
| if (dn.dn.dn_bonustype == DMU_OT_ZNODE) |
| { |
| info.mtimeset = 1; |
| info.mtime = grub_zfs_to_cpu64 (((znode_phys_t *) DN_BONUS (&dn.dn))->zp_mtime[0], |
| dn.endian); |
| } |
| info.dir = (dn.dn.dn_type == DMU_OT_DIRECTORY_CONTENTS); |
| grub_dprintf ("zfs", "type=%d, name=%s\n", |
| (int)dn.dn.dn_type, (char *)name); |
| return ctx->hook (name, &info, ctx->hook_data); |
| } |
| |
| /* Helper for grub_zfs_dir. */ |
| static int |
| iterate_zap_fs (const char *name, grub_uint64_t val, |
| struct grub_zfs_dir_ctx *ctx) |
| { |
| grub_err_t err; |
| struct grub_dirhook_info info; |
| |
| dnode_end_t mdn; |
| err = dnode_get (&(ctx->data->mos), val, 0, &mdn, ctx->data); |
| if (err) |
| { |
| grub_errno = 0; |
| return 0; |
| } |
| if (mdn.dn.dn_type != DMU_OT_DSL_DIR) |
| return 0; |
| |
| err = fill_fs_info (&info, mdn, ctx->data); |
| if (err) |
| { |
| grub_errno = 0; |
| return 0; |
| } |
| return ctx->hook (name, &info, ctx->hook_data); |
| } |
| |
| /* Helper for grub_zfs_dir. */ |
| static int |
| iterate_zap_snap (const char *name, grub_uint64_t val, |
| struct grub_zfs_dir_ctx *ctx) |
| { |
| grub_err_t err; |
| struct grub_dirhook_info info; |
| char *name2; |
| int ret; |
| |
| dnode_end_t mdn; |
| |
| err = dnode_get (&(ctx->data->mos), val, 0, &mdn, ctx->data); |
| if (err) |
| { |
| grub_errno = 0; |
| return 0; |
| } |
| |
| if (mdn.dn.dn_type != DMU_OT_DSL_DATASET) |
| return 0; |
| |
| err = fill_fs_info (&info, mdn, ctx->data); |
| if (err) |
| { |
| grub_errno = 0; |
| return 0; |
| } |
| |
| name2 = grub_malloc (grub_strlen (name) + 2); |
| name2[0] = '@'; |
| grub_memcpy (name2 + 1, name, grub_strlen (name) + 1); |
| ret = ctx->hook (name2, &info, ctx->hook_data); |
| grub_free (name2); |
| return ret; |
| } |
| |
| static grub_err_t |
| grub_zfs_dir (grub_device_t device, const char *path, |
| grub_fs_dir_hook_t hook, void *hook_data) |
| { |
| struct grub_zfs_dir_ctx ctx = { |
| .hook = hook, |
| .hook_data = hook_data |
| }; |
| struct grub_zfs_data *data; |
| grub_err_t err; |
| int isfs; |
| |
| data = zfs_mount (device); |
| if (! data) |
| return grub_errno; |
| err = dnode_get_fullpath (path, &(data->subvol), &(data->dnode), &isfs, data); |
| if (err) |
| { |
| zfs_unmount (data); |
| return err; |
| } |
| ctx.data = data; |
| |
| if (isfs) |
| { |
| grub_uint64_t childobj, headobj; |
| grub_uint64_t snapobj; |
| dnode_end_t dn; |
| struct grub_dirhook_info info; |
| |
| err = fill_fs_info (&info, data->dnode, data); |
| if (err) |
| { |
| zfs_unmount (data); |
| return err; |
| } |
| if (hook ("@", &info, hook_data)) |
| { |
| zfs_unmount (data); |
| return GRUB_ERR_NONE; |
| } |
| |
| childobj = grub_zfs_to_cpu64 (((dsl_dir_phys_t *) DN_BONUS (&data->dnode.dn))->dd_child_dir_zapobj, data->dnode.endian); |
| headobj = grub_zfs_to_cpu64 (((dsl_dir_phys_t *) DN_BONUS (&data->dnode.dn))->dd_head_dataset_obj, data->dnode.endian); |
| err = dnode_get (&(data->mos), childobj, |
| DMU_OT_DSL_DIR_CHILD_MAP, &dn, data); |
| if (err) |
| { |
| zfs_unmount (data); |
| return err; |
| } |
| |
| zap_iterate_u64 (&dn, iterate_zap_fs, data, &ctx); |
| |
| err = dnode_get (&(data->mos), headobj, DMU_OT_DSL_DATASET, &dn, data); |
| if (err) |
| { |
| zfs_unmount (data); |
| return err; |
| } |
| |
| snapobj = grub_zfs_to_cpu64 (((dsl_dataset_phys_t *) DN_BONUS (&dn.dn))->ds_snapnames_zapobj, dn.endian); |
| |
| err = dnode_get (&(data->mos), snapobj, |
| DMU_OT_DSL_DS_SNAP_MAP, &dn, data); |
| if (err) |
| { |
| zfs_unmount (data); |
| return err; |
| } |
| |
| zap_iterate_u64 (&dn, iterate_zap_snap, data, &ctx); |
| } |
| else |
| { |
| if (data->dnode.dn.dn_type != DMU_OT_DIRECTORY_CONTENTS) |
| { |
| zfs_unmount (data); |
| return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a directory")); |
| } |
| zap_iterate_u64 (&(data->dnode), iterate_zap, data, &ctx); |
| } |
| zfs_unmount (data); |
| return grub_errno; |
| } |
| |
| static int |
| check_feature (const char *name, grub_uint64_t val, |
| struct grub_zfs_dir_ctx *ctx __attribute__((unused))) |
| { |
| int i; |
| if (val == 0) |
| return 0; |
| if (name[0] == 0) |
| return 0; |
| for (i = 0; spa_feature_names[i] != NULL; i++) |
| if (grub_strcmp (name, spa_feature_names[i]) == 0) |
| return 0; |
| return 1; |
| } |
| |
| /* |
| * Checks whether the MOS features that are active are supported by this |
| * (GRUB's) implementation of ZFS. |
| * |
| * Return: |
| * 0: Success. |
| * errnum: Failure. |
| */ |
| |
| static grub_err_t |
| check_mos_features(dnode_phys_t *mosmdn_phys,grub_zfs_endian_t endian,struct grub_zfs_data* data ) |
| { |
| grub_uint64_t objnum; |
| grub_err_t errnum = 0; |
| dnode_end_t dn,mosmdn; |
| mzap_phys_t* mzp; |
| grub_zfs_endian_t endianzap; |
| int size; |
| grub_memmove(&(mosmdn.dn),mosmdn_phys,sizeof(dnode_phys_t)); |
| mosmdn.endian=endian; |
| errnum = dnode_get(&mosmdn, DMU_POOL_DIRECTORY_OBJECT, |
| DMU_OT_OBJECT_DIRECTORY, &dn,data); |
| if (errnum != 0) |
| return errnum; |
| |
| /* |
| * Find the object number for 'features_for_read' and retrieve its |
| * corresponding dnode. Note that we don't check features_for_write |
| * because GRUB is not opening the pool for write. |
| */ |
| errnum = zap_lookup(&dn, DMU_POOL_FEATURES_FOR_READ, &objnum, data,0); |
| if (errnum != 0) |
| return errnum; |
| |
| errnum = dnode_get(&mosmdn, objnum, DMU_OTN_ZAP_METADATA, &dn, data); |
| if (errnum != 0) |
| return errnum; |
| |
| errnum = dmu_read(&dn, 0, (void**)&mzp, &endianzap,data); |
| if (errnum != 0) |
| return errnum; |
| |
| size = grub_zfs_to_cpu16 (dn.dn.dn_datablkszsec, dn.endian) << SPA_MINBLOCKSHIFT; |
| return mzap_iterate (mzp,endianzap, size, check_feature,NULL); |
| } |
| |
| |
| #ifdef GRUB_UTIL |
| static grub_err_t |
| grub_zfs_embed (grub_device_t device __attribute__ ((unused)), |
| unsigned int *nsectors, |
| unsigned int max_nsectors, |
| grub_embed_type_t embed_type, |
| grub_disk_addr_t **sectors) |
| { |
| unsigned i; |
| |
| if (embed_type != GRUB_EMBED_PCBIOS) |
| return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, |
| "ZFS currently supports only PC-BIOS embedding"); |
| |
| if ((VDEV_BOOT_SIZE >> GRUB_DISK_SECTOR_BITS) < *nsectors) |
| return grub_error (GRUB_ERR_OUT_OF_RANGE, |
| N_("your core.img is unusually large. " |
| "It won't fit in the embedding area")); |
| |
| *nsectors = (VDEV_BOOT_SIZE >> GRUB_DISK_SECTOR_BITS); |
| if (*nsectors > max_nsectors) |
| *nsectors = max_nsectors; |
| *sectors = grub_malloc (*nsectors * sizeof (**sectors)); |
| if (!*sectors) |
| return grub_errno; |
| for (i = 0; i < *nsectors; i++) |
| (*sectors)[i] = i + (VDEV_BOOT_OFFSET >> GRUB_DISK_SECTOR_BITS); |
| |
| return GRUB_ERR_NONE; |
| } |
| #endif |
| |
| static struct grub_fs grub_zfs_fs = { |
| .name = "zfs", |
| .dir = grub_zfs_dir, |
| .open = grub_zfs_open, |
| .read = grub_zfs_read, |
| .close = grub_zfs_close, |
| .label = zfs_label, |
| .uuid = zfs_uuid, |
| .mtime = zfs_mtime, |
| #ifdef GRUB_UTIL |
| .embed = grub_zfs_embed, |
| .reserved_first_sector = 1, |
| .blocklist_install = 0, |
| #endif |
| .next = 0 |
| }; |
| |
| GRUB_MOD_INIT (zfs) |
| { |
| COMPILE_TIME_ASSERT (sizeof (zap_leaf_chunk_t) == ZAP_LEAF_CHUNKSIZE); |
| grub_fs_register (&grub_zfs_fs); |
| #ifndef GRUB_UTIL |
| my_mod = mod; |
| #endif |
| } |
| |
| GRUB_MOD_FINI (zfs) |
| { |
| grub_fs_unregister (&grub_zfs_fs); |
| } |