yaffs_initialize_spare_format(YAFFSFS_INFO * yfs, TSK_OFF_T maxBlocksToTest){

    // Testing parameters - can all be changed
    unsigned int blocksToTest = 10;  // Number of blocks (64 chunks) to test
    unsigned int chunksToTest = 10;  // Number of chunks to test in each block
    unsigned int minChunksRead = 10; // Minimum number of chunks we require to run the test (we might not get the full number we want to test for a very small file)

    unsigned int chunkSize = yfs->page_size + yfs->spare_size;
    unsigned int blockSize = yfs->chunks_per_block * chunkSize;

    TSK_FS_INFO *fs = &(yfs->fs_info);
    unsigned char *spareBuffer;

    unsigned int blockIndex;
    unsigned int chunkIndex;

    unsigned int currentOffset;

    unsigned char * allSpares;
    unsigned int allSparesLength;

    TSK_OFF_T maxBlocks;

    bool skipBlock;
    int goodOffset;
    unsigned int nGoodSpares;
    unsigned int nBlocksTested;

    int okOffsetFound = 0;   // Used as a flag for if we've found an offset that sort of works but doesn't seem great
    int goodOffsetFound = 0; // Flag to mark that we've found an offset that also passed secondary testing
    int bestOffset = 0;

    bool allSameByte; // Used in test that the spare area fields not be one repeated byte

    unsigned int i;

    int thisChunkBase;
    int lastChunkBase;

    // The spare area needs to be at least 16 bytes to run the test
    if(yfs->spare_size < 16){
        if(tsk_verbose && (! yfs->autoDetect)){
            tsk_fprintf(stderr,
                "yaffs_initialize_spare_format failed - given spare size (%d) is not large enough to contain needed fields\n", yfs->spare_size);
        }
        return TSK_ERR;
    }

    if ((spareBuffer = (unsigned char*) tsk_malloc(yfs->spare_size)) == NULL) {
        return TSK_ERR;
    }

    allSparesLength = yfs->spare_size * blocksToTest * chunksToTest;
    if ((allSpares = (unsigned char*) tsk_malloc(allSparesLength)) == NULL) {
        free(spareBuffer);
        return TSK_ERR;
    }

    // Initialize the pointers to one of the configurations we've seen (thought these defaults should not get used)
    yfs->spare_seq_offset = 0;
    yfs->spare_obj_id_offset = 4;
    yfs->spare_chunk_id_offset = 8;
    yfs->spare_nbytes_offset = 12;

    // Assume the data we want is 16 consecutive bytes in the order:
    //  seq num, obj id, chunk id, byte count
    //  (not sure we're guaranteed this but we wouldn't be able to deal with the alternative anyway)
    // Seq num is the important one. This number is constant in each block (block = 64 chunks), meaning
    //  all chunks in a block will share the same sequence number. The YAFFS2 descriptions would seem to
    //  indicate it should be different for each block, but this doesn't seem to always be the case.
    //  In particular we frequently see the 0x1000 seq number used over multiple blocks, but this isn't the only
    //  observed exception.

    // Calculate the number of blocks in the image
    maxBlocks = yfs->fs_info.img_info->size / (yfs->chunks_per_block * chunkSize);

    // If maxBlocksToTest = 0 (unlimited), set it to the total number of blocks
    // Also reduce the number of blocks to test if it is larger than the total number of blocks
    if ((maxBlocksToTest == 0) || (maxBlocksToTest > maxBlocks)){
        maxBlocksToTest = maxBlocks;
    }

    nGoodSpares = 0;
    nBlocksTested = 0;
    for (TSK_OFF_T blockIndex = 0;blockIndex < maxBlocksToTest;blockIndex++){

        // Read the last spare area that we want to test first
        TSK_OFF_T offset = (TSK_OFF_T)blockIndex * blockSize + (chunksToTest - 1) * chunkSize + yfs->page_size;
        ssize_t cnt = tsk_img_read(fs->img_info, offset, (char *) spareBuffer,
            yfs->spare_size);
        if ((cnt < 0) || ((unsigned int)cnt < yfs->spare_size)) {
            break;
        }

        // Is the spare all 0xff / 0x00?
        // If not, we know we should have all allocated chunks since YAFFS2 writes sequentially in a block
        //  - can't have an unallocated chunk followed by an allocated one
        // We occasionally see almost all null spare area with a few 0xff, which is not a valid spare.
        skipBlock = true;
        for (i = 0;i < yfs->spare_size;i++){
            if((spareBuffer[i] != 0xff) && (spareBuffer[i] != 0x00)){
                skipBlock = false;
                break;
            }
        }

        if (skipBlock){
            continue;
        }

        // If this block is potentialy valid (i.e., the spare contains something besides 0x00 and 0xff), copy all the spares into
        // the big array of extracted spare areas

        // Copy this spare area
        nGoodSpares++;
        for (i = 0;i < yfs->spare_size;i++){
            allSpares[nBlocksTested * yfs->spare_size * chunksToTest + (chunksToTest - 1) * yfs->spare_size + i] = spareBuffer[i];
        }

        // Copy all earlier spare areas in the block
        for (chunkIndex = 0;chunkIndex < chunksToTest - 1;chunkIndex++){
            offset = blockIndex * blockSize + chunkIndex * chunkSize + yfs->page_size;
            cnt = tsk_img_read(fs->img_info, offset, (char *) spareBuffer,
                yfs->spare_size);
            if ((cnt < 0) || ((unsigned int)cnt < yfs->spare_size)) {
                // We really shouldn't run out of data here since we already read in the furthest entry
                break; // Break out of chunksToTest loop
            }

            nGoodSpares++;
            for(i = 0;i < yfs->spare_size;i++){
                allSpares[nBlocksTested * yfs->spare_size * chunksToTest + chunkIndex * yfs->spare_size + i] = spareBuffer[i];
            }
        }

        // Record that we've found a potentially valid block
        nBlocksTested++;

        // If we've found enough potentailly valid blocks, break
        if (nBlocksTested >= blocksToTest){
            break;
        }
    }

    // Make sure we read enough data to reasonably perform the testing
    if (nGoodSpares < minChunksRead){

        if (tsk_verbose && (! yfs->autoDetect)){
            tsk_fprintf(stderr,
                "yaffs_initialize_spare_format failed - not enough potentially valid data could be read\n");
        }

        free(spareBuffer);
        free(allSpares);
        return TSK_ERR;
    }

    if (tsk_verbose && (! yfs->autoDetect)){
        tsk_fprintf(stderr,
            "yaffs_initialize_spare_format: Testing potential offsets for the sequence number in the spare area\n");
    }

    // Print out the collected spare areas if we're in verbose mode
    if(tsk_verbose && (! yfs->autoDetect)){
        for(blockIndex = 0;blockIndex < nBlocksTested;blockIndex++){
            for(chunkIndex = 0;chunkIndex < chunksToTest;chunkIndex++){
                for(i = 0;i < yfs->spare_size;i++){
                    fprintf(stderr, "%02x", allSpares[blockIndex * yfs->spare_size * chunksToTest + chunkIndex * yfs->spare_size + i]);
                }
                fprintf(stderr, "\n");
            }
        }
    }

    // Test all indices into the spare area (that leave enough space for all 16 bytes)
    for(currentOffset = 0;currentOffset <= yfs->spare_size - 16;currentOffset++){
        goodOffset = 1;
        for(blockIndex = 0;blockIndex < nBlocksTested;blockIndex++){
            for(chunkIndex = 1;chunkIndex < chunksToTest;chunkIndex++){

                lastChunkBase = blockIndex * yfs->spare_size * chunksToTest + (chunkIndex - 1) * yfs->spare_size;
                thisChunkBase = lastChunkBase + yfs->spare_size;

                // Seq num should not be all 0xff (we tested earlier that the chunk has been initialized)
                if((0xff == allSpares[thisChunkBase + currentOffset]) &&
                    (0xff == allSpares[thisChunkBase + currentOffset + 1]) &&
                    (0xff == allSpares[thisChunkBase + currentOffset + 2]) &&
                    (0xff == allSpares[thisChunkBase + currentOffset + 3])){
                        if(tsk_verbose && (! yfs->autoDetect)){
                            tsk_fprintf(stderr,
                                "yaffs_initialize_spare_format: Eliminating offset %d - invalid sequence number 0xffffffff\n",
                                currentOffset);
                        }
                        goodOffset = 0;
                        break;
                }

                // Seq num should not be zero
                if((0 == allSpares[thisChunkBase + currentOffset]) &&
                    (0 == allSpares[thisChunkBase + currentOffset + 1]) &&
                    (0 == allSpares[thisChunkBase + currentOffset + 2]) &&
                    (0 == allSpares[thisChunkBase + currentOffset + 3])){
                        if(tsk_verbose && (! yfs->autoDetect)){
                            tsk_fprintf(stderr,
                                "yaffs_initialize_spare_format: Eliminating offset %d - invalid sequence number 0\n",
                                currentOffset);
                        }
                        goodOffset = 0;
                        break;
                }

                // Seq num should match the previous one in the block
                if((allSpares[lastChunkBase + currentOffset] != allSpares[thisChunkBase + currentOffset]) ||
                    (allSpares[lastChunkBase + currentOffset + 1] != allSpares[thisChunkBase + currentOffset + 1]) ||
                    (allSpares[lastChunkBase + currentOffset + 2] != allSpares[thisChunkBase + currentOffset + 2]) ||
                    (allSpares[lastChunkBase + currentOffset + 3] != allSpares[thisChunkBase + currentOffset + 3])){
                        if(tsk_verbose && (! yfs->autoDetect)){
                            tsk_fprintf(stderr,
                                "yaffs_initialize_spare_format: Eliminating offset %d - did not match previous chunk sequence number\n",
                                currentOffset);
                        }
                        goodOffset = 0;
                        break;
                }

                // Obj id should not be zero
                if((0 == allSpares[thisChunkBase + currentOffset + 4]) &&
                    (0 == allSpares[thisChunkBase + currentOffset + 5]) &&
                    (0 == allSpares[thisChunkBase + currentOffset + 6]) &&
                    (0 == allSpares[thisChunkBase + currentOffset + 7])){
                        if(tsk_verbose && (! yfs->autoDetect)){
                            tsk_fprintf(stderr,
                                "yaffs_initialize_spare_format: Eliminating offset %d - invalid object id 0\n",
                                currentOffset);
                        }
                        goodOffset = 0;
                        break;
                }

                // All 16 bytes should not be the same
                // (It is theoretically possible that this could be valid, but incredibly unlikely)
                allSameByte = true;
                for(i = 1;i < 16;i++){
                    if(allSpares[thisChunkBase + currentOffset] != allSpares[thisChunkBase + currentOffset + i]){
                        allSameByte = false;
                        break;
                    }
                }
                if(allSameByte){
                    if(tsk_verbose && (! yfs->autoDetect)){
                        tsk_fprintf(stderr,
                            "yaffs_initialize_spare_format: Eliminating offset %d - all repeated bytes\n",
                            currentOffset);
                    }
                    goodOffset = 0;
                    break;
                }

            } // End of loop over chunks

            if(!goodOffset){ // Break out of loop over blocks
                break;
            }
        }
        if(goodOffset){

            // Note that we've found an offset that is at least promising
            if((! goodOffsetFound) && (! okOffsetFound)){
                bestOffset = currentOffset;
            }
            okOffsetFound = 1;

            if(tsk_verbose && (! yfs->autoDetect)){
                tsk_fprintf(stderr,
                    "yaffs_initialize_spare_format: Found potential spare offsets:  %d (sequence number), %d (object id), %d (chunk id), %d (n bytes)\n",
                    currentOffset, currentOffset+4, currentOffset+8, currentOffset+12);
            }

            // Now do some more tests
            // Really need some more real-world test data to do this right.
            int possibleError = 0;

            // We probably don't want the first byte to always be 0xff
            int firstByteFF = 1;
            for(blockIndex = 0;blockIndex < nBlocksTested;blockIndex++){
                for(chunkIndex = 1;chunkIndex < chunksToTest;chunkIndex++){
                    if(allSpares[blockIndex * yfs->spare_size * chunksToTest + chunkIndex * yfs->spare_size + currentOffset] != 0xff){
                        firstByteFF = 0;
                    }
                }
            }

            if(firstByteFF){
                if(tsk_verbose && (! yfs->autoDetect)){
                    tsk_fprintf(stderr,
                        "yaffs_initialize_spare_format:  Previous data starts with all 0xff bytes. Looking for better offsets.\n");
                }
                possibleError = 1;
            }

            if(! possibleError){

                // If we already have a good offset, print this one out but don't record it
                if(! goodOffsetFound){

                    goodOffsetFound = 1;
                    bestOffset = currentOffset;

                    // Offset passed additional testing and we haven't seen an earlier good one, so go ahead and use it
                    if(tsk_verbose && (! yfs->autoDetect)){
                        tsk_fprintf(stderr,
                            "yaffs_initialize_spare_format:  Previous offsets appear good - will use as final offsets\n");
                    }

                }
                else{
                    // Keep using the old one
                    if(tsk_verbose && (! yfs->autoDetect)){
                        tsk_fprintf(stderr,
                            "yaffs_initialize_spare_format:  Previous offsets appear good but staying with earlier valid ones\n");
                    }
                }
            }
        }
    }

    free(spareBuffer);
    free(allSpares);

    if(okOffsetFound || goodOffsetFound){
        // Record everything
        yfs->spare_seq_offset = bestOffset;
        yfs->spare_obj_id_offset = bestOffset + 4;
        yfs->spare_chunk_id_offset = bestOffset + 8;
        yfs->spare_nbytes_offset = bestOffset + 12;

        if(tsk_verbose && (! yfs->autoDetect)){
            tsk_fprintf(stderr,
                "yaffs_initialize_spare_format: Final offsets: %d (sequence number), %d (object id), %d (chunk id), %d (n bytes)\n",
                bestOffset, bestOffset+4, bestOffset+8, bestOffset+12);
            tsk_fprintf(stderr,
                "If these do not seem valid: %s\n", YAFFS_HELP_MESSAGE);
        }
        return TSK_OK;
    }
    else{
        return TSK_ERR;
    }
}


    yaffscache_version_find_by_inode(YAFFSFS_INFO *yfs, TSK_INUM_T inode, YaffsCacheVersion **version, YaffsCacheObject **obj_ret) {
        uint32_t obj_id, version_num;
        YaffsCacheObject *obj;
        YaffsCacheVersion *curr;

        if (version == NULL) {
            return TSK_ERR;
        }

        // convert inode to obj and version and find it in cache
        if (yaffscache_inode_to_obj_id_and_version(inode, &obj_id, &version_num) != TSK_OK) {
            *version = NULL;
            return TSK_ERR;
        }

        if (yaffscache_object_find(yfs, obj_id, &obj) != TSK_OK) {
            *version = NULL;
            return TSK_ERR;
        }

        if (version_num == 0) {
            if (obj_ret != NULL) {
                *obj_ret = obj;
            }
            *version = obj->yco_latest;
            return TSK_OK;
        }

        // Find the requested version in the list.
        for(curr = obj->yco_latest; curr != NULL; curr = curr->ycv_prior) {
            if (curr->ycv_version == version_num) {
                if (obj_ret != NULL) {
                    *obj_ret = obj;
                }
                *version = curr;
                return TSK_OK;
            }
        }

        if (obj_ret != NULL) {
            *obj_ret = NULL;
        }
        *version = NULL;
        return TSK_ERR;
}

Fix stack buffer overflow in yaffsfs_istat

Prevent a stack buffer overflow in yaffsfs_istat by increasing the buffer size to the size required by tsk_fs_time_to_str.

static uint8_t
    yaffsfs_read_header(YAFFSFS_INFO *yfs, YaffsHeader ** header, TSK_OFF_T offset);

typedef TSK_RETVAL_ENUM yc_find_children_cb(YaffsCacheObject *obj, YaffsCacheVersion *version, void *args);

typedef struct _dir_open_cb_args {
    YAFFSFS_INFO *yfs;
    TSK_FS_DIR *dir;
    TSK_INUM_T parent_addr;
} dir_open_cb_args;


    yaffscache_objects_stats(YAFFSFS_INFO *yfs,
    unsigned int *obj_count,
    uint32_t *obj_first, uint32_t *obj_last,
    uint32_t *version_count,
    uint32_t *version_first, uint32_t *version_last)
{
    YaffsCacheObject *obj;
    YaffsCacheVersion *ver;

    /* deleted and unlinked special objects don't have headers */
    *obj_count = 2;
    *obj_first = 0xffffffff;
    *obj_last = 0;

    *version_count = 0;
    *version_first = 0xffffffff;
    *version_last = 0;

    for(obj = yfs->cache_objects; obj != NULL; obj = obj->yco_next) {
        *obj_count += 1;
        if (obj->yco_obj_id < *obj_first)
            *obj_first = obj->yco_obj_id;
        if (obj->yco_obj_id > *obj_last)
            *obj_last = obj->yco_obj_id;

        for(ver = obj->yco_latest; ver != NULL; ver = ver->ycv_prior) {
            *version_count += 1;
            if (ver->ycv_seq_number < *version_first)
                *version_first = ver->ycv_seq_number;
            if (ver->ycv_seq_number > *version_last)
                *version_last = ver->ycv_seq_number;
        }
    }
}

static const int TWELVE_BITS_MASK = 0xFFF;

static uint8_t
    yaffsfs_read_header(YAFFSFS_INFO *yfs, YaffsHeader ** header, TSK_OFF_T offset);

typedef TSK_RETVAL_ENUM yc_find_children_cb(YaffsCacheObject *obj, YaffsCacheVersion *version, void *args);

typedef struct {
    FILE *hFile;
    int idx;
} YAFFSFS_PRINT_ADDR;

typedef struct _dir_open_cb_args {
    YAFFSFS_INFO *yfs;
    TSK_FS_DIR *dir;
    TSK_INUM_T parent_addr;
} dir_open_cb_args;


    yaffsfs_block_getflags(TSK_FS_INFO *fs, TSK_DADDR_T a_addr)
{
    YAFFSFS_INFO *yfs = (YAFFSFS_INFO *)fs;
    TSK_FS_BLOCK_FLAG_ENUM flags = TSK_FS_BLOCK_FLAG_UNUSED;

    TSK_OFF_T offset = (a_addr * (fs->block_pre_size + fs->block_size + fs->block_post_size)) + yfs->page_size;
    YaffsSpare *spare = NULL;
    YaffsHeader *header = NULL;
    if (yaffsfs_read_spare(yfs, &spare, offset) != TSK_OK) {
        /* NOTE: Uh, how do we signal error? */
        return flags;
    }

    if (yaffsfs_is_spare_valid(yfs, spare) == TSK_OK) {
        /* XXX: Do we count blocks of older versions unallocated?
        *      If so, we need a smarter way to do this :/
        *
        *      Walk the object from this block and see if this
        *      block is used in the latest version. Could pre-
        *      calculate this at cache time as well.
        */

        if (spare->chunk_id == 0) {
            flags = (TSK_FS_BLOCK_FLAG_ENUM)(flags | TSK_FS_BLOCK_FLAG_META);
        } else {
            flags = (TSK_FS_BLOCK_FLAG_ENUM)(flags | TSK_FS_BLOCK_FLAG_CONT);
        }

        // Have obj id and offset
        // 1. Is the current version of this object allocated?
        // 2. If this is a header, is it the header of the current version?
        // 3. Is the chunk id too big given the current header?
        // 4. Is there a more recent version of this chunk id?
        YaffsCacheObject * obj = NULL;
        yaffscache_object_find(yfs, spare->object_id, &obj);

        // The result really shouldn't be NULL since we loaded every chunk
        if(obj != NULL){
            if(! yaffs_is_version_allocated(yfs, spare->object_id)){
                // If the current version isn't allocated, then no chunks in it are
                flags = (TSK_FS_BLOCK_FLAG_ENUM)(flags | TSK_FS_BLOCK_FLAG_UNALLOC);
            }
            else if (obj->yco_latest == NULL || obj->yco_latest->ycv_header_chunk == NULL) {
                flags = (TSK_FS_BLOCK_FLAG_ENUM)(flags | TSK_FS_BLOCK_FLAG_UNALLOC);
            }
            else if(spare->chunk_id == 0){
                if(obj->yco_latest->ycv_header_chunk->ycc_offset == offset - yfs->page_size){
                    // Have header chunk and it's the most recent header chunk
                    flags = (TSK_FS_BLOCK_FLAG_ENUM)(flags | TSK_FS_BLOCK_FLAG_ALLOC);
                }
                else{
                    // Have header chunk but isn't the most recent
                    flags = (TSK_FS_BLOCK_FLAG_ENUM)(flags | TSK_FS_BLOCK_FLAG_UNALLOC);
                }
            }
            else{
                // Read in the full header
                yaffsfs_read_header(yfs, &header, obj->yco_latest->ycv_header_chunk->ycc_offset);

                // chunk_id is 1-based, so for example chunk id 2 would be too big for a file
                //   500 bytes long
                if(header->file_size <= ((spare->chunk_id - 1) * (fs->block_size))){
                    flags = (TSK_FS_BLOCK_FLAG_ENUM)(flags | TSK_FS_BLOCK_FLAG_UNALLOC);
                }
                else{
                    // Since at this point we know there should be a chunk with this chunk id in the file, if
                    // this is the most recent version of the chunk assume it's part of the current version of the object.
                    YaffsCacheChunk * curr = obj->yco_latest->ycv_last_chunk;
                    while(curr != NULL){ // curr should really never make it to the beginning of the list

                        // Did we find our chunk?
                        if(curr->ycc_offset == offset - yfs->page_size){
                            flags = (TSK_FS_BLOCK_FLAG_ENUM)(flags | TSK_FS_BLOCK_FLAG_ALLOC);
                            break;
                        }

                        // Did we find a different chunk with our chunk id?
                        if(curr->ycc_chunk_id == spare->chunk_id){
                            flags = (TSK_FS_BLOCK_FLAG_ENUM)(flags | TSK_FS_BLOCK_FLAG_UNALLOC);
                            break;
                        }
                        curr = curr->ycc_prev;
                    }
                }
            }
        }

    } else {
        flags = (TSK_FS_BLOCK_FLAG_ENUM)(flags | TSK_FS_BLOCK_FLAG_UNUSED | TSK_FS_BLOCK_FLAG_UNALLOC);
    }

    free(spare);
    free(header);
    return flags;
}

static uint8_t
    yaffsfs_read_header(YAFFSFS_INFO *yfs, YaffsHeader ** header, TSK_OFF_T offset);

static uint8_t
    yaffsfs_load_attrs(TSK_FS_FILE *file);

typedef TSK_RETVAL_ENUM yc_find_children_cb(YaffsCacheObject *obj, YaffsCacheVersion *version, void *args);

typedef struct _dir_open_cb_args {
    YAFFSFS_INFO *yfs;
    TSK_FS_DIR *dir;
    TSK_INUM_T parent_addr;
} dir_open_cb_args;


    yaffsfs_fscheck(TSK_FS_INFO * /*fs*/, FILE * /*hFile*/)
{
    tsk_error_reset();
    tsk_error_set_errno(TSK_ERR_FS_UNSUPFUNC);
    tsk_error_set_errstr("fscheck not implemented yet for YAFFS");
    return 1;
}

typedef struct {
    FILE *hFile;
    int idx;
} YAFFSFS_PRINT_ADDR;


    yaffscache_chunks_free(YAFFSFS_INFO *yfs)
{
    if((yfs != NULL) && (yfs->chunkMap != NULL)){
        // Free the YaffsCacheChunks in each ChunkGroup
        std::map<unsigned int,YaffsCacheChunkGroup>::iterator iter;
        for( iter = yfs->chunkMap->begin(); iter != yfs->chunkMap->end(); ++iter ) {
            YaffsCacheChunk *chunk = yfs->chunkMap->operator[](iter->first).cache_chunks_head;
            while(chunk != NULL) {
                YaffsCacheChunk *to_free = chunk;
                chunk = chunk->ycc_next;
                free(to_free);
            }
        }

        // Free the map
        yfs->chunkMap->clear();
        delete yfs->chunkMap;
    }

}

static const int TWELVE_BITS_MASK = 0xFFF;

static uint8_t
    yaffsfs_read_header(YAFFSFS_INFO *yfs, YaffsHeader ** header, TSK_OFF_T offset);

typedef struct {
    FILE *hFile;
    int idx;
} YAFFSFS_PRINT_ADDR;

typedef struct _dir_open_cb_args {
    YAFFSFS_INFO *yfs;
    TSK_FS_DIR *dir;
    TSK_INUM_T parent_addr;
} dir_open_cb_args;


    yaffscache_versions_compute(YAFFSFS_INFO *yfs)
{
    std::map<unsigned int,YaffsCacheChunkGroup>::iterator iter;
    for( iter = yfs->chunkMap->begin(); iter != yfs->chunkMap->end(); ++iter ) {
        YaffsCacheChunk *chunk_curr = yfs->chunkMap->operator[](iter->first).cache_chunks_head;

        while(chunk_curr != NULL) {
            if (yaffscache_versions_insert_chunk(yfs, chunk_curr) != TSK_OK) {
                return TSK_ERR;
            }

            chunk_curr = chunk_curr->ycc_next;
        }
    }

    return TSK_OK;
}


    yaffsfs_istat(TSK_FS_INFO *fs, TSK_FS_ISTAT_FLAG_ENUM flags, FILE * hFile, TSK_INUM_T inum,
    TSK_DADDR_T numblock, int32_t sec_skew)
{
    TSK_FS_META *fs_meta;
    TSK_FS_FILE *fs_file;
    YAFFSFS_INFO *yfs = (YAFFSFS_INFO *)fs;
    char ls[12];
    YAFFSFS_PRINT_ADDR print;
    char timeBuf[128];
    YaffsCacheObject * obj = NULL;
    YaffsCacheVersion * version = NULL;
    YaffsHeader * header = NULL;

    yaffscache_version_find_by_inode(yfs, inum, &version, &obj);

    if ((fs_file = tsk_fs_file_open_meta(fs, NULL, inum)) == NULL) {
        return 1;
    }
    fs_meta = fs_file->meta;

    tsk_fprintf(hFile, "inode: %" PRIuINUM "\n", inum);
    tsk_fprintf(hFile, "%sAllocated\n",
        (fs_meta->flags & TSK_FS_META_FLAG_ALLOC) ? "" : "Not ");

    if (fs_meta->link)
        tsk_fprintf(hFile, "symbolic link to: %s\n", fs_meta->link);

    tsk_fprintf(hFile, "uid / gid: %" PRIuUID " / %" PRIuGID "\n",
        fs_meta->uid, fs_meta->gid);

    tsk_fs_meta_make_ls(fs_meta, ls, sizeof(ls));
    tsk_fprintf(hFile, "mode: %s\n", ls);

    tsk_fprintf(hFile, "size: %" PRIdOFF "\n", fs_meta->size);
    tsk_fprintf(hFile, "num of links: %d\n", fs_meta->nlink);

    if(version != NULL){
        yaffsfs_read_header(yfs, &header, version->ycv_header_chunk->ycc_offset);
        if(header != NULL){
            tsk_fprintf(hFile, "Name: %s\n", header->name);
        }
    }

    if (sec_skew != 0) {
        tsk_fprintf(hFile, "\nAdjusted Inode Times:\n");
        fs_meta->mtime -= sec_skew;
        fs_meta->atime -= sec_skew;
        fs_meta->ctime -= sec_skew;

        tsk_fprintf(hFile, "Accessed:\t%s\n",
            tsk_fs_time_to_str(fs_meta->atime, timeBuf));
        tsk_fprintf(hFile, "File Modified:\t%s\n",
            tsk_fs_time_to_str(fs_meta->mtime, timeBuf));
        tsk_fprintf(hFile, "Inode Modified:\t%s\n",
            tsk_fs_time_to_str(fs_meta->ctime, timeBuf));

        fs_meta->mtime += sec_skew;
        fs_meta->atime += sec_skew;
        fs_meta->ctime += sec_skew;

        tsk_fprintf(hFile, "\nOriginal Inode Times:\n");
    }
    else {
        tsk_fprintf(hFile, "\nInode Times:\n");
    }

    tsk_fprintf(hFile, "Accessed:\t%s\n",
        tsk_fs_time_to_str(fs_meta->atime, timeBuf));
    tsk_fprintf(hFile, "File Modified:\t%s\n",
        tsk_fs_time_to_str(fs_meta->mtime, timeBuf));
    tsk_fprintf(hFile, "Inode Modified:\t%s\n",
        tsk_fs_time_to_str(fs_meta->ctime, timeBuf));

    if(version != NULL){
        tsk_fprintf(hFile, "\nHeader Chunk:\n");
        tsk_fprintf(hFile, "%" PRIuDADDR "\n", (version->ycv_header_chunk->ycc_offset / (yfs->page_size + yfs->spare_size)));
    }

    if (numblock > 0) {
        TSK_OFF_T lower_size = numblock * fs->block_size;
        fs_meta->size = (lower_size < fs_meta->size)?(lower_size):(fs_meta->size);
    }
    tsk_fprintf(hFile, "\nData Chunks:\n");

    if (flags & TSK_FS_ISTAT_RUNLIST){
        const TSK_FS_ATTR *fs_attr_default =
            tsk_fs_file_attr_get_type(fs_file,
                TSK_FS_ATTR_TYPE_DEFAULT, 0, 0);
        if (fs_attr_default && (fs_attr_default->flags & TSK_FS_ATTR_NONRES)) {
            if (tsk_fs_attr_print(fs_attr_default, hFile)) {
                tsk_fprintf(hFile, "\nError creating run lists  ");
                tsk_error_print(hFile);
                tsk_error_reset();
            }
        }
    }
    else {
        print.idx = 0;
        print.hFile = hFile;

        if (tsk_fs_file_walk(fs_file, TSK_FS_FILE_WALK_FLAG_AONLY,
            (TSK_FS_FILE_WALK_CB)print_addr_act, (void *)&print)) {
            tsk_fprintf(hFile, "\nError reading file:  ");
            tsk_error_print(hFile);
            tsk_error_reset();
        }
        else if (print.idx != 0) {
            tsk_fprintf(hFile, "\n");
        }
    }

    tsk_fs_file_close(fs_file);

    return 0;
}

static const int TWELVE_BITS_MASK = 0xFFF;

static uint8_t
    yaffsfs_read_header(YAFFSFS_INFO *yfs, YaffsHeader ** header, TSK_OFF_T offset);

static uint8_t
    yaffsfs_load_attrs(TSK_FS_FILE *file);

typedef TSK_RETVAL_ENUM yc_find_children_cb(YaffsCacheObject *obj, YaffsCacheVersion *version, void *args);

typedef struct {
    FILE *hFile;
    int idx;
} YAFFSFS_PRINT_ADDR;

typedef struct _dir_open_cb_args {
    YAFFSFS_INFO *yfs;
    TSK_FS_DIR *dir;
    TSK_INUM_T parent_addr;
} dir_open_cb_args;


    yaffsfs_is_spare_valid(YAFFSFS_INFO * /*yfs*/, YaffsSpare *spare)
{
    if (spare == NULL) {
        return 1;
    }

    if ((spare->object_id > YAFFS_MAX_OBJECT_ID) ||
        (spare->seq_number < YAFFS_LOWEST_SEQUENCE_NUMBER) ||
        (spare->seq_number > YAFFS_HIGHEST_SEQUENCE_NUMBER)) {
            return 1;
    }

    return 0;
}

static uint8_t
    yaffsfs_read_header(YAFFSFS_INFO *yfs, YaffsHeader ** header, TSK_OFF_T offset);

typedef struct _dir_open_cb_args {
    YAFFSFS_INFO *yfs;
    TSK_FS_DIR *dir;
    TSK_INUM_T parent_addr;
} dir_open_cb_args;


    yaffscache_obj_id_and_version_to_inode(uint32_t obj_id, uint32_t version_num, TSK_INUM_T *inode) {
        if ((obj_id & ~YAFFS_OBJECT_ID_MASK) != 0) {
            tsk_error_reset();
            tsk_error_set_errno(TSK_ERR_FS);
            tsk_error_set_errstr(
                "yaffsfs_parse_image_load_cache: Max object ID %" PRIu32 " is invalid", obj_id);
            return TSK_ERR;
        }

        if ((version_num & ~YAFFS_VERSION_NUM_MASK) != 0) {
            tsk_error_reset();
            tsk_error_set_errno(TSK_ERR_FS);
            tsk_error_set_errstr(
                "yaffsfs_parse_image_load_cache: Max version number %" PRIu32 " is invalid", version_num);
            return TSK_ERR;
        }

        *inode = obj_id | (version_num << YAFFS_VERSION_NUM_SHIFT);
        return TSK_OK;
}

typedef TSK_RETVAL_ENUM yc_find_children_cb(YaffsCacheObject *obj, YaffsCacheVersion *version, void *args);

typedef struct _dir_open_cb_args {
    YAFFSFS_INFO *yfs;
    TSK_FS_DIR *dir;
    TSK_INUM_T parent_addr;
} dir_open_cb_args;


    yaffsfs_block_walk(TSK_FS_INFO *a_fs, TSK_DADDR_T a_start_blk,
    TSK_DADDR_T a_end_blk, TSK_FS_BLOCK_WALK_FLAG_ENUM a_flags,
    TSK_FS_BLOCK_WALK_CB a_action, void *a_ptr)
{
    TSK_FS_BLOCK *fs_block;
    TSK_DADDR_T addr;

    // clean up any error messages that are lying around
    tsk_error_reset();

    /*
    * Sanity checks.
    */
    if (a_start_blk < a_fs->first_block || a_start_blk > a_fs->last_block) {
        tsk_error_reset();
        tsk_error_set_errno(TSK_ERR_FS_WALK_RNG);
        tsk_error_set_errstr("yaffsfs_block_walk: start block: %" PRIuDADDR,
            a_start_blk);
        return 1;
    }
    if (a_end_blk < a_fs->first_block || a_end_blk > a_fs->last_block
        || a_end_blk < a_start_blk) {
            tsk_error_reset();
            tsk_error_set_errno(TSK_ERR_FS_WALK_RNG);
            tsk_error_set_errstr("yaffsfs_block_walk: end block: %" PRIuDADDR ,
                a_end_blk);
            return 1;
    }

    /* Sanity check on a_flags -- make sure at least one ALLOC is set */
    if (((a_flags & TSK_FS_BLOCK_WALK_FLAG_ALLOC) == 0) &&
        ((a_flags & TSK_FS_BLOCK_WALK_FLAG_UNALLOC) == 0)) {
            a_flags = (TSK_FS_BLOCK_WALK_FLAG_ENUM)
                (a_flags | TSK_FS_BLOCK_WALK_FLAG_ALLOC |
                TSK_FS_BLOCK_WALK_FLAG_UNALLOC);
    }
    if (((a_flags & TSK_FS_BLOCK_WALK_FLAG_META) == 0) &&
        ((a_flags & TSK_FS_BLOCK_WALK_FLAG_CONT) == 0)) {
            a_flags = (TSK_FS_BLOCK_WALK_FLAG_ENUM)
                (a_flags | TSK_FS_BLOCK_WALK_FLAG_CONT | TSK_FS_BLOCK_WALK_FLAG_META);
    }

    if ((fs_block = tsk_fs_block_alloc(a_fs)) == NULL) {
        return 1;
    }

    for (addr = a_start_blk; addr <= a_end_blk; addr++) {
        int retval;
        int myflags;

        myflags = yaffsfs_block_getflags(a_fs, addr);

        // test if we should call the callback with this one
        if ((myflags & TSK_FS_BLOCK_FLAG_META)
            && (!(a_flags & TSK_FS_BLOCK_WALK_FLAG_META)))
            continue;
        else if ((myflags & TSK_FS_BLOCK_FLAG_CONT)
            && (!(a_flags & TSK_FS_BLOCK_WALK_FLAG_CONT)))
            continue;
        else if ((myflags & TSK_FS_BLOCK_FLAG_ALLOC)
            && (!(a_flags & TSK_FS_BLOCK_WALK_FLAG_ALLOC)))
            continue;
        else if ((myflags & TSK_FS_BLOCK_FLAG_UNALLOC)
            && (!(a_flags & TSK_FS_BLOCK_WALK_FLAG_UNALLOC)))
            continue;

        if (tsk_fs_block_get(a_fs, fs_block, addr) == NULL) {
            tsk_error_set_errstr2("yaffsfs_block_walk: block %" PRIuDADDR,
                addr);
            tsk_fs_block_free(fs_block);
            return 1;
        }

        retval = a_action(fs_block, a_ptr);
        if (retval == TSK_WALK_STOP) {
            break;
        }
        else if (retval == TSK_WALK_ERROR) {
            tsk_fs_block_free(fs_block);
            return 1;
        }
    }

    /*
    * Cleanup.
    */
    tsk_fs_block_free(fs_block);
    return 0;
}

static const int TWELVE_BITS_MASK = 0xFFF;

typedef TSK_RETVAL_ENUM yc_find_children_cb(YaffsCacheObject *obj, YaffsCacheVersion *version, void *args);

typedef struct {
    FILE *hFile;
    int idx;
} YAFFSFS_PRINT_ADDR;


    yaffsfs_read_header(YAFFSFS_INFO *yfs, YaffsHeader ** header, TSK_OFF_T offset)
{
    unsigned char *hdr;
    ssize_t cnt;
    YaffsHeader *head;
    TSK_FS_INFO *fs = &(yfs->fs_info);

    if ((hdr = (unsigned char*) tsk_malloc(yfs->page_size)) == NULL) {
        return 1;
    }

    cnt = tsk_img_read(fs->img_info, offset, (char *) hdr,
        yfs->page_size);
    if ((cnt < 0) || ((unsigned int)cnt < yfs->page_size)) {
        free(hdr);
        return 1;
    }

    if ((head = (YaffsHeader*) tsk_malloc( sizeof(YaffsHeader))) == NULL) {
        free(hdr);
        return 1;
    }

    memcpy(&head->obj_type, hdr, 4);
    memcpy(&head->parent_id, &hdr[4], 4);
    memcpy(head->name, (char*) &hdr[0xA], YAFFS_HEADER_NAME_LENGTH);
    memcpy(&head->file_mode, &hdr[0x10C], 4);
    memcpy(&head->user_id, &hdr[0x110], 4);
    memcpy(&head->group_id, &hdr[0x114], 4);
    memcpy(&head->atime, &hdr[0x118], 4);
    memcpy(&head->mtime, &hdr[0x11C], 4);
    memcpy(&head->ctime, &hdr[0x120], 4);
    memcpy(&head->file_size, &hdr[0x124], 4);
    memcpy(&head->equivalent_id, &hdr[0x128], 4);
    memcpy(head->alias, (char*) &hdr[0x12C], YAFFS_HEADER_ALIAS_LENGTH);

    //memcpy(&head->rdev_mode, &hdr[0x1CC], 4);
    //memcpy(&head->win_ctime, &hdr[0x1D0], 8);
    //memcpy(&head->win_atime, &hdr[0x1D8], 8);
    //memcpy(&head->win_mtime, &hdr[0x1E0], 8);
    //memcpy(&head->inband_obj_id, &hdr[0x1E8], 4);
    //memcpy(&head->inband_is_shrink, &hdr[0x1EC], 4);

    // NOTE: This isn't in Android 3.3 kernel but is in YAFFS2 git
    //memcpy(&head->file_size_high, &hdr[0x1F0], 4);

    free(hdr);

    *header = head;
    return 0;
}


    yaffsfs_dir_open_meta(TSK_FS_INFO *a_fs, TSK_FS_DIR ** a_fs_dir,
    TSK_INUM_T a_addr)
{
    TSK_FS_DIR *fs_dir;
    TSK_FS_NAME *fs_name;
    YAFFSFS_INFO *yfs = (YAFFSFS_INFO *)a_fs;
    int should_walk_children = 0;
    uint32_t obj_id;
    uint32_t ver_number;

    if (a_addr < a_fs->first_inum || a_addr > a_fs->last_inum) {
        tsk_error_reset();
        tsk_error_set_errno(TSK_ERR_FS_WALK_RNG);
        tsk_error_set_errstr("yaffs_dir_open_meta: Invalid inode value: %"
            PRIuINUM, a_addr);
        return TSK_ERR;
    }
    else if (a_fs_dir == NULL) {
        tsk_error_reset();
        tsk_error_set_errno(TSK_ERR_FS_ARG);
        tsk_error_set_errstr("yaffs_dir_open_meta: NULL fs_dir argument given");
        return TSK_ERR;
    }

    fs_dir = *a_fs_dir;

    if (fs_dir) {
        tsk_fs_dir_reset(fs_dir);
        fs_dir->addr = a_addr;
    }
    else if ((*a_fs_dir = fs_dir = tsk_fs_dir_alloc(a_fs, a_addr, 128)) == NULL) {
        return TSK_ERR;
    }

    if (tsk_verbose)
        fprintf(stderr,"yaffs_dir_open_meta: called for directory %" PRIu32 "\n", (uint32_t) a_addr);

    //  handle the orphan directory if its contents were requested
    if (a_addr == TSK_FS_ORPHANDIR_INUM(a_fs)) {
        return tsk_fs_dir_find_orphans(a_fs, fs_dir);
    }

    if ((fs_name = tsk_fs_name_alloc(YAFFSFS_MAXNAMLEN, 0)) == NULL) {
        return TSK_ERR;
    }

    if ((fs_dir->fs_file =
        tsk_fs_file_open_meta(a_fs, NULL, a_addr)) == NULL) {
            tsk_error_errstr2_concat(" - yaffs_dir_open_meta");
            tsk_fs_name_free(fs_name);
            return TSK_ERR;
    }

    // extract obj_id and ver_number from inum
    yaffscache_inode_to_obj_id_and_version(a_addr, &obj_id, &ver_number);

    // Decide if we should walk the directory structure
    if (obj_id == YAFFS_OBJECT_DELETED ||
        obj_id == YAFFS_OBJECT_UNLINKED) {
            should_walk_children = 1;
    }
    else {
        YaffsCacheObject *obj;
        YaffsCacheVersion *versionFound;
        TSK_RETVAL_ENUM result = yaffscache_version_find_by_inode(yfs, a_addr, &versionFound, &obj);
        if (result != TSK_OK) {
            if (tsk_verbose)
                tsk_fprintf(stderr, "yaffsfs_dir_open_meta: yaffscache_version_find_by_inode failed! (inode: %d\n", a_addr);
            tsk_fs_name_free(fs_name);
            return TSK_ERR;
        }

        /* Only attach files onto the latest version of the directory */
        should_walk_children = (obj->yco_latest == versionFound);
    }

    // Search the cache for the children of this object and add them to fs_dir
    if (should_walk_children) {
        dir_open_cb_args args;
        args.yfs = yfs;
        args.dir = fs_dir;
        args.parent_addr = a_addr;
        yaffscache_find_children(yfs, a_addr, yaffs_dir_open_meta_cb, &args);
    }

    // add special entries to root directory
    if (obj_id == YAFFS_OBJECT_ROOT) {
        strncpy(fs_name->name, YAFFS_OBJECT_UNLINKED_NAME, fs_name->name_size);
        fs_name->meta_addr = YAFFS_OBJECT_UNLINKED;
        fs_name->type = TSK_FS_NAME_TYPE_DIR;
        fs_name->flags = TSK_FS_NAME_FLAG_ALLOC;
        if (tsk_fs_dir_add(fs_dir, fs_name)) {
            tsk_fs_name_free(fs_name);
            return TSK_ERR;
        }

        strncpy(fs_name->name, YAFFS_OBJECT_DELETED_NAME, fs_name->name_size);
        fs_name->meta_addr = YAFFS_OBJECT_DELETED;
        fs_name->type = TSK_FS_NAME_TYPE_DIR;
        fs_name->flags = TSK_FS_NAME_FLAG_ALLOC;
        if (tsk_fs_dir_add(fs_dir, fs_name)) {
            tsk_fs_name_free(fs_name);
            return TSK_ERR;
        }

        // orphan directory
        if (tsk_fs_dir_make_orphan_dir_name(a_fs, fs_name)) {
            tsk_fs_name_free(fs_name);
            return TSK_ERR;
        }
        fs_name->meta_addr = yfs->fs_info.last_inum;
        fs_name->type = TSK_FS_NAME_TYPE_DIR;
        fs_name->flags = TSK_FS_NAME_FLAG_ALLOC;
        if (tsk_fs_dir_add(fs_dir, fs_name)) {
            tsk_fs_name_free(fs_name);
            return TSK_ERR;
        }
    }

    tsk_fs_name_free(fs_name);
    return TSK_OK;
}


    yaffsfs_load_attrs(TSK_FS_FILE *file)
{
    TSK_FS_ATTR *attr;
    TSK_FS_META *meta;
    TSK_FS_INFO *fs;
    YAFFSFS_INFO *yfs;
    TSK_FS_ATTR_RUN *data_run;
    TSK_DADDR_T file_block_count;
    YaffsCacheObject *obj;
    YaffsCacheVersion *version;
    TSK_RETVAL_ENUM result;
    TSK_LIST *chunks_seen = NULL;
    YaffsCacheChunk *curr;
    TSK_FS_ATTR_RUN *data_run_new;

    if (file == NULL || file->meta == NULL || file->fs_info == NULL)
    {
        tsk_error_set_errno(TSK_ERR_FS_ARG);
        tsk_error_set_errstr
            ("yaffsfs_load_attrs: called with NULL pointers");
        return 1;
    }

    meta = file->meta;
    yfs = (YAFFSFS_INFO *)file->fs_info;
    fs = &yfs->fs_info;

    // see if we have already loaded the runs
    if ((meta->attr != NULL)
        && (meta->attr_state == TSK_FS_META_ATTR_STUDIED)) {
            return 0;
    }
    else if (meta->attr_state == TSK_FS_META_ATTR_ERROR) {
        return 1;
    }
    // not sure why this would ever happen, but...
    else if (meta->attr != NULL) {
        tsk_fs_attrlist_markunused(meta->attr);
    }
    else if (meta->attr == NULL) {
        meta->attr = tsk_fs_attrlist_alloc();
    }

    attr = tsk_fs_attrlist_getnew(meta->attr, TSK_FS_ATTR_NONRES);
    if (attr == NULL) {
        meta->attr_state = TSK_FS_META_ATTR_ERROR;
        return 1;
    }

    if (meta->size == 0) {
        data_run = NULL;
    }
    else {
        /* BC: I'm not entirely sure this is needed.  My guess is that
         * this was done instead of maintaining the head of the list of
         * runs.  In theory, the tsk_fs_attr_add_run() method should handle
         * the fillers. */
        data_run = tsk_fs_attr_run_alloc();
        if (data_run == NULL) {
            tsk_fs_attr_run_free(data_run);
            meta->attr_state = TSK_FS_META_ATTR_ERROR;
            return 1;
        }

        data_run->offset = 0;
        data_run->addr = 0;
        data_run->len = (meta->size + fs->block_size - 1) / fs->block_size;
        data_run->flags = TSK_FS_ATTR_RUN_FLAG_FILLER;
    }

    // initialize the data run
    if (tsk_fs_attr_set_run(file, attr, data_run, NULL,
        TSK_FS_ATTR_TYPE_DEFAULT, TSK_FS_ATTR_ID_DEFAULT,
        meta->size, meta->size, roundup(meta->size, fs->block_size), (TSK_FS_ATTR_FLAG_ENUM)0, 0)) {
            meta->attr_state = TSK_FS_META_ATTR_ERROR;
            return 1;
    }

    // If the file has size zero, return now
    if(meta->size == 0){
        meta->attr_state = TSK_FS_META_ATTR_STUDIED;
        return 0;
    }

    /* Get the version for the given object. */
    result = yaffscache_version_find_by_inode(yfs, meta->addr, &version, &obj);
    if (result != TSK_OK || version == NULL) {
        if (tsk_verbose)
            tsk_fprintf(stderr, "yaffsfs_load_attrs: yaffscache_version_find_by_inode failed!\n");
        meta->attr_state = TSK_FS_META_ATTR_ERROR;
        return 1;
    }

    if (tsk_verbose)
        yaffscache_object_dump(stderr, obj);

    file_block_count = data_run->len;
    /* Cycle through the chunks for this version of this object */
    curr = version->ycv_last_chunk;
    while (curr != NULL && curr->ycc_obj_id == obj->yco_obj_id) {

        if (curr->ycc_chunk_id == 0) {
            if (tsk_verbose)
                tsk_fprintf(stderr, "yaffsfs_load_attrs: skipping header chunk\n");
        }
        else if (tsk_list_find(chunks_seen, curr->ycc_chunk_id)) {
            if (tsk_verbose)
                tsk_fprintf(stderr, "yaffsfs_load_attrs: skipping duplicate chunk\n");
        }
        else if (curr->ycc_chunk_id > file_block_count) {
            if (tsk_verbose)
                tsk_fprintf(stderr, "yaffsfs_load_attrs: skipping chunk past end\n");
        }
        /* We like this chunk */
        else {
            // add it to our internal list
            if (tsk_list_add(&chunks_seen, curr->ycc_chunk_id)) {
                meta->attr_state = TSK_FS_META_ATTR_ERROR;
                tsk_list_free(chunks_seen);
                chunks_seen = NULL;
                return 1;
            }

            data_run_new = tsk_fs_attr_run_alloc();
            if (data_run_new == NULL) {
                tsk_fs_attr_run_free(data_run_new);
                meta->attr_state = TSK_FS_META_ATTR_ERROR;
                return 1;
            }

            data_run_new->offset = (curr->ycc_chunk_id - 1);
            data_run_new->addr = curr->ycc_offset / (fs->block_pre_size + fs->block_size + fs->block_post_size);
            data_run_new->len = 1;
            data_run_new->flags = TSK_FS_ATTR_RUN_FLAG_NONE;

            if (tsk_verbose)
                tsk_fprintf(stderr, "yaffsfs_load_attrs: @@@ Chunk %d : %08x is at offset 0x%016llx\n",
                curr->ycc_chunk_id, curr->ycc_seq_number, curr->ycc_offset);

            tsk_fs_attr_add_run(fs, attr, data_run_new);
        }

        curr = curr->ycc_prev;
    }

    tsk_list_free(chunks_seen);
    meta->attr_state = TSK_FS_META_ATTR_STUDIED;
    return 0;
}


    yaffs_inode_lookup(TSK_FS_INFO *a_fs, TSK_FS_FILE * a_fs_file,
    TSK_INUM_T inum)
{
    YAFFSFS_INFO *yfs = (YAFFSFS_INFO *)a_fs;
    YaffsCacheObject *obj;
    YaffsCacheVersion *version;
    YaffsHeader *header = NULL;
    YaffsSpare *spare = NULL;
    TSK_RETVAL_ENUM result;
    uint8_t type;
    const char *real_name;

    if (a_fs_file == NULL) {
        tsk_error_set_errno(TSK_ERR_FS_ARG);
        tsk_error_set_errstr("yaffsfs_inode_lookup: fs_file is NULL");
        return 1;
    }

    if (a_fs_file->meta == NULL) {
        if ((a_fs_file->meta =
            tsk_fs_meta_alloc(YAFFS_FILE_CONTENT_LEN)) == NULL)
            return 1;
    }
    else {
        tsk_fs_meta_reset(a_fs_file->meta);
    }

    if (tsk_verbose)
        tsk_fprintf(stderr, "yaffs_inode_lookup: looking up %" PRIuINUM "\n",inum);

    switch(inum) {
    case YAFFS_OBJECT_UNLINKED:
        yaffs_make_unlinked(yfs, a_fs_file);
        return 0;

    case YAFFS_OBJECT_DELETED:
        yaffs_make_deleted(yfs, a_fs_file);
        return 0;
    }

    if(inum == yfs->fs_info.last_inum){
        yaffs_make_orphan_dir(yfs, a_fs_file);
        return 0;
    }

    result = yaffscache_version_find_by_inode(yfs, inum, &version, &obj);
    if (result != TSK_OK) {
        if (tsk_verbose)
            tsk_fprintf(stderr, "yaffs_inode_lookup: yaffscache_version_find_by_inode failed! (inode = %d)\n", inum);
        return 1;
    }

    if(version->ycv_header_chunk == NULL){
        return 1;
    }

    if (yaffsfs_read_chunk(yfs, &header, &spare, version->ycv_header_chunk->ycc_offset) != TSK_OK) {
        if (tsk_verbose)
            tsk_fprintf(stderr, "yaffs_inode_lookup: yaffsfs_read_chunk failed!\n");
        return 1;
    }

    type = header->obj_type;

    switch(inum) {
    case YAFFS_OBJECT_LOSTNFOUND:
        real_name = YAFFS_OBJECT_LOSTNFOUND_NAME;
        break;
    case YAFFS_OBJECT_UNLINKED:
        real_name = YAFFS_OBJECT_UNLINKED_NAME;
        break;
    case YAFFS_OBJECT_DELETED:
        real_name = YAFFS_OBJECT_DELETED_NAME;
        break;
    default:
        real_name = header->name;
        break;
    }

    switch(type) {
    case YAFFS_TYPE_FILE:
        if (tsk_verbose)
            tsk_fprintf(stderr, "yaffs_inode_lookup: is a file\n");
        yaffs_make_regularfile(yfs, a_fs_file, inum, real_name);
        break;

    case YAFFS_TYPE_DIRECTORY:
        if (tsk_verbose)
            tsk_fprintf(stderr, "yaffs_inode_lookup: is a directory\n");
        yaffs_make_directory(yfs, a_fs_file, inum, real_name);
        break;

    case YAFFS_TYPE_SOFTLINK:
        if (tsk_verbose)
            tsk_fprintf(stderr, "yaffs_inode_lookup: is a symbolic link\n");
        yaffs_make_regularfile(yfs, a_fs_file, inum, real_name);
        a_fs_file->meta->type = TSK_FS_META_TYPE_LNK;
        break;

    case YAFFS_TYPE_HARDLINK:
    case YAFFS_TYPE_UNKNOWN:
    default:
        if (tsk_verbose)
            tsk_fprintf(stderr, "yaffs_inode_lookup: is *** UNHANDLED *** (type %d, header at 0x%x)\n", type, version->ycv_header_chunk->ycc_offset);
        // We can still set a few things
        a_fs_file->meta->type = TSK_FS_META_TYPE_UNDEF;
        a_fs_file->meta->addr = inum;
        if(yaffs_is_version_allocated(yfs, inum)){
            a_fs_file->meta->flags =
                (TSK_FS_META_FLAG_ENUM)(TSK_FS_META_FLAG_USED | TSK_FS_META_FLAG_ALLOC);
        }
        else{
            a_fs_file->meta->flags =
                (TSK_FS_META_FLAG_ENUM)(TSK_FS_META_FLAG_USED | TSK_FS_META_FLAG_UNALLOC);
        }
        if (a_fs_file->meta->name2 == NULL) {
            if ((a_fs_file->meta->name2 = (TSK_FS_META_NAME_LIST *)
                tsk_malloc(sizeof(TSK_FS_META_NAME_LIST))) == NULL){
                    return 1;
            }
            a_fs_file->meta->name2->next = NULL;
        }
        strncpy(a_fs_file->meta->name2->name, real_name,
            TSK_FS_META_NAME_LIST_NSIZE);
        break;
    }

    /* Who owns this? I'm following the way FATFS does it by freeing + NULLing
    * this and mallocing if used.
    */
    free(a_fs_file->meta->link);
    a_fs_file->meta->link = NULL;

    if (type != YAFFS_TYPE_HARDLINK) {
        a_fs_file->meta->mode = (TSK_FS_META_MODE_ENUM)(header->file_mode & TWELVE_BITS_MASK); // chop at 12 bits;
        a_fs_file->meta->uid = header->user_id;
        a_fs_file->meta->gid = header->group_id;
        a_fs_file->meta->mtime = header->mtime;
        a_fs_file->meta->atime = header->atime;
        a_fs_file->meta->ctime = header->ctime;
    }

    if (type == YAFFS_TYPE_FILE) {
        a_fs_file->meta->size = header->file_size;
        // NOTE: This isn't in Android 3.3 kernel but is in YAFFS2 git
        //a_fs_file->meta->size |= ((TSK_OFF_T) header->file_size_high) << 32;
    }

    if (type == YAFFS_TYPE_HARDLINK) {
        // TODO: Store equivalent_id somewhere? */
    }

    if (type == YAFFS_TYPE_SOFTLINK) {
        a_fs_file->meta->link = (char*)tsk_malloc(YAFFS_HEADER_ALIAS_LENGTH);
        if (a_fs_file->meta->link == NULL) {
            free(header);
            free(spare);
            return 1;
        }

        memcpy(a_fs_file->meta->link, header->alias, YAFFS_HEADER_ALIAS_LENGTH);
    }

    free(header);
    free(spare);
    return 0;
}

static const int TWELVE_BITS_MASK = 0xFFF;

static uint8_t
    yaffsfs_read_header(YAFFSFS_INFO *yfs, YaffsHeader ** header, TSK_OFF_T offset);

static uint8_t
    yaffsfs_load_attrs(TSK_FS_FILE *file);

typedef TSK_RETVAL_ENUM yc_find_children_cb(YaffsCacheObject *obj, YaffsCacheVersion *version, void *args);

typedef struct _dir_open_cb_args {
    YAFFSFS_INFO *yfs;
    TSK_FS_DIR *dir;
    TSK_INUM_T parent_addr;
} dir_open_cb_args;


yaffs_validate_config_file(std::map<std::string, std::string> & paramMap){
    int offset_field_count;

    // Make a list of all fields to test
    std::set<std::string> integerParams;
    integerParams.insert(YAFFS_CONFIG_SEQ_NUM_STR);
    integerParams.insert(YAFFS_CONFIG_OBJ_ID_STR);
    integerParams.insert(YAFFS_CONFIG_CHUNK_ID_STR);
    integerParams.insert(YAFFS_CONFIG_PAGE_SIZE_STR);
    integerParams.insert(YAFFS_CONFIG_SPARE_SIZE_STR);
    integerParams.insert(YAFFS_CONFIG_CHUNKS_PER_BLOCK_STR);

    // If the parameter is set, verify that the value is an int
    for(std::set<std::string>::iterator it = integerParams.begin();it != integerParams.end();it++){
        if((paramMap.find(*it) != paramMap.end()) &&
            (0 != yaffs_validate_integer_field(paramMap[*it]))){
            tsk_error_reset();
            tsk_error_set_errno(TSK_ERR_FS);
            tsk_error_set_errstr(
                        "yaffs_validate_config_file: Empty or non-integer value for Yaffs2 parameter \"%s\". %s", (*it).c_str(), YAFFS_HELP_MESSAGE);
            return 1;
        }
    }

    // Check that we have all three spare offset fields, or none of the three
    offset_field_count = 0;
    if(paramMap.find(YAFFS_CONFIG_SEQ_NUM_STR) != paramMap.end()){
        offset_field_count++;
    }
    if(paramMap.find(YAFFS_CONFIG_OBJ_ID_STR) != paramMap.end()){
        offset_field_count++;
    }
    if(paramMap.find(YAFFS_CONFIG_CHUNK_ID_STR) != paramMap.end()){
        offset_field_count++;
    }

    if(! ((offset_field_count == 0) || (offset_field_count == 3))){
            tsk_error_reset();
            tsk_error_set_errno(TSK_ERR_FS);
            tsk_error_set_errstr(
                        "yaffs_validate_config_file: Require either all three spare offset fields or none. %s", YAFFS_HELP_MESSAGE);
            return 1;
    }

    // Make sure there aren't any unexpected fields present
    for(std::map<std::string, std::string>::iterator it = paramMap.begin(); it != paramMap.end();it++){
        if(integerParams.find(it->first) == integerParams.end()){
            tsk_error_reset();
            tsk_error_set_errno(TSK_ERR_FS);
            tsk_error_set_errstr(
                        "yaffs_validate_config_file: Found unexpected field in config file (\"%s\"). %s", it->first.c_str(), YAFFS_HELP_MESSAGE);
            return 1;
        }
    }

    return 0;
}

static const int TWELVE_BITS_MASK = 0xFFF;

static uint8_t
    yaffsfs_read_header(YAFFSFS_INFO *yfs, YaffsHeader ** header, TSK_OFF_T offset);

static uint8_t
    yaffsfs_load_attrs(TSK_FS_FILE *file);

typedef struct {
    FILE *hFile;
    int idx;
} YAFFSFS_PRINT_ADDR;


    yaffsfs_jblk_walk(TSK_FS_INFO * /*info*/, TSK_DADDR_T /*daddr*/,
    TSK_DADDR_T /*daddrt*/, int /*entry*/, TSK_FS_JBLK_WALK_CB /*cb*/,
    void * /*fn*/)
{
    tsk_error_reset();
    tsk_error_set_errno(TSK_ERR_FS_UNSUPFUNC);
    tsk_error_set_errstr("Journal support for YAFFS is not implemented");
    return 1;
}


    yaffsfs_get_default_attr_type(const TSK_FS_FILE * /*a_file*/)
{
    return TSK_FS_ATTR_TYPE_DEFAULT;
}

static const int TWELVE_BITS_MASK = 0xFFF;

static uint8_t
    yaffsfs_load_attrs(TSK_FS_FILE *file);


    yaffs_dir_open_meta_cb(YaffsCacheObject * /*obj*/, YaffsCacheVersion *version, void *args) {
        dir_open_cb_args *cb_args = (dir_open_cb_args *) args;
        YaffsCacheChunk *chunk = version->ycv_header_chunk;
        TSK_INUM_T curr_inode = 0;
        uint32_t obj_id = chunk->ycc_obj_id;
        uint32_t chunk_id = chunk->ycc_chunk_id;
        uint32_t vnum = version->ycv_version;
        YaffsHeader *header = NULL;
        TSK_FS_NAME * fs_name;
        char *file_ext;
        char version_string[64]; // Allow a max of 64 bytes in the version string

        yaffscache_obj_id_and_version_to_inode(obj_id, vnum, &curr_inode);

        if (chunk_id != 0) {
            return TSK_ERR;
        }

        if (tsk_verbose)
            fprintf(stderr, "dir_open_find_children_cb: %08" PRIxINUM " -> %08" PRIx32 ":%d\n", cb_args->parent_addr, obj_id, vnum);

        if (yaffsfs_read_header(cb_args->yfs, &header, chunk->ycc_offset) != TSK_OK) {
            return TSK_ERR;
        }

        if ((fs_name = tsk_fs_name_alloc(YAFFSFS_MAXNAMLEN + 64, 0)) == NULL) {
            free(header);
            return TSK_ERR;
        }

        switch (obj_id) {
        case YAFFS_OBJECT_LOSTNFOUND:
            strncpy(fs_name->name, YAFFS_OBJECT_LOSTNFOUND_NAME,
                fs_name->name_size - 64);
            break;
        case YAFFS_OBJECT_UNLINKED:
            strncpy(fs_name->name, YAFFS_OBJECT_UNLINKED_NAME,
                fs_name->name_size - 64);
            break;
        case YAFFS_OBJECT_DELETED:
            strncpy(fs_name->name, YAFFS_OBJECT_DELETED_NAME,
                fs_name->name_size - 64);
            break;
        default:
            strncpy(fs_name->name, header->name, fs_name->name_size - 64);
            break;
        }
        fs_name->name[fs_name->name_size - 65] = 0;

        // Only put object/version string onto unallocated versions
        if(! yaffs_is_version_allocated(cb_args->yfs, curr_inode)){
            // Also copy the extension so that it also shows up after the version string, which allows
            // easier searching by file extension. Max extension length is 5 characters after the dot,
            // and require at least one character before the dot
            file_ext = strrchr(fs_name->name, '.');
            if((file_ext != NULL) && (file_ext != fs_name->name) && (strlen(file_ext) < 7)){
               snprintf(version_string, 64, "#%d,%d%s", obj_id, vnum, file_ext);
            }
            else{
               snprintf(version_string, 64, "#%d,%d", obj_id, vnum);
            }
            strncat(fs_name->name, version_string, 64);
            fs_name->flags = TSK_FS_NAME_FLAG_UNALLOC;
        }
        else{
            fs_name->flags = TSK_FS_NAME_FLAG_ALLOC;
        }

        fs_name->meta_addr = curr_inode;

        switch (header->obj_type) {
        case YAFFS_TYPE_FILE:
            fs_name->type = TSK_FS_NAME_TYPE_REG;
            break;

        case YAFFS_TYPE_DIRECTORY:
            fs_name->type = TSK_FS_NAME_TYPE_DIR;
            break;

        case YAFFS_TYPE_SOFTLINK:
        case YAFFS_TYPE_HARDLINK:
            fs_name->type = TSK_FS_NAME_TYPE_LNK;
            break;

        case YAFFS_TYPE_SPECIAL:
            fs_name->type = TSK_FS_NAME_TYPE_UNDEF; // Could be a socket
            break;

        default:
            if (tsk_verbose)
                fprintf(stderr, "yaffs_dir_open_meta_cb: unhandled object type\n");
            fs_name->type = TSK_FS_NAME_TYPE_UNDEF;
            break;
        }

        free(header);

        if (tsk_fs_dir_add(cb_args->dir, fs_name)) {
            tsk_fs_name_free(fs_name);
            return TSK_ERR;
        }

        /* A copy is made in tsk_fs_dir_add, so we can free this one */
        tsk_fs_name_free(fs_name);

        return TSK_OK;
}


    yaffs_make_regularfile( YAFFSFS_INFO * yaffsfs, TSK_FS_FILE * a_fs_file,
    TSK_INUM_T inode, const char * name )
{
    TSK_FS_FILE *fs_file = a_fs_file;

    fs_file->meta->type = TSK_FS_META_TYPE_REG;
    fs_file->meta->mode = (TSK_FS_META_MODE_ENUM)0;
    fs_file->meta->nlink =1;

    if(yaffs_is_version_allocated(yaffsfs, inode)){
        fs_file->meta->flags =
            (TSK_FS_META_FLAG_ENUM)(TSK_FS_META_FLAG_USED | TSK_FS_META_FLAG_ALLOC);
    }
    else{
        fs_file->meta->flags =
            (TSK_FS_META_FLAG_ENUM)(TSK_FS_META_FLAG_USED | TSK_FS_META_FLAG_UNALLOC);
    }

    fs_file->meta->uid = fs_file->meta->gid = 0;
    fs_file->meta->mtime = fs_file->meta->atime = fs_file->meta->ctime =
        fs_file->meta->crtime = 0;
    fs_file->meta->mtime_nano = fs_file->meta->atime_nano =
        fs_file->meta->ctime_nano = fs_file->meta->crtime_nano = 0;

    if (fs_file->meta->name2 == NULL) {
        if ((fs_file->meta->name2 = (TSK_FS_META_NAME_LIST *)
            tsk_malloc(sizeof(TSK_FS_META_NAME_LIST))) == NULL)
            return 1;
        fs_file->meta->name2->next = NULL;
    }

    if (fs_file->meta->attr != NULL) {
        tsk_fs_attrlist_markunused(fs_file->meta->attr);
    }
    else {
        fs_file->meta->attr = tsk_fs_attrlist_alloc();
    }

    fs_file->meta->addr = inode;
    strncpy(fs_file->meta->name2->name, name,
        TSK_FS_META_NAME_LIST_NSIZE);

    fs_file->meta->size = 0;
    fs_file->meta->attr_state = TSK_FS_META_ATTR_EMPTY;

    return 0;
}

static const int TWELVE_BITS_MASK = 0xFFF;

static uint8_t
    yaffsfs_read_header(YAFFSFS_INFO *yfs, YaffsHeader ** header, TSK_OFF_T offset);

static uint8_t
    yaffsfs_load_attrs(TSK_FS_FILE *file);

typedef struct _dir_open_cb_args {
    YAFFSFS_INFO *yfs;
    TSK_FS_DIR *dir;
    TSK_INUM_T parent_addr;
} dir_open_cb_args;


    yaffsfs_jopen(TSK_FS_INFO * /*info*/, TSK_INUM_T /*inum*/)
{
    tsk_error_reset();
    tsk_error_set_errno(TSK_ERR_FS_UNSUPFUNC);
    tsk_error_set_errstr("Journal support for YAFFS is not implemented");
    return 1;
}

typedef struct _dir_open_cb_args {
    YAFFSFS_INFO *yfs;
    TSK_FS_DIR *dir;
    TSK_INUM_T parent_addr;
} dir_open_cb_args;


    yaffscache_chunk_add(YAFFSFS_INFO *yfs, TSK_OFF_T offset, uint32_t seq_number,
    uint32_t obj_id, uint32_t chunk_id, uint32_t parent_id)
{
    TSK_RETVAL_ENUM result;
    YaffsCacheChunk *prev;
    YaffsCacheChunk *chunk;
    if ((chunk = (YaffsCacheChunk*)tsk_malloc(sizeof(YaffsCacheChunk))) == NULL) {
        return TSK_ERR;
    }

    chunk->ycc_offset = offset;
    chunk->ycc_seq_number = seq_number;
    chunk->ycc_obj_id = obj_id;
    chunk->ycc_chunk_id = chunk_id;
    chunk->ycc_parent_id = parent_id;

    // Bit of a hack here. In some images, the root directory (obj_id = 1) lists iself as its parent
    // directory, which can cause issues later when we get directory contents. To prevent this,
    // if a chunk comes in with obj_id = 1 and parent_id = 1, manually set the parent ID to zero.
    if((obj_id == 1) && (parent_id == 1)){
        chunk->ycc_parent_id = 0;
    }

    // Find the chunk that should go right before the new chunk
    result = yaffscache_chunk_find_insertion_point(yfs, obj_id, offset, seq_number, &prev);

    if (result == TSK_ERR) {
        return TSK_ERR;
    }

    if (prev == NULL) {
        // No previous chunk - new chunk is the lowest we've seen and the new start of the list
        chunk->ycc_prev = NULL;
        chunk->ycc_next = yfs->chunkMap->operator[](obj_id).cache_chunks_head;
    }
    else {
        chunk->ycc_prev = prev;
        chunk->ycc_next = prev->ycc_next;
    }

    if (chunk->ycc_next != NULL) {
        // If we're not at the end, set the prev pointer on the next chunk to point to our new one
        chunk->ycc_next->ycc_prev = chunk;
    }
    else {
        yfs->chunkMap->operator[](obj_id).cache_chunks_tail = chunk;
    }

    if (chunk->ycc_prev != NULL) {
        // If we're not at the beginning, set the next pointer on the previous chunk to point at our new one
        chunk->ycc_prev->ycc_next = chunk;
    }
    else {
        yfs->chunkMap->operator[](obj_id).cache_chunks_head = chunk;
    }

    return TSK_OK;
}


    yaffs_make_orphan_dir( YAFFSFS_INFO * yaffsfs, TSK_FS_FILE * a_fs_file )
{
    TSK_FS_FILE * fs_file = a_fs_file;
    TSK_FS_NAME *fs_name = tsk_fs_name_alloc(256, 0);
    if (fs_name == NULL)
        return TSK_ERR;

    if (tsk_verbose)
        tsk_fprintf(stderr, "yaffs_make_orphan_dir: Making orphan dir node\n");

    if (tsk_fs_dir_make_orphan_dir_name(&(yaffsfs->fs_info), fs_name)) {
        tsk_fs_name_free(fs_name);
        return TSK_ERR;
    }

    if (yaffs_make_directory(yaffsfs, fs_file, yaffsfs->fs_info.last_inum, (char *)fs_name)){
        tsk_fs_name_free(fs_name);
        return 1;
    }
    tsk_fs_name_free(fs_name);
    return 0;
}

static uint8_t
    yaffsfs_read_header(YAFFSFS_INFO *yfs, YaffsHeader ** header, TSK_OFF_T offset);

static uint8_t
    yaffsfs_load_attrs(TSK_FS_FILE *file);

typedef struct _dir_open_cb_args {
    YAFFSFS_INFO *yfs;
    TSK_FS_DIR *dir;
    TSK_INUM_T parent_addr;
} dir_open_cb_args;


    print_addr_act(YAFFSFS_INFO * /*fs_file*/, TSK_OFF_T /*a_off*/,
    TSK_DADDR_T addr, char * /*buf*/, size_t /*size*/,
    TSK_FS_BLOCK_FLAG_ENUM flags, void *a_ptr)
{
    YAFFSFS_PRINT_ADDR *print = (YAFFSFS_PRINT_ADDR *) a_ptr;

    if (flags & TSK_FS_BLOCK_FLAG_CONT) {
        tsk_fprintf(print->hFile, "%" PRIuDADDR " ", addr);

        if (++(print->idx) == 8) {
            tsk_fprintf(print->hFile, "\n");
            print->idx = 0;
        }
    }

    return TSK_WALK_CONT;
}

static uint8_t
    yaffsfs_read_header(YAFFSFS_INFO *yfs, YaffsHeader ** header, TSK_OFF_T offset);

typedef TSK_RETVAL_ENUM yc_find_children_cb(YaffsCacheObject *obj, YaffsCacheVersion *version, void *args);

typedef struct {
    FILE *hFile;
    int idx;
} YAFFSFS_PRINT_ADDR;

typedef struct _dir_open_cb_args {
    YAFFSFS_INFO *yfs;
    TSK_FS_DIR *dir;
    TSK_INUM_T parent_addr;
} dir_open_cb_args;


    yaffs_make_unlinked( YAFFSFS_INFO * yaffsfs, TSK_FS_FILE * a_fs_file )
{
    TSK_FS_FILE * fs_file = a_fs_file;

    if (tsk_verbose)
        tsk_fprintf(stderr, "yaffs_make_unlinked: Making virtual unlinked node\n");

    if (yaffs_make_directory(yaffsfs, fs_file, YAFFS_OBJECT_UNLINKED, YAFFS_OBJECT_UNLINKED_NAME))
        return 1;

    return 0;
}


    yaffsfs_fsstat(TSK_FS_INFO * fs, FILE * hFile)
{
    YAFFSFS_INFO *yfs = (YAFFSFS_INFO *) fs;
    unsigned int obj_count, version_count;
    uint32_t obj_first, obj_last, version_first, version_last;

    // clean up any error messages that are lying around
    tsk_error_reset();

    tsk_fprintf(hFile, "FILE SYSTEM INFORMATION\n");
    tsk_fprintf(hFile, "--------------------------------------------\n");

    tsk_fprintf(hFile, "File System Type: YAFFS2\n");
    tsk_fprintf(hFile, "Page Size: %u\n", yfs->page_size);
    tsk_fprintf(hFile, "Spare Size: %u\n", yfs->spare_size);
    tsk_fprintf(hFile, "Spare Offsets: Sequence number: %d, Object ID: %d, Chunk ID: %d, nBytes: %d\n",
        yfs->spare_seq_offset, yfs->spare_obj_id_offset, yfs->spare_chunk_id_offset, yfs->spare_nbytes_offset);

    tsk_fprintf(hFile, "\nMETADATA INFORMATION\n");
    tsk_fprintf(hFile, "--------------------------------------------\n");

    yaffscache_objects_stats(yfs,
        &obj_count, &obj_first, &obj_last,
        &version_count, &version_first, &version_last);

    tsk_fprintf(hFile, "Number of Allocated Objects: %u\n", obj_count);
    tsk_fprintf(hFile, "Object Id Range: %" PRIu32 " - %" PRIu32 "\n",
        obj_first, obj_last);
    tsk_fprintf(hFile, "Number of Total Object Versions: %u\n", version_count);
    tsk_fprintf(hFile, "Object Version Range: %" PRIu32 " - %" PRIu32 "\n",
        version_first, version_last);

    return 0;
}


    yaffscache_inode_to_obj_id_and_version(TSK_INUM_T inode, uint32_t *obj_id, uint32_t *version_num) {
        *obj_id = inode & YAFFS_OBJECT_ID_MASK;
        *version_num = (inode >> YAFFS_VERSION_NUM_SHIFT) & YAFFS_VERSION_NUM_MASK;

        return TSK_OK;
}


    yaffs_make_deleted( YAFFSFS_INFO * yaffsfs, TSK_FS_FILE * a_fs_file )
{
    TSK_FS_FILE *fs_file = a_fs_file;

    if (tsk_verbose)
        tsk_fprintf(stderr, "yaffs_make_deleted: Making virtual deleted node\n");

    if (yaffs_make_directory(yaffsfs, fs_file, YAFFS_OBJECT_DELETED, YAFFS_OBJECT_DELETED_NAME))
        return 1;

    return 0;
}


    yaffscache_versions_insert_chunk(YAFFSFS_INFO *yfs, YaffsCacheChunk *chunk)
{
    YaffsCacheObject *obj;
    TSK_RETVAL_ENUM result;
    YaffsCacheVersion *version;

    // Building a list in yfs->cache_objects, sorted by obj_id
    result = yaffscache_object_find_or_add(yfs, chunk->ycc_obj_id, &obj);
    if (result != TSK_OK) {
        return TSK_ERR;
    }
    version = obj->yco_latest;

    /* First chunk in this object? */
    if (version == NULL) {
        yaffscache_object_add_version(obj, chunk);
    }
    else {
        /* Chunk in the same update? */
        if (chunk->ycc_seq_number == version->ycv_seq_number) {
            version->ycv_last_chunk = chunk;
            if ((chunk->ycc_chunk_id == 0) && (chunk->ycc_parent_id != YAFFS_OBJECT_UNLINKED)
                &&(chunk->ycc_parent_id != YAFFS_OBJECT_DELETED)) {
                    version->ycv_header_chunk = chunk;
            }
            else if((chunk->ycc_chunk_id == 0) && (version->ycv_header_chunk == NULL)){
                version->ycv_header_chunk = chunk;
            }
        }
        // If there was no header for the last version, continue adding to it instead
        // of starting a new version.
        else if(version->ycv_header_chunk == NULL){
            version->ycv_seq_number = chunk->ycc_seq_number;
            version->ycv_last_chunk = chunk;
            if ((chunk->ycc_chunk_id == 0) && (chunk->ycc_parent_id != YAFFS_OBJECT_UNLINKED)
                &&(chunk->ycc_parent_id != YAFFS_OBJECT_DELETED)) {
                    version->ycv_header_chunk = chunk;
            }
            else if((chunk->ycc_chunk_id == 0) && (version->ycv_header_chunk == NULL)){
                version->ycv_header_chunk = chunk;
            }
        }
        else if(chunk->ycc_chunk_id == 0){   // Directories only have a header block
            // If we're looking at a new version of a directory where the previous version had the same name,
            // leave everything in the same version. Multiple versions of the same directory aren't really giving us
            // any information.
            YaffsHeader * newHeader;
            yaffsfs_read_header(yfs, &newHeader, chunk->ycc_offset);
            if((newHeader != NULL) && (newHeader->obj_type == YAFFS_TYPE_DIRECTORY)){
                // Read in the old header
                YaffsHeader * oldHeader;
                yaffsfs_read_header(yfs, &oldHeader, version->ycv_header_chunk->ycc_offset);
                if((oldHeader != NULL) && (oldHeader->obj_type == YAFFS_TYPE_DIRECTORY) &&
                    (0 == strncmp(oldHeader->name, newHeader->name, YAFFS_HEADER_NAME_LENGTH))){
                        version->ycv_seq_number = chunk->ycc_seq_number;
                        version->ycv_last_chunk = chunk;
                        version->ycv_header_chunk = chunk;
                }
                else{
                    // The older header either isn't a directory or it doesn't have the same name, so leave it
                    // as its own version
                    yaffscache_object_add_version(obj, chunk);
                }
            }
            else{
                //  Not a directory
                yaffscache_object_add_version(obj, chunk);
            }
        }
        else{
            //  Otherwise, add this chunk as the start of a new version
            yaffscache_object_add_version(obj, chunk);
        }
    }

    return TSK_OK;
}


    yaffscache_find_children(YAFFSFS_INFO *yfs, TSK_INUM_T parent_inode, yc_find_children_cb cb, void *args)
{
    YaffsCacheObject *obj;

    uint32_t parent_id, version_num;
    if (yaffscache_inode_to_obj_id_and_version(parent_inode, &parent_id, &version_num) != TSK_OK) {
        return TSK_ERR;
    }

    /* Iterate over all objects and all versions of the objects to see if one is the child
     * of the given parent. */
    for (obj = yfs->cache_objects; obj != NULL; obj = obj->yco_next) {
        YaffsCacheVersion *version;
        for (version = obj->yco_latest; version != NULL; version = version->ycv_prior) {
            /* Is this an incomplete version? */
            if (version->ycv_header_chunk == NULL) {
                continue;
            }

            if (version->ycv_header_chunk->ycc_parent_id == parent_id) {
                TSK_RETVAL_ENUM result = cb(obj, version, args);
                if (result != TSK_OK)
                    return result;
            }
        }
    }

    return TSK_OK;
}


    yaffsfs_read_spare(YAFFSFS_INFO *yfs, YaffsSpare ** spare, TSK_OFF_T offset)
{
    unsigned char *spr;
    ssize_t cnt;
    YaffsSpare *sp;
    TSK_FS_INFO *fs = &(yfs->fs_info);

    uint32_t seq_number;
    uint32_t object_id;
    uint32_t chunk_id;

    // Should have checked this by now, but just in case
    if((yfs->spare_seq_offset + 4 > yfs->spare_size) ||
        (yfs->spare_obj_id_offset + 4 > yfs->spare_size) ||
        (yfs->spare_chunk_id_offset + 4 > yfs->spare_size)){
            return 1;
    }

    if ((spr = (unsigned char*) tsk_malloc(yfs->spare_size)) == NULL) {
        return 1;
    }

    if (yfs->spare_size < 46) { // Why is this 46?
        tsk_error_reset();
        tsk_error_set_errno(TSK_ERR_FS_ARG);
        tsk_error_set_errstr("yaffsfs_read_spare: spare size is too small");
        free(spr);
        return 1;
    }

    cnt = tsk_img_read(fs->img_info, offset, (char*) spr, yfs->spare_size);
    if ((cnt < 0) || ((unsigned int)cnt < yfs->spare_size)) {
        // couldn't read sufficient bytes...
        if (spare) {
            free(spr);
            *spare = NULL;
        }
        return 1;
    }

    if ((sp = (YaffsSpare*) tsk_malloc(sizeof(YaffsSpare))) == NULL) {
        return 1;
    }

    memset(sp, 0, sizeof(YaffsSpare));

    /*
    * Complete read of the YAFFS2 spare
    */

    // The format of the spare area should have been determined earlier
    memcpy(&seq_number, &spr[yfs->spare_seq_offset], 4);
    memcpy(&object_id, &spr[yfs->spare_obj_id_offset], 4);
    memcpy(&chunk_id, &spr[yfs->spare_chunk_id_offset], 4);

    if ((YAFFS_SPARE_FLAGS_IS_HEADER & chunk_id) != 0) {

        sp->seq_number = seq_number;
        sp->object_id = object_id & ~YAFFS_SPARE_OBJECT_TYPE_MASK;
        sp->chunk_id = 0;

        sp->has_extra_fields = 1;
        sp->extra_parent_id = chunk_id & YAFFS_SPARE_PARENT_ID_MASK;
        sp->extra_object_type =
            (object_id & YAFFS_SPARE_OBJECT_TYPE_MASK)
            >> YAFFS_SPARE_OBJECT_TYPE_SHIFT;
    }
    else {
        sp->seq_number = seq_number;
        sp->object_id = object_id;
        sp->chunk_id = chunk_id;

        sp->has_extra_fields = 0;
    }

    free(spr);
    *spare = sp;

    return 0;
}


    yaffsfs_read_chunk(YAFFSFS_INFO *yfs,
    YaffsHeader **header, YaffsSpare **spare, TSK_OFF_T offset)
{
    TSK_OFF_T header_offset = offset;
    TSK_OFF_T spare_offset = offset + yfs->page_size;

    if (header == NULL || spare == NULL) {
        return 1;
    }

    if (yaffsfs_read_header(yfs, header, header_offset) != 0) {
        return 1;
    }

    if (yaffsfs_read_spare(yfs, spare, spare_offset) != 0) {
        free(*header);
        *header = NULL;
        return 1;
    }

    return 0;
}


static uint8_t yaffs_is_version_allocated(YAFFSFS_INFO * yfs, TSK_INUM_T inode){
    YaffsCacheObject * obj;
    YaffsCacheVersion * version;
    YaffsCacheChunk * curr;

    TSK_RETVAL_ENUM result = yaffscache_version_find_by_inode(yfs, inode, &version, &obj);
    if (result != TSK_OK) {
        if (tsk_verbose)
            tsk_fprintf(stderr, "yaffs_is_version_allocated: yaffscache_version_find_by_inode failed! (inode: %d)\n", inode);
        return 0;
    }

    if(obj->yco_latest == version){
        curr = obj->yco_latest->ycv_header_chunk;
        while(curr != NULL){
            // We're looking for a newer unlinked or deleted header. If one exists, then this object should be considered unallocated
            if((curr->ycc_parent_id == YAFFS_OBJECT_UNLINKED) || (curr->ycc_parent_id == YAFFS_OBJECT_DELETED)){
                return 0;
            }
            curr = curr ->ycc_next;
        }
        return 1;
    }
    else{
        return 0;
    }

}


    yaffscache_object_add_version(YaffsCacheObject *obj, YaffsCacheChunk *chunk)
{
    uint32_t ver_number;
    YaffsCacheChunk *header_chunk = NULL;
    YaffsCacheVersion *version;

    // Going to try ignoring unlinked/deleted headers (objID 3 and 4)
    if ((chunk->ycc_chunk_id == 0) && (chunk->ycc_parent_id != YAFFS_OBJECT_UNLINKED)
        &&(chunk->ycc_parent_id != YAFFS_OBJECT_DELETED)) {
            header_chunk = chunk;
    }

    /* If this is the second version (since last header_chunk is not NULL) and no
    * header was added, get rid of this incomplete old version -- can't be
    * reasonably recovered.
    *
    * TODO: These chunks are still in the structure and can be walked,
    *       but I'm not sure how to represent this set of data chunks
    *       with no metadata under TSK. This is rare and we don't have
    *       a testcase for it now. Punting right now.
    *
    * Edit: Shouldn't get to this point anymore. Changes to
    *       yaffscache_versions_insert_chunk make a version continue until it
    *       has a header block.
    */
    if (obj->yco_latest != NULL) {
        if (obj->yco_latest->ycv_header_chunk == NULL) {
            YaffsCacheVersion *incomplete = obj->yco_latest;

            if (tsk_verbose)
                tsk_fprintf(stderr, "yaffscache_object_add_version: "
                "removed an incomplete first version (no header)\n");

            obj->yco_latest = obj->yco_latest->ycv_prior;
            free(incomplete);
        }
    }

    if (obj->yco_latest != NULL) {
        ver_number = obj->yco_latest->ycv_version + 1;

        /* Until a new header is given, use the last seen header. */
        if (header_chunk == NULL) {
            header_chunk = obj->yco_latest->ycv_header_chunk;

            // If we haven't seen a good header yet and we have a deleted/unlinked one, use it
            if((header_chunk == NULL) && (chunk->ycc_chunk_id == 0)){
                header_chunk = chunk;
            }
        }
    }
    else {
        ver_number = 1;
    }

    if ((version = (YaffsCacheVersion *) tsk_malloc(sizeof(YaffsCacheVersion))) == NULL) {
        return TSK_ERR;
    }

    version->ycv_prior = obj->yco_latest;
    version->ycv_version = ver_number;
    version->ycv_seq_number = chunk->ycc_seq_number;
    version->ycv_header_chunk = header_chunk;
    version->ycv_first_chunk = chunk;
    version->ycv_last_chunk = chunk;

    obj->yco_latest = version;

    return TSK_OK;
}

static uint8_t
    yaffsfs_read_header(YAFFSFS_INFO *yfs, YaffsHeader ** header, TSK_OFF_T offset);

typedef TSK_RETVAL_ENUM yc_find_children_cb(YaffsCacheObject *obj, YaffsCacheVersion *version, void *args);


    yaffsfs_close(TSK_FS_INFO *fs)
{
    if(fs != NULL){
        YAFFSFS_INFO *yfs = (YAFFSFS_INFO *)fs;

        fs->tag = 0;

        // Walk and free the cache structures
        yaffscache_objects_free(yfs);
        yaffscache_chunks_free(yfs);

        //tsk_deinit_lock(&yaffsfs->lock);
        tsk_fs_free(fs);
	}
}


    yaffscache_chunk_compare(YaffsCacheChunk *curr, uint32_t addee_obj_id, TSK_OFF_T addee_offset, uint32_t addee_seq_number)
{
    if (curr->ycc_obj_id == addee_obj_id) {
        if (curr->ycc_seq_number == addee_seq_number) {
            if (curr->ycc_offset == addee_offset) {
                return 0;
            }
            else if (curr->ycc_offset < addee_offset) {
                return -1;
            }
            else {
                return 1;
            }
        }
        else if (curr->ycc_seq_number < addee_seq_number) {
            return -1;
        }
        else {
            return 1;
        }
    }
    else if (curr->ycc_obj_id < addee_obj_id) {
        return -1;
    }
    else {
        return 1;
    }
}

static uint8_t
    yaffsfs_read_header(YAFFSFS_INFO *yfs, YaffsHeader ** header, TSK_OFF_T offset);


    yaffscache_object_find_or_add(YAFFSFS_INFO *yfs, uint32_t obj_id, YaffsCacheObject **obj)
{
    YaffsCacheObject *prev;
    TSK_RETVAL_ENUM result;

    if (obj == NULL) {
        return TSK_ERR;
    }

    // Look for this obj_id in yfs->cache_objects
    // If not found, add it in the correct spot
    // yaffscache_object_find returns the last object with obj_id less than the one
    // we were searching for, so use that to insert the new one in the list
    result = yaffscache_object_find(yfs, obj_id, &prev);
    if (result == TSK_OK) {
        *obj = prev;
        return TSK_OK;
    }
    else if (result == TSK_STOP) {
        *obj = (YaffsCacheObject *) tsk_malloc(sizeof(YaffsCacheObject));
        (*obj)->yco_obj_id = obj_id;
        if (prev == NULL) {
            (*obj)->yco_next = yfs->cache_objects;
            yfs->cache_objects = *obj;
        }
        else {
            (*obj)->yco_next = prev->yco_next;
            prev->yco_next = (*obj);
        }
        return TSK_OK;
    }
    else {
        *obj = NULL;
        return TSK_ERR;
    }
}

static uint8_t
    yaffsfs_read_header(YAFFSFS_INFO *yfs, YaffsHeader ** header, TSK_OFF_T offset);

typedef TSK_RETVAL_ENUM yc_find_children_cb(YaffsCacheObject *obj, YaffsCacheVersion *version, void *args);

typedef struct _dir_open_cb_args {
    YAFFSFS_INFO *yfs;
    TSK_FS_DIR *dir;
    TSK_INUM_T parent_addr;
} dir_open_cb_args;


    yaffsfs_jentry_walk(TSK_FS_INFO * /*info*/, int /*entry*/,
    TSK_FS_JENTRY_WALK_CB /*cb*/, void * /*fn*/)
{
    tsk_error_reset();
    tsk_error_set_errno(TSK_ERR_FS_UNSUPFUNC);
    tsk_error_set_errstr("Journal support for YAFFS is not implemented");
    return 1;
}


yaffs_validate_integer_field(std::string numStr){
    unsigned int i;

    // Test if empty
    if(numStr.length() == 0){
        return 1;
    }

    // Test each character
    for(i = 0;i < numStr.length();i++){
        if(isdigit(numStr[i]) == 0){
            return 1;
        }
    }

    return 0;
}

static const int TWELVE_BITS_MASK = 0xFFF;

typedef struct {
    FILE *hFile;
    int idx;
} YAFFSFS_PRINT_ADDR;


xmlParseExternalEntityPrivate(xmlDocPtr doc, xmlParserCtxtPtr oldctxt,
	              xmlSAXHandlerPtr sax,
		      void *user_data, int depth, const xmlChar *URL,
		      const xmlChar *ID, xmlNodePtr *list) {
    xmlParserCtxtPtr ctxt;
    xmlDocPtr newDoc;
    xmlNodePtr newRoot;
    xmlSAXHandlerPtr oldsax = NULL;
    xmlParserErrors ret = XML_ERR_OK;
    xmlChar start[4];
    xmlCharEncoding enc;

    if (((depth > 40) &&
	((oldctxt == NULL) || (oldctxt->options & XML_PARSE_HUGE) == 0)) ||
	(depth > 1024)) {
	return(XML_ERR_ENTITY_LOOP);
    }

    if (list != NULL)
        *list = NULL;
    if ((URL == NULL) && (ID == NULL))
	return(XML_ERR_INTERNAL_ERROR);
    if (doc == NULL)
	return(XML_ERR_INTERNAL_ERROR);

    ctxt = xmlCreateEntityParserCtxtInternal(URL, ID, NULL, oldctxt);
    if (ctxt == NULL) return(XML_WAR_UNDECLARED_ENTITY);
    ctxt->userData = ctxt;
    if (oldctxt != NULL) {
	ctxt->_private = oldctxt->_private;
	ctxt->loadsubset = oldctxt->loadsubset;
	ctxt->validate = oldctxt->validate;
	ctxt->external = oldctxt->external;
	ctxt->record_info = oldctxt->record_info;
	ctxt->node_seq.maximum = oldctxt->node_seq.maximum;
	ctxt->node_seq.length = oldctxt->node_seq.length;
	ctxt->node_seq.buffer = oldctxt->node_seq.buffer;
    } else {
	/*
	 * Doing validity checking on chunk without context
	 * doesn't make sense
	 */
	ctxt->_private = NULL;
	ctxt->validate = 0;
	ctxt->external = 2;
	ctxt->loadsubset = 0;
    }
    if (sax != NULL) {
	oldsax = ctxt->sax;
        ctxt->sax = sax;
	if (user_data != NULL)
	    ctxt->userData = user_data;
    }
    xmlDetectSAX2(ctxt);
    newDoc = xmlNewDoc(BAD_CAST "1.0");
    if (newDoc == NULL) {
	ctxt->node_seq.maximum = 0;
	ctxt->node_seq.length = 0;
	ctxt->node_seq.buffer = NULL;
	xmlFreeParserCtxt(ctxt);
	return(XML_ERR_INTERNAL_ERROR);
    }
    newDoc->properties = XML_DOC_INTERNAL;
    newDoc->intSubset = doc->intSubset;
    newDoc->extSubset = doc->extSubset;
    newDoc->dict = doc->dict;
    xmlDictReference(newDoc->dict);

    if (doc->URL != NULL) {
	newDoc->URL = xmlStrdup(doc->URL);
    }
    newRoot = xmlNewDocNode(newDoc, NULL, BAD_CAST "pseudoroot", NULL);
    if (newRoot == NULL) {
	if (sax != NULL)
	    ctxt->sax = oldsax;
	ctxt->node_seq.maximum = 0;
	ctxt->node_seq.length = 0;
	ctxt->node_seq.buffer = NULL;
	xmlFreeParserCtxt(ctxt);
	newDoc->intSubset = NULL;
	newDoc->extSubset = NULL;
        xmlFreeDoc(newDoc);
	return(XML_ERR_INTERNAL_ERROR);
    }
    xmlAddChild((xmlNodePtr) newDoc, newRoot);
    nodePush(ctxt, newDoc->children);
    ctxt->myDoc = doc;
    newRoot->doc = doc;

    /*
     * Get the 4 first bytes and decode the charset
     * if enc != XML_CHAR_ENCODING_NONE
     * plug some encoding conversion routines.
     */
    GROW;
    if ((ctxt->input->end - ctxt->input->cur) >= 4) {
	start[0] = RAW;
	start[1] = NXT(1);
	start[2] = NXT(2);
	start[3] = NXT(3);
	enc = xmlDetectCharEncoding(start, 4);
	if (enc != XML_CHAR_ENCODING_NONE) {
	    xmlSwitchEncoding(ctxt, enc);
	}
    }

    /*
     * Parse a possible text declaration first
     */
    if ((CMP5(CUR_PTR, '<', '?', 'x', 'm', 'l')) && (IS_BLANK_CH(NXT(5)))) {
	xmlParseTextDecl(ctxt);
    }

    ctxt->instate = XML_PARSER_CONTENT;
    ctxt->depth = depth;

    xmlParseContent(ctxt);

    if ((RAW == '<') && (NXT(1) == '/')) {
	xmlFatalErr(ctxt, XML_ERR_NOT_WELL_BALANCED, NULL);
    } else if (RAW != 0) {
	xmlFatalErr(ctxt, XML_ERR_EXTRA_CONTENT, NULL);
    }
    if (ctxt->node != newDoc->children) {
	xmlFatalErr(ctxt, XML_ERR_NOT_WELL_BALANCED, NULL);
    }

    if (!ctxt->wellFormed) {
        if (ctxt->errNo == 0)
	    ret = XML_ERR_INTERNAL_ERROR;
	else
	    ret = (xmlParserErrors)ctxt->errNo;
    } else {
	if (list != NULL) {
	    xmlNodePtr cur;

	    /*
	     * Return the newly created nodeset after unlinking it from
	     * they pseudo parent.
	     */
	    cur = newDoc->children->children;
	    *list = cur;
	    while (cur != NULL) {
		cur->parent = NULL;
		cur = cur->next;
	    }
            newDoc->children->children = NULL;
	}
	ret = XML_ERR_OK;
    }

    /*
     * Record in the parent context the number of entities replacement
     * done when parsing that reference.
     */
    if (oldctxt != NULL)
        oldctxt->nbentities += ctxt->nbentities;

    /*
     * Also record the size of the entity parsed
     */
    if (ctxt->input != NULL && oldctxt != NULL) {
	oldctxt->sizeentities += ctxt->input->consumed;
	oldctxt->sizeentities += (ctxt->input->cur - ctxt->input->base);
    }
    /*
     * And record the last error if any
     */
    if ((oldctxt != NULL) && (ctxt->lastError.code != XML_ERR_OK))
        xmlCopyError(&ctxt->lastError, &oldctxt->lastError);

    if (sax != NULL)
	ctxt->sax = oldsax;
    if (oldctxt != NULL) {
        oldctxt->node_seq.maximum = ctxt->node_seq.maximum;
        oldctxt->node_seq.length = ctxt->node_seq.length;
        oldctxt->node_seq.buffer = ctxt->node_seq.buffer;
    }
    ctxt->node_seq.maximum = 0;
    ctxt->node_seq.length = 0;
    ctxt->node_seq.buffer = NULL;
    xmlFreeParserCtxt(ctxt);
    newDoc->intSubset = NULL;
    newDoc->extSubset = NULL;
    xmlFreeDoc(newDoc);

    return(ret);
}

Fix memory leak in xmlParseBalancedChunkMemoryRecover

When doc is NULL, namespace created in xmlTreeEnsureXMLDecl
is bind to newDoc->oldNs, in this case, set newDoc->oldNs to
NULL and free newDoc will cause a memory leak.

Found with libFuzzer.

Closes #82.

static void
xmlFatalErr(xmlParserCtxtPtr ctxt, xmlParserErrors error, const char *info);

static xmlParserCtxtPtr
xmlCreateEntityParserCtxtInternal(const xmlChar *URL, const xmlChar *ID,
	                  const xmlChar *base, xmlParserCtxtPtr pctx);

static void xmlHaltParser(xmlParserCtxtPtr ctxt);

unsigned int xmlParserMaxDepth = 256;

#define SAX_COMPAT_MODE BAD_CAST "SAX compatibility mode document"

static const char *xmlW3CPIs[] = {
    "xml-stylesheet",
    "xml-model",
    NULL
};

static xmlEntityPtr xmlParseStringPEReference(xmlParserCtxtPtr ctxt,
                                              const xmlChar **str);

static xmlParserErrors
xmlParseExternalEntityPrivate(xmlDocPtr doc, xmlParserCtxtPtr oldctxt,
	              xmlSAXHandlerPtr sax,
		      void *user_data, int depth, const xmlChar *URL,
		      const xmlChar *ID, xmlNodePtr *list);

static int
xmlCtxtUseOptionsInternal(xmlParserCtxtPtr ctxt, int options,
                          const char *encoding);

static void
xmlAddEntityReference(xmlEntityPtr ent, xmlNodePtr firstNode,
                      xmlNodePtr lastNode);

static xmlParserErrors
xmlParseBalancedChunkMemoryInternal(xmlParserCtxtPtr oldctxt,
		      const xmlChar *string, void *user_data, xmlNodePtr *lst);

static int
xmlLoadEntityContent(xmlParserCtxtPtr ctxt, xmlEntityPtr entity);

struct _xmlDefAttrs {
    int nbAttrs;	/* number of defaulted attributes on that element */
    int maxAttrs;       /* the size of the array */
#if __STDC_VERSION__ >= 199901L
    /* Using a C99 flexible array member avoids UBSan errors. */
    const xmlChar *values[]; /* array of localname/prefix/values/external */
#else
    const xmlChar *values[5];
#endif
}

static xmlEntityPtr xmlParseStringEntityRef(xmlParserCtxtPtr ctxt,
                                            const xmlChar ** str);

#define RAW (*ctxt->input->cur)

#define CUR (*ctxt->input->cur)

#define NXT(val) ctxt->input->cur[(val)]

#define CUR_PTR ctxt->input->cur

#define BASE_PTR ctxt->input->base

#define CMP5( s, c1, c2, c3, c4, c5 ) \
  ( CMP4( s, c1, c2, c3, c4 ) && ((unsigned char *) s)[ 4 ] == c5 )

#define CMP6( s, c1, c2, c3, c4, c5, c6 ) \
  ( CMP5( s, c1, c2, c3, c4, c5 ) && ((unsigned char *) s)[ 5 ] == c6 )

#define SKIP(val) do {							\
    ctxt->nbChars += (val),ctxt->input->cur += (val),ctxt->input->col+=(val);			\
    if (*ctxt->input->cur == 0)						\
        xmlParserInputGrow(ctxt->input, INPUT_CHUNK);			\
  } while (0)


xmlCtxtReset(xmlParserCtxtPtr ctxt)
{
    xmlParserInputPtr input;
    xmlDictPtr dict;

    if (ctxt == NULL)
        return;

    dict = ctxt->dict;

    while ((input = inputPop(ctxt)) != NULL) { /* Non consuming */
        xmlFreeInputStream(input);
    }
    ctxt->inputNr = 0;
    ctxt->input = NULL;

    ctxt->spaceNr = 0;
    if (ctxt->spaceTab != NULL) {
	ctxt->spaceTab[0] = -1;
	ctxt->space = &ctxt->spaceTab[0];
    } else {
        ctxt->space = NULL;
    }

    ctxt->nodeNr = 0;
    ctxt->node = NULL;

    ctxt->nameNr = 0;
    ctxt->name = NULL;

    DICT_FREE(ctxt->version);
    ctxt->version = NULL;
    DICT_FREE(ctxt->encoding);
    ctxt->encoding = NULL;
    DICT_FREE(ctxt->directory);
    ctxt->directory = NULL;
    DICT_FREE(ctxt->extSubURI);
    ctxt->extSubURI = NULL;
    DICT_FREE(ctxt->extSubSystem);
    ctxt->extSubSystem = NULL;
    if (ctxt->myDoc != NULL)
        xmlFreeDoc(ctxt->myDoc);
    ctxt->myDoc = NULL;

    ctxt->standalone = -1;
    ctxt->hasExternalSubset = 0;
    ctxt->hasPErefs = 0;
    ctxt->html = 0;
    ctxt->external = 0;
    ctxt->instate = XML_PARSER_START;
    ctxt->token = 0;

    ctxt->wellFormed = 1;
    ctxt->nsWellFormed = 1;
    ctxt->disableSAX = 0;
    ctxt->valid = 1;
#if 0
    ctxt->vctxt.userData = ctxt;
    ctxt->vctxt.error = xmlParserValidityError;
    ctxt->vctxt.warning = xmlParserValidityWarning;
#endif
    ctxt->record_info = 0;
    ctxt->nbChars = 0;
    ctxt->checkIndex = 0;
    ctxt->inSubset = 0;
    ctxt->errNo = XML_ERR_OK;
    ctxt->depth = 0;
    ctxt->charset = XML_CHAR_ENCODING_UTF8;
    ctxt->catalogs = NULL;
    ctxt->nbentities = 0;
    ctxt->sizeentities = 0;
    ctxt->sizeentcopy = 0;
    xmlInitNodeInfoSeq(&ctxt->node_seq);

    if (ctxt->attsDefault != NULL) {
        xmlHashFree(ctxt->attsDefault, xmlHashDefaultDeallocator);
        ctxt->attsDefault = NULL;
    }
    if (ctxt->attsSpecial != NULL) {
        xmlHashFree(ctxt->attsSpecial, NULL);
        ctxt->attsSpecial = NULL;
    }

#ifdef LIBXML_CATALOG_ENABLED
    if (ctxt->catalogs != NULL)
	xmlCatalogFreeLocal(ctxt->catalogs);
#endif
    if (ctxt->lastError.code != XML_ERR_OK)
        xmlResetError(&ctxt->lastError);
}

static void
xmlFatalErr(xmlParserCtxtPtr ctxt, xmlParserErrors error, const char *info);

static xmlParserCtxtPtr
xmlCreateEntityParserCtxtInternal(const xmlChar *URL, const xmlChar *ID,
	                  const xmlChar *base, xmlParserCtxtPtr pctx);

static void xmlHaltParser(xmlParserCtxtPtr ctxt);

static int
xmlParseElementStart(xmlParserCtxtPtr ctxt);

static void
xmlParseElementEnd(xmlParserCtxtPtr ctxt);

static const char *xmlW3CPIs[] = {
    "xml-stylesheet",
    "xml-model",
    NULL
};

static xmlEntityPtr xmlParseStringPEReference(xmlParserCtxtPtr ctxt,
                                              const xmlChar **str);

static xmlParserErrors
xmlParseExternalEntityPrivate(xmlDocPtr doc, xmlParserCtxtPtr oldctxt,
	              xmlSAXHandlerPtr sax,
		      void *user_data, int depth, const xmlChar *URL,
		      const xmlChar *ID, xmlNodePtr *list);

static int
xmlCtxtUseOptionsInternal(xmlParserCtxtPtr ctxt, int options,
                          const char *encoding);

static xmlParserErrors
xmlParseBalancedChunkMemoryInternal(xmlParserCtxtPtr oldctxt,
		      const xmlChar *string, void *user_data, xmlNodePtr *lst);

static int
xmlLoadEntityContent(xmlParserCtxtPtr ctxt, xmlEntityPtr entity);

struct _xmlDefAttrs {
    int nbAttrs;	/* number of defaulted attributes on that element */
    int maxAttrs;       /* the size of the array */
#if __STDC_VERSION__ >= 199901L
    /* Using a C99 flexible array member avoids UBSan errors. */
    const xmlChar *values[]; /* array of localname/prefix/values/external */
#else
    const xmlChar *values[5];
#endif
}

static xmlEntityPtr xmlParseStringEntityRef(xmlParserCtxtPtr ctxt,
                                            const xmlChar ** str);

#define RAW (*ctxt->input->cur)

#define CUR (*ctxt->input->cur)

#define NXT(val) ctxt->input->cur[(val)]

#define CUR_PTR ctxt->input->cur

#define BASE_PTR ctxt->input->base

#define SKIP(val) do {							\
    ctxt->nbChars += (val),ctxt->input->cur += (val),ctxt->input->col+=(val);			\
    if (*ctxt->input->cur == 0)						\
        xmlParserInputGrow(ctxt->input, INPUT_CHUNK);			\
  } while (0)

#define SKIPL(val) do {							\
    int skipl;								\
    for(skipl=0; skipl<val; skipl++) {					\
	if (*(ctxt->input->cur) == '\n') {				\
	ctxt->input->line++; ctxt->input->col = 1;			\
	} else ctxt->input->col++;					\
	ctxt->nbChars++;						\
	ctxt->input->cur++;						\
    }									\
    if (*ctxt->input->cur == 0)						\
        xmlParserInputGrow(ctxt->input, INPUT_CHUNK);			\
  } while (0)

#define SHRINK if ((ctxt->progressive == 0) &&				\
		   (ctxt->input->cur - ctxt->input->base > 2 * INPUT_CHUNK) && \
		   (ctxt->input->end - ctxt->input->cur < 2 * INPUT_CHUNK)) \
	xmlSHRINK (ctxt);

#define GROW if ((ctxt->progressive == 0) &&				\
		 (ctxt->input->end - ctxt->input->cur < INPUT_CHUNK))	\
	xmlGROW (ctxt);