Merge the I/O buffering branch
authorTony Cook <tony@develop-help.com>
Mon, 10 Oct 2011 07:13:10 +0000 (18:13 +1100)
committerTony Cook <tony@develop-help.com>
Mon, 10 Oct 2011 07:13:10 +0000 (18:13 +1100)
commit 1d817f4340de9da358142746fdca48d45f078bc9
Author: Tony Cook <tony@develop-help.com>
Date:   Mon Oct 10 18:07:12 2011 +1100

    move changes into release area

commit a0392ffea07d33bb4daec26bb8099e729ab31ac5
Author: Tony Cook <tony@develop-help.com>
Date:   Sat Oct 8 13:37:10 2011 +1100

    bump API revision level and note the cause (in a comment)

commit fdbad1c9b2eb0ce899622aa0421a1096d2b76b2f
Author: Tony Cook <tony@develop-help.com>
Date:   Sat Oct 8 12:36:44 2011 +1100

    fill out changes some more

commit b54571f975d3a6f5643fddd80a0da852f65b43de
Author: Tony Cook <tony@develop-help.com>
Date:   Sat Oct 8 12:15:05 2011 +1100

    document io_new_cb() callbacks a little + some cleanup

commit a5504e7dde96bdc8db5af40d1f02612461d08ba4
Author: Tony Cook <tony@develop-help.com>
Date:   Sat Oct 8 11:53:17 2011 +1100

    make new I/O layer object constructors and document them

commit c631d25b87134f26b2bb026d1cf87711e0564947
Author: Tony Cook <tony@develop-help.com>
Date:   Sat Oct 8 00:14:07 2011 +1100

    document most I/O layer functions, export the io_glue constructors

commit 1d7d283cc69e0787fe235db1b906b391398a296f
Author: Tony Cook <tony@develop-help.com>
Date:   Thu Oct 6 18:59:49 2011 +1100

    make sure we read bytes for callback IO objects

commit 2aacacb2743d89836dde4fbc02bdaa08c6f4a8e9
Author: Tony Cook <tony@develop-help.com>
Date:   Thu Oct 6 17:00:35 2011 +1100

    document io_new_cb perl callbacks, buffered parameter and some cleanup

commit b1cb9fb3741984ac2372e78bb6700e3b142c0963
Author: Tony Cook <tony@develop-help.com>
Date:   Mon Oct 3 16:36:58 2011 +1100

    add Imager::IO pod coverage tests + add method docs

commit dd203834a9f5765bf57caa10b1e41e9e94da8b06
Author: Tony Cook <tony@develop-help.com>
Date:   Mon Oct 3 12:31:29 2011 +1100

    move internals to the end of iolayer.c

commit 4692ab9a42838919f1f5fd185f341d592319601b
Author: Tony Cook <tony@develop-help.com>
Date:   Mon Oct 3 11:58:35 2011 +1100

    clean up unneeded structures

commit b179462d315f0f953d55ec216d92912d8af6be7f
Author: Tony Cook <tony@develop-help.com>
Date:   Fri Sep 30 23:36:41 2011 +1000

    fail flush if error is set

commit 0db35f7e68d5027c85f7bf28b6706fb7dc210c96
Author: Tony Cook <tony@develop-help.com>
Date:   Tue Sep 27 00:08:11 2011 +1000

    add i_io_gets()

commit f1d7a30ae3376dcacf337faa3b172b135b8a85c2
Author: Tony Cook <tony@develop-help.com>
Date:   Mon Sep 26 23:18:20 2011 +1000

    remove more unused code, including unused variables

commit 9d4eea2b2d0725466162ee2008faa52825ea68ca
Author: Tony Cook <tony@develop-help.com>
Date:   Mon Sep 26 21:46:29 2011 +1000

    final coverage tests

commit 28a5ceb962c54f01d68115c8ce23cf47c9c33742
Author: Tony Cook <tony@develop-help.com>
Date:   Thu Sep 22 22:36:01 2011 +1000

    WIP, more coverage and fixes

commit 5a426928bc533e39c09acc795803a74054b7c779
Author: Tony Cook <tony@develop-help.com>
Date:   Thu Sep 22 22:35:43 2011 +1000

    we almost always want symbols with our coverage, so enable them

commit 7cfdd0cfde8b63760fb1817812ca53cbbb479b5b
Author: Tony Cook <tony@develop-help.com>
Date:   Thu Sep 22 22:35:07 2011 +1000

    actually store the character in the i_io_putc() macro

commit 676e43d395e0928ac46e3c6763169186c094fdcd
Author: Tony Cook <tony@develop-help.com>
Date:   Thu Sep 22 22:32:42 2011 +1000

    remove the 0 size check, since i_io_peekn() itself does the check

    This ensures we can test the underlying function's behaviour

commit 6175b9ef623b8c627b700f186b31937813c94f7f
Author: Tony Cook <tony@develop-help.com>
Date:   Thu Sep 22 22:30:56 2011 +1000

    make i_io_set_buffered() into a real function

    since it needs to flush

commit 3fc2c6587a5dbbaa7ef4ab84a1c83decc9f15bf8
Author: Tony Cook <tony@develop-help.com>
Date:   Sat Sep 17 15:06:27 2011 +1000

    more coverage, some fixes, still WIP

commit ddb433c610b9bc1e5588fcb7276b4f1916a09e8f
Author: Tony Cook <tony@develop-help.com>
Date:   Sat Sep 17 13:16:00 2011 +1000

    improve i_io_peekn() / i_io_read_fill() interaction

    a peekn() for a small amount no longer limits a following peekn() for
    a larger amount

    it also handles partly consumed input buffers

commit 830b5c652aa665b4ffe70c03b991ed53c18ea671
Author: Tony Cook <tony@develop-help.com>
Date:   Sat Sep 17 00:24:55 2011 +1000

    WIP, working on coverage tests

commit 1987245122d4a6eae095592f995786ad6097ed21
Author: Tony Cook <tony@develop-help.com>
Date:   Wed Sep 14 20:47:49 2011 +1000

    update iobuf todos

commit 2dbbb48e431d9c4f085330224e9a975be61f8a4c
Author: Tony Cook <tony@develop-help.com>
Date:   Tue Sep 13 11:28:36 2011 +1000

    make the error message checks unbuffered, update the grey32 test image

    since test_image_double() has changed

commit 84dc488a323bcded457d4f338b2636db59d51f03
Author: Tony Cook <tony@develop-help.com>
Date:   Tue Sep 13 11:28:24 2011 +1000

    improve error checking a bit

commit 617340662609eaf1ddf385758c7729e3fa8d37bb
Author: Tony Cook <tony@develop-help.com>
Date:   Mon Sep 12 22:37:42 2011 +1000

    WIP commit

commit 342167b92f30c4a1f8f94b16f85875c866383263
Author: Tony Cook <tony@develop-help.com>
Date:   Sat Sep 10 15:07:00 2011 +1000

    remove buffering from the PNM reader

commit 8c2fe37a2612a261cc53bcb955be11bd31974770
Author: Tony Cook <tony@develop-help.com>
Date:   Sat Sep 10 14:00:10 2011 +1000

    remove buffering from the callback IO object implementation

       - the callback IO object did its own buffering, controlled by the
         maxbuffer parameter supplied to the read() and write() methods.
         This buffering has been removed, to avoid redundancy with the
         common io_glue buffering.

       - the callback IO object new tests the result of calling the close
         callback, which should return true for success.

commit 2ffd623b6849096a3bd1ea9a0708c2f45d1824bf
Author: Tony Cook <tony@develop-help.com>
Date:   Sat Sep 10 13:13:46 2011 +1000

    change notes + todo for the iobuf changes

commit dce56aa69674ff522c0d617e84e1daa965c00775
Author: Tony Cook <tony@develop-help.com>
Date:   Sat Sep 10 00:01:23 2011 +1000

    buffering working

    Todo:

    - some file handlers don't check the result of i_io_close(), test that
      (with a close callback handler), fix that

    - remove other buffering implementations where possible, the callback
      code (Imager.xs) and pnm.c in particular

    - allow _get_writer() to produce unbuffered writers

    - add coverage tests for the buffering code

commit d45c6298a616a4481def0a1abfe0e91a26ef29b8
Author: Tony Cook <tony@develop-help.com>
Date:   Tue Aug 30 00:15:12 2011 +1000

    reorganize the io_glue types for the upcoming buffering changes

commit e080fa60bd5db126d4e2620131db4017cfe7a98c
Author: Tony Cook <tony@develop-help.com>
Date:   Tue Aug 30 00:14:26 2011 +1000

    eliminate the final remnants of io_glue_commit_types()

    it's not part of the API and has been a no-op for years

45 files changed:
Changes
GIF/imgif.c
GIF/t/t10gif.t
ICO/msicon.c
ICO/msicon.h
ICO/t/t10icon.t
ICO/t/t60writefail.t
Imager.pm
Imager.xs
JPEG/imjpeg.c
JPEG/t/t10jpeg.t
Makefile.PL
PNG/impng.c
PNG/t/10png.t
SGI/imsgi.c
SGI/t/20write.t
TIFF/imtiff.c
TIFF/t/t10tiff.t
TIFF/testimg/grey32.tif
apidocs.perl
bmp.c
image.c
imager.h
imext.c
imext.h
imexttypes.h
iolayer.c
iolayer.h
iolayert.h
lib/Imager/APIRef.pod
lib/Imager/Files.pod
lib/Imager/Filters.pod
lib/Imager/IO.pod
pnm.c
raw.c
t/Pod/Coverage/Imager.pm
t/t07iolayer.t
t/t103raw.t
t/t104ppm.t
t/t107bmp.t
t/t108tga.t
t/t82inline.t
t/t93podcover.t
t/x20spell.t
tga.c

diff --git a/Changes b/Changes
index 34d53de..0f0cc01 100644 (file)
--- a/Changes
+++ b/Changes
@@ -1,10 +1,67 @@
 Imager release history.  Older releases can be found in Changes.old
 
-Imager 0.86
-===========
+Imager 0.85_01 - unreleased
+==============
 
  - add simple tests for the Imager::Test test_image generators
 
+ - io_glue I/O buffering re-work:
+
+   - reorganize io_glue to do it's own buffering by default
+
+   - the unbuffered functions are available as i_io_raw_read() (or
+     raw_read() from perl) etc but are not recommended for typical
+     use.
+
+   - use the new i_io_peekn() when checking for file magic to avoid
+     seek, allowing Imager to detect the file type and read the file
+     from an unseekable stream (for formats that don't use random
+     access)
+
+   - added several new I/O layer API functions.
+
+   - fix the TGA performance problem, most noticably on Win32
+     https://rt.cpan.org/Ticket/Display.html?id=70037
+
+   - TIFF now uses wrapper functions of the correct types to avoid casts
+     https://rt.cpan.org/Ticket/Display.html?id=69912
+
+   - the callback IO object did its own buffering, controlled by the
+     maxbuffer parameter supplied to the read() and write() methods.
+     This buffering has been removed, to avoid redundancy with the
+     common io_glue buffering.  This also avoids a bug in that code
+     which could rarely pass a zero length to the read callback and
+     then panic about the result.
+
+   - the callback IO object now tests the result of calling the close
+     callback, which should return true for success.
+
+   - the PNM reader did its own buffering.  This buffering has been
+     removed to avoid redundancy with the common io_glue buffering.
+
+   - previously the read handlers for fd and callback I/O layers would
+     call the underlying interface (POSIX read or your supplied callback)
+     until it filled the buffer.  It now only makes one call.
+
+   - added public constructors for I/O layer objects (see Imager::IO)
+
+   - all core file handlers now use the i_io_foo() wrappers to gain
+     access to buffered I/O rather than calling the I/O layer
+     callbacks directly.
+
+   - all core file handlers now check for error on close.
+
+   - Backward compatibility: if you hava custom file handlers, you can
+     use i_io_write() etc since they're available as macros in older
+     versions of Imager.
+
+   - eliminate the final remnants of io_glue_commit_types().
+
+   - bump IMAGER_API_VERSION, since the above may break assumptions.
+
+ - removed the long unused i_gen_reader() and i_gen_writer() utility
+   functions.
+
 Imager 0.85 - 29 Aug 2011
 ===========
 
index 4e1234b..a9875c2 100644 (file)
@@ -852,7 +852,7 @@ static int
 io_glue_read_cb(GifFileType *gft, GifByteType *buf, int length) {
   io_glue *ig = (io_glue *)gft->UserData;
 
-  return ig->readcb(ig, buf, length);
+  return i_io_read(ig, buf, length);
 }
 
 i_img *
@@ -1772,7 +1772,7 @@ static int
 io_glue_write_cb(GifFileType *gft, const GifByteType *data, int length) {
   io_glue *ig = (io_glue *)gft->UserData;
 
-  return ig->writecb(ig, data, length);
+  return i_io_write(ig, data, length);
 }
 
 
@@ -1800,7 +1800,8 @@ i_writegif_wiol(io_glue *ig, i_quantize *quant, i_img **imgs,
   
   result = i_writegif_low(quant, GifFile, imgs, count);
   
-  ig->closecb(ig);
+  if (i_io_close(ig))
+    return 0;
   
   return result;
 }
index 40a8860..c926b0c 100644 (file)
@@ -14,7 +14,7 @@ use strict;
 $|=1;
 use Test::More;
 use Imager qw(:all);
-use Imager::Test qw(is_color3 test_image test_image_raw);
+use Imager::Test qw(is_color3 test_image test_image_raw test_image_mono);
 use Imager::File::GIF;
 
 use Carp 'confess';
@@ -24,7 +24,7 @@ $SIG{__DIE__} = sub { confess @_ };
 
 init_log("testout/t105gif.log",1);
 
-plan tests => 144;
+plan tests => 146;
 
 my $green=i_color_new(0,255,0,255);
 my $blue=i_color_new(0,0,255,255);
@@ -738,6 +738,21 @@ SKIP:
   ok($im, "read image with zero-length extension");
 }
 
+
+{ # check close failures are handled correctly
+  my $im = test_image_mono();
+  my $fail_close = sub {
+    Imager::i_push_error(0, "synthetic close failure");
+    return 0;
+  };
+  ok(!$im->write(type => "gif", callback => sub { 1 },
+                closecb => $fail_close),
+     "check failing close fails");
+    like($im->errstr, qr/synthetic close failure/,
+        "check error message");
+}
+
+
 sub test_readgif_cb {
   my ($size) = @_;
 
index b59b5b3..6847504 100644 (file)
@@ -1,3 +1,4 @@
+#include "imext.h"
 #include "msicon.h"
 #include <string.h>
 #include <stdlib.h>
index 5b66704..c0f5b9d 100644 (file)
@@ -1,7 +1,7 @@
 #ifndef IMAGER_MSICON_H_
 #define IMAGER_MSICON_H_
 
-#include "iolayer.h"
+#include "iolayert.h"
 
 typedef struct ico_reader_tag ico_reader_t;
 
index 707bc4b..25b1917 100644 (file)
@@ -1,7 +1,7 @@
 #!perl -w
 use strict;
-use Test::More tests => 100;
-use Imager::Test qw(is_image);
+use Test::More tests => 102;
+use Imager::Test qw(is_image test_image);
 
 BEGIN { use_ok('Imager::File::ICO'); }
 
@@ -371,3 +371,17 @@ EOS
   $vs->box(filled => 1, color => '#333366');
   is_image($im, $vs, "check we got the right colors");
 }
+
+
+{ # check close failures are handled correctly
+  my $im = test_image();
+  my $fail_close = sub {
+    Imager::i_push_error(0, "synthetic close failure");
+    return 0;
+  };
+  ok(!$im->write(type => "ico", callback => sub { 1 },
+                closecb => $fail_close),
+     "check failing close fails");
+    like($im->errstr, qr/synthetic close failure/,
+        "check error message");
+}
index 80ce21a..1841b53 100644 (file)
@@ -77,21 +77,27 @@ use Imager ':handy';
 # low level write failure tests for each entry point (fail on write)
 {
   my $im = Imager->new(xsize => 10, ysize => 10);
-  ok(!$im->write(callback => \&write_failure, maxbuffer => 1, type=>'ico'),
+  my $io = Imager::io_new_cb(\&write_failure, undef, undef, undef, 1);
+  $io->set_buffered(0);
+  ok(!$im->write(io => $io, type=>'ico'),
      "low level write failure (ico)");
   is($im->errstr, "Write failure: synthetic error", "check message");
 }
 
 {
   my $im = Imager->new(xsize => 10, ysize => 10);
-  ok(!$im->write(callback => \&write_failure, maxbuffer => 1, type=>'cur'),
+  my $io = Imager::io_new_cb(\&write_failure, undef, undef, undef, 1);
+  $io->set_buffered(0);
+  ok(!$im->write(io => $io, type=>'cur'),
      "low level write failure (cur)");
   is($im->errstr, "Write failure: synthetic error", "check message");
 }
 
 {
   my $im = Imager->new(xsize => 10, ysize => 10);
-  ok(!Imager->write_multi({ callback => \&write_failure, maxbuffer => 1, type=>'ico' }, $im, $im),
+  my $io = Imager::io_new_cb(\&write_failure, undef, undef, undef, 1);
+  $io->set_buffered(0);
+  ok(!Imager->write_multi({ io => $io, type=>'ico' }, $im, $im),
      "low level write_multi failure (ico)");
   is(Imager->errstr, "Write failure: synthetic error", "check message");
   Imager->_set_error('');
@@ -99,7 +105,9 @@ use Imager ':handy';
 
 {
   my $im = Imager->new(xsize => 10, ysize => 10);
-  ok(!Imager->write_multi({ callback => \&write_failure, maxbuffer => 1, type=>'cur' }, $im, $im),
+  my $io = Imager::io_new_cb(\&write_failure, undef, undef, undef, 1);
+  $io->set_buffered(0);
+  ok(!Imager->write_multi({ io => $io, type=>'cur' }, $im, $im),
      "low level write_multi failure (cur)");
   is(Imager->errstr, "Write failure: synthetic error", "check message");
   Imager->_set_error('');
@@ -107,27 +115,27 @@ use Imager ':handy';
 
 {
   my $im = Imager->new(xsize => 10, ysize => 10);
-  ok(!$im->write(type => 'ico', callback => limited_write(6), maxbuffer => 1),
+  ok(!$im->write(type => 'ico', io => limited_write_io(6)),
      "second write (resource) should fail (ico)");
   is($im->errstr, "Write failure: limit reached", "check message");
   $im->_set_error('');
 
-  ok(!$im->write(type => 'cur', callback => limited_write(6), maxbuffer => 1),
+  ok(!$im->write(type => 'cur', io => limited_write_io(6)),
      "second (resource) write should fail (cur)");
   is($im->errstr, "Write failure: limit reached", "check message");
   $im->_set_error('');
 
-  ok(!$im->write(type => 'ico', callback => limited_write(22), maxbuffer => 1),
+  ok(!$im->write(type => 'ico', io => limited_write_io(22)),
      "third write (bmi) should fail (32-bit)");
   is($im->errstr, "Write failure: limit reached", "check message");
   $im->_set_error('');
 
-  ok(!$im->write(type => 'ico', callback => limited_write(62), maxbuffer => 1),
+  ok(!$im->write(type => 'ico', io => limited_write_io(62)),
      "fourth write (data) should fail (32-bit)");
   is($im->errstr, "Write failure: limit reached", "check message");
   $im->_set_error('');
 
-  ok(!$im->write(type => 'ico', callback => limited_write(462), maxbuffer => 1),
+  ok(!$im->write(type => 'ico', io => limited_write_io(462)),
      "mask write should fail (32-bit)");
   is($im->errstr, "Write failure: limit reached", "check message");
 }
@@ -139,14 +147,14 @@ use Imager ':handy';
   $im->addcolors(colors => [ $red, $blue ]);
   $im->box(filled => 1, color => $red, ymax => 5);
   $im->box(filled => 1, color => $blue, ymin => 6);
-  ok(!$im->write(type => 'ico', callback => limited_write(22), maxbuffer => 1),
+  ok(!$im->write(type => 'ico', io => limited_write_io(22)),
      "third write (bmi) should fail (1-bit)");
   is($im->errstr, "Write failure: limit reached", "check message");
   
-  ok(!$im->write(type => 'ico', callback => limited_write(66), maxbuffer => 1),
+  ok(!$im->write(type => 'ico', io => limited_write_io(66)),
      "fourth write (palette) should fail (1-bit)");
   is($im->errstr, "Write failure: limit reached", "check message");
-  ok(!$im->write(type => 'ico', callback => limited_write(74), maxbuffer => 1),
+  ok(!$im->write(type => 'ico', io => limited_write_io(74)),
      "fifth write (image) should fail (1-bit)");
   is($im->errstr, "Write failure: limit reached", "check message");
   my $data;
@@ -165,14 +173,14 @@ use Imager ':handy';
   $im->addcolors(colors => [ ($red, $blue) x 8 ]);
   $im->box(filled => 1, color => $red, ymax => 5);
   $im->box(filled => 1, color => $blue, ymin => 6);
-  ok(!$im->write(type => 'ico', callback => limited_write(22), maxbuffer => 1),
+  ok(!$im->write(type => 'ico', io => limited_write_io(22)),
      "third write (bmi) should fail (4-bit)");
   is($im->errstr, "Write failure: limit reached", "check message");
   
-  ok(!$im->write(type => 'ico', callback => limited_write(66), maxbuffer => 1),
+  ok(!$im->write(type => 'ico', io => limited_write_io(66)),
      "fourth write (palette) should fail (4-bit)");
   is($im->errstr, "Write failure: limit reached", "check message");
-  ok(!$im->write(type => 'ico', callback => limited_write(130), maxbuffer => 1),
+  ok(!$im->write(type => 'ico', io => limited_write_io(130)),
      "fifth write (image) should fail (4-bit)");
   is($im->errstr, "Write failure: limit reached", "check message");
   my $data;
@@ -191,17 +199,17 @@ use Imager ':handy';
   $im->addcolors(colors => [ ($red, $blue) x 9 ]);
   $im->box(filled => 1, color => $red, ymax => 5);
   $im->box(filled => 1, color => $blue, ymin => 6);
-  ok(!$im->write(type => 'ico', callback => limited_write(22), maxbuffer => 1),
+  ok(!$im->write(type => 'ico', io => limited_write_io(22)),
      "third write (bmi) should fail (8-bit)");
   is($im->errstr, "Write failure: limit reached", "check message");
   
-  ok(!$im->write(type => 'ico', callback => limited_write(62), maxbuffer => 1),
+  ok(!$im->write(type => 'ico', io => limited_write_io(62)),
      "fourth write (palette) should fail (8-bit)");
   is($im->errstr, "Write failure: limit reached", "check message");
-  ok(!$im->write(type => 'ico', callback => limited_write(62 + 1024), maxbuffer => 1),
+  ok(!$im->write(type => 'ico', io => limited_write_io(62 + 1024)),
      "fifth write (image) should fail (8-bit)");
   is($im->errstr, "Write failure: limit reached", "check message");
-  ok(!$im->write(type => 'ico', callback => limited_write(62 + 1024 + 10), maxbuffer => 1),
+  ok(!$im->write(type => 'ico', io => limited_write_io(62 + 1024 + 10)),
      "sixth write (zeroes) should fail (8-bit)");
   is($im->errstr, "Write failure: limit reached", "check message");
   my $data;
@@ -220,6 +228,15 @@ sub write_failure {
   return;
 }
 
+sub limited_write_io {
+  my ($limit) = @_;
+
+  my $io = Imager::io_new_cb(limited_write($limit), undef, undef, undef, 1);
+  $io->set_buffered(0);
+
+  return $io;
+}
+
 sub limited_write {
   my ($limit) = @_;
 
index aecf0c4..bd645bb 100644 (file)
--- a/Imager.pm
+++ b/Imager.pm
@@ -1338,11 +1338,15 @@ sub _get_reader_io {
 sub _get_writer_io {
   my ($self, $input) = @_;
 
+  my $buffered = exists $input->{buffered} ? $input->{buffered} : 1;
+
+  my $io;
+  my @extras;
   if ($input->{io}) {
-    return $input->{io};
+    $io = $input->{io};
   }
   elsif ($input->{fd}) {
-    return io_new_fd($input->{fd});
+    $io = io_new_fd($input->{fd});
   }
   elsif ($input->{fh}) {
     my $fd = fileno($input->{fh});
@@ -1355,7 +1359,7 @@ sub _get_writer_io {
     # flush anything that's buffered, and make sure anything else is flushed
     $| = 1;
     select($oldfh);
-    return io_new_fd($fd);
+    $io = io_new_fd($fd);
   }
   elsif ($input->{file}) {
     my $fh = new IO::File($input->{file},"w+");
@@ -1364,28 +1368,30 @@ sub _get_writer_io {
       return;
     }
     binmode($fh) or die;
-    return (io_new_fd(fileno($fh)), $fh);
+    $io = io_new_fd(fileno($fh));
+    push @extras, $fh;
   }
   elsif ($input->{data}) {
-    return io_new_bufchain();
+    $io = io_new_bufchain();
   }
   elsif ($input->{callback} || $input->{writecb}) {
-    if ($input->{maxbuffer}) {
-      return io_new_cb($input->{callback} || $input->{writecb},
-                       $input->{readcb},
-                       $input->{seekcb}, $input->{closecb},
-                       $input->{maxbuffer});
-    }
-    else {
-      return io_new_cb($input->{callback} || $input->{writecb},
-                       $input->{readcb},
-                       $input->{seekcb}, $input->{closecb});
+    if ($input->{maxbuffer} && $input->{maxbuffer} == 1) {
+      $buffered = 0;
     }
+    $io = io_new_cb($input->{callback} || $input->{writecb},
+                   $input->{readcb},
+                   $input->{seekcb}, $input->{closecb});
   }
   else {
     $self->_set_error("file/fd/fh/data/callback parameter missing");
     return;
   }
+
+  unless ($buffered) {
+    $io->set_buffered(0);
+  }
+
+  return ($io, @extras);
 }
 
 # Read an image from file
@@ -4345,8 +4351,8 @@ different combine type keywords
 compose() - L<Imager::Transformations/compose()> - compose one image
 over another.
 
-convert() - L<Imager::Transformations/"Color transformations"> -
-transform the color space
+convert() - L<Imager::Transformations/convert()> - transform the color
+space
 
 copy() - L<Imager::Transformations/copy()> - make a duplicate of an
 image
@@ -4358,13 +4364,12 @@ used to guess the output file format based on the output file name
 
 deltag() -  L<Imager::ImageTypes/deltag()> - delete image tags
 
-difference() - L<Imager::Filters/"Image Difference"> - produce a
-difference images from two input images.
+difference() - L<Imager::Filters/difference()> - produce a difference
+images from two input images.
 
-errstr() - L</"Basic Overview"> - the error from the last failed
-operation.
+errstr() - L</errstr()> - the error from the last failed operation.
 
-filter() - L<Imager::Filters> - image filtering
+filter() - L<Imager::Filters/filter()> - image filtering
 
 findcolor() - L<Imager::ImageTypes/findcolor()> - search the image
 palette, if it has one
@@ -4467,9 +4472,9 @@ polyline() - L<Imager::Draw/polyline()>
 
 preload() - L<Imager::Files/preload()>
 
-read() - L<Imager::Files> - read a single image from an image file
+read() - L<Imager::Files/read()> - read a single image from an image file
 
-read_multi() - L<Imager::Files> - read multiple images from an image
+read_multi() - L<Imager::Files/read_multi()> - read multiple images from an image
 file
 
 read_types() - L<Imager::Files/read_types()> - list image types Imager
@@ -4533,9 +4538,9 @@ unload_plugin() - L<Imager::Filters/unload_plugin()>
 virtual() - L<Imager::ImageTypes/virtual()> - whether the image has it's own
 data
 
-write() - L<Imager::Files> - write an image to a file
+write() - L<Imager::Files/write()> - write an image to a file
 
-write_multi() - L<Imager::Files> - write multiple image to an image
+write_multi() - L<Imager::Files/write_multi()> - write multiple image to an image
 file.
 
 write_types() - L<Imager::Files/read_types()> - list image types Imager
index e11fd03..9716a56 100644 (file)
--- a/Imager.xs
+++ b/Imager.xs
@@ -143,73 +143,11 @@ struct cbdata {
   SV *readcb;
   SV *seekcb;
   SV *closecb;
-
-  /* we need to remember whether the buffer contains write data or 
-     read data
-   */
-  int reading;
-  int writing;
-
-  /* how far we've read into the buffer (not used for writing) */
-  int where;
-
-  /* the amount of space used/data available in the buffer */
-  int used;
-
-  /* the maximum amount to fill the buffer before flushing
-     If any write is larger than this then the buffer is flushed and 
-     the full write is performed.  The write is _not_ split into 
-     maxwrite sized calls
-   */
-  int maxlength;
-
-  char buffer[CBDATA_BUFSIZE];
 };
 
-/* 
-
-call_writer(cbd, buf, size)
-
-Low-level function to call the perl writer callback.
-
-*/
-
-static ssize_t call_writer(struct cbdata *cbd, void const *buf, size_t size) {
-  dTHX;
-  int count;
-  int success;
-  SV *sv;
-  dSP;
-
-  if (!SvOK(cbd->writecb))
-    return -1;
-
-  ENTER;
-  SAVETMPS;
-  EXTEND(SP, 1);
-  PUSHMARK(SP);
-  PUSHs(sv_2mortal(newSVpv((char *)buf, size)));
-  PUTBACK;
-
-  count = perl_call_sv(cbd->writecb, G_SCALAR);
-
-  SPAGAIN;
-  if (count != 1)
-    croak("Result of perl_call_sv(..., G_SCALAR) != 1");
-
-  sv = POPs;
-  success = SvTRUE(sv);
-
-
-  PUTBACK;
-  FREETMPS;
-  LEAVE;
-
-  return success ? size : -1;
-}
-
-static ssize_t call_reader(struct cbdata *cbd, void *buf, size_t size, 
-                           size_t maxread) {
+static ssize_t
+call_reader(struct cbdata *cbd, void *buf, size_t size, 
+            size_t maxread) {
   dTHX;
   int count;
   int result;
@@ -238,9 +176,10 @@ static ssize_t call_reader(struct cbdata *cbd, void *buf, size_t size,
 
   if (SvOK(data)) {
     STRLEN len;
-    char *ptr = SvPV(data, len);
+    char *ptr = SvPVbyte(data, len);
     if (len > maxread)
-      croak("Too much data returned in reader callback");
+      croak("Too much data returned in reader callback (wanted %d, got %d, expected %d)",
+      (int)size, (int)len, (int)maxread);
     
     memcpy(buf, ptr, len);
     result = len;
@@ -256,21 +195,8 @@ static ssize_t call_reader(struct cbdata *cbd, void *buf, size_t size,
   return result;
 }
 
-static ssize_t write_flush(struct cbdata *cbd) {
-  dTHX;
-  ssize_t result;
-
-  if (cbd->used) {
-    result = call_writer(cbd, cbd->buffer, cbd->used);
-    cbd->used = 0;
-    return result;
-  }
-  else {
-    return 1; /* success of some sort */
-  }
-}
-
-static off_t io_seeker(void *p, off_t offset, int whence) {
+static off_t
+io_seeker(void *p, off_t offset, int whence) {
   dTHX;
   struct cbdata *cbd = p;
   int count;
@@ -280,17 +206,6 @@ static off_t io_seeker(void *p, off_t offset, int whence) {
   if (!SvOK(cbd->seekcb))
     return -1;
 
-  if (cbd->writing) {
-    if (cbd->used && write_flush(cbd) <= 0)
-      return -1;
-    cbd->writing = 0;
-  }
-  if (whence == SEEK_CUR && cbd->reading && cbd->where != cbd->used) {
-    offset -= cbd->where - cbd->used;
-  }
-  cbd->reading = 0;
-  cbd->where = cbd->used = 0;
-  
   ENTER;
   SAVETMPS;
   EXTEND(SP, 2);
@@ -315,126 +230,77 @@ static off_t io_seeker(void *p, off_t offset, int whence) {
   return result;
 }
 
-static ssize_t io_writer(void *p, void const *data, size_t size) {
+static ssize_t
+io_writer(void *p, void const *data, size_t size) {
   dTHX;
   struct cbdata *cbd = p;
+  I32 count;
+  SV *sv;
+  dSP;
+  bool success;
 
-  /* printf("io_writer(%p, %p, %u)\n", p, data, size); */
-  if (!cbd->writing) {
-    if (cbd->reading && cbd->where < cbd->used) {
-      /* we read past the place where the caller expected us to be
-         so adjust our position a bit */
-      if (io_seeker(p, cbd->where - cbd->used, SEEK_CUR) < 0) {
-        return -1;
-      }
-      cbd->reading = 0;
-    }
-    cbd->where = cbd->used = 0;
-  }
-  cbd->writing = 1;
-  if (cbd->used && cbd->used + size > cbd->maxlength) {
-    int write_res = write_flush(cbd);
-    if (write_res <= 0) {
-      return write_res;
-    }
-    cbd->used = 0;
-  }
-  if (cbd->used+size <= cbd->maxlength) {
-    memcpy(cbd->buffer + cbd->used, data, size);
-    cbd->used += size;
-    return size;
-  }
-  /* it doesn't fit - just pass it up */
-  return call_writer(cbd, data, size);
+  if (!SvOK(cbd->writecb))
+    return -1;
+
+  ENTER;
+  SAVETMPS;
+  EXTEND(SP, 1);
+  PUSHMARK(SP);
+  PUSHs(sv_2mortal(newSVpv((char *)data, size)));
+  PUTBACK;
+
+  count = perl_call_sv(cbd->writecb, G_SCALAR);
+
+  SPAGAIN;
+  if (count != 1)
+    croak("Result of perl_call_sv(..., G_SCALAR) != 1");
+
+  sv = POPs;
+  success = SvTRUE(sv);
+
+
+  PUTBACK;
+  FREETMPS;
+  LEAVE;
+
+  return success ? size : -1;
 }
 
 static ssize_t 
 io_reader(void *p, void *data, size_t size) {
-  dTHX;
   struct cbdata *cbd = p;
-  ssize_t total;
-  char *out = data; /* so we can do pointer arithmetic */
-
-  /* printf("io_reader(%p, %p, %d)\n", p, data, size); */
-  if (cbd->writing) {
-    if (write_flush(cbd) <= 0)
-      return 0;
-    cbd->writing = 0;
-  }
-
-  cbd->reading = 1;
-  if (size <= cbd->used - cbd->where) {
-    /* simplest case */
-    memcpy(data, cbd->buffer+cbd->where, size);
-    cbd->where += size;
-    return size;
-  }
-  total = 0;
-  memcpy(out, cbd->buffer + cbd->where, cbd->used - cbd->where);
-  total += cbd->used - cbd->where;
-  size  -= cbd->used - cbd->where;
-  out   += cbd->used - cbd->where;
-  if (size < sizeof(cbd->buffer)) {
-    int did_read = 0;
-    int copy_size;
-    while (size
-          && (did_read = call_reader(cbd, cbd->buffer, size, 
-                                   sizeof(cbd->buffer))) > 0) {
-      cbd->where = 0;
-      cbd->used  = did_read;
-
-      copy_size = i_min(size, cbd->used);
-      memcpy(out, cbd->buffer, copy_size);
-      cbd->where += copy_size;
-      out   += copy_size;
-      total += copy_size;
-      size  -= copy_size;
-    }
-    if (did_read < 0)
-      return -1;
-  }
-  else {
-    /* just read the rest - too big for our buffer*/
-    int did_read;
-    while ((did_read = call_reader(cbd, out, size, size)) > 0) {
-      size  -= did_read;
-      total += did_read;
-      out   += did_read;
-    }
-    if (did_read < 0)
-      return -1;
-  }
 
-  return total;
+  return call_reader(cbd, data, size, size);
 }
 
 static int io_closer(void *p) {
   dTHX;
   struct cbdata *cbd = p;
-
-  if (cbd->writing && cbd->used > 0) {
-    if (write_flush(cbd) < 0)
-      return -1;
-    cbd->writing = 0;
-  }
+  int success = 1;
 
   if (SvOK(cbd->closecb)) {
     dSP;
+    I32 count;
+    SV *sv;
 
     ENTER;
     SAVETMPS;
     PUSHMARK(SP);
     PUTBACK;
 
-    perl_call_sv(cbd->closecb, G_VOID);
+    count = perl_call_sv(cbd->closecb, G_SCALAR);
 
     SPAGAIN;
+    
+    sv = POPs;
+    success = SvTRUE(sv);
+
     PUTBACK;
     FREETMPS;
     LEAVE;
   }
 
-  return 0;
+  return success ? 0 : -1;
 }
 
 static void io_destroyer(void *p) {
@@ -448,6 +314,30 @@ static void io_destroyer(void *p) {
   myfree(cbd);
 }
 
+static i_io_glue_t *
+do_io_new_buffer(pTHX_ SV *data_sv) {
+  const char *data;
+  STRLEN length;
+
+  data = SvPVbyte(data_sv, length);
+  SvREFCNT_inc(data_sv);
+  return io_new_buffer(data, length, my_SvREFCNT_dec, data_sv);
+}
+
+static i_io_glue_t *
+do_io_new_cb(pTHX_ SV *writecb, SV *readcb, SV *seekcb, SV *closecb) {
+  struct cbdata *cbd;
+
+  cbd = mymalloc(sizeof(struct cbdata));
+  cbd->writecb = newSVsv(writecb);
+  cbd->readcb = newSVsv(readcb);
+  cbd->seekcb = newSVsv(seekcb);
+  cbd->closecb = newSVsv(closecb);
+
+  return io_new_cb(cbd, io_reader, io_writer, io_seeker, io_closer, 
+                  io_destroyer);
+}
+
 struct value_name {
   char *name;
   int value;
@@ -897,6 +787,9 @@ static im_pl_ext_funcs im_perl_funcs =
 
 #define i_img_epsilonf() (DBL_EPSILON * 4)
 
+/* avoid some xsubpp strangeness */
+#define NEWLINE '\n'
+
 MODULE = Imager                PACKAGE = Imager::Color PREFIX = ICL_
 
 Imager::Color
@@ -1048,14 +941,10 @@ io_new_bufchain()
 
 
 Imager::IO
-io_new_buffer(data)
-         char   *data
-       PREINIT:
-         size_t length;
+io_new_buffer(data_sv)
+         SV   *data_sv
        CODE:
-         SvPV(ST(0), length);
-          SvREFCNT_inc(ST(0));
-         RETVAL = io_new_buffer(data, length, my_SvREFCNT_dec, ST(0));
+         RETVAL = do_io_new_buffer(aTHX_ data_sv);
         OUTPUT:
           RETVAL
 
@@ -1066,39 +955,24 @@ io_new_cb(writecb, readcb, seekcb, closecb, maxwrite = CBDATA_BUFSIZE)
         SV *seekcb;
         SV *closecb;
         int maxwrite;
-      PREINIT:
-        struct cbdata *cbd;
       CODE:
-        cbd = mymalloc(sizeof(struct cbdata));
-        SvREFCNT_inc(writecb);
-        cbd->writecb = writecb;
-        SvREFCNT_inc(readcb);
-        cbd->readcb = readcb;
-        SvREFCNT_inc(seekcb);
-        cbd->seekcb = seekcb;
-        SvREFCNT_inc(closecb);
-        cbd->closecb = closecb;
-        cbd->reading = cbd->writing = cbd->where = cbd->used = 0;
-        if (maxwrite > CBDATA_BUFSIZE)
-          maxwrite = CBDATA_BUFSIZE;
-        cbd->maxlength = maxwrite;
-        RETVAL = io_new_cb(cbd, io_reader, io_writer, io_seeker, io_closer, 
-                           io_destroyer);
+        RETVAL = do_io_new_cb(aTHX_ writecb, readcb, seekcb, closecb);
       OUTPUT:
         RETVAL
 
-void
+SV *
 io_slurp(ig)
         Imager::IO     ig
             PREINIT:
              unsigned char*    data;
              size_t    tlength;
-            PPCODE:
+            CODE:
              data    = NULL;
               tlength = io_slurp(ig, &data);
-              EXTEND(SP,1);
-              PUSHs(sv_2mortal(newSVpv((char *)data,tlength)));
+              RETVAL = newSVpv((char *)data,tlength);
               myfree(data);
+            OUTPUT:
+             RETVAL
 
 
 undef_int
@@ -1120,10 +994,60 @@ i_get_image_file_limits()
           PUSHs(sv_2mortal(newSVuv(bytes)));
         }
 
+MODULE = Imager                PACKAGE = Imager::IO    PREFIX = io_
+
+Imager::IO
+io_new_fd(class, fd)
+       int fd
+    CODE:
+       RETVAL = io_new_fd(fd);
+    OUTPUT:
+       RETVAL
+
+Imager::IO
+io_new_buffer(class, data_sv)
+       SV *data_sv
+    CODE:
+        RETVAL = do_io_new_buffer(aTHX_ data_sv);
+    OUTPUT:
+        RETVAL
+
+Imager::IO
+io_new_cb(class, writecb, readcb, seekcb, closecb)
+        SV *writecb;
+        SV *readcb;
+        SV *seekcb;
+        SV *closecb;
+    CODE:
+        RETVAL = do_io_new_cb(aTHX_ writecb, readcb, seekcb, closecb);
+    OUTPUT:
+        RETVAL
+
+Imager::IO
+io_new_bufchain(class)
+    CODE:
+       RETVAL = io_new_bufchain();
+    OUTPUT:
+        RETVAL
+
+SV *
+io_slurp(class, ig)
+        Imager::IO     ig
+    PREINIT:
+       unsigned char*    data;
+       size_t    tlength;
+    CODE:
+       data    = NULL;
+       tlength = io_slurp(ig, &data);
+       RETVAL = newSVpv((char *)data,tlength);
+       myfree(data);
+    OUTPUT:
+       RETVAL
+
 MODULE = Imager                PACKAGE = Imager::IO    PREFIX = i_io_
 
-int
-i_io_write(ig, data_sv)
+IV
+i_io_raw_write(ig, data_sv)
        Imager::IO ig
        SV *data_sv
       PREINIT:
@@ -1138,12 +1062,12 @@ i_io_write(ig, data_sv)
        }
 #endif        
        data = SvPV(data_sv, size);
-        RETVAL = i_io_write(ig, data, size);
+        RETVAL = i_io_raw_write(ig, data, size);
       OUTPUT:
        RETVAL
 
 void
-i_io_read(ig, buffer_sv, size)
+i_io_raw_read(ig, buffer_sv, size)
        Imager::IO ig
        SV *buffer_sv
        IV size
@@ -1152,7 +1076,7 @@ i_io_read(ig, buffer_sv, size)
        ssize_t result;
       PPCODE:
         if (size <= 0)
-         croak("size negative in call to i_io_read()");
+         croak("size negative in call to i_io_raw_read()");
         /* prevent an undefined value warning if they supplied an 
           undef buffer.
            Orginally conditional on !SvOK(), but this will prevent the
@@ -1163,7 +1087,7 @@ i_io_read(ig, buffer_sv, size)
           sv_utf8_downgrade(buffer_sv, FALSE);
 #endif
        buffer = SvGROW(buffer_sv, size+1);
-        result = i_io_read(ig, buffer, size);
+        result = i_io_raw_read(ig, buffer, size);
         if (result >= 0) {
          SvCUR_set(buffer_sv, result);
          *SvEND(buffer_sv) = '\0';
@@ -1175,7 +1099,7 @@ i_io_read(ig, buffer_sv, size)
        SvSETMAGIC(ST(1));
 
 void
-i_io_read2(ig, size)
+i_io_raw_read2(ig, size)
        Imager::IO ig
        IV size
       PREINIT:
@@ -1187,7 +1111,7 @@ i_io_read2(ig, size)
          croak("size negative in call to i_io_read2()");
        buffer_sv = newSV(size);
        buffer = SvGROW(buffer_sv, size+1);
-        result = i_io_read(ig, buffer, size);
+        result = i_io_raw_read(ig, buffer, size);
         if (result >= 0) {
          SvCUR_set(buffer_sv, result);
          *SvEND(buffer_sv) = '\0';
@@ -1201,13 +1125,13 @@ i_io_read2(ig, size)
         }
 
 off_t
-i_io_seek(ig, position, whence)
+i_io_raw_seek(ig, position, whence)
        Imager::IO ig
        off_t position
        int whence
 
 int
-i_io_close(ig)
+i_io_raw_close(ig)
        Imager::IO ig
 
 void
@@ -1222,6 +1146,180 @@ i_io_CLONE_SKIP(...)
     OUTPUT:
        RETVAL
 
+int
+i_io_getc(ig)
+       Imager::IO ig
+
+int
+i_io_putc(ig, c)
+       Imager::IO ig
+        int c
+
+int
+i_io_close(ig)
+       Imager::IO ig
+
+int
+i_io_flush(ig)
+       Imager::IO ig
+
+int
+i_io_peekc(ig)
+       Imager::IO ig
+
+int
+i_io_seek(ig, off, whence)
+       Imager::IO ig
+       off_t off
+        int whence
+
+void
+i_io_peekn(ig, size)
+       Imager::IO ig
+       STRLEN size
+      PREINIT:
+       SV *buffer_sv;
+        void *buffer;
+       ssize_t result;
+      PPCODE:
+       buffer_sv = newSV(size+1);
+       buffer = SvGROW(buffer_sv, size+1);
+        result = i_io_peekn(ig, buffer, size);
+        if (result >= 0) {
+         SvCUR_set(buffer_sv, result);
+         *SvEND(buffer_sv) = '\0';
+         SvPOK_only(buffer_sv);
+         EXTEND(SP, 1);
+         PUSHs(sv_2mortal(buffer_sv));
+       }
+       else {
+          /* discard it */
+         SvREFCNT_dec(buffer_sv);
+        }
+
+void
+i_io_read(ig, buffer_sv, size)
+       Imager::IO ig
+       SV *buffer_sv
+       IV size
+      PREINIT:
+        void *buffer;
+       ssize_t result;
+      PPCODE:
+        if (size <= 0)
+         croak("size negative in call to i_io_read()");
+        /* prevent an undefined value warning if they supplied an 
+          undef buffer.
+           Orginally conditional on !SvOK(), but this will prevent the
+          downgrade from croaking */
+       sv_setpvn(buffer_sv, "", 0);
+#ifdef SvUTF8
+       if (SvUTF8(buffer_sv))
+          sv_utf8_downgrade(buffer_sv, FALSE);
+#endif
+       buffer = SvGROW(buffer_sv, size+1);
+        result = i_io_read(ig, buffer, size);
+        if (result >= 0) {
+         SvCUR_set(buffer_sv, result);
+         *SvEND(buffer_sv) = '\0';
+         SvPOK_only(buffer_sv);
+         EXTEND(SP, 1);
+         PUSHs(sv_2mortal(newSViv(result)));
+       }
+       ST(1) = buffer_sv;
+       SvSETMAGIC(ST(1));
+
+void
+i_io_read2(ig, size)
+       Imager::IO ig
+       STRLEN size
+      PREINIT:
+       SV *buffer_sv;
+        void *buffer;
+       ssize_t result;
+      PPCODE:
+        if (size == 0)
+         croak("size zero in call to read2()");
+       buffer_sv = newSV(size);
+       buffer = SvGROW(buffer_sv, size+1);
+        result = i_io_read(ig, buffer, size);
+        if (result > 0) {
+         SvCUR_set(buffer_sv, result);
+         *SvEND(buffer_sv) = '\0';
+         SvPOK_only(buffer_sv);
+         EXTEND(SP, 1);
+         PUSHs(sv_2mortal(buffer_sv));
+       }
+       else {
+          /* discard it */
+         SvREFCNT_dec(buffer_sv);
+        }
+
+void
+i_io_gets(ig, size = 8192, eol = NEWLINE)
+       Imager::IO ig
+       STRLEN size
+       int eol
+      PREINIT:
+       SV *buffer_sv;
+        void *buffer;
+       ssize_t result;
+      PPCODE:
+        if (size < 2)
+         croak("size too small in call to gets()");
+       buffer_sv = sv_2mortal(newSV(size+1));
+       buffer = SvPVX(buffer_sv);
+        result = i_io_gets(ig, buffer, size+1, eol);
+        if (result > 0) {
+         SvCUR_set(buffer_sv, result);
+         *SvEND(buffer_sv) = '\0';
+         SvPOK_only(buffer_sv);
+         EXTEND(SP, 1);
+         PUSHs(buffer_sv);
+       }
+
+IV
+i_io_write(ig, data_sv)
+       Imager::IO ig
+       SV *data_sv
+      PREINIT:
+        void *data;
+       STRLEN size;
+      CODE:
+#ifdef SvUTF8
+        if (SvUTF8(data_sv)) {
+         data_sv = sv_2mortal(newSVsv(data_sv));
+          /* yes, we want this to croak() if the SV can't be downgraded */
+         sv_utf8_downgrade(data_sv, FALSE);
+       }
+#endif        
+       data = SvPV(data_sv, size);
+        RETVAL = i_io_write(ig, data, size);
+      OUTPUT:
+       RETVAL
+
+void
+i_io_dump(ig, flags = I_IO_DUMP_DEFAULT)
+       Imager::IO ig
+       int flags
+
+bool
+i_io_set_buffered(ig, flag = 1)
+       Imager::IO ig
+       int flag
+
+bool
+i_io_is_buffered(ig)
+       Imager::IO ig
+
+bool
+i_io_eof(ig)
+       Imager::IO ig
+
+bool
+i_io_error(ig)
+       Imager::IO ig
+
 MODULE = Imager                PACKAGE = Imager
 
 PROTOTYPES: ENABLE
@@ -3780,4 +3878,4 @@ i_int_hlines_CLONE_SKIP(cls)
 
 BOOT:
         PERL_SET_GLOBAL_CALLBACKS;
-       PERL_PL_SET_GLOBAL_CALLBACKS;
\ No newline at end of file
+       PERL_PL_SET_GLOBAL_CALLBACKS;
index eaf9a5d..d4341b4 100644 (file)
@@ -105,7 +105,7 @@ wiol_fill_input_buffer(j_decompress_ptr cinfo) {
   
   mm_log((1,"wiol_fill_input_buffer(cinfo %p)\n", cinfo));
   
-  nbytes = src->data->readcb(src->data, src->buffer, JPGS);
+  nbytes = i_io_read(src->data, src->buffer, JPGS);
   
   if (nbytes <= 0) { /* Insert a fake EOI marker */
     src->pub.next_input_byte = fake_eoi;
@@ -219,7 +219,7 @@ wiol_empty_output_buffer(j_compress_ptr cinfo) {
   */
 
   mm_log((1,"wiol_empty_output_buffer(cinfo %p)\n", cinfo));
-  rc = dest->data->writecb(dest->data, dest->buffer, JPGS);
+  rc = i_io_write(dest->data, dest->buffer, JPGS);
 
   if (rc != JPGS) { /* XXX: Should raise some jpeg error */
     myfree(dest->buffer);
@@ -238,11 +238,11 @@ wiol_term_destination (j_compress_ptr cinfo) {
   /* yes, this needs to flush the buffer */
   /* needs error handling */
 
-  if (dest->data->writecb(dest->data, dest->buffer, nbytes) != nbytes) {
+  if (i_io_write(dest->data, dest->buffer, nbytes) != nbytes) {
     myfree(dest->buffer);
     ERREXIT(cinfo, JERR_FILE_WRITE);
   }
-  
+
   if (dest != NULL) myfree(dest->buffer);
 }
 
@@ -721,7 +721,8 @@ i_writejpeg_wiol(i_img *im, io_glue *ig, int qfactor) {
 
   jpeg_destroy_compress(&cinfo);
 
-  ig->closecb(ig);
+  if (i_io_close(ig))
+    return 0;
 
   return(1);
 }
index 2ca080c..a88554d 100644 (file)
@@ -11,7 +11,7 @@ init_log("testout/t101jpeg.log",1);
 $Imager::formats{"jpeg"}
   or plan skip_all => "no jpeg support";
 
-plan tests => 101;
+plan tests => 103;
 
 my $green=i_color_new(0,255,0,255);
 my $blue=i_color_new(0,0,255,255);
@@ -55,7 +55,9 @@ ok($diff < 10000, "difference between original and jpeg within bounds");
 # write failure test
 open FH, "< testout/t101.jpg" or die "Cannot open testout/t101.jpg: $!";
 binmode FH;
-ok(!$imoo->write(fd=>fileno(FH), type=>'jpeg'), 'failure handling');
+my $io = Imager::io_new_fd(fileno(FH));
+$io->set_buffered(0);
+ok(!$imoo->write(io => $io, type=>'jpeg'), 'failure handling');
 close FH;
 print "# ",$imoo->errstr,"\n";
 
@@ -324,7 +326,8 @@ SKIP:
   }
   my $data;
   ok($im->write(data => \$data, type=>'jpeg', jpegquality => 100), 
-     "write big file to ensure wiol_empty_output_buffer is called");
+     "write big file to ensure wiol_empty_output_buffer is called")
+    or print "# ", $im->errstr, "\n";
   
   # code coverage - write failure path in wiol_empty_output_buffer
   ok(!$im->write(callback => sub { return },
@@ -432,3 +435,16 @@ SKIP:
 
   is_image($rdprog, $norm, "prog vs norm should be the same image");
 }
+
+{ # check close failures are handled correctly
+  my $im = test_image();
+  my $fail_close = sub {
+    Imager::i_push_error(0, "synthetic close failure");
+    return 0;
+  };
+  ok(!$im->write(type => "jpeg", callback => sub { 1 },
+                closecb => $fail_close),
+     "check failing close fails");
+    like($im->errstr, qr/synthetic close failure/,
+        "check error message");
+}
index d85bd5b..edd8a2b 100644 (file)
@@ -181,8 +181,8 @@ my %opts=(
 
 if ($coverage) {
     if ($Config{gccversion}) {
-       push @ARGV, 'OPTIMIZE=-ftest-coverage -fprofile-arcs';
-       #$opts{dynamic_lib} = { OTHERLDFLAGS => '-ftest-coverage -fprofile-arcs' };
+       push @ARGV, 'OPTIMIZE=-ftest-coverage -fprofile-arcs -g';
+       $opts{dynamic_lib} = { OTHERLDFLAGS => '-ftest-coverage -fprofile-arcs' };
     }
     else {
        die "Don't know the coverage C flags for your compiler\n";
index fa3218d..5fd4151 100644 (file)
@@ -34,7 +34,7 @@ static int CC2C[PNG_COLOR_MASK_PALETTE|PNG_COLOR_MASK_COLOR|PNG_COLOR_MASK_ALPHA
 static void
 wiol_read_data(png_structp png_ptr, png_bytep data, png_size_t length) {
   io_glue *ig = png_get_io_ptr(png_ptr);
-  int rc = ig->readcb(ig, data, length);
+  int rc = i_io_read(ig, data, length);
   if (rc != length) png_error(png_ptr, "Read overflow error on an iolayer source.");
 }
 
@@ -42,7 +42,7 @@ static void
 wiol_write_data(png_structp png_ptr, png_bytep data, png_size_t length) {
   int rc;
   io_glue *ig = png_get_io_ptr(png_ptr);
-  rc = ig->writecb(ig, data, length);
+  rc = i_io_write(ig, data, length);
   if (rc != length) png_error(png_ptr, "Write error on an iolayer source.");
 }
 
@@ -177,7 +177,8 @@ i_writepng_wiol(i_img *im, io_glue *ig) {
 
   png_destroy_write_struct(&png_ptr, &info_ptr);
 
-  ig->closecb(ig);
+  if (i_io_close(ig))
+    return 0;
 
   return(1);
 }
index 9dbc399..4077a99 100644 (file)
@@ -2,7 +2,7 @@
 use strict;
 use Imager qw(:all);
 use Test::More;
-use Imager::Test qw(test_image_raw);
+use Imager::Test qw(test_image_raw test_image);
 
 -d "testout" or mkdir "testout";
 
@@ -11,7 +11,7 @@ init_log("testout/t102png.log",1);
 $Imager::formats{"png"}
   or plan skip_all => "No png support";
 
-plan tests => 33;
+plan tests => 35;
 
 my $green  = i_color_new(0,   255, 0,   255);
 my $blue   = i_color_new(0,   0,   255, 255);
@@ -147,6 +147,19 @@ EOS
      'test write_multi() callback failure');
 }
 
+{ # check close failures are handled correctly
+  my $im = test_image();
+  my $fail_close = sub {
+    Imager::i_push_error(0, "synthetic close failure");
+    return 0;
+  };
+  ok(!$im->write(type => "png", callback => sub { 1 },
+                closecb => $fail_close),
+     "check failing close fails");
+    like($im->errstr, qr/synthetic close failure/,
+        "check error message");
+}
+
 {
   ok(grep($_ eq 'png', Imager->read_types), "check png in read types");
   ok(grep($_ eq 'png', Imager->write_types), "check png in write types");
index d8d9be3..077388a 100644 (file)
@@ -182,7 +182,7 @@ i_readsgi_wiol(io_glue *ig, int partial) {
   mm_log((1,"i_readsgi(ig %p, partial %d)\n", ig, partial));
   i_clear_error();
 
-  if (ig->readcb(ig, headbuf, 512) != 512) {
+  if (i_io_read(ig, headbuf, 512) != 512) {
     i_push_error(errno, "SGI image: could not read header");
     return NULL;
   }
@@ -375,7 +375,7 @@ read_rgb_8_verbatim(i_img *img, io_glue *ig, rgb_header const *header) {
     for(y = 0; y < height; y++) {
       int x;
       
-      if (ig->readcb(ig, databuf, width) != width) {
+      if (i_io_read(ig, databuf, width) != width) {
        i_push_error(0, "SGI image: cannot read image data");
        i_img_destroy(img);
        myfree(linebuf);
@@ -436,7 +436,7 @@ read_rle_tables(io_glue *ig, i_img *img,
   length_tab = mymalloc(height*channels*sizeof(unsigned long));
     
     /* Read offset table */
-  if (ig->readcb(ig, databuf, height * channels * 4) != height * channels * 4) {
+  if (i_io_read(ig, databuf, height * channels * 4) != height * channels * 4) {
     i_push_error(0, "SGI image: short read reading RLE start table");
     goto ErrorReturn;
   }
@@ -447,7 +447,7 @@ read_rle_tables(io_glue *ig, i_img *img,
 
 
   /* Read length table */
-  if (ig->readcb(ig, databuf, height*channels*4) != height*channels*4) {
+  if (i_io_read(ig, databuf, height*channels*4) != height*channels*4) {
     i_push_error(0, "SGI image: short read reading RLE length table");
     goto ErrorReturn;
   }
@@ -520,11 +520,11 @@ read_rgb_8_rle(i_img *img, io_glue *ig, rgb_header const *header) {
       int pixels_left = width;
       i_sample_t sample;
       
-      if (ig->seekcb(ig, start_tab[ci], SEEK_SET) != start_tab[ci]) {
+      if (i_io_seek(ig, start_tab[ci], SEEK_SET) != start_tab[ci]) {
        i_push_error(0, "SGI image: cannot seek to RLE data");
        goto ErrorReturn;
       }
-      if (ig->readcb(ig, databuf, datalen) != datalen) {
+      if (i_io_read(ig, databuf, datalen) != datalen) {
        i_push_error(0, "SGI image: cannot read RLE data");
        goto ErrorReturn;
       }
@@ -656,7 +656,7 @@ read_rgb_16_verbatim(i_img *img, io_glue *ig, rgb_header const *header) {
     for(y = 0; y < height; y++) {
       int x;
       
-      if (ig->readcb(ig, databuf, width*2) != width*2) {
+      if (i_io_read(ig, databuf, width*2) != width*2) {
        i_push_error(0, "SGI image: cannot read image data");
        i_img_destroy(img);
        myfree(linebuf);
@@ -738,11 +738,11 @@ read_rgb_16_rle(i_img *img, io_glue *ig, rgb_header const *header) {
        i_push_error(0, "SGI image: invalid RLE length value for BPC=2");
        goto ErrorReturn;
       }
-      if (ig->seekcb(ig, start_tab[ci], SEEK_SET) != start_tab[ci]) {
+      if (i_io_seek(ig, start_tab[ci], SEEK_SET) != start_tab[ci]) {
        i_push_error(0, "SGI image: cannot seek to RLE data");
        goto ErrorReturn;
       }
-      if (ig->readcb(ig, databuf, datalen) != datalen) {
+      if (i_io_read(ig, databuf, datalen) != datalen) {
        i_push_error(0, "SGI image: cannot read RLE data");
        goto ErrorReturn;
       }
@@ -912,7 +912,7 @@ write_sgi_8_verb(i_img *img, io_glue *ig) {
   for (c = 0; c < img->channels; ++c) {
     for (y = img->ysize - 1; y >= 0; --y) {
       i_gsamp(img, 0, width, y, linebuf, &c, 1);
-      if (ig->writecb(ig, linebuf, width) != width) {
+      if (i_io_write(ig, linebuf, width) != width) {
        i_push_error(errno, "SGI image: error writing image data");
        myfree(linebuf);
        return 0;
@@ -921,6 +921,9 @@ write_sgi_8_verb(i_img *img, io_glue *ig) {
   }
   myfree(linebuf);
 
+  if (i_io_close(ig))
+    return 0;
+
   return 1;
 }
 
@@ -1011,7 +1014,7 @@ write_sgi_8_rle(i_img *img, io_glue *ig) {
       store_32(lengths + offset_pos, comp_size);
       offset_pos += 4;
       current_offset += comp_size;
-      if (ig->writecb(ig, comp_buf, comp_size) != comp_size) {
+      if (i_io_write(ig, comp_buf, comp_size) != comp_size) {
        i_push_error(errno, "SGI image: error writing RLE data");
        goto Error;
       }
@@ -1033,6 +1036,9 @@ write_sgi_8_rle(i_img *img, io_glue *ig) {
   myfree(comp_buf);
   myfree(linebuf);
 
+  if (i_io_close(ig))
+    return 0;
+
   return 1;
 
  Error:
@@ -1061,7 +1067,7 @@ write_sgi_16_verb(i_img *img, io_glue *ig) {
        unsigned short samp16 = SampleFTo16(linebuf[x]);
        store_16(outp, samp16);
       }
-      if (ig->writecb(ig, encbuf, width * 2) != width * 2) {
+      if (i_io_write(ig, encbuf, width * 2) != width * 2) {
        i_push_error(errno, "SGI image: error writing image data");
        myfree(linebuf);
        myfree(encbuf);
@@ -1072,6 +1078,9 @@ write_sgi_16_verb(i_img *img, io_glue *ig) {
   myfree(linebuf);
   myfree(encbuf);
 
+  if (i_io_close(ig))
+    return 0;
+
   return 1;
 }
 
@@ -1171,7 +1180,7 @@ write_sgi_16_rle(i_img *img, io_glue *ig) {
       store_32(lengths + offset_pos, comp_size);
       offset_pos += 4;
       current_offset += comp_size;
-      if (ig->writecb(ig, comp_buf, comp_size) != comp_size) {
+      if (i_io_write(ig, comp_buf, comp_size) != comp_size) {
        i_push_error(errno, "SGI image: error writing RLE data");
        goto Error;
       }
@@ -1194,6 +1203,9 @@ write_sgi_16_rle(i_img *img, io_glue *ig) {
   myfree(linebuf);
   myfree(sampbuf);
 
+  if (i_io_close(ig))
+    return 0;
+
   return 1;
 
  Error:
index 879412b..bcbf4e5 100644 (file)
@@ -1,7 +1,7 @@
 #!perl -w
 use strict;
 use Imager;
-use Test::More tests => 55;
+use Test::More tests => 57;
 use Imager::Test qw(test_image test_image_16 is_image);
 use IO::Seekable;
 
@@ -161,14 +161,38 @@ Imager::init_log('testout/20write.log', 2);
     );
   for my $test (@tests) {
     my ($im, $limit, $expected_msg, $desc) = @$test;
-    my ($writecb, $seekcb) = limited_write($limit);
-    ok(!$im->write(type => 'sgi', writecb => $writecb,
-                  seekcb => $seekcb, maxbuffer => 1),
+    my $io = limited_write_io($limit);
+    ok(!$im->write(type => 'sgi', io => $io),
        "write should fail - $desc");
     is($im->errstr, "$expected_msg: limit reached", "check error - $desc");
   }
 }
 
+
+{ # check close failures are handled correctly
+  my $im = test_image();
+  my $fail_close = sub {
+    Imager::i_push_error(0, "synthetic close failure");
+    return 0;
+  };
+  ok(!$im->write(type => "sgi", callback => sub { 1 },
+                closecb => $fail_close),
+     "check failing close fails");
+    like($im->errstr, qr/synthetic close failure/,
+        "check error message");
+}
+
+sub limited_write_io {
+  my ($limit) = @_;
+
+  my ($writecb, $seekcb) = limited_write($limit);
+
+  my $io = Imager::io_new_cb($writecb, undef, $seekcb, undef, 1);
+  $io->set_buffered(0);
+
+  return $io;
+}
+
 sub limited_write {
   my ($limit) = @_;
 
index 07e363c..832f38d 100644 (file)
@@ -234,7 +234,7 @@ static
 toff_t
 comp_seek(thandle_t h, toff_t o, int w) {
   io_glue *ig = (io_glue*)h;
-  return (toff_t) ig->seekcb(ig, o, w);
+  return (toff_t) i_io_seek(ig, o, w);
 }
 
 /*
@@ -268,6 +268,21 @@ comp_munmap(thandle_t h, tdata_t p, toff_t off) {
   /* do nothing */
 }
 
+static tsize_t
+comp_read(thandle_t h, tdata_t p, tsize_t size) {
+  return i_io_read((io_glue *)h, p, size);
+}
+
+static tsize_t
+comp_write(thandle_t h, tdata_t p, tsize_t size) {
+  return i_io_write((io_glue *)h, p, size);
+}
+
+static int
+comp_close(thandle_t h) {
+  return i_io_close((io_glue *)h);
+}
+
 static i_img *read_one_tiff(TIFF *tif, int allow_incomplete) {
   i_img *im;
   uint32 width, height;
@@ -547,13 +562,13 @@ i_readtiff_wiol(io_glue *ig, int allow_incomplete, int page) {
   tif = TIFFClientOpen("(Iolayer)", 
                       "rm", 
                       (thandle_t) ig,
-                      (TIFFReadWriteProc) ig->readcb,
-                      (TIFFReadWriteProc) ig->writecb,
-                      (TIFFSeekProc) comp_seek,
-                      (TIFFCloseProc) ig->closecb,
-                      ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc,
-                      (TIFFMapFileProc) comp_mmap,
-                      (TIFFUnmapFileProc) comp_munmap);
+                      comp_read,
+                      comp_write,
+                      comp_seek,
+                      comp_close,
+                      sizeproc,
+                      comp_mmap,
+                      comp_munmap);
   
   if (!tif) {
     mm_log((1, "i_readtiff_wiol: Unable to open tif file\n"));
@@ -597,7 +612,6 @@ i_readtiff_multi_wiol(io_glue *ig, int *count) {
   TIFFErrorHandler old_warn_handler;
   i_img **results = NULL;
   int result_alloc = 0;
-  int dirnum = 0;
 
   i_clear_error();
   old_handler = TIFFSetErrorHandler(error_handler);
@@ -613,13 +627,13 @@ i_readtiff_multi_wiol(io_glue *ig, int *count) {
   tif = TIFFClientOpen("(Iolayer)", 
                       "rm", 
                       (thandle_t) ig,
-                      (TIFFReadWriteProc) ig->readcb,
-                      (TIFFReadWriteProc) ig->writecb,
-                      (TIFFSeekProc) comp_seek,
-                      (TIFFCloseProc) ig->closecb,
-                      ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc,
-                      (TIFFMapFileProc) comp_mmap,
-                      (TIFFUnmapFileProc) comp_munmap);
+                      comp_read,
+                      comp_write,
+                      comp_seek,
+                      comp_close,
+                      sizeproc,
+                      comp_mmap,
+                      comp_munmap);
   
   if (!tif) {
     mm_log((1, "i_readtiff_wiol: Unable to open tif file\n"));
@@ -1330,13 +1344,13 @@ i_writetiff_multi_wiol(io_glue *ig, i_img **imgs, int count) {
   tif = TIFFClientOpen("No name", 
                       "wm",
                       (thandle_t) ig, 
-                      (TIFFReadWriteProc) ig->readcb,
-                      (TIFFReadWriteProc) ig->writecb,
-                      (TIFFSeekProc)      comp_seek,
-                      (TIFFCloseProc)     ig->closecb
-                      ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc,
-                      (TIFFMapFileProc)   comp_mmap,
-                      (TIFFUnmapFileProc) comp_munmap);
+                      comp_read,
+                      comp_write,
+                      comp_seek,
+                      comp_close
+                      sizeproc,
+                      comp_mmap,
+                      comp_munmap);
   
 
 
@@ -1365,6 +1379,9 @@ i_writetiff_multi_wiol(io_glue *ig, i_img **imgs, int count) {
   TIFFSetErrorHandler(old_handler);
   (void) TIFFClose(tif);
 
+  if (i_io_close(ig))
+    return 0;
+
   return 1;
 }
 
@@ -1398,13 +1415,13 @@ i_writetiff_multi_wiol_faxable(io_glue *ig, i_img **imgs, int count, int fine) {
   tif = TIFFClientOpen("No name", 
                       "wm",
                       (thandle_t) ig, 
-                      (TIFFReadWriteProc) ig->readcb,
-                      (TIFFReadWriteProc) ig->writecb,
-                      (TIFFSeekProc)      comp_seek,
-                      (TIFFCloseProc)     ig->closecb
-                      ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc,
-                      (TIFFMapFileProc)   comp_mmap,
-                      (TIFFUnmapFileProc) comp_munmap);
+                      comp_read,
+                      comp_write,
+                      comp_seek,
+                      comp_close
+                      sizeproc,
+                      comp_mmap,
+                      comp_munmap);
   
 
 
@@ -1433,6 +1450,9 @@ i_writetiff_multi_wiol_faxable(io_glue *ig, i_img **imgs, int count, int fine) {
   (void) TIFFClose(tif);
   TIFFSetErrorHandler(old_handler);
 
+  if (i_io_close(ig))
+    return 0;
+
   return 1;
 }
 
@@ -1461,13 +1481,13 @@ i_writetiff_wiol(i_img *img, io_glue *ig) {
   tif = TIFFClientOpen("No name", 
                       "wm",
                       (thandle_t) ig, 
-                      (TIFFReadWriteProc) ig->readcb,
-                      (TIFFReadWriteProc) ig->writecb,
-                      (TIFFSeekProc)      comp_seek,
-                      (TIFFCloseProc)     ig->closecb
-                      ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc,
-                      (TIFFMapFileProc)   comp_mmap,
-                      (TIFFUnmapFileProc) comp_munmap);
+                      comp_read,
+                      comp_write,
+                      comp_seek,
+                      comp_close
+                      sizeproc,
+                      comp_mmap,
+                      comp_munmap);
   
 
 
@@ -1487,6 +1507,9 @@ i_writetiff_wiol(i_img *img, io_glue *ig) {
   (void) TIFFClose(tif);
   TIFFSetErrorHandler(old_handler);
 
+  if (i_io_close(ig))
+    return 0;
+
   return 1;
 }
 
@@ -1522,13 +1545,13 @@ i_writetiff_wiol_faxable(i_img *im, io_glue *ig, int fine) {
   tif = TIFFClientOpen("No name", 
                       "wm",
                       (thandle_t) ig, 
-                      (TIFFReadWriteProc) ig->readcb,
-                      (TIFFReadWriteProc) ig->writecb,
-                      (TIFFSeekProc)      comp_seek,
-                      (TIFFCloseProc)     ig->closecb
-                      ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc,
-                      (TIFFMapFileProc)   comp_mmap,
-                      (TIFFUnmapFileProc) comp_munmap);
+                      comp_read,
+                      comp_write,
+                      comp_seek,
+                      comp_close
+                      sizeproc,
+                      comp_mmap,
+                      comp_munmap);
   
 
 
@@ -1548,6 +1571,9 @@ i_writetiff_wiol_faxable(i_img *im, io_glue *ig, int fine) {
   (void) TIFFClose(tif);
   TIFFSetErrorHandler(old_handler);
 
+  if (i_io_close(ig))
+    return 0;
+
   return 1;
 }
 
index c93cfc0..40d15ae 100644 (file)
@@ -1,6 +1,6 @@
 #!perl -w
 use strict;
-use Test::More tests => 235;
+use Test::More tests => 239;
 use Imager qw(:all);
 use Imager::Test qw(is_image is_image_similar test_image test_image_16 test_image_double test_image_raw);
 
@@ -204,14 +204,17 @@ sub io_writer {
 }
 sub io_reader {
   my ($size, $maxread) = @_;
-  #print "io_reader($size, $maxread) pos $seekpos\n";
+  print "# io_reader($size, $maxread) pos $seekpos\n";
+  if ($seekpos + $maxread > length $work) {
+    $maxread = length($work) - $seekpos;
+  }
   my $out = substr($work, $seekpos, $maxread);
   $seekpos += length $out;
   $out;
 }
 sub io_reader2 {
   my ($size, $maxread) = @_;
-  #print "io_reader2($size, $maxread) pos $seekpos\n";
+  print "# io_reader2($size, $maxread) pos $seekpos\n";
   my $out = substr($work, $seekpos, $size);
   $seekpos += length $out;
   $out;
@@ -219,7 +222,7 @@ sub io_reader2 {
 use IO::Seekable;
 sub io_seeker {
   my ($offset, $whence) = @_;
-  #print "io_seeker($offset, $whence)\n";
+  print "# io_seeker($offset, $whence)\n";
   if ($whence == SEEK_SET) {
     $seekpos = $offset;
   }
@@ -286,6 +289,44 @@ open D3, ">testout/d3.tiff" or die;
 print D3 $work;
 close D3;
 
+
+{ # check close failures are handled correctly
+  { # single image
+    my $im = test_image();
+    my $fail_close = sub {
+      Imager::i_push_error(0, "synthetic close failure");
+      return 0;
+    };
+    $work = '';
+    $seekpos = 0;
+    ok(!$im->write(type => "tiff",
+                  readcb => \&io_reader,
+                  writecb => \&io_writer,
+                  seekcb => \&io_seeker,
+                  closecb => $fail_close),
+       "check failing close fails");
+    like($im->errstr, qr/synthetic close failure/,
+        "check error message");
+  }
+  { # multiple images
+    my $im = test_image();
+    my $fail_close = sub {
+      Imager::i_push_error(0, "synthetic close failure");
+      return 0;
+    };
+    $work = '';
+    $seekpos = 0;
+    ok(!Imager->write_multi({type => "tiff",
+                            readcb => \&io_reader,
+                            writecb => \&io_writer,
+                            seekcb => \&io_seeker,
+                            closecb => $fail_close}, $im, $im),
+       "check failing close fails");
+    like(Imager->errstr, qr/synthetic close failure/,
+        "check error message");
+  }
+}
+
 # multi-image write/read
 my @imgs;
 push(@imgs, map $ooim->copy(), 1..3);
@@ -381,19 +422,19 @@ like($warning, qr/[Uu]nknown field with tag 28712/,
     or skip "Cannot open testout/t106_empty.tif for reading", 8;
   binmode TIFF;
   my $im = Imager->new(xsize=>100, ysize=>100);
-  ok(!$im->write(fh => \*TIFF, type=>'tiff'),
+  ok(!$im->write(fh => \*TIFF, type=>'tiff', buffered => 0),
      "fail to write to read only handle");
   cmp_ok($im->errstr, '=~', 'Could not create TIFF object: Error writing TIFF header: write\(\)',
         "check error message");
-  ok(!Imager->write_multi({ type => 'tiff', fh => \*TIFF }, $im),
+  ok(!Imager->write_multi({ type => 'tiff', fh => \*TIFF, buffered => 0 }, $im),
      "fail to write multi to read only handle");
   cmp_ok(Imager->errstr, '=~', 'Could not create TIFF object: Error writing TIFF header: write\(\)',
         "check error message");
-  ok(!$im->write(fh => \*TIFF, type=>'tiff', class=>'fax'),
+  ok(!$im->write(fh => \*TIFF, type=>'tiff', class=>'fax', buffered => 0),
      "fail to write to read only handle (fax)");
   cmp_ok($im->errstr, '=~', 'Could not create TIFF object: Error writing TIFF header: write\(\)',
         "check error message");
-  ok(!Imager->write_multi({ type => 'tiff', fh => \*TIFF, class=>'fax' }, $im),
+  ok(!Imager->write_multi({ type => 'tiff', fh => \*TIFF, class=>'fax', buffered => 0 }, $im),
      "fail to write multi to read only handle (fax)");
   cmp_ok(Imager->errstr, '=~', 'Could not create TIFF object: Error writing TIFF header: write\(\)',
         "check error message");
index a3844b8..2982554 100644 (file)
Binary files a/TIFF/testimg/grey32.tif and b/TIFF/testimg/grey32.tif differ
index ad5e259..c0c389a 100644 (file)
@@ -189,8 +189,10 @@ sub make_func_list {
   my $in_struct;
   while (<FUNCS>) {
     /^typedef struct/ && ++$in_struct;
-    if ($in_struct && /\(\*f_(i_\w+)/) {
-      push @funcs, $1;
+    if ($in_struct && /\(\*f_(io?_\w+)/) {
+      my $name = $1;
+      $name =~ s/_imp$//;
+      push @funcs, $name;
     }
     if (/^\} im_ext_funcs;$/) {
       $in_struct
diff --git a/bmp.c b/bmp.c
index 9cf7615..ed134b2 100644 (file)
--- a/bmp.c
+++ b/bmp.c
@@ -76,7 +76,6 @@ Never compresses the image.
 */
 int
 i_writebmp_wiol(i_img *im, io_glue *ig) {
-  io_glue_commit_types(ig);
   i_clear_error();
 
   /* pick a format */
@@ -120,7 +119,6 @@ i_readbmp_wiol(io_glue *ig, int allow_incomplete) {
 
   mm_log((1, "i_readbmp_wiol(ig %p)\n", ig));
   
-  io_glue_commit_types(ig);
   i_clear_error();
 
   if (!read_packed(ig, "CCVvvVVV!V!vvVVVVVV", &b_magic, &m_magic, &filesize, 
@@ -237,7 +235,7 @@ read_packed(io_glue *ig, char *format, ...) {
 
     switch (code) {
     case 'v':
-      if (ig->readcb(ig, buf, 2) != 2)
+      if (i_io_read(ig, buf, 2) != 2)
        return 0;
       work = buf[0] + ((i_packed_t)buf[1] << 8);
       if (shrieking)
@@ -247,7 +245,7 @@ read_packed(io_glue *ig, char *format, ...) {
       break;
 
     case 'V':
-      if (ig->readcb(ig, buf, 4) != 4)
+      if (i_io_read(ig, buf, 4) != 4)
        return 0;
       work = buf[0] + (buf[1] << 8) + ((i_packed_t)buf[2] << 16) + ((i_packed_t)buf[3] << 24);
       if (shrieking)
@@ -257,19 +255,19 @@ read_packed(io_glue *ig, char *format, ...) {
       break;
 
     case 'C':
-      if (ig->readcb(ig, buf, 1) != 1)
+      if (i_io_read(ig, buf, 1) != 1)
        return 0;
       *p = buf[0];
       break;
 
     case 'c':
-      if (ig->readcb(ig, buf, 1) != 1)
+      if (i_io_read(ig, buf, 1) != 1)
        return 0;
       *p = (char)buf[0];
       break;
       
     case '3': /* extension - 24-bit number */
-      if (ig->readcb(ig, buf, 3) != 3)
+      if (i_io_read(ig, buf, 3) != 3)
         return 0;
       *p = buf[0] + (buf[1] << 8) + ((i_packed_t)buf[2] << 16);
       break;
@@ -306,7 +304,7 @@ write_packed(io_glue *ig, char *format, ...) {
     case 'v':
       buf[0] = i & 255;
       buf[1] = i / 256;
-      if (ig->writecb(ig, buf, 2) == -1)
+      if (i_io_write(ig, buf, 2) == -1)
        return 0;
       break;
 
@@ -315,14 +313,14 @@ write_packed(io_glue *ig, char *format, ...) {
       buf[1] = (i >> 8) & 0xFF;
       buf[2] = (i >> 16) & 0xFF;
       buf[3] = (i >> 24) & 0xFF;
-      if (ig->writecb(ig, buf, 4) == -1)
+      if (i_io_write(ig, buf, 4) == -1)
        return 0;
       break;
 
     case 'C':
     case 'c':
       buf[0] = i & 0xFF;
-      if (ig->writecb(ig, buf, 1) == -1)
+      if (i_io_write(ig, buf, 1) == -1)
        return 0;
       break;
 
@@ -491,7 +489,7 @@ write_1bit_data(io_glue *ig, i_img *im) {
     if (mask != 0x80) {
       *out++ = byte;
     }
-    if (ig->writecb(ig, packed, line_size) < 0) {
+    if (i_io_write(ig, packed, line_size) < 0) {
       myfree(packed);
       myfree(line);
       i_push_error(0, "writing 1 bit/pixel packed data");
@@ -501,7 +499,8 @@ write_1bit_data(io_glue *ig, i_img *im) {
   myfree(packed);
   myfree(line);
 
-  ig->closecb(ig);
+  if (i_io_close(ig))
+    return 0;
 
   return 1;
 }
@@ -550,7 +549,7 @@ write_4bit_data(io_glue *ig, i_img *im) {
     for (x = 0; x < im->xsize; x += 2) {
       *out++ = (line[x] << 4) + line[x+1];
     }
-    if (ig->writecb(ig, packed, line_size) < 0) {
+    if (i_io_write(ig, packed, line_size) < 0) {
       myfree(packed);
       myfree(line);
       i_push_error(0, "writing 4 bit/pixel packed data");
@@ -560,7 +559,8 @@ write_4bit_data(io_glue *ig, i_img *im) {
   myfree(packed);
   myfree(line);
 
-  ig->closecb(ig);
+  if (i_io_close(ig))
+    return 0;
 
   return 1;
 }
@@ -598,7 +598,7 @@ write_8bit_data(io_glue *ig, i_img *im) {
   
   for (y = im->ysize-1; y >= 0; --y) {
     i_gpal(im, 0, im->xsize, y, line);
-    if (ig->writecb(ig, line, line_size) < 0) {
+    if (i_io_write(ig, line, line_size) < 0) {
       myfree(line);
       i_push_error(0, "writing 8 bit/pixel packed data");
       return 0;
@@ -606,7 +606,8 @@ write_8bit_data(io_glue *ig, i_img *im) {
   }
   myfree(line);
 
-  ig->closecb(ig);
+  if (i_io_close(ig))
+    return 0;
 
   return 1;
 }
@@ -652,7 +653,7 @@ write_24bit_data(io_glue *ig, i_img *im) {
       samplep[0] = tmp;
       samplep += 3;
     }
-    if (ig->writecb(ig, samples, line_size) < 0) {
+    if (i_io_write(ig, samples, line_size) < 0) {
       i_push_error(0, "writing image data");
       myfree(samples);
       return 0;
@@ -660,7 +661,8 @@ write_24bit_data(io_glue *ig, i_img *im) {
   }
   myfree(samples);
 
-  ig->closecb(ig);
+  if (i_io_close(ig))
+    return 0;
 
   return 1;
 }
@@ -773,7 +775,7 @@ read_1bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used,
        rare */
     char buffer;
     while (base_offset < offbits) {
-      if (ig->readcb(ig, &buffer, 1) != 1) {
+      if (i_io_read(ig, &buffer, 1) != 1) {
         i_img_destroy(im);
         i_push_error(0, "failed skipping to image data offset");
         return NULL;
@@ -787,7 +789,7 @@ read_1bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used,
   packed = mymalloc(line_size); /* checked 29jun05 tonyc */
   line = mymalloc(xsize+8); /* checked 29jun05 tonyc */
   while (y != lasty) {
-    if (ig->readcb(ig, packed, line_size) != line_size) {
+    if (i_io_read(ig, packed, line_size) != line_size) {
       myfree(packed);
       myfree(line);
       if (allow_incomplete) {
@@ -890,7 +892,7 @@ read_4bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used,
        rare */
     char buffer;
     while (base_offset < offbits) {
-      if (ig->readcb(ig, &buffer, 1) != 1) {
+      if (i_io_read(ig, &buffer, 1) != 1) {
         i_img_destroy(im);
         i_push_error(0, "failed skipping to image data offset");
         return NULL;
@@ -908,7 +910,7 @@ read_4bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used,
   if (compression == BI_RGB) {
     i_tags_add(&im->tags, "bmp_compression_name", 0, "BI_RGB", -1, 0);
     while (y != lasty) {
-      if (ig->readcb(ig, packed, line_size) != line_size) {
+      if (i_io_read(ig, packed, line_size) != line_size) {
        myfree(packed);
        myfree(line);
         if (allow_incomplete) {
@@ -943,7 +945,7 @@ read_4bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used,
     x = 0;
     while (1) {
       /* there's always at least 2 bytes in a sequence */
-      if (ig->readcb(ig, packed, 2) != 2) {
+      if (i_io_read(ig, packed, 2) != 2) {
         myfree(packed);
         myfree(line);
         if (allow_incomplete) {
@@ -988,7 +990,7 @@ read_4bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used,
           return im;
 
         case BMPRLE_DELTA:
-          if (ig->readcb(ig, packed, 2) != 2) {
+          if (i_io_read(ig, packed, 2) != 2) {
             myfree(packed);
             myfree(line);
             if (allow_incomplete) {
@@ -1018,7 +1020,7 @@ read_4bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used,
          }
           size = (count + 1) / 2;
           read_size = (size+1) / 2 * 2;
-          if (ig->readcb(ig, packed, read_size) != read_size) {
+          if (i_io_read(ig, packed, read_size) != read_size) {
             myfree(packed);
             myfree(line);
             if (allow_incomplete) {
@@ -1117,7 +1119,7 @@ read_8bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used,
        rare */
     char buffer;
     while (base_offset < offbits) {
-      if (ig->readcb(ig, &buffer, 1) != 1) {
+      if (i_io_read(ig, &buffer, 1) != 1) {
         i_img_destroy(im);
         i_push_error(0, "failed skipping to image data offset");
         return NULL;
@@ -1130,7 +1132,7 @@ read_8bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used,
   if (compression == BI_RGB) {
     i_tags_add(&im->tags, "bmp_compression_name", 0, "BI_RGB", -1, 0);
     while (y != lasty) {
-      if (ig->readcb(ig, line, line_size) != line_size) {
+      if (i_io_read(ig, line, line_size) != line_size) {
        myfree(line);
         if (allow_incomplete) {
           i_tags_setn(&im->tags, "i_incomplete", 1);
@@ -1157,7 +1159,7 @@ read_8bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used,
     x = 0;
     while (1) {
       /* there's always at least 2 bytes in a sequence */
-      if (ig->readcb(ig, packed, 2) != 2) {
+      if (i_io_read(ig, packed, 2) != 2) {
         myfree(line);
         if (allow_incomplete) {
           i_tags_setn(&im->tags, "i_incomplete", 1);
@@ -1193,7 +1195,7 @@ read_8bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used,
           return im;
 
         case BMPRLE_DELTA:
-          if (ig->readcb(ig, packed, 2) != 2) {
+          if (i_io_read(ig, packed, 2) != 2) {
             myfree(line);
             if (allow_incomplete) {
               i_tags_setn(&im->tags, "i_incomplete", 1);
@@ -1221,7 +1223,7 @@ read_8bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used,
            return NULL;
          }
           read_size = (count+1) / 2 * 2;
-          if (ig->readcb(ig, line, read_size) != read_size) {
+          if (i_io_read(ig, line, read_size) != read_size) {
             myfree(line);
             if (allow_incomplete) {
               i_tags_setn(&im->tags, "i_incomplete", 1);
@@ -1324,7 +1326,7 @@ read_direct_bmp(io_glue *ig, int xsize, int ysize, int bit_count,
     /* there's a potential "palette" after the header */
     for (i = 0; i < clr_used; ++clr_used) {
       char buf[4];
-      if (ig->readcb(ig, buf, 4) != 4) {
+      if (i_io_read(ig, buf, 4) != 4) {
         i_push_error(0, "skipping colors");
         return 0;
       }
@@ -1368,7 +1370,7 @@ read_direct_bmp(io_glue *ig, int xsize, int ysize, int bit_count,
        rare */
     char buffer;
     while (base_offset < offbits) {
-      if (ig->readcb(ig, &buffer, 1) != 1) {
+      if (i_io_read(ig, &buffer, 1) != 1) {
         i_push_error(0, "failed skipping to image data offset");
         return NULL;
       }
@@ -1418,7 +1420,7 @@ read_direct_bmp(io_glue *ig, int xsize, int ysize, int bit_count,
     }
     i_plin(im, 0, xsize, y, line);
     if (extras)
-      ig->readcb(ig, junk, extras);
+      i_io_read(ig, junk, extras);
     y += yinc;
   }
   myfree(line);
diff --git a/image.c b/image.c
index abf8676..6cb49d4 100644 (file)
--- a/image.c
+++ b/image.c
@@ -1489,194 +1489,6 @@ i_gsamp_bits_fb(i_img *im, i_img_dim l, i_img_dim r, i_img_dim y, unsigned *samp
   }
 }
 
-/*
-=back
-
-=head2 Stream reading and writing wrapper functions
-
-=over
-
-=item i_gen_reader(i_gen_read_data *info, char *buf, int length)
-
-Performs general read buffering for file readers that permit reading
-to be done through a callback.
-
-The final callback gets two parameters, a I<need> value, and a I<want>
-value, where I<need> is the amount of data that the file library needs
-to read, and I<want> is the amount of space available in the buffer
-maintained by these functions.
-
-This means if you need to read from a stream that you don't know the
-length of, you can return I<need> bytes, taking the performance hit of
-possibly expensive callbacks (eg. back to perl code), or if you are
-reading from a stream where it doesn't matter if some data is lost, or
-if the total length of the stream is known, you can return I<want>
-bytes.
-
-=cut 
-*/
-
-int
-i_gen_reader(i_gen_read_data *gci, char *buf, int length) {
-  int total;
-
-  if (length < gci->length - gci->cpos) {
-    /* simplest case */
-    memcpy(buf, gci->buffer+gci->cpos, length);
-    gci->cpos += length;
-    return length;
-  }
-  
-  total = 0;
-  memcpy(buf, gci->buffer+gci->cpos, gci->length-gci->cpos);
-  total  += gci->length - gci->cpos;
-  length -= gci->length - gci->cpos;
-  buf    += gci->length - gci->cpos;
-  if (length < (int)sizeof(gci->buffer)) {
-    int did_read;
-    int copy_size;
-    while (length
-          && (did_read = (gci->cb)(gci->userdata, gci->buffer, length, 
-                                   sizeof(gci->buffer))) > 0) {
-      gci->cpos = 0;
-      gci->length = did_read;
-
-      copy_size = i_min(length, gci->length);
-      memcpy(buf, gci->buffer, copy_size);
-      gci->cpos += copy_size;
-      buf += copy_size;
-      total += copy_size;
-      length -= copy_size;
-    }
-  }
-  else {
-    /* just read the rest - too big for our buffer*/
-    int did_read;
-    while ((did_read = (gci->cb)(gci->userdata, buf, length, length)) > 0) {
-      length -= did_read;
-      total += did_read;
-      buf += did_read;
-    }
-  }
-  return total;
-}
-
-/*
-=item i_gen_read_data_new(i_read_callback_t cb, char *userdata)
-
-For use by callback file readers to initialize the reader buffer.
-
-Allocates, initializes and returns the reader buffer.
-
-See also L<image.c/free_gen_read_data> and L<image.c/i_gen_reader>.
-
-=cut
-*/
-i_gen_read_data *
-i_gen_read_data_new(i_read_callback_t cb, char *userdata) {
-  i_gen_read_data *self = mymalloc(sizeof(i_gen_read_data));
-  self->cb = cb;
-  self->userdata = userdata;
-  self->length = 0;
-  self->cpos = 0;
-
-  return self;
-}
-
-/*
-=item i_free_gen_read_data(i_gen_read_data *)
-
-Cleans up.
-
-=cut
-*/
-void i_free_gen_read_data(i_gen_read_data *self) {
-  myfree(self);
-}
-
-/*
-=item i_gen_writer(i_gen_write_data *info, char const *data, int size)
-
-Performs write buffering for a callback based file writer.
-
-Failures are considered fatal, if a write fails then data will be
-dropped.
-
-=cut
-*/
-int 
-i_gen_writer(
-i_gen_write_data *self, 
-char const *data, 
-int size)
-{
-  if (self->filledto && self->filledto+size > self->maxlength) {
-    if (self->cb(self->userdata, self->buffer, self->filledto)) {
-      self->filledto = 0;
-    }
-    else {
-      self->filledto = 0;
-      return 0;
-    }
-  }
-  if (self->filledto+size <= self->maxlength) {
-    /* just save it */
-    memcpy(self->buffer+self->filledto, data, size);
-    self->filledto += size;
-    return 1;
-  }
-  /* doesn't fit - hand it off */
-  return self->cb(self->userdata, data, size);
-}
-
-/*
-=item i_gen_write_data_new(i_write_callback_t cb, char *userdata, int max_length)
-
-Allocates and initializes the data structure used by i_gen_writer.
-
-This should be released with L<image.c/i_free_gen_write_data>
-
-=cut
-*/
-i_gen_write_data *i_gen_write_data_new(i_write_callback_t cb, 
-                                      char *userdata, int max_length)
-{
-  i_gen_write_data *self = mymalloc(sizeof(i_gen_write_data));
-  self->cb = cb;
-  self->userdata = userdata;
-  self->maxlength = i_min(max_length, sizeof(self->buffer));
-  if (self->maxlength < 0)
-    self->maxlength = sizeof(self->buffer);
-  self->filledto = 0;
-
-  return self;
-}
-
-/*
-=item i_free_gen_write_data(i_gen_write_data *info, int flush)
-
-Cleans up the write buffer.
-
-Will flush any left-over data if I<flush> is non-zero.
-
-Returns non-zero if flush is zero or if info->cb() returns non-zero.
-
-Return zero only if flush is non-zero and info->cb() returns zero.
-ie. if it fails.
-
-=cut
-*/
-
-int i_free_gen_write_data(i_gen_write_data *info, int flush)
-{
-  int result = !flush || 
-    info->filledto == 0 ||
-    info->cb(info->userdata, info->buffer, info->filledto);
-  myfree(info);
-
-  return result;
-}
-
 struct magic_entry {
   unsigned char *magic;
   size_t magic_size;
@@ -1801,10 +1613,17 @@ i_test_format_probe(io_glue *data, int length) {
   unsigned char head[18];
   ssize_t rc;
 
-  io_glue_commit_types(data);
-  rc = data->readcb(data, head, 18);
+  rc = i_io_peekn(data, head, 18);
   if (rc == -1) return NULL;
-  data->seekcb(data, -rc, SEEK_CUR);
+#if 0
+  {
+    int i;
+    fprintf(stderr, "%d bytes -", (int)rc);
+    for (i = 0; i < rc; ++i)
+      fprintf(stderr, " %02x", head[i]);
+    fprintf(stderr, "\n");
+  }
+#endif
 
   for(i=0; i<sizeof(formats)/sizeof(formats[0]); i++) { 
     struct magic_entry const *entry = formats + i;
index 62451c7..66602e3 100644 (file)
--- a/imager.h
+++ b/imager.h
@@ -246,58 +246,6 @@ size_t i_tt_glyph_name(TT_Fonthandle *handle, unsigned long ch, char *name_buf,
 
 #endif  /* End of freetype headers */
 
-/* functions for reading and writing formats */
-
-/* general reader callback 
- userdata - data the user passed into the reader
- buffer - the buffer to fill with data
- need - the amount of data needed
- want - the amount of space we have to store data
- fill buffer and return the number of bytes read, 0 for eof, -1 for error
-*/
-
-typedef int (*i_read_callback_t)(char *userdata, char *buffer, int need, 
-                                int want);
-
-/* i_gen_reader() translates the low-level requests from whatever library
-   into buffered requests.
-   but the called function can always bypass buffering by only ever 
-   reading I<need> bytes.
-*/
-#define CBBUFSIZ 4096
-
-typedef struct {
-  i_read_callback_t cb;
-  char *userdata;
-  char buffer[CBBUFSIZ];
-  int length;
-  int cpos;
-} i_gen_read_data;
-
-extern int  i_gen_reader(i_gen_read_data *info, char *buffer, int need);
-extern      i_gen_read_data *i_gen_read_data_new(i_read_callback_t cb, char *userdata);
-extern void i_free_gen_read_data(i_gen_read_data *);
-
-/* general writer callback
-   userdata - the data the user passed into the writer
-   data - the data to write
-   data_size - the number of bytes to write
-   write the data, return non-zero on success, zero on failure.
-*/
-typedef int (*i_write_callback_t)(char *userdata, char const *data, int size);
-
-typedef struct {
-  i_write_callback_t cb;
-  char *userdata;
-  char buffer[CBBUFSIZ];
-  int maxlength;
-  int filledto;
-} i_gen_write_data;
-
-extern int i_gen_writer(i_gen_write_data *info, char const *data, int size);
-extern i_gen_write_data *i_gen_write_data_new(i_write_callback_t cb, char *userdata, int maxlength);
-extern int i_free_gen_write_data(i_gen_write_data *, int flush);
-
 extern void i_quant_makemap(i_quantize *quant, i_img **imgs, int count);
 extern i_palidx *i_quant_translate(i_quantize *quant, i_img *img);
 extern void i_quant_transparent(i_quantize *quant, i_palidx *indices, i_img *img, i_palidx trans_index);
diff --git a/imext.c b/imext.c
index d0544e6..e6f0eee 100644 (file)
--- a/imext.c
+++ b/imext.c
@@ -131,7 +131,26 @@ im_ext_funcs imager_function_table =
     i_render_color,
     i_render_fill,
     i_render_line,
-    i_render_linef
+    i_render_linef,
+
+    /* level 6 */
+    i_io_getc_imp,
+    i_io_peekc_imp,
+    i_io_peekn,
+    i_io_putc_imp,
+    i_io_read,
+    i_io_write,
+    i_io_seek,
+    i_io_flush,
+    i_io_close,
+    i_io_set_buffered,
+    i_io_gets,
+    io_new_fd,
+    io_new_bufchain,
+    io_new_buffer,
+    io_new_cb,
+    io_slurp,
+    io_glue_destroy
   };
 
 /* in general these functions aren't called by Imager internally, but
diff --git a/imext.h b/imext.h
index 08d6a0c..912b04d 100644 (file)
--- a/imext.h
+++ b/imext.h
@@ -234,6 +234,26 @@ extern im_ext_funcs *imager_function_ext_table;
 #define i_render_linef(r, x, y, width, src, line, combine) \
   ((im_extt->f_i_render_linef)((r), (x), (y), (width), (src), (line), (combine)))
 
+#define i_io_getc_imp (im_extt->f_i_io_getc_imp)
+#define i_io_peekc_imp (im_extt->f_i_io_peekc_imp)
+#define i_io_peekn (im_extt->f_i_io_peekn)
+#define i_io_putc_imp (im_extt->f_i_io_putc_imp)
+#define i_io_read (im_extt->f_i_io_read)
+#define i_io_write (im_extt->f_i_io_write)
+#define i_io_seek (im_extt->f_i_io_seek)
+#define i_io_flush (im_extt->f_i_io_flush)
+#define i_io_close (im_extt->f_i_io_close)
+#define i_io_set_buffered (im_extt->f_i_io_set_buffered)
+#define i_io_gets (im_extt->f_i_io_gets)
+#define io_new_fd(fd) ((im_extt->f_io_new_fd)(fd))
+#define io_new_bufchain() ((im_extt->f_io_new_bufchain)())
+#define io_new_buffer(data, len, closecb, closedata) \
+  ((im_extt->f_io_new_buffer)((data), (len), (closecb), (closedata)))
+#define io_new_cb(p, readcb, writecb, seekcb, closecb, destroycb) \
+  ((im_extt->f_io_new_cb)((p), (readcb), (writecb), (seekcb), (closecb), (destroycb)))
+#define io_slurp(ig, datap) ((im_extt->f_io_slurp)((ig), (datap)))
+#define io_glue_destroy(ig) ((im_extt->f_io_glue_destroy)(ig))
+
 #ifdef IMAGER_LOG
 #define mm_log(x) { i_lhead(__FILE__,__LINE__); i_loog x; } 
 #else
index 573cdc7..1355382 100644 (file)
  Version 2 changed the types of some parameters and pointers.  A
  simple recompile should be enough in most cases.
 
+ Version 3 changed the behaviour of some of the I/O layer functions,
+ and in some cases the initial seek position when calling file
+ readers.  Switching away from calling readcb etc to i_io_read() etc
+ should fix your code.
+
 */
-#define IMAGER_API_VERSION 2
+#define IMAGER_API_VERSION 3
 
 /*
  IMAGER_API_LEVEL is the level of the structure.  New function pointers
@@ -23,7 +28,7 @@
  will result in an increment of IMAGER_API_LEVEL.
 */
 
-#define IMAGER_API_LEVEL 6
+#define IMAGER_API_LEVEL 7
 
 typedef struct {
   int version;
@@ -185,7 +190,29 @@ typedef struct {
                          i_img_dim width, const double *src,
                          i_fcolor *line, i_fill_combinef_f combine);
 
-  /* IMAGER_API_LEVEL 6 functions will be added here */
+  /* Level 6 lost to mis-numbering */
+  /* IMAGER_API_LEVEL 7 */
+  int (*f_i_io_getc_imp)(io_glue *ig);
+  int (*f_i_io_peekc_imp)(io_glue *ig);
+  ssize_t (*f_i_io_peekn)(io_glue *ig, void *buf, size_t size);
+  int (*f_i_io_putc_imp)(io_glue *ig, int c);
+  ssize_t (*f_i_io_read)(io_glue *, void *buf, size_t size);
+  ssize_t (*f_i_io_write)(io_glue *, const void *buf, size_t size);
+  off_t (*f_i_io_seek)(io_glue *, off_t offset, int whence);
+  int (*f_i_io_flush)(io_glue *ig);
+  int (*f_i_io_close)(io_glue *ig);
+  int (*f_i_io_set_buffered)(io_glue *ig, int buffered);
+  ssize_t (*f_i_io_gets)(io_glue *ig, char *, size_t, int);
+
+  i_io_glue_t *(*f_io_new_fd)(int fd);
+  i_io_glue_t *(*f_io_new_bufchain)(void);
+  i_io_glue_t *(*f_io_new_buffer)(const char *data, size_t len, i_io_closebufp_t closecb, void *closedata);
+  i_io_glue_t *(*f_io_new_cb)(void *p, i_io_readl_t readcb, i_io_writel_t writecb, i_io_seekl_t seekcb, i_io_closel_t closecb, i_io_destroyl_t destroycb);
+  size_t (*f_io_slurp)(i_io_glue_t *ig, unsigned char **c);
+  void (*f_io_glue_destroy)(i_io_glue_t *ig);
+
+  /* IMAGER_API_LEVEL 8 functions will be added here */
+  
 } im_ext_funcs;
 
 #define PERL_FUNCTION_TABLE_NAME "Imager::__ext_func_table"
index 5cb8753..f26a4bc 100644 (file)
--- a/iolayer.c
+++ b/iolayer.c
@@ -12,7 +12,9 @@
 #include "imageri.h"
 
 #define IOL_DEB(x)
+#define IOL_DEBs stderr
 
+#define IO_BUF_SIZE 8192
 
 char *io_type_names[] = { "FDSEEK", "FDNOSEEK", "BUFFER", "CBSEEK", "CBNOSEEK", "BUFCHAIN" };
 
@@ -25,22 +27,29 @@ typedef struct io_blink {
 } io_blink;
 
 
-/* Structures that describe callback interfaces */
-
 typedef struct {
-  off_t offset;
-  off_t cpos;
-} io_ex_rseek;
-
+  i_io_glue_t  base;
+  int          fd;
+} io_fdseek;
 
 typedef struct {
-  off_t offset;
+  i_io_glue_t   base;
+  const char   *data;
+  size_t       len;
+  i_io_closebufp_t     closecb;        /* free memory mapped segment or decrement refcount */
+  void          *closedata;
   off_t cpos;
-  io_blink *head;
-  io_blink *tail;
-  io_blink *cp;
-} io_ex_fseek;
+} io_buffer;
 
+typedef struct {
+  i_io_glue_t   base;
+  void         *p;             /* Callback data */
+  i_io_readl_t readcb;
+  i_io_writel_t        writecb;
+  i_io_seekl_t seekcb;
+  i_io_closel_t closecb;
+  i_io_destroyl_t      destroycb;
+} io_cb;
 
 typedef struct {
   off_t offset;                        /* Offset of the source - not used */
@@ -53,14 +62,6 @@ typedef struct {
   off_t gpos;                  /* Global position in stream */
 } io_ex_bchain;
 
-typedef struct {
-  off_t offset;                        /* Offset of the source - not used */
-  off_t cpos;                  /* Offset within the current */
-} io_ex_buffer;
-
-static void io_obj_setp_buffer(io_obj *io, char *p, size_t len, i_io_closebufp_t closecb, void *closedata);
-static void io_obj_setp_cb2     (io_obj *io, void *p, i_io_readl_t readcb, i_io_writel_t writecb, i_io_seekl_t seekcb, i_io_closel_t closecb, i_io_destroyl_t destroycb);
-
 /* turn current offset, file length, whence and offset into a new offset */
 #define calc_seek_offset(curr_off, length, offset, whence) \
   (((whence) == SEEK_SET) ? (offset) : \
@@ -76,7 +77,7 @@ iolayer.c - encapsulates different source of data into a single framework.
 
   io_glue *ig = io_new_fd( fileno(stdin) );
   method = io_reqmeth( IOL_NOSEEK | IOL_MMAP ); // not implemented yet
-  io_glue_commit_types(ig);                     // always assume IOL_SEEK for now
+
   switch (method) {
   case IOL_NOSEEK:
     code that uses ig->readcb()
@@ -111,64 +112,1170 @@ Some of these functions are internal.
 =cut
 */
 
+static void
+i_io_init(io_glue *ig, int type, i_io_readp_t readcb, i_io_writep_t writecb,
+         i_io_seekp_t seekcb);
+
 static ssize_t fd_read(io_glue *ig, void *buf, size_t count);
 static ssize_t fd_write(io_glue *ig, const void *buf, size_t count);
 static off_t fd_seek(io_glue *ig, off_t offset, int whence);
 static int fd_close(io_glue *ig);
 static ssize_t fd_size(io_glue *ig);
 static const char *my_strerror(int err);
+static void i_io_setup_buffer(io_glue *ig);
+static void
+i_io_start_write(io_glue *ig);
+static int
+i_io_read_fill(io_glue *ig, ssize_t needed);
+static void
+dump_data(unsigned char *start, unsigned char *end, int bias);
+static ssize_t realseek_read(io_glue *igo, void *buf, size_t count);
+static ssize_t realseek_write(io_glue *igo, const void *buf, size_t count);
+static int realseek_close(io_glue *igo);
+static off_t realseek_seek(io_glue *igo, off_t offset, int whence);
+static void realseek_destroy(io_glue *igo);
+static ssize_t buffer_read(io_glue *igo, void *buf, size_t count);
+static ssize_t buffer_write(io_glue *ig, const void *buf, size_t count);
+static int buffer_close(io_glue *ig);
+static off_t buffer_seek(io_glue *igo, off_t offset, int whence);
+static void buffer_destroy(io_glue *igo);
+static io_blink*io_blink_new(void);
+static void io_bchain_advance(io_ex_bchain *ieb);
+static void io_destroy_bufchain(io_ex_bchain *ieb);
+static ssize_t bufchain_read(io_glue *ig, void *buf, size_t count);
+static ssize_t bufchain_write(io_glue *ig, const void *buf, size_t count);
+static int bufchain_close(io_glue *ig);
+static off_t bufchain_seek(io_glue *ig, off_t offset, int whence);
+static void bufchain_destroy(io_glue *ig);
 
 /*
- * Callbacks for sources that cannot seek
+ * Methods for setting up data source
  */
 
-/* fakeseek_read: read method for when emulating a seekable source
-static
-ssize_t
-fakeseek_read(io_glue *ig, void *buf, size_t count) {
-  io_ex_fseek *exdata = ig->exdata; 
-  return 0;
+/*
+=item io_new_bufchain()
+=order 10
+=category I/O Layers
+
+returns a new io_glue object that has the 'empty' source and but can
+be written to and read from later (like a pseudo file).
+
+=cut
+*/
+
+io_glue *
+io_new_bufchain() {
+  io_glue *ig;
+  io_ex_bchain *ieb = mymalloc(sizeof(io_ex_bchain));
+
+  mm_log((1, "io_new_bufchain()\n"));
+
+  ig = mymalloc(sizeof(io_glue));
+  memset(ig, 0, sizeof(*ig));
+  i_io_init(ig, BUFCHAIN, bufchain_read, bufchain_write, bufchain_seek);
+
+  ieb->offset = 0;
+  ieb->length = 0;
+  ieb->cpos   = 0;
+  ieb->gpos   = 0;
+  ieb->tfill  = 0;
+  
+  ieb->head   = io_blink_new();
+  ieb->cp     = ieb->head;
+  ieb->tail   = ieb->head;
+  
+  ig->exdata    = ieb;
+  ig->closecb   = bufchain_close;
+  ig->destroycb = bufchain_destroy;
+
+  return ig;
 }
+
+/*
+=item io_new_buffer(data, length)
+=order 10
+=category I/O Layers
+
+Returns a new io_glue object that has the source defined as reading
+from specified buffer.  Note that the buffer is not copied.
+
+   data - buffer to read from
+   length - length of buffer
+
+=cut
 */
 
+io_glue *
+io_new_buffer(const char *data, size_t len, i_io_closebufp_t closecb, void *closedata) {
+  io_buffer *ig;
+  
+  mm_log((1, "io_new_buffer(data %p, len %ld, closecb %p, closedata %p)\n", data, (long)len, closecb, closedata));
+
+  ig = mymalloc(sizeof(io_buffer));
+  memset(ig, 0, sizeof(*ig));
+  i_io_init(&ig->base, BUFFER, buffer_read, buffer_write, buffer_seek);
+  ig->data      = data;
+  ig->len       = len;
+  ig->closecb   = closecb;
+  ig->closedata = closedata;
+
+  ig->cpos   = 0;
+  
+  ig->base.closecb   = buffer_close;
+  ig->base.destroycb = buffer_destroy;
+
+  return (io_glue *)ig;
+}
 
 
 /*
- * Callbacks for sources that can seek 
- */
+=item io_new_fd(fd)
+=order 10
+=category I/O Layers
+
+returns a new io_glue object that has the source defined as reading
+from specified file descriptor.  Note that the the interface to receiving
+data from the io_glue callbacks hasn't been done yet.
+
+   fd - file descriptor to read/write from
+
+=cut
+*/
+
+io_glue *
+io_new_fd(int fd) {
+  io_fdseek *ig;
+
+  mm_log((1, "io_new_fd(fd %d)\n", fd));
+
+  ig = mymalloc(sizeof(io_fdseek));
+  memset(ig, 0, sizeof(*ig));
+  i_io_init(&ig->base, FDSEEK, fd_read, fd_write, fd_seek);
+  ig->fd = fd;
+
+  ig->base.closecb   = fd_close;
+  ig->base.sizecb    = fd_size;
+  ig->base.destroycb = NULL;
+
+  mm_log((1, "(%p) <- io_new_fd\n", ig));
+  return (io_glue *)ig;
+}
 
 /*
-=item realseek_read(ig, buf, count)
+=item io_new_cb(p, read_cb, write_cb, seek_cb, close_cb, destroy_cb)
+=category I/O Layers
+=order 10
 
-Does the reading from a source that can be seeked on
+Create a new I/O layer object that calls your supplied callbacks.
 
-   ig    - io_glue object
-   buf   - buffer to return data in
-   count - number of bytes to read into buffer max
+In general the callbacks should behave like the corresponding POSIX
+primitives.
+
+=over
+
+=item *
+
+C<read_cb>(p, buffer, length) should read up to C<length> bytes into
+C<buffer> and return the number of bytes read.  At end of file, return
+0.  On error, return -1.
+
+=item *
+
+C<write_cb>(p, buffer, length) should write up to C<length> bytes from
+C<buffer> and return the number of bytes written.  A return value <= 0
+will be treated as an error.
+
+=item *
+
+C<seekcb>(p, offset, whence) should seek and return the new offset.
+
+=item *
+
+C<close_cb>(p) should return 0 on success, -1 on failure.
+
+=item *
+
+C<destroy_cb>(p) should release any memory specific to your callback
+handlers.
+
+=back
 
 =cut
 */
 
-static
-ssize_t 
-realseek_read(io_glue *ig, void *buf, size_t count) {
-  io_ex_rseek *ier = ig->exdata;
-  void *p          = ig->source.cb.p;
-  ssize_t       rc = 0;
-  size_t        bc = 0;
-  char       *cbuf = buf;
+io_glue *
+io_new_cb(void *p, i_io_readl_t readcb, i_io_writel_t writecb, 
+         i_io_seekl_t seekcb, i_io_closel_t closecb, 
+         i_io_destroyl_t destroycb) {
+  io_cb *ig;
 
-  IOL_DEB( printf("realseek_read:  buf = %p, count = %d\n", 
-                 buf, count) );
-  /* Is this a good idea? Would it be better to handle differently?
-     skip handling? */
-  while( count!=bc && (rc = ig->source.cb.readcb(p,cbuf+bc,count-bc))>0 ) {
-    bc+=rc;
+  mm_log((1, "io_new_cb(p %p, readcb %p, writecb %p, seekcb %p, closecb %p, "
+          "destroycb %p)\n", p, readcb, writecb, seekcb, closecb, destroycb));
+  ig = mymalloc(sizeof(io_cb));
+  memset(ig, 0, sizeof(*ig));
+  i_io_init(&ig->base, CBSEEK, realseek_read, realseek_write, realseek_seek);
+  mm_log((1, "(%p) <- io_new_cb\n", ig));
+
+  ig->base.closecb   = realseek_close;
+  ig->base.destroycb = realseek_destroy;
+
+  ig->p         = p;
+  ig->readcb    = readcb;
+  ig->writecb   = writecb;
+  ig->seekcb    = seekcb;
+  ig->closecb   = closecb;
+  ig->destroycb = destroycb;
+
+  return (io_glue *)ig;
+}
+
+/*
+=item io_slurp(ig, c)
+=category I/O Layers
+
+Takes the source that the io_glue is bound to and allocates space for
+a return buffer and returns the entire content in a single buffer.
+Note: This only works for io_glue objects created by
+io_new_bufchain().  It is useful for saving to scalars and such.
+
+   ig - io_glue object
+   c  - pointer to a pointer to where data should be copied to
+
+  char *data;
+  size_t size = io_slurp(ig, &data);
+  ... do something with the data ...
+  myfree(data);
+
+io_slurp() will abort the program if the supplied I/O layer is not
+from io_new_bufchain().
+
+=cut
+*/
+
+size_t
+io_slurp(io_glue *ig, unsigned char **c) {
+  ssize_t rc;
+  off_t orgoff;
+  io_ex_bchain *ieb;
+  unsigned char *cc;
+  io_type inn = ig->type;
+  
+  if ( inn != BUFCHAIN ) {
+    i_fatal(0, "io_slurp: called on a source that is not from a bufchain\n");
   }
+
+  ieb = ig->exdata;
+  cc = *c = mymalloc( ieb->length );
   
-  ier->cpos += bc;
-  IOL_DEB( printf("realseek_read: rc = %d, bc = %d\n", rc, bc) );
-  return rc < 0 ? rc : bc;
+  orgoff = ieb->gpos;
+  
+  bufchain_seek(ig, 0, SEEK_SET);
+  
+  rc = bufchain_read(ig, cc, ieb->length);
+
+  if (rc != ieb->length)
+    i_fatal(1, "io_slurp: bufchain_read returned an incomplete read: rc = %d, request was %d\n", rc, ieb->length);
+
+  return rc;
+}
+
+/*
+=item io_glue_destroy(ig)
+=category I/O Layers
+=order 90
+=synopsis io_glue_destroy(ig);
+
+Destroy an io_glue objects.  Should clean up all related buffers.
+
+   ig - io_glue object to destroy.
+
+=cut
+*/
+
+void
+io_glue_destroy(io_glue *ig) {
+  mm_log((1, "io_glue_DESTROY(ig %p)\n", ig));
+
+  if (ig->destroycb)
+    ig->destroycb(ig);
+
+  if (ig->buffer)
+    myfree(ig->buffer);
+  
+  myfree(ig);
+}
+
+/*
+=item i_io_getc(ig)
+=category I/O Layers
+
+A macro to read a single byte from a buffered I/O glue object.
+
+Returns EOF on failure, or a byte.
+
+=cut
+*/
+
+int
+i_io_getc_imp(io_glue *ig) {
+  if (ig->write_ptr)
+    return EOF;
+  
+  if (ig->error || ig->buf_eof)
+    return EOF;
+  
+  if (!ig->buffered) {
+    unsigned char buf;
+    ssize_t rc = i_io_raw_read(ig, &buf, 1);
+    if (rc > 0) {
+      return buf;
+    }
+    else if (rc == 0) {
+      ig->buf_eof = 1;
+      return EOF;
+    }
+    else {
+      ig->error = 1;
+      return EOF;
+    }
+  }
+
+  if (!ig->buffer)
+    i_io_setup_buffer(ig);
+  
+  if (!ig->read_ptr || ig->read_ptr == ig->read_end) {
+    if (!i_io_read_fill(ig, 1))
+      return EOF;
+  }
+  
+  return *(ig->read_ptr++);
+}
+
+/*
+=item i_io_peekc(ig)
+=category I/O Layers
+
+Read the next character from the stream without advancing the stream.
+
+On error or end of file, return EOF.
+
+For unbuffered streams a single character buffer will be setup.
+
+=cut
+*/
+
+int
+i_io_peekc_imp(io_glue *ig) {
+  if (ig->write_ptr)
+    return EOF;
+
+  if (!ig->buffer)
+    i_io_setup_buffer(ig);
+
+  if (!ig->buffered) {
+    ssize_t rc = i_io_raw_read(ig, ig->buffer, 1);
+    if (rc > 0) {
+      ig->read_ptr = ig->buffer;
+      ig->read_end = ig->buffer + 1;
+      return *(ig->buffer);
+    }
+    else if (rc == 0) {
+      ig->buf_eof = 1;
+      return EOF;
+    }
+    else {
+      ig->error = 1;
+      return EOF;
+    }
+  }
+
+  if (!ig->read_ptr || ig->read_ptr == ig->read_end) {
+    if (ig->error || ig->buf_eof)
+      return EOF;
+    
+    if (!i_io_read_fill(ig, 1))
+      return EOF;
+  }
+
+  return *(ig->read_ptr);
+}
+
+/*
+=item i_io_peekn(ig, buffer, size)
+=category I/O Layers
+=synopsis ssize_t count = i_io_peekn(ig, buffer, sizeof(buffer));
+
+Buffer at least C<size> (at most C<< ig->buf_size >> bytes of data
+from the stream and return C<size> bytes of it to the caller in
+C<buffer>.
+
+This ignores the buffered state of the stream, and will always setup
+buffering if needed.
+
+If no C<type> parameter is provided to Imager::read() or
+Imager::read_multi(), Imager will call C<i_io_peekn()> when probing
+for the file format.
+
+Returns -1 on error, 0 if there is no data before EOF, or the number
+of bytes read into C<buffer>.
+
+=cut
+*/
+
+ssize_t
+i_io_peekn(io_glue *ig, void *buf, size_t size) {
+  IOL_DEB(fprintf(IOL_DEBs, "i_io_peekn(%p, %p, %d)\n", ig, buf, (int)size));
+
+  if (size == 0) {
+    i_push_error(0, "peekn size must be positive");
+    IOL_DEB(fprintf(IOL_DEBs, "i_io_peekn() => -1 (zero size)\n"));
+    return -1;
+  }
+
+  if (ig->write_ptr) {
+    IOL_DEB(fprintf(IOL_DEBs, "i_io_peekn() => -1 (write_ptr set)\n"));
+    return -1;
+  }
+
+  if (!ig->buffer)
+    i_io_setup_buffer(ig);
+
+  if ((!ig->read_ptr || size > ig->read_end - ig->read_ptr)
+      && !(ig->buf_eof || ig->error)) {
+    i_io_read_fill(ig, size);
+  }
+  
+  if (size > ig->read_end - ig->read_ptr)
+    size = ig->read_end - ig->read_ptr;
+
+  if (size)
+    memcpy(buf, ig->read_ptr, size);
+  else if (ig->buf_eof) {
+    IOL_DEB(fprintf(IOL_DEBs, "i_io_peekn() => 0 (eof)\n"));
+    return 0;
+  }
+  else if (ig->error) {
+    IOL_DEB(fprintf(IOL_DEBs, "i_io_peekn() => -1 (error)\n"));
+    return -1;
+  }
+  else {
+    IOL_DEB(fprintf(IOL_DEBs, "i_io_peekn() - size 0 but not eof or error!\n"));
+    return -1;
+  }
+
+  IOL_DEB(fprintf(IOL_DEBs, "i_io_peekn() => %d\n", (int)size));
+
+  return size;
+}
+
+/*
+=item i_io_putc(ig, c)
+=category I/O Layers
+
+Write a single character to the stream.
+
+On success return c, on error returns EOF
+
+=cut
+*/
+
+int
+i_io_putc_imp(io_glue *ig, int c) {
+  IOL_DEB(fprintf(IOL_DEBs, "i_io_putc_imp(%p, %d)\n", ig, c));
+
+  if (!ig->buffered) {
+    char buf = c;
+    ssize_t write_result;
+
+    if (ig->error)
+      return EOF;
+
+    write_result = i_io_raw_write(ig, &buf, 1);
+    int result = c;
+    if (write_result != 1) {
+      ig->error = 1;
+      result = EOF;
+      IOL_DEB(fprintf(IOL_DEBs, "  unbuffered putc() failed, setting error mode\n"));
+    }
+    IOL_DEB(fprintf(IOL_DEBs, "  unbuffered: result %d\n", result));
+
+    return result;
+  }
+
+  if (ig->read_ptr)
+    return EOF;
+
+  if (ig->error)
+    return EOF;
+
+  if (!ig->buffer)
+    i_io_setup_buffer(ig);
+
+  if (ig->write_ptr && ig->write_ptr == ig->write_end) {
+    if (!i_io_flush(ig))
+      return EOF;
+  }
+
+  i_io_start_write(ig);
+
+  *(ig->write_ptr)++ = c;
+
+  return (unsigned char)c;
+}
+
+/*
+=item i_io_read(io, buffer, size)
+=category I/O Layers
+
+Read up to C<size> bytes from the stream C<io> into C<buffer>.
+
+Returns the number of bytes read.  Returns 0 on end of file.  Returns
+-1 on error.
+
+=cut
+*/
+
+ssize_t
+i_io_read(io_glue *ig, void *buf, size_t size) {
+  unsigned char *pbuf = buf;
+  ssize_t read_total = 0;
+
+  IOL_DEB(fprintf(IOL_DEBs, "i_io_read(%p, %p, %u)\n", ig, buf, (unsigned)size));
+
+  if (ig->write_ptr) {
+    IOL_DEB(fprintf(IOL_DEBs, "i_io_read() => -1 (write_ptr set)\n"));
+    return -1;
+  }
+
+  if (!ig->buffer && ig->buffered)
+    i_io_setup_buffer(ig);
+
+  if (ig->read_ptr && ig->read_ptr < ig->read_end) {
+    size_t alloc = ig->read_end - ig->read_ptr;
+    
+    if (alloc > size)
+      alloc = size;
+
+    memcpy(pbuf, ig->read_ptr, alloc);
+    ig->read_ptr += alloc;
+    pbuf += alloc;
+    size -= alloc;
+    read_total += alloc;
+  }
+
+  if (size > 0 && !(ig->error || ig->buf_eof)) {
+    if (!ig->buffered || size > ig->buf_size) {
+      ssize_t rc;
+      
+      while (size > 0 && (rc = i_io_raw_read(ig, pbuf, size)) > 0) {
+       size -= rc;
+       pbuf += rc;
+       read_total += rc;
+      }
+      
+      IOL_DEB(fprintf(IOL_DEBs, "i_io_read() => %d (raw read)\n", (int)read_total));
+
+      if (rc < 0)
+       ig->error = 1;
+      else if (rc == 0)
+       ig->buf_eof = 1;
+
+      if (!read_total)
+       return rc;
+    }
+    else {
+      if (i_io_read_fill(ig, size)) {
+       size_t alloc = ig->read_end - ig->read_ptr;
+       if (alloc > size)
+         alloc = size;
+       
+       memcpy(pbuf, ig->read_ptr, alloc);
+       ig->read_ptr += alloc;
+       pbuf += alloc;
+       size -= alloc;
+       read_total += alloc;
+      }
+      else {
+       if (!read_total && ig->error) {
+         IOL_DEB(fprintf(IOL_DEBs, "i_io_read() => -1 (fill failure)\n"));
+         return -1;
+       }
+      }
+    }
+  }
+
+  if (!read_total && ig->error)
+    read_total = -1;
+
+  IOL_DEB(fprintf(IOL_DEBs, "i_io_read() => %d\n", (int)read_total));
+
+  return read_total;
+}
+
+/*
+=item i_io_write(io, buffer, size)
+=category I/O Layers
+=synopsis ssize_t result = i_io_write(io, buffer, size)
+
+Write to the given I/O stream.
+
+Returns the number of bytes written.
+
+=cut
+*/
+
+ssize_t
+i_io_write(io_glue *ig, const void *buf, size_t size) {
+  const unsigned char *pbuf = buf;
+  size_t write_count = 0;
+
+  IOL_DEB(fprintf(IOL_DEBs, "i_io_write(%p, %p, %u)\n", ig, buf, (unsigned)size));
+
+  if (!ig->buffered) {
+    ssize_t result;
+
+    if (ig->error) {
+      IOL_DEB(fprintf(IOL_DEBs, "  unbuffered, error state\n"));
+      return -1;
+    }
+
+    result = i_io_raw_write(ig, buf, size);
+
+    if (result != size) {
+      ig->error = 1;
+      IOL_DEB(fprintf(IOL_DEBs, "  unbuffered, setting error flag\n"));
+    }
+
+    IOL_DEB(fprintf(IOL_DEBs, "  unbuffered, result: %d\n", (int)result));
+
+    return result;
+  }
+
+  if (ig->read_ptr) {
+    IOL_DEB(fprintf(IOL_DEBs, "i_io_write() => -1 (read_ptr set)\n"));
+    return -1;
+  }
+
+  if (ig->error) {
+    IOL_DEB(fprintf(IOL_DEBs, "i_io_write() => -1 (error)\n"));
+    return -1;
+  }
+
+  if (!ig->buffer)
+    i_io_setup_buffer(ig);
+
+  if (!ig->write_ptr)
+    i_io_start_write(ig);
+
+  if (ig->write_ptr && ig->write_ptr + size <= ig->write_end) {
+    size_t alloc = ig->write_end - ig->write_ptr;
+    if (alloc > size)
+      alloc = size;
+    memcpy(ig->write_ptr, pbuf, alloc);
+    write_count += alloc;
+    size -= alloc;
+    pbuf += alloc;
+    ig->write_ptr += alloc;
+  }
+
+  if (size) {
+    if (!i_io_flush(ig)) {
+      IOL_DEB(fprintf(IOL_DEBs, "i_io_write() => %d (i_io_flush failure)\n", (int)write_count));
+      return write_count ? write_count : -1;
+    }
+
+    i_io_start_write(ig);
+    
+    if (size > ig->buf_size) {
+      ssize_t rc;
+      while (size > 0 && (rc = i_io_raw_write(ig, pbuf, size)) > 0) {
+       write_count += rc;
+       pbuf += rc;
+       size -= rc;
+      }
+      if (rc <= 0) {
+       ig->error = 1;
+       if (!write_count) {
+         IOL_DEB(fprintf(IOL_DEBs, "i_io_write() => -1 (direct write failure)\n"));
+         return -1;
+       }
+      }
+    }
+    else {
+      memcpy(ig->write_ptr, pbuf, size);
+      write_count += size;
+      ig->write_ptr += size;
+    }
+  }
+
+  IOL_DEB(fprintf(IOL_DEBs, "i_io_write() => %d\n", (int)write_count));
+
+  return write_count;
+}
+
+/*
+=item i_io_seek(io, offset, whence)
+=category I/O Layers
+
+Seek within the stream.
+
+Acts like perl's seek.
+
+=cut
+ */
+
+off_t
+i_io_seek(io_glue *ig, off_t offset, int whence) {
+  off_t new_off;
+
+  IOL_DEB(fprintf(IOL_DEBs, "i_io_seek(%p, %ld, %d)\n", ig, (long)offset, whence));
+
+  if (ig->write_ptr && ig->write_ptr != ig->write_end) {
+    if (!i_io_flush(ig))
+      return (off_t)(-1);
+  }
+
+  if (whence == SEEK_CUR && ig->read_ptr && ig->read_ptr != ig->read_end)
+    offset -= ig->read_end - ig->read_ptr;
+
+  ig->read_ptr = ig->read_end = NULL;
+  ig->write_ptr = ig->write_end = NULL;
+  ig->error = 0;
+  ig->buf_eof = 0;
+  
+  new_off = i_io_raw_seek(ig, offset, whence);
+  if (new_off < 0)
+    ig->error = 1;
+
+  IOL_DEB(fprintf(IOL_DEBs, "i_io_seek() => %ld\n", (long)new_off));
+
+  return new_off;
+}
+
+/*
+=item i_io_flush(io)
+=category I/O Layers
+
+Flush any buffered output.
+
+Returns true on success,
+
+=cut
+*/
+
+int
+i_io_flush(io_glue *ig) {
+  unsigned char *bufp;
+
+  IOL_DEB(fprintf(IOL_DEBs, "i_io_flush(%p)\n", ig));
+
+  if (ig->error) {
+    IOL_DEB(fprintf(IOL_DEBs, "i_io_flush() => 0 (error set)\n", ig));
+    return 0;
+  }
+
+  /* nothing to do */
+  if (!ig->write_ptr)
+    return 1;
+
+  bufp = ig->buffer;
+  while (bufp < ig->write_ptr) {
+    ssize_t rc = i_io_raw_write(ig, bufp, ig->write_ptr - bufp);
+    if (rc <= 0) {
+      IOL_DEB(fprintf(IOL_DEBs, "i_io_flush() => 0 (write error)\n", ig));
+      ig->error = 1;
+      return 0;
+    }
+    
+    bufp += rc;
+  }
+
+  ig->write_ptr = ig->write_end = NULL;
+
+  IOL_DEB(fprintf(IOL_DEBs, "i_io_flush() => 1\n", ig));
+
+  return 1;
+}
+
+/*
+=item i_io_close(io)
+=category I/O Layers
+
+Flush any pending output and perform the close action for the stream.
+
+Returns 0 on success.
+
+=cut
+*/
+
+int
+i_io_close(io_glue *ig) {
+  int result = 0;
+
+  IOL_DEB(fprintf(IOL_DEBs, "i_io_close(%p)\n", ig));
+  if (ig->error)
+    result = -1;
+
+  if (ig->write_ptr && !i_io_flush(ig))
+    result = -1;
+
+  if (i_io_raw_close(ig))
+    result = -1;
+
+  IOL_DEB(fprintf(IOL_DEBs, "i_io_close() => %d\n", result));
+
+  return result;
+}
+
+/*
+=item i_io_gets(ig, buffer, size, end_of_line)
+=category I/O Layers
+=synopsis char buffer[BUFSIZ]
+=synopsis ssize_t len = i_io_gets(buffer, sizeof(buffer), '\n');
+
+Read up to C<size>-1 bytes from the stream C<ig> into C<buffer>.
+
+If the byte C<end_of_line> is seen then no further bytes will be read.
+
+Returns the number of bytes read.
+
+Always C<NUL> terminates the buffer.
+
+=cut
+*/
+
+ssize_t
+i_io_gets(io_glue *ig, char *buffer, size_t size, int eol) {
+  ssize_t read_count = 0;
+  if (size < 2)
+    return 0;
+  --size; /* room for nul */
+  while (size > 0) {
+    int byte = i_io_getc(ig);
+    if (byte == EOF)
+      break;
+    *buffer++ = byte;
+    ++read_count;
+    if (byte == eol)
+      break;
+    --size;
+  }
+  *buffer++ = '\0';
+
+  return read_count;
+}
+
+/*
+=item i_io_init(ig, readcb, writecb, seekcb)
+
+Do common initialization for io_glue objects.
+
+=cut
+*/
+
+static void
+i_io_init(io_glue *ig, int type, i_io_readp_t readcb, i_io_writep_t writecb,
+         i_io_seekp_t seekcb) {
+  ig->type = type;
+  ig->exdata = NULL;
+  ig->readcb = readcb;
+  ig->writecb = writecb;
+  ig->seekcb = seekcb;
+  ig->closecb = NULL;
+  ig->sizecb = NULL;
+  ig->destroycb = NULL;
+
+  ig->buffer = NULL;
+  ig->read_ptr = NULL;
+  ig->read_end = NULL;
+  ig->write_ptr = NULL;
+  ig->write_end = NULL;
+  ig->buf_size = IO_BUF_SIZE;
+  ig->buf_eof = 0;
+  ig->error = 0;
+  ig->buffered = 1;
+}
+
+/*
+=item i_io_set_buffered(io, buffered)
+=category I/O Layers
+
+Set the buffering mode of the stream.
+
+If you switch buffering off on a stream with buffering on:
+
+=over
+
+=item *
+
+any buffered output will be flushed.
+
+=item *
+
+any existing buffered input will be consumed before reads become
+unbuffered.
+
+=back
+
+Returns true on success.  This may fail if any buffered output cannot
+be flushed.
+
+=cut
+*/
+
+int
+i_io_set_buffered(io_glue *ig, int buffered) {
+  if (!buffered && ig->write_ptr) {
+    if (!i_io_flush(ig)) {
+      ig->error = 1;
+      return 0;
+    }
+  }
+  ig->buffered = buffered;
+
+  return 1;
+}
+
+/*
+=item i_io_dump(ig)
+
+Dump the base fields of an io_glue object to stdout.
+
+=cut
+*/
+void
+i_io_dump(io_glue *ig, int flags) {
+  fprintf(IOL_DEBs, "ig %p:\n", ig);
+  fprintf(IOL_DEBs, "  type: %d\n", ig->type);  
+  fprintf(IOL_DEBs, "  exdata: %p\n", ig->exdata);
+  if (flags & I_IO_DUMP_CALLBACKS) {
+    fprintf(IOL_DEBs, "  readcb: %p\n", ig->readcb);
+    fprintf(IOL_DEBs, "  writecb: %p\n", ig->writecb);
+    fprintf(IOL_DEBs, "  seekcb: %p\n", ig->seekcb);
+    fprintf(IOL_DEBs, "  closecb: %p\n", ig->closecb);
+    fprintf(IOL_DEBs, "  sizecb: %p\n", ig->sizecb);
+  }
+  if (flags & I_IO_DUMP_BUFFER) {
+    fprintf(IOL_DEBs, "  buffer: %p\n", ig->buffer);
+    fprintf(IOL_DEBs, "  read_ptr: %p\n", ig->read_ptr);
+    if (ig->read_ptr) {
+      fprintf(IOL_DEBs, "    ");
+      dump_data(ig->read_ptr, ig->read_end, 0);
+      putc('\n', IOL_DEBs);
+    }
+    fprintf(IOL_DEBs, "  read_end: %p\n", ig->read_end);
+    fprintf(IOL_DEBs, "  write_ptr: %p\n", ig->write_ptr);
+    if (ig->write_ptr) {
+      fprintf(IOL_DEBs, "    ");
+      dump_data(ig->buffer, ig->write_ptr, 1);
+      putc('\n', IOL_DEBs);
+    }
+    fprintf(IOL_DEBs, "  write_end: %p\n", ig->write_end);
+    fprintf(IOL_DEBs, "  buf_size: %u\n", (unsigned)(ig->buf_size));
+  }
+  if (flags & I_IO_DUMP_STATUS) {
+    fprintf(IOL_DEBs, "  buf_eof: %d\n", ig->buf_eof);
+    fprintf(IOL_DEBs, "  error: %d\n", ig->error);
+    fprintf(IOL_DEBs, "  buffered: %d\n", ig->buffered);
+  }
+}
+
+/*
+=back
+
+=head1 INTERNAL FUNCTIONS
+
+=over
+
+=item my_strerror
+
+Calls strerror() and ensures we don't return NULL.
+
+On some platforms it's possible for strerror() to return NULL, this
+wrapper ensures we only get non-NULL values.
+
+=cut
+*/
+
+static
+const char *my_strerror(int err) {
+  const char *result = strerror(err);
+  
+  if (!result)
+    result = "Unknown error";
+  
+  return result;
+}
+
+static void
+i_io_setup_buffer(io_glue *ig) {
+  ig->buffer = mymalloc(ig->buf_size);
+}
+
+static void
+i_io_start_write(io_glue *ig) {
+  ig->write_ptr = ig->buffer;
+  ig->write_end = ig->buffer + ig->buf_size;
+}
+
+static int
+i_io_read_fill(io_glue *ig, ssize_t needed) {
+  unsigned char *buf_end = ig->buffer + ig->buf_size;
+  unsigned char *buf_start = ig->buffer;
+  unsigned char *work = ig->buffer;
+  ssize_t rc;
+  int good = 0;
+
+  IOL_DEB(fprintf(IOL_DEBs, "i_io_read_fill(%p, %d)\n", ig, (int)needed));
+
+  /* these conditions may be unused, callers should also be checking them */
+  if (ig->error || ig->buf_eof)
+    return 0;
+
+  if (needed > ig->buf_size)
+    needed = ig->buf_size;
+
+  if (ig->read_ptr && ig->read_ptr < ig->read_end) {
+    size_t kept = ig->read_end - ig->read_ptr;
+
+    if (needed < kept) {
+      IOL_DEB(fprintf(IOL_DEBs, "i_io_read_fill(%u) -> 1 (already have enough)\n", (unsigned)needed));
+      return 1;
+    }
+
+    if (ig->read_ptr != ig->buffer)
+      memmove(ig->buffer, ig->read_ptr, kept);
+
+    good = 1; /* we have *something* available to read */
+    work = buf_start + kept;
+    needed -= kept;
+  }
+  else {
+    work = ig->buffer;
+  }
+
+  while (work < buf_end && (rc = i_io_raw_read(ig, work, buf_end - work)) > 0) {
+    work += rc;
+    good = 1;
+    if (needed < rc)
+      break;
+
+    needed -= rc;
+  }
+
+  if (rc < 0) {
+    ig->error = 1;
+    IOL_DEB(fprintf(IOL_DEBs, " i_io_read_fill -> rc %d, setting error\n",
+                   (int)rc));
+  }
+  else if (rc == 0) {
+    ig->buf_eof = 1;
+    IOL_DEB(fprintf(IOL_DEBs, " i_io_read_fill -> rc 0, setting eof\n"));
+  }
+
+  if (good) {
+    ig->read_ptr = buf_start;
+    ig->read_end = work;
+  }
+  
+  IOL_DEB(fprintf(IOL_DEBs, "i_io_read_fill => %d, %u buffered\n", good,
+                 (unsigned)(ig->read_end - ig->read_ptr)));
+  return good;
+}
+
+/*
+=item dump_data(start, end, bias)
+
+Hex dump the data between C<start> and C<end>.
+
+If there is more than a pleasing amount of data, either dump the
+beginning (C<bias == 0>) or dump the end C(<bias != 0>) of the range.
+
+=cut
+*/
+
+static void
+dump_data(unsigned char *start, unsigned char *end, int bias) {
+  unsigned char *p;
+  size_t count = end - start;
+
+  if (start == end) {
+    fprintf(IOL_DEBs, "(empty)");
+    return;
+  }
+
+  if (count > 15) {
+    if (bias) {
+      fprintf(IOL_DEBs, "... ");
+      start = end - 14;
+    }
+    else {
+      end = start + 14;
+    }
+      
+    for (p = start; p < end; ++p) {
+      fprintf(IOL_DEBs, " %02x", *p);
+    }
+    putc(' ', IOL_DEBs);
+    putc('<', IOL_DEBs);
+    for (p = start; p < end; ++p) {
+      if (*p < ' ' || *p > '~')
+       putc('.', IOL_DEBs);
+      else
+       putc(*p, IOL_DEBs);
+    }
+    putc('>', IOL_DEBs);
+    if (!bias)
+      fprintf(IOL_DEBs, " ...");
+  }
+  else {
+    for (p = start; p < end; ++p) {
+      fprintf(IOL_DEBs, " %02x", *p);
+    }
+    putc(' ', IOL_DEBs);
+    for (p = start; p < end; ++p) {
+      if (*p < ' ' || *p > '~')
+       putc('.', IOL_DEBs);
+      else
+       putc(*p, IOL_DEBs);
+    }
+  }
+}
+
+/*
+ * Callbacks for sources that cannot seek
+ */
+
+/*
+ * Callbacks for sources that can seek 
+ */
+
+/*
+=item realseek_read(ig, buf, count)
+
+Does the reading from a source that can be seeked on
+
+   ig    - io_glue object
+   buf   - buffer to return data in
+   count - number of bytes to read into buffer max
+
+=cut
+*/
+
+static
+ssize_t 
+realseek_read(io_glue *igo, void *buf, size_t count) {
+  io_cb        *ig = (io_cb *)igo;
+  void *p          = ig->p;
+  ssize_t       rc = 0;
+
+  IOL_DEB( fprintf(IOL_DEBs, "realseek_read:  buf = %p, count = %u\n", 
+                  buf, (unsigned)count) );
+  rc = ig->readcb(p,buf,count);
+
+  IOL_DEB( fprintf(IOL_DEBs, "realseek_read: rc = %d\n", (int)rc) );
+
+  return rc;
 }
 
 
@@ -186,24 +1293,23 @@ Does the writing to a 'source' that can be seeked on
 
 static
 ssize_t 
-realseek_write(io_glue *ig, const void *buf, size_t count) {
-  io_ex_rseek *ier = ig->exdata;
-  void          *p = ig->source.cb.p;
+realseek_write(io_glue *igo, const void *buf, size_t count) {
+  io_cb        *ig = (io_cb *)igo;
+  void          *p = ig->p;
   ssize_t       rc = 0;
   size_t        bc = 0;
   char       *cbuf = (char*)buf; 
   
-  IOL_DEB( printf("realseek_write: ig = %p, ier->cpos = %ld, buf = %p, "
-                  "count = %d\n", ig, (long) ier->cpos, buf, count) );
+  IOL_DEB( fprintf(IOL_DEBs, "realseek_write: ig = %p, buf = %p, "
+                  "count = %u\n", ig, buf, (unsigned)count) );
 
   /* Is this a good idea? Would it be better to handle differently? 
      skip handling? */
-  while( count!=bc && (rc = ig->source.cb.writecb(p,cbuf+bc,count-bc))>0 ) {
+  while( count!=bc && (rc = ig->writecb(p,cbuf+bc,count-bc))>0 ) {
     bc+=rc;
   }
 
-  ier->cpos += bc;
-  IOL_DEB( printf("realseek_write: rc = %d, bc = %d\n", rc, bc) );
+  IOL_DEB( fprintf(IOL_DEBs, "realseek_write: rc = %d, bc = %u\n", (int)rc, (unsigned)bc) );
   return rc < 0 ? rc : bc;
 }
 
@@ -220,10 +1326,13 @@ actual close or not.  Does nothing for now.  Should be fixed.
 
 static
 int
-realseek_close(io_glue *ig) {
+realseek_close(io_glue *igo) {
+  io_cb *ig = (io_cb *)igo;
+
+  IOL_DEB(fprintf(IOL_DEBs, "realseek_close(%p)\n", ig));
   mm_log((1, "realseek_close(ig %p)\n", ig));
-  if (ig->source.cb.closecb)
-    return ig->source.cb.closecb(ig->source.cb.p);
+  if (ig->closecb)
+    return ig->closecb(ig->p);
   else
     return 0;
 }
@@ -243,27 +1352,25 @@ have an offset into a file that is different from what the underlying library th
 
 static
 off_t
-realseek_seek(io_glue *ig, off_t offset, int whence) {
-  /*  io_ex_rseek *ier = ig->exdata; Needed later */
-  void *p = ig->source.cb.p;
+realseek_seek(io_glue *igo, off_t offset, int whence) {
+  io_cb *ig = (io_cb *)igo;
+  void *p = ig->p;
   off_t rc;
-  IOL_DEB( printf("realseek_seek(ig %p, offset %ld, whence %d)\n", ig, (long) offset, whence) );
-  rc = ig->source.cb.seekcb(p, offset, whence);
+  IOL_DEB( fprintf(IOL_DEBs, "realseek_seek(ig %p, offset %ld, whence %d)\n", ig, (long) offset, whence) );
+  rc = ig->seekcb(p, offset, whence);
 
-  IOL_DEB( printf("realseek_seek: rc %ld\n", (long) rc) );
+  IOL_DEB( fprintf(IOL_DEBs, "realseek_seek: rc %ld\n", (long) rc) );
   return rc;
   /* FIXME: How about implementing this offset handling stuff? */
 }
 
 static
 void
-realseek_destroy(io_glue *ig) {
-  io_ex_rseek *ier = ig->exdata;
+realseek_destroy(io_glue *igo) {
+  io_cb *ig = (io_cb *)igo;
 
-  if (ig->source.cb.destroycb)
-    ig->source.cb.destroycb(ig->source.cb.p);
-
-  myfree(ier);
+  if (ig->destroycb)
+    ig->destroycb(ig->p);
 }
 
 /*
@@ -284,19 +1391,19 @@ Does the reading from a buffer source
 
 static
 ssize_t 
-buffer_read(io_glue *ig, void *buf, size_t count) {
-  io_ex_buffer *ieb = ig->exdata;
+buffer_read(io_glue *igo, void *buf, size_t count) {
+  io_buffer *ig = (io_buffer *)igo;
 
-  IOL_DEB( printf("buffer_read: ieb->cpos = %ld, buf = %p, count = %d\n", (long) ieb->cpos, buf, count) );
+  IOL_DEB( fprintf(IOL_DEBs, "buffer_read: ig->cpos = %ld, buf = %p, count = %u\n", (long) ig->cpos, buf, (unsigned)count) );
 
-  if ( ieb->cpos+count > ig->source.buffer.len ) {
-    mm_log((1,"buffer_read: short read: cpos=%ld, len=%ld, count=%ld\n", (long)ieb->cpos, (long)ig->source.buffer.len, (long)count));
-    count = ig->source.buffer.len - ieb->cpos;
+  if ( ig->cpos+count > ig->len ) {
+    mm_log((1,"buffer_read: short read: cpos=%ld, len=%ld, count=%ld\n", (long)ig->cpos, (long)ig->len, (long)count));
+    count = ig->len - ig->cpos;
   }
   
-  memcpy(buf, ig->source.buffer.data+ieb->cpos, count);
-  ieb->cpos += count;
-  IOL_DEB( printf("buffer_read: count = %ld\n", (long)count) );
+  memcpy(buf, ig->data+ig->cpos, count);
+  ig->cpos += count;
+  IOL_DEB( fprintf(IOL_DEBs, "buffer_read: count = %ld\n", (long)count) );
   return count;
 }
 
@@ -354,12 +1461,12 @@ Implements seeking for a buffer source.
 
 static
 off_t
-buffer_seek(io_glue *ig, off_t offset, int whence) {
-  io_ex_buffer *ieb = ig->exdata;
+buffer_seek(io_glue *igo, off_t offset, int whence) {
+  io_buffer *ig = (io_buffer *)igo;
   off_t reqpos = 
-    calc_seek_offset(ieb->cpos, ig->source.buffer.len, offset, whence);
+    calc_seek_offset(ig->cpos, ig->len, offset, whence);
   
-  if (reqpos > ig->source.buffer.len) {
+  if (reqpos > ig->len) {
     mm_log((1, "seeking out of readable range\n"));
     return (off_t)-1;
   }
@@ -368,8 +1475,8 @@ buffer_seek(io_glue *ig, off_t offset, int whence) {
     return (off_t)-1;
   }
   
-  ieb->cpos = reqpos;
-  IOL_DEB( printf("buffer_seek(ig %p, offset %ld, whence %d)\n", ig, (long) offset, whence) );
+  ig->cpos = reqpos;
+  IOL_DEB( fprintf(IOL_DEBs, "buffer_seek(ig %p, offset %ld, whence %d)\n", ig, (long) offset, whence) );
 
   return reqpos;
   /* FIXME: How about implementing this offset handling stuff? */
@@ -377,15 +1484,14 @@ buffer_seek(io_glue *ig, off_t offset, int whence) {
 
 static
 void
-buffer_destroy(io_glue *ig) {
-  io_ex_buffer *ieb = ig->exdata;
+buffer_destroy(io_glue *igo) {
+  io_buffer *ig = (io_buffer *)igo;
 
-  if (ig->source.buffer.closecb) {
+  if (ig->closecb) {
     mm_log((1,"calling close callback %p for io_buffer\n", 
-           ig->source.buffer.closecb));
-    ig->source.buffer.closecb(ig->source.buffer.closedata);
+           ig->closecb));
+    ig->closecb(ig->closedata);
   }
-  myfree(ieb);
 }
 
 
@@ -452,7 +1558,7 @@ frees all resources used by a buffer chain.
 =cut
 */
 
-void
+static void
 io_destroy_bufchain(io_ex_bchain *ieb) {
   io_blink *cp;
   mm_log((1, "io_destroy_bufchain(ieb %p)\n", ieb));
@@ -567,16 +1673,6 @@ chaincert( io_glue *ig) {
 }
 */
 
-
-
-
-
-
-
-
-
-
-
 /*
 =item bufchain_read(ig, buf, count)
 
@@ -647,7 +1743,7 @@ bufchain_write(io_glue *ig, const void *buf, size_t count) {
 
   mm_log((1, "bufchain_write: ig = %p, buf = %p, count = %ld\n", ig, buf, (long)count));
 
-  IOL_DEB( printf("bufchain_write: ig = %p, ieb->cpos = %ld, buf = %p, count = %ld\n", ig, (long) ieb->cpos, buf, (long)count) );
+  IOL_DEB( fprintf(IOL_DEBs, "bufchain_write: ig = %p, ieb->cpos = %ld, buf = %p, count = %ld\n", ig, (long) ieb->cpos, buf, (long)count) );
   
   while(count) {
     mm_log((2, "bufchain_write: - looping - count = %ld\n", (long)count));
@@ -691,7 +1787,7 @@ static
 int
 bufchain_close(io_glue *ig) {
   mm_log((1, "bufchain_close(ig %p)\n",ig));
-  IOL_DEB( printf("bufchain_close(ig %p)\n", ig) );
+  IOL_DEB( fprintf(IOL_DEBs, "bufchain_close(ig %p)\n", ig) );
 
   return 0;  
 }
@@ -781,306 +1877,25 @@ bufchain_destroy(io_glue *ig) {
   myfree(ieb);
 }
 
-/*
- * Methods for setting up data source
- */
-
-/*
-=item io_obj_setp_buffer(io, p, len)
-
-Sets an io_object for reading from a buffer source
-
-   io  - io object that describes a source
-   p   - pointer to buffer
-   len - length of buffer
-
-=cut
-*/
-
-static void
-io_obj_setp_buffer(io_obj *io, char *p, size_t len, i_io_closebufp_t closecb, 
-                  void *closedata) {
-  io->buffer.type      = BUFFER;
-  io->buffer.data      = p;
-  io->buffer.len       = len;
-  io->buffer.closecb   = closecb;
-  io->buffer.closedata = closedata;
-}
-
-
-
-/*
-=item io_obj_setp_cb2(io, p, readcb, writecb, seekcb, closecb, destroycb)
-
-Sets an io_object for reading from a source that uses callbacks
-
-   io      - io object that describes a source
-   p         - pointer to data for callbacks
-   readcb    - read callback to read from source
-   writecb   - write callback to write to source
-   seekcb    - seek callback to seek on source
-   closecb   - flush any pending data
-   destroycb - release any extra resources
-
-=cut
-*/
-
-static void
-io_obj_setp_cb2(io_obj *io, void *p, i_io_readl_t readcb, i_io_writel_t writecb, i_io_seekl_t seekcb, i_io_closel_t closecb, i_io_destroyl_t destroycb) {
-  io->cb.type      = CBSEEK;
-  io->cb.p         = p;
-  io->cb.readcb    = readcb;
-  io->cb.writecb   = writecb;
-  io->cb.seekcb    = seekcb;
-  io->cb.closecb   = closecb;
-  io->cb.destroycb = destroycb;
-}
-
-/*
-=item io_glue_commit_types(ig)
-
-This is now effectively a no-op.
-
-=cut
-*/
-
-void
-io_glue_commit_types(io_glue *ig) {
-  io_type      inn = ig->source.type;
-
-  mm_log((1, "io_glue_commit_types(ig %p)\n", ig));
-  mm_log((1, "io_glue_commit_types: source type %d (%s)\n", inn, io_type_names[inn]));
-
-  if (ig->flags & 0x01) {
-    mm_log((1, "io_glue_commit_types: type already set up\n"));
-    return;
-  }
-
-  ig->flags |= 0x01; /* indicate source has been setup already */
-}
-
-/*
-=item io_glue_gettypes(ig, reqmeth)
-
-Returns a set of compatible interfaces to read data with.
-
-  ig      - io_glue object
-  reqmeth - request mask
-
-The request mask is a bit mask (of something that hasn't been implemented yet)
-of interfaces that it would like to read data from the source which the ig
-describes.
-
-=cut
-*/
-
-void
-io_glue_gettypes(io_glue *ig, int reqmeth) {
-
-  ig = NULL;
-  reqmeth = 0;
-  
-  /* FIXME: Implement this function! */
-  /* if (ig->source.type = 
-     if (reqmeth & IO_BUFF) */ 
-
-}
-
-
-/*
-=item io_new_bufchain()
-
-returns a new io_glue object that has the 'empty' source and but can
-be written to and read from later (like a pseudo file).
-
-=cut
-*/
-
-io_glue *
-io_new_bufchain() {
-  io_glue *ig;
-  io_ex_bchain *ieb = mymalloc(sizeof(io_ex_bchain));
-
-  mm_log((1, "io_new_bufchain()\n"));
-
-  ig = mymalloc(sizeof(io_glue));
-  memset(ig, 0, sizeof(*ig));
-  ig->source.type = BUFCHAIN;
-
-  ieb->offset = 0;
-  ieb->length = 0;
-  ieb->cpos   = 0;
-  ieb->gpos   = 0;
-  ieb->tfill  = 0;
-  
-  ieb->head   = io_blink_new();
-  ieb->cp     = ieb->head;
-  ieb->tail   = ieb->head;
-  
-  ig->exdata    = ieb;
-  ig->readcb    = bufchain_read;
-  ig->writecb   = bufchain_write;
-  ig->seekcb    = bufchain_seek;
-  ig->closecb   = bufchain_close;
-  ig->destroycb = bufchain_destroy;
-
-  return ig;
-}
-
-/*
-=item io_new_buffer(data, len)
-
-Returns a new io_glue object that has the source defined as reading
-from specified buffer.  Note that the buffer is not copied.
-
-   data - buffer to read from
-   len - length of buffer
-
-=cut
-*/
-
-io_glue *
-io_new_buffer(char *data, size_t len, i_io_closebufp_t closecb, void *closedata) {
-  io_glue *ig;
-  io_ex_buffer *ieb = mymalloc(sizeof(io_ex_buffer));
-  
-  mm_log((1, "io_new_buffer(data %p, len %ld, closecb %p, closedata %p)\n", data, (long)len, closecb, closedata));
-
-  ig = mymalloc(sizeof(io_glue));
-  memset(ig, 0, sizeof(*ig));
-  io_obj_setp_buffer(&ig->source, data, len, closecb, closedata);
-  ig->flags = 0;
-
-  ieb->offset = 0;
-  ieb->cpos   = 0;
-  
-  ig->exdata    = ieb;
-  ig->readcb    = buffer_read;
-  ig->writecb   = buffer_write;
-  ig->seekcb    = buffer_seek;
-  ig->closecb   = buffer_close;
-  ig->destroycb = buffer_destroy;
-
-  return ig;
-}
-
-
-/*
-=item io_new_fd(fd)
-
-returns a new io_glue object that has the source defined as reading
-from specified filedescriptor.  Note that the the interface to recieving
-data from the io_glue callbacks hasn't been done yet.
-
-   fd - file descriptor to read/write from
-
-=cut
-*/
-
-io_glue *
-io_new_fd(int fd) {
-  io_glue *ig;
-
-  mm_log((1, "io_new_fd(fd %d)\n", fd));
-
-  ig = mymalloc(sizeof(io_glue));
-  memset(ig, 0, sizeof(*ig));
-  ig->source.type = FDSEEK;
-  ig->source.fdseek.fd = fd;
-  ig->flags = 0;
-
-  ig->exdata    = NULL;
-  ig->readcb    = fd_read;
-  ig->writecb   = fd_write;
-  ig->seekcb    = fd_seek;
-  ig->closecb   = fd_close;
-  ig->sizecb    = fd_size;
-  ig->destroycb = NULL;
-      
-  mm_log((1, "(%p) <- io_new_fd\n", ig));
-  return ig;
-}
-
-io_glue *io_new_cb(void *p, i_io_readl_t readcb, i_io_writel_t writecb, 
-                  i_io_seekl_t seekcb, i_io_closel_t closecb, 
-                  i_io_destroyl_t destroycb) {
-  io_glue *ig;
-  io_ex_rseek *ier = mymalloc(sizeof(io_ex_rseek));
-
-  mm_log((1, "io_new_cb(p %p, readcb %p, writecb %p, seekcb %p, closecb %p, "
-          "destroycb %p)\n", p, readcb, writecb, seekcb, closecb, destroycb));
-  ig = mymalloc(sizeof(io_glue));
-  memset(ig, 0, sizeof(*ig));
-  io_obj_setp_cb2(&ig->source, p, readcb, writecb, seekcb, closecb, destroycb);
-  mm_log((1, "(%p) <- io_new_cb\n", ig));
-
-  ier->offset = 0;
-  ier->cpos   = 0;
-  
-  ig->exdata    = ier;
-  ig->readcb    = realseek_read;
-  ig->writecb   = realseek_write;
-  ig->seekcb    = realseek_seek;
-  ig->closecb   = realseek_close;
-  ig->destroycb = realseek_destroy;
-
-  return ig;
-}
-
-/*
-=item io_slurp(ig)
-
-Takes the source that the io_glue is bound to and allocates space
-for a return buffer and returns the entire content in a single buffer.
-Note: This only works for io_glue objects that contain a bufchain.  It
-is usefull for saving to scalars and such.
-
-   ig - io_glue object
-   c  - pointer to a pointer to where data should be copied to
-
-=cut
-*/
-
-size_t
-io_slurp(io_glue *ig, unsigned char **c) {
-  ssize_t rc;
-  off_t orgoff;
-  io_ex_bchain *ieb;
-  unsigned char *cc;
-  io_type inn = ig->source.type;
-  
-  if ( inn != BUFCHAIN ) {
-    i_fatal(0, "io_slurp: called on a source that is not from a bufchain\n");
-  }
-
-  ieb = ig->exdata;
-  cc = *c = mymalloc( ieb->length );
-  
-  orgoff = ieb->gpos;
-  
-  bufchain_seek(ig, 0, SEEK_SET);
-  
-  rc = bufchain_read(ig, cc, ieb->length);
-
-  if (rc != ieb->length)
-    i_fatal(1, "io_slurp: bufchain_read returned an incomplete read: rc = %d, request was %d\n", rc, ieb->length);
-
-  return rc;
-}
-
 /*
 =item fd_read(ig, buf, count)
 
+Read callback for file descriptor IO objects.
+
 =cut
 */
-static ssize_t fd_read(io_glue *ig, void *buf, size_t count) {
+static ssize_t fd_read(io_glue *igo, void *buf, size_t count) {
+  io_fdseek *ig = (io_fdseek *)igo;
   ssize_t result;
 #ifdef _MSC_VER
-  result = _read(ig->source.fdseek.fd, buf, count);
+  result = _read(ig->fd, buf, count);
 #else
-  result = read(ig->source.fdseek.fd, buf, count);
+  result = read(ig->fd, buf, count);
 #endif
 
+  IOL_DEB(fprintf(IOL_DEBs, "fd_read(%p, %p, %u) => %d\n", ig, buf,
+                 (unsigned)count, (int)result));
+
   /* 0 is valid - means EOF */
   if (result < 0) {
     i_push_errorf(0, "read() failure: %s (%d)", my_strerror(errno), errno);
@@ -1089,14 +1904,18 @@ static ssize_t fd_read(io_glue *ig, void *buf, size_t count) {
   return result;
 }
 
-static ssize_t fd_write(io_glue *ig, const void *buf, size_t count) {
+static ssize_t fd_write(io_glue *igo, const void *buf, size_t count) {
+  io_fdseek *ig = (io_fdseek *)igo;
   ssize_t result;
 #ifdef _MSC_VER
-  result = _write(ig->source.fdseek.fd, buf, count);
+  result = _write(ig->fd, buf, count);
 #else
-  result = write(ig->source.fdseek.fd, buf, count);
+  result = write(ig->fd, buf, count);
 #endif
 
+  IOL_DEB(fprintf(IOL_DEBs, "fd_write(%p, %p, %u) => %d\n", ig, buf,
+                 (unsigned)count, (int)result));
+
   if (result <= 0) {
     i_push_errorf(errno, "write() failure: %s (%d)", my_strerror(errno), errno);
   }
@@ -1104,12 +1923,13 @@ static ssize_t fd_write(io_glue *ig, const void *buf, size_t count) {
   return result;
 }
 
-static off_t fd_seek(io_glue *ig, off_t offset, int whence) {
+static off_t fd_seek(io_glue *igo, off_t offset, int whence) {
+  io_fdseek *ig = (io_fdseek *)igo;
   off_t result;
 #ifdef _MSC_VER
-  result = _lseek(ig->source.fdseek.fd, offset, whence);
+  result = _lseek(ig->fd, offset, whence);
 #else
-  result = lseek(ig->source.fdseek.fd, offset, whence);
+  result = lseek(ig->fd, offset, whence);
 #endif
 
   if (result == (off_t)-1) {
@@ -1130,53 +1950,6 @@ static ssize_t fd_size(io_glue *ig) {
   return -1;
 }
 
-/*
-=item io_glue_destroy(ig)
-
-A destructor method for io_glue objects.  Should clean up all related buffers.
-Might leave us with a dangling pointer issue on some buffers.
-
-   ig - io_glue object to destroy.
-
-=cut
-*/
-
-void
-io_glue_destroy(io_glue *ig) {
-  mm_log((1, "io_glue_DESTROY(ig %p)\n", ig));
-
-  if (ig->destroycb)
-    ig->destroycb(ig);
-  
-  myfree(ig);
-}
-
-/*
-=back
-
-=head1 INTERNAL FUNCTIONS
-
-=over
-
-=item my_strerror
-
-Calls strerror() and ensures we don't return NULL.
-
-On some platforms it's possible for strerror() to return NULL, this
-wrapper ensures we only get non-NULL values.
-
-=cut
-*/
-
-static
-const char *my_strerror(int err) {
-  const char *result = strerror(err);
-
-  if (!result)
-    result = "Unknown error";
-
-  return result;
-}
 
 /*
 =back
index ec164c0..db7c577 100644 (file)
--- a/iolayer.h
+++ b/iolayer.h
@@ -4,10 +4,9 @@
 
 /* How the IO layer works:
  * 
- * Start by getting an io_glue object.  Then define its
- * datasource via io_obj_setp_buffer or io_obj_setp_cb.  Before
- * using the io_glue object be sure to call io_glue_commit_types().
- * After that data can be read via the io_glue->readcb() method.
+ * Start by getting an io_glue object by calling the appropriate
+ * io_new...() function.  After that data can be read via the
+ * io_glue->readcb() method.
  *
  */
 
 #define IO_TEMP_SEEK 1<<1L
 
 
-void io_glue_commit_types(io_glue *ig);
 void io_glue_gettypes    (io_glue *ig, int reqmeth);
 
 /* XS functions */
 io_glue *io_new_fd(int fd);
 io_glue *io_new_bufchain(void);
-io_glue *io_new_buffer(char *data, size_t len, i_io_closebufp_t closecb, void *closedata);
+io_glue *io_new_buffer(const char *data, size_t len, i_io_closebufp_t closecb, void *closedata);
 io_glue *io_new_cb(void *p, i_io_readl_t readcb, i_io_writel_t writecb, i_io_seekl_t seekcb, i_io_closel_t closecb, i_io_destroyl_t destroycb);
 size_t   io_slurp(io_glue *ig, unsigned char **c);
 void     io_glue_destroy(io_glue *ig);
 
+void i_io_dump(io_glue *ig, int flags);
+
+/* Buffered I/O */
+extern int i_io_getc_imp(io_glue *ig);
+extern int i_io_peekc_imp(io_glue *ig);
+extern ssize_t i_io_peekn(io_glue *ig, void *buf, size_t size);
+extern int i_io_putc_imp(io_glue *ig, int c);
+extern ssize_t i_io_read(io_glue *ig, void *buf, size_t size);
+extern ssize_t i_io_write(io_glue *ig, const void *buf, size_t size);
+extern off_t i_io_seek(io_glue *ig, off_t offset, int whence);
+extern int i_io_flush(io_glue *ig);
+extern int i_io_close(io_glue *ig);
+extern int i_io_set_buffered(io_glue *ig, int buffered);
+extern ssize_t i_io_gets(io_glue *ig, char *, size_t, int);
+
 #endif /* _IOLAYER_H_ */
index f7fdac9..167ef30 100644 (file)
@@ -46,55 +46,59 @@ extern char *io_type_names[];
 
 /* Structures to describe data sources */
 
-typedef struct {
-  io_type      type;
-  int          fd;
-} io_fdseek;
-
-typedef struct {
-  io_type      type;           /* Must be first parameter */
-  char         *name;          /* Data source name */
-  char         *data;
-  size_t       len;
-  i_io_closebufp_t     closecb;        /* free memory mapped segment or decrement refcount */
-  void          *closedata;
-} io_buffer;
-
-typedef struct {
-  io_type      type;           /* Must be first parameter */
-  char         *name;          /* Data source name */
-  void         *p;             /* Callback data */
-  i_io_readl_t readcb;
-  i_io_writel_t        writecb;
-  i_io_seekl_t seekcb;
-  i_io_closel_t closecb;
-  i_io_destroyl_t      destroycb;
-} io_cb;
-
-typedef union {
-  io_type       type;
-  io_fdseek     fdseek;
-  io_buffer    buffer;
-  io_cb                cb;
-} io_obj;
-
 struct i_io_glue_t {
-  io_obj       source;
-  int          flags;          /* Flags */
-  void         *exdata;        /* Pair specific data */
+  io_type type;
+  void *exdata;
   i_io_readp_t readcb;
   i_io_writep_t        writecb;
   i_io_seekp_t seekcb;
   i_io_closep_t        closecb;
   i_io_sizep_t sizecb;
   i_io_destroyp_t destroycb;
+  unsigned char *buffer;
+  unsigned char *read_ptr;
+  unsigned char *read_end;
+  unsigned char *write_ptr;
+  unsigned char *write_end;
+  size_t buf_size;
+
+  /* non-zero if we encountered EOF */
+  int buf_eof;
+
+  /* non-zero if we've seen an error */
+  int error;
+
+  /* if non-zero we do write buffering (enabled by default) */
+  int buffered;
 };
 
-#define i_io_type(ig) ((ig)->source.ig_type)
-#define i_io_read(ig, buf, size) ((ig)->readcb((ig), (buf), (size)))
-#define i_io_write(ig, data, size) ((ig)->writecb((ig), (data), (size)))
-#define i_io_seek(ig, offset, whence) ((ig)->seekcb((ig), (offset), (whence)))
-#define i_io_close(ig) ((ig)->closecb(ig))
+#define I_IO_DUMP_CALLBACKS 1
+#define I_IO_DUMP_BUFFER 2
+#define I_IO_DUMP_STATUS 4
+#define I_IO_DUMP_DEFAULT (I_IO_DUMP_BUFFER | I_IO_DUMP_STATUS)
 
+#define i_io_type(ig) ((ig)->source.ig_type)
+#define i_io_raw_read(ig, buf, size) ((ig)->readcb((ig), (buf), (size)))
+#define i_io_raw_write(ig, data, size) ((ig)->writecb((ig), (data), (size)))
+#define i_io_raw_seek(ig, offset, whence) ((ig)->seekcb((ig), (offset), (whence)))
+#define i_io_raw_close(ig) ((ig)->closecb(ig))
+#define i_io_is_buffered(ig) ((int)((ig)->buffered))
+
+#define i_io_getc(ig) \
+  ((ig)->read_ptr < (ig)->read_end ? \
+     *((ig)->read_ptr++) : \
+     i_io_getc_imp(ig))
+#define i_io_peekc(ig) \
+  ((ig)->read_ptr < (ig)->read_end ? \
+   *((ig)->read_ptr) :              \
+     i_io_peekc_imp(ig))
+#define i_io_putc(ig, c) \
+  ((ig)->write_ptr < (ig)->write_end && !(ig)->error ? \
+    *(ig)->write_ptr++ = (c) :        \
+      i_io_putc_imp(ig, (c)))
+#define i_io_eof(ig) \
+  ((ig)->read_ptr == (ig)->read_end && (ig)->buf_eof)
+#define i_io_error(ig) \
+  ((ig)->read_ptr == (ig)->read_end && (ig)->error)
 
 #endif
index 4e7c6ce..f080131 100644 (file)
@@ -60,6 +60,13 @@ Imager::APIRef - Imager's C API - reference.
                           i_fr_triangle, 0, i_fts_grid, 9, 1, segs);
   i_fill_destroy(fill);
 
+  # I/O Layers
+  ssize_t count = i_io_peekn(ig, buffer, sizeof(buffer));
+  ssize_t result = i_io_write(io, buffer, size)
+  char buffer[BUFSIZ]
+  ssize_t len = i_io_gets(buffer, sizeof(buffer), '\n');
+  io_glue_destroy(ig);
+
   # Image
 
   # Image creation/destruction
@@ -1028,6 +1035,270 @@ Call to destroy any fill object.
 From: File fills.c
 
 
+=back
+
+=head2 I/O Layers
+
+=over
+
+=item io_new_bufchain()
+
+returns a new io_glue object that has the 'empty' source and but can
+be written to and read from later (like a pseudo file).
+
+
+=for comment
+From: File iolayer.c
+
+=item io_new_buffer(data, length)
+
+Returns a new io_glue object that has the source defined as reading
+from specified buffer.  Note that the buffer is not copied.
+
+   data - buffer to read from
+   length - length of buffer
+
+
+=for comment
+From: File iolayer.c
+
+=item io_new_cb(p, read_cb, write_cb, seek_cb, close_cb, destroy_cb)
+
+Create a new I/O layer object that calls your supplied callbacks.
+
+In general the callbacks should behave like the corresponding POSIX
+primitives.
+
+=over
+
+=item *
+
+C<read_cb>(p, buffer, length) should read up to C<length> bytes into
+C<buffer> and return the number of bytes read.  At end of file, return
+0.  On error, return -1.
+
+=item *
+
+C<write_cb>(p, buffer, length) should write up to C<length> bytes from
+C<buffer> and return the number of bytes written.  A return value <= 0
+will be treated as an error.
+
+=item *
+
+C<seekcb>(p, offset, whence) should seek and return the new offset.
+
+=item *
+
+C<close_cb>(p) should return 0 on success, -1 on failure.
+
+=item *
+
+C<destroy_cb>(p) should release any memory specific to your callback
+handlers.
+
+=back
+
+
+=for comment
+From: File iolayer.c
+
+=item io_new_fd(fd)
+
+returns a new io_glue object that has the source defined as reading
+from specified file descriptor.  Note that the the interface to receiving
+data from the io_glue callbacks hasn't been done yet.
+
+   fd - file descriptor to read/write from
+
+
+=for comment
+From: File iolayer.c
+
+=item i_io_close(io)
+
+Flush any pending output and perform the close action for the stream.
+
+Returns 0 on success.
+
+
+=for comment
+From: File iolayer.c
+
+=item i_io_flush(io)
+
+Flush any buffered output.
+
+Returns true on success,
+
+
+=for comment
+From: File iolayer.c
+
+=item i_io_getc(ig)
+
+A macro to read a single byte from a buffered I/O glue object.
+
+Returns EOF on failure, or a byte.
+
+
+=for comment
+From: File iolayer.c
+
+=item i_io_gets(ig, buffer, size, end_of_line)
+
+  char buffer[BUFSIZ]
+  ssize_t len = i_io_gets(buffer, sizeof(buffer), '\n');
+
+Read up to C<size>-1 bytes from the stream C<ig> into C<buffer>.
+
+If the byte C<end_of_line> is seen then no further bytes will be read.
+
+Returns the number of bytes read.
+
+Always C<NUL> terminates the buffer.
+
+
+=for comment
+From: File iolayer.c
+
+=item i_io_peekc(ig)
+
+Read the next character from the stream without advancing the stream.
+
+On error or end of file, return EOF.
+
+For unbuffered streams a single character buffer will be setup.
+
+
+=for comment
+From: File iolayer.c
+
+=item i_io_peekn(ig, buffer, size)
+
+  ssize_t count = i_io_peekn(ig, buffer, sizeof(buffer));
+
+Buffer at least C<size> (at most C<< ig->buf_size >> bytes of data
+from the stream and return C<size> bytes of it to the caller in
+C<buffer>.
+
+This ignores the buffered state of the stream, and will always setup
+buffering if needed.
+
+If no C<type> parameter is provided to Imager::read() or
+Imager::read_multi(), Imager will call C<i_io_peekn()> when probing
+for the file format.
+
+Returns -1 on error, 0 if there is no data before EOF, or the number
+of bytes read into C<buffer>.
+
+
+=for comment
+From: File iolayer.c
+
+=item i_io_putc(ig, c)
+
+Write a single character to the stream.
+
+On success return c, on error returns EOF
+
+
+=for comment
+From: File iolayer.c
+
+=item i_io_read(io, buffer, size)
+
+Read up to C<size> bytes from the stream C<io> into C<buffer>.
+
+Returns the number of bytes read.  Returns 0 on end of file.  Returns
+-1 on error.
+
+
+=for comment
+From: File iolayer.c
+
+=item i_io_seek(io, offset, whence)
+
+Seek within the stream.
+
+Acts like perl's seek.
+
+
+=for comment
+From: File iolayer.c
+
+=item i_io_set_buffered(io, buffered)
+
+Set the buffering mode of the stream.
+
+If you switch buffering off on a stream with buffering on:
+
+=over
+
+=item *
+
+any buffered output will be flushed.
+
+=item *
+
+any existing buffered input will be consumed before reads become
+unbuffered.
+
+=back
+
+Returns true on success.  This may fail if any buffered output cannot
+be flushed.
+
+
+=for comment
+From: File iolayer.c
+
+=item i_io_write(io, buffer, size)
+
+  ssize_t result = i_io_write(io, buffer, size)
+
+Write to the given I/O stream.
+
+Returns the number of bytes written.
+
+
+=for comment
+From: File iolayer.c
+
+=item io_slurp(ig, c)
+
+Takes the source that the io_glue is bound to and allocates space for
+a return buffer and returns the entire content in a single buffer.
+Note: This only works for io_glue objects created by
+io_new_bufchain().  It is useful for saving to scalars and such.
+
+   ig - io_glue object
+   c  - pointer to a pointer to where data should be copied to
+
+  char *data;
+  size_t size = io_slurp(ig, &data);
+  ... do something with the data ...
+  myfree(data);
+
+io_slurp() will abort the program if the supplied I/O layer is not
+from io_new_bufchain().
+
+
+=for comment
+From: File iolayer.c
+
+=item io_glue_destroy(ig)
+
+  io_glue_destroy(ig);
+
+Destroy an io_glue objects.  Should clean up all related buffers.
+
+   ig - io_glue object to destroy.
+
+
+=for comment
+From: File iolayer.c
+
+
 =back
 
 =head2 Image
index 6c16257..0bc8f81 100644 (file)
@@ -73,7 +73,7 @@ or L</write_types()> methods useful.
 
 =over 
 
-=item read
+=item read()
 
 Reading writing to and from files is simple, use the C<read()>
 method to read an image:
@@ -88,8 +88,8 @@ supply the file name:
   $img->read(file => $filename)
     or die "Cannot read $filename: ", $img->errstr;
 
-The read() method accepts the C<allow_partial> parameter.  If this is
-non-zero then read() can return true on an incomplete image and set
+The read() method accepts the C<allow_incomplete> parameter.  If this
+is non-zero then read() can return true on an incomplete image and set
 the C<i_incomplete> tag.
 
 From Imager 0.68 you can supply most read() parameters to the new()
@@ -100,14 +100,14 @@ Imager->errstr() for the cause:
   my $img = Imager->new(file => $filename)
     or die "Cannot read $filename: ", Imager->errstr;
 
-=item write
+=item write()
 
 and the C<write()> method to write an image:
 
   $img->write(file=>$filename, type=>$type)
     or die "Cannot write $filename: ", $img->errstr;
 
-=item read_multi
+=item read_multi()
 
 If you're reading from a format that supports multiple images per
 file, use the C<read_multi()> method:
@@ -118,7 +118,7 @@ file, use the C<read_multi()> method:
 As with the read() method, Imager will normally detect the C<type>
 automatically.
 
-=item write_multi
+=item write_multi()
 
 and if you want to write multiple images to a single file use the
 C<write_multi()> method:
@@ -252,34 +252,33 @@ writing.
 
 =item *
 
-C<callback> - Imager will make calls back to your supplied coderefs to
-read, write and seek from/to/through the image file.
+C<callback>, C<readcb>, C<writecb>, C<seekcb>, C<closecb> - Imager
+will make calls back to your supplied coderefs to read, write and seek
+from/to/through the image file.  See L</"I/O Callbacks"> below for details.
 
-When reading from a file you can use either C<callback> or C<readcb>
-to supply the read callback, and when writing C<callback> or
-C<writecb> to supply the write callback.
+=item *
 
-When writing you can also supply the C<maxbuffer> option to set the
-maximum amount of data that will be buffered before your write
-callback is called.  Note: the amount of data supplied to your
-callback can be smaller or larger than this size.
+C<io> - an L<Imager::IO> object.
 
-The read callback is called with 2 parameters, the minimum amount of
-data required, and the maximum amount that Imager will store in it's C
-level buffer.  You may want to return the minimum if you have a slow
-data source, or the maximum if you have a fast source and want to
-prevent many calls to your perl callback.  The read data should be
-returned as a scalar.
+=back
 
-Your write callback takes exactly one parameter, a scalar containing
-the data to be written.  Return true for success.
+X<buffering>X<unbuffered>By default Imager will use buffered I/O when
+reading or writing an image.  You can disabled buffering for output by
+supplying a C<< buffered => 0 >> parameter to C<write()> or
+C<write_multi()>.
 
-The seek callback takes 2 parameters, a I<POSITION>, and a I<WHENCE>,
-defined in the same way as perl's seek function.
+=head2 I/O Callbacks
 
-You can also supply a C<closecb> which is called with no parameters
-when there is no more data to be written.  This could be used to flush
-buffered data.
+When reading from a file you can use either C<callback> or C<readcb>
+to supply the read callback, and when writing C<callback> or
+C<writecb> to supply the write callback.
+
+Some file formats, currently only C<TIFF>, also require a C<seekcb>
+parameter to change position in the file.  If no C<seekcb> parameter
+is provided a default will be provided that fails.
+
+You can also provide a C<closecb> parameter called when writing the
+file is complete. 
 
   # contrived
   my $data;
@@ -290,12 +289,76 @@ buffered data.
   Imager->write_multi({ callback => \&mywrite, type => 'gif'}, @images)
     or die Imager->errstr;
 
-Note that for reading you'll almost always need to provide a
-C<seekcb>.
+=head3 C<readcb>
+
+The read callback is called with 2 parameters:
+
+=over
+
+=item *
+
+C<size> - the minimum amount of data required.
+
+=item *
+
+C<maxsize> - previously this was the maximum amount of data returnable
+- currently it's always the same as C<size>
 
 =back
 
+Your read callback should return the data as a scalar:
+
+=over
+
+=item *
+
+on success, a string containing the bytes read.
+
+=item *
+
+on end of file, an empty string
+
+=item *
+
+on error, C<undef>.
+
+=back
+
+If your return value contains more data than C<size> Imager will
+panic.
+
+Your return value must not contain any characters over C<\xFF> or
+Imager will panic.
+
+=head3 C<writecb>
+
+Your write callback takes exactly one parameter, a scalar containing
+the data to be written.
+
+Return true for success.
+
+=head3 C<seekcb>
+
+The seek callback takes 2 parameters, a I<POSITION>, and a I<WHENCE>,
+defined in the same way as perl's seek function.
+
+Previously you always needed a C<seekcb> callback if you called
+Imager's L</read()> or L</read_multi()> without a C<type> parameter,
+but this is no longer necessary unless the file handler requires
+seeking, such as for TIFF files.
+
+Returns the new position in the file, or -1 on failure.
+
+=head3 C<closecb>
+
+You can also supply a C<closecb> which is called with no parameters
+when there is no more data to be written.  This could be used to flush
+buffered data.
+
+Return true on success.
+
 =head2 Guessing types
+X<FORMATGUESS>
 
 When writing to a file, if you don't supply a C<type> parameter Imager
 will attempt to guess it from the file name.  This is done by calling
@@ -308,6 +371,7 @@ C<\&Imager::def_guess_type>.
 =over
 
 =item def_guess_type()
+X<methods, def_guess_type()>
 
 This is the default function Imager uses to derive a file type from a
 file name.  This is a function, not a method.
index fa7e700..b614b1e 100644 (file)
@@ -30,7 +30,7 @@ Filters are operations that have similar calling interface.
 
 =over
 
-=item filter
+=item filter()
 
 Parameters:
 
index ec2b618..2dae013 100644 (file)
@@ -20,11 +20,56 @@ same code to work with disk files, in memory data and callbacks.
 If you're writing an Imager file handler your code will be passed an
 Imager::IO object to write to or read from.
 
-=head1 METHODS
+X<UTF-8>X<Unicode>Note that Imager::IO can only work with collections of bytes -
+if you need to read UTF-8 data you will need to read the bytes and
+decode them.  If you want to write UTF-8 data you will need to encode
+your characters to bytes and write the bytes.
+
+=head1 CONSTRUCTORS
+
+=over
+
+=item new_fd($fd)
+
+Create a new I/O layer based on a file descriptor.
+
+  my $io = Imager::IO->new(fileno($fh));
+
+=item new_buffer($data)
+
+Create a new I/O layer based on a memory buffer.
+
+The supplied variable must not be changed on the the life of the I/O
+object.
+
+Buffer I/O layers are read only.
+
+=item new_cb($writecb, $readcb, $seekcb, $closecb)
+
+Create a new I/O layer based on callbacks.  See 
+L<Imager::Files/"I/O Callbacks"> for details on the behavior of 
+the callbacks.
+
+=item new_bufchain()
+
+Create a new C<bufchain> based I/O layer.  This accumulates the file
+data as a chain of buffers starting from an empty stream.
+
+Use the L</slurp()> method to retrieve the accumulated content into a
+perl string.
+
+=back
+
+=head1 BUFFERED I/O METHODS
+
+These methods use buffered I/O to improve performance unless you call
+set_buffered() to disable buffering.
+
+Prior to Imager 0.86 the write and read methods performed raw I/O.
 
 =over
 
-=item write
+=item write($data)
 
 Call to write to the file.  Returns the number of bytes written.  The
 data provided may contain only characters \x00 to \xFF - characters
@@ -37,7 +82,7 @@ Returns -1 on error, though in most cases if the result of the write
 isn't the number of bytes supplied you'll want to treat it as an error
 anyway.
 
-=item read
+=item read($buffer, $size)
 
   my $buffer;
   my $count = $io->read($buffer, $max_bytes);
@@ -47,16 +92,17 @@ and stores them in I<$buffer>.  Returns the number of bytes read on
 success or an empty list on failure.  Note that a read of zero bytes
 is B<not> a failure, this indicates end of file.
 
-=item read2
+=item read2($size)
 
   my $buffer = $io->read2($max_bytes);
 
 An alternative interface to read, that might be simpler to use in some
 cases.
 
-Returns the data read or an empty list.
+Returns the data read or an empty list.  At end of file the data read
+will be an empty string.
 
-=item seek
+=item seek($offset, $whence)
 
   my $new_position = $io->seek($offset, $whence);
 
@@ -83,20 +129,230 @@ file.
 Note that seeking past the end of the file may or may not result in an
 error.
 
+Any buffered output will be flushed, if flushing fails, seek() will
+return -1.
+
 Returns the new position in the file, or -1 on error.
 
-=item close
+=item getc()
+
+Return the next byte from the stream.
+
+Returns the ordinal of the byte or -1 on error or end of file.
+
+  while ((my $c = $io->getc) != -1) {
+    print chr($c);
+  }
+
+=item gets()
+
+=item gets($max_size)
+
+=item gets($max_size, $end_of_line)
+
+Returns the next line of input from the stream, as terminated by
+C<end_of_line>.
+
+The default C<max_size> is 8192.
+
+The default C<end_of_line> is C<ord "\n">.
+
+Returns nothing if the stream is in error or at end of file.
+
+Returns the line as a string, including the line terminator (if one
+was found) on success.
+
+  while (defined(my $line = $io->gets)) {
+    # do something with $line
+  }
+
+=item peekc()
+
+Return the buffered next character from the stream, loading the buffer
+if necessary.
+
+For an unbuffered stream a buffer will be setup and loaded with a
+single character.
+
+Returns the ordinal of the byte or -1 on error or end of file.
+
+  my $c = $io->peekc;
+
+=item peekn($size)
+
+Returns up to the next C<size> bytes from the file as a string.
+
+Only up to the stream buffer size bytes (currently 8192) can be peeked.
+
+This method ignores the buffering state of the stream.
+
+Returns nothing on EOF.
+
+  my $s = $io->peekn(4);
+  if ($s =~ /^(II|MM)\*\0/) {
+    print "TIFF image";
+  }
+
+=item putc($code)
+
+Write a single character to the stream.
+
+Returns C<code> on success, or -1 on failure.
+
+=item close()
 
   my $result = $io->close;
 
-Call when you're with the file.  If the IO object is connected to a
-file this won't close the file handle, but buffers may be flushed (if
-any).
+Call when you're done with the file.  If the IO object is connected to
+a file this won't close the file handle, but buffers may be flushed
+(if any).
+
+Returns 0 on success, -1 on failure.
+
+=item eof()
+
+  $io->eof
+
+Test if the stream is at end of file.  No further read requests will
+be passed to your read callback until you seek().
+
+=item error()
+
+Test if the stream has encountered a read or write error.
+
+  my $data = $io->read2(100);
+  $io->error
+     and die "Failed";
+
+When the stream has the error flag set no further read or write
+requests will be passed to your callbacks until you seek.
+
+=item flush()
+
+  $io->flush
+    or die "Flush error";
+
+Flush any buffered output.  This will not call lower write layers when
+the stream has it's error flag set.
+
+Returns a true value on success.
+
+=item is_buffered()
+
+Test if buffering is enabled for this stream.
+
+Returns a true value if the stream is buffered.
+
+=item set_buffered($enabled)
+
+If C<$enabled> is a non-zero integer, enable buffering, other disable
+it.
+
+Disabling buffering will flush any buffered output, but any buffered
+input will be retained and consumed by input methods.
+
+Returns true if any buffered output was flushed successfully, false if
+there was an error flushing output.
+
+=back
+
+=head1 RAW I/O METHODS
+
+These call the underlying I/O abstraction directly.
+
+=over
+
+=item raw_write()
+
+Call to write to the file.  Returns the number of bytes written.  The
+data provided may contain only characters \x00 to \xFF - characters
+outside this range will cause this method to croak().
+
+If you supply a UTF-8 flagged string it will be converted to a byte
+string, which may have a performance impact.
+
+Returns -1 on error, though in most cases if the result of the write
+isn't the number of bytes supplied you'll want to treat it as an error
+anyway.
+
+=item raw_read()
+
+  my $buffer;
+  my $count = $io->raw_read($buffer, $max_bytes);
+
+Reads up to I<$max_bytes> bytes from the current position in the file
+and stores them in I<$buffer>.  Returns the number of bytes read on
+success or an empty list on failure.  Note that a read of zero bytes
+is B<not> a failure, this indicates end of file.
+
+=item raw_read2()
+
+  my $buffer = $io->raw_read2($max_bytes);
+
+An alternative interface to raw_read, that might be simpler to use in some
+cases.
+
+Returns the data read or an empty list.
+
+=item raw_seek()
+
+  my $new_position = $io->raw_seek($offset, $whence);
+
+Seek to a new position in the file.  Possible values for I<$whence> are:
+
+=over
+
+=item *
+
+C<SEEK_SET> - I<$offset> is the new position in the file.
+
+=item *
+
+C<SEEK_CUR> - I<$offset> is the offset from the current position in
+the file.
+
+=item *
+
+C<SEEK_END> - I<$offset> is the offset relative to the end of the
+file.
+
+=back
+
+Note that seeking past the end of the file may or may not result in an
+error.
+
+Returns the new position in the file, or -1 on error.
+
+=item raw_close()
+
+  my $result = $io->raw_close;
+
+Call when you're done with the file.  If the IO object is connected to
+a file this won't close the file handle.
 
 Returns 0 on success, -1 on failure.
 
 =back
 
+=head1 UTILITY METHODS
+
+=over
+
+=item slurp()
+
+Retrieve the data accumulated from an I/O layer object created with
+the new_bufchain() method.
+
+  my $data = $io->slurp;
+
+=item dump()
+
+Dump the internal buffering state of the I/O object to C<stderr>.
+
+  $io->dump();
+
+=back
+
 =head1 AUTHOR
 
 Tony Cook <tonyc@cpan.org>
diff --git a/pnm.c b/pnm.c
index 31afbd5..5dfd0c8 100644 (file)
--- a/pnm.c
+++ b/pnm.c
@@ -36,169 +36,66 @@ Some of these functions are internal.
 */
 
 
-#define BSIZ 1024
 #define misspace(x) (x==' ' || x=='\n' || x=='\r' || x=='\t' || x=='\f' || x=='\v')
 #define misnumber(x) (x <= '9' && x>='0')
 
 static char *typenames[]={"ascii pbm", "ascii pgm", "ascii ppm", "binary pbm", "binary pgm", "binary ppm"};
 
 /*
- * Type to encapsulate the local buffer
- * management skipping over in a file 
- */
-
-typedef struct {
-  io_glue *ig;
-  int len;
-  int cp;
-  char buf[BSIZ];
-} mbuf;
-
-
-static
-void init_buf(mbuf *mb, io_glue *ig) {
-  mb->len = 0;
-  mb->cp  = 0;
-  mb->ig  = ig;
-}
-
-
-
-/*
-=item gnext(mbuf *mb)
-
-Fetches a character and advances in stream by one character.  
-Returns a pointer to the byte or NULL on failure (internal).
-
-   mb - buffer object
-
-=cut
-*/
-
-#define gnext(mb) (((mb)->cp == (mb)->len) ? gnextf(mb) : (mb)->buf + (mb)->cp++)
-
-static
-char *
-gnextf(mbuf *mb) {
-  io_glue *ig = mb->ig;
-  if (mb->cp == mb->len) {
-    mb->cp = 0;
-    mb->len = ig->readcb(ig, mb->buf, BSIZ);
-    if (mb->len == -1) {
-      i_push_error(errno, "file read error");
-      mm_log((1, "i_readpnm: read error\n"));
-      return NULL;
-    }
-    if (mb->len == 0) {
-      mm_log((1, "i_readpnm: end of file\n"));
-      return NULL;
-    }
-  }
-  return &mb->buf[mb->cp++];
-}
-
-
-/*
-=item gpeek(mbuf *mb)
-
-Fetches a character but does NOT advance.  Returns a pointer to
-the byte or NULL on failure (internal).
-
-   mb - buffer object
-
-=cut
-*/
-
-#define gpeek(mb) ((mb)->cp == (mb)->len ? gpeekf(mb) : (mb)->buf + (mb)->cp)
-
-static
-char *
-gpeekf(mbuf *mb) {
-  io_glue *ig = mb->ig;
-  if (mb->cp == mb->len) {
-    mb->cp = 0;
-    mb->len = ig->readcb(ig, mb->buf, BSIZ);
-    if (mb->len == -1) {
-      i_push_error(errno, "read error");
-      mm_log((1, "i_readpnm: read error\n"));
-      return NULL;
-    }
-    if (mb->len == 0) {
-      mm_log((1, "i_readpnm: end of file\n"));
-      return NULL;
-    }
-  }
-  return &mb->buf[mb->cp];
-}
-
-int
-gread(mbuf *mb, unsigned char *buf, size_t read_size) {
-  int total_read = 0;
-  if (mb->cp != mb->len) {
-    int avail_size = mb->len - mb->cp;
-    int use_size = read_size > avail_size ? avail_size : read_size;
-    memcpy(buf, mb->buf+mb->cp, use_size);
-    mb->cp += use_size;
-    total_read += use_size;
-    read_size -= use_size;
-    buf += use_size;
-  }
-  if (read_size) {
-    io_glue *ig = mb->ig;
-    int read_res = i_io_read(ig, buf, read_size);
-    if (read_res >= 0) {
-      total_read += read_res;
-    }
-  }
-  return total_read;
-}
-
-
-/*
-=item skip_spaces(mb)
+=item skip_spaces(ig)
 
 Advances in stream until it is positioned at a
 non white space character. (internal)
 
-   mb - buffer object
+   ig - io_glue
 
 =cut
 */
 
 static
 int
-skip_spaces(mbuf *mb) {
-  char *cp;
-  while( (cp = gpeek(mb)) && misspace(*cp) ) if ( !gnext(mb) ) break;
-  if (!cp) return 0;
+skip_spaces(io_glue *ig) {
+  int c;
+  while( (c = i_io_peekc(ig)) != EOF && misspace(c) ) {
+    if ( i_io_getc(ig) == EOF )
+      break;
+  }
+  if (c == EOF)
+    return 0;
+
   return 1;
 }
 
 
 /*
-=item skip_comment(mb)
+=item skip_comment(ig)
 
 Advances in stream over whitespace and a comment if one is found. (internal)
 
-   mb - buffer object
+   ig - io_glue object
 
 =cut
 */
 
 static
 int
-skip_comment(mbuf *mb) {
-  char *cp;
+skip_comment(io_glue *ig) {
+  int c;
 
-  if (!skip_spaces(mb)) return 0;
+  if (!skip_spaces(ig))
+    return 0;
 
-  if (!(cp = gpeek(mb))) return 0;
-  if (*cp == '#') {
-    while( (cp = gpeek(mb)) && (*cp != '\n' && *cp != '\r') ) {
-      if ( !gnext(mb) ) break;
+  if ((c = i_io_peekc(ig)) == EOF)
+    return 0;
+
+  if (c == '#') {
+    while( (c = i_io_peekc(ig)) != EOF && (c != '\n' && c != '\r') ) {
+      if ( i_io_getc(ig) == EOF )
+       break;
     }
   }
-  if (!cp) return 0;
+  if (c == EOF)
+    return 0;
   
   return 1;
 }
@@ -218,32 +115,33 @@ on success else false.
 
 static
 int
-gnum(mbuf *mb, int *i) {
-  char *cp;
+gnum(io_glue *ig, int *i) {
+  int c;
   *i = 0;
 
-  if (!skip_spaces(mb)) return 0; 
+  if (!skip_spaces(ig)) return 0; 
 
-  if (!(cp = gpeek(mb))
+  if ((c = i_io_peekc(ig)) == EOF
     return 0;
-  if (!misnumber(*cp))
+  if (!misnumber(c))
     return 0;
-  while( (cp = gpeek(mb)) && misnumber(*cp) ) {
-    int work = *i*10+(*cp-'0');
+  while( (c = i_io_peekc(ig)) != EOF && misnumber(c) ) {
+    int work = *i * 10 + (c - '0');
     if (work < *i) {
       /* overflow */
       i_push_error(0, "integer overflow");
       return 0;
     }
     *i = work;
-    cp = gnext(mb);
+    i_io_getc(ig);
   }
+
   return 1;
 }
 
 static
 i_img *
-read_pgm_ppm_bin8(mbuf *mb, i_img *im, int width, int height, 
+read_pgm_ppm_bin8(io_glue *ig, i_img *im, int width, int height, 
                   int channels, int maxval, int allow_incomplete) {
   i_color *line, *linep;
   int read_size;
@@ -257,7 +155,7 @@ read_pgm_ppm_bin8(mbuf *mb, i_img *im, int width, int height,
   for(y=0;y<height;y++) {
     linep = line;
     readp = read_buf;
-    if (gread(mb, read_buf, read_size) != read_size) {
+    if (i_io_read(ig, read_buf, read_size) != read_size) {
       myfree(line);
       myfree(read_buf);
       if (allow_incomplete) {
@@ -301,7 +199,7 @@ read_pgm_ppm_bin8(mbuf *mb, i_img *im, int width, int height,
 
 static
 i_img *
-read_pgm_ppm_bin16(mbuf *mb, i_img *im, int width, int height, 
+read_pgm_ppm_bin16(io_glue *ig, i_img *im, int width, int height, 
                   int channels, int maxval, int allow_incomplete) {
   i_fcolor *line, *linep;
   int read_size;
@@ -315,7 +213,7 @@ read_pgm_ppm_bin16(mbuf *mb, i_img *im, int width, int height,
   for(y=0;y<height;y++) {
     linep = line;
     readp = read_buf;
-    if (gread(mb, read_buf, read_size) != read_size) {
+    if (i_io_read(ig, read_buf, read_size) != read_size) {
       myfree(line);
       myfree(read_buf);
       if (allow_incomplete) {
@@ -349,7 +247,7 @@ read_pgm_ppm_bin16(mbuf *mb, i_img *im, int width, int height,
 
 static 
 i_img *
-read_pbm_bin(mbuf *mb, i_img *im, int width, int height, int allow_incomplete) {
+read_pbm_bin(io_glue *ig, i_img *im, int width, int height, int allow_incomplete) {
   i_palidx *line, *linep;
   int read_size;
   unsigned char *read_buf, *readp;
@@ -360,7 +258,7 @@ read_pbm_bin(mbuf *mb, i_img *im, int width, int height, int allow_incomplete) {
   read_size = (width + 7) / 8;
   read_buf = mymalloc(read_size);
   for(y = 0; y < height; y++) {
-    if (gread(mb, read_buf, read_size) != read_size) {
+    if (i_io_read(ig, read_buf, read_size) != read_size) {
       myfree(line);
       myfree(read_buf);
       if (allow_incomplete) {
@@ -399,7 +297,7 @@ read_pbm_bin(mbuf *mb, i_img *im, int width, int height, int allow_incomplete) {
 */
 static 
 i_img *
-read_pbm_ascii(mbuf *mb, i_img *im, int width, int height, int allow_incomplete) {
+read_pbm_ascii(io_glue *ig, i_img *im, int width, int height, int allow_incomplete) {
   i_palidx *line, *linep;
   int x, y;
 
@@ -407,9 +305,9 @@ read_pbm_ascii(mbuf *mb, i_img *im, int width, int height, int allow_incomplete)
   for(y = 0; y < height; y++) {
     linep = line;
     for(x = 0; x < width; ++x) {
-      char *cp;
-      skip_spaces(mb);
-      if (!(cp = gnext(mb)) || (*cp != '0' && *cp != '1')) {
+      int c;
+      skip_spaces(ig);
+      if ((c = i_io_getc(ig)) == EOF || (c != '0' && c != '1')) {
         myfree(line);
         if (allow_incomplete) {
           i_tags_setn(&im->tags, "i_incomplete", 1);
@@ -417,7 +315,7 @@ read_pbm_ascii(mbuf *mb, i_img *im, int width, int height, int allow_incomplete)
           return im;
         }
         else {
-          if (cp)
+          if (c != EOF)
             i_push_error(0, "invalid data for ascii pnm");
           else
             i_push_error(0, "short read - file truncated?");
@@ -425,7 +323,7 @@ read_pbm_ascii(mbuf *mb, i_img *im, int width, int height, int allow_incomplete)
           return NULL;
         }
       }
-      *linep++ = *cp == '0' ? 0 : 1;
+      *linep++ = c == '0' ? 0 : 1;
     }
     i_ppal(im, 0, width, y, line);
   }
@@ -436,7 +334,7 @@ read_pbm_ascii(mbuf *mb, i_img *im, int width, int height, int allow_incomplete)
 
 static
 i_img *
-read_pgm_ppm_ascii(mbuf *mb, i_img *im, int width, int height, int channels, 
+read_pgm_ppm_ascii(io_glue *ig, i_img *im, int width, int height, int channels, 
                    int maxval, int allow_incomplete) {
   i_color *line, *linep;
   int x, y, ch;
@@ -449,7 +347,7 @@ read_pgm_ppm_ascii(mbuf *mb, i_img *im, int width, int height, int channels,
       for(ch=0; ch<channels; ch++) {
         int sample;
         
-        if (!gnum(mb, &sample)) {
+        if (!gnum(ig, &sample)) {
           myfree(line);
           if (allow_incomplete) {
             i_tags_setn(&im->tags, "i_incomplete", 1);
@@ -457,7 +355,7 @@ read_pgm_ppm_ascii(mbuf *mb, i_img *im, int width, int height, int channels,
             return im;
           }
           else {
-            if (gpeek(mb))
+            if (i_io_peekc(ig) != EOF)
               i_push_error(0, "invalid data for ascii pnm");
             else
               i_push_error(0, "short read - file truncated?");
@@ -480,7 +378,7 @@ read_pgm_ppm_ascii(mbuf *mb, i_img *im, int width, int height, int channels,
 
 static
 i_img *
-read_pgm_ppm_ascii_16(mbuf *mb, i_img *im, int width, int height, 
+read_pgm_ppm_ascii_16(io_glue *ig, i_img *im, int width, int height, 
                       int channels, int maxval, int allow_incomplete) {
   i_fcolor *line, *linep;
   int x, y, ch;
@@ -493,7 +391,7 @@ read_pgm_ppm_ascii_16(mbuf *mb, i_img *im, int width, int height,
       for(ch=0; ch<channels; ch++) {
         int sample;
         
-        if (!gnum(mb, &sample)) {
+        if (!gnum(ig, &sample)) {
           myfree(line);
           if (allow_incomplete) {
            i_tags_setn(&im->tags, "i_incomplete", 1);
@@ -501,7 +399,7 @@ read_pgm_ppm_ascii_16(mbuf *mb, i_img *im, int width, int height,
            return im;
           }
           else {
-            if (gpeek(mb))
+            if (i_io_peekc(ig) != EOF)
               i_push_error(0, "invalid data for ascii pnm");
             else
               i_push_error(0, "short read - file truncated?");
@@ -532,42 +430,32 @@ Retrieve an image and stores in the iolayer object. Returns NULL on fatal error.
 
 =cut
 */
-static i_img *i_readpnm_wiol_low( mbuf*, int);
 
 i_img *
-i_readpnm_wiol(io_glue *ig, int allow_incomplete) {
-  mbuf buf;
-  io_glue_commit_types(ig);
-  init_buf(&buf, ig);
-
-  return i_readpnm_wiol_low( &buf, allow_incomplete );
-}
-
-static i_img *
-i_readpnm_wiol_low( mbuf *buf, int allow_incomplete) {
+i_readpnm_wiol( io_glue *ig, int allow_incomplete) {
   i_img* im;
   int type;
   int width, height, maxval, channels;
   int rounder;
-  char *cp;
+  int c;
 
   i_clear_error();
-  mm_log((1,"i_readpnm(ig %p, allow_incomplete %d)\n", buf->ig, allow_incomplete));
+  mm_log((1,"i_readpnm(ig %p, allow_incomplete %d)\n", ig, allow_incomplete));
 
-  cp = gnext(buf);
+  c = i_io_getc(ig);
 
-  if (!cp || *cp != 'P') {
+  if (c != 'P') {
     i_push_error(0, "bad header magic, not a PNM file");
     mm_log((1, "i_readpnm: Could not read header of file\n"));
     return NULL;
   }
 
-  if ( !(cp = gnext(buf)) ) {
+  if ((c = i_io_getc(ig)) == EOF ) {
     mm_log((1, "i_readpnm: Could not read header of file\n"));
     return NULL;
   }
   
-  type = *cp-'0';
+  type = c - '0';
 
   if (type < 1 || type > 6) {
     i_push_error(0, "unknown PNM file type, not a PNM file");
@@ -575,12 +463,12 @@ i_readpnm_wiol_low( mbuf *buf, int allow_incomplete) {
     return NULL;
   }
 
-  if ( !(cp = gnext(buf)) ) {
+  if ( (c = i_io_getc(ig)) == EOF ) {
     mm_log((1, "i_readpnm: Could not read header of file\n"));
     return NULL;
   }
   
-  if ( !misspace(*cp) ) {
+  if ( !misspace(c) ) {
     i_push_error(0, "unexpected character, not a PNM file");
     mm_log((1, "i_readpnm: Not a pnm file\n"));
     return NULL;
@@ -591,38 +479,38 @@ i_readpnm_wiol_low( mbuf *buf, int allow_incomplete) {
   
   /* Read sizes and such */
 
-  if (!skip_comment(buf)) {
+  if (!skip_comment(ig)) {
     i_push_error(0, "while skipping to width");
     mm_log((1, "i_readpnm: error reading before width\n"));
     return NULL;
   }
   
-  if (!gnum(buf, &width)) {
+  if (!gnum(ig, &width)) {
     i_push_error(0, "could not read image width");
     mm_log((1, "i_readpnm: error reading width\n"));
     return NULL;
   }
 
-  if (!skip_comment(buf)) {
+  if (!skip_comment(ig)) {
     i_push_error(0, "while skipping to height");
     mm_log((1, "i_readpnm: error reading before height\n"));
     return NULL;
   }
 
-  if (!gnum(buf, &height)) {
+  if (!gnum(ig, &height)) {
     i_push_error(0, "could not read image height");
     mm_log((1, "i_readpnm: error reading height\n"));
     return NULL;
   }
   
   if (!(type == 1 || type == 4)) {
-    if (!skip_comment(buf)) {
+    if (!skip_comment(ig)) {
       i_push_error(0, "while skipping to maxval");
       mm_log((1, "i_readpnm: error reading before maxval\n"));
       return NULL;
     }
 
-    if (!gnum(buf, &maxval)) {
+    if (!gnum(ig, &maxval)) {
       i_push_error(0, "could not read maxval");
       mm_log((1, "i_readpnm: error reading maxval\n"));
       return NULL;
@@ -642,7 +530,7 @@ i_readpnm_wiol_low( mbuf *buf, int allow_incomplete) {
   } else maxval=1;
   rounder = maxval / 2;
 
-  if (!(cp = gnext(buf)) || !misspace(*cp)) {
+  if ((c = i_io_getc(ig)) == EOF || !misspace(c)) {
     i_push_error(0, "garbage in header, invalid PNM file");
     mm_log((1, "i_readpnm: garbage in header\n"));
     return NULL;
@@ -674,27 +562,27 @@ i_readpnm_wiol_low( mbuf *buf, int allow_incomplete) {
 
   switch (type) {
   case 1: /* Ascii types */
-    im = read_pbm_ascii(buf, im, width, height, allow_incomplete);
+    im = read_pbm_ascii(ig, im, width, height, allow_incomplete);
     break;
 
   case 2:
   case 3:
     if (maxval > 255)
-      im = read_pgm_ppm_ascii_16(buf, im, width, height, channels, maxval, allow_incomplete);
+      im = read_pgm_ppm_ascii_16(ig, im, width, height, channels, maxval, allow_incomplete);
     else
-      im = read_pgm_ppm_ascii(buf, im, width, height, channels, maxval, allow_incomplete);
+      im = read_pgm_ppm_ascii(ig, im, width, height, channels, maxval, allow_incomplete);
     break;
     
   case 4: /* binary pbm */
-    im = read_pbm_bin(buf, im, width, height, allow_incomplete);
+    im = read_pbm_bin(ig, im, width, height, allow_incomplete);
     break;
 
   case 5: /* binary pgm */
   case 6: /* binary ppm */
     if (maxval > 255)
-      im = read_pgm_ppm_bin16(buf, im, width, height, channels, maxval, allow_incomplete);
+      im = read_pgm_ppm_bin16(ig, im, width, height, channels, maxval, allow_incomplete);
     else
-      im = read_pgm_ppm_bin8(buf, im, width, height, channels, maxval, allow_incomplete);
+      im = read_pgm_ppm_bin8(ig, im, width, height, channels, maxval, allow_incomplete);
     break;
 
   default:
@@ -725,17 +613,15 @@ static void free_images(i_img **imgs, int count) {
 i_img **i_readpnm_multi_wiol(io_glue *ig, int *count, int allow_incomplete) {
     i_img **results = NULL;
     i_img *img = NULL;
-    char *cp = NULL;
-    mbuf buf;
+    char c = EOF;
     int result_alloc = 0, 
         value = 0, 
         eof = 0;
     *count=0;
-    io_glue_commit_types(ig);
-    init_buf(&buf, ig);
+
     do {
         mm_log((1, "read image %i\n", 1+*count));
-        img = i_readpnm_wiol_low( &buf, allow_incomplete );
+        img = i_readpnm_wiol( ig, allow_incomplete );
         if( !img ) {
             free_images( results, *count );
             return NULL;
@@ -758,7 +644,7 @@ i_img **i_readpnm_multi_wiol(io_glue *ig, int *count, int allow_incomplete) {
         if( i_tags_get_int(&img->tags, "i_incomplete", 0, &value ) && value) {
             eof = 1;
         }
-        else if( skip_spaces( &buf ) && ( cp=gpeek( &buf ) ) && *cp == 'P' ) {
+        else if( skip_spaces( ig ) && ( c=i_io_peekc( ig ) ) != EOF && c == 'P' ) {
             eof = 0;
         }
         else {
@@ -893,10 +779,9 @@ i_writeppm_wiol(i_img *im, io_glue *ig) {
   /* Add code to get the filename info from the iolayer */
   /* Also add code to check for mmapped code */
 
-  io_glue_commit_types(ig);
-
   if (i_img_is_monochrome(im, &zero_is_white)) {
-    return write_pbm(im, ig, zero_is_white);
+    if (!write_pbm(im, ig, zero_is_white))
+      return 0;
   }
   else {
     int type;
@@ -928,7 +813,7 @@ i_writeppm_wiol(i_img *im, io_glue *ig) {
     sprintf(header,"P%d\n#CREATOR: Imager\n%" i_DF " %" i_DF"\n%d\n", 
             type, i_DFc(im->xsize), i_DFc(im->ysize), maxval);
 
-    if (ig->writecb(ig,header,strlen(header)) != strlen(header)) {
+    if (i_io_write(ig,header,strlen(header)) != strlen(header)) {
       i_push_error(errno, "could not write ppm header");
       mm_log((1,"i_writeppm: unable to write ppm header.\n"));
       return(0);
@@ -936,7 +821,7 @@ i_writeppm_wiol(i_img *im, io_glue *ig) {
 
     if (!im->virtual && im->bits == i_8_bits && im->type == i_direct_type
        && im->channels == want_channels) {
-      if (ig->writecb(ig,im->idata,im->bytes) != im->bytes) {
+      if (i_io_write(ig,im->idata,im->bytes) != im->bytes) {
         i_push_error(errno, "could not write ppm data");
         return 0;
       }
@@ -950,7 +835,10 @@ i_writeppm_wiol(i_img *im, io_glue *ig) {
         return 0;
     }
   }
-  ig->closecb(ig);
+  if (i_io_close(ig)) {
+    i_push_errorf(i_io_error(ig), "Error closing stream: %d", i_io_error(ig));
+    return 0;
+  }
 
   return(1);
 }
diff --git a/raw.c b/raw.c
index 9159e5a..bae403d 100644 (file)
--- a/raw.c
+++ b/raw.c
@@ -70,7 +70,6 @@ i_readraw_wiol(io_glue *ig, i_img_dim x, i_img_dim y, int datachannels, int stor
 
   i_clear_error();
   
-  io_glue_commit_types(ig);
   mm_log((1, "i_readraw(ig %p,x %" i_DF ",y %" i_DF ",datachannels %d,storechannels %d,intrl %d)\n",
          ig, i_DFc(x), i_DFc(y), datachannels, storechannels, intrl));
 
@@ -102,7 +101,7 @@ i_readraw_wiol(io_glue *ig, i_img_dim x, i_img_dim y, int datachannels, int stor
   
   k=0;
   while( k<im->ysize ) {
-    rc = ig->readcb(ig, inbuffer, inbuflen);
+    rc = i_io_read(ig, inbuffer, inbuflen);
     if (rc != inbuflen) { 
       if (rc < 0)
        i_push_error(0, "error reading file");
@@ -136,13 +135,12 @@ undef_int
 i_writeraw_wiol(i_img* im, io_glue *ig) {
   ssize_t rc;
 
-  io_glue_commit_types(ig);
   i_clear_error();
   mm_log((1,"writeraw(im %p,ig %p)\n", im, ig));
   
   if (im == NULL) { mm_log((1,"Image is empty\n")); return(0); }
   if (!im->virtual) {
-    rc = ig->writecb(ig,im->idata,im->bytes);
+    rc = i_io_write(ig,im->idata,im->bytes);
     if (rc != im->bytes) { 
       i_push_error(errno, "Could not write to file");
       mm_log((1,"i_writeraw: Couldn't write to file\n")); 
@@ -159,7 +157,7 @@ i_writeraw_wiol(i_img* im, io_glue *ig) {
       rc = line_size;
       while (rc == line_size && y < im->ysize) {
        i_gsamp(im, 0, im->xsize, y, data, NULL, im->channels);
-       rc = ig->writecb(ig, data, line_size);
+       rc = i_io_write(ig, data, line_size);
        ++y;
       }
       if (rc != line_size) {
@@ -178,7 +176,7 @@ i_writeraw_wiol(i_img* im, io_glue *ig) {
       rc = line_size;
       while (rc == line_size && y < im->ysize) {
        i_gpal(im, 0, im->xsize, y, data);
-       rc = ig->writecb(ig, data, line_size);
+       rc = i_io_write(ig, data, line_size);
        ++y;
       }
       myfree(data);
@@ -189,7 +187,8 @@ i_writeraw_wiol(i_img* im, io_glue *ig) {
     }
   }
 
-  ig->closecb(ig);
+  if (i_io_close(ig))
+    return 0;
 
   return(1);
 }
index 212d026..bccd5cd 100644 (file)
@@ -26,4 +26,20 @@ sub _get_pods {
   return $pod->{identifiers} || [];
 }
 
+sub _get_syms {
+  my ($self, $package) = @_;
+
+  if ($self->{module}) {
+    eval "require $self->{module}";
+    return if $@;
+
+    # fake out require
+    (my $file = $package) =~ s(::)(/)g;
+    $file .= ".pm";
+    $INC{$file} = 1;
+  }
+
+  return $self->SUPER::_get_syms($package);
+}
+
 1;
index 83a3e9b..bead870 100644 (file)
@@ -1,6 +1,6 @@
 #!perl -w
 use strict;
-use Test::More tests => 68;
+use Test::More tests => 246;
 # for SEEK_SET etc, Fcntl doesn't provide these in 5.005_03
 use IO::Seekable;
 
@@ -8,6 +8,8 @@ BEGIN { use_ok(Imager => ':all') };
 
 -d "testout" or mkdir "testout";
 
+$| = 1;
+
 Imager->open_log(log => "testout/t07iolayer.log");
 
 undef($/);
@@ -31,7 +33,7 @@ open(FH, "<testimg/penguin-base.ppm");
 binmode(FH);
 $data = <FH>;
 close(FH);
-my $IO3 = Imager::io_new_buffer($data);
+my $IO3 = Imager::IO->new_buffer($data);
 #undef($data);
 $im = Imager::i_readpnm_wiol($IO3, -1);
 
@@ -41,7 +43,7 @@ undef $IO3;
 open(FH, "<testimg/penguin-base.ppm") or die $!;
 binmode(FH);
 $fd = fileno(FH);
-my $IO4 = Imager::io_new_fd( $fd );
+my $IO4 = Imager::IO->new_fd( $fd );
 my $im2 = Imager::i_readpnm_wiol($IO4, -1);
 close(FH);
 undef($IO4);
@@ -77,7 +79,7 @@ sub io_reader2 {
   $pos += length $out;
   $out;
 }
-my $IO7 = Imager::io_new_cb(undef, \&io_reader, undef, undef);
+my $IO7 = Imager::IO->new_cb(undef, \&io_reader, undef, undef);
 ok($IO7, "making readcb object");
 my $im4 = Imager::i_readpnm_wiol($IO7, -1);
 ok($im4, "read from cb");
@@ -126,33 +128,33 @@ is($work, $data2, "short write image match");
   my $io9 = Imager::io_new_buffer($buf_data);
   is(ref $io9, "Imager::IO", "check class");
   my $work;
-  is($io9->read($work, 4), 4, "read 4 from buffer object");
+  is($io9->raw_read($work, 4), 4, "read 4 from buffer object");
   is($work, "Test", "check data read");
-  is($io9->read($work, 5), 5, "read the rest");
+  is($io9->raw_read($work, 5), 5, "read the rest");
   is($work, " data", "check data read");
-  is($io9->seek(5, SEEK_SET), 5, "seek");
-  is($io9->read($work, 5), 4, "short read");
+  is($io9->raw_seek(5, SEEK_SET), 5, "seek");
+  is($io9->raw_read($work, 5), 4, "short read");
   is($work, "data", "check data read");
-  is($io9->seek(-1, SEEK_CUR), 8, "seek relative");
-  is($io9->seek(-5, SEEK_END), 4, "seek relative to end");
-  is($io9->seek(-10, SEEK_CUR), -1, "seek failure");
+  is($io9->raw_seek(-1, SEEK_CUR), 8, "seek relative");
+  is($io9->raw_seek(-5, SEEK_END), 4, "seek relative to end");
+  is($io9->raw_seek(-10, SEEK_CUR), -1, "seek failure");
   undef $io9;
 }
 {
-  my $io = Imager::io_new_bufchain();
+  my $io = Imager::IO->new_bufchain();
   is(ref $io, "Imager::IO", "check class");
-  is($io->write("testdata"), 8, "check write");
-  is($io->seek(-8, SEEK_CUR), 0, "seek relative");
+  is($io->raw_write("testdata"), 8, "check write");
+  is($io->raw_seek(-8, SEEK_CUR), 0, "seek relative");
   my $work;
-  is($io->read($work, 8), 8, "check read");
+  is($io->raw_read($work, 8), 8, "check read");
   is($work, "testdata", "check data read");
-  is($io->seek(-3, SEEK_END), 5, "seek end relative");
-  is($io->read($work, 5), 3, "short read");
+  is($io->raw_seek(-3, SEEK_END), 5, "seek end relative");
+  is($io->raw_read($work, 5), 3, "short read");
   is($work, "ata", "check read data");
-  is($io->seek(4, SEEK_SET), 4, "absolute seek to write some");
-  is($io->write("testdata"), 8, "write");
-  is($io->seek(0, SEEK_CUR), 12, "check size");
-  $io->close();
+  is($io->raw_seek(4, SEEK_SET), 4, "absolute seek to write some");
+  is($io->raw_write("testdata"), 8, "write");
+  is($io->raw_seek(0, SEEK_CUR), 12, "check size");
+  $io->raw_close();
   
   # grab the data
   my $data = Imager::io_slurp($io);
@@ -163,19 +165,19 @@ is($work, $data2, "short write image match");
   my $fail_io = Imager::io_new_cb(\&fail_write, \&fail_read, \&fail_seek, undef, 1);
   # scalar context
   my $buffer;
-  my $read_result = $fail_io->read($buffer, 10);
+  my $read_result = $fail_io->raw_read($buffer, 10);
   is($read_result, undef, "read failure undef in scalar context");
-  my @read_result = $fail_io->read($buffer, 10);
+  my @read_result = $fail_io->raw_read($buffer, 10);
   is(@read_result, 0, "empty list in list context");
-  $read_result = $fail_io->read2(10);
-  is($read_result, undef, "read2 failure (scalar)");
-  @read_result = $fail_io->read2(10);
-  is(@read_result, 0, "read2 failure (list)");
+  $read_result = $fail_io->raw_read2(10);
+  is($read_result, undef, "raw_read2 failure (scalar)");
+  @read_result = $fail_io->raw_read2(10);
+  is(@read_result, 0, "raw_read2 failure (list)");
 
-  my $write_result = $fail_io->write("test");
+  my $write_result = $fail_io->raw_write("test");
   is($write_result, -1, "failed write");
 
-  my $seek_result = $fail_io->seek(-1, SEEK_SET);
+  my $seek_result = $fail_io->raw_seek(-1, SEEK_SET);
   is($seek_result, -1, "failed seek");
 }
 
@@ -183,35 +185,35 @@ is($work, $data2, "short write image match");
   my $good_io = Imager::io_new_cb(\&good_write, \&good_read, \&good_seek, undef, 1);
   # scalar context
   my $buffer;
-  my $read_result = $good_io->read($buffer, 10);
-  is($read_result, 10, "read success (scalar)");
-  is($buffer, "testdatate", "check data");
-  my @read_result = $good_io->read($buffer, 10);
-  is_deeply(\@read_result, [ 10 ], "read success (list)");
-  is($buffer, "testdatate", "check data");
-  $read_result = $good_io->read2(10);
-  is($read_result, "testdatate", "read2 success (scalar)");
-  @read_result = $good_io->read2(10);
-  is_deeply(\@read_result, [ "testdatate" ], "read2 success (list)");
+  my $read_result = $good_io->raw_read($buffer, 10);
+  is($read_result, 8, "read success (scalar)");
+  is($buffer, "testdata", "check data");
+  my @read_result = $good_io->raw_read($buffer, 10);
+  is_deeply(\@read_result, [ 8 ], "read success (list)");
+  is($buffer, "testdata", "check data");
+  $read_result = $good_io->raw_read2(10);
+  is($read_result, "testdata", "read2 success (scalar)");
+  @read_result = $good_io->raw_read2(10);
+  is_deeply(\@read_result, [ "testdata" ], "read2 success (list)");
 }
 
 { # end of file
   my $eof_io = Imager::io_new_cb(undef, \&eof_read, undef, undef, 1);
   my $buffer;
-  my $read_result = $eof_io->read($buffer, 10);
+  my $read_result = $eof_io->raw_read($buffer, 10);
   is($read_result, 0, "read eof (scalar)");
   is($buffer, '', "check data");
-  my @read_result = $eof_io->read($buffer, 10);
+  my @read_result = $eof_io->raw_read($buffer, 10);
   is_deeply(\@read_result, [ 0 ], "read eof (list)");
   is($buffer, '', "check data");
 }
 
 { # no callbacks
   my $none_io = Imager::io_new_cb(undef, undef, undef, undef, 0);
-  is($none_io->write("test"), -1, "write with no writecb should fail");
+  is($none_io->raw_write("test"), -1, "write with no writecb should fail");
   my $buffer;
-  is($none_io->read($buffer, 10), undef, "read with no readcb should fail");
-  is($none_io->seek(0, SEEK_SET), -1, "seek with no seekcb should fail");
+  is($none_io->raw_read($buffer, 10), undef, "read with no readcb should fail");
+  is($none_io->raw_seek(0, SEEK_SET), -1, "seek with no seekcb should fail");
 }
 
 SKIP:
@@ -224,7 +226,7 @@ SKIP:
   is(ord $data, 0x100, "make sure we got what we expected");
   my $result = 
     eval {
-      $io->write($data);
+      $io->raw_write($data);
     };
   ok($@, "should have croaked")
     and print "# $@\n";
@@ -246,10 +248,564 @@ SKIP:
      sub { print "# seek\n"; 0 }, 
      sub { print "# close\n"; 1 });
   my $buffer;
-  is($io->read($buffer, 10), 10, "read 10");
+  is($io->raw_read($buffer, 10), 10, "read 10");
   is($buffer, "xxxxxxxxxx", "read value");
-  ok($io->write("foo"), "write");
-  is($io->close, 0, "close");
+  ok($io->raw_write("foo"), "write");
+  is($io->raw_close, 0, "close");
+}
+
+SKIP:
+{ # fd_seek write failure
+  -c "/dev/full"
+    or skip("No /dev/full", 3);
+  open my $fh, "> /dev/full"
+    or skip("Can't open /dev/full: $!", 3);
+  my $io = Imager::io_new_fd(fileno($fh));
+  ok($io, "make fd io for /dev/full");
+  Imager::i_clear_error();
+  is($io->raw_write("test"), -1, "fail to write");
+  my $msg = Imager->_error_as_msg;
+  like($msg, qr/^write\(\) failure: /, "check error message");
+  print "# $msg\n";
+
+  # /dev/full succeeds on seek on Linux
+
+  undef $io;
+}
+
+SKIP:
+{ # fd_seek seek failure
+  my $seekfail = "testout/t07seekfail.dat";
+  open my $fh, "> $seekfail"
+    or skip("Can't open $seekfail: $!", 3);
+  my $io = Imager::io_new_fd(fileno($fh));
+  ok($io, "make fd io for $seekfail");
+
+  Imager::i_clear_error();
+  is($io->raw_seek(-1, SEEK_SET), -1, "shouldn't be able to seek to -1");
+  my $msg = Imager->_error_as_msg;
+  like($msg, qr/^lseek\(\) failure: /, "check error message");
+  print "# $msg\n";
+
+  undef $io;
+  close $fh;
+  unlink $seekfail;
+}
+
+SKIP:
+{ # fd_seek read failure
+  open my $fh, "> testout/t07writeonly.txt"
+    or skip("Can't open testout/t07writeonly.txt: $!", 3);
+  my $io = Imager::io_new_fd(fileno($fh));
+  ok($io, "make fd io for write-only");
+
+  Imager::i_clear_error();
+  my $buf;
+  is($io->raw_read($buf, 10), undef,
+     "file open for write shouldn't be readable");
+  my $msg = Imager->_error_as_msg;
+  like($msg, qr/^read\(\) failure: /, "check error message");
+  print "# $msg\n";
+
+  undef $io;
+}
+
+SKIP:
+{ # fd_seek eof
+  open my $fh, "> testout/t07readeof.txt"
+    or skip("Can't open testout/t07readeof.txt: $!", 5);
+  binmode $fh;
+  print $fh "test";
+  close $fh;
+  open my $fhr, "< testout/t07readeof.txt",
+    or skip("Can't open testout/t07readeof.txt: $!", 5);
+  my $io = Imager::io_new_fd(fileno($fhr));
+  ok($io, "make fd io for read eof");
+
+  Imager::i_clear_error();
+  my $buf;
+  is($io->raw_read($buf, 10), 4,
+     "10 byte read on 4 byte file should return 4");
+  my $msg = Imager->_error_as_msg;
+  is($msg, "", "should be no error message")
+    or print STDERR "# read(4) message is: $msg\n";
+
+  Imager::i_clear_error();
+  $buf = '';
+  is($io->raw_read($buf, 10), 0,
+     "10 byte read at end of 4 byte file should return 0 (eof)");
+
+  $msg = Imager->_error_as_msg;
+  is($msg, "", "should be no error message")
+    or print STDERR "# read(4), eof message is: $msg\n";
+
+  undef $io;
+}
+
+{ # buffered I/O
+  my $data="P2\n2 2\n255\n 255 0\n0 255\n";
+  my $io = Imager::io_new_buffer($data);
+
+  my $c = $io->getc();
+
+  is($c, ord "P", "getc");
+  my $peekc = $io->peekc();
+
+  is($peekc, ord "2", "peekc");
+
+  my $peekn = $io->peekn(2);
+  is($peekn, "2\n", "peekn");
+
+  $c = $io->getc();
+  is($c, ord "2", "getc after peekc/peekn");
+
+  is($io->seek(0, SEEK_SET), "0", "seek");
+  is($io->getc, ord "P", "check we got back to the start");
+}
+
+{ # test closecb result is propagated
+  my $success_cb = sub { 1 };
+  my $failure_cb = sub { 0 };
+
+  {
+    my $io = Imager::io_new_cb(undef, $success_cb, undef, $success_cb);
+    is($io->close(), 0, "test successful close");
+  }
+  {
+    my $io = Imager::io_new_cb(undef, $success_cb, undef, $failure_cb);
+    is($io->close(), -1, "test failed close");
+  }
+}
+
+{ # buffered coverage/function tests
+  # some data to play with
+  my $base = pack "C*", map rand(26) + ord("a"), 0 .. 20_001;
+
+  { # buffered accessors
+    my $io = Imager::io_new_buffer($base);
+    ok($io->set_buffered(0), "set unbuffered");
+    ok(!$io->is_buffered, "verify unbuffered");
+    ok($io->set_buffered(1), "set buffered");
+    ok($io->is_buffered, "verify buffered");
+  }
+
+  { # initial i_io_read(), buffered
+    my $pos = 0;
+    my $ops = "";
+    my $work = $base;
+    my $read = sub {
+      my ($size) = @_;
+
+      my $req_size = $size;
+
+      if ($pos + $size > length $work) {
+       $size = length($work) - $pos;
+      }
+
+      my $result = substr($work, $pos, $size);
+      $pos += $size;
+      $ops .= "R$req_size>$size;";
+
+      print "# read $req_size>$size\n";
+
+      return $result;
+    };
+    my $write = sub {
+      my ($data) = @_;
+
+      substr($work, $pos, length($data), $data);
+
+      return 1;
+    };
+    {
+      my $io = Imager::io_new_cb(undef, $read, undef, undef);
+      my $buf;
+      is($io->read($buf, 1000), 1000, "read initial 1000");
+      is($buf, substr($base, 0, 1000), "check data read");
+      is($ops, "R8192>8192;", "check read op happened to buffer size");
+
+      undef $buf;
+      is($io->read($buf, 1001), 1001, "read another 1001");
+      is($buf, substr($base, 1000, 1001), "check data read");
+      is($ops, "R8192>8192;", "should be no further reads");
+
+      undef $buf;
+      is($io->read($buf, 40_000), length($base) - 2001,
+        "read the rest in one chunk");
+      is($buf, substr($base, 2001), "check the data read");
+      my $buffer_left = 8192 - 2001;
+      my $after_buffer = length($base) - 8192;
+      is($ops, "R8192>8192;R".(40_000 - $buffer_left).">$after_buffer;R21999>0;",
+        "check we tried to read the remainder");
+    }
+    {
+      # read after write errors
+      my $io = Imager::io_new_cb($write, $read, undef, undef);
+      is($io->write("test"), 4, "write 4 bytes, io in write mode");
+      is($io->read2(10), undef, "read should fail");
+      is($io->peekn(10), undef, "peekn should fail");
+      is($io->getc(), -1, "getc should fail");
+      is($io->peekc(), -1, "peekc should fail");
+    }
+  }
+
+  {
+    my $io = Imager::io_new_buffer($base);
+    print "# buffer fill check\n";
+    ok($io, "make memory io");
+    my $buf;
+    is($io->read($buf, 4096), 4096, "read 4k");
+    is($buf, substr($base, 0, 4096), "check data is correct");
+
+    # peek a bit
+    undef $buf;
+    is($io->peekn(5120), substr($base, 4096, 5120),
+       "peekn() 5120, which should exceed the buffer, and only read the left overs");
+  }
+
+  { # initial peekn
+    my $io = Imager::io_new_buffer($base);
+    is($io->peekn(10), substr($base, 0, 10),
+       "make sure initial peekn() is sane");
+    is($io->read2(10), substr($base, 0, 10),
+       "and that reading 10 gets the expected data");
+  }
+
+  { # oversize peekn
+    my $io = Imager::io_new_buffer($base);
+    is($io->peekn(10_000), substr($base, 0, 8192),
+       "peekn() larger than buffer should return buffer-size bytes");
+  }
+
+  { # small peekn then large peekn with a small I/O back end
+    # this might happen when reading from a socket
+    my $work = $base;
+    my $pos = 0;
+    my $ops = '';
+    my $reader = sub {
+      my ($size) = @_;
+
+      my $req_size = $size;
+      # do small reads, to trigger a possible bug
+      if ($size > 10) {
+       $size = 10;
+      }
+
+      if ($pos + $size > length $work) {
+       $size = length($work) - $pos;
+      }
+
+      my $result = substr($work, $pos, $size);
+      $pos += $size;
+      $ops .= "R$req_size>$size;";
+
+      print "# read $req_size>$size\n";
+
+      return $result;
+    };
+    my $io = Imager::io_new_cb(undef, $reader, undef, undef);
+    ok($io, "small reader io");
+    is($io->peekn(25), substr($base, 0, 25), "peek 25");
+    is($ops, "R8192>10;R8182>10;R8172>10;",
+       "check we got the raw calls expected");
+    is($io->peekn(65), substr($base, 0, 65), "peek 65");
+    is($ops, "R8192>10;R8182>10;R8172>10;R8162>10;R8152>10;R8142>10;R8132>10;",
+       "check we got the raw calls expected");
+  }
+  for my $buffered (1, 0) { # peekn followed by errors
+    my $buffered_desc = $buffered ? "buffered" : "unbuffered";
+    my $read = 0;
+    my $base = "abcdef";
+    my $pos = 0;
+    my $reader = sub {
+      my $size = shift;
+      my $req_size = $size;
+      if ($pos + $size > length $base) {
+       $size = length($base) - $pos;
+      }
+      # error instead of eof
+      if ($size == 0) {
+       print "# read $req_size>error\n";
+       return;
+      }
+      my $result = substr($base, $pos, $size);
+      $pos += $size;
+
+      print "# read $req_size>$size\n";
+
+      return $result;
+    };
+    my $io = Imager::io_new_cb(undef, $reader, undef, undef);
+    ok($io, "make $buffered_desc cb with error after 6 bytes");
+    is($io->peekn(5), "abcde",
+       "peekn until just before error ($buffered_desc)");
+    is($io->peekn(6), "abcdef", "peekn until error ($buffered_desc)");
+    is($io->peekn(7), "abcdef", "peekn past error ($buffered_desc)");
+    ok(!$io->error,
+       "should be no error indicator, since data buffered ($buffered_desc)");
+    ok(!$io->eof,
+       "should be no eof indicator, since data buffered ($buffered_desc)");
+
+    # consume it
+    is($io->read2(6), "abcdef", "consume the buffer ($buffered_desc)");
+    is($io->peekn(10), undef,
+       "peekn should get an error indicator ($buffered_desc)");
+    ok($io->error, "should be an error state ($buffered_desc)");
+    ok(!$io->eof, "but not eof ($buffered_desc)");
+  }
+  { # peekn on an empty file
+    my $io = Imager::io_new_buffer("");
+    is($io->peekn(10), "", "peekn on empty source");
+    ok($io->eof, "should be in eof state");
+    ok(!$io->error, "but not error");
+  }
+  { # peekn on error source
+    my $io = Imager::io_new_cb(undef, sub { return; }, undef, undef);
+    is($io->peekn(10), undef, "peekn on empty source");
+    ok($io->error, "should be in error state");
+    ok(!$io->eof, "but not eof");
+  }
+  { # peekn on short source
+    my $io = Imager::io_new_buffer("abcdef");
+    is($io->peekn(4), "abcd", "peekn 4 on 6 byte source");
+    is($io->peekn(10), "abcdef", "followed by peekn 10 on 6 byte source");
+    is($io->peekn(10), "abcdef", "and again, now eof is set");
+  }
+  { # peekn(0)
+    Imager::i_clear_error();
+    my $io = Imager::io_new_buffer("abcdef");
+    is($io->peekn(0), undef, "peekn 0 on 6 byte source");
+    my $msg = Imager->_error_as_msg;
+    is($msg, "peekn size must be positive");
+  }
+  { # getc through a whole file (buffered)
+    my $io = Imager::io_new_buffer($base);
+    my $out = '';
+    while ((my $c = $io->getc()) != -1) {
+      $out .= chr($c);
+    }
+    is($out, $base, "getc should return the file byte by byte (buffered)");
+    is($io->getc, -1, "another getc after eof should fail too");
+    ok($io->eof, "should be marked eof");
+    ok(!$io->error, "shouldn't be marked in error");
+  }
+  { # getc through a whole file (unbuffered)
+    my $io = Imager::io_new_buffer($base);
+    $io->set_buffered(0);
+    my $out = '';
+    while ((my $c = $io->getc()) != -1) {
+      $out .= chr($c);
+    }
+    is($out, $base, "getc should return the file byte by byte (unbuffered)");
+    is($io->getc, -1, "another getc after eof should fail too");
+    ok($io->eof, "should be marked eof");
+    ok(!$io->error, "shouldn't be marked in error");
+  }
+  { # buffered getc with an error
+    my $io = Imager::io_new_cb(undef, sub { return; }, undef, undef);
+    is($io->getc, -1, "buffered getc error");
+    ok($io->error, "io marked in error");
+    ok(!$io->eof, "but not eof");
+  }
+  { # unbuffered getc with an error
+    my $io = Imager::io_new_cb(undef, sub { return; }, undef, undef);
+    $io->set_buffered(0);
+    is($io->getc, -1, "unbuffered getc error");
+    ok($io->error, "io marked in error");
+    ok(!$io->eof, "but not eof");
+  }
+  { # initial peekc - buffered
+    my $io = Imager::io_new_buffer($base);
+    my $c = $io->peekc;
+    is($c, ord($base), "buffered peekc matches");
+    is($io->peekc, $c, "duplicate peekc matchess");
+  }
+  { # initial peekc - unbuffered
+    my $io = Imager::io_new_buffer($base);
+    $io->set_buffered(0);
+    my $c = $io->peekc;
+    is($c, ord($base), "unbuffered peekc matches");
+    is($io->peekc, $c, "duplicate peekc matchess");
+  }
+  { # initial peekc eof - buffered
+    my $io = Imager::io_new_cb(undef, sub { "" }, undef, undef);
+    my $c = $io->peekc;
+    is($c, -1, "buffered eof peekc is -1");
+    is($io->peekc, $c, "duplicate matches");
+    ok($io->eof, "io marked eof");
+    ok(!$io->error, "but not error");
+  }
+  { # initial peekc eof - unbuffered
+    my $io = Imager::io_new_cb(undef, sub { "" }, undef, undef);
+    $io->set_buffered(0);
+    my $c = $io->peekc;
+    is($c, -1, "buffered eof peekc is -1");
+    is($io->peekc, $c, "duplicate matches");
+    ok($io->eof, "io marked eof");
+    ok(!$io->error, "but not error");
+  }
+  { # initial peekc error - buffered
+    my $io = Imager::io_new_cb(undef, sub { return; }, undef, undef);
+    my $c = $io->peekc;
+    is($c, -1, "buffered error peekc is -1");
+    is($io->peekc, $c, "duplicate matches");
+    ok($io->error, "io marked error");
+    ok(!$io->eof, "but not eof");
+  }
+  { # initial peekc error - unbuffered
+    my $io = Imager::io_new_cb(undef, sub { return; }, undef, undef);
+    $io->set_buffered(0);
+    my $c = $io->peekc;
+    is($c, -1, "unbuffered error peekc is -1");
+    is($io->peekc, $c, "duplicate matches");
+    ok($io->error, "io marked error");
+    ok(!$io->eof, "but not eof");
+  }
+  { # initial putc
+    my $io = Imager::io_new_bufchain();
+    is($io->putc(ord "A"), ord "A", "initial putc buffered");
+    is($io->close, 0, "close it");
+    is(Imager::io_slurp($io), "A", "check it was written");
+  }
+  { # initial putc - unbuffered
+    my $io = Imager::io_new_bufchain();
+    $io->set_buffered(0);
+    is($io->putc(ord "A"), ord "A", "initial putc unbuffered");
+    is($io->close, 0, "close it");
+    is(Imager::io_slurp($io), "A", "check it was written");
+  }
+  { # putc unbuffered with error
+    my $io = Imager::io_new_cb(undef, undef, undef, undef);
+    $io->set_buffered(0);
+    is($io->putc(ord "A"), -1, "initial putc unbuffered error");
+    ok($io->error, "io in error");
+    is($io->putc(ord "B"), -1, "still in error");
+  }
+  { # writes while in read state
+    my $io = Imager::io_new_cb(sub { 1 }, sub { return "AA" }, undef, undef);
+    is($io->getc, ord "A", "read to setup read buffer");
+    is($io->putc(ord "B"), -1, "putc should fail");
+    is($io->write("test"), -1, "write should fail");
+  }
+  { # buffered putc error handling
+    # tests the check for error state in the buffered putc code
+    my $io = Imager::io_new_cb(undef, undef, undef, undef);
+    $io->putc(ord "A");
+    ok(!$io->flush, "flush should fail");
+    ok($io->error, "should be in error state");
+    is($io->putc(ord "B"), -1, "check for error");
+  }
+  { # buffered putc flush error handling
+    # test handling of flush failure and of the error state resulting
+    # from that
+    my $io = Imager::io_new_cb(undef, undef, undef, undef);
+    my $i = 0;
+    while (++$i < 100_000 && $io->putc(ord "A") == ord "A") {
+      # until we have to flush and fail doing do
+    }
+    is($i, 8193, "should have failed on 8193rd byte");
+    ok($io->error, "should be in error state");
+    is($io->putc(ord "B"), -1, "next putc should fail");
+  }
+  { # buffered write flush error handling
+    # test handling of flush failure and of the error state resulting
+    # from that
+    my $io = Imager::io_new_cb(undef, undef, undef, undef);
+    my $i = 0;
+    while (++$i < 100_000 && $io->write("A") == 1) {
+      # until we have to flush and fail doing do
+    }
+    is($i, 8193, "should have failed on 8193rd byte");
+    ok($io->error, "should be in error state");
+    is($io->write("B"), -1, "next write should fail");
+  }
+  { # buffered read error
+    my $io = Imager::io_new_cb(undef, undef, undef, undef);
+    is($io->read2(10), undef, "initial read returning error");
+    ok($io->error, "should be in error state");
+  }
+  { # unbuffered read error
+    my $io = Imager::io_new_cb(undef, undef, undef, undef);
+    $io->set_buffered(0);
+    is($io->read2(10), undef, "initial read returning error");
+    ok($io->error, "should be in error state");
+  }
+  { # unbuffered write error
+    my $count = 0;
+    my $io = Imager::io_new_cb(sub { return $count++; }, undef, undef, undef);
+    $io->set_buffered(0);
+    is($io->write("A"), -1, "unbuffered write failure");
+    ok($io->error, "should be in error state");
+    is($io->write("BC"), -1, "should still fail");
+  }
+  { # buffered write + large write
+    my $io = Imager::io_new_bufchain();
+    is($io->write(substr($base, 0, 4096)), 4096,
+       "should be buffered");
+    is($io->write(substr($base, 4096)), length($base) - 4096,
+       "large write, should fill buffer and fall back to direct write");
+    is($io->close, 0, "close it");
+    is(Imager::io_slurp($io), $base, "make sure the data is correct");
+  }
+  { # initial large write with failure
+    # tests error handling for the case where we bypass the buffer
+    # when the write is too large to fit
+    my $io = Imager::io_new_cb(undef, undef, undef, undef);
+    ok($io->flush, "flush with nothing buffered should succeed");
+    is($io->write($base), -1, "large write failure");
+    ok($io->error, "should be in error state");
+    is($io->close, -1, "should fail to close");
+  }
+  { # write that causes a flush then fills the buffer a bit
+    my $io = Imager::io_new_bufchain();
+    is($io->write(substr($base, 0, 6000)), 6000, "fill the buffer a bit");
+    is($io->write(substr($base, 6000, 4000)), 4000,
+       "cause it to flush and then fill some more");
+    is($io->write(substr($base, 10000)), length($base)-10000,
+       "write out the rest of our test data");
+    is($io->close, 0, "close the stream");
+    is(Imager::io_slurp($io), $base, "make sure the data is right");
+  }
+  { # failure on flush on close
+    my $io = Imager::io_new_cb(undef, undef, undef, undef);
+    is($io->putc(ord "A"), ord "A", "something in the buffer");
+    ok(!$io->error, "should be no error yet");
+    is($io->close, -1, "close should failure due to flush error");
+  }
+  { # seek failure
+    my $io = Imager::io_new_cb(undef, undef, undef, undef);
+    is($io->seek(0, SEEK_SET), -1, "seek failure");
+  }
+  { # read a little and seek
+    my $io = Imager::io_new_buffer($base);
+    is($io->getc, ord $base, "read one");
+    is($io->getc, ord substr($base, 1, 1), "read another");
+    is($io->seek(-1, SEEK_CUR), 1, "seek relative back to origin+1");
+    is($io->getc, ord substr($base, 1, 1), "read another again");
+  }
+  { # seek with failing flush
+    my $io = Imager::io_new_cb(undef, undef, undef, undef);
+    is($io->putc(ord "A"), ord "A", "write one");
+    ok(!$io->error, "not in error mode (yet)");
+    is($io->seek(0, SEEK_SET), -1, "seek failure due to flush");
+    ok($io->error, "in error mode");
+  }
+  { # gets()
+    my $data = "test1\ntest2\ntest3";
+    my $io = Imager::io_new_buffer($data);
+    is($io->gets(6), "test1\n", "gets(6)");
+    is($io->gets(5), "test2", "gets(5) (short for the line)");
+    is($io->gets(10), "\n", "gets(10) the rest of the line (the newline)");
+    is($io->gets(), "test3", "gets(default) unterminated line");
+  }
+  { # more gets()
+    my $data = "test1\ntest2\ntest3";
+    my $io = Imager::io_new_buffer($data);
+    is($io->gets(6, ord("1")), "test1", "gets(6) (line terminator 1)");
+    is($io->gets(6, ord("2")), "\ntest2", "gets(6) (line terminator 2)");
+    is($io->gets(6, ord("3")), "\ntest3", "gets(6) (line terminator 3)");
+    is($io->getc, -1, "should be eof");
+  }
 }
 
 Imager->close_log;
index 316ce27..01f5272 100644 (file)
@@ -1,8 +1,8 @@
 #!perl -w
 use strict;
-use Test::More tests => 47;
+use Test::More tests => 53;
 use Imager qw(:all);
-use Imager::Test qw/is_color3 is_color4/;
+use Imager::Test qw/is_color3 is_color4 test_image test_image_mono/;
 
 -d "testout" or mkdir "testout";
 
@@ -166,7 +166,7 @@ SKIP:
   open RAW, "< testout/t103_empty.raw"
     or die "Cannot open testout/t103_empty.raw: $!";
   my $im = Imager->new(xsize => 50, ysize=>50);
-  ok(!$im->write(fh => \*RAW, type => 'raw'),
+  ok(!$im->write(fh => \*RAW, type => 'raw', buffered => 0),
      "write to open for read handle");
   cmp_ok($im->errstr, '=~', '^Could not write to file: write\(\) failure', 
         "check error message");
@@ -270,6 +270,24 @@ SKIP:
            "check last channel zeroed");
 }
 
+{
+  my @ims = ( basic => test_image(), mono => test_image_mono() );
+  push @ims, masked => test_image()->masked();
+
+  my $fail_close = sub {
+    Imager::i_push_error(0, "synthetic close failure");
+    return 0;
+  };
+
+  while (my ($type, $im) = splice(@ims, 0, 2)) {
+    my $io = Imager::io_new_cb(sub { 1 }, undef, undef, $fail_close);
+    ok(!$im->write(io => $io, type => "raw"),
+       "write $type image with a failing close handler");
+    like($im->errstr, qr/synthetic close failure/,
+        "check error message");
+  }
+}
+
 Imager->close_log;
 
 unless ($ENV{IMAGER_KEEP_FILES}) {
index b6f9526..c85d7a9 100644 (file)
@@ -1,8 +1,10 @@
 #!perl -w
 use Imager ':all';
-use Test::More tests => 195;
+use Test::More tests => 205;
 use strict;
-use Imager::Test qw(test_image_raw test_image_16 is_color3 is_color1 is_image);
+use Imager::Test qw(test_image_raw test_image_16 is_color3 is_color1 is_image test_image_named);
+
+$| = 1;
 
 -d "testout" or mkdir "testout";
 
@@ -65,7 +67,8 @@ is(i_img_diff($gimg, $gcmpimg), 0,
    "compare written and read greyscale images");
 
 my $ooim = Imager->new;
-ok($ooim->read(file=>"testimg/simple.pbm"), "read simple pbm, via OO");
+ok($ooim->read(file=>"testimg/simple.pbm"), "read simple pbm, via OO")
+  or print "# ", $ooim->errstr, "\n";
 
 check_gray(Imager::i_get_pixel($ooim->{IMG}, 0, 0), 0);
 check_gray(Imager::i_get_pixel($ooim->{IMG}, 0, 1), 255);
@@ -233,7 +236,8 @@ is($ooim->tags(name=>'pnm_type'), 1, "check pnm_type tag");
   ok($im->write(file=>"testout/t104_alpha.ppm", type=>'pnm'),
      "should succeed writing 4 channel image");
   my $imread = Imager->new;
-  ok($imread->read(file => 'testout/t104_alpha.ppm'), "read it back");
+  ok($imread->read(file => 'testout/t104_alpha.ppm'), "read it back")
+    or print "# ", $imread->errstr, "\n";
   is_color3($imread->getpixel('x' => 0, 'y' => 0), 0, 0, 0, 
            "check transparent became black");
   is_color3($imread->getpixel('x' => 8, 'y' => 0), 255, 224, 192,
@@ -594,8 +598,6 @@ print "# check error handling\n";
   }
 }
 
-Imager->close_log;
-
 { # image too large handling
   {
     ok(!Imager->new(file => "testimg/toowide.ppm", filetype => "pnm"),
@@ -611,6 +613,24 @@ Imager->close_log;
   }
 }
 
+{ # make sure close is checked for each image type
+  my $fail_close = sub {
+    Imager::i_push_error(0, "synthetic close failure");
+    return 0;
+  };
+
+  for my $type (qw(basic basic16 gray gray16 mono)) {
+    my $im = test_image_named($type);
+    my $io = Imager::io_new_cb(sub { 1 }, undef, undef, $fail_close);
+    ok(!$im->write(io => $io, type => "pnm"),
+       "write $type image with a failing close handler");
+    like($im->errstr, qr/synthetic close failure/,
+        "check error message");
+  }
+}
+
+Imager->close_log;
+
 unless ($ENV{IMAGER_KEEP_FILES}) {
   unlink "testout/t104ppm.log";
   unlink map "testout/$_", @files;
index 0129ea8..356d776 100644 (file)
@@ -1,6 +1,6 @@
 #!perl -w
 use strict;
-use Test::More tests => 213;
+use Test::More tests => 215;
 use Imager qw(:all);
 use Imager::Test qw(test_image_raw is_image is_color3 test_image);
 
@@ -613,8 +613,10 @@ for my $comp (@comp) {
     $im->read(file => "testimg/$file")
       or die "Cannot read $file: ", $im->errstr;
 
-    ok(!$im->write(type => 'bmp', callback => limited_write($limit),
-                  maxbuffer => 1),
+    my $io = Imager::io_new_cb(limited_write($limit), undef, undef, undef, 1);
+    $io->set_buffered(0);
+    print "# writing with limit of $limit\n";
+    ok(!$im->write(type => 'bmp', io => $io),
        "$test_index - $desc: write should fail");
     is($im->errstr, $error, "$test_index - $desc: check error message");
 
@@ -666,6 +668,19 @@ for my $comp (@comp) {
   is($size, 67800, "check data size");
 }
 
+{ # check close failures are handled correctly
+  my $im = test_image();
+  my $fail_close = sub {
+    Imager::i_push_error(0, "synthetic close failure");
+    return 0;
+  };
+  ok(!$im->write(type => "bmp", callback => sub { 1 },
+                closecb => $fail_close),
+     "check failing close fails");
+    like($im->errstr, qr/synthetic close failure/,
+        "check error message");
+}
+
 Imager->close_log;
 
 unless ($ENV{IMAGER_KEEP_FILES}) {
index 11be19e..cfdd664 100644 (file)
@@ -1,8 +1,8 @@
 #!perl -w
 use Imager qw(:all);
 use strict;
-use Test::More tests=>66;
-use Imager::Test qw(is_color4 is_image);
+use Test::More tests=>68;
+use Imager::Test qw(is_color4 is_image test_image);
 
 -d "testout" or mkdir "testout";
 
@@ -232,6 +232,19 @@ is($compressed, 1, "check compressed tag");
   }
 }
 
+{ # check close failures are handled correctly
+  my $im = test_image();
+  my $fail_close = sub {
+    Imager::i_push_error(0, "synthetic close failure");
+    return 0;
+  };
+  ok(!$im->write(type => "tga", callback => sub { 1 },
+                closecb => $fail_close),
+     "check failing close fails");
+    like($im->errstr, qr/synthetic close failure/,
+        "check error message");
+}
+
 sub write_test {
   my ($im, $filename, $wierdpack, $compress, $idstring) = @_;
   local *FH;
index c4e24a2..15f625d 100644 (file)
@@ -18,10 +18,11 @@ plan skip_all => "perl 5.005_04, 5.005_05 too buggy"
 
 -d "testout" or mkdir "testout";
 
-plan tests => 9;
+plan tests => 16;
 require Inline;
 Inline->import(with => 'Imager');
 Inline->import("FORCE"); # force rebuild
+#Inline->import(C => Config => OPTIMIZE => "-g");
 
 Inline->bind(C => <<'EOS');
 #include <math.h>
@@ -233,6 +234,135 @@ Imager do_lots(Imager src) {
   return im;
 }
 
+void
+io_fd(int fd) {
+  Imager::IO io = io_new_fd(fd);
+  i_io_write(io, "test", 4);
+  i_io_close(io);
+  io_glue_destroy(io);
+}
+
+int
+io_bufchain_test() {
+  Imager::IO io = io_new_bufchain();
+  unsigned char *result;
+  size_t size;
+  if (i_io_write(io, "test2", 5) != 5) {
+    fprintf(stderr, "write failed\n");
+    return 0;
+  }
+  if (!i_io_flush(io)) {
+    fprintf(stderr, "flush failed\n");
+    return 0;
+  }
+  if (i_io_close(io) != 0) {
+    fprintf(stderr, "close failed\n");
+    return 0;
+  }
+  size = io_slurp(io, &result);
+  if (size != 5) {
+    fprintf(stderr, "wrong size\n");
+    return 0;
+  }
+  if (memcmp(result, "test2", 5)) {
+    fprintf(stderr, "data mismatch\n");
+    return 0;
+  }
+  if (i_io_seek(io, 0, 0) != 0) {
+    fprintf(stderr, "seek failure\n");
+    return 0;
+  }
+  myfree(result);
+  io_glue_destroy(io);
+
+  return 1;
+}
+
+const char *
+io_buffer_test(SV *in) {
+  STRLEN len;
+  const char *in_str = SvPV(in, len);
+  static char buf[100];
+  Imager::IO io = io_new_buffer(in_str, len, NULL, NULL);
+  ssize_t read_size;
+
+  read_size = i_io_read(io, buf, sizeof(buf)-1);
+  io_glue_destroy(io);
+  if (read_size < 0 || read_size >= sizeof(buf)) {
+    return "";
+  }
+
+  buf[read_size] = '\0';
+
+  return buf;
+}
+
+const char *
+io_peekn_test(SV *in) {
+  STRLEN len;
+  const char *in_str = SvPV(in, len);
+  static char buf[100];
+  Imager::IO io = io_new_buffer(in_str, len, NULL, NULL);
+  ssize_t read_size;
+
+  read_size = i_io_peekn(io, buf, sizeof(buf)-1);
+  io_glue_destroy(io);
+  if (read_size < 0 || read_size >= sizeof(buf)) {
+    return "";
+  }
+
+  buf[read_size] = '\0';
+
+  return buf;
+}
+
+const char *
+io_gets_test(SV *in) {
+  STRLEN len;
+  const char *in_str = SvPV(in, len);
+  static char buf[100];
+  Imager::IO io = io_new_buffer(in_str, len, NULL, NULL);
+  ssize_t read_size;
+
+  read_size = i_io_gets(io, buf, sizeof(buf), 's');
+  io_glue_destroy(io);
+  if (read_size < 0 || read_size >= sizeof(buf)) {
+    return "";
+  }
+
+  return buf;
+}
+
+int
+io_getc_test(SV *in) {
+  STRLEN len;
+  const char *in_str = SvPV(in, len);
+  static char buf[100];
+  Imager::IO io = io_new_buffer(in_str, len, NULL, NULL);
+  int result;
+
+  result = i_io_getc(io);
+  io_glue_destroy(io);
+
+  return result;
+}
+
+int
+io_peekc_test(SV *in) {
+  STRLEN len;
+  const char *in_str = SvPV(in, len);
+  static char buf[100];
+  Imager::IO io = io_new_buffer(in_str, len, NULL, NULL);
+  int result;
+
+  i_io_set_buffered(io, 0);
+
+  result = i_io_peekc(io);
+  io_glue_destroy(io);
+
+  return result;
+}
+
 EOS
 
 my $im = Imager->new(xsize=>50, ysize=>50);
@@ -266,3 +396,34 @@ ok($im3->write(file=>'testout/t82lots.ppm'), "write t82lots.ppm");
   is ($imb->REFCNT, $im2b->REFCNT, 
       "check refcnt of imager object hash between normal and typemap generated");
 }
+
+SKIP:
+{
+  use IO::File;
+  my $fd_filename = "testout/t82fd.txt";
+  {
+    my $fh = IO::File->new($fd_filename, "w")
+      or skip("Can't create file: $!", 1);
+    io_fd(fileno($fh));
+    $fh->close;
+  }
+  {
+    my $fh = IO::File->new($fd_filename, "r")
+      or skip("Can't open file: $!", 1);
+    my $data = <$fh>;
+    is($data, "test", "make sure data written to fd");
+  }
+  unlink $fd_filename;
+}
+
+ok(io_bufchain_test(), "check bufchain functions");
+
+is(io_buffer_test("test3"), "test3", "check io_new_buffer() and i_io_read");
+
+is(io_peekn_test("test5"), "test5", "check i_io_peekn");
+
+is(io_gets_test("test"), "tes", "check i_io_gets()");
+
+is(io_getc_test("ABC"), ord "A", "check i_io_getc(_imp)?");
+
+is(io_getc_test("XYZ"), ord "X", "check i_io_peekc(_imp)?");
index 775e0d3..2305c30 100644 (file)
@@ -3,6 +3,7 @@ use strict;
 use lib 't';
 use Test::More;
 use ExtUtils::Manifest qw(maniread);
+#sub Pod::Coverage::TRACE_ALL() { 1 }
 eval "use Test::Pod::Coverage 1.08;";
 # 1.08 required for coverage_class support
 plan skip_all => "Test::Pod::Coverage 1.08 required for POD coverage" if $@;
@@ -24,7 +25,7 @@ my @private =
   );
 my @trustme = ( '^open$',  );
 
-plan tests => 19;
+plan tests => 20;
 
 {
   pod_coverage_ok('Imager', { also_private => \@private,
@@ -50,6 +51,12 @@ plan tests => 19;
   pod_coverage_ok('Imager::Regops');
   pod_coverage_ok('Imager::Transform');
   pod_coverage_ok('Imager::Test');
+  pod_coverage_ok('Imager::IO',
+                 {
+                  pod_from => "lib/Imager/IO.pod",
+                  coverage_class => "Pod::Coverage::Imager",
+                  module => "Imager",
+                 });
 }
 
 {
index 00d9e4b..030997a 100644 (file)
@@ -25,6 +25,7 @@ IMAGER
 Imager
 Imager's
 JPEG
+POSIX
 PNG
 PNM
 RGB
diff --git a/tga.c b/tga.c
index ac02f50..fcfdb87 100644 (file)
--- a/tga.c
+++ b/tga.c
@@ -433,7 +433,7 @@ int
 tga_source_read(tga_source *s, unsigned char *buf, size_t pixels) {
   int cp = 0, j, k;
   if (!s->compressed) {
-    if (s->ig->readcb(s->ig, buf, pixels*s->bytepp) != pixels*s->bytepp) return 0;
+    if (i_io_read(s->ig, buf, pixels*s->bytepp) != pixels*s->bytepp) return 0;
     return 1;
   }
   
@@ -442,7 +442,7 @@ tga_source_read(tga_source *s, unsigned char *buf, size_t pixels) {
     if (s->len == 0) s->state = NoInit;
     switch (s->state) {
     case NoInit:
-      if (s->ig->readcb(s->ig, &s->hdr, 1) != 1) return 0;
+      if (i_io_read(s->ig, &s->hdr, 1) != 1) return 0;
 
       s->len = (s->hdr &~(1<<7))+1;
       s->state = (s->hdr & (1<<7)) ? Rle : Raw;
@@ -452,7 +452,7 @@ tga_source_read(tga_source *s, unsigned char *buf, size_t pixels) {
        printf("%04d %s: %d\n", cnt++, s->state==Rle?"RLE":"RAW", s->len);
  */
      }
-      if (s->state == Rle && s->ig->readcb(s->ig, s->cval, s->bytepp) != s->bytepp) return 0;
+      if (s->state == Rle && i_io_read(s->ig, s->cval, s->bytepp) != s->bytepp) return 0;
 
       break;
     case Rle:
@@ -464,7 +464,7 @@ tga_source_read(tga_source *s, unsigned char *buf, size_t pixels) {
       break;
     case Raw:
       ml = i_min(s->len, pixels-cp);
-      if (s->ig->readcb(s->ig, buf+cp*s->bytepp, ml*s->bytepp) != ml*s->bytepp) return 0;
+      if (i_io_read(s->ig, buf+cp*s->bytepp, ml*s->bytepp) != ml*s->bytepp) return 0;
       cp     += ml;
       s->len -= ml;
       break;
@@ -495,7 +495,7 @@ tga_dest_write(tga_dest *s, unsigned char *buf, size_t pixels) {
   int cp = 0;
 
   if (!s->compressed) {
-    if (s->ig->writecb(s->ig, buf, pixels*s->bytepp) != pixels*s->bytepp) return 0;
+    if (i_io_write(s->ig, buf, pixels*s->bytepp) != pixels*s->bytepp) return 0;
     return 1;
   }
   
@@ -506,9 +506,9 @@ tga_dest_write(tga_dest *s, unsigned char *buf, size_t pixels) {
     while(tlen) {
       unsigned char clen = (tlen>128) ? 128 : tlen;
       clen--;
-      if (s->ig->writecb(s->ig, &clen, 1) != 1) return 0;
+      if (i_io_write(s->ig, &clen, 1) != 1) return 0;
       clen++;
-      if (s->ig->writecb(s->ig, buf+cp*s->bytepp, clen*s->bytepp) != clen*s->bytepp) return 0;
+      if (i_io_write(s->ig, buf+cp*s->bytepp, clen*s->bytepp) != clen*s->bytepp) return 0;
       tlen -= clen;
       cp += clen;
     }
@@ -518,9 +518,9 @@ tga_dest_write(tga_dest *s, unsigned char *buf, size_t pixels) {
     while (tlen) {
       unsigned char clen = (tlen>128) ? 128 : tlen;
       clen = (clen - 1) | 0x80;
-      if (s->ig->writecb(s->ig, &clen, 1) != 1) return 0;
+      if (i_io_write(s->ig, &clen, 1) != 1) return 0;
       clen = (clen & ~0x80) + 1;
-      if (s->ig->writecb(s->ig, buf+cp*s->bytepp, s->bytepp) != s->bytepp) return 0;
+      if (i_io_write(s->ig, buf+cp*s->bytepp, s->bytepp) != s->bytepp) return 0;
       tlen -= clen;
       cp += clen;
     }
@@ -558,7 +558,7 @@ tga_palette_read(io_glue *ig, i_img *img, int bytepp, int colourmaplength) {
   palbsize = colourmaplength*bytepp;
   palbuf   = mymalloc(palbsize);
   
-  if (ig->readcb(ig, palbuf, palbsize) != palbsize) {
+  if (i_io_read(ig, palbuf, palbsize) != palbsize) {
     i_push_error(errno, "could not read targa colourmap");
     return 0;
   }
@@ -600,7 +600,7 @@ tga_palette_write(io_glue *ig, i_img *img, int bitspp, int colourmaplength) {
     color_pack(palbuf+i*bytepp, bitspp, &val);
   }
   
-  if (ig->writecb(ig, palbuf, palbsize) != palbsize) {
+  if (i_io_write(ig, palbuf, palbsize) != palbsize) {
     i_push_error(errno, "could not write targa colourmap");
     return 0;
   }
@@ -641,9 +641,7 @@ i_readtga_wiol(io_glue *ig, int length) {
 
   mm_log((1,"i_readtga(ig %p, length %d)\n", ig, length));
   
-  io_glue_commit_types(ig);
-
-  if (ig->readcb(ig, &headbuf, 18) != 18) {
+  if (i_io_read(ig, &headbuf, 18) != 18) {
     i_push_error(errno, "could not read targa header");
     return NULL;
   }
@@ -666,7 +664,7 @@ i_readtga_wiol(io_glue *ig, int length) {
   if (header.idlength) {
     /* max of 256, so this is safe */
     idstring = mymalloc(header.idlength+1);
-    if (ig->readcb(ig, idstring, header.idlength) != header.idlength) {
+    if (i_io_read(ig, idstring, header.idlength) != header.idlength) {
       i_push_error(errno, "short read on targa idstring");
       return NULL;
     }
@@ -898,13 +896,13 @@ i_writetga_wiol(i_img *img, io_glue *ig, int wierdpack, int compress, char *idst
 
   tga_header_pack(&header, headbuf);
 
-  if (ig->writecb(ig, &headbuf, sizeof(headbuf)) != sizeof(headbuf)) {
+  if (i_io_write(ig, &headbuf, sizeof(headbuf)) != sizeof(headbuf)) {
     i_push_error(errno, "could not write targa header");
     return 0;
   }
 
   if (idlen) {
-    if (ig->writecb(ig, idstring, idlen) != idlen) {
+    if (i_io_write(ig, idstring, idlen) != idlen) {
       i_push_error(errno, "could not write targa idstring");
       return 0;
     }
@@ -922,7 +920,7 @@ i_writetga_wiol(i_img *img, io_glue *ig, int wierdpack, int compress, char *idst
     if (!tga_palette_write(ig, img, bitspp, i_colorcount(img))) return 0;
     
     if (!img->virtual && !dest.compressed) {
-      if (ig->writecb(ig, img->idata, img->bytes) != img->bytes) {
+      if (i_io_write(ig, img->idata, img->bytes) != img->bytes) {
        i_push_error(errno, "could not write targa image data");
        return 0;
       }
@@ -951,7 +949,8 @@ i_writetga_wiol(i_img *img, io_glue *ig, int wierdpack, int compress, char *idst
     myfree(vals);
   }
 
-  ig->closecb(ig);
+  if (i_io_close(ig))
+    return 0;
 
   return 1;
 }