diff --git a/lfs.c b/lfs.c index 81b6f1fa..f5b75e18 100644 --- a/lfs.c +++ b/lfs.c @@ -578,8 +578,19 @@ static int lfsr_bd_erase(lfs_t *lfs, lfs_block_t block) { enum lfsr_tag_type { LFSR_TAG_NULL = 0x0000, - LFSR_TAG_SUPERMAGIC = 0x0003, - LFSR_TAG_SUPERCONFIG = 0x0004, + LFSR_TAG_CONFIG = 0x0000, + LFSR_TAG_MAGIC = 0x0003, + LFSR_TAG_VERSION = 0x0004, + LFSR_TAG_FLAGS = 0x0005, + LFSR_TAG_CKSUMTYPE = 0x0006, + LFSR_TAG_REDUNDTYPE = 0x0007, + LFSR_TAG_BLOCKLIMIT = 0x0008, + LFSR_TAG_DISKLIMIT = 0x0009, + LFSR_TAG_MLEAFLIMIT = 0x000a, + LFSR_TAG_SIZELIMIT = 0x000b, + LFSR_TAG_NAMELIMIT = 0x000c, + LFSR_TAG_UTAGLIMIT = 0x000d, + LFSR_TAG_UATTRLIMIT = 0x000e, LFSR_TAG_GSTATE = 0x0100, LFSR_TAG_GRM = 0x0100, @@ -6187,80 +6198,81 @@ static int lfsr_mtree_traversal_next(lfs_t *lfs, /// Superblock things /// -// These are all leb128s, but we can expect smaller encodings -// if we assume the version. +//// TODO rm? +//// These are all leb128s, but we can expect smaller encodings +//// if we assume the version. +//// +//// - 7-bit major_version => 1 byte leb128 (worst case) +//// - 7-bit minor_version => 1 byte leb128 (worst case) +//// - 7-bit cksum_type => 1 byte leb128 (worst case) +//// - 7-bit flags => 1 byte leb128 (worst case) +//// - 32-bit block_size => 5 byte leb128 (worst case) +//// - 32-bit block_count => 5 byte leb128 (worst case) +//// - 7-bit utag_limit => 1 byte leb128 (worst case) +//// - 32-bit mtree_limit => 5 byte leb128 (worst case) +//// - 32-bit attr_limit => 5 byte leb128 (worst case) +//// - 32-bit name_limit => 5 byte leb128 (worst case) +//// - 32-bit file_limit => 5 byte leb128 (worst case) +//// => 33 bytes total +//// +//#define LFSR_SUPERCONFIG_DSIZE (1+1+1+1+5+5+1+5+5+5+5) // -// - 7-bit major_version => 1 byte leb128 (worst case) -// - 7-bit minor_version => 1 byte leb128 (worst case) -// - 7-bit cksum_type => 1 byte leb128 (worst case) -// - 7-bit flags => 1 byte leb128 (worst case) -// - 32-bit block_size => 5 byte leb128 (worst case) -// - 32-bit block_count => 5 byte leb128 (worst case) -// - 7-bit utag_limit => 1 byte leb128 (worst case) -// - 32-bit mtree_limit => 5 byte leb128 (worst case) -// - 32-bit attr_limit => 5 byte leb128 (worst case) -// - 32-bit name_limit => 5 byte leb128 (worst case) -// - 32-bit file_limit => 5 byte leb128 (worst case) -// => 33 bytes total -// -#define LFSR_SUPERCONFIG_DSIZE (1+1+1+1+5+5+1+5+5+5+5) - -#define LFSR_DATA_FROMSUPERCONFIG(_lfs, _buffer) \ - lfsr_data_fromsuperconfig(_lfs, _buffer) - -static lfsr_data_t lfsr_data_fromsuperconfig(lfs_t *lfs, - uint8_t buffer[static LFSR_SUPERCONFIG_DSIZE]) { - // TODO most of these should also be in the lfs_config/lfs_t structs - - // note we take a shortcut for for single-byte leb128s, but these - // are still leb128s! the top bit must be zero! - - // on-disk major version - buffer[0] = LFS_DISK_VERSION_MAJOR; - // on-disk minor version - buffer[1] = LFS_DISK_VERSION_MINOR; - // on-disk cksum type - buffer[2] = 2; - // on-disk flags - buffer[3] = 0; - - // on-disk block size - lfs_ssize_t d = 4; - lfs_ssize_t d_ = lfs_toleb128(lfs->cfg->block_size, &buffer[d], 5); - LFS_ASSERT(d_ >= 0); - d += d_; - - // on-disk block count - d_ = lfs_toleb128(lfs->cfg->block_count, &buffer[d], 5); - LFS_ASSERT(d_ >= 0); - d += d_; - - // on-disk mleaf limit - d_ = lfs_toleb128(lfsr_mleafweight(lfs)-1, &buffer[d], 5); - LFS_ASSERT(d_ >= 0); - d += d_; - - // on-disk utag limit - buffer[d] = 0x7f; - d += 1; - - // on-disk attr limit - d_ = lfs_toleb128(0x7fffffff, &buffer[d], 5); - LFS_ASSERT(d_ >= 0); - d += d_; - - // on-disk name limit - d_ = lfs_toleb128(0xff, &buffer[d], 5); - LFS_ASSERT(d_ >= 0); - d += d_; - - // on-disk file limit - d_ = lfs_toleb128(0x7fffffff, &buffer[d], 5); - LFS_ASSERT(d_ >= 0); - d += d_; - - return LFSR_DATA_BUF(buffer, d); -} +//#define LFSR_DATA_FROMSUPERCONFIG(_lfs, _buffer) +// lfsr_data_fromsuperconfig(_lfs, _buffer) +// +//static lfsr_data_t lfsr_data_fromsuperconfig(lfs_t *lfs, +// uint8_t buffer[static LFSR_SUPERCONFIG_DSIZE]) { +// // TODO most of these should also be in the lfs_config/lfs_t structs +// +// // note we take a shortcut for for single-byte leb128s, but these +// // are still leb128s! the top bit must be zero! +// +// // on-disk major version +// buffer[0] = LFS_DISK_VERSION_MAJOR; +// // on-disk minor version +// buffer[1] = LFS_DISK_VERSION_MINOR; +// // on-disk cksum type +// buffer[2] = 2; +// // on-disk flags +// buffer[3] = 0; +// +// // on-disk block size +// lfs_ssize_t d = 4; +// lfs_ssize_t d_ = lfs_toleb128(lfs->cfg->block_size, &buffer[d], 5); +// LFS_ASSERT(d_ >= 0); +// d += d_; +// +// // on-disk block count +// d_ = lfs_toleb128(lfs->cfg->block_count, &buffer[d], 5); +// LFS_ASSERT(d_ >= 0); +// d += d_; +// +// // on-disk mleaf limit +// d_ = lfs_toleb128(lfsr_mleafweight(lfs)-1, &buffer[d], 5); +// LFS_ASSERT(d_ >= 0); +// d += d_; +// +// // on-disk utag limit +// buffer[d] = 0x7f; +// d += 1; +// +// // on-disk attr limit +// d_ = lfs_toleb128(0x7fffffff, &buffer[d], 5); +// LFS_ASSERT(d_ >= 0); +// d += d_; +// +// // on-disk name limit +// d_ = lfs_toleb128(0xff, &buffer[d], 5); +// LFS_ASSERT(d_ >= 0); +// d += d_; +// +// // on-disk file limit +// d_ = lfs_toleb128(0x7fffffff, &buffer[d], 5); +// LFS_ASSERT(d_ >= 0); +// d += d_; +// +// return LFSR_DATA_BUF(buffer, d); +//} /// Filesystem init functions /// @@ -6304,215 +6316,325 @@ static int lfsr_mountinited(lfs_t *lfs) { if (mdir->mid == -1) { // has magic string? lfsr_data_t data; - err = lfsr_mdir_lookup(lfs, mdir, -1, LFSR_TAG_SUPERMAGIC, + err = lfsr_mdir_lookup(lfs, mdir, -1, LFSR_TAG_MAGIC, NULL, &data); - if (err && err != LFS_ERR_NOENT) { + if (err) { + if (err == LFS_ERR_NOENT) { + LFS_ERROR("No littlefs magic found"); + return LFS_ERR_INVAL; + } return err; } - if (err != LFS_ERR_NOENT) { - lfs_scmp_t cmp = lfsr_data_cmp(lfs, &data, "littlefs", 8); - if (cmp < 0) { - return cmp; - } - - // treat corrupted magic as no magic - if (lfs_cmp(cmp) != 0) { - err = LFS_ERR_NOENT; - } + lfs_scmp_t cmp = lfsr_data_cmp(lfs, &data, "littlefs", 8); + if (cmp < 0) { + return cmp; } - if (err == LFS_ERR_NOENT) { + // treat corrupted magic as no magic + if (lfs_cmp(cmp) != 0) { LFS_ERROR("No littlefs magic found"); return LFS_ERR_INVAL; } - // lookup the superconfig - err = lfsr_mdir_lookup(lfs, mdir, -1, LFSR_TAG_SUPERCONFIG, + // check the disk version + err = lfsr_mdir_lookup(lfs, mdir, -1, LFSR_TAG_VERSION, + NULL, &data); + if (err) { + if (err == LFS_ERR_NOENT) { + LFS_ERROR("No littlefs version found"); + return LFS_ERR_INVAL; + } + return err; + } + + uint32_t major_version; + err = lfsr_data_readleb128(lfs, &data, (int32_t*)&major_version); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + if (err == LFS_ERR_CORRUPT) { + major_version = -1; + } + + uint32_t minor_version; + err = lfsr_data_readleb128(lfs, &data, (int32_t*)&minor_version); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + if (err == LFS_ERR_CORRUPT) { + minor_version = -1; + } + + if (major_version != LFS_DISK_VERSION_MAJOR + || minor_version > LFS_DISK_VERSION_MINOR) { + LFS_ERROR("Incompatible version v%"PRId32".%"PRId32 + " (!= v%"PRId32".%"PRId32")", + major_version, + minor_version, + LFS_DISK_VERSION_MAJOR, + LFS_DISK_VERSION_MINOR); + return LFS_ERR_INVAL; + } + + // check for any flags + err = lfsr_mdir_lookup(lfs, mdir, -1, LFSR_TAG_FLAGS, + NULL, &data); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + uint32_t flags = 0; + if (err != LFS_ERR_NOENT) { + err = lfsr_data_readleb128(lfs, &data, (int32_t*)&flags); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + if (err == LFS_ERR_CORRUPT) { + flags = -1; + } + } + + if (flags != 0) { + LFS_ERROR("Incompatible flags 0x%"PRIx32, flags); + return LFS_ERR_INVAL; + } + + // check checksum type + err = lfsr_mdir_lookup(lfs, mdir, -1, LFSR_TAG_CKSUMTYPE, + NULL, &data); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + uint32_t cksum_type = 0; + if (err != LFS_ERR_NOENT) { + err = lfsr_data_readleb128(lfs, &data, (int32_t*)&cksum_type); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + if (err == LFS_ERR_CORRUPT) { + cksum_type = -1; + } + } + + if (cksum_type != 0) { + LFS_ERROR("Incompatible cksum type 0x%"PRId32, cksum_type); + return LFS_ERR_INVAL; + } + + // check redundancy type + err = lfsr_mdir_lookup(lfs, mdir, -1, LFSR_TAG_REDUNDTYPE, + NULL, &data); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + uint32_t redund_type = 0; + if (err != LFS_ERR_NOENT) { + err = lfsr_data_readleb128(lfs, &data, (int32_t*)&redund_type); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + if (err == LFS_ERR_CORRUPT) { + redund_type = -1; + } + } + + if (redund_type != 0) { + LFS_ERROR("Incompatible redund type 0x%"PRId32, redund_type); + return LFS_ERR_INVAL; + } + + // check block limit / block size + err = lfsr_mdir_lookup(lfs, mdir, -1, LFSR_TAG_BLOCKLIMIT, + NULL, &data); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + uint32_t block_limit = 0; + if (err != LFS_ERR_NOENT) { + err = lfsr_data_readleb128(lfs, &data, (int32_t*)&block_limit); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + if (err == LFS_ERR_CORRUPT) { + block_limit = -1; + } + } + + if (block_limit != lfs->cfg->block_size-1) { + LFS_ERROR("Incompatible block size %"PRId32" " + "(!= %"PRId32")", + block_limit+1, + lfs->cfg->block_size); + return LFS_ERR_INVAL; + } + + // check disk limit / block count + err = lfsr_mdir_lookup(lfs, mdir, -1, LFSR_TAG_DISKLIMIT, + NULL, &data); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + uint32_t disk_limit = 0; + if (err != LFS_ERR_NOENT) { + err = lfsr_data_readleb128(lfs, &data, (int32_t*)&disk_limit); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + if (err == LFS_ERR_CORRUPT) { + disk_limit = -1; + } + } + + if (disk_limit != lfs->cfg->block_count-1) { + LFS_ERROR("Incompatible block count %"PRId32" " + "(!= %"PRId32")", + disk_limit+1, + lfs->cfg->block_count); + return LFS_ERR_INVAL; + } + + // read the mleaf limit + err = lfsr_mdir_lookup(lfs, mdir, -1, LFSR_TAG_MLEAFLIMIT, + NULL, &data); + if (err) { + if (err == LFS_ERR_NOENT) { + LFS_ERROR("No mleaf limit found"); + return LFS_ERR_INVAL; + } + return err; + } + + uint32_t mleaf_limit; + err = lfsr_data_readleb128(lfs, &data, (int32_t*)&mleaf_limit); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + if (err == LFS_ERR_CORRUPT) { + mleaf_limit = -1; + } + + // we only support power-of-two mleaf weights, this unlikely to + // ever to change since mleaf weights are pretty arbitrary + if (lfs_popc(mleaf_limit+1) != 1) { + LFS_ERROR("Incompatible mleaf weight %"PRId32, mleaf_limit+1); + return LFS_ERR_INVAL; + } + + lfs->mleaf_bits = lfs_nlog2(mleaf_limit); + + // read the size limit + err = lfsr_mdir_lookup(lfs, mdir, -1, LFSR_TAG_SIZELIMIT, + NULL, &data); + if (err) { + if (err == LFS_ERR_NOENT) { + LFS_ERROR("No size limit found"); + return LFS_ERR_INVAL; + } + return err; + } + + uint32_t size_limit; + err = lfsr_data_readleb128(lfs, &data, (int32_t*)&size_limit); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + if (err == LFS_ERR_CORRUPT) { + size_limit = -1; + } + + if (size_limit > lfs->size_limit) { + LFS_ERROR("Incompatible size limit (%"PRId32" > %"PRId32")", + size_limit, + lfs->size_limit); + return LFS_ERR_INVAL; + } + + lfs->size_limit = size_limit; + + // read the name limit + err = lfsr_mdir_lookup(lfs, mdir, -1, LFSR_TAG_NAMELIMIT, + NULL, &data); + if (err) { + if (err == LFS_ERR_NOENT) { + LFS_ERROR("No name limit found"); + return LFS_ERR_INVAL; + } + return err; + } + + uint32_t name_limit; + err = lfsr_data_readleb128(lfs, &data, (int32_t*)&name_limit); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + if (err == LFS_ERR_CORRUPT) { + name_limit = -1; + } + + if (name_limit > lfs->name_limit) { + LFS_ERROR("Incompatible name limit (%"PRId32" > %"PRId32")", + name_limit, + lfs->name_limit); + return LFS_ERR_INVAL; + } + + lfs->name_limit = name_limit; + + // check the utag limit + err = lfsr_mdir_lookup(lfs, mdir, -1, LFSR_TAG_UTAGLIMIT, NULL, &data); if (err && err != LFS_ERR_NOENT) { return err; } if (err != LFS_ERR_NOENT) { - // check the major/minor version - int32_t major_version; - int32_t minor_version; - err = lfsr_data_readleb128(lfs, &data, &major_version); - // treat any leb128 overflows as out-of-range values + uint32_t utag_limit; + err = lfsr_data_readleb128(lfs, &data, (int32_t*)&utag_limit); if (err && err != LFS_ERR_CORRUPT) { return err; } - - if (err != LFS_ERR_CORRUPT) { - err = lfsr_data_readleb128(lfs, &data, &minor_version); - // treat any leb128 overflows as out-of-range values - if (err && err != LFS_ERR_CORRUPT) { - return err; - } - } - - if (err == LFS_ERR_CORRUPT - || major_version != LFS_DISK_VERSION_MAJOR - || minor_version > LFS_DISK_VERSION_MINOR) { - LFS_ERROR("Incompatible version v%"PRId32".%"PRId32 - " (!= v%"PRId32".%"PRId32")", - (err ? -1 : major_version), - (err ? -1 : minor_version), - LFS_DISK_VERSION_MAJOR, - LFS_DISK_VERSION_MINOR); - return LFS_ERR_INVAL; + if (err == LFS_ERR_CORRUPT) { + utag_limit = -1; } - // check the on-disk cksum type - int32_t cksum_type; - err = lfsr_data_readleb128(lfs, &data, &cksum_type); - // treat any leb128 overflows as out-of-range values - if (err && err != LFS_ERR_CORRUPT) { - return err; - } - - if (err == LFS_ERR_CORRUPT || cksum_type != 2) { - LFS_ERROR("Incompatible cksum type 0x%"PRIx32 - " (!= 0x%"PRIx32")", - (err ? -1 : cksum_type), - 2); - return LFS_ERR_INVAL; - } - - // check for any on-disk flags - int32_t flags; - err = lfsr_data_readleb128(lfs, &data, &flags); - // treat any leb128 overflows as out-of-range values - if (err && err != LFS_ERR_CORRUPT) { - return err; - } - - if (err == LFS_ERR_CORRUPT || flags != 0) { - LFS_ERROR("Incompatible flags 0x%"PRIx32 - " (!= 0x%"PRIx32")", - (err ? -1 : flags), - 0); - return LFS_ERR_INVAL; - } - - // check the on-disk block size - // TODO actually use this - lfs_size_t block_size; - err = lfsr_data_readleb128(lfs, &data, (int32_t*)&block_size); - // treat any leb128 overflows as out-of-range values - if (err && err != LFS_ERR_CORRUPT) { - return err; - } - - if (err == LFS_ERR_CORRUPT - || block_size != lfs->cfg->block_size) { - LFS_ERROR("Incompatible block size 0x%"PRIx32 - " (!= 0x%"PRIx32")", - (err ? (lfs_size_t)-1 : block_size), - lfs->cfg->block_size); - return LFS_ERR_INVAL; - } - - // check the on-disk block count - // TODO actually use this - lfs_block_t block_count; - err = lfsr_data_readleb128(lfs, &data, (int32_t*)&block_count); - // treat any leb128 overflows as out-of-range values - if (err && err != LFS_ERR_CORRUPT) { - return err; - } - - if (err == LFS_ERR_CORRUPT - || block_count != lfs->cfg->block_count) { - LFS_ERROR("Incompatible block count 0x%"PRIx32 - " (!= 0x%"PRIx32")", - (err ? (lfs_block_t)-1 : block_count), - lfs->cfg->block_count); - return LFS_ERR_INVAL; - } - - // check the on-disk mtree limit - // TODO actually use this - lfsr_mid_t mleaf_limit; - err = lfsr_data_readleb128(lfs, &data, (int32_t*)&mleaf_limit); - // treat any leb128 overflows as out-of-range values - if (err && err != LFS_ERR_CORRUPT) { - return err; - } - - if (err == LFS_ERR_CORRUPT - || mleaf_limit != lfsr_mleafweight(lfs)-1) { - LFS_ERROR("Incompatible mleaf limit 0x%"PRIx32, - (err ? (lfsr_mid_t)-1 : mleaf_limit)); - return LFS_ERR_INVAL; - } - - // check the on-disk utag limit - // TODO actually use this - int32_t utag_limit; - err = lfsr_data_readleb128(lfs, &data, &utag_limit); - // treat any leb128 overflows as out-of-range values - if (err && err != LFS_ERR_CORRUPT) { - return err; - } - - if (err == LFS_ERR_CORRUPT || utag_limit != 0x7f) { - LFS_ERROR("Incompatible utag limit 0x%"PRIx32 - " (> 0x%"PRIx32")", - (err ? -1 : utag_limit), + // only 7-bit utags are supported + if (utag_limit != 0x7f) { + LFS_ERROR("Incompatible utag limit " + "(%"PRId32" != %"PRId32")", + utag_limit, 0x7f); return LFS_ERR_INVAL; } + } - // check the on-disk attr limit - // TODO actually use this - lfs_ssize_t attr_limit; - err = lfsr_data_readleb128(lfs, &data, &attr_limit); - // treat any leb128 overflows as out-of-range values + // check the uattr limit + err = lfsr_mdir_lookup(lfs, mdir, -1, LFSR_TAG_UATTRLIMIT, + NULL, &data); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + if (err != LFS_ERR_NOENT) { + uint32_t uattr_limit; + err = lfsr_data_readleb128(lfs, &data, (int32_t*)&uattr_limit); if (err && err != LFS_ERR_CORRUPT) { return err; } - - if (err == LFS_ERR_CORRUPT || attr_limit != 0x7fffffff) { - LFS_ERROR("Incompatible attr limit 0x%"PRIx32 - " (> 0x%"PRIx32")", - (err ? -1 : attr_limit), - 0x7fffffff); - return LFS_ERR_INVAL; + if (err == LFS_ERR_CORRUPT) { + uattr_limit = -1; } - // check the on-disk name limit - // TODO actually use this - lfs_ssize_t name_limit; - err = lfsr_data_readleb128(lfs, &data, &name_limit); - // treat any leb128 overflows as out-of-range values - if (err && err != LFS_ERR_CORRUPT) { - return err; - } - - if (err == LFS_ERR_CORRUPT || name_limit != 0xff) { - LFS_ERROR("Incompatible name limit 0x%"PRIx32 - " (> 0x%"PRIx32")", - (err ? -1 : name_limit), - 0xff); - return LFS_ERR_INVAL; - } - - // check the on-disk file limit - // TODO actually use this - lfs_soff_t file_limit; - err = lfsr_data_readleb128(lfs, &data, &file_limit); - // treat any leb128 overflows as out-of-range values - if (err && err != LFS_ERR_CORRUPT) { - return err; - } - - if (err == LFS_ERR_CORRUPT || file_limit != 0x7fffffff) { - LFS_ERROR("Incompatible file limit 0x%"PRIx32 - " (> 0x%"PRIx32")", - (err ? -1 : file_limit), - 0x7fffffff); + // only >=block_size uattrs are (implicitly) supported + if (uattr_limit < lfs->cfg->block_size-1) { + LFS_ERROR("Incompatible uattr limit " + "(%"PRId32" < %"PRId32")", + uattr_limit, + lfs->cfg->block_size-1); return LFS_ERR_INVAL; } } @@ -6563,11 +6685,6 @@ static int lfsr_mountinited(lfs_t *lfs) { } static int lfsr_formatinited(lfs_t *lfs) { - // create superconfig - uint8_t superconfig_buf[LFSR_SUPERCONFIG_DSIZE]; - lfsr_data_t superconfig_data = lfsr_data_fromsuperconfig(lfs, - superconfig_buf); - for (int i = 0; i < 2; i++) { // write superblock to both rbyds in the root mroot to hopefully // avoid mounting an older filesystem on disk @@ -6588,11 +6705,18 @@ static int lfsr_formatinited(lfs_t *lfs) { // our initial superblock contains a couple things: // - our magic string, "littlefs" - // - the superconfig, format-time configuration + // - any format-time configuration // - the root's bookmark tag, which reserves did = 0 for the root err = lfsr_rbyd_commit(lfs, &rbyd, LFSR_ATTRS( - LFSR_ATTR(-1, SUPERMAGIC, 0, BUF("littlefs", 8)), - LFSR_ATTR(-1, SUPERCONFIG, 0, DATA(superconfig_data)), + LFSR_ATTR(-1, MAGIC, 0, BUF("littlefs", 8)), + LFSR_ATTR(-1, VERSION, 0, BUF(((const uint8_t[2]){ + LFS_DISK_VERSION_MAJOR, + LFS_DISK_VERSION_MINOR}), 2)), + LFSR_ATTR(-1, BLOCKLIMIT, 0, LEB128(lfs->cfg->block_size-1)), + LFSR_ATTR(-1, DISKLIMIT, 0, LEB128(lfs->cfg->block_count-1)), + LFSR_ATTR(-1, MLEAFLIMIT, 0, LEB128(lfsr_mleafweight(lfs)-1)), + LFSR_ATTR(-1, SIZELIMIT, 0, LEB128(0x7fffffff)), + LFSR_ATTR(-1, NAMELIMIT, 0, LEB128(0xff)), LFSR_ATTR(0, BOOKMARK, +1, NAME(0, NULL, 0)))); if (err) { return err; @@ -6794,7 +6918,7 @@ int lfsr_mkdir(lfs_t *lfs, const char *path) { } // check that name fits - if (name_size > lfs->name_max) { + if (name_size > lfs->name_limit) { return LFS_ERR_NAMETOOLONG; } @@ -7019,7 +7143,7 @@ int lfsr_rename(lfs_t *lfs, const char *old_path, const char *new_path) { // there are a few cases we need to watch out for if (!exists) { // check that name fits - if (new_name_size > lfs->name_max) { + if (new_name_size > lfs->name_limit) { return LFS_ERR_NAMETOOLONG; } @@ -10848,26 +10972,18 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { lfs->lookahead.acked = 0; // check that the size limits are sane - LFS_ASSERT(lfs->cfg->name_max <= LFS_NAME_MAX); - lfs->name_max = lfs->cfg->name_max; - if (!lfs->name_max) { - lfs->name_max = LFS_NAME_MAX; + LFS_ASSERT(lfs->cfg->name_limit <= LFS_NAME_MAX); + lfs->name_limit = lfs->cfg->name_limit; + if (!lfs->name_limit) { + lfs->name_limit = LFS_NAME_MAX; } - LFS_ASSERT(lfs->cfg->file_max <= LFS_FILE_MAX); - lfs->file_max = lfs->cfg->file_max; - if (!lfs->file_max) { - lfs->file_max = LFS_FILE_MAX; + LFS_ASSERT(lfs->cfg->size_limit <= LFS_FILE_MAX); + lfs->size_limit = lfs->cfg->size_limit; + if (!lfs->size_limit) { + lfs->size_limit = LFS_FILE_MAX; } - LFS_ASSERT(lfs->cfg->attr_max <= LFS_ATTR_MAX); - lfs->attr_max = lfs->cfg->attr_max; - if (!lfs->attr_max) { - lfs->attr_max = LFS_ATTR_MAX; - } - - LFS_ASSERT(lfs->cfg->metadata_max <= lfs->cfg->block_size); - // setup default state lfs->root[0] = LFS_BLOCK_NULL; lfs->root[1] = LFS_BLOCK_NULL; diff --git a/lfs.h b/lfs.h index 99c4db53..1d439a8e 100644 --- a/lfs.h +++ b/lfs.h @@ -75,11 +75,12 @@ typedef int32_t lfsr_sdid_t; #define LFS_FILE_MAX 2147483647 #endif -// Maximum size of custom attributes in bytes, may be redefined, but there is -// no real benefit to using a smaller LFS_ATTR_MAX. Limited to <= 1022. -#ifndef LFS_ATTR_MAX -#define LFS_ATTR_MAX 1022 -#endif +// TODO rm me +//// Maximum size of custom attributes in bytes, may be redefined, but there is +//// no real benefit to using a smaller LFS_ATTR_MAX. Limited to <= 1022. +//#ifndef LFS_ATTR_MAX +//#define LFS_ATTR_MAX 1022 +//#endif // Possible error codes, these are negative to allow // valid positive return values @@ -261,23 +262,24 @@ struct lfs_config { // larger names except the size of the info struct which is controlled by // the LFS_NAME_MAX define. Defaults to LFS_NAME_MAX when zero. Stored in // superblock and must be respected by other littlefs drivers. - lfs_size_t name_max; + lfs_size_t name_limit; // Optional upper limit on files in bytes. No downside for larger files // but must be <= LFS_FILE_MAX. Defaults to LFS_FILE_MAX when zero. Stored // in superblock and must be respected by other littlefs drivers. - lfs_size_t file_max; + lfs_size_t size_limit; - // Optional upper limit on custom attributes in bytes. No downside for - // larger attributes size but must be <= LFS_ATTR_MAX. Defaults to - // LFS_ATTR_MAX when zero. - lfs_size_t attr_max; - - // Optional upper limit on total space given to metadata pairs in bytes. On - // devices with large blocks (e.g. 128kB) setting this to a low size (2-8kB) - // can help bound the metadata compaction time. Must be <= block_size. - // Defaults to block_size when zero. - lfs_size_t metadata_max; +// TODO rm me +// // Optional upper limit on custom attributes in bytes. No downside for +// // larger attributes size but must be <= LFS_ATTR_MAX. Defaults to +// // LFS_ATTR_MAX when zero. +// lfs_size_t attr_max; +// +// // Optional upper limit on total space given to metadata pairs in bytes. On +// // devices with large blocks (e.g. 128kB) setting this to a low size (2-8kB) +// // can help bound the metadata compaction time. Must be <= block_size. +// // Defaults to block_size when zero. +// lfs_size_t metadata_max; }; // File info structure @@ -505,9 +507,8 @@ typedef struct lfs { } lookahead; const struct lfs_config *cfg; - lfs_size_t name_max; - lfs_size_t file_max; - lfs_size_t attr_max; + lfs_size_t name_limit; + lfs_off_t size_limit; // begin lfsr things lfsr_grm_t grm; diff --git a/scripts/dbgbtree.py b/scripts/dbgbtree.py index 109cf35a..bc8a263a 100755 --- a/scripts/dbgbtree.py +++ b/scripts/dbgbtree.py @@ -9,8 +9,19 @@ import struct TAG_NULL = 0x0000 -TAG_SUPERMAGIC = 0x0003 -TAG_SUPERCONFIG = 0x0004 +TAG_CONFIG = 0x0000 +TAG_MAGIC = 0x0003 +TAG_VERSION = 0x0004 +TAG_FLAGS = 0x0005 +TAG_CKSUMTYPE = 0x0006 +TAG_REDUNDTYPE = 0x0007 +TAG_BLOCKLIMIT = 0x0008 +TAG_DISKLIMIT = 0x0009 +TAG_MLEAFLIMIT = 0x000a +TAG_SIZELIMIT = 0x000b +TAG_NAMELIMIT = 0x000c +TAG_UTAGLIMIT = 0x000d +TAG_UATTRLIMIT = 0x000e TAG_GSTATE = 0x0100 TAG_GRM = 0x0100 TAG_NAME = 0x0200 @@ -122,12 +133,21 @@ def tagrepr(tag, w, size, off=None): return 'null%s%s' % ( ' w%d' % w if w else '', ' %d' % size if size else '') - elif tag == TAG_SUPERMAGIC: - return 'supermagic%s %d' % ( - ' w%d' % w if w else '', - size) - elif tag == TAG_SUPERCONFIG: - return 'superconfig%s %d' % ( + elif (tag & 0xff00) == TAG_CONFIG: + return '%s%s %d' % ( + 'magic' if tag == TAG_MAGIC + else 'version' if tag == TAG_VERSION + else 'flags' if tag == TAG_FLAGS + else 'cksumtype' if tag == TAG_CKSUMTYPE + else 'redundtype' if tag == TAG_REDUNDTYPE + else 'blocklimit' if tag == TAG_BLOCKLIMIT + else 'disklimit' if tag == TAG_DISKLIMIT + else 'mleaflimit' if tag == TAG_MLEAFLIMIT + else 'sizelimit' if tag == TAG_SIZELIMIT + else 'namelimit' if tag == TAG_NAMELIMIT + else 'utaglimit' if tag == TAG_UTAGLIMIT + else 'uattrlimit' if tag == TAG_UATTRLIMIT + else 'config 0x%02x' % (tag & 0xff), ' w%d' % w if w else '', size) elif (tag & 0xff00) == TAG_GSTATE: diff --git a/scripts/dbglfs.py b/scripts/dbglfs.py index 3b5d403c..54788948 100755 --- a/scripts/dbglfs.py +++ b/scripts/dbglfs.py @@ -10,8 +10,19 @@ import struct TAG_NULL = 0x0000 -TAG_SUPERMAGIC = 0x0003 -TAG_SUPERCONFIG = 0x0004 +TAG_CONFIG = 0x0000 +TAG_MAGIC = 0x0003 +TAG_VERSION = 0x0004 +TAG_FLAGS = 0x0005 +TAG_CKSUMTYPE = 0x0006 +TAG_REDUNDTYPE = 0x0007 +TAG_BLOCKLIMIT = 0x0008 +TAG_DISKLIMIT = 0x0009 +TAG_MLEAFLIMIT = 0x000a +TAG_SIZELIMIT = 0x000b +TAG_NAMELIMIT = 0x000c +TAG_UTAGLIMIT = 0x000d +TAG_UATTRLIMIT = 0x000e TAG_GSTATE = 0x0100 TAG_GRM = 0x0100 TAG_NAME = 0x0200 @@ -132,12 +143,21 @@ def tagrepr(tag, w, size, off=None): return 'null%s%s' % ( ' w%d' % w if w else '', ' %d' % size if size else '') - elif tag == TAG_SUPERMAGIC: - return 'supermagic%s %d' % ( - ' w%d' % w if w else '', - size) - elif tag == TAG_SUPERCONFIG: - return 'superconfig%s %d' % ( + elif (tag & 0xff00) == TAG_CONFIG: + return '%s%s %d' % ( + 'magic' if tag == TAG_MAGIC + else 'version' if tag == TAG_VERSION + else 'flags' if tag == TAG_FLAGS + else 'cksumtype' if tag == TAG_CKSUMTYPE + else 'redundtype' if tag == TAG_REDUNDTYPE + else 'blocklimit' if tag == TAG_BLOCKLIMIT + else 'disklimit' if tag == TAG_DISKLIMIT + else 'mleaflimit' if tag == TAG_MLEAFLIMIT + else 'sizelimit' if tag == TAG_SIZELIMIT + else 'namelimit' if tag == TAG_NAMELIMIT + else 'utaglimit' if tag == TAG_UTAGLIMIT + else 'uattrlimit' if tag == TAG_UATTRLIMIT + else 'config 0x%02x' % (tag & 0xff), ' w%d' % w if w else '', size) elif (tag & 0xff00) == TAG_GSTATE: @@ -625,32 +645,164 @@ class Rbyd: break -# read the superconfig -def superconfig(mroot): - done, rid, tag, w, j, d, data, _ = mroot.lookup(-1, TAG_SUPERCONFIG) - if done or rid != -1 or tag != TAG_SUPERCONFIG: - return {} - j += d +# read the config +class Config: + def __init__(self, mroot=None): + # read the config from the mroot + self.config = {} + if mroot is not None: + tag = 0 + while True: + done, rid, tag, w, j, d, data, _ = mroot.lookup(-1, tag+0x1) + if done or rid != -1 or (tag & 0xff00) != TAG_CONFIG: + break + + self.config[tag] = (j+d, data) + + # accessors for known config + @ft.cached_property + def magic(self): + if TAG_MAGIC in self.config: + _, data = self.config[TAG_MAGIC] + return data + else: + return None + + @ft.cached_property + def version(self): + if TAG_VERSION in self.config: + _, data = self.config[TAG_VERSION] + d = 0 + major, d_ = fromleb128(data[d:]); d += d_ + minor, d_ = fromleb128(data[d:]); d += d_ + return (major, minor) + else: + return (None, None) + + @ft.cached_property + def flags(self): + if TAG_FLAGS in self.config: + _, data = self.config[TAG_FLAGS] + flags, _ = fromleb128(data) + return flags + else: + return None + + @ft.cached_property + def cksum_type(self): + if TAG_CKSUMTYPE in self.config: + _, data = self.config[TAG_CKSUMTYPE] + cksum_type, _ = fromleb128(data) + return cksum_type + else: + return None + + @ft.cached_property + def redund_type(self): + if TAG_REDUNDTYPE in self.config: + _, data = self.config[TAG_REDUNDTYPE] + redund_type, _ = fromleb128(data) + return redund_type + else: + return None + + @ft.cached_property + def block_limit(self): + if TAG_BLOCKLIMIT in self.config: + _, data = self.config[TAG_BLOCKLIMIT] + block_limit, _ = fromleb128(data) + return block_limit + else: + return None + + @ft.cached_property + def disk_limit(self): + if TAG_DISKLIMIT in self.config: + _, data = self.config[TAG_DISKLIMIT] + disk_limit, _ = fromleb128(data) + return disk_limit + else: + return None + + @ft.cached_property + def mleaf_limit(self): + if TAG_MLEAFLIMIT in self.config: + _, data = self.config[TAG_MLEAFLIMIT] + mleaf_limit, _ = fromleb128(data) + return mleaf_limit + else: + return None + + @ft.cached_property + def size_limit(self): + if TAG_SIZELIMIT in self.config: + _, data = self.config[TAG_SIZELIMIT] + size_limit, _ = fromleb128(data) + return size_limit + else: + return None + + @ft.cached_property + def name_limit(self): + if TAG_NAMELIMIT in self.config: + _, data = self.config[TAG_NAMELIMIT] + name_limit, _ = fromleb128(data) + return name_limit + else: + return None + + @ft.cached_property + def utag_limit(self): + if TAG_UTAGLIMIT in self.config: + _, data = self.config[TAG_UTAGLIMIT] + utag_limit, _ = fromleb128(data) + return utag_limit + else: + return None + + @ft.cached_property + def uattr_limit(self): + if TAG_UATTRLIMIT in self.config: + _, data = self.config[TAG_UATTRLIMIT] + uattr_limit, _ = fromleb128(data) + return uattr_limit + else: + return None + + def repr(self): + def crepr(tag, data): + if tag == TAG_MAGIC: + return 'magic \"%s\"' % ''.join( + b if b >= ' ' and b <= '~' else '.' + for b in map(chr, self.magic)) + elif tag == TAG_VERSION: + return 'version v%d.%d' % self.version + elif tag == TAG_FLAGS: + return 'flags 0x%x' % self.flags + elif tag == TAG_CKSUMTYPE: + return 'cksum_type %d' % self.cksum_type + elif tag == TAG_REDUNDTYPE: + return 'redund_type %d' % self.redund_type + elif tag == TAG_BLOCKLIMIT: + return 'block_limit %d' % self.block_limit + elif tag == TAG_DISKLIMIT: + return 'disk_limit %d' % self.disk_limit + elif tag == TAG_MLEAFLIMIT: + return 'mleaf_limit %d' % self.mleaf_limit + elif tag == TAG_SIZELIMIT: + return 'size_limit %d' % self.size_limit + elif tag == TAG_NAMELIMIT: + return 'name_limit %d' % self.name_limit + elif tag == TAG_UTAGLIMIT: + return 'utag_limit %d' % self.utag_limit + elif tag == TAG_UATTRLIMIT: + return 'uattr_limit %d' % self.uattr_limit + else: + return 'config 0x%02x %d' % (tag, len(data)) + + for tag, (j, data) in sorted(self.config.items()): + yield crepr(tag, data), tag, j, data - config = co.OrderedDict() - d = 0 - def next(name): - nonlocal config, d - c, d_ = fromleb128(data[d:]) - config[name] = (c, j, data[d:d+d_]) - d += d_ - next('major_version') - next('minor_version') - next('csum_type') - next('flags') - next('block_size') - next('block_count') - next('mlimit') - next('utag_limit') - next('attr_limit') - next('name_limit') - next('file_limit') - return config # collect gstate class GState: @@ -695,25 +847,21 @@ class GState: mid % self.mleaf_weight)) return rms -def grepr(tag, data, mleaf_weight): - if tag == TAG_GRM: - d = 0 - count, d_ = fromleb128(data[d:]); d += d_ - rms = [] - if count <= 2: - for _ in range(count): - mid, d_ = fromleb128(data[d:]); d += d_ - rms.append(( - mid - (mid % mleaf_weight), - mid % mleaf_weight)) - return 'grm %s' % ( - 'none' if count == 0 - else ' '.join('%d.%d' % (mbid//mleaf_weight, rid) - for mbid, rid in rms) - if count <= 2 - else '0x%x' % count) - else: - return 'gstate 0x%02x %d' % (tag, len(data)) + def repr(self): + def grepr(tag, data): + if tag == TAG_GRM: + count, _ = fromleb128(data) + return 'grm %s' % ( + 'none' if count == 0 + else ' '.join('%d.%d' % (mbid//self.mleaf_weight, rid) + for mbid, rid in self.grm) + if count <= 2 + else '0x%x %d' % (count, len(data))) + else: + return 'gstate 0x%02x %d' % (tag, len(data)) + + for tag, data in sorted(self.gstate.items()): + yield grepr(tag, data), tag, data def frepr(mdir, rid, tag): if tag == TAG_BOOKMARK: @@ -770,14 +918,14 @@ def main(disk, mroots=None, *, # - find the actual mroot # - find the total weight # - are we corrupted? - # - collect superconfig + # - collect config # - collect gstate # - any missing or orphaned bookmark entries bweight = 0 rweight = 0 corrupted = False gstate = GState(mleaf_weight) - config = {} + config = Config() dir_dids = [(0, b'', -1, 0, None, -1, TAG_DID, 0)] bookmark_dids = [] @@ -792,8 +940,8 @@ def main(disk, mroots=None, *, rweight = max(rweight, mroot.weight) # yes we get gstate from all mroots gstate.xor(-1, 0, mroot) - # get the superconfig - config = superconfig(mroot) + # get the config + config = Config(mroot) # find any dids for rid, tag, w, j, d, data in mroot: @@ -942,8 +1090,8 @@ def main(disk, mroots=None, *, # print some information about the filesystem print('littlefs v%s.%s %s, rev %d, weight %d.%d' % ( - config.get('major_version', ('?',))[0], - config.get('minor_version', ('?',))[0], + config.version[0] if config.version[0] is not None else '?', + config.version[1] if config.version[1] is not None else '?', mroot.addr(), mroot.rev, bweight//mleaf_weight, 1*mleaf_weight)) # print header @@ -964,12 +1112,12 @@ def main(disk, mroots=None, *, 'data (truncated)' if not args.get('no_truncate') else '')) - # print superconfig? + # print config? if args.get('config'): - for i, (name, (c, j, data)) in enumerate(config.items()): + for i, (repr_, tag, j, data) in enumerate(config.repr()): print('%12s %-*s %s' % ( 'config:' if i == 0 else '', - w_width + 23, '%s %d' % (name, c), + w_width + 23, repr_, next(xxd(data, 8), '') if not args.get('no_truncate') else '')) @@ -979,7 +1127,7 @@ def main(disk, mroots=None, *, '', w_width, '', '%-22s%s' % ( - '%08x' % c, + '%04x %08x %07x' % (tag, 0, len(data)), ' %s' % ' '.join( '%08x' % fromle32( data[i*4 : min(i*4+4,len(data))]) @@ -998,11 +1146,10 @@ def main(disk, mroots=None, *, # print gstate? if args.get('gstate'): - for i, (tag, data) in enumerate(sorted(gstate.gstate.items())): + for i, (repr_, tag, data) in enumerate(gstate.repr()): print('%12s %-*s %s' % ( 'gstate:' if i == 0 else '', - w_width + 23, - grepr(tag, data, mleaf_weight), + w_width + 23, repr_, next(xxd(data, 8), '') if not args.get('no_truncate') else '')) @@ -1253,9 +1400,9 @@ if __name__ == "__main__": default='auto', help="When to use terminal colors. Defaults to 'auto'.") parser.add_argument( - '-c', '--config', '--superconfig', + '-c', '--config', action='store_true', - help="Show the on-disk superconfig.") + help="Show the on-disk config.") parser.add_argument( '-g', '--gstate', action='store_true', diff --git a/scripts/dbgmtree.py b/scripts/dbgmtree.py index aea7a6a0..b1545acf 100755 --- a/scripts/dbgmtree.py +++ b/scripts/dbgmtree.py @@ -9,8 +9,19 @@ import struct TAG_NULL = 0x0000 -TAG_SUPERMAGIC = 0x0003 -TAG_SUPERCONFIG = 0x0004 +TAG_CONFIG = 0x0000 +TAG_MAGIC = 0x0003 +TAG_VERSION = 0x0004 +TAG_FLAGS = 0x0005 +TAG_CKSUMTYPE = 0x0006 +TAG_REDUNDTYPE = 0x0007 +TAG_BLOCKLIMIT = 0x0008 +TAG_DISKLIMIT = 0x0009 +TAG_MLEAFLIMIT = 0x000a +TAG_SIZELIMIT = 0x000b +TAG_NAMELIMIT = 0x000c +TAG_UTAGLIMIT = 0x000d +TAG_UATTRLIMIT = 0x000e TAG_GSTATE = 0x0100 TAG_GRM = 0x0100 TAG_NAME = 0x0200 @@ -131,12 +142,21 @@ def tagrepr(tag, w, size, off=None): return 'null%s%s' % ( ' w%d' % w if w else '', ' %d' % size if size else '') - elif tag == TAG_SUPERMAGIC: - return 'supermagic%s %d' % ( - ' w%d' % w if w else '', - size) - elif tag == TAG_SUPERCONFIG: - return 'superconfig%s %d' % ( + elif (tag & 0xff00) == TAG_CONFIG: + return '%s%s %d' % ( + 'magic' if tag == TAG_MAGIC + else 'version' if tag == TAG_VERSION + else 'flags' if tag == TAG_FLAGS + else 'cksumtype' if tag == TAG_CKSUMTYPE + else 'redundtype' if tag == TAG_REDUNDTYPE + else 'blocklimit' if tag == TAG_BLOCKLIMIT + else 'disklimit' if tag == TAG_DISKLIMIT + else 'mleaflimit' if tag == TAG_MLEAFLIMIT + else 'sizelimit' if tag == TAG_SIZELIMIT + else 'namelimit' if tag == TAG_NAMELIMIT + else 'utaglimit' if tag == TAG_UTAGLIMIT + else 'uattrlimit' if tag == TAG_UATTRLIMIT + else 'config 0x%02x' % (tag & 0xff), ' w%d' % w if w else '', size) elif (tag & 0xff00) == TAG_GSTATE: diff --git a/scripts/dbgrbyd.py b/scripts/dbgrbyd.py index 584b36d8..290cc4f8 100755 --- a/scripts/dbgrbyd.py +++ b/scripts/dbgrbyd.py @@ -18,8 +18,19 @@ COLORS = [ TAG_NULL = 0x0000 -TAG_SUPERMAGIC = 0x0003 -TAG_SUPERCONFIG = 0x0004 +TAG_CONFIG = 0x0000 +TAG_MAGIC = 0x0003 +TAG_VERSION = 0x0004 +TAG_FLAGS = 0x0005 +TAG_CKSUMTYPE = 0x0006 +TAG_REDUNDTYPE = 0x0007 +TAG_BLOCKLIMIT = 0x0008 +TAG_DISKLIMIT = 0x0009 +TAG_MLEAFLIMIT = 0x000a +TAG_SIZELIMIT = 0x000b +TAG_NAMELIMIT = 0x000c +TAG_UTAGLIMIT = 0x000d +TAG_UATTRLIMIT = 0x000e TAG_GSTATE = 0x0100 TAG_GRM = 0x0100 TAG_NAME = 0x0200 @@ -123,12 +134,21 @@ def tagrepr(tag, w, size, off=None): return 'null%s%s' % ( ' w%d' % w if w else '', ' %d' % size if size else '') - elif tag == TAG_SUPERMAGIC: - return 'supermagic%s %d' % ( - ' w%d' % w if w else '', - size) - elif tag == TAG_SUPERCONFIG: - return 'superconfig%s %d' % ( + elif (tag & 0xff00) == TAG_CONFIG: + return '%s%s %d' % ( + 'magic' if tag == TAG_MAGIC + else 'version' if tag == TAG_VERSION + else 'flags' if tag == TAG_FLAGS + else 'cksumtype' if tag == TAG_CKSUMTYPE + else 'redundtype' if tag == TAG_REDUNDTYPE + else 'blocklimit' if tag == TAG_BLOCKLIMIT + else 'disklimit' if tag == TAG_DISKLIMIT + else 'mleaflimit' if tag == TAG_MLEAFLIMIT + else 'sizelimit' if tag == TAG_SIZELIMIT + else 'namelimit' if tag == TAG_NAMELIMIT + else 'utaglimit' if tag == TAG_UTAGLIMIT + else 'uattrlimit' if tag == TAG_UATTRLIMIT + else 'config 0x%02x' % (tag & 0xff), ' w%d' % w if w else '', size) elif (tag & 0xff00) == TAG_GSTATE: