pixOctreeQuantizePixels(PIX       *pixs,
                        CQCELL  ***cqcaa,
                        l_int32    ditherflag)
{
l_uint8   *bufu8r, *bufu8g, *bufu8b;
l_int32    rval, gval, bval;
l_int32    octindex, index;
l_int32    val1, val2, val3, dif;
l_int32    w, h, wpls, wpld, i, j, success;
l_int32    rc, gc, bc;
l_int32   *buf1r, *buf1g, *buf1b, *buf2r, *buf2g, *buf2b;
l_uint32  *rtab, *gtab, *btab;
l_uint32  *datas, *datad, *lines, *lined;
PIX       *pixd;

    PROCNAME("pixOctreeQuantizePixels");

    if (!pixs)
        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
    if (pixGetDepth(pixs) != 32)
        return (PIX *)ERROR_PTR("pixs must be 32 bpp", procName, NULL);
    if (!cqcaa)
        return (PIX *)ERROR_PTR("cqcaa not defined", procName, NULL);

        /* Make output 8 bpp palette image */
    pixGetDimensions(pixs, &w, &h, NULL);
    datas = pixGetData(pixs);
    wpls = pixGetWpl(pixs);
    if ((pixd = pixCreate(w, h, 8)) == NULL)
        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
    pixCopyResolution(pixd, pixs);
    pixCopyInputFormat(pixd, pixs);
    datad = pixGetData(pixd);
    wpld = pixGetWpl(pixd);

        /* Make the canonical index tables */
    rtab = gtab = btab = NULL;
    makeRGBToIndexTables(CqNLevels, &rtab, &gtab, &btab);

        /* Traverse tree from root, looking for lowest cube
         * that is a leaf, and set dest pix to its
         * colortable index value.  The results are far
         * better when dithering to get a more accurate
         * average color.  */
    if (ditherflag == 0) {    /* no dithering */
        for (i = 0; i < h; i++) {
            lines = datas + i * wpls;
            lined = datad + i * wpld;
            for (j = 0; j < w; j++) {
                extractRGBValues(lines[j], &rval, &gval, &bval);
                octindex = rtab[rval] | gtab[gval] | btab[bval];
                octreeFindColorCell(octindex, cqcaa, &index, &rc, &gc, &bc);
                SET_DATA_BYTE(lined, j, index);
            }
        }
    } else {  /* Dither */
        success = TRUE;
        bufu8r = bufu8g = bufu8b = NULL;
        buf1r = buf1g = buf1b = buf2r = buf2g = buf2b = NULL;
        bufu8r = (l_uint8 *)LEPT_CALLOC(w, sizeof(l_uint8));
        bufu8g = (l_uint8 *)LEPT_CALLOC(w, sizeof(l_uint8));
        bufu8b = (l_uint8 *)LEPT_CALLOC(w, sizeof(l_uint8));
        buf1r = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
        buf1g = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
        buf1b = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
        buf2r = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
        buf2g = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
        buf2b = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
        if (!bufu8r || !bufu8g || !bufu8b || !buf1r || !buf1g ||
            !buf1b || !buf2r || !buf2g || !buf2b) {
            L_ERROR("buffer not made\n", procName);
            success = FALSE;
            goto buffer_cleanup;
        }

            /* Start by priming buf2; line 1 is above line 2 */
        pixGetRGBLine(pixs, 0, bufu8r, bufu8g, bufu8b);
        for (j = 0; j < w; j++) {
            buf2r[j] = 64 * bufu8r[j];
            buf2g[j] = 64 * bufu8g[j];
            buf2b[j] = 64 * bufu8b[j];
        }

        for (i = 0; i < h - 1; i++) {
                /* Swap data 2 --> 1, and read in new line 2 */
            memcpy(buf1r, buf2r, 4 * w);
            memcpy(buf1g, buf2g, 4 * w);
            memcpy(buf1b, buf2b, 4 * w);
            pixGetRGBLine(pixs, i + 1, bufu8r, bufu8g, bufu8b);
            for (j = 0; j < w; j++) {
                buf2r[j] = 64 * bufu8r[j];
                buf2g[j] = 64 * bufu8g[j];
                buf2b[j] = 64 * bufu8b[j];
            }

                /* Dither */
            lined = datad + i * wpld;
            for (j = 0; j < w - 1; j++) {
                rval = buf1r[j] / 64;
                gval = buf1g[j] / 64;
                bval = buf1b[j] / 64;
                octindex = rtab[rval] | gtab[gval] | btab[bval];
                octreeFindColorCell(octindex, cqcaa, &index, &rc, &gc, &bc);
                SET_DATA_BYTE(lined, j, index);

                dif = buf1r[j] / 8 - 8 * rc;
                if (dif != 0) {
                    val1 = buf1r[j + 1] + 3 * dif;
                    val2 = buf2r[j] + 3 * dif;
                    val3 = buf2r[j + 1] + 2 * dif;
                    if (dif > 0) {
                        buf1r[j + 1] = L_MIN(16383, val1);
                        buf2r[j] = L_MIN(16383, val2);
                        buf2r[j + 1] = L_MIN(16383, val3);
                    } else {
                        buf1r[j + 1] = L_MAX(0, val1);
                        buf2r[j] = L_MAX(0, val2);
                        buf2r[j + 1] = L_MAX(0, val3);
                    }
                }

                dif = buf1g[j] / 8 - 8 * gc;
                if (dif != 0) {
                    val1 = buf1g[j + 1] + 3 * dif;
                    val2 = buf2g[j] + 3 * dif;
                    val3 = buf2g[j + 1] + 2 * dif;
                    if (dif > 0) {
                        buf1g[j + 1] = L_MIN(16383, val1);
                        buf2g[j] = L_MIN(16383, val2);
                        buf2g[j + 1] = L_MIN(16383, val3);
                    } else {
                        buf1g[j + 1] = L_MAX(0, val1);
                        buf2g[j] = L_MAX(0, val2);
                        buf2g[j + 1] = L_MAX(0, val3);
                    }
                }

                dif = buf1b[j] / 8 - 8 * bc;
                if (dif != 0) {
                    val1 = buf1b[j + 1] + 3 * dif;
                    val2 = buf2b[j] + 3 * dif;
                    val3 = buf2b[j + 1] + 2 * dif;
                    if (dif > 0) {
                        buf1b[j + 1] = L_MIN(16383, val1);
                        buf2b[j] = L_MIN(16383, val2);
                        buf2b[j + 1] = L_MIN(16383, val3);
                    } else {
                        buf1b[j + 1] = L_MAX(0, val1);
                        buf2b[j] = L_MAX(0, val2);
                        buf2b[j + 1] = L_MAX(0, val3);
                    }
                }
            }

                /* Get last pixel in row; no downward propagation */
            rval = buf1r[w - 1] / 64;
            gval = buf1g[w - 1] / 64;
            bval = buf1b[w - 1] / 64;
            octindex = rtab[rval] | gtab[gval] | btab[bval];
            octreeFindColorCell(octindex, cqcaa, &index, &rc, &gc, &bc);
            SET_DATA_BYTE(lined, w - 1, index);
        }

            /* Get last row of pixels; no leftward propagation */
        lined = datad + (h - 1) * wpld;
        for (j = 0; j < w; j++) {
            rval = buf2r[j] / 64;
            gval = buf2g[j] / 64;
            bval = buf2b[j] / 64;
            octindex = rtab[rval] | gtab[gval] | btab[bval];
            octreeFindColorCell(octindex, cqcaa, &index, &rc, &gc, &bc);
            SET_DATA_BYTE(lined, j, index);
        }

buffer_cleanup:
        LEPT_FREE(bufu8r);
        LEPT_FREE(bufu8g);
        LEPT_FREE(bufu8b);
        LEPT_FREE(buf1r);
        LEPT_FREE(buf1g);
        LEPT_FREE(buf1b);
        LEPT_FREE(buf2r);
        LEPT_FREE(buf2g);
        LEPT_FREE(buf2b);
        if (!success) pixDestroy(&pixd);
    }

    LEPT_FREE(rtab);
    LEPT_FREE(gtab);
    LEPT_FREE(btab);
    return pixd;
}


Fixed issue 22140 in oss-fuzz: Heap-buffer-overflow
* color quantized pix must be 8 bpp before extra colors are added.

pixDitherOctindexWithCmap(PIX       *pixs,
                          PIX       *pixd,
                          l_uint32  *rtab,
                          l_uint32  *gtab,
                          l_uint32  *btab,
                          l_int32   *indexmap,
                          l_int32    difcap)
{
l_uint8   *bufu8r, *bufu8g, *bufu8b;
l_int32    i, j, w, h, wpld, octindex, cmapindex, success;
l_int32    rval, gval, bval, rc, gc, bc;
l_int32    dif, val1, val2, val3;
l_int32   *buf1r, *buf1g, *buf1b, *buf2r, *buf2g, *buf2b;
l_uint32  *datad, *lined;
PIXCMAP   *cmap;

    PROCNAME("pixDitherOctindexWithCmap");

    if (!pixs || pixGetDepth(pixs) != 32)
        return ERROR_INT("pixs undefined or not 32 bpp", procName, 1);
    if (!pixd || pixGetDepth(pixd) != 8)
        return ERROR_INT("pixd undefined or not 8 bpp", procName, 1);
    if ((cmap = pixGetColormap(pixd)) == NULL)
        return ERROR_INT("pixd not cmapped", procName, 1);
    if (!rtab || !gtab || !btab || !indexmap)
        return ERROR_INT("not all 4 tables defined", procName, 1);
    pixGetDimensions(pixs, &w, &h, NULL);
    if (pixGetWidth(pixd) != w || pixGetHeight(pixd) != h)
        return ERROR_INT("pixs and pixd not same size", procName, 1);

    success = TRUE;
    bufu8r = bufu8g = bufu8b = NULL;
    buf1r = buf1g = buf1b = buf2r = buf2g = buf2b = NULL;
    bufu8r = (l_uint8 *)LEPT_CALLOC(w, sizeof(l_uint8));
    bufu8g = (l_uint8 *)LEPT_CALLOC(w, sizeof(l_uint8));
    bufu8b = (l_uint8 *)LEPT_CALLOC(w, sizeof(l_uint8));
    buf1r = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
    buf1g = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
    buf1b = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
    buf2r = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
    buf2g = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
    buf2b = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
    if (!bufu8r || !bufu8g || !bufu8b || !buf1r || !buf1g ||
        !buf1b || !buf2r || !buf2g || !buf2b) {
        L_ERROR("buffer not made\n", procName);
        success = FALSE;
        goto buffer_cleanup;
    }

        /* Start by priming buf2; line 1 is above line 2 */
    pixGetRGBLine(pixs, 0, bufu8r, bufu8g, bufu8b);
    for (j = 0; j < w; j++) {
        buf2r[j] = 64 * bufu8r[j];
        buf2g[j] = 64 * bufu8g[j];
        buf2b[j] = 64 * bufu8b[j];
    }

    datad = pixGetData(pixd);
    wpld = pixGetWpl(pixd);
    for (i = 0; i < h - 1; i++) {
            /* Swap data 2 --> 1, and read in new line 2 */
        memcpy(buf1r, buf2r, 4 * w);
        memcpy(buf1g, buf2g, 4 * w);
        memcpy(buf1b, buf2b, 4 * w);
        pixGetRGBLine(pixs, i + 1, bufu8r, bufu8g, bufu8b);
        for (j = 0; j < w; j++) {
            buf2r[j] = 64 * bufu8r[j];
            buf2g[j] = 64 * bufu8g[j];
            buf2b[j] = 64 * bufu8b[j];
        }

            /* Dither */
        lined = datad + i * wpld;
        for (j = 0; j < w - 1; j++) {
            rval = buf1r[j] / 64;
            gval = buf1g[j] / 64;
            bval = buf1b[j] / 64;
            octindex = rtab[rval] | gtab[gval] | btab[bval];
            cmapindex = indexmap[octindex] - 1;
            SET_DATA_BYTE(lined, j, cmapindex);
            pixcmapGetColor(cmap, cmapindex, &rc, &gc, &bc);

            dif = buf1r[j] / 8 - 8 * rc;
            if (difcap > 0) {
                if (dif > difcap) dif = difcap;
                if (dif < -difcap) dif = -difcap;
            }
            if (dif != 0) {
                val1 = buf1r[j + 1] + 3 * dif;
                val2 = buf2r[j] + 3 * dif;
                val3 = buf2r[j + 1] + 2 * dif;
                if (dif > 0) {
                    buf1r[j + 1] = L_MIN(16383, val1);
                    buf2r[j] = L_MIN(16383, val2);
                    buf2r[j + 1] = L_MIN(16383, val3);
                } else {
                    buf1r[j + 1] = L_MAX(0, val1);
                    buf2r[j] = L_MAX(0, val2);
                    buf2r[j + 1] = L_MAX(0, val3);
                }
            }

            dif = buf1g[j] / 8 - 8 * gc;
            if (difcap > 0) {
                if (dif > difcap) dif = difcap;
                if (dif < -difcap) dif = -difcap;
            }
            if (dif != 0) {
                val1 = buf1g[j + 1] + 3 * dif;
                val2 = buf2g[j] + 3 * dif;
                val3 = buf2g[j + 1] + 2 * dif;
                if (dif > 0) {
                    buf1g[j + 1] = L_MIN(16383, val1);
                    buf2g[j] = L_MIN(16383, val2);
                    buf2g[j + 1] = L_MIN(16383, val3);
                } else {
                    buf1g[j + 1] = L_MAX(0, val1);
                    buf2g[j] = L_MAX(0, val2);
                    buf2g[j + 1] = L_MAX(0, val3);
                }
            }

            dif = buf1b[j] / 8 - 8 * bc;
            if (difcap > 0) {
                if (dif > difcap) dif = difcap;
                if (dif < -difcap) dif = -difcap;
            }
            if (dif != 0) {
                val1 = buf1b[j + 1] + 3 * dif;
                val2 = buf2b[j] + 3 * dif;
                val3 = buf2b[j + 1] + 2 * dif;
                if (dif > 0) {
                    buf1b[j + 1] = L_MIN(16383, val1);
                    buf2b[j] = L_MIN(16383, val2);
                    buf2b[j + 1] = L_MIN(16383, val3);
                } else {
                    buf1b[j + 1] = L_MAX(0, val1);
                    buf2b[j] = L_MAX(0, val2);
                    buf2b[j + 1] = L_MAX(0, val3);
                }
            }
        }

            /* Get last pixel in row; no downward propagation */
        rval = buf1r[w - 1] / 64;
        gval = buf1g[w - 1] / 64;
        bval = buf1b[w - 1] / 64;
        octindex = rtab[rval] | gtab[gval] | btab[bval];
        cmapindex = indexmap[octindex] - 1;
        SET_DATA_BYTE(lined, w - 1, cmapindex);
    }

        /* Get last row of pixels; no leftward propagation */
    lined = datad + (h - 1) * wpld;
    for (j = 0; j < w; j++) {
        rval = buf2r[j] / 64;
        gval = buf2g[j] / 64;
        bval = buf2b[j] / 64;
        octindex = rtab[rval] | gtab[gval] | btab[bval];
        cmapindex = indexmap[octindex] - 1;
        SET_DATA_BYTE(lined, j, cmapindex);
    }

buffer_cleanup:
    LEPT_FREE(bufu8r);
    LEPT_FREE(bufu8g);
    LEPT_FREE(bufu8b);
    LEPT_FREE(buf1r);
    LEPT_FREE(buf1g);
    LEPT_FREE(buf1b);
    LEPT_FREE(buf2r);
    LEPT_FREE(buf2g);
    LEPT_FREE(buf2b);

    return (success) ? 0 : 1;
}

Leptonica before 1.80.0 allows a heap-based buffer over-read in pixFewColorsOctcubeQuantMixed in colorquant1.c.


pixOctcubeHistogram(PIX      *pixs,
                    l_int32   level,
                    l_int32  *pncolors)
{
l_int32     size, i, j, w, h, wpl, ncolors, val;
l_int32     rval, gval, bval;
l_uint32    octindex;
l_uint32   *rtab, *gtab, *btab;
l_uint32   *data, *line;
l_float32  *array;
NUMA       *na;

    PROCNAME("pixOctcubeHistogram");

    if (pncolors) *pncolors = 0;
    if (!pixs)
        return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL);
    if (pixGetDepth(pixs) != 32)
        return (NUMA *)ERROR_PTR("pixs not 32 bpp", procName, NULL);

    pixGetDimensions(pixs, &w, &h, NULL);
    wpl = pixGetWpl(pixs);
    data = pixGetData(pixs);

    if (octcubeGetCount(level, &size))  /* array size = 2 ** (3 * level) */
        return (NUMA *)ERROR_PTR("size not returned", procName, NULL);
    rtab = gtab = btab = NULL;
    makeRGBToIndexTables(level, &rtab, &gtab, &btab);

    if ((na = numaCreate(size)) == NULL) {
        L_ERROR("na not made\n", procName);
        goto cleanup_arrays;
    }
    numaSetCount(na, size);
    array = numaGetFArray(na, L_NOCOPY);

    for (i = 0; i < h; i++) {
        line = data + i * wpl;
        for (j = 0; j < w; j++) {
            extractRGBValues(line[j], &rval, &gval, &bval);
            octindex = rtab[rval] | gtab[gval] | btab[bval];
#if DEBUG_OCTINDEX
            if ((level == 1 && octindex > 7) ||
                (level == 2 && octindex > 63) ||
                (level == 3 && octindex > 511) ||
                (level == 4 && octindex > 4097) ||
                (level == 5 && octindex > 32783) ||
                (level == 6 && octindex > 262271)) {
                lept_stderr("level = %d, octindex = %d, index error!\n",
                            level, octindex);
                continue;
            }
#endif  /* DEBUG_OCTINDEX */
              array[octindex] += 1.0;
        }
    }

    if (pncolors) {
        for (i = 0, ncolors = 0; i < size; i++) {
            numaGetIValue(na, i, &val);
            if (val > 0)
                ncolors++;
        }
        *pncolors = ncolors;
    }

cleanup_arrays:
    LEPT_FREE(rtab);
    LEPT_FREE(gtab);
    LEPT_FREE(btab);
    return na;
}


getOctcubeIndexFromRGB(l_int32    rval,
                       l_int32    gval,
                       l_int32    bval,
                       l_uint32  *rtab,
                       l_uint32  *gtab,
                       l_uint32  *btab,
                       l_uint32  *pindex)
{
    *pindex = rtab[rval] | gtab[gval] | btab[bval];
    return;
}


libvncserver: scale: cast to 64 bit before shifting

Since pixel_value is 64 bit the data type of the shift operand should
be 64 bit too to prevent integer overflows.

rfbScreenInfoPtr rfbScalingFind(rfbClientPtr cl, int width, int height)
{
    rfbScreenInfoPtr ptr;
    /* include the original in the search (ie: fine 1:1 scaled version of the frameBuffer) */
    for (ptr=cl->screen; ptr!=NULL; ptr=ptr->scaledScreenNext)
    {
        if ((ptr->width==width) && (ptr->height==height))
            return ptr;
    }
    return NULL;
}

An issue was discovered in LibVNCServer before 0.9.13. libvncserver/scale.c has a pixel_value integer overflow.


void rfbScaledScreenUpdate(rfbScreenInfoPtr screen, int x1, int y1, int x2, int y2)
{
    /* ok, now the task is to update each and every scaled version of the framebuffer
     * and we only have to do this for this specific changed rectangle!
     */
    rfbScreenInfoPtr ptr;
    int count=0;

    /* We don't point to cl->screen as it is the original */
    for (ptr=screen->scaledScreenNext;ptr!=NULL;ptr=ptr->scaledScreenNext)
    {
        /* Only update if it has active clients... */
        if (ptr->scaledScreenRefCount>0)
        {
          rfbScaledScreenUpdateRect(screen, ptr, x1, y1, x2-x1, y2-y1);
          count++;
        }
    }
}


int ScaleY(rfbScreenInfoPtr from, rfbScreenInfoPtr to, int y)
{
    if ((from==to) || (from==NULL) || (to==NULL)) return y;
    return ((int)(((double) y / (double)from->height) * (double)to->height ));
}


rfbScreenInfoPtr rfbScaledScreenAllocate(rfbClientPtr cl, int width, int height)
{
    rfbScreenInfoPtr ptr;
    ptr = malloc(sizeof(rfbScreenInfo));
    if (ptr!=NULL)
    {
        int allocSize;

        /* copy *everything* (we don't use most of it, but just in case) */
        memcpy(ptr, cl->screen, sizeof(rfbScreenInfo));

        /* SECURITY: make sure that no integer overflow will occur afterwards.
         * Note: this is defensive coding, as the check should have already been
         * performed during initial, non-scaled screen setup.
         */
        allocSize = pad4(width * (ptr->bitsPerPixel/8)); /* per protocol, width<2**16 and bpp<256 */
        if (height == 0 || allocSize >= SIZE_MAX / height)
        {
          free(ptr);
          return NULL; /* malloc() will allocate an incorrect buffer size - early abort */
        }

        /* Resume copy everything */
        ptr->width = width;
        ptr->height = height;
        ptr->paddedWidthInBytes = (ptr->bitsPerPixel/8)*ptr->width;

        /* Need to by multiples of 4 for Sparc systems */
        ptr->paddedWidthInBytes = pad4(ptr->paddedWidthInBytes);

        /* Reset the reference count to 0! */
        ptr->scaledScreenRefCount = 0;

        ptr->sizeInBytes = ptr->paddedWidthInBytes * ptr->height;
        ptr->serverFormat = cl->screen->serverFormat;

        ptr->frameBuffer = malloc(ptr->sizeInBytes);
        if (ptr->frameBuffer!=NULL)
        {
            /* Reset to a known condition: scale the entire framebuffer */
            rfbScaledScreenUpdateRect(cl->screen, ptr, 0, 0, cl->screen->width, cl->screen->height);
            /* Now, insert into the chain */
            LOCK(cl->updateMutex);
            ptr->scaledScreenNext = cl->screen->scaledScreenNext;
            cl->screen->scaledScreenNext = ptr;
            UNLOCK(cl->updateMutex);
        }
        else
        {
            /* Failed to malloc the new frameBuffer, cleanup */
            free(ptr);
            ptr=NULL;
        }
    }
    return ptr;
}


static InlineX int pad4(int value)
{
    int remainder = value & 3;
    if (!remainder) return value;
    return value + 4 - remainder;
}


void rfbScalingSetup(rfbClientPtr cl, int width, int height)
{
    rfbScreenInfoPtr ptr;

    ptr = rfbScalingFind(cl,width,height);
    if (ptr==NULL)
        ptr = rfbScaledScreenAllocate(cl,width,height);
    /* Now, there is a new screen available (if ptr is not NULL) */
    if (ptr!=NULL)
    {
        /* Update it! */
        if (ptr->scaledScreenRefCount<1)
            rfbScaledScreenUpdateRect(cl->screen, ptr, 0, 0, cl->screen->width, cl->screen->height);
        /*
         * rfbLog("Taking one from %dx%d-%d and adding it to %dx%d-%d\n",
         *    cl->scaledScreen->width, cl->scaledScreen->height,
         *    cl->scaledScreen->scaledScreenRefCount,
         *    ptr->width, ptr->height, ptr->scaledScreenRefCount);
         */

        LOCK(cl->updateMutex);
        cl->scaledScreen->scaledScreenRefCount--;
        ptr->scaledScreenRefCount++;
        cl->scaledScreen=ptr;
        cl->newFBSizePending = TRUE;
        UNLOCK(cl->updateMutex);

        rfbLog("Scaling to %dx%d (refcount=%d)\n",width,height,ptr->scaledScreenRefCount);
    }
    else
        rfbLog("Scaling to %dx%d failed, leaving things alone\n",width,height);
}


void rfbScaledScreenUpdateRect(rfbScreenInfoPtr screen, rfbScreenInfoPtr ptr, int x0, int y0, int w0, int h0)
{
    int x,y,w,v,z;
    int x1, y1, w1, h1;
    int bitsPerPixel, bytesPerPixel, bytesPerLine, areaX, areaY, area2;
    unsigned char *srcptr, *dstptr;

    /* Nothing to do!!! */
    if (screen==ptr) return;

    x1 = x0;
    y1 = y0;
    w1 = w0;
    h1 = h0;

    rfbScaledCorrection(screen, ptr, &x1, &y1, &w1, &h1, "rfbScaledScreenUpdateRect");
    x0 = ScaleX(ptr, screen, x1);
    y0 = ScaleY(ptr, screen, y1);
    w0 = ScaleX(ptr, screen, w1);
    h0 = ScaleY(ptr, screen, h1);

    bitsPerPixel = screen->bitsPerPixel;
    bytesPerPixel = bitsPerPixel / 8;
    bytesPerLine = w1 * bytesPerPixel;
    srcptr = (unsigned char *)(screen->frameBuffer +
     (y0 * screen->paddedWidthInBytes + x0 * bytesPerPixel));
    dstptr = (unsigned char *)(ptr->frameBuffer +
     ( y1 * ptr->paddedWidthInBytes + x1 * bytesPerPixel));
    /* The area of the source framebuffer for each destination pixel */
    areaX = ScaleX(ptr,screen,1);
    areaY = ScaleY(ptr,screen,1);
    area2 = areaX*areaY;

    /* Ensure that we do not go out of bounds */
    if ((x1+w1) > (ptr->width))
    {
      if (x1==0) w1=ptr->width; else x1 = ptr->width - w1;
    }
    if ((y1+h1) > (ptr->height))
    {
      if (y1==0) h1=ptr->height; else y1 = ptr->height - h1;
    }
    /*
     * rfbLog("rfbScaledScreenUpdateRect(%dXx%dY-%dWx%dH  ->  %dXx%dY-%dWx%dH <%dx%d>) {%dWx%dH -> %dWx%dH} 0x%p\n",
     *    x0, y0, w0, h0, x1, y1, w1, h1, areaX, areaY,
     *    screen->width, screen->height, ptr->width, ptr->height, ptr->frameBuffer);
     */

    if (screen->serverFormat.trueColour) { /* Blend neighbouring pixels together */
      unsigned char *srcptr2;
      unsigned long pixel_value, red, green, blue;
      unsigned int redShift = screen->serverFormat.redShift;
      unsigned int greenShift = screen->serverFormat.greenShift;
      unsigned int blueShift = screen->serverFormat.blueShift;
      unsigned long redMax = screen->serverFormat.redMax;
      unsigned long greenMax = screen->serverFormat.greenMax;
      unsigned long blueMax = screen->serverFormat.blueMax;

     /* for each *destination* pixel... */
     for (y = 0; y < h1; y++) {
       for (x = 0; x < w1; x++) {
         red = green = blue = 0;
         /* Get the totals for rgb from the source grid... */
         for (w = 0; w < areaX; w++) {
           for (v = 0; v < areaY; v++) {
             srcptr2 = &srcptr[(((x * areaX) + w) * bytesPerPixel) +
                               (v * screen->paddedWidthInBytes)];
             pixel_value = 0;

             switch (bytesPerPixel) {
             case 4: pixel_value = *((unsigned int *)srcptr2);   break;
             case 2: pixel_value = *((unsigned short *)srcptr2); break;
             case 1: pixel_value = *((unsigned char *)srcptr2);  break;
             default:
               /* fixme: endianness problem? */
               for (z = 0; z < bytesPerPixel; z++)
                 pixel_value += ((unsigned long)srcptr2[z] << (8 * z));
                break;
              }
              /*
              srcptr2 += bytesPerPixel;
              */

            red += ((pixel_value >> redShift) & redMax);
            green += ((pixel_value >> greenShift) & greenMax);
            blue += ((pixel_value >> blueShift) & blueMax);

           }
         }
         /* We now have a total for all of the colors, find the average! */
         red /= area2;
         green /= area2;
         blue /= area2;
          /* Stuff the new value back into memory */
         pixel_value = ((red & redMax) << redShift) | ((green & greenMax) << greenShift) | ((blue & blueMax) << blueShift);

         switch (bytesPerPixel) {
         case 4: *((unsigned int *)dstptr)   = (unsigned int)   pixel_value; break;
         case 2: *((unsigned short *)dstptr) = (unsigned short) pixel_value; break;
         case 1: *((unsigned char *)dstptr)  = (unsigned char)  pixel_value; break;
         default:
           /* fixme: endianness problem? */
           for (z = 0; z < bytesPerPixel; z++)
             dstptr[z]=(pixel_value >> (8 * z)) & 0xff;
            break;
          }
          dstptr += bytesPerPixel;
       }
       srcptr += (screen->paddedWidthInBytes * areaY);
       dstptr += (ptr->paddedWidthInBytes - bytesPerLine);
     }
   } else
   { /* Not truecolour, so we can't blend. Just use the top-left pixel instead */
     for (y = y1; y < (y1+h1); y++) {
       for (x = x1; x < (x1+w1); x++)
         memcpy (&ptr->frameBuffer[(y *ptr->paddedWidthInBytes) + (x * bytesPerPixel)],
                 &screen->frameBuffer[(y * areaY * screen->paddedWidthInBytes) + (x *areaX * bytesPerPixel)], bytesPerPixel);
     }
  }
}


int ScaleX(rfbScreenInfoPtr from, rfbScreenInfoPtr to, int x)
{
    if ((from==to) || (from==NULL) || (to==NULL)) return x;
    return ((int)(((double) x / (double)from->width) * (double)to->width ));
}


int rfbSendNewScaleSize(rfbClientPtr cl)
{
    /* if the client supports newFBsize Encoding, use it */
    if (cl->useNewFBSize && cl->newFBSizePending)
	return FALSE;

    LOCK(cl->updateMutex);
    cl->newFBSizePending = FALSE;
    UNLOCK(cl->updateMutex);

    if (cl->PalmVNC==TRUE)
    {
        rfbPalmVNCReSizeFrameBufferMsg pmsg;
        pmsg.type = rfbPalmVNCReSizeFrameBuffer;
        pmsg.pad1 = 0;
        pmsg.desktop_w = Swap16IfLE(cl->screen->width);
        pmsg.desktop_h = Swap16IfLE(cl->screen->height);
        pmsg.buffer_w  = Swap16IfLE(cl->scaledScreen->width);
        pmsg.buffer_h  = Swap16IfLE(cl->scaledScreen->height);
        pmsg.pad2 = 0;

        rfbLog("Sending a response to a PalmVNC style frameuffer resize event (%dx%d)\n", cl->scaledScreen->width, cl->scaledScreen->height);
        if (rfbWriteExact(cl, (char *)&pmsg, sz_rfbPalmVNCReSizeFrameBufferMsg) < 0) {
            rfbLogPerror("rfbNewClient: write");
            rfbCloseClient(cl);
            return FALSE;
        }
    }
    else
    {
        rfbResizeFrameBufferMsg        rmsg;
        rmsg.type = rfbResizeFrameBuffer;
        rmsg.pad1=0;
        rmsg.framebufferWidth  = Swap16IfLE(cl->scaledScreen->width);
        rmsg.framebufferHeigth = Swap16IfLE(cl->scaledScreen->height);
        rfbLog("Sending a response to a UltraVNC style frameuffer resize event (%dx%d)\n", cl->scaledScreen->width, cl->scaledScreen->height);
        if (rfbWriteExact(cl, (char *)&rmsg, sz_rfbResizeFrameBufferMsg) < 0) {
            rfbLogPerror("rfbNewClient: write");
            rfbCloseClient(cl);
            return FALSE;
        }
    }
    return TRUE;
}


void rfbScaledCorrection(rfbScreenInfoPtr from, rfbScreenInfoPtr to, int *x, int *y, int *w, int *h, const char *function)
{
    double x1,y1,w1,h1, x2, y2, w2, h2;
    double scaleW = ((double) to->width) / ((double) from->width);
    double scaleH = ((double) to->height) / ((double) from->height);

    /*
     * rfbLog("rfbScaledCorrection(%p -> %p, %dx%d->%dx%d (%dXx%dY-%dWx%dH)\n",
     * from, to, from->width, from->height, to->width, to->height, *x, *y, *w, *h);
     */

    /* If it's the original framebuffer... */
    if (from==to) return;

    x1 = ((double) *x) * scaleW;
    y1 = ((double) *y) * scaleH;
    w1 = ((double) *w) * scaleW;
    h1 = ((double) *h) * scaleH;

    /*cast from double to int is same as "*x = floor(x1);" */
    x2 = FLOOR(x1);
    y2 = FLOOR(y1);

    /* include into W and H the jitter of scaling X and Y */
    w2 = CEIL(w1 + ( x1 - x2 ));
    h2 = CEIL(h1 + ( y1 - y2 ));

    /*
     * rfbLog("%s (%dXx%dY-%dWx%dH  ->  %fXx%fY-%fWx%fH) {%dWx%dH -> %dWx%dH}\n",
     *    function, *x, *y, *w, *h, x2, y2, w2, h2,
     *    from->width, from->height, to->width, to->height);
     */

    /* simulate ceil() without math library */
    *x = (int)x2;
    *y = (int)y2;
    *w = (int)w2;
    *h = (int)h2;

    /* Small changes for a thumbnail may be scaled to zero */
    if (*w==0) (*w)++;
    if (*h==0) (*h)++;
    /* scaling from small to big may overstep the size a bit */
    if (*x+*w > to->width)  *w=to->width - *x;
    if (*y+*h > to->height) *h=to->height - *y;
}


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.

    yaffs2_open(TSK_IMG_INFO * img_info, TSK_OFF_T offset,
    TSK_FS_TYPE_ENUM ftype, uint8_t test)
{
    YAFFSFS_INFO *yaffsfs = NULL;
    TSK_FS_INFO *fs = NULL;
    const unsigned int psize = img_info->page_size;
    const unsigned int ssize = img_info->spare_size;
    YaffsHeader * first_header = NULL;
    TSK_FS_DIR *test_dir;
    std::map<std::string, std::string> configParams;
    YAFFS_CONFIG_STATUS config_file_status;

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

    if (TSK_FS_TYPE_ISYAFFS2(ftype) == 0) {
        tsk_error_reset();
        tsk_error_set_errno(TSK_ERR_FS_ARG);
        tsk_error_set_errstr("Invalid FS Type in yaffsfs_open");
        return NULL;
    }

    if (img_info->sector_size == 0) {
        tsk_error_reset();
        tsk_error_set_errno(TSK_ERR_FS_ARG);
        tsk_error_set_errstr("yaffs2_open: sector size is 0");
        return NULL;
    }

    if ((yaffsfs = (YAFFSFS_INFO *) tsk_fs_malloc(sizeof(YAFFSFS_INFO))) == NULL)
        return NULL;
    yaffsfs->cache_objects = NULL;
    yaffsfs->chunkMap = NULL;

    fs = &(yaffsfs->fs_info);

    fs->tag = TSK_FS_INFO_TAG;
    fs->ftype = ftype;
    fs->flags = (TSK_FS_INFO_FLAG_ENUM)0;
    fs->img_info = img_info;
    fs->offset = offset;
    fs->endian = TSK_LIT_ENDIAN;

    // Read config file (if it exists)
    config_file_status = yaffs_load_config_file(img_info, configParams);
    // BL-6929(JTS): When using external readers, this call will fail.
    // Not having a config should not be a fatal error.
  /*if(config_file_status == YAFFS_CONFIG_ERROR){
        // tsk_error was set by yaffs_load_config
        goto on_error;
    }
    else*/ if(config_file_status == YAFFS_CONFIG_OK){
        // Validate the input
        // If it fails validation, return (tsk_error will be set up already)
        if(1 == yaffs_validate_config_file(configParams)){
            goto on_error;
        }
    }

    // If we read these fields from the config file, use those values. Otherwise use the defaults
    if(configParams.find(YAFFS_CONFIG_PAGE_SIZE_STR) != configParams.end()){
        yaffsfs->page_size = atoi(configParams[YAFFS_CONFIG_PAGE_SIZE_STR].c_str());
    }
    else{
        yaffsfs->page_size = psize == 0 ? YAFFS_DEFAULT_PAGE_SIZE : psize;
    }

    if(configParams.find(YAFFS_CONFIG_SPARE_SIZE_STR) != configParams.end()){
        yaffsfs->spare_size = atoi(configParams[YAFFS_CONFIG_SPARE_SIZE_STR].c_str());
    }
    else{
        yaffsfs->spare_size = ssize == 0 ? YAFFS_DEFAULT_SPARE_SIZE : ssize;
    }

    if(configParams.find(YAFFS_CONFIG_CHUNKS_PER_BLOCK_STR) != configParams.end()){
        yaffsfs->chunks_per_block = atoi(configParams[YAFFS_CONFIG_CHUNKS_PER_BLOCK_STR].c_str());
    }
    else{
        yaffsfs->chunks_per_block = 64;
    }

    // TODO: Why are 2 different memory allocation methods used in the same code?
    // This makes things unnecessary complex.
    yaffsfs->max_obj_id = 1;
    yaffsfs->max_version = 0;

    // Keep track of whether we're doing auto-detection of the file system
    if(test){
        yaffsfs->autoDetect = 1;
    }
    else{
        yaffsfs->autoDetect = 0;
    }

    // Determine the layout of the spare area
    // If it was specified in the config file, use those values. Otherwise do the auto-detection
    if(configParams.find(YAFFS_CONFIG_SEQ_NUM_STR) != configParams.end()){
        // In the validation step, we ensured that if one of the offsets was set, we have all of them
        yaffsfs->spare_seq_offset = atoi(configParams[YAFFS_CONFIG_SEQ_NUM_STR].c_str());
        yaffsfs->spare_obj_id_offset = atoi(configParams[YAFFS_CONFIG_OBJ_ID_STR].c_str());
        yaffsfs->spare_chunk_id_offset = atoi(configParams[YAFFS_CONFIG_CHUNK_ID_STR].c_str());

        // Check that the offsets are valid for the given spare area size (fields are 4 bytes long)
        if((yaffsfs->spare_seq_offset + 4 > yaffsfs->spare_size) ||
            (yaffsfs->spare_obj_id_offset + 4 > yaffsfs->spare_size) ||
            (yaffsfs->spare_chunk_id_offset + 4 > yaffsfs->spare_size)){
            tsk_error_reset();
            tsk_error_set_errno(TSK_ERR_FS);
            tsk_error_set_errstr("yaffs2_open: Offset(s) in config file too large for spare area (size %d). %s", yaffsfs->spare_size, YAFFS_HELP_MESSAGE);
            goto on_error;
        }

        // nBytes isn't currently used, so just set to zero
        yaffsfs->spare_nbytes_offset = 0;
    }
    else{
        // Decide how many blocks to test. If we're not doing auto-detection, set to zero (no limit)
        unsigned int maxBlocksToTest;
        if(yaffsfs->autoDetect){
            maxBlocksToTest = YAFFS_DEFAULT_MAX_TEST_BLOCKS;
        }
        else{
            maxBlocksToTest = 0;
        }

        if(yaffs_initialize_spare_format(yaffsfs, maxBlocksToTest) != TSK_OK){
            tsk_error_reset();
            tsk_error_set_errno(TSK_ERR_FS_MAGIC);
            tsk_error_set_errstr("not a YAFFS file system (bad spare format). %s", YAFFS_HELP_MESSAGE);
            if (tsk_verbose)
                fprintf(stderr, "yaffsfs_open: could not find valid spare area format\n%s\n", YAFFS_HELP_MESSAGE);
            goto on_error;
        }
    }

    /*
    * Read the first record, make sure it's a valid header...
    *
    * Used for verification and autodetection of
    * the FS type.
    */
    if (yaffsfs_read_header(yaffsfs, &first_header, 0)) {
        tsk_error_reset();
        tsk_error_set_errno(TSK_ERR_FS_MAGIC);
        tsk_error_set_errstr("not a YAFFS file system (first record). %s", YAFFS_HELP_MESSAGE);
        if (tsk_verbose)
            fprintf(stderr, "yaffsfs_open: invalid first record\n%s\n", YAFFS_HELP_MESSAGE);
        goto on_error;
    }
    free(first_header);
    first_header = NULL;

    fs->duname = "Chunk";

    /*
    * Calculate the meta data info
    */
    //fs->last_inum = 0xffffffff; // Will update this as we go
    fs->last_inum = 0;
    fs->root_inum = YAFFS_OBJECT_ROOT;
    fs->first_inum = YAFFS_OBJECT_FIRST;
    //fs->inum_count = fs->last_inum; // For now this will be the last_inum - 1 (after we calculate it)

    /*
    * Calculate the block info
    */
    fs->dev_bsize = img_info->sector_size;
    fs->block_size = yaffsfs->page_size;
    fs->block_pre_size = 0;
    fs->block_post_size = yaffsfs->spare_size;
    fs->block_count = img_info->size / (fs->block_pre_size + fs->block_size + fs->block_post_size);
    fs->first_block = 0;
    fs->last_block_act = fs->last_block = fs->block_count ? fs->block_count - 1 : 0;

    /* Set the generic function pointers */
    fs->inode_walk = yaffsfs_inode_walk;
    fs->block_walk = yaffsfs_block_walk;
    fs->block_getflags = yaffsfs_block_getflags;

    fs->get_default_attr_type = yaffsfs_get_default_attr_type;
    fs->load_attrs = yaffsfs_load_attrs;

    fs->file_add_meta = yaffs_inode_lookup;
    fs->dir_open_meta = yaffsfs_dir_open_meta;
    fs->fsstat = yaffsfs_fsstat;
    fs->fscheck = yaffsfs_fscheck;
    fs->istat = yaffsfs_istat;
    fs->name_cmp = tsk_fs_unix_name_cmp;

    fs->close = yaffsfs_close;

    /* Journal */
    fs->jblk_walk = yaffsfs_jblk_walk;
    fs->jentry_walk = yaffsfs_jentry_walk;
    fs->jopen = yaffsfs_jopen;

    /* Initialize the caches */
    if (tsk_verbose)
        fprintf(stderr, "yaffsfs_open: building cache...\n");

    /* Build cache */
    /* NOTE: The only modifications to the cache happen here, during at
    *       the open. Should be fine with no lock, even if access to the
    *       cache is shared among threads.
    */
    //tsk_init_lock(&yaffsfs->lock);
    yaffsfs->chunkMap = new std::map<uint32_t, YaffsCacheChunkGroup>;
    if (TSK_OK != yaffsfs_parse_image_load_cache(yaffsfs)) {
        goto on_error;
    }

    if (tsk_verbose) {
        fprintf(stderr, "yaffsfs_open: done building cache!\n");
        //yaffscache_objects_dump(yaffsfs, stderr);
    }

    // Update the number of inums now that we've read in the file system
    fs->inum_count = fs->last_inum - 1;

    test_dir = tsk_fs_dir_open_meta(fs, fs->root_inum);
    if (test_dir == NULL) {
        tsk_error_reset();
        tsk_error_set_errno(TSK_ERR_FS_MAGIC);
        tsk_error_set_errstr("not a YAFFS file system (no root directory). %s", YAFFS_HELP_MESSAGE);
        if (tsk_verbose)
            fprintf(stderr, "yaffsfs_open: invalid file system\n%s\n", YAFFS_HELP_MESSAGE);
        goto on_error;
    }
    tsk_fs_dir_close(test_dir);

    return fs;

on_error:
    // yaffsfs_close frees all the cache objects
    yaffsfs_close(fs);

    return NULL;
}

In version 4.8.0 and earlier of The Sleuth Kit (TSK), there is a stack buffer overflow vulnerability in the YAFFS file timestamp parsing logic in yaffsfs_istat() in fs/yaffs.c.


    yaffscache_objects_free(YAFFSFS_INFO *yfs)
{
    if((yfs != NULL) && (yfs->cache_objects != NULL)){
        YaffsCacheObject *obj = yfs->cache_objects;
        while(obj != NULL) {
            YaffsCacheObject *to_free = obj;

            YaffsCacheVersion *ver = obj->yco_latest;
            while(ver != NULL) {
                YaffsCacheVersion *v_to_free = ver;
                ver = ver->ycv_prior;
                free(v_to_free);
            }

            obj = obj->yco_next;
            free(to_free);
        }
    }
}


    yaffsfs_parse_image_load_cache(YAFFSFS_INFO * yfs)
{
    uint8_t status = TSK_OK;
    uint32_t nentries = 0;
    YaffsSpare *spare = NULL;

    uint8_t tempBuf[8];
    uint32_t parentID;

    if (yfs->cache_objects)
        return 0;

    for(TSK_OFF_T offset = 0;offset < yfs->fs_info.img_info->size;offset += yfs->page_size + yfs->spare_size){
        status = yaffsfs_read_spare( yfs, &spare, offset + yfs->page_size);
        if (status != TSK_OK) {
            break;
        }

        if (yaffsfs_is_spare_valid(yfs, spare) == TSK_OK) {

            if((spare->has_extra_fields) || (spare->chunk_id != 0)){
                yaffscache_chunk_add(yfs,
                    offset,
                    spare->seq_number,
                    spare->object_id,
                    spare->chunk_id,
                    spare->extra_parent_id);
            }
            else{
                // If we have a header block and didn't extract it already from the spare, get the parent ID from
                // the non-spare data
                if(8 == tsk_img_read(yfs->fs_info.img_info, offset, (char*) tempBuf, 8)){
                    memcpy(&parentID, &tempBuf[4], 4);

                    yaffscache_chunk_add(yfs,
                        offset,
                        spare->seq_number,
                        spare->object_id,
                        spare->chunk_id,
                        parentID);
                }
                else{
                    // Really shouldn't happen
                    fprintf(stderr, "Error reading header to get parent id at offset %" PRIxOFF "\n", offset);
                    yaffscache_chunk_add(yfs,
                        offset,
                        spare->seq_number,
                        spare->object_id,
                        spare->chunk_id,
                        0);
                }
            }
        }

        free(spare);
        spare = NULL;

        ++nentries;
    }

    if (tsk_verbose)
        fprintf(stderr, "yaffsfs_parse_image_load_cache: read %d entries\n", nentries);

    if (tsk_verbose)
        fprintf(stderr, "yaffsfs_parse_image_load_cache: started processing chunks for version cache...\n");
    fflush(stderr);

    // At this point, we have a list of chunks sorted by obj id, seq number, and offset
    // This makes the list of objects in cache_objects, which link to different versions
    yaffscache_versions_compute(yfs);

    if (tsk_verbose)
        fprintf(stderr, "yaffsfs_parse_image_load_cache: done version cache!\n");
    fflush(stderr);

    // Having multiple inodes point to the same object seems to cause trouble in TSK, especially in orphan file detection,
    //  so set the version number of the final one to zero.
    // While we're at it, find the highest obj_id and the highest version (before resetting to zero)
    YaffsCacheObject * currObj = yfs->cache_objects;
    YaffsCacheVersion * currVer;
    while(currObj != NULL){
        if(currObj->yco_obj_id > yfs->max_obj_id){
            yfs->max_obj_id = currObj->yco_obj_id;
        }

        currVer = currObj->yco_latest;
        if(currVer->ycv_version > yfs->max_version){
            yfs->max_version = currVer->ycv_version;
        }

        currVer->ycv_version = 0;
        currObj = currObj->yco_next;
    }

    // Use the max object id and version number to construct an upper bound on the inode
    TSK_INUM_T max_inum = 0;
    if (TSK_OK != yaffscache_obj_id_and_version_to_inode(yfs->max_obj_id, yfs->max_version, &max_inum)) {
        return TSK_ERR;
    }
    yfs->fs_info.last_inum = max_inum + 1; // One more for the orphan dir

    // Make sure the orphan dir is greater than the root dir
    if (yfs->fs_info.last_inum <= yfs->fs_info.root_inum) {
        tsk_error_reset();
        tsk_error_set_errno(TSK_ERR_FS);
        tsk_error_set_errstr(
            "yaffsfs_parse_image_load_cache: Maximum inum %" PRIuINUM " is not greater than the root inum", yfs->fs_info.last_inum);
        return TSK_ERR;
    }

    return TSK_OK;
}


    yaffscache_object_find(YAFFSFS_INFO *yfs, uint32_t obj_id, YaffsCacheObject **obj)
{
    YaffsCacheObject *curr, *prev;
    curr = yfs->cache_objects;
    prev = NULL;

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

    while(curr != NULL) {
        if (curr->yco_obj_id == obj_id) {
            *obj = curr;
            return TSK_OK;
        }
        else if (curr->yco_obj_id > obj_id) {
            *obj = prev;
            return TSK_STOP;
        }

        prev = curr;
        curr = curr->yco_next;
    }

    *obj = prev;
    return TSK_STOP;
}


    yaffscache_chunk_find_insertion_point(YAFFSFS_INFO *yfs, uint32_t obj_id, TSK_OFF_T offset, uint32_t seq_number, YaffsCacheChunk **chunk)
{
    YaffsCacheChunk *curr, *prev;

    // Have we seen this obj_id? If not, add an entry for it
    if(yfs->chunkMap->find(obj_id) == yfs->chunkMap->end()){
        fflush(stderr);
        YaffsCacheChunkGroup chunkGroup;
        chunkGroup.cache_chunks_head = NULL;
        chunkGroup.cache_chunks_tail = NULL;
        yfs->chunkMap->insert(std::make_pair(obj_id, chunkGroup));
    }

    curr = yfs->chunkMap->operator[](obj_id).cache_chunks_head;
    prev = NULL;

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

    while(curr != NULL) {
        // Compares obj id, then seq num, then offset. -1 => current < new
        int cmp = yaffscache_chunk_compare(curr, obj_id, offset, seq_number);

        if (cmp == 0) {
            *chunk = curr;
            return TSK_OK;
        }
        else if (cmp == 1) {
            *chunk = prev;
            return TSK_STOP;
        }

        prev = curr;
        curr = curr->ycc_next;
    }

    *chunk = prev;
    return TSK_STOP;
}


    yaffs_make_directory(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_DIR;
    fs_file->meta->mode = (TSK_FS_META_MODE_ENUM)0;
    fs_file->meta->nlink = 1;

    if((inode == YAFFS_OBJECT_UNLINKED) || (inode == YAFFS_OBJECT_DELETED) ||
        (inode == yaffsfs->fs_info.last_inum)){
            fs_file->meta->flags =
                (TSK_FS_META_FLAG_ENUM)(TSK_FS_META_FLAG_USED | TSK_FS_META_FLAG_ALLOC);
    }
    else{
        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();
    }

    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;
    fs_file->meta->addr = inode;
    return 0;
}


yaffs_load_config_file(TSK_IMG_INFO * a_img_info, std::map<std::string, std::string> & results){
    size_t config_file_name_len;
    TSK_TCHAR * config_file_name;
    FILE* config_file;
    char buf[1001];

    // Ensure there is at least one image name
    if(a_img_info->num_img < 1){
        return YAFFS_CONFIG_ERROR;
    }

    // Construct the name of the config file from the first image name
    config_file_name_len = TSTRLEN(a_img_info->images[0]);
    config_file_name_len += TSTRLEN(YAFFS_CONFIG_FILE_SUFFIX);
    config_file_name = (TSK_TCHAR *) tsk_malloc(sizeof(TSK_TCHAR) * (config_file_name_len + 1));

    TSTRNCPY(config_file_name, a_img_info->images[0], TSTRLEN(a_img_info->images[0]) + 1);
    TSTRNCAT(config_file_name, YAFFS_CONFIG_FILE_SUFFIX, TSTRLEN(YAFFS_CONFIG_FILE_SUFFIX) + 1);

#ifdef TSK_WIN32
    HANDLE hWin;

    if ((hWin = CreateFile(config_file_name, GENERIC_READ,
            FILE_SHARE_READ, 0, OPEN_EXISTING, 0,
            0)) == INVALID_HANDLE_VALUE) {

        // For the moment, assume that the file just doesn't exist, which isn't an error
        free(config_file_name);
        return YAFFS_CONFIG_FILE_NOT_FOUND;
    }
    config_file = _fdopen(_open_osfhandle((intptr_t) hWin, _O_RDONLY), "r");
    if (config_file == NULL) {
        tsk_error_reset();
        tsk_error_set_errno(TSK_ERR_FS);
        tsk_error_set_errstr(
                    "yaffs_load_config: Error converting Windows handle to C handle");
        free(config_file_name);
        CloseHandle(hWin);
        return YAFFS_CONFIG_ERROR;
    }
#else
    if (NULL == (config_file = fopen(config_file_name, "r"))) {
        free(config_file_name);
        return YAFFS_CONFIG_FILE_NOT_FOUND;
    }
#endif

    while(fgets(buf, 1000, config_file) != NULL){

        // Is it a comment?
        if((buf[0] == '#') || (buf[0] == ';')){
            continue;
        }

        // Is there a '=' ?
        if(strchr(buf, '=') == NULL){
            continue;
        }

        // Copy to strings while removing whitespace and converting to lower case
        std::string paramName("");
        std::string paramVal("");

        const char * paramNamePtr = strtok(buf, "=");
        while(*paramNamePtr != '\0'){
            if(! isspace((char)(*paramNamePtr))){
                paramName += tolower((char)(*paramNamePtr));
            }
            paramNamePtr++;
        }

        const char * paramValPtr = strtok(NULL, "=");
        while(*paramValPtr != '\0'){
            if(! isspace(*paramValPtr)){
                paramVal += tolower((char)(*paramValPtr));
            }
            paramValPtr++;
        }

        // Make sure this parameter is not already in the map
        if(results.find(paramName) != results.end()){
            // Duplicate parameter - return an error
            tsk_error_reset();
            tsk_error_set_errno(TSK_ERR_FS);
            tsk_error_set_errstr(
                        "yaffs_load_config: Duplicate parameter name in config file (\"%s\"). %s", paramName.c_str(), YAFFS_HELP_MESSAGE);
            fclose(config_file);
            free(config_file_name);
            return YAFFS_CONFIG_ERROR;
        }

        // Add this entry to the map
        results[paramName] = paramVal;
    }

    fclose(config_file);
    free(config_file_name);
    return YAFFS_CONFIG_OK;
}


    yaffscache_object_dump(FILE *fp, YaffsCacheObject *obj)
{
    YaffsCacheVersion *next_version = obj->yco_latest;
    YaffsCacheChunk *chunk = next_version->ycv_last_chunk;

    fprintf(fp, "Object %d\n", obj->yco_obj_id);
    while(chunk != NULL && chunk->ycc_obj_id == obj->yco_obj_id) {
        if (next_version != NULL &&
            chunk == next_version->ycv_last_chunk) {
                fprintf(fp, "  @%d: %p %p %p\n",
                    next_version->ycv_version,
                    (void*) next_version->ycv_header_chunk,
                    (void*) next_version->ycv_first_chunk,
                    (void*)next_version->ycv_last_chunk);
                next_version = next_version->ycv_prior;
        }

        fprintf(fp, "    + %p %08x %08x %0" PRIxOFF "\n",
            (void*) chunk,
            chunk->ycc_chunk_id,
            chunk->ycc_seq_number,
            chunk->ycc_offset);

        chunk = chunk->ycc_prev;
    }
}


    yaffsfs_inode_walk(TSK_FS_INFO *fs, TSK_INUM_T start_inum,
    TSK_INUM_T end_inum, TSK_FS_META_FLAG_ENUM flags,
    TSK_FS_META_WALK_CB a_action, void *a_ptr)
{
    YAFFSFS_INFO *yfs = (YAFFSFS_INFO *)fs;
    TSK_FS_FILE *fs_file;
    TSK_RETVAL_ENUM result;

    uint32_t start_obj_id;
    uint32_t start_ver_number;
    uint32_t end_obj_id;
    uint32_t end_ver_number;

    uint32_t obj_id;

    YaffsCacheObject *curr_obj;
    YaffsCacheVersion *curr_version;

    result = yaffscache_inode_to_obj_id_and_version(start_inum, &start_obj_id, &start_ver_number);

    result = yaffscache_inode_to_obj_id_and_version(end_inum, &end_obj_id, &end_ver_number);

    if (end_obj_id < start_obj_id) {
        tsk_error_reset();
        tsk_error_set_errno(TSK_ERR_FS_WALK_RNG);
        tsk_error_set_errstr("yaffsfs_inode_walk: end object id must be >= start object id: "
            "%" PRIx32 " must be >= %" PRIx32 "",
            end_obj_id, start_obj_id);
        return 1;
    }

    /* The ORPHAN flag is unsupported for YAFFS2 */
    if (flags & TSK_FS_META_FLAG_ORPHAN) {
        if (tsk_verbose){
            tsk_fprintf(stderr, "yaffsfs_inode_walk: ORPHAN flag unsupported by YAFFS2");
        }
    }

    if (((flags & TSK_FS_META_FLAG_ALLOC) == 0) &&
        ((flags & TSK_FS_META_FLAG_UNALLOC) == 0)) {
            flags = (TSK_FS_META_FLAG_ENUM)(flags | TSK_FS_META_FLAG_ALLOC | TSK_FS_META_FLAG_UNALLOC);
    }

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

    if ((fs_file = tsk_fs_file_alloc(fs)) == NULL)
        return 1;
    if ((fs_file->meta =
        tsk_fs_meta_alloc(YAFFS_FILE_CONTENT_LEN)) == NULL)
        return 1;

    for (obj_id = start_obj_id; obj_id <= end_obj_id; obj_id++) {
        int retval;

        result = yaffscache_version_find_by_inode(yfs, obj_id, &curr_version, &curr_obj);
        if (result == TSK_OK) {

            TSK_INUM_T curr_inode;
            YaffsCacheVersion *version;

            // ALLOC, UNALLOC, or both are set at this point
            if (flags & TSK_FS_META_FLAG_ALLOC) {
                // Allocated only - just look at current version
                if (yaffscache_obj_id_and_version_to_inode(obj_id, curr_obj->yco_latest->ycv_version, &curr_inode) != TSK_OK) {
                    tsk_fs_file_close(fs_file);
                    return 1;
                }

                // It's possible for the current version to be unallocated if the last header was a deleted or unlinked header
                if(yaffs_is_version_allocated(yfs, curr_inode)){
                    if (yaffs_inode_lookup(fs, fs_file, curr_inode) != TSK_OK) {
                        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;
                    }
                }
            }
            if (flags & TSK_FS_META_FLAG_UNALLOC){
                for (version = curr_obj->yco_latest; version != NULL; version = version->ycv_prior) {
                    if (yaffscache_obj_id_and_version_to_inode(obj_id, version->ycv_version, &curr_inode) != TSK_OK) {
                        tsk_fs_file_close(fs_file);
                        return 1;
                    }

                    if(! yaffs_is_version_allocated(yfs, curr_inode)){
                        if (yaffs_inode_lookup(fs, fs_file, curr_inode) != TSK_OK) {
                            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;
                        }
                    }
                }
            }

            curr_obj = curr_obj->yco_next;
        }
    }

    /*
    * Cleanup.
    */
    tsk_fs_file_close(fs_file);
    return 0;
}


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;
}


    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;
        }
    }
}


    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;
}


    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;
}


    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;
    }

}


    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;
}


    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;
}


    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;
}


    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;
}


    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;
}