| /**************************************************************************** |
| * fs/nxffs/nxffs_dump.c |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| |
| #include <string.h> |
| #include <debug.h> |
| #include <errno.h> |
| |
| #include <nuttx/crc32.h> |
| #include <nuttx/kmalloc.h> |
| #include <nuttx/fs/ioctl.h> |
| #include <nuttx/mtd/mtd.h> |
| |
| #include "nxffs.h" |
| #include "fs_heap.h" |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| struct nxffs_blkinfo_s |
| { |
| struct mtd_geometry_s geo; |
| FAR uint8_t *buffer; |
| off_t nblocks; |
| off_t block; |
| off_t offset; |
| #if defined(CONFIG_DEBUG_FEATURES) && defined(CONFIG_DEBUG_FS) |
| bool verbose; |
| #endif |
| }; |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| #if defined(CONFIG_DEBUG_FEATURES) && defined(CONFIG_DEBUG_FS) |
| static const char g_hdrformat[] = " BLOCK:OFFS TYPE STATE LENGTH\n"; |
| static const char g_format[] = " %5"PRIiOFF":%-5d %s %s %5"PRIu32"\n"; |
| #endif |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: nxffs_analyzeinode |
| * |
| * Description: |
| * Analyze one candidate inode found in the block. |
| * |
| ****************************************************************************/ |
| |
| #if defined(CONFIG_DEBUG_FEATURES) && defined(CONFIG_DEBUG_FS) |
| static inline ssize_t nxffs_analyzeinode(FAR struct nxffs_blkinfo_s *blkinfo, |
| int offset) |
| { |
| FAR struct nxffs_inode_s inode; |
| off_t nextblock; |
| uint8_t state; |
| uint32_t noffs; |
| uint32_t doffs; |
| #if 0 |
| uint32_t utc; |
| #endif |
| uint32_t ecrc; |
| uint32_t datlen; |
| uint32_t crc; |
| size_t spaceleft; |
| |
| /* Verify that there is space for an inode header remaining in the block */ |
| |
| if (offset + SIZEOF_NXFFS_INODE_HDR > blkinfo->geo.blocksize) |
| { |
| /* No.. then this can't be an inode header */ |
| |
| return ERROR; |
| } |
| |
| /* Unpack the header */ |
| |
| memcpy(&inode, &blkinfo->buffer[offset], SIZEOF_NXFFS_INODE_HDR); |
| noffs = nxffs_rdle32(inode.noffs); |
| doffs = nxffs_rdle32(inode.doffs); |
| #if 0 |
| utc = nxffs_rdle32(inode.utc); |
| #endif |
| ecrc = nxffs_rdle32(inode.crc); |
| datlen = nxffs_rdle32(inode.datlen); |
| |
| /* Misc. sanity checks */ |
| |
| if (noffs < blkinfo->offset + offset + SIZEOF_NXFFS_BLOCK_HDR) |
| { |
| /* The name begins before the inode header. This can't can't be |
| * a real inode header (or it is a corrupted one). |
| */ |
| |
| return ERROR; |
| } |
| |
| /* Can we verify the inode? We need to have the inode name in the same |
| * block to do that (or get access to the next block) |
| */ |
| |
| if (doffs < blkinfo->offset + offset + SIZEOF_NXFFS_BLOCK_HDR) |
| { |
| /* The first data block begins before the inode header. This can't |
| * can't be a real inode header (or it is a corrupted one). |
| */ |
| |
| return ERROR; |
| } |
| |
| spaceleft = (blkinfo->nblocks - blkinfo->block) * blkinfo->geo.blocksize; |
| spaceleft -= (offset + SIZEOF_NXFFS_BLOCK_HDR); |
| if (datlen > spaceleft) |
| { |
| /* The data length is greater than what would fit in the rest of FLASH |
| * (even ignoring block and data header sizes. |
| */ |
| |
| return ERROR; |
| } |
| |
| /* The name begins after the inode header. Does it begin in this block? */ |
| |
| nextblock = blkinfo->offset + blkinfo->geo.blocksize; |
| if (noffs > nextblock) |
| { |
| /* Note than we cannot verify the inode header */ |
| |
| if (blkinfo->verbose) |
| { |
| syslog(LOG_NOTICE, g_format, |
| blkinfo->block, offset, "INODE", "UNVERFD", datlen); |
| } |
| |
| return ERROR; |
| } |
| |
| /* The name begins in this block. Does it also end in this block? */ |
| |
| if (noffs + inode.namlen > nextblock) |
| { |
| /* No.. Assume that this is not an inode. */ |
| |
| return ERROR; |
| } |
| |
| /* Calculate the CRC */ |
| |
| state = inode.state; |
| inode.state = CONFIG_NXFFS_ERASEDSTATE; |
| nxffs_wrle32(inode.crc, 0); |
| |
| crc = crc32((FAR const uint8_t *)&inode, SIZEOF_NXFFS_INODE_HDR); |
| crc = crc32part(&blkinfo->buffer[noffs - blkinfo->offset], |
| inode.namlen, crc); |
| |
| if (crc != ecrc) |
| { |
| syslog(LOG_NOTICE, g_format, |
| blkinfo->block, offset, "INODE", "CRC BAD", datlen); |
| return ERROR; |
| } |
| |
| /* If must be a good header */ |
| |
| if (state == INODE_STATE_FILE) |
| { |
| if (blkinfo->verbose) |
| { |
| syslog(LOG_NOTICE, g_format, |
| blkinfo->block, offset, "INODE", "OK ", datlen); |
| } |
| } |
| else if (state == INODE_STATE_DELETED) |
| { |
| if (blkinfo->verbose) |
| { |
| syslog(LOG_NOTICE, g_format, |
| blkinfo->block, offset, "INODE", "DELETED", datlen); |
| } |
| } |
| else |
| { |
| syslog(LOG_NOTICE, g_format, |
| blkinfo->block, offset, "INODE", "CORRUPT", datlen); |
| } |
| |
| /* Return the block-relative offset to the next byte after the inode name */ |
| |
| return noffs + inode.namlen - offset - blkinfo->offset; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: nxffs_analyzedata |
| * |
| * Description: |
| * Analyze one candidate inode found in the block. |
| * |
| ****************************************************************************/ |
| |
| #if defined(CONFIG_DEBUG_FEATURES) && defined(CONFIG_DEBUG_FS) |
| static inline ssize_t nxffs_analyzedata(FAR struct nxffs_blkinfo_s *blkinfo, |
| int offset) |
| { |
| struct nxffs_data_s dathdr; |
| uint32_t ecrc; |
| uint16_t datlen; |
| uint32_t crc; |
| |
| /* Copy and unpack the data block header */ |
| |
| memcpy(&dathdr, &blkinfo->buffer[offset], SIZEOF_NXFFS_DATA_HDR); |
| ecrc = nxffs_rdle32(dathdr.crc); |
| datlen = nxffs_rdle16(dathdr.datlen); |
| |
| /* Sanity checks */ |
| |
| if (offset + SIZEOF_NXFFS_DATA_HDR + datlen > blkinfo->geo.blocksize) |
| { |
| /* Data does not fit in within the block, this can't be a data block */ |
| |
| return ERROR; |
| } |
| |
| /* Calculate the CRC */ |
| |
| nxffs_wrle32(dathdr.crc, 0); |
| |
| crc = crc32((FAR const uint8_t *)&dathdr, SIZEOF_NXFFS_DATA_HDR); |
| crc = crc32part(&blkinfo->buffer[offset + SIZEOF_NXFFS_DATA_HDR], |
| datlen, crc); |
| |
| if (crc != ecrc) |
| { |
| syslog(LOG_NOTICE, g_format, |
| blkinfo->block, offset, "DATA ", "CRC BAD", |
| (long unsigned int)datlen); |
| return ERROR; |
| } |
| |
| /* If must be a good header */ |
| |
| if (blkinfo->verbose) |
| { |
| syslog(LOG_NOTICE, g_format, |
| blkinfo->block, offset, "DATA ", "OK ", |
| (long unsigned int)datlen); |
| } |
| |
| return SIZEOF_NXFFS_DATA_HDR + datlen; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: nxffs_analyze |
| * |
| * Description: |
| * Analyze the content of one block. |
| * |
| ****************************************************************************/ |
| |
| #if defined(CONFIG_DEBUG_FEATURES) && defined(CONFIG_DEBUG_FS) |
| static inline void nxffs_analyze(FAR struct nxffs_blkinfo_s *blkinfo) |
| { |
| FAR struct nxffs_block_s *blkhdr; |
| ssize_t nbytes; |
| int hdrndx; |
| int datndx; |
| int inndx; |
| int i; |
| |
| /* Verify that there is a header on the block */ |
| |
| blkhdr = (FAR struct nxffs_block_s *)blkinfo->buffer; |
| if (memcmp(blkhdr->magic, g_blockmagic, NXFFS_MAGICSIZE) != 0) |
| { |
| syslog(LOG_NOTICE, g_format, blkinfo->block, 0, "BLOCK", "NO FRMT", |
| blkinfo->geo.blocksize); |
| } |
| else if (blkhdr->state == BLOCK_STATE_GOOD) |
| { |
| size_t datsize = blkinfo->geo.blocksize - SIZEOF_NXFFS_BLOCK_HDR; |
| size_t nerased = nxffs_erased(blkinfo->buffer + SIZEOF_NXFFS_BLOCK_HDR, |
| datsize); |
| if (nerased == datsize) |
| { |
| if (blkinfo->verbose) |
| { |
| syslog(LOG_NOTICE, |
| g_format, |
| blkinfo->block, |
| 0, |
| "BLOCK", |
| "ERASED ", |
| blkinfo->geo.blocksize); |
| } |
| |
| return; |
| } |
| #if 0 /* Too much output, to little information */ |
| else |
| { |
| syslog(LOG_NOTICE, g_format, blkinfo->block, 0, "BLOCK", "IN USE ", |
| blkinfo->geo.blocksize); |
| } |
| #endif |
| } |
| else if (blkhdr->state == BLOCK_STATE_BAD) |
| { |
| syslog(LOG_NOTICE, g_format, blkinfo->block, 0, "BLOCK", "BAD ", |
| blkinfo->geo.blocksize); |
| } |
| else |
| { |
| syslog(LOG_NOTICE, g_format, blkinfo->block, 0, "BLOCK", "CORRUPT", |
| blkinfo->geo.blocksize); |
| } |
| |
| /* Search for Inode and data block headers. */ |
| |
| inndx = 0; |
| datndx = 0; |
| |
| for (i = SIZEOF_NXFFS_BLOCK_HDR; i < blkinfo->geo.blocksize; i++) |
| { |
| uint8_t ch = blkinfo->buffer[i]; |
| |
| if (ch == g_inodemagic[inndx]) |
| { |
| inndx++; |
| datndx = 0; |
| |
| if (inndx == NXFFS_MAGICSIZE) |
| { |
| hdrndx = i - NXFFS_MAGICSIZE + 1; |
| nbytes = nxffs_analyzeinode(blkinfo, hdrndx); |
| if (nbytes > 0) |
| { |
| i = hdrndx + nbytes - 1; |
| } |
| |
| inndx = 0; |
| } |
| } |
| else if (ch == g_datamagic[datndx]) |
| { |
| datndx++; |
| inndx = 0; |
| |
| if (datndx == NXFFS_MAGICSIZE) |
| { |
| hdrndx = i - NXFFS_MAGICSIZE + 1; |
| nbytes = nxffs_analyzedata(blkinfo, hdrndx); |
| if (nbytes > 0) |
| { |
| i = hdrndx + nbytes - 1; |
| } |
| |
| datndx = 0; |
| } |
| } |
| } |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: nxffs_dump |
| * |
| * Description: |
| * Dump a summary of the contents of an NXFFS file system. |
| * CONFIG_DEBUG_FEATURES and CONFIG_DEBUG_FS must be enabled |
| * for this function to do anything. |
| * |
| * Input Parameters: |
| * mtd - The MTD device that provides the interface to NXFFS-formatted |
| * media. |
| * verbose - FALSE: only show errors |
| * |
| * Returned Value: |
| * Zero is returned on success. Otherwise, a negated errno value is |
| * returned to indicate the nature of the failure. |
| * |
| ****************************************************************************/ |
| |
| int nxffs_dump(FAR struct mtd_dev_s *mtd, bool verbose) |
| { |
| #if defined(CONFIG_DEBUG_FEATURES) && defined(CONFIG_DEBUG_FS) |
| struct nxffs_blkinfo_s blkinfo; |
| int ret; |
| |
| /* Get the volume geometry. (casting to uintptr_t first eliminates |
| * complaints on some architectures where the sizeof long is different |
| * from the size of a pointer). |
| */ |
| |
| memset(&blkinfo, 0, sizeof(struct nxffs_blkinfo_s)); |
| ret = MTD_IOCTL(mtd, |
| MTDIOC_GEOMETRY, |
| (unsigned long)((uintptr_t)&blkinfo.geo)); |
| if (ret < 0) |
| { |
| ferr("ERROR: MTD ioctl(MTDIOC_GEOMETRY) failed: %d\n", -ret); |
| return ret; |
| } |
| |
| /* Save the verbose output indication */ |
| |
| blkinfo.verbose = verbose; |
| |
| /* Allocate a buffer to hold one block */ |
| |
| blkinfo.buffer = fs_heap_malloc(blkinfo.geo.blocksize); |
| if (!blkinfo.buffer) |
| { |
| ferr("ERROR: Failed to allocate block cache\n"); |
| return -ENOMEM; |
| } |
| |
| /* Now read every block on the device */ |
| |
| syslog(LOG_NOTICE, "NXFFS Dump:\n"); |
| syslog(LOG_NOTICE, g_hdrformat); |
| |
| blkinfo.nblocks = blkinfo.geo.erasesize * blkinfo.geo.neraseblocks / |
| blkinfo.geo.blocksize; |
| for (blkinfo.block = 0, blkinfo.offset = 0; |
| blkinfo.block < blkinfo.nblocks; |
| blkinfo.block++, blkinfo.offset += blkinfo.geo.blocksize) |
| { |
| /* Read the next block */ |
| |
| ret = MTD_BREAD(mtd, blkinfo.block, 1, blkinfo.buffer); |
| if (ret < 0) |
| { |
| #ifndef CONFIG_NXFFS_NAND |
| /* Read errors are fatal */ |
| |
| ferr("ERROR: Failed to read block %d\n", blkinfo.block); |
| fs_heap_free(blkinfo.buffer); |
| return ret; |
| #else |
| /* A read error is probably fatal on all media but NAND. |
| * On NAND, the read error probably just signifies a block |
| * with an uncorrectable ECC failure. So, to handle NAND, |
| * just report the read error and continue. |
| */ |
| |
| syslog(LOG_NOTICE, g_format, blkinfo.block, 0, "BLOCK", "RD FAIL", |
| blkinfo.geo.blocksize); |
| #endif |
| } |
| else |
| { |
| /* Analyze the block that we just read */ |
| |
| nxffs_analyze(&blkinfo); |
| } |
| } |
| |
| syslog(LOG_NOTICE, "%" PRIiOFF " blocks analyzed\n", blkinfo.nblocks); |
| |
| fs_heap_free(blkinfo.buffer); |
| return OK; |
| |
| #else |
| return -ENOSYS; |
| #endif |
| } |