From 6d5c85a2f47f23387f7e6ef35cb5606cdeb2fc6c Mon Sep 17 00:00:00 2001 From: Tony Cook Date: Mon, 10 Oct 2011 18:13:10 +1100 Subject: [PATCH] Merge the I/O buffering branch commit 1d817f4340de9da358142746fdca48d45f078bc9 Author: Tony Cook Date: Mon Oct 10 18:07:12 2011 +1100 move changes into release area commit a0392ffea07d33bb4daec26bb8099e729ab31ac5 Author: Tony Cook 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 Date: Sat Oct 8 12:36:44 2011 +1100 fill out changes some more commit b54571f975d3a6f5643fddd80a0da852f65b43de Author: Tony Cook Date: Sat Oct 8 12:15:05 2011 +1100 document io_new_cb() callbacks a little + some cleanup commit a5504e7dde96bdc8db5af40d1f02612461d08ba4 Author: Tony Cook Date: Sat Oct 8 11:53:17 2011 +1100 make new I/O layer object constructors and document them commit c631d25b87134f26b2bb026d1cf87711e0564947 Author: Tony Cook 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 Date: Thu Oct 6 18:59:49 2011 +1100 make sure we read bytes for callback IO objects commit 2aacacb2743d89836dde4fbc02bdaa08c6f4a8e9 Author: Tony Cook 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 Date: Mon Oct 3 16:36:58 2011 +1100 add Imager::IO pod coverage tests + add method docs commit dd203834a9f5765bf57caa10b1e41e9e94da8b06 Author: Tony Cook Date: Mon Oct 3 12:31:29 2011 +1100 move internals to the end of iolayer.c commit 4692ab9a42838919f1f5fd185f341d592319601b Author: Tony Cook Date: Mon Oct 3 11:58:35 2011 +1100 clean up unneeded structures commit b179462d315f0f953d55ec216d92912d8af6be7f Author: Tony Cook Date: Fri Sep 30 23:36:41 2011 +1000 fail flush if error is set commit 0db35f7e68d5027c85f7bf28b6706fb7dc210c96 Author: Tony Cook Date: Tue Sep 27 00:08:11 2011 +1000 add i_io_gets() commit f1d7a30ae3376dcacf337faa3b172b135b8a85c2 Author: Tony Cook Date: Mon Sep 26 23:18:20 2011 +1000 remove more unused code, including unused variables commit 9d4eea2b2d0725466162ee2008faa52825ea68ca Author: Tony Cook Date: Mon Sep 26 21:46:29 2011 +1000 final coverage tests commit 28a5ceb962c54f01d68115c8ce23cf47c9c33742 Author: Tony Cook Date: Thu Sep 22 22:36:01 2011 +1000 WIP, more coverage and fixes commit 5a426928bc533e39c09acc795803a74054b7c779 Author: Tony Cook 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 Date: Thu Sep 22 22:35:07 2011 +1000 actually store the character in the i_io_putc() macro commit 676e43d395e0928ac46e3c6763169186c094fdcd Author: Tony Cook 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 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 Date: Sat Sep 17 15:06:27 2011 +1000 more coverage, some fixes, still WIP commit ddb433c610b9bc1e5588fcb7276b4f1916a09e8f Author: Tony Cook 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 Date: Sat Sep 17 00:24:55 2011 +1000 WIP, working on coverage tests commit 1987245122d4a6eae095592f995786ad6097ed21 Author: Tony Cook Date: Wed Sep 14 20:47:49 2011 +1000 update iobuf todos commit 2dbbb48e431d9c4f085330224e9a975be61f8a4c Author: Tony Cook 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 Date: Tue Sep 13 11:28:24 2011 +1000 improve error checking a bit commit 617340662609eaf1ddf385758c7729e3fa8d37bb Author: Tony Cook Date: Mon Sep 12 22:37:42 2011 +1000 WIP commit commit 342167b92f30c4a1f8f94b16f85875c866383263 Author: Tony Cook Date: Sat Sep 10 15:07:00 2011 +1000 remove buffering from the PNM reader commit 8c2fe37a2612a261cc53bcb955be11bd31974770 Author: Tony Cook 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 Date: Sat Sep 10 13:13:46 2011 +1000 change notes + todo for the iobuf changes commit dce56aa69674ff522c0d617e84e1daa965c00775 Author: Tony Cook 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 Date: Tue Aug 30 00:15:12 2011 +1000 reorganize the io_glue types for the upcoming buffering changes commit e080fa60bd5db126d4e2620131db4017cfe7a98c Author: Tony Cook 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 --- Changes | 61 +- GIF/imgif.c | 7 +- GIF/t/t10gif.t | 19 +- ICO/msicon.c | 1 + ICO/msicon.h | 2 +- ICO/t/t10icon.t | 18 +- ICO/t/t60writefail.t | 55 +- Imager.pm | 57 +- Imager.xs | 542 +++++++----- JPEG/imjpeg.c | 11 +- JPEG/t/t10jpeg.t | 22 +- Makefile.PL | 4 +- PNG/impng.c | 7 +- PNG/t/10png.t | 17 +- SGI/imsgi.c | 38 +- SGI/t/20write.t | 32 +- TIFF/imtiff.c | 114 ++- TIFF/t/t10tiff.t | 57 +- TIFF/testimg/grey32.tif | Bin 123078 -> 126462 bytes apidocs.perl | 6 +- bmp.c | 68 +- image.c | 201 +---- imager.h | 52 -- imext.c | 21 +- imext.h | 20 + imexttypes.h | 33 +- iolayer.c | 1675 ++++++++++++++++++++++++++++---------- iolayer.h | 25 +- iolayert.h | 84 +- lib/Imager/APIRef.pod | 271 ++++++ lib/Imager/Files.pod | 124 ++- lib/Imager/Filters.pod | 2 +- lib/Imager/IO.pod | 276 ++++++- pnm.c | 286 ++----- raw.c | 13 +- t/Pod/Coverage/Imager.pm | 16 + t/t07iolayer.t | 652 +++++++++++++-- t/t103raw.t | 24 +- t/t104ppm.t | 32 +- t/t107bmp.t | 21 +- t/t108tga.t | 17 +- t/t82inline.t | 163 +++- t/t93podcover.t | 9 +- t/x20spell.t | 1 + tga.c | 37 +- 45 files changed, 3723 insertions(+), 1470 deletions(-) diff --git a/Changes b/Changes index 34d53def..0f0cc01b 100644 --- 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 =========== diff --git a/GIF/imgif.c b/GIF/imgif.c index 4e1234bb..a9875c2a 100644 --- a/GIF/imgif.c +++ b/GIF/imgif.c @@ -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; } diff --git a/GIF/t/t10gif.t b/GIF/t/t10gif.t index 40a8860f..c926b0c6 100644 --- a/GIF/t/t10gif.t +++ b/GIF/t/t10gif.t @@ -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) = @_; diff --git a/ICO/msicon.c b/ICO/msicon.c index b59b5b3d..6847504f 100644 --- a/ICO/msicon.c +++ b/ICO/msicon.c @@ -1,3 +1,4 @@ +#include "imext.h" #include "msicon.h" #include #include diff --git a/ICO/msicon.h b/ICO/msicon.h index 5b66704c..c0f5b9d7 100644 --- a/ICO/msicon.h +++ b/ICO/msicon.h @@ -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; diff --git a/ICO/t/t10icon.t b/ICO/t/t10icon.t index 707bc4b8..25b19177 100644 --- a/ICO/t/t10icon.t +++ b/ICO/t/t10icon.t @@ -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"); +} diff --git a/ICO/t/t60writefail.t b/ICO/t/t60writefail.t index 80ce21ae..1841b533 100644 --- a/ICO/t/t60writefail.t +++ b/ICO/t/t60writefail.t @@ -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) = @_; diff --git a/Imager.pm b/Imager.pm index aecf0c4c..bd645bb5 100644 --- 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 - compose one image over another. -convert() - L - -transform the color space +convert() - L - transform the color +space copy() - L - 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 - delete image tags -difference() - L - produce a -difference images from two input images. +difference() - L - produce a difference +images from two input images. -errstr() - L - the error from the last failed -operation. +errstr() - L - the error from the last failed operation. -filter() - L - image filtering +filter() - L - image filtering findcolor() - L - search the image palette, if it has one @@ -4467,9 +4472,9 @@ polyline() - L preload() - L -read() - L - read a single image from an image file +read() - L - read a single image from an image file -read_multi() - L - read multiple images from an image +read_multi() - L - read multiple images from an image file read_types() - L - list image types Imager @@ -4533,9 +4538,9 @@ unload_plugin() - L virtual() - L - whether the image has it's own data -write() - L - write an image to a file +write() - L - write an image to a file -write_multi() - L - write multiple image to an image +write_multi() - L - write multiple image to an image file. write_types() - L - list image types Imager diff --git a/Imager.xs b/Imager.xs index e11fd031..9716a563 100644 --- 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; diff --git a/JPEG/imjpeg.c b/JPEG/imjpeg.c index eaf9a5d7..d4341b40 100644 --- a/JPEG/imjpeg.c +++ b/JPEG/imjpeg.c @@ -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); } diff --git a/JPEG/t/t10jpeg.t b/JPEG/t/t10jpeg.t index 2ca080cc..a88554d7 100644 --- a/JPEG/t/t10jpeg.t +++ b/JPEG/t/t10jpeg.t @@ -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"); +} diff --git a/Makefile.PL b/Makefile.PL index d85bd5bb..edd8a2be 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -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"; diff --git a/PNG/impng.c b/PNG/impng.c index fa3218d8..5fd41512 100644 --- a/PNG/impng.c +++ b/PNG/impng.c @@ -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); } diff --git a/PNG/t/10png.t b/PNG/t/10png.t index 9dbc399b..4077a99e 100644 --- a/PNG/t/10png.t +++ b/PNG/t/10png.t @@ -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"); diff --git a/SGI/imsgi.c b/SGI/imsgi.c index d8d9be3d..077388aa 100644 --- a/SGI/imsgi.c +++ b/SGI/imsgi.c @@ -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: diff --git a/SGI/t/20write.t b/SGI/t/20write.t index 879412b6..bcbf4e53 100644 --- a/SGI/t/20write.t +++ b/SGI/t/20write.t @@ -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) = @_; diff --git a/TIFF/imtiff.c b/TIFF/imtiff.c index 07e363cb..832f38d4 100644 --- a/TIFF/imtiff.c +++ b/TIFF/imtiff.c @@ -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; } diff --git a/TIFF/t/t10tiff.t b/TIFF/t/t10tiff.t index c93cfc0e..40d15aeb 100644 --- a/TIFF/t/t10tiff.t +++ b/TIFF/t/t10tiff.t @@ -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"); diff --git a/TIFF/testimg/grey32.tif b/TIFF/testimg/grey32.tif index a3844b84ac6873178cd18c7921562d2e901a4616..2982554504a0e761a186e8bc69f1328dba5d8e8b 100644 GIT binary patch literal 126462 zcmeI53vd)=7KLXRkO)X2hVaHHk5wduB=QD<2vI`Fij)zFL@X*`!gEPvSr#Zth!Lm= z0*VEqk`P%n@(7VtQNRrla49hAu8SZ51B?O20AqkLz!+c* zFa{U{i~+^~V}LQh7+?%A1{ed30mcAhfHA-rU<@z@7z2y}#sFi0F~AsL3@`>51B?O2 z0AqkLz!+c*Fa{U{i~(O_z;Qn9sJ*t%vA5xjMj}%uzTHe@So7o-B0VzOwxpP*32;?# zjc~=B*Uy(r&vW!|87?N!?1^_Y=q50fcR$%nWN~atABt(309OUq2v^KWh?&CN@p*nv zSrF=&yLP#f@|`2py*KMXp^`zVl@!04;KN7XQSP-nJM&JFy(|AQTI6p<1(QS)-^rTc zm5iU?WR{$;tX)cuNKM+&KZ&&asl@`3_*p8WD-CS4EaRf2NS)PZNw>v~HEOAh?C!I*ry;z`|2O>bSaZ zBjmR}H7%#9NKxnal|N8?Zh}d7{Z5rbXW|?kMXHDIiWixjx^}Qgx3P`x_ezfZlAS6i zbb6>yrpWI99999=Jo)7}MRxvBTSoDx32@6ps)BmOS<#ycSXe7O6{K#z`d)Ln{O$Ip zD}SK)+ysqU-=l#2YTuz&BF{!QR@|l^DUKFN>iV?GlRcMC(>uh;3E;M8hlr(ebn@-A zH$(`mzfE8jU{R+c?S+{F(X{3dMXab-OXFj{7>k_#*2Tr`ZiNN|}t+>|_SaU>q z7~O3MtiLX*mxmBo0vq7uguoKmYeP3oiOqk1A+Q9Nz*?wAC$I#Tz!KO1blB+ChE{ox zHLUq_OpA3$(P8PZbXYnp9ro8bCv;dkEPIJ9G(t>Z2`qsnuz{Y|=vtA(VIA||Us%Jk zhGh-Q8kRLIYgo=fHlhQX4oion!_r~tuyj~DEFIQJH#A~0278Ik--}}eRod@)(Xmo3 zP>jC#$ZaCe?EfZGWXHD6YISVN8%?6TlA?->UFC#_n|v@rWZR0=Jdupg+7ycz5yJKB zdlK0C0<0dC3tsqCL0L8B@irpGk-6$e#4*jUt2AKeAbb6nk>dBCufrtQJuuvk%O^+FC$>VP=24yptTcg;KUi z>#06@xgb^~dsA_|$l-Z$_lneJUiMBR&)wEv&P%+IGE8K{kGs{5Zl7Ga{Funes-|iu z#$zQvPZRlc_`lRXZHLMe3q(Niklq<;lQMWQP51_i39Lnd)k74?Z0wApJ0$dBm{rMh zddQJmC|^n+-&@|RNQ&+w(!FDevdHv^+5JR*sQtn_$@#M706A~=vgZeh%)FGRxSdgC zA1-nsc9+VNYhEl(5vhK1W12`+%*u%(p!mq;v5VyhUQB!mY=CFtX}fQXKLV*^zG|;} zhNAKll#9;%=@nIP0@3!bX51}D@J$$I=Bzc!DFXwg*A(Lfw=hpnTaYBb87Mwo@p8Hx z9lyFTTLkzfIp(gGqx;SsQ#(WTFFUwJq+Zs(bXfB{tXngE>%qLX610ccwpLpk!bO2- zgLM%J@*;fGPs8+;P0!EMQVcK7{xElx{0+bt?t6H`sORLxVMix#5UH10Gl4ZRuzF^S zT695fO?Qdi*URUttwpVZs2HE6z za^$pK`>4KkYUksj&zycuB;3(+14t&HIy^e1B+$kOAt1UBfvYLj4$r(%=_eZLr<3BOKX&+AlQ zKMB8ej^?`hlQ9m2@mouGBoo-61FKDfG4lcAsTiffTnAr+mgl)9MmooVT;sQxcVX!S znZO1QSSG<!Gjq_i*~~>`u~xlbg1{OHEOKG&&VoH3u#z0>Q#o`K zJYZq8SbvLk^VshK`_!0zjF!L}2`qA9Y;K0JR_ytJmE;~Ibo@wQnZ_m<*zW>kwAf$D z^j%N{)<|HP3kMdpz}&hlof#8YBZ0-dL-Y${8$xVu##}g5Fe5X@{!-W%PTOa!S4D>Q zJpyY8uqa-!-!FC%)egA^#x3|=e8wJo(O?srzqhDZCad#4`ndHQBIYe+39KQ&vUv5G zqoCb_tXawuSVMqi@oIixty#(vSVMqi@oIixty#(vSVMrt6QuA!8a(X+d*}x(=i?9Z zSImbarOr;$ie=l7e5wA-8BLd5vp_PzcIbXZT!%@o)Z*g(Uoz8`_D-!^C<+60!s zUV92MYgqFeErBJl1Xgk)15GiY-9gWEtoa?512LjIn#;2UK?WKTw?PB%elk^_8PEUtz&**xmt(7UIP|0Em+E$ky~pP zujs5|r;{O_P5_prtQmo|X7Ng3jU*MovXnI=u+}VI32YF6<*tdQ0v5S2cTEiZWPjIi zj!UgJtfO5mz|7bj+*&#pro#roVYQhtAF2~7Zegw3eqn1qVuzhhLEsjdv9`~c&(;YZ zzOYt4TPNV_UR^2hjcVu_g(&Px`)YytF5tpEK;} zqj_lX1u+u5%nK6u(39`>ny7knsO@g)D64+1y>o!47C@i~OZvH7t zyL1<++>#hC!opS;Z7r!;GcS#nHan5zc&fI}KB{lsQaQTs+_6m}rhhb@mQA-u1UAIL zY7^wS6ZW)`$jU637cEk~FrVCh(N9{!15X916Q=KtmT%# zh8S3Ff{enJ+`2~;+`9GDa3>Yq5?FHp>jAgOlQ95@n*q_|R~Kf>5th`{OEV>~R%26J*lLeZ=4x#h3-}tJuT#^MfiK** zDtS&1d9nC%L97TUK0Gf@Q4B8*49sq!L_g7m*j*3E&jXv)Pi{<;qpFyd6Gh;nK(ylS z0juQ2A-yvSMe3!Q64-D8)^mJ|BXiqJ%x8bRypsrOUr@aDiEa1Di|}H%FypDVo=fDp z+xn~Tc_C$($c7(xj}iIg%H_vIPF6LYBmy>3iykUZERYxXt%zGILPT|+7{HV`3@O&S zFZR2rUOzobc&M84xSE;)is8lUOU9@PR&HU&)2Ka{M{kZ&BTc8b9!eIu@=LGTB8!X8 zaBjs`ny+4MSkH?u#S06r%@g!3&@MCd_yO zpXYKaezS7zmT^A*=^#w||q7@^w>Q0oh=IY!Z|!~S z5?BIDU@dfCBCrIOz!KOpX#&08n0oRaJDt4G`Isl`wizAP$QuiR)eSzf>3#DMSOUwG zm?<$+;#$=NbXe~#gtn{OW^`COEFG2(ONXVyHjim(ArP!$>~vT+Q_54`^jD+!U{W3=+e&2Sr{{+S7CIG3I3b&4vhLp{?x`|Ak z__oaa3pSK`KhKeMpioUs960t|Ly=R{a+->4zxrNt5zJ7en4=~B@ZWBca#f=L{E^!G z4LhCp`1c}(@8Z}LwaF^Q=O%!sf>fuqI`%f4Av1RM01Ebdj`E!&6tKx%Djg!@>b{K- z0k@c0OmWSz4sA<(Ql5B=T%5KI)70H`=aTtqzIVg@D{d9(k=eE-#h)esqE1f*sgCn$ zM}>}HL#g-k9COz$mxsT*j27I&Z?PhP;#wOK+Lpjo!8Jl^wk2>?aE)-qoY#-x?_2n@-Pmr70mcAhfHA-rU<@z@7z2y}#sFi0 zF~AsL3@`>51B?O20AqkLz!+c*Fa{U{i~+^~V}LQh7+?%A1{ed30mcAhfHA-rU<@z@ z7z2y}#sFi0F~AsL3|J`!Y)x%8yTfL;*=#S#fBSV`H;}Iz$ZOH^&whJvTzWZh777t?;!vzcoHH^*g(*!*!o|_`0v!-FN(xBBV&x5%0zOcAv|1L!BLYH&QiX;FEg(t&VR^QdAggpDGzAe+ zY^cJzhPJ?}l_J(^fglQqCaZ{S7Bs=AQHq5C$x15CcDvu{{a>B2Lo+kIGjs0ri($UG zGri-z=YGHY|DVS_=Ya7>|s(o+{d?bmtZh#zVyEF!JG4lin(CJ2Jk}8O% z4LGShF*@UXTS#S1jyh_J9!21ok=X&4qcEvgJmg??>8+3lKVCQt^6HSZ6iQ2q;Pjwx zhoS_h^=oqrMDR7Z)e2aNsY2?khU!=x?oW$zIzry;U#0Fg6g`R{^YgylaXHEpY6e3d zJMNwdsp^@Q1DSvHoi&t}6v5TG?XppV@g?OmAi}K?Z?yteLaW47A+_q-2XQ#+{)e+v zmO#;?2%NF5n{hb~7EK)qNq+y*O30-NqY5GaeBsApN=u3$-@R=MN>I3!cxwb&t$>w! zs-(RVOa;+7ad)X&0Y#4@$Q<}&A}+_cjF@qd-sJ<=LE4{vuk^nSZT?eV!B@``K+(ue zOp*_&G#HhAB1o*^kbv)OL@kO&gNh*pk?AQ2>j zM9{OzL+AIb3$iX~{r!qjkWtWjSGDG5?S%>}iL(XkZ&xaI$)qSVu-JkD`5SkY7b3kNt-?t5tmwxgMCC({jN*0WN zM@i4a@3v+nQ3S*=3NiFa+~w^{u8Z2PvZ-ozioZO(P(Aa0b>AzuLH0koSK-trW#nA} z$%6J>hhvAj^3Ntg66=R%Ln=#t%m-?>-@65`n}-$QH7j8UzR24?eYcWU3?@#W7)Aw# z-rz|pIW7BIH(cU!Be>;$w6QO?e4>`#uZ^0{X>aYOA&`}&JCh)1Ru8)y(tdtzV3K(L z#u3;r<6_!@qNd+O5Uzp3=qekapU#K*i8nS_#*F~*=jl#GibJ! zXo*uMh&BbWIrJo<9I>>dA1<+kw+oY2-ia-ruoY)Lau2>cH6bAdlG3M41+)dR*?)#O z{`arI#C^KUz1YwF+zV>zC-;?n#iP5#IbQwP&;u%0b!^?2rsVM*nMww~I7f*=XKlt> zaEZW)k6ICzI9sx4Hx>oLtZy3U5ltvaR)O!StAA6~Iic^Czt6rMTN3Id#;sUhsDhg{ zyaf!X?BJWz%x5Ox$V@;#eI-k6)vZ|sCP5}P@?5K4R&xI9QY96i?okqDHA|clY1E%a zpFyx9HNOY0-|3=m2@r{X!e89B*kpVt@ven|E?DsV-|r8I8Zp#;Fn2P}!69rKQFGSh z_4q9B#QfKkNaIiR_cK`fbRq8HCGNcI0U_%eGwzY>rjmbS5r-2JEXYho*Jc*6#4q_jM%i;`(q*h4! zWO4bX)haKYv0*@zg+u>oH_4P1B1vl{a87hRp;%nksX&R%$t06%#EBOD_|3t1Wle#C z@$8l02#V@~^9ZBu$q&^n72BF)#$Z=fZXk-*YBYn6U~>28!x4(aI+)|%Y!x8Zz^-6&@Fri?n!J)tF*Va1YZ zYXCWE3vVrE(3Hj?o29e_Gpt}rpp*wRWPqxSjAP;&5@=06W%xVB?EOBFRvtRhupNk8 zhKEb;kYBwR|^H6K!uA+^RhcX{?i$Ce<186GehDMLw}A33kx;wHfi z512A*jXYc|eIfEx6+>nCpq0RBJ||{8go1evz=4|DK%b6Hf*E2kC5M&iHso=CdDvK< zb7XHw7{QdMujM&TJX#GFG1q|tQ>Gr#q;HdChL}uAVr8-^>8F(E|K-tRKQb}?iM8;` zga~Tx1EYTvuQD}|CM`h(GsIvriM19fYi?0CNNGuCh{=?pcG40nGs4M?RLo){MXfXj znUe=$GB3o;-&<+15%E#9Br{ZGO8ru1PL-J(y*n+fx&QN5g67+t-OK3S?0oz=Ao)?Z zU4SC;69bXVP?5>{WzfKc?OvmEZIfh%icD58Z9t|m`+=T4C`2<W@pvhrCEy-ieeeKpd%dxXIK+{;dTFWOj88kx` zH2J)V4aoGGPLI!<1UKHmZZT6v9`N~Od*r?O)bgPxrRwHaaZyl zQWuIXRyVaIQ{xII!3+zSoIOt~FwJRaIeT7kRZ6ot)>Ca-&16oi7n%BJu?(4E37G<> zq_kYjBvP1~vC^w8wl-reOpvGJ(k3`A4gm^7T~kt8?`B#qY7#z7xu}U}`f25Rd#ADt zoZ(~8dt9#FLM1cVaAvZ?rFY!qYVV> z;rKIdlxp6I`L8Ld_;imFO;!~$QdiGc930zy!qDy`VbWWe)CP^7cNJ2$Odjo-xDv~7 z4EFIRu(>ViLy?qrbaBPaxLMBFFklem?4GenkeH-%DG)Ax;+x|?@VM;C6cmpk*nG{I5JS`RjKqJ zO{s8R-S^6EaH2HUj>-Qb8Cy|X?=)eQ7M#Y8j>d*n1>@Uwi#;liHq2B#VPu%ZCG|;z zP9J&)O=${}(Ff9vvLUxdRXU?6cA60SjEIE=^I%=+C(7OMiMT~b@$2p-IxjB{{cAz38|hw$vI$?LIUb~mP< zd_!%aHC2hWy^hzTuj%JF4L0uRA1A$Ic5_j4#L|*}xTrGZUFcj{x>I$htXN)nH{{fW z1l6k&q59_^&@-MwcFQx5Tlb~m$V>w3)+~Z!<*AP7i-n}oZs2)i=)L7LWXc0#T`n~A zhOdQ9iJTWEt-KQ-?ycQ41R{u@Sv^c4Dsj{rQhgIAPfiUzFcJShb0OHrcVw!q!7t8H zVnjgj2dCbC?-qPyK3f%aagZ!V6N~`(=SDUXcZgxAf(itt4%g3l92 zoHWw5JT@+DO*0- zlxCpyFMWAkL}mGE|K&>3ZWUuwFD_JRXx;3~Yakij_icmZNtW4)p=*jH%7qxV77Tr| z=?9{+Fs}bnas2W?IBQvl{c2_NbDPuiA;n*H-`UiF{DcVVHuk1DPVDks=D;Tt;jD2P zG2NR0z{Aq-eq|a5JA6|vomlcf|mQwE}<%f$4LZiG$qW7r&H{r!qjkWtWjR|TAm zwdNg&AQ2>jOholeJDVOA6Hz9j*59uf1sMgccU5c7D+4}vB1i;@AQRE1DW9I7FcD=U zYW@9+QIJv4dRGOUjJ4(+h#(Opf=op9Ogoz%m5mb7MV}Nqak!&1&bNj9v^b|Dr0UuS zaghFpvpQ3>DFSD#OU;IHuxRQ?Nb>uaRzfaK7*z<7C&Vd1mqfmM+ZGh$)w%7mA*Tm@ zI~0-{Hwl;Ds&x>p_#M~L%KH_tX#-BWAhQ~(VP-nYT~Hux+7|GDJz7`MaGPTs41{^V@i ixNj`pck%fACV8*+?qQ diff --git a/apidocs.perl b/apidocs.perl index ad5e259f..c0c389a5 100644 --- a/apidocs.perl +++ b/apidocs.perl @@ -189,8 +189,10 @@ sub make_func_list { my $in_struct; while () { /^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 9cf76157..ed134b2e 100644 --- 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 abf86768..6cb49d48 100644 --- 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 value, and a I -value, where I is the amount of data that the file library needs -to read, and I 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 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 -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 and L. - -=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 - -=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 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 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 d0544e6e..e6f0eee7 100644 --- 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 08d6a0ca..912b04d3 100644 --- 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 diff --git a/imexttypes.h b/imexttypes.h index 573cdc72..13553824 100644 --- a/imexttypes.h +++ b/imexttypes.h @@ -14,8 +14,13 @@ 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" diff --git a/iolayer.c b/iolayer.c index 5cb8753b..f26a4bce 100644 --- 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(p, buffer, length) should read up to C bytes into +C and return the number of bytes read. At end of file, return +0. On error, return -1. + +=item * + +C(p, buffer, length) should write up to C bytes from +C and return the number of bytes written. A return value <= 0 +will be treated as an error. + +=item * + +C(p, offset, whence) should seek and return the new offset. + +=item * + +C(p) should return 0 on success, -1 on failure. + +=item * + +C(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 (at most C<< ig->buf_size >> bytes of data +from the stream and return C bytes of it to the caller in +C. + +This ignores the buffered state of the stream, and will always setup +buffering if needed. + +If no C parameter is provided to Imager::read() or +Imager::read_multi(), Imager will call C 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. + +=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 bytes from the stream C into C. + +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-1 bytes from the stream C into C. + +If the byte C is seen then no further bytes will be read. + +Returns the number of bytes read. + +Always C 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 and C. + +If there is more than a pleasing amount of data, either dump the +beginning (C) or dump the end C() 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 diff --git a/iolayer.h b/iolayer.h index ec164c0f..db7c5776 100644 --- 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. * */ @@ -20,15 +19,29 @@ #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_ */ diff --git a/iolayert.h b/iolayert.h index f7fdac9b..167ef30f 100644 --- a/iolayert.h +++ b/iolayert.h @@ -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 diff --git a/lib/Imager/APIRef.pod b/lib/Imager/APIRef.pod index 4e7c6ce3..f080131c 100644 --- a/lib/Imager/APIRef.pod +++ b/lib/Imager/APIRef.pod @@ -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(p, buffer, length) should read up to C bytes into +C and return the number of bytes read. At end of file, return +0. On error, return -1. + +=item * + +C(p, buffer, length) should write up to C bytes from +C and return the number of bytes written. A return value <= 0 +will be treated as an error. + +=item * + +C(p, offset, whence) should seek and return the new offset. + +=item * + +C(p) should return 0 on success, -1 on failure. + +=item * + +C(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-1 bytes from the stream C into C. + +If the byte C is seen then no further bytes will be read. + +Returns the number of bytes read. + +Always C 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 (at most C<< ig->buf_size >> bytes of data +from the stream and return C bytes of it to the caller in +C. + +This ignores the buffered state of the stream, and will always setup +buffering if needed. + +If no C parameter is provided to Imager::read() or +Imager::read_multi(), Imager will call C 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. + + +=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 bytes from the stream C into C. + +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 diff --git a/lib/Imager/Files.pod b/lib/Imager/Files.pod index 6c162571..0bc8f819 100644 --- a/lib/Imager/Files.pod +++ b/lib/Imager/Files.pod @@ -73,7 +73,7 @@ or L methods useful. =over -=item read +=item read() Reading writing to and from files is simple, use the C 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 parameter. If this is -non-zero then read() can return true on an incomplete image and set +The read() method accepts the C parameter. If this +is non-zero then read() can return true on an incomplete image and set the C 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 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 method: @@ -118,7 +118,7 @@ file, use the C method: As with the read() method, Imager will normally detect the C automatically. -=item write_multi +=item write_multi() and if you want to write multiple images to a single file use the C method: @@ -252,34 +252,33 @@ writing. =item * -C - Imager will make calls back to your supplied coderefs to -read, write and seek from/to/through the image file. +C, C, C, C, C - Imager +will make calls back to your supplied coderefs to read, write and seek +from/to/through the image file. See L below for details. -When reading from a file you can use either C or C -to supply the read callback, and when writing C or -C to supply the write callback. +=item * -When writing you can also supply the C 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 - an L 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. +XXBy 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 or +C. -The seek callback takes 2 parameters, a I, and a I, -defined in the same way as perl's seek function. +=head2 I/O Callbacks -You can also supply a C 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 or C +to supply the read callback, and when writing C or +C to supply the write callback. + +Some file formats, currently only C, also require a C +parameter to change position in the file. If no C parameter +is provided a default will be provided that fails. + +You can also provide a C 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. +=head3 C + +The read callback is called with 2 parameters: + +=over + +=item * + +C - the minimum amount of data required. + +=item * + +C - previously this was the maximum amount of data returnable +- currently it's always the same as C =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. + +=back + +If your return value contains more data than C Imager will +panic. + +Your return value must not contain any characters over C<\xFF> or +Imager will panic. + +=head3 C + +Your write callback takes exactly one parameter, a scalar containing +the data to be written. + +Return true for success. + +=head3 C + +The seek callback takes 2 parameters, a I, and a I, +defined in the same way as perl's seek function. + +Previously you always needed a C callback if you called +Imager's L or L without a C 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 + +You can also supply a C 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 When writing to a file, if you don't supply a C 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 This is the default function Imager uses to derive a file type from a file name. This is a function, not a method. diff --git a/lib/Imager/Filters.pod b/lib/Imager/Filters.pod index fa7e7002..b614b1e6 100644 --- a/lib/Imager/Filters.pod +++ b/lib/Imager/Filters.pod @@ -30,7 +30,7 @@ Filters are operations that have similar calling interface. =over -=item filter +=item filter() Parameters: diff --git a/lib/Imager/IO.pod b/lib/Imager/IO.pod index ec2b618f..2dae0133 100644 --- a/lib/Imager/IO.pod +++ b/lib/Imager/IO.pod @@ -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 +XXNote 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 for details on the behavior of +the callbacks. + +=item new_bufchain() + +Create a new C based I/O layer. This accumulates the file +data as a chain of buffers starting from an empty stream. + +Use the L 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 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. + +The default C is 8192. + +The default C is C. + +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 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 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 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 - I<$offset> is the new position in the file. + +=item * + +C - I<$offset> is the offset from the current position in +the file. + +=item * + +C - 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. + + $io->dump(); + +=back + =head1 AUTHOR Tony Cook diff --git a/pnm.c b/pnm.c index 31afbd50..5dfd0c88 100644 --- 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;ytags, "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; chtags, "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; chtags, "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 9159e5a6..bae403d0 100644 --- 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( kysize ) { - 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); } diff --git a/t/Pod/Coverage/Imager.pm b/t/Pod/Coverage/Imager.pm index 212d026b..bccd5cd7 100644 --- a/t/Pod/Coverage/Imager.pm +++ b/t/Pod/Coverage/Imager.pm @@ -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; diff --git a/t/t07iolayer.t b/t/t07iolayer.t index 83a3e9b9..bead870b 100644 --- a/t/t07iolayer.t +++ b/t/t07iolayer.t @@ -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, "; 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, "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; diff --git a/t/t103raw.t b/t/t103raw.t index 316ce271..01f52729 100644 --- a/t/t103raw.t +++ b/t/t103raw.t @@ -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}) { diff --git a/t/t104ppm.t b/t/t104ppm.t index b6f95268..c85d7a9b 100644 --- a/t/t104ppm.t +++ b/t/t104ppm.t @@ -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; diff --git a/t/t107bmp.t b/t/t107bmp.t index 0129ea83..356d7767 100644 --- a/t/t107bmp.t +++ b/t/t107bmp.t @@ -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}) { diff --git a/t/t108tga.t b/t/t108tga.t index 11be19ec..cfdd6645 100644 --- a/t/t108tga.t +++ b/t/t108tga.t @@ -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; diff --git a/t/t82inline.t b/t/t82inline.t index c4e24a25..15f625d1 100644 --- a/t/t82inline.t +++ b/t/t82inline.t @@ -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 @@ -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)?"); diff --git a/t/t93podcover.t b/t/t93podcover.t index 775e0d38..2305c300 100644 --- a/t/t93podcover.t +++ b/t/t93podcover.t @@ -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", + }); } { diff --git a/t/x20spell.t b/t/x20spell.t index 00d9e4b6..030997a4 100644 --- a/t/x20spell.t +++ b/t/x20spell.t @@ -25,6 +25,7 @@ IMAGER Imager Imager's JPEG +POSIX PNG PNM RGB diff --git a/tga.c b/tga.c index ac02f503..fcfdb878 100644 --- 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; } -- 2.39.5