/*
** fatfs
** The Sleuth Kit 
**
** Content and meta data layer support for the FAT file system 
**
** Brian Carrier [carrier <at> sleuthkit [dot] org]
** Copyright (c) 2006-2008 Brian Carrier, Basis Technology.  All Rights reserved
** Copyright (c) 2003-2005 Brian Carrier.  All rights reserved 
**
** TASK
** Copyright (c) 2002 Brian Carrier, @stake Inc.  All rights reserved
**
**
** This software is distributed under the Common Public License 1.0
**
** Unicode added with support from I.D.E.A.L. Technology Corp (Aug '05)
**
*/

/**
 * \file fatfs_meta.c
 * Contains the internal TSK FAT file system code to handle metadata structures. 
 */
#include "tsk_fs_i.h"
#include "tsk_fatfs.h"



/* 
 * Identify if the dentry is a valid 8.3 name
 *
 * returns 1 if it is, 0 if it does not
 */
static uint8_t
is_83_name(fatfs_dentry * de)
{
    if (!de)
        return 0;

    /* The IS_NAME macro will fail if the value is 0x05, which is only
     * valid in name[0], similarly with '.' */
    if ((de->name[0] != FATFS_SLOT_E5) && (de->name[0] != '.') &&
        (FATFS_IS_83_NAME(de->name[0]) == 0)) {
        if (tsk_verbose)
            fprintf(stderr, "fatfs_is_83_name: name[0] is invalid\n");
        return 0;
    }

    // the name cannot start with 0x20
    else if (de->name[0] == 0x20) {
        if (tsk_verbose)
            fprintf(stderr, "fatfs_is_83_name: name[0] has 0x20\n");
        return 0;
    }

    /* the second name field can only be . if the first one is a . */
    if (de->name[1] == '.') {
        if (de->name[0] != '.') {
            if (tsk_verbose)
                fprintf(stderr, "fatfs_is_83_name: name[1] is .\n");
            return 0;
        }
    }
    else if (FATFS_IS_83_NAME(de->name[1]) == 0) {
        if (tsk_verbose)
            fprintf(stderr, "fatfs_is_83_name: name[1] is invalid\n");
        return 0;
    }

    if (FATFS_IS_83_NAME(de->name[2]) == 0) {
        if (tsk_verbose)
            fprintf(stderr, "fatfs_is_83_name: name[2] is invalid\n");
        return 0;
    }
    else if (FATFS_IS_83_NAME(de->name[3]) == 0) {
        if (tsk_verbose)
            fprintf(stderr, "fatfs_is_83_name: name[3] is invalid\n");
        return 0;
    }
    else if (FATFS_IS_83_NAME(de->name[4]) == 0) {
        if (tsk_verbose)
            fprintf(stderr, "fatfs_is_83_name: name[4] is invalid\n");
        return 0;
    }
    else if (FATFS_IS_83_NAME(de->name[5]) == 0) {
        if (tsk_verbose)
            fprintf(stderr, "fatfs_is_83_name: name[5] is invalid\n");
        return 0;
    }
    else if (FATFS_IS_83_NAME(de->name[6]) == 0) {
        if (tsk_verbose)
            fprintf(stderr, "fatfs_is_83_name: name[6] is invalid\n");
        return 0;
    }
    else if (FATFS_IS_83_NAME(de->name[7]) == 0) {
        if (tsk_verbose)
            fprintf(stderr, "fatfs_is_83_name: name[7] is invalid\n");
        return 0;
    }
    else if (FATFS_IS_83_NAME(de->ext[0]) == 0) {
        if (tsk_verbose)
            fprintf(stderr, "fatfs_is_83_name: ext[0] is invalid\n");
        return 0;
    }
    else if (FATFS_IS_83_NAME(de->ext[1]) == 0) {
        if (tsk_verbose)
            fprintf(stderr, "fatfs_is_83_name: ext[1] is invalid\n");
        return 0;
    }
    else if (FATFS_IS_83_NAME(de->ext[2]) == 0) {
        if (tsk_verbose)
            fprintf(stderr, "fatfs_is_83_name: ext[2] is invalid\n");
        return 0;
    }

    /* Ensure that if we get a "space", that the rest of the
     * name is spaces.  This is not in the spec, but is how
     * windows operates and serves as a good check to remove 
     * false positives.  We do not do this check for the 
     * volume label though. */
    if ((de->attrib & FATFS_ATTR_VOLUME) != FATFS_ATTR_VOLUME) {
        if (((de->name[1] == 0x20) && (de->name[2] != 0x20)) ||
            ((de->name[2] == 0x20) && (de->name[3] != 0x20)) ||
            ((de->name[3] == 0x20) && (de->name[4] != 0x20)) ||
            ((de->name[4] == 0x20) && (de->name[5] != 0x20)) ||
            ((de->name[5] == 0x20) && (de->name[6] != 0x20)) ||
            ((de->name[6] == 0x20) && (de->name[7] != 0x20)) ||
            ((de->ext[1] == 0x20) && (de->ext[2] != 0x20))) {
            if (tsk_verbose)
                fprintf(stderr,
                    "fatfs_is_83_name: space before non-space\n");
            return 0;
        }
    }

    return 1;
}


/*
** Convert the DOS time to the UNIX version
** 
** UNIX stores the time in seconds from 1970 in UTC
** FAT dates are the actual date with the year relative to 1980
** 
*/
static time_t
dos2unixtime(uint16_t date, uint16_t time, uint8_t timetens)
{
    struct tm tm1;
    time_t ret;

    if (date == 0)
        return 0;

    memset(&tm1, 0, sizeof(struct tm));

    tm1.tm_sec = ((time & FATFS_SEC_MASK) >> FATFS_SEC_SHIFT) * 2;
    if ((tm1.tm_sec < 0) || (tm1.tm_sec > 60))
        tm1.tm_sec = 0;
    // the ctimetens value has a range of 0 to 199
    if (timetens > 100)
        tm1.tm_sec++;

    tm1.tm_min = ((time & FATFS_MIN_MASK) >> FATFS_MIN_SHIFT);
    if ((tm1.tm_min < 0) || (tm1.tm_min > 59))
        tm1.tm_min = 0;

    tm1.tm_hour = ((time & FATFS_HOUR_MASK) >> FATFS_HOUR_SHIFT);
    if ((tm1.tm_hour < 0) || (tm1.tm_hour > 23))
        tm1.tm_hour = 0;

    tm1.tm_mday = ((date & FATFS_DAY_MASK) >> FATFS_DAY_SHIFT);
    if ((tm1.tm_mday < 1) || (tm1.tm_mday > 31))
        tm1.tm_mday = 0;

    tm1.tm_mon = ((date & FATFS_MON_MASK) >> FATFS_MON_SHIFT) - 1;
    if ((tm1.tm_mon < 0) || (tm1.tm_mon > 11))
        tm1.tm_mon = 0;

    /* There is a limit to the year because the UNIX time value is
     * a 32-bit value 
     * the maximum UNIX time is Tue Jan 19 03:14:07 2038
     */
    tm1.tm_year = ((date & FATFS_YEAR_MASK) >> FATFS_YEAR_SHIFT) + 80;
    if ((tm1.tm_year < 0) || (tm1.tm_year > 137))
        tm1.tm_year = 0;

    /* set the daylight savings variable to -1 so that mktime() figures
     * it out */
    tm1.tm_isdst = -1;

    ret = mktime(&tm1);

    if (ret < 0) {
        if (tsk_verbose)
            tsk_fprintf(stderr,
                "dos2unixtime: Error running mktime(): %d:%d:%d %d/%d/%d",
                ((time & FATFS_HOUR_MASK) >> FATFS_HOUR_SHIFT),
                ((time & FATFS_MIN_MASK) >> FATFS_MIN_SHIFT),
                ((time & FATFS_SEC_MASK) >> FATFS_SEC_SHIFT) * 2,
                ((date & FATFS_MON_MASK) >> FATFS_MON_SHIFT) - 1,
                ((date & FATFS_DAY_MASK) >> FATFS_DAY_SHIFT),
                ((date & FATFS_YEAR_MASK) >> FATFS_YEAR_SHIFT) + 80);
        return 0;
    }

    return ret;
}

/* timetens is number of tenths of a second for a 2 second range (values 0 to 199) */
static uint32_t
dos2nanosec(uint8_t timetens)
{
    timetens %= 100;
    return timetens * 10000000;
}


/* 
 * convert the attribute list in FAT to a UNIX mode 
 */
static TSK_FS_META_TYPE_ENUM
attr2type(uint16_t attr)
{
    if (attr & FATFS_ATTR_DIRECTORY)
        return TSK_FS_META_TYPE_DIR;
    else
        return TSK_FS_META_TYPE_REG;
}


static int
attr2mode(uint16_t attr)
{
    int mode;

    /* every file is executable */
    mode =
        (TSK_FS_META_MODE_IXUSR | TSK_FS_META_MODE_IXGRP |
        TSK_FS_META_MODE_IXOTH);

    if ((attr & FATFS_ATTR_READONLY) == 0)
        mode |=
            (TSK_FS_META_MODE_IRUSR | TSK_FS_META_MODE_IRGRP |
            TSK_FS_META_MODE_IROTH);

    if ((attr & FATFS_ATTR_HIDDEN) == 0)
        mode |=
            (TSK_FS_META_MODE_IWUSR | TSK_FS_META_MODE_IWGRP |
            TSK_FS_META_MODE_IWOTH);

    return mode;
}



/** 
 * Cleans up a char string so that it is only ASCII. We do this
 * before we copy something into a TSK buffer that is supposed 
 * to be UTF-8.  If it is not ASCII and it is from a single-byte
 * data structure, then we we clean it up because we dont' know
 * what the actual encoding is (or if it is corrupt). 
 * @param name Name to cleanup
 */
void 
fatfs_cleanup_ascii(char *name) 
{
    int i;
    for (i = 0; name[i] != '\0'; i++) {
        if ((unsigned char)(name[i]) > 0x7e) {
            name[i] = '^';
        }
    }
}


/** 
 * \internal
 * Copy the contents of a raw directry entry into a TSK_FS_INFO structure.
 *
 * @param fatfs File system that directory entry is from
 * @param fs_meta Generic inode structure to copy data into
 * @param in Directory entry to copy data from
 * @param sect Sector address where directory entry is from -- used
 * to determine allocation status.
 * @param inum Address of the inode.
 *
 * @returns 1 on error and 0 on success.  Errors should only occur for
 * Unicode conversion problems and when this occurs the name will be
 * NULL terminated (but with unknown contents). 
 *
 */
static TSK_RETVAL_ENUM
fatfs_dinode_copy(FATFS_INFO * fatfs, TSK_FS_META * fs_meta,
    fatfs_dentry * in, TSK_DADDR_T sect, TSK_INUM_T inum)
{
    int retval;
    int i;
    TSK_FS_INFO *fs = (TSK_FS_INFO *) & fatfs->fs_info;
    TSK_DADDR_T *addr_ptr;

    if (fs_meta->content_len < FATFS_FILE_CONTENT_LEN) {
        if ((fs_meta =
                tsk_fs_meta_realloc(fs_meta,
                    FATFS_FILE_CONTENT_LEN)) == NULL) {
            return 1;
        }
    }

    fs_meta->attr_state = TSK_FS_META_ATTR_EMPTY;
    if (fs_meta->attr) {
        tsk_fs_attrlist_markunused(fs_meta->attr);
    }

    fs_meta->mode = attr2mode(in->attrib);
    fs_meta->type = attr2type(in->attrib);

    fs_meta->addr = inum;

    /* Use the allocation status of the sector to determine if the
     * dentry is allocated or not */
    retval = fatfs_is_sectalloc(fatfs, sect);
    if (retval == -1) {
        return TSK_ERR;
    }
    else if (retval == 1) {
        fs_meta->flags = ((in->name[0] == FATFS_SLOT_DELETED) ?
            TSK_FS_META_FLAG_UNALLOC : TSK_FS_META_FLAG_ALLOC);
    }
    else {
        fs_meta->flags = TSK_FS_META_FLAG_UNALLOC;
    }

    /* Slot has not been used yet */
    fs_meta->flags |= ((in->name[0] == FATFS_SLOT_EMPTY) ?
        TSK_FS_META_FLAG_UNUSED : TSK_FS_META_FLAG_USED);

    if ((in->attrib & FATFS_ATTR_LFN) == FATFS_ATTR_LFN) {
        /* LFN entries don't have these values */
        fs_meta->nlink = 0;
        fs_meta->size = 0;
        fs_meta->mtime = 0;
        fs_meta->atime = 0;
        fs_meta->ctime = 0;
        fs_meta->crtime = 0;
        fs_meta->mtime_nano = fs_meta->atime_nano = fs_meta->ctime_nano =
            fs_meta->crtime_nano = 0;
    }
    else {
        /* There is no notion of link in FAT, just deleted or not */
        fs_meta->nlink = (in->name[0] == FATFS_SLOT_DELETED) ? 0 : 1;
        fs_meta->size = (TSK_OFF_T) tsk_getu32(fs->endian, in->size);

        /* If these are valid dates, then convert to a unix date format */
        if (FATFS_ISDATE(tsk_getu16(fs->endian, in->wdate)))
            fs_meta->mtime =
                dos2unixtime(tsk_getu16(fs->endian, in->wdate),
                tsk_getu16(fs->endian, in->wtime), 0);
        else
            fs_meta->mtime = 0;
        fs_meta->mtime_nano = 0;

        if (FATFS_ISDATE(tsk_getu16(fs->endian, in->adate)))
            fs_meta->atime =
                dos2unixtime(tsk_getu16(fs->endian, in->adate), 0, 0);
        else
            fs_meta->atime = 0;
        fs_meta->atime_nano = 0;


        /* cdate is the creation date in FAT and there is no change,
         * so we just put in into change and set create to 0.  The other
         * front-end code knows how to handle it and display it
         */
        if (FATFS_ISDATE(tsk_getu16(fs->endian, in->cdate))) {
            fs_meta->crtime =
                dos2unixtime(tsk_getu16(fs->endian, in->cdate),
                tsk_getu16(fs->endian, in->ctime), in->ctimeten);
            fs_meta->crtime_nano = dos2nanosec(in->ctimeten);
        }
        else {
            fs_meta->crtime = 0;
            fs_meta->crtime_nano = 0;
        }

        // FAT does not have a changed time
        fs_meta->ctime = 0;
        fs_meta->ctime_nano = 0;
    }

    /* Values that do not exist in FAT */
    fs_meta->uid = 0;
    fs_meta->gid = 0;
    fs_meta->seq = 0;


    /* We will be copying a name, so allocate a structure */
    if (fs_meta->name2 == NULL) {
        if ((fs_meta->name2 = (TSK_FS_META_NAME_LIST *)
                tsk_malloc(sizeof(TSK_FS_META_NAME_LIST))) == NULL)
            return TSK_ERR;
        fs_meta->name2->next = NULL;
    }

    /* If we have a LFN entry, then we need to convert the three
     * parts of the name to UTF-8 and copy it into the name structure .
     */
    if ((in->attrib & FATFS_ATTR_LFN) == FATFS_ATTR_LFN) {
        fatfs_dentry_lfn *lfn = (fatfs_dentry_lfn *) in;

        /* Convert the first part of the name */
        UTF8 *name8 = (UTF8 *) fs_meta->name2->name;
        UTF16 *name16 = (UTF16 *) lfn->part1;

        int retVal = tsk_UTF16toUTF8(fs->endian, (const UTF16 **) &name16,
            (UTF16 *) & lfn->part1[10],
            &name8,
            (UTF8 *) ((uintptr_t) fs_meta->name2->name +
                sizeof(fs_meta->name2->name)),
            TSKlenientConversion);

        if (retVal != TSKconversionOK) {
            tsk_error_reset();
            tsk_errno = TSK_ERR_FS_UNICODE;
            snprintf(tsk_errstr, TSK_ERRSTR_L,
                "fatfs_dinode_copy: Error converting FAT LFN (1) to UTF8: %d",
                retVal);
            *name8 = '\0';

            return TSK_COR;
        }

        /* Convert the second part of the name */
        name16 = (UTF16 *) lfn->part2;
        retVal = tsk_UTF16toUTF8(fs->endian, (const UTF16 **) &name16,
            (UTF16 *) & lfn->part2[12],
            &name8,
            (UTF8 *) ((uintptr_t) fs_meta->name2->name +
                sizeof(fs_meta->name2->name)), TSKlenientConversion);

        if (retVal != TSKconversionOK) {
            tsk_error_reset();
            tsk_errno = TSK_ERR_FS_UNICODE;
            snprintf(tsk_errstr, TSK_ERRSTR_L,
                "fatfs_dinode_copy: Error converting FAT LFN (2) to UTF8: %d",
                retVal);
            *name8 = '\0';

            return TSK_COR;
        }

        /* Convert the third part of the name */
        name16 = (UTF16 *) lfn->part3;
        retVal = tsk_UTF16toUTF8(fs->endian, (const UTF16 **) &name16,
            (UTF16 *) & lfn->part3[4],
            &name8,
            (UTF8 *) ((uintptr_t) fs_meta->name2->name +
                sizeof(fs_meta->name2->name)), TSKlenientConversion);

        if (retVal != TSKconversionOK) {
            tsk_error_reset();
            tsk_errno = TSK_ERR_FS_UNICODE;
            snprintf(tsk_errstr, TSK_ERRSTR_L,
                "fatfs_dinode_copy: Error converting FAT LFN (3) to UTF8: %d",
                retVal);
            *name8 = '\0';

            return TSK_COR;
        }

        /* Make sure it is NULL Terminated */
        if ((uintptr_t) name8 >
            (uintptr_t) fs_meta->name2->name +
            sizeof(fs_meta->name2->name))
            fs_meta->name2->name[sizeof(fs_meta->name2->name) - 1] = '\0';
        else
            *name8 = '\0';
    }
    /* If the entry is for a volume label, then copy the name and
     * append a special label 
     */
    else if ((in->attrib & FATFS_ATTR_VOLUME) == FATFS_ATTR_VOLUME) {
        int a;

        i = 0;
        for (a = 0; a < 8; a++) {
            if ((in->name[a] != 0x00) && (in->name[a] != 0xff))
                fs_meta->name2->name[i++] = in->name[a];
        }
        for (a = 0; a < 3; a++) {
            if ((in->ext[a] != 0x00) && (in->ext[a] != 0xff))
                fs_meta->name2->name[i++] = in->ext[a];
        }
        fs_meta->name2->name[i] = '\0';

        /* clean up non-ASCII because we are
         * copying it into a buffer that is supposed to be UTF-8 and
         * we don't know what encoding it is actually in or if it is 
         * simply junk. */
        fatfs_cleanup_ascii(fs_meta->name2->name);
    }
    /* If the entry is a normal short entry, then copy the name
     * and add the '.' for the extension
     */
    else {
        for (i = 0; (i < 8) && (in->name[i] != 0) && (in->name[i] != ' ');
            i++) {
            if ((i == 0) && (in->name[0] == FATFS_SLOT_DELETED))
                fs_meta->name2->name[0] = '_';
            else if ((in->lowercase & FATFS_CASE_LOWER_BASE) &&
                (in->name[i] >= 'A') && (in->name[i] <= 'Z'))
                fs_meta->name2->name[i] = in->name[i] + 32;
            else
                fs_meta->name2->name[i] = in->name[i];
        }

        if ((in->ext[0]) && (in->ext[0] != ' ')) {
            int a;
            fs_meta->name2->name[i++] = '.';
            for (a = 0;
                (a < 3) && (in->ext[a] != 0) && (in->ext[a] != ' ');
                a++, i++) {
                if ((in->lowercase & FATFS_CASE_LOWER_EXT)
                    && (in->ext[a] >= 'A') && (in->ext[a] <= 'Z'))
                    fs_meta->name2->name[i] = in->ext[a] + 32;
                else
                    fs_meta->name2->name[i] = in->ext[a];
            }
        }
        fs_meta->name2->name[i] = '\0';

        /* clean up non-ASCII because we are
         * copying it into a buffer that is supposed to be UTF-8 and
         * we don't know what encoding it is actually in or if it is 
         * simply junk. */
        fatfs_cleanup_ascii(fs_meta->name2->name);
    }

    /* Clean up name to remove control characters */
    i = 0;
    while (fs_meta->name2->name[i] != '\0') {
        if (TSK_IS_CNTRL(fs_meta->name2->name[i]))
            fs_meta->name2->name[i] = '^';
        i++;
    }

    /* get the starting cluster */
    addr_ptr = (TSK_DADDR_T *) fs_meta->content_ptr;
    if ((in->attrib & FATFS_ATTR_LFN) == FATFS_ATTR_LFN) {
        addr_ptr[0] = 0;
    }
    else {
        addr_ptr[0] = FATFS_DENTRY_CLUST(fs, in) & fatfs->mask;
    }

    /* FAT does not store a size for its directories so make one based
     * on the number of allocated sectors 
     */
    if ((in->attrib & FATFS_ATTR_DIRECTORY) &&
        ((in->attrib & FATFS_ATTR_LFN) != FATFS_ATTR_LFN)) {
        if (fs_meta->flags & TSK_FS_META_FLAG_ALLOC) {
            TSK_LIST *list_seen = NULL;

            /* count the total number of clusters in this file */
            TSK_DADDR_T clust = FATFS_DENTRY_CLUST(fs, in);
            int cnum = 0;

            while ((clust) && (0 == FATFS_ISEOF(clust, fatfs->mask))) {
                TSK_DADDR_T nxt;

                /* Make sure we do not get into an infinite loop */
                if (tsk_list_find(list_seen, clust)) {
                    if (tsk_verbose)
                        tsk_fprintf(stderr,
                            "Loop found while determining directory size\n");
                    break;
                }
                if (tsk_list_add(&list_seen, clust)) {
                    tsk_list_free(list_seen);
                    list_seen = NULL;
                    return TSK_ERR;
                }

                cnum++;

                if (fatfs_getFAT(fatfs, clust, &nxt))
                    break;
                else
                    clust = nxt;
            }

            tsk_list_free(list_seen);
            list_seen = NULL;

            fs_meta->size =
                (TSK_OFF_T) ((cnum * fatfs->csize) << fatfs->ssize_sh);
        }
        /* if the dir is unallocated, then assume 0 or cluster size
         * Ideally, we would have a smart algo here to do recovery
         * and look for dentries.  However, we do not have that right
         * now and if we do not add this special check then it can 
         * assume that an allocated file cluster chain belongs to the
         * directory */
        else {
            // if the first cluster is allocated, then set size to be 0
            if (fatfs_is_clustalloc(fatfs, FATFS_DENTRY_CLUST(fs,
                        in)) == 1)
                fs_meta->size = 0;
            else
                fs_meta->size = fatfs->csize << fatfs->ssize_sh;
        }
    }


    return TSK_OK;
}

/**
 * \internal
 * Create an FS_INODE structure for the root directory.  FAT does
 * not have a directory entry for the root directory, but this
 * function collects the needed data to make one. 
 *
 * @param fatfs File system to analyze
 * @param fs_meta Inode structure to copy root directory information into.
 * @return 1 on error and 0 on success
 */
uint8_t
fatfs_make_root(FATFS_INFO * fatfs, TSK_FS_META * fs_meta)
{
//    TSK_FS_INFO *fs = (TSK_FS_INFO *) fatfs;
    TSK_DADDR_T *addr_ptr;

    fs_meta->type = (TSK_FS_META_TYPE_DIR);
    fs_meta->mode = 0;
    fs_meta->nlink = 1;
    fs_meta->addr = FATFS_ROOTINO;
    fs_meta->flags = (TSK_FS_META_FLAG_USED | TSK_FS_META_FLAG_ALLOC);
    fs_meta->uid = fs_meta->gid = 0;
    fs_meta->mtime = fs_meta->atime = fs_meta->ctime = fs_meta->crtime = 0;
    fs_meta->mtime_nano = fs_meta->atime_nano = fs_meta->ctime_nano =
        fs_meta->crtime_nano = 0;

    if (fs_meta->name2 == NULL) {
        if ((fs_meta->name2 = (TSK_FS_META_NAME_LIST *)
                tsk_malloc(sizeof(TSK_FS_META_NAME_LIST))) == NULL)
            return 1;
        fs_meta->name2->next = NULL;
    }
    fs_meta->name2->name[0] = '\0';

    fs_meta->attr_state = TSK_FS_META_ATTR_EMPTY;
    if (fs_meta->attr) {
        tsk_fs_attrlist_markunused(fs_meta->attr);
    }
    addr_ptr = (TSK_DADDR_T *) fs_meta->content_ptr;

    /* TSK_FS_TYPE_FAT12 and TSK_FS_TYPE_FAT16 don't use the FAT for root directory, so 
     * we will have to fake it.
     */
    if (fatfs->fs_info.ftype != TSK_FS_TYPE_FAT32) {
        TSK_DADDR_T snum;

        /* Other code will have to check this as a special condition 
         */
        addr_ptr[0] = 1;

        /* difference between end of FAT and start of clusters */
        snum = fatfs->firstclustsect - fatfs->firstdatasect;

        /* number of bytes */
        fs_meta->size = snum << fatfs->ssize_sh;
    }
    else {
        /* Get the number of allocated clusters */
        TSK_DADDR_T cnum;
        TSK_DADDR_T clust;
        TSK_LIST *list_seen = NULL;

        /* base cluster */
        clust = FATFS_SECT_2_CLUST(fatfs, fatfs->rootsect);
        addr_ptr[0] = clust;

        cnum = 0;
        while ((clust) && (0 == FATFS_ISEOF(clust, FATFS_32_MASK))) {
            TSK_DADDR_T nxt;

            /* Make sure we do not get into an infinite loop */
            if (tsk_list_find(list_seen, clust)) {
                if (tsk_verbose)
                    tsk_fprintf(stderr,
                        "Loop found while determining root directory size\n");
                break;
            }
            if (tsk_list_add(&list_seen, clust)) {
                tsk_list_free(list_seen);
                list_seen = NULL;
                return 1;
            }

            cnum++;
            if (fatfs_getFAT(fatfs, clust, &nxt))
                break;
            else
                clust = nxt;
        }
        tsk_list_free(list_seen);
        list_seen = NULL;
        fs_meta->size = (cnum * fatfs->csize) << fatfs->ssize_sh;
    }
    return 0;
}


/**
* \internal
 * Create an FS_INODE structure for the master boot record. 
 *
 * @param fatfs File system to analyze
 * @param fs_meta Inode structure to copy file information into.
 * @return 1 on error and 0 on success
 */
uint8_t
fatfs_make_mbr(FATFS_INFO * fatfs, TSK_FS_META * fs_meta)
{
    TSK_DADDR_T *addr_ptr;
    TSK_FS_INFO *fs = (TSK_FS_INFO *) fatfs;

    fs_meta->type = TSK_FS_META_TYPE_VIRT;
    fs_meta->mode = 0;
    fs_meta->nlink = 1;
    fs_meta->addr = FATFS_MBRINO(fs);
    fs_meta->flags = (TSK_FS_META_FLAG_USED | TSK_FS_META_FLAG_ALLOC);
    fs_meta->uid = fs_meta->gid = 0;
    fs_meta->mtime = fs_meta->atime = fs_meta->ctime = fs_meta->crtime = 0;
    fs_meta->mtime_nano = fs_meta->atime_nano = fs_meta->ctime_nano =
        fs_meta->crtime_nano = 0;

    if (fs_meta->name2 == NULL) {
        if ((fs_meta->name2 = (TSK_FS_META_NAME_LIST *)
                tsk_malloc(sizeof(TSK_FS_META_NAME_LIST))) == NULL)
            return 1;
        fs_meta->name2->next = NULL;
    }
    strncpy(fs_meta->name2->name, FATFS_MBRNAME,
        TSK_FS_META_NAME_LIST_NSIZE);

    fs_meta->attr_state = TSK_FS_META_ATTR_EMPTY;
    if (fs_meta->attr) {
        tsk_fs_attrlist_markunused(fs_meta->attr);
    }

    addr_ptr = (TSK_DADDR_T *) fs_meta->content_ptr;
    addr_ptr[0] = 0;
    fs_meta->size = 512;

    return 0;
}

/**
* \internal
 * Create an FS_INODE structure for the FAT tables. 
 *
 * @param fatfs File system to analyze
 * @param a_which 1 or 2 to choose between defining FAT1 or FAT2
 * @param fs_meta Inode structure to copy file information into.
 * @return 1 on error and 0 on success
 */
uint8_t
fatfs_make_fat(FATFS_INFO * fatfs, uint8_t a_which, TSK_FS_META * fs_meta)
{
    TSK_FS_INFO *fs = (TSK_FS_INFO *) fatfs;
    TSK_DADDR_T *addr_ptr;

    fs_meta->type = TSK_FS_META_TYPE_VIRT;
    fs_meta->mode = 0;
    fs_meta->nlink = 1;

    fs_meta->flags = (TSK_FS_META_FLAG_USED | TSK_FS_META_FLAG_ALLOC);
    fs_meta->uid = fs_meta->gid = 0;
    fs_meta->mtime = fs_meta->atime = fs_meta->ctime = fs_meta->crtime = 0;
    fs_meta->mtime_nano = fs_meta->atime_nano = fs_meta->ctime_nano =
        fs_meta->crtime_nano = 0;

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

    if (a_which == 1) {
        fs_meta->addr = FATFS_FAT1INO(fs);
        strncpy(fs_meta->name2->name, FATFS_FAT1NAME,
            TSK_FS_META_NAME_LIST_NSIZE);
        addr_ptr = (TSK_DADDR_T *) fs_meta->content_ptr;
        addr_ptr[0] = fatfs->firstfatsect;
    }
    else if (a_which == 2) {
        fs_meta->addr = FATFS_FAT2INO(fs);
        strncpy(fs_meta->name2->name, FATFS_FAT2NAME,
            TSK_FS_META_NAME_LIST_NSIZE);
        addr_ptr = (TSK_DADDR_T *) fs_meta->content_ptr;
        addr_ptr[0] = fatfs->firstfatsect + fatfs->sectperfat;
    }
    else {
        ////@@@ 
    }

    fs_meta->attr_state = TSK_FS_META_ATTR_EMPTY;
    if (fs_meta->attr) {
        tsk_fs_attrlist_markunused(fs_meta->attr);
    }

    fs_meta->size = fatfs->sectperfat * fs->block_size;

    return 0;
}



/* 
 * Is the pointed to buffer a directory entry buffer? 
 *
 * @param a_basic 1 if only basic tests should be performed. 
 * Returns 1 if it is, 0 if not
 */
uint8_t
fatfs_isdentry(FATFS_INFO * fatfs, fatfs_dentry * de, uint8_t a_basic)
{
    TSK_FS_INFO *fs = (TSK_FS_INFO *) & fatfs->fs_info;
    if (!de)
        return 0;

    /* LFN have their own checks, which are pretty weak since most
     * fields are UTF16 */
    if ((de->attrib & FATFS_ATTR_LFN) == FATFS_ATTR_LFN) {
        fatfs_dentry_lfn *de_lfn = (fatfs_dentry_lfn *) de;

        if ((de_lfn->seq > (FATFS_LFN_SEQ_FIRST | 0x0f))
            && (de_lfn->seq != FATFS_SLOT_DELETED)) {
            if (tsk_verbose)
                fprintf(stderr, "fatfs_isdentry: LFN seq\n");
            return 0;
        }

        return 1;
    }
    else {
        // the basic test is only for the 'essential data'.
        if (a_basic == 0) {
            if (de->lowercase & ~(FATFS_CASE_LOWER_ALL)) {
                if (tsk_verbose)
                    fprintf(stderr, "fatfs_isdentry: lower case all\n");
                return 0;
            }
            else if (de->attrib & ~(FATFS_ATTR_ALL)) {
                if (tsk_verbose)
                    fprintf(stderr, "fatfs_isdentry: attribute all\n");
                return 0;
            }

            // verify we do not have too many flags set
            if (de->attrib & FATFS_ATTR_NORMAL) {
                if ((de->attrib & FATFS_ATTR_VOLUME) ||
                    (de->attrib & FATFS_ATTR_DIRECTORY)) {
                    if (tsk_verbose)
                        fprintf(stderr,
                            "fatfs_isdentry: Normal and Vol/Dir\n");
                    return 0;
                }
            }
            if (de->attrib & FATFS_ATTR_VOLUME) {
                if ((de->attrib & FATFS_ATTR_DIRECTORY) ||
                    (de->attrib & FATFS_ATTR_READONLY) ||
                    (de->attrib & FATFS_ATTR_ARCHIVE)) {
                    if (tsk_verbose)
                        fprintf(stderr,
                            "fatfs_isdentry: Vol and Dir/RO/Arch\n");
                    return 0;
                }
            }
        

            /* The ctime, cdate, and adate fields are optional and 
             * therefore 0 is a valid value
             */
            if ((tsk_getu16(fs->endian, de->ctime) != 0) &&
                (FATFS_ISTIME(tsk_getu16(fs->endian, de->ctime)) == 0)) {
                if (tsk_verbose)
                    fprintf(stderr, "fatfs_isdentry: ctime\n");
                return 0;
            }
            else if ((tsk_getu16(fs->endian, de->wtime) != 0) &&
                (FATFS_ISTIME(tsk_getu16(fs->endian, de->wtime)) == 0)) {
                if (tsk_verbose)
                    fprintf(stderr, "fatfs_isdentry: wtime\n");
                return 0;
            }
            else if ((tsk_getu16(fs->endian, de->cdate) != 0) &&
                (FATFS_ISDATE(tsk_getu16(fs->endian, de->cdate)) == 0)) {
                if (tsk_verbose)
                    fprintf(stderr, "fatfs_isdentry: cdate\n");
                return 0;
            }
            else if (de->ctimeten > 200) {
                if (tsk_verbose)
                    fprintf(stderr, "fatfs_isdentry: ctimeten\n");
                return 0;
            }
            else if ((tsk_getu16(fs->endian, de->adate) != 0) &&
                (FATFS_ISDATE(tsk_getu16(fs->endian, de->adate)) == 0)) {
                if (tsk_verbose)
                    fprintf(stderr, "fatfs_isdentry: adate\n");
                return 0;
            }
            else if ((tsk_getu16(fs->endian, de->wdate) != 0) &&
                (FATFS_ISDATE(tsk_getu16(fs->endian, de->wdate)) == 0)) {
                if (tsk_verbose)
                    fprintf(stderr, "fatfs_isdentry: wdate\n");
                return 0;
            }
        }

        /* verify the starting cluster is small enough */
        if ((FATFS_DENTRY_CLUST(fs, de) > (fatfs->lastclust)) &&
            (FATFS_ISEOF(FATFS_DENTRY_CLUST(fs, de), fatfs->mask) == 0)) {
            if (tsk_verbose)
                fprintf(stderr, "fatfs_isdentry: start cluster\n");
            return 0;
        }

        /* Verify the file size is smaller than the data area */
        else if (tsk_getu32(fs->endian, de->size) >
            ((fatfs->clustcnt * fatfs->csize) << fatfs->ssize_sh)) {
            if (tsk_verbose)
                fprintf(stderr, "fatfs_isdentry: size\n");
            return 0;
        }

        else if ((tsk_getu32(fs->endian, de->size) > 0)
            && (FATFS_DENTRY_CLUST(fs, de) == 0)) {
            if (tsk_verbose)
                fprintf(stderr,
                    "fatfs_isdentry: non-zero size and NULL starting cluster\n");
            return 0;
        }

        else if (is_83_name(de) == 0)
            return 0;

        // basic sanity check on values
        else if ((tsk_getu16(fs->endian, de->ctime) == 0)
            && (tsk_getu16(fs->endian, de->wtime) == 0)
            && (tsk_getu16(fs->endian, de->cdate) == 0)
            && (tsk_getu16(fs->endian, de->adate) == 0)
            && (tsk_getu16(fs->endian, de->wdate) == 0)
            && (FATFS_DENTRY_CLUST(fs, de) == 0)
            && (tsk_getu32(fs->endian, de->size) == 0)) {
            if (tsk_verbose)
                fprintf(stderr,
                    "fatfs_isdentry: nearly all values zero\n");
            return 0;
        }

        return 1;
    }
}



/**************************************************************************
 *
 * INODE WALKING
 * 
 *************************************************************************/
/* Mark the sector used in the bitmap */
static TSK_WALK_RET_ENUM
inode_walk_file_act(TSK_FS_FILE * fs_file, TSK_OFF_T a_off,
    TSK_DADDR_T addr, char *buf, size_t size,
    TSK_FS_BLOCK_FLAG_ENUM a_flags, void *a_ptr)
{
    setbit((uint8_t *) a_ptr, addr);
    return TSK_WALK_CONT;
}

/* The inode_walk call back for each file.  we want only the directories */
static TSK_WALK_RET_ENUM
inode_walk_dent_act(TSK_FS_FILE * fs_file, const char *a_path, void *a_ptr)
{
    if ((fs_file->meta == NULL)
        || (fs_file->meta->type != TSK_FS_META_TYPE_DIR))
        return TSK_WALK_CONT;

    /* Get the sector addresses & ignore any errors */
    if (tsk_fs_file_walk(fs_file,
            TSK_FS_FILE_WALK_FLAG_SLACK | TSK_FS_FILE_WALK_FLAG_AONLY,
            inode_walk_file_act, a_ptr)) {
        tsk_error_reset();
    }

    return TSK_WALK_CONT;
}


/*
 * walk the inodes
 *
 * Flags that are used: TSK_FS_META_FLAG_ALLOC, TSK_FS_META_FLAG_UNALLOC,
 * TSK_FS_META_FLAG_USED, TSK_FS_META_FLAG_UNUSED, TSK_FS_META_FLAG_ORPHAN
 *
 */
uint8_t
fatfs_inode_walk(TSK_FS_INFO * fs, TSK_INUM_T start_inum,
    TSK_INUM_T end_inum, TSK_FS_META_FLAG_ENUM a_flags,
    TSK_FS_META_WALK_CB a_action, void *a_ptr)
{
    char *myname = "fatfs_inode_walk";
    FATFS_INFO *fatfs = (FATFS_INFO *) fs;
    TSK_INUM_T end_inum_tmp;
    TSK_FS_FILE *fs_file;
    TSK_DADDR_T sect, ssect, lsect;
    fatfs_dentry *dep;
    unsigned int myflags, didx;
    uint8_t *sect_alloc;
    ssize_t cnt;
    uint8_t done = 0;
    char *dinode_buf = NULL;

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

    /*
     * Sanity checks.
     */
    if (start_inum < fs->first_inum || start_inum > fs->last_inum) {
        tsk_error_reset();
        tsk_errno = TSK_ERR_FS_WALK_RNG;
        snprintf(tsk_errstr, TSK_ERRSTR_L,
            "%s: Start inode:  %" PRIuINUM "", myname, start_inum);
        return 1;
    }
    else if (end_inum < fs->first_inum || end_inum > fs->last_inum
        || end_inum < start_inum) {
        tsk_error_reset();
        tsk_errno = TSK_ERR_FS_WALK_RNG;
        snprintf(tsk_errstr, TSK_ERRSTR_L,
            "%s: End inode: %" PRIuINUM "", myname, end_inum);
        return 1;
    }

    if (tsk_verbose)
        tsk_fprintf(stderr,
            "fatfs_inode_walk: Inode Walking %" PRIuINUM " to %"
            PRIuINUM "\n", start_inum, end_inum);

    /* If ORPHAN is wanted, then make sure that the a_flags are correct */
    if (a_flags & TSK_FS_META_FLAG_ORPHAN) {
        a_flags |= TSK_FS_META_FLAG_UNALLOC;
        a_flags &= ~TSK_FS_META_FLAG_ALLOC;
        a_flags |= TSK_FS_META_FLAG_USED;
        a_flags &= ~TSK_FS_META_FLAG_UNUSED;
    }

    else {
        if (((a_flags & TSK_FS_META_FLAG_ALLOC) == 0) &&
            ((a_flags & TSK_FS_META_FLAG_UNALLOC) == 0)) {
            a_flags |= (TSK_FS_META_FLAG_ALLOC | TSK_FS_META_FLAG_UNALLOC);
        }

        /* If neither of the USED or UNUSED a_flags are set, then set them 
         * both
         */
        if (((a_flags & TSK_FS_META_FLAG_USED) == 0) &&
            ((a_flags & TSK_FS_META_FLAG_UNUSED) == 0)) {
            a_flags |= (TSK_FS_META_FLAG_USED | TSK_FS_META_FLAG_UNUSED);
        }
    }


    /* If we are looking for orphan files and have not yet filled
     * in the list of unalloc inodes that are pointed to, then fill
     * in the list 
     */
    if ((a_flags & TSK_FS_META_FLAG_ORPHAN)
        && (fs->list_inum_named == NULL)) {

        if (tsk_fs_dir_load_inum_named(fs) != TSK_OK) {
            strncat(tsk_errstr2,
                " - fatfs_inode_walk: identifying inodes allocated by file names",
                TSK_ERRSTR_L);
            return 1;
        }
    }

    if ((fs_file = tsk_fs_file_alloc(fs)) == NULL)
        return 1;

    if ((fs_file->meta =
            tsk_fs_meta_alloc(FATFS_FILE_CONTENT_LEN)) == NULL)
        return 1;


    // handle the root directory
    if (start_inum == FATFS_ROOTINO) {

        if (((TSK_FS_META_FLAG_ALLOC & a_flags) == TSK_FS_META_FLAG_ALLOC)
            && ((TSK_FS_META_FLAG_USED & a_flags) == TSK_FS_META_FLAG_USED)
            && ((TSK_FS_META_FLAG_ORPHAN & a_flags) == 0)) {
            int retval;

            if (fatfs_make_root(fatfs, fs_file->meta)) {
                tsk_fs_file_close(fs_file);
                return 1;
            }

            retval = a_action(fs_file, a_ptr);
            if (retval == TSK_WALK_STOP) {
                tsk_fs_file_close(fs_file);
                return 0;
            }
            else if (retval == TSK_WALK_ERROR) {
                tsk_fs_file_close(fs_file);
                return 1;
            }
        }
        /* advance it so that it is a valid starting point */
        start_inum++;

        // exit if that is all that was requested 
        if (start_inum == end_inum) {
            tsk_fs_file_close(fs_file);
            return 0;
        }
    }

    /* We will be looking at each sector to see if it contains directory
     * entries.  We can make mistakes and ignore sectors that have valid
     * entries in them.  To make sure we at least get all sectors that
     * are allocated by directories in the directory tree, we will
     * run name_walk and then a file walk on each dir. 
     * We'll be make sure to print those.  We skip this for ORPHAN hunting
     * because it doesn't help and can introduce infinite loop situations  
     * inode_walk was called by the function that determines which inodes
     * are orphans. */
    if (tsk_verbose)
        tsk_fprintf(stderr,
            "fatfs_inode_walk: Walking directories to collect sector info\n");

    if ((sect_alloc =
            (uint8_t *) tsk_malloc((size_t) ((fs->block_count +
                        7) / 8))) == NULL) {
        tsk_fs_file_close(fs_file);
        return 1;
    }
    if ((a_flags & TSK_FS_META_FLAG_ORPHAN) == 0) {

        // Do a file_walk on the root directory to get its layout
        if (fatfs_make_root(fatfs, fs_file->meta)) {
            tsk_fs_file_close(fs_file);
            free(sect_alloc);
            return 1;
        }

        if (tsk_fs_file_walk(fs_file,
                TSK_FS_FILE_WALK_FLAG_SLACK | TSK_FS_FILE_WALK_FLAG_AONLY,
                inode_walk_file_act, (void *) sect_alloc)) {
            tsk_fs_file_close(fs_file);
            free(sect_alloc);
            return 1;
        }

        // now get the rest of the directories. 
        if (tsk_fs_dir_walk(fs, fs->root_inum,
                TSK_FS_DIR_WALK_FLAG_ALLOC | TSK_FS_DIR_WALK_FLAG_RECURSE |
                TSK_FS_DIR_WALK_FLAG_NOORPHAN, inode_walk_dent_act,
                (void *) sect_alloc)) {
            strncat(tsk_errstr2,
                " - fatfs_inode_walk: mapping directories", TSK_ERRSTR_L);
            tsk_fs_file_close(fs_file);
            free(sect_alloc);
            return 1;
        }
    }


    /* start analyzing each sector
     *
     * Perform a test on the first 32 bytes of each sector to identify if
     * the sector contains directory entries.  If it does, then continue
     * to analyze it.  If not, then read the next sector 
     */

    /* identify the starting and ending inodes sector addrs */

    /* we need to handle end_inum specially if it is for the 
     * virtual ORPHANS directory or virtual FAT files.  
     * Handle these outside of the loop. */
    if (end_inum > fs->last_inum - FATFS_NUM_SPECFILE)
        end_inum_tmp = fs->last_inum - FATFS_NUM_SPECFILE;
    else
        end_inum_tmp = end_inum;

    ssect = FATFS_INODE_2_SECT(fatfs, start_inum);
    lsect = FATFS_INODE_2_SECT(fatfs, end_inum_tmp);

    if (ssect > fs->last_block) {
        tsk_error_reset();
        tsk_errno = TSK_ERR_FS_WALK_RNG;
        snprintf(tsk_errstr, TSK_ERRSTR_L,
            "fatfs_inode_walk: Starting inode in sector too big for image: %"
            PRIuDADDR, ssect);
        tsk_fs_file_close(fs_file);
        free(sect_alloc);
        return 1;
    }
    else if (lsect > fs->last_block) {
        tsk_error_reset();
        tsk_errno = TSK_ERR_FS_WALK_RNG;
        snprintf(tsk_errstr, TSK_ERRSTR_L,
            "fatfs_inode_walk: Ending inode in sector too big for image: %"
            PRIuDADDR, lsect);
        tsk_fs_file_close(fs_file);
        free(sect_alloc);
        return 1;
    }

    if ((dinode_buf = tsk_malloc(fatfs->csize * fatfs->ssize)) == NULL) {
        tsk_fs_file_close(fs_file);
        free(sect_alloc);
        return 1;
    }

    sect = ssect;
    while (sect <= lsect) {
        int clustalloc;         // 1 if current sector / cluster is allocated
        size_t sect_proc;       // number of sectors read for this loop
        size_t sidx;            // sector index for loop

        /* This occurs for the root directory of TSK_FS_TYPE_FAT12/16 
         * 
         * We are going to process the image in clusters, so take care of the root
         * directory seperately.
         */
        if (sect < fatfs->firstclustsect) {

            // there are no orphans in the root directory
            if ((a_flags & TSK_FS_META_FLAG_ORPHAN) != 0) {
                sect = fatfs->firstclustsect;
                continue;
            }

            clustalloc = 1;

            /* read the sector */
            cnt =
                tsk_fs_read_block(fs, sect, dinode_buf, fatfs->ssize);
            if (cnt != fatfs->ssize) {
                if (cnt >= 0) {
                    tsk_error_reset();
                    tsk_errno = TSK_ERR_FS_READ;
                }
                snprintf(tsk_errstr2, TSK_ERRSTR_L,
                    "fatfs_inode_walk (root dir): sector: %" PRIuDADDR,
                    sect);
                tsk_fs_file_close(fs_file);
                free(sect_alloc);
                free(dinode_buf);
                return 1;
            }
            sect_proc = 1;
        }

        /* For the data area, we will read in cluster-sized chunks */
        else {

            /* get the base sector for the cluster in which the first inode exists */
            sect =
                FATFS_CLUST_2_SECT(fatfs, (FATFS_SECT_2_CLUST(fatfs,
                        sect)));

            /* if the cluster is not allocated, then do not go into it if we 
             * only want allocated/link entries
             * If it is allocated, then go into it no matter what
             */
            clustalloc = fatfs_is_sectalloc(fatfs, sect);
            if (clustalloc == -1) {
                tsk_fs_file_close(fs_file);
                free(sect_alloc);
                free(dinode_buf);
                return 1;
            }
            else if ((clustalloc == 0)
                && ((a_flags & TSK_FS_META_FLAG_UNALLOC) == 0)) {
                sect += fatfs->csize;
                continue;
            }


            /* If it is allocated, but we know it is not allocated to a
             * directory then skip it.  NOTE: This will miss unallocated
             * entries in slack space of the file...
             */
            if ((clustalloc == 1) && (isset(sect_alloc, sect) == 0)) {
                sect += fatfs->csize;
                continue;
            }

            /* The final cluster may not be full */
            if (lsect - sect + 1 < fatfs->csize)
                sect_proc = (size_t) (lsect - sect + 1);
            else
                sect_proc = fatfs->csize;

            /* read the full cluster */
            cnt = tsk_fs_read_block
                (fs, sect, dinode_buf, sect_proc << fatfs->ssize_sh);
            if (cnt != (sect_proc << fatfs->ssize_sh)) {
                if (cnt >= 0) {
                    tsk_error_reset();
                    tsk_errno = TSK_ERR_FS_READ;
                }
                snprintf(tsk_errstr2, TSK_ERRSTR_L,
                    "fatfs_inode_walk: sector: %" PRIuDADDR, sect);
                tsk_fs_file_close(fs_file);
                free(sect_alloc);
                free(dinode_buf);
                return 1;
            }
        }

        // cycle through the sectors read
        for (sidx = 0; sidx < sect_proc; sidx++) {
            TSK_INUM_T inum;
            uint8_t isInDir;

            dep =
                (fatfs_dentry *) & dinode_buf[sidx << fatfs->ssize_sh];

            /* if we know it is not part of a directory and it is not valid dentires,
             * then skip it */
            isInDir = isset(sect_alloc, sect);
            if ((isInDir == 0) &&
                (fatfs_isdentry(fatfs, dep, 0) == 0)) {
                sect++;
                continue;
            }

            /* See if the last inode in this sector is smaller than the starting one */
            if (FATFS_SECT_2_INODE(fatfs, sect + 1) < start_inum) {
                sect++;
                continue;
            }

            /* get the base inode address of this sector */
            inum = FATFS_SECT_2_INODE(fatfs, sect);

            if (tsk_verbose)
                tsk_fprintf(stderr,
                    "fatfs_inode_walk: Processing sector %" PRIuDADDR
                    " starting at inode %" PRIuINUM "\n", sect, inum);

            /* cycle through the directory entries */
            for (didx = 0; didx < fatfs->dentry_cnt_se;
                didx++, inum++, dep++) {
                int retval;
                TSK_RETVAL_ENUM retval2;

                /* If less, then move on */
                if (inum < start_inum)
                    continue;

                /* If we are done, then exit from the loops  */
                if (inum > end_inum_tmp) {
                    done = 1;
                    break;
                }


                /* if this is a long file name entry, then skip it and 
                 * wait for the short name */
                if ((dep->attrib & FATFS_ATTR_LFN) == FATFS_ATTR_LFN)
                    continue;


                /* we don't care about . and .. entries because they
                 * are redundant of other 'inode' entries */
                if (((dep->attrib & FATFS_ATTR_DIRECTORY)
                        == FATFS_ATTR_DIRECTORY)
                    && (dep->name[0] == '.'))
                    continue;


                /* Allocation status 
                 * This is determined first by the sector allocation status
                 * an then the dentry flag.  When a directory is deleted, the
                 * contents are not always set to unallocated
                 */
                if (clustalloc == 1) {
                    myflags =
                        ((dep->name[0] ==
                            FATFS_SLOT_DELETED) ? TSK_FS_META_FLAG_UNALLOC
                        : TSK_FS_META_FLAG_ALLOC);
                }
                else {
                    myflags = TSK_FS_META_FLAG_UNALLOC;
                }

                if ((a_flags & myflags) != myflags)
                    continue;

                /* Slot has not been used yet */
                myflags |= ((dep->name[0] == FATFS_SLOT_EMPTY) ?
                    TSK_FS_META_FLAG_UNUSED : TSK_FS_META_FLAG_USED);

                if ((a_flags & myflags) != myflags)
                    continue;

                /* If we want only orphans, then check if this
                 * inode is in the seen list 
                 */
                if ((myflags & TSK_FS_META_FLAG_UNALLOC) &&
                    (a_flags & TSK_FS_META_FLAG_ORPHAN) &&
                    (tsk_list_find(fs->list_inum_named, inum))) {
                    continue;
                }

                /* Do a final sanity check */
                if (0 == fatfs_isdentry(fatfs, dep, isInDir))
                    continue;

                if ((retval2 =
                        fatfs_dinode_copy(fatfs, fs_file->meta, dep, sect,
                            inum)) != TSK_OK) {
                    /* Ignore this error and continue */
                    if (retval2 == TSK_COR) {
                        if (tsk_verbose)
                            tsk_error_print(stderr);
                        tsk_error_reset();
                        continue;
                    }
                    else {
                        tsk_fs_file_close(fs_file);
                        free(sect_alloc);
                        free(dinode_buf);
                        return 1;
                    }
                }

                if (tsk_verbose)
                    tsk_fprintf(stderr,
                        "fatfs_inode_walk: Directory Entry %" PRIuINUM
                        " (%u) at sector %" PRIuDADDR "\n", inum, didx,
                        sect);

                retval = a_action(fs_file, a_ptr);
                if (retval == TSK_WALK_STOP) {
                    tsk_fs_file_close(fs_file);
                    free(sect_alloc);
                    free(dinode_buf);
                    return 0;
                }
                else if (retval == TSK_WALK_ERROR) {
                    tsk_fs_file_close(fs_file);
                    free(sect_alloc);
                    free(dinode_buf);
                    return 1;
                }
            }                   /* dentries */
            sect++;
            if (done)
                break;
        }
        if (done)
            break;
    }


    free(sect_alloc);
    free(dinode_buf);
    dinode_buf = NULL;


    // handle the virtual orphans folder and FAT files if they asked for them
    if ((end_inum > fs->last_inum - FATFS_NUM_SPECFILE)
        && (a_flags & TSK_FS_META_FLAG_ALLOC)
        && (a_flags & TSK_FS_META_FLAG_USED)
        && ((a_flags & TSK_FS_META_FLAG_ORPHAN) == 0)) {
        TSK_INUM_T inum;

        // cycle through the special files
        for (inum = fs->last_inum - FATFS_NUM_SPECFILE + 1;
            inum <= end_inum; inum++) {
            int retval;

            tsk_fs_meta_reset(fs_file->meta);

            if (inum == FATFS_MBRINO(fs)) {
                if (fatfs_make_mbr(fatfs, fs_file->meta)) {
                    tsk_fs_file_close(fs_file);
                    return 1;
                }
            }
            else if (inum == FATFS_FAT1INO(fs)) {
                if (fatfs_make_fat(fatfs, 1, fs_file->meta)) {
                    tsk_fs_file_close(fs_file);
                    return 1;
                }
            }
            else if (inum == FATFS_FAT2INO(fs)) {
                if (fatfs_make_fat(fatfs, 2, fs_file->meta)) {
                    tsk_fs_file_close(fs_file);
                    return 1;
                }
            }
            else if (inum == TSK_FS_ORPHANDIR_INUM(fs)) {
                if (tsk_fs_dir_make_orphan_dir_meta(fs, fs_file->meta)) {
                    tsk_fs_file_close(fs_file);
                    return 1;
                }
            }

            retval = a_action(fs_file, a_ptr);
            if (retval == TSK_WALK_STOP) {
                tsk_fs_file_close(fs_file);
                return 0;
            }
            else if (retval == TSK_WALK_ERROR) {
                tsk_fs_file_close(fs_file);
                return 1;
            }
        }
    }

    tsk_fs_file_close(fs_file);
    return 0;
}                               /* end of inode_walk */


/*
 * return the contents of a specific inode
 *
 * 1 is returned if an error occurs or if the entry is not
 * a valid inode
 */
uint8_t
fatfs_inode_lookup(TSK_FS_INFO * fs, TSK_FS_FILE * a_fs_file,
    TSK_INUM_T inum)
{
    FATFS_INFO *fatfs = (FATFS_INFO *) fs;
    ssize_t cnt;
    TSK_DADDR_T sect;
    size_t off;
    TSK_RETVAL_ENUM retval;

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

    /* 
     * Sanity check.
     */
    if (inum < fs->first_inum || inum > fs->last_inum) {
        tsk_error_reset();
        tsk_errno = TSK_ERR_FS_INODE_NUM;
        snprintf(tsk_errstr, TSK_ERRSTR_L,
            "fatfs_inode_lookup: %" PRIuINUM " too large/small", inum);
        return 1;
    }

    if (a_fs_file == NULL) {
        tsk_errno = TSK_ERR_FS_ARG;
        snprintf(tsk_errstr, TSK_ERRSTR_L,
            "fatfs_inode_lookup: fs_file is NULL");
        return 1;
    }

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

    /* As there is no real root inode in FAT, use the made up one */
    if (inum == FATFS_ROOTINO) {
        fatfs->dep = NULL;
        if (fatfs_make_root(fatfs, a_fs_file->meta))
            return 1;
        else
            return 0;
    }
    else if (inum == FATFS_MBRINO(fs)) {
        fatfs->dep = NULL;
        if (fatfs_make_mbr(fatfs, a_fs_file->meta))
            return 1;
        else
            return 0;
    }
    else if (inum == FATFS_FAT1INO(fs)) {
        fatfs->dep = NULL;
        if (fatfs_make_fat(fatfs, 1, a_fs_file->meta))
            return 1;
        else
            return 0;
    }
    else if (inum == FATFS_FAT2INO(fs)) {
        fatfs->dep = NULL;
        if (fatfs_make_fat(fatfs, 2, a_fs_file->meta))
            return 1;
        else
            return 0;
    }
    else if (inum == TSK_FS_ORPHANDIR_INUM(fs)) {
        fatfs->dep = NULL;
        if (tsk_fs_dir_make_orphan_dir_meta(fs, a_fs_file->meta))
            return 1;
        else
            return 0;
    }

    /* Get the sector that this inode would be in and its offset */
    sect = FATFS_INODE_2_SECT(fatfs, inum);
    off = FATFS_INODE_2_OFF(fatfs, inum);

    if (sect > fs->last_block) {
        tsk_error_reset();
        tsk_errno = TSK_ERR_FS_INODE_NUM;
        snprintf(tsk_errstr, TSK_ERRSTR_L,
            "fatfs_inode_lookup: Inode %" PRIuINUM
            " in sector too big for image: %" PRIuDADDR, inum, sect);
        return 1;
    }


    if (tsk_verbose)
        tsk_fprintf(stderr,
            "fatfs_inode_lookup: reading sector %" PRIuDADDR
            " for inode %" PRIuINUM "\n", sect, inum);

    cnt = tsk_fs_read_block(fs, sect, fatfs->dinodes, fatfs->ssize);
    if (cnt != fatfs->ssize) {
        if (cnt >= 0) {
            tsk_error_reset();
            tsk_errno = TSK_ERR_FS_READ;
        }
        snprintf(tsk_errstr2, TSK_ERRSTR_L,
            "fatfs_inode_lookup: block: %" PRIuDADDR, sect);
        return 1;
    }

    fatfs->dep = (fatfs_dentry *) & fatfs->dinodes[off];
    if (fatfs_isdentry(fatfs, fatfs->dep, 1)) {
        if ((retval =
                fatfs_dinode_copy(fatfs, a_fs_file->meta, fatfs->dep, sect,
                    inum)) != TSK_OK) {
            /* If there was a unicode conversion error, 
             * then still return the inode */
            if (retval == TSK_ERR) {
                return 1;
            }
            else {
                if (tsk_verbose)
                    tsk_error_print(stderr);
                tsk_error_reset();
            }
        }
        return 0;
    }
    else {
        tsk_error_reset();
        tsk_errno = TSK_ERR_FS_INODE_NUM;
        snprintf(tsk_errstr, TSK_ERRSTR_L,
            "fatfs_inode_lookup: %" PRIuINUM " is not an inode", inum);
        return 1;
    }
}


/** \internal
 * Process the file and load up the clusters into the FS_DATA attribute 
 * in fs_meta. The run will list the starting sector and length in sectors
 *
 * @param a_fs_file File to process and structore to store results in
 *
 * @returns 1 on error and 0 on success
 */
uint8_t
fatfs_make_data_run(TSK_FS_FILE * a_fs_file)
{
    TSK_FS_INFO *fs;
    TSK_DADDR_T clust;
    TSK_OFF_T size_remain;
    TSK_FS_ATTR *fs_attr = NULL;
    TSK_FS_META *fs_meta;
    FATFS_INFO *fatfs;

    if ((a_fs_file == NULL) || (a_fs_file->meta == NULL)
        || (a_fs_file->fs_info == NULL)) {
        tsk_errno = TSK_ERR_FS_ARG;
        snprintf(tsk_errstr, TSK_ERRSTR_L,
            "fatfs_make_data_run: called with NULL pointers");
        return 1;
    }
    fs_meta = a_fs_file->meta;
    fs = a_fs_file->fs_info;
    fatfs = (FATFS_INFO *) fs;

    clust = ((TSK_DADDR_T *) fs_meta->content_ptr)[0];
    size_remain = roundup(fs_meta->size, fatfs->csize * fs->block_size);

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

    // sanity check on input
    if ((clust > (fatfs->lastclust)) &&
        (FATFS_ISEOF(clust, fatfs->mask) == 0)) {
        fs_meta->attr_state = TSK_FS_META_ATTR_ERROR;
        tsk_error_reset();
        if (a_fs_file->meta->flags & TSK_FS_META_FLAG_UNALLOC)
            tsk_errno = TSK_ERR_FS_RECOVER;
        else
            tsk_errno = TSK_ERR_FS_INODE_COR;
        snprintf(tsk_errstr, TSK_ERRSTR_L,
            "fatfs_make_data_run: Starting cluster address too large: %"
            PRIuDADDR, clust);
        return 1;
    }


    /* We need to handle the special files specially because they 
     * are not in the FAT.  Except for FAT32 root dirs, those are normal.
     */
    if ((a_fs_file->meta->addr == FATFS_ROOTINO)
        && (fs->ftype != TSK_FS_TYPE_FAT32) && (clust == 1)) {
        TSK_FS_ATTR_RUN *data_run;

        if (tsk_verbose)
            tsk_fprintf(stderr,
                "fatfs_make_data_run: Loading root directory\n");

        // make a non-resident run
        data_run = tsk_fs_attr_run_alloc();
        if (data_run == NULL) {
            return 1;
        }
        data_run->addr = fatfs->rootsect;
        data_run->len = fatfs->firstclustsect - fatfs->firstdatasect;

        if ((fs_attr =
                tsk_fs_attrlist_getnew(fs_meta->attr,
                    TSK_FS_ATTR_NONRES)) == NULL) {
            return 1;
        }

        // initialize the data run
        if (tsk_fs_attr_set_run(a_fs_file, fs_attr, data_run, NULL,
                TSK_FS_ATTR_TYPE_DEFAULT, TSK_FS_ATTR_ID_DEFAULT,
                data_run->len * fs->block_size,
                data_run->len * fs->block_size,
                data_run->len * fs->block_size, 0, 0)) {
            return 1;
        }

        fs_meta->attr_state = TSK_FS_META_ATTR_STUDIED;
        return 0;
    }

    // see if it is one of the special files
    else if ((a_fs_file->meta->addr > fs->last_inum - FATFS_NUM_SPECFILE)
        && (a_fs_file->meta->addr != TSK_FS_ORPHANDIR_INUM(fs))) {
        TSK_FS_ATTR_RUN *data_run;

        if (tsk_verbose)
            tsk_fprintf(stderr,
                "fatfs_make_data_run: Loading special file: %" PRIuINUM
                "\n", a_fs_file->meta->addr);

        // make a non-resident run
        data_run = tsk_fs_attr_run_alloc();
        if (data_run == NULL) {
            return 1;
        }
        data_run->addr = clust;
        data_run->len = a_fs_file->meta->size / fs->block_size;

        if ((fs_attr =
                tsk_fs_attrlist_getnew(fs_meta->attr,
                    TSK_FS_ATTR_NONRES)) == NULL) {
            return 1;
        }

        // initialize the data run
        if (tsk_fs_attr_set_run(a_fs_file, fs_attr, data_run, NULL,
                TSK_FS_ATTR_TYPE_DEFAULT, TSK_FS_ATTR_ID_DEFAULT,
                data_run->len * fs->block_size,
                data_run->len * fs->block_size,
                data_run->len * fs->block_size, 0, 0)) {
            return 1;
        }

        fs_meta->attr_state = TSK_FS_META_ATTR_STUDIED;
        return 0;
    }


    /* A deleted file that we want to recover 
     * In this case, we could get a lot of errors because of inconsistent
     * data.  TO make it clear that these are from a recovery, we set most
     * error codes to _RECOVER so that they can be more easily suppressed.
     */
    else if (fs_meta->flags & TSK_FS_META_FLAG_UNALLOC) {
        TSK_DADDR_T sbase;
        TSK_DADDR_T startclust = clust;
        TSK_OFF_T recoversize = fs_meta->size;
        int retval;
        TSK_FS_ATTR_RUN *data_run = NULL;
        TSK_FS_ATTR_RUN *data_run_head = NULL;
        TSK_OFF_T full_len_s = 0;
        uint8_t canRecover = 1; // set to 0 if recovery is not possible

        if (tsk_verbose)
            tsk_fprintf(stderr,
                "fatfs_make_data_run: Processing deleted file %" PRIuINUM
                " in recovery mode\n", fs_meta->addr);

        /* We know the size and the starting cluster
         *
         * We are going to take the clusters from the starting cluster
         * onwards and skip the clusters that are current allocated
         */

        /* Sanity checks on the starting cluster */
        /* Convert the cluster addr to a sector addr */
        sbase = FATFS_CLUST_2_SECT(fatfs, startclust);

        if (sbase > fs->last_block) {
            tsk_error_reset();
            tsk_errno = TSK_ERR_FS_RECOVER;
            snprintf(tsk_errstr, TSK_ERRSTR_L,
                "fatfs_make_data_run: Starting cluster address too large (recovery): %"
                PRIuDADDR, sbase);
            fs_meta->attr_state = TSK_FS_META_ATTR_ERROR;
            return 1;
        }
        else {

            /* If the starting cluster is already allocated then we can't
             * recover it */
            retval = fatfs_is_clustalloc(fatfs, startclust);
            if (retval != 0) {
                canRecover = 0;
            }
        }


        /* Part 1 is to make sure there are enough unallocated clusters
         * for the size of the file 
         */
        clust = startclust;
        size_remain = recoversize;

        // we could make this negative so sign it for the comparison
        while (((int64_t) size_remain > 0) && (canRecover)) {
            int retval;
            sbase = FATFS_CLUST_2_SECT(fatfs, clust);

            /* Are we past the end of the FS? 
             * that means we could not find enough unallocated clusters
             * for the file size */
            if (sbase > fs->last_block) {
                canRecover = 0;

                if (tsk_verbose)
                    tsk_fprintf(stderr,
                        "Could not find enough unallocated sectors to recover with - aborting\n");
                break;
            }

            /* Skip allocated clusters */
            retval = fatfs_is_clustalloc(fatfs, clust);
            if (retval == -1) {
                canRecover = 0;
                break;
            }
            else if (retval == 1) {
                clust++;
                continue;
            }

            /* We can use this sector */
            // see if we need a new run
            if ((data_run == NULL)
                || (data_run->addr + data_run->len != sbase)) {

                TSK_FS_ATTR_RUN *data_run_tmp = tsk_fs_attr_run_alloc();
                if (data_run_tmp == NULL) {
                    fs_meta->attr_state = TSK_FS_META_ATTR_ERROR;
                    tsk_fs_attr_run_free(data_run_head);
                    return 1;
                }

                if (data_run_head == NULL) {
                    data_run_head = data_run_tmp;
                    data_run_tmp->offset = 0;
                }
                else if (data_run != NULL) {
                    data_run->next = data_run_tmp;
                    data_run_tmp->offset =
                        data_run->offset + data_run->len;
                }
                data_run = data_run_tmp;
                data_run->len = 0;
                data_run->addr = sbase;
            }
            data_run->len += fatfs->csize;
            full_len_s += fatfs->csize;

            size_remain -= (fatfs->csize << fatfs->ssize_sh);
            clust++;
        }

        // Get a FS_DATA structure and add the runlist to it
        if ((fs_attr =
                tsk_fs_attrlist_getnew(fs_meta->attr,
                    TSK_FS_ATTR_NONRES)) == NULL) {
            fs_meta->attr_state = TSK_FS_META_ATTR_ERROR;
            return 1;
        }

        if (canRecover) {
            /* We can recover the file */

            // initialize the data run
            if (tsk_fs_attr_set_run(a_fs_file, fs_attr, data_run_head,
                    NULL, TSK_FS_ATTR_TYPE_DEFAULT, TSK_FS_ATTR_ID_DEFAULT,
                    fs_meta->size, fs_meta->size, roundup(fs_meta->size,
                        fatfs->csize * fs->block_size), 0, 0)) {
                fs_meta->attr_state = TSK_FS_META_ATTR_ERROR;
                return 1;
            }

            fs_meta->attr_state = TSK_FS_META_ATTR_STUDIED;
        }
        // create a one cluster run
        else {
            TSK_FS_ATTR_RUN *data_run_tmp = tsk_fs_attr_run_alloc();
            if (data_run_tmp == NULL) {
                fs_meta->attr_state = TSK_FS_META_ATTR_ERROR;
                return 1;
            }
            data_run_tmp->addr = sbase;
            data_run_tmp->len = fatfs->csize;

            // initialize the data run
            if (tsk_fs_attr_set_run(a_fs_file, fs_attr, data_run_tmp, NULL,
                    TSK_FS_ATTR_TYPE_DEFAULT, TSK_FS_ATTR_ID_DEFAULT,
                    fs_meta->size, fs_meta->size, roundup(fs_meta->size,
                        fatfs->csize * fs->block_size), 0, 0)) {
                fs_meta->attr_state = TSK_FS_META_ATTR_ERROR;
                return 1;
            }

            fs_meta->attr_state = TSK_FS_META_ATTR_STUDIED;
        }

        return 0;
    }

    /* Normal cluster chain walking */
    else {
        TSK_LIST *list_seen = NULL;
        TSK_FS_ATTR_RUN *data_run = NULL;
        TSK_FS_ATTR_RUN *data_run_head = NULL;
        TSK_OFF_T full_len_s = 0;
        TSK_DADDR_T sbase;

        if (tsk_verbose)
            tsk_fprintf(stderr,
                "fatfs_make_data_run: Processing file %" PRIuINUM
                " in normal mode\n", fs_meta->addr);

        /* Cycle through the cluster chain */
        while ((clust & fatfs->mask) > 0 && (int64_t) size_remain > 0 &&
            (0 == FATFS_ISEOF(clust, fatfs->mask))) {

            /* Convert the cluster addr to a sector addr */
            sbase = FATFS_CLUST_2_SECT(fatfs, clust);

            if (sbase > fs->last_block) {
                fs_meta->attr_state = TSK_FS_META_ATTR_ERROR;
                tsk_error_reset();

                tsk_errno = TSK_ERR_FS_INODE_COR;
                snprintf(tsk_errstr, TSK_ERRSTR_L,
                    "fatfs_make_data_run: Invalid sector address in FAT (too large): %"
                    PRIuDADDR, sbase);
                return 1;
            }


            // see if we need a new run
            if ((data_run == NULL)
                || (data_run->addr + data_run->len != sbase)) {

                TSK_FS_ATTR_RUN *data_run_tmp = tsk_fs_attr_run_alloc();
                if (data_run_tmp == NULL) {
                    tsk_fs_attr_run_free(data_run_head);
                    fs_meta->attr_state = TSK_FS_META_ATTR_ERROR;
                    return 1;
                }

                if (data_run_head == NULL) {
                    data_run_head = data_run_tmp;
                    data_run_tmp->offset = 0;
                }
                else if (data_run != NULL) {
                    data_run->next = data_run_tmp;
                    data_run_tmp->offset =
                        data_run->offset + data_run->len;
                }
                data_run = data_run_tmp;
                data_run->len = 0;
                data_run->addr = sbase;
            }

            data_run->len += fatfs->csize;
            full_len_s += fatfs->csize;
            size_remain -= (fatfs->csize * fs->block_size);

            if ((int64_t) size_remain > 0) {
                TSK_DADDR_T nxt;
                if (fatfs_getFAT(fatfs, clust, &nxt)) {
                    snprintf(tsk_errstr2, TSK_ERRSTR_L,
                        "file walk: Inode: %" PRIuINUM "  cluster: %"
                        PRIuDADDR, fs_meta->addr, clust);
                    fs_meta->attr_state = TSK_FS_META_ATTR_ERROR;
                    tsk_fs_attr_run_free(data_run_head);
                    tsk_list_free(list_seen);
                    list_seen = NULL;
                    return 1;
                }
                clust = nxt;

                /* Make sure we do not get into an infinite loop */
                if (tsk_list_find(list_seen, clust)) {
                    if (tsk_verbose)
                        tsk_fprintf(stderr,
                            "Loop found while processing file\n");
                    break;
                }

                if (tsk_list_add(&list_seen, clust)) {
                    fs_meta->attr_state = TSK_FS_META_ATTR_ERROR;
                    tsk_list_free(list_seen);
                    list_seen = NULL;
                    return 1;
                }
            }
        }

        // add the run list to the inode structure
        if ((fs_attr =
                tsk_fs_attrlist_getnew(fs_meta->attr,
                    TSK_FS_ATTR_NONRES)) == NULL) {
            fs_meta->attr_state = TSK_FS_META_ATTR_ERROR;
            return 1;
        }
        // initialize the data run
        if (tsk_fs_attr_set_run(a_fs_file, fs_attr, data_run_head, NULL,
                TSK_FS_ATTR_TYPE_DEFAULT, TSK_FS_ATTR_ID_DEFAULT,
                fs_meta->size, fs_meta->size, roundup(fs_meta->size,
                    fatfs->csize * fs->block_size), 0, 0)) {
            fs_meta->attr_state = TSK_FS_META_ATTR_ERROR;
            return 1;
        }

        tsk_list_free(list_seen);
        list_seen = NULL;

        fs_meta->attr_state = TSK_FS_META_ATTR_STUDIED;
        return 0;
    }
}
