2005.01.25 18:29 "[Tiff] issues with TIFFStreamOpen", by Michael Rinne

2005.01.27 19:45 "Re: [Tiff] issues with TIFFStreamOpen", by Edward Lam

Hi Michael,

It looks like the LibTIFF uses the seek function right at the start in order to seek to the beginning of the file. This happens in TIFFClientOpen by a call to _tiffosSeekProc with off=0 and whence=SEEK_SET. Therefore, any data written to the stream before calling TIFFStreamOpen gets overwritten by the TIFF image.

Sorry for the late reply as I've been swamped. I had contributed the libtiff changes that we made in our local baseline against tiff 3.4. It appears that the code has never been used for writing files at an arbitrary location. I don't see your fix in the attached file from your orignal email though. I did a diff with my own version and found no real differences. Perhaps you attached the wrong file? I believe you're right though as _tiffosSeekProc() should behave the same ass _tiffisSeekProc. The TIFFStreamOpen() code was previously using tiff 3.4 which didn't have this extra call to TIFFSeekOpen() from TIFFClientOpen().

The comment in TIFFClientOpen() is still suspicious though:

/*
  * This seek shouldn't be necessary, but I have had some
  * crazy problems with a failed fseek() on Solaris leaving
  * the current file pointer out of whack when an fwrite()
  * is done.
  */
TIFFSeekFile( tif, 0, SEEK_SET );

if (!WriteOK(tif, &tif->tif_header, sizeof (TIFFHeader))) {
        TIFFError(name, "Error writing TIFF header");
        goto bad;
}

Does anyone know why this was added? (Frank? Andrey?) The comment doesn't even make sense. Prior to the call to TIFFSeekFile() in TIFFClientOpen(), there's no calls to fwrite at all. We only call WriteOK() *after* calling TIFFSeekFile(). So this means that if WriteOK() was messing up the file pointer, then doing the seek before wouldn't have an effect anyhow. Another problem with this code is that WriteOK() will not call fwrite() in general anyhow as WriteOK() ultimately just resolves to tif_writeproc which is passed down to TIFFClientOpen(). There's no requirement that tif_writeproc uses fwrite().

If no one has a test case which can prove that the seek is required, I propose that we scrap the call to TIFFSeekFile() in TIFFClientOpen() regardless. If the seek is really necessary, I think it should be up to the caller of TIFFClientOpen() to do it. ReadOK() shouldn't change the file pointer anyhow, if it does, then it is a bug in the readproc.

2. LibTIFF is currently unable to write images to an ostringstream.

This might be Microsoft Visual C++ specific. I am using MS VC 7.1. If one calls tellp on a newly constructed ostringstream object it returns the position -1.

How does anyone expect to treat streams generically? <sigh> We could do something like this in TIFFStreamOpen(const char *, ostream *)

        if( !os->fail() && (toff_t)os->tellp() < 0 ) {
                *os << '\0';
                os->seekp(0);
        }

the ostringstream. This results almost in a valid stream, but it fails at the end with the error message 'Error writing data for field "BitsPerSample".'. I tracked it down to the macro SeekOK which gets called in tif_dirwrite.c at line 1066. This macro calls _tiffosSeekProc with an offset that seems to exceed the amount of data written to the stream and therefore the ostream method returns a bad offset.

Does anyone know if this is supposed to be the way as to how it works? I assume that libtiff will at some point go back and fill in the missing data? We could workaround this by writing intermediate '\0' characters if the seek goes beyond the end of the stream.

I've attached a new tif_stream.cxx incorporating all the changes mentioned above. I haven't tested them though. Please let me know if it works for you. It's much appreciated, Michael!

Thanks,
-Edward

/* $Id: tif_stream.cxx,v 1.2 2004/11/13 11:19:15 dron Exp $ */

/*
 * Copyright (c) 1988-1996 Sam Leffler
 * Copyright (c) 1991-1996 Silicon Graphics, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software and
 * its documentation for any purpose is hereby granted without fee, provided
 * that (i) the above copyright notices and this permission notice appear in
 * all copies of the software and related documentation, and (ii) the names of
 * Sam Leffler and Silicon Graphics may not be used in any advertising or
 * publicity relating to the software without the specific, prior written
 * permission of Sam Leffler and Silicon Graphics.
 *
 * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
 *
 * IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR
 * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF
 * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
 * OF THIS SOFTWARE.
 */

/*
 * TIFF Library UNIX-specific Routines.
 */
#include <iostream>
#include "tiffiop.h"

using namespace std;

class tiffis_data
{
  public:

        istream *myIS;
        long    myStreamStartPos;
};

class tiffos_data
{
  public:

        ostream *myOS;
        long    myStreamStartPos;
};

static tsize_t
_tiffosReadProc(thandle_t, tdata_t, tsize_t)
{
        return 0;
}

static tsize_t
_tiffisReadProc(thandle_t fd, tdata_t buf, tsize_t size)
{
        tiffis_data     *data = (tiffis_data *)fd;

        data->myIS->read((char *)buf, (int)size);

        return data->myIS->gcount();
}

static tsize_t
_tiffosWriteProc(thandle_t fd, tdata_t buf, tsize_t size)
{
        tiffos_data     *data = (tiffos_data *)fd;
        ostream         *os = data->myOS;
        int             pos = os->tellp();

        os->write((const char *)buf, size);

        return ((int)os->tellp()) - pos;
}

static tsize_t
_tiffisWriteProc(thandle_t, tdata_t, tsize_t)
{
        return 0;
}

static toff_t
_tiffosSeekProc(thandle_t fd, toff_t off, int whence)
{
        tiffos_data     *data = (tiffos_data *)fd;
        ostream *os = data->myOS;

        // if the stream has already failed, don't do anything
        if( os->fail() )
                return os->tellp();

        switch(whence) {
        case SEEK_SET:
            os->seekp(data->myStreamStartPos + off, ios::beg);
                break;
        case SEEK_CUR:
                os->seekp(off, ios::cur);
                break;
        case SEEK_END:
                os->seekp(off, ios::end);
                break;
        }

        // Attempt to workaround problems with seeking past the end of the
        // stream.  ofstream doesn't have a problem with this but
        // ostrstream/ostringstream does. In that situation, add intermediate
        // '\0' characters.
        if( os->fail() ) {
                ios_base::iostate   old_state;
                toff_t              origin;

                old_state = os->rdstate();
                // reset the fail bit or else tellp() won't work below
                os->clear(os->rdstate() & ~ios::failbit);
                switch( whence ) {
                        case SEEK_SET:
                                origin = data->myStreamStartPos;
                                break;
                        case SEEK_CUR:
                                origin = os->tellp();
                                break;
                        case SEEK_END:
                                os->seekp(0, ios::end);
                                origin = os->tellp();
                                break;
                }
                // restore original stream state
                os->clear(old_state);

                // only do something if desired seek position is valid
                if( origin + off > data->myStreamStartPos ) {
                        toff_t  num_fill;

                        // clear the fail bit
                        os->clear(os->rdstate() & ~ios::failbit);

                        // fill the intermediate space
                        num_fill = origin + off - (toff_t)os->tellp();
                        for( toff_t i = 0; i < num_fill; i++ )
                                os->put('\0');

                        // retry the seek
                        os->seekp(origin + off, ios::beg);
                }
        }

        return os->tellp();
}

static toff_t
_tiffisSeekProc(thandle_t fd, toff_t off, int whence)
{
        tiffis_data     *data = (tiffis_data *)fd;

        switch(whence) {
        case SEEK_SET:
                data->myIS->seekg(data->myStreamStartPos + off, ios::beg);
                break;
        case SEEK_CUR:
                data->myIS->seekg(off, ios::cur);
                break;
        case SEEK_END:
                data->myIS->seekg(off, ios::end);
                break;
        }

        return ((long)data->myIS->tellg()) - data->myStreamStartPos;
}

static toff_t
_tiffosSizeProc(thandle_t fd)
{
        tiffos_data     *data = (tiffos_data *)fd;
        ostream         *os = data->myOS;
        toff_t          pos = os->tellp();
        toff_t          len;

        os->seekp(0, ios::end);
        len = os->tellp();
        os->seekp(pos);

        return len;
}

static toff_t
_tiffisSizeProc(thandle_t fd)
{
        tiffis_data     *data = (tiffis_data *)fd;
        int             pos = data->myIS->tellg();
        int             len;

        data->myIS->seekg(0, ios::end);
        len = data->myIS->tellg();
        data->myIS->seekg(pos);

        return len;
}

static int
_tiffosCloseProc(thandle_t fd)
{
        // Our stream was not allocated by us, so it shouldn't be closed by us.
        delete (tiffos_data *)fd;
        return 0;
}

static int
_tiffisCloseProc(thandle_t fd)
{
        // Our stream was not allocated by us, so it shouldn't be closed by us.
        delete (tiffis_data *)fd;
        return 0;
}

static int
_tiffDummyMapProc(thandle_t , tdata_t* , toff_t* )
{
        return (0);
}

static void
_tiffDummyUnmapProc(thandle_t , tdata_t , toff_t )
{
}

/*
 * Open a TIFF file descriptor for read/writing.
 */
static TIFF*
_tiffStreamOpen(const char* name, const char* mode, void *fd)
{
        TIFF*   tif;

        if( strchr(mode, 'w') ) {
                tiffos_data     *data = new tiffos_data;
                data->myOS = (ostream *)fd;
                data->myStreamStartPos = data->myOS->tellp();

                // Open for writing.
                tif = TIFFClientOpen(name, mode,
                                (thandle_t) fd,
                                _tiffosReadProc, _tiffosWriteProc,
                                _tiffosSeekProc, _tiffosCloseProc,
                                _tiffosSizeProc,
                                _tiffDummyMapProc, _tiffDummyUnmapProc);
        } else {
                tiffis_data     *data = new tiffis_data;
                data->myIS = (istream *)fd;
                data->myStreamStartPos = data->myIS->tellg();
                // Open for reading.
                tif = TIFFClientOpen(name, mode,
                                (thandle_t) data,
                                _tiffisReadProc, _tiffisWriteProc,
                                _tiffisSeekProc, _tiffisCloseProc,
                                _tiffisSizeProc,
                                _tiffDummyMapProc, _tiffDummyUnmapProc);
        }

        return (tif);
}

TIFF*
TIFFStreamOpen(const char* name, ostream *os)
{
        // If os is either a ostrstream or ostringstream, and has no data
        // written to it yet, then tellp() will return -1 which will break us.
        // We workaround this by writing out a dummy character and
        // then seek back to the beginning.
        if( !os->fail() && (int)os->tellp() < 0 ) {
                *os << '\0';
                os->seekp(0);
        }

        // NB: We don't support mapped files with streams so add 'm'
        return _tiffStreamOpen(name, "wm", os);
}

TIFF*
TIFFStreamOpen(const char* name, istream *is)
{
        // NB: We don't support mapped files with streams so add 'm'
        return _tiffStreamOpen(name, "rm", is);
}

/* vim: set ts=8 sts=8 sw=8 noet: */