From 4e2f7c8e8fdcdb1dbf6f2346e13f46f8ce3eca48 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 20 Apr 2009 04:02:15 +0000 Subject: Add tmpfile() implementation of FILE_Cookie. --- configure.ac | 2 + libdimensionxx/Makefile.am | 9 +- libdimensionxx/cookie-fopencookie.cpp | 173 ++++++++++++++++++++++++++++++++++ libdimensionxx/cookie-tmpfile.cpp | 136 ++++++++++++++++++++++++++ libdimensionxx/cookie.cpp | 173 ---------------------------------- libdimensionxx/dimensionxx/cookie.hpp | 5 +- libdimensionxx/png.cpp | 2 +- 7 files changed, 322 insertions(+), 178 deletions(-) create mode 100644 libdimensionxx/cookie-fopencookie.cpp create mode 100644 libdimensionxx/cookie-tmpfile.cpp delete mode 100644 libdimensionxx/cookie.cpp diff --git a/configure.ac b/configure.ac index 03014f8..ad52306 100644 --- a/configure.ac +++ b/configure.ac @@ -29,6 +29,8 @@ AC_PROG_LN_S AC_PROG_MAKE_SET AC_PROG_LIBTOOL +AM_CONDITIONAL([FOPENCOOKIE], false) + dnl Generate Makefiles AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_FILES([Makefile diff --git a/libdimensionxx/Makefile.am b/libdimensionxx/Makefile.am index 143ab83..74eb03b 100644 --- a/libdimensionxx/Makefile.am +++ b/libdimensionxx/Makefile.am @@ -23,6 +23,13 @@ INCLUDES = -I../libdimension -I../libdimension-png lib_LTLIBRARIES = libdimensionxx.la -libdimensionxx_la_SOURCES = $(nobase_include_HEADERS) canvas.cpp color.cpp cookie.cpp error.cpp png.cpp +libdimensionxx_la_SOURCES = $(nobase_include_HEADERS) canvas.cpp color.cpp error.cpp png.cpp + +if FOPENCOOKIE + libdimensionxx_la_SOURCES += cookie-fopencookie.cpp +else + libdimensionxx_la_SOURCES += cookie-tmpfile.cpp +endif + libdimensionxx_la_LDFLAGS = -version-info 0:0:0 libdimensionxx_la_LIBADD = ../libdimension/libdimension.la ../libdimension-png/libdimension-png.la diff --git a/libdimensionxx/cookie-fopencookie.cpp b/libdimensionxx/cookie-fopencookie.cpp new file mode 100644 index 0000000..9fd4e07 --- /dev/null +++ b/libdimensionxx/cookie-fopencookie.cpp @@ -0,0 +1,173 @@ +/************************************************************************* + * Copyright (C) 2008 Tavian Barnes * + * * + * This file is part of The Dimension Library. * + * * + * The Dimension Library is free software; you can redistribute it and/ * + * or modify it under the terms of the GNU Lesser General Public License * + * as published by the Free Software Foundation; either version 3 of the * + * License, or (at your option) any later version. * + * * + * The Dimension Library is distributed in the hope that it will be * + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty * + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this program. If not, see * + * . * + *************************************************************************/ + +#include "dimensionxx.hpp" +#include + +// The conundrum: libdimension and libdimension-* use C I/O, with FILE*'s. +// We want to use C++ I/O with std::i/ostreams. If present, we use the +// nonportable GNU stdio extension fopencookie(), which creates a FILE* with +// custom read/write/seek functions. BSD also has a similar function, funopen() +// which we should use too. Failing in all that, fall back on a tmpfile() +// buffer (see cookie-tmpfile.cpp). + +namespace Dimension +{ + namespace + { + // Cookie read function + ssize_t + cookie_read(void* cookie, char* buf, size_t size) + { + FILE_Cookie* streams = reinterpret_cast(cookie); + + // Do the unformatted read + streams->istr().read(buf, size); + + if (streams->istr().eof() || streams->istr().good()) { + return streams->istr().gcount(); // This returns 0 on an immediate EOF + // for us. + } else { + // Some non-EOF error + return -1; + } + } + + // Cookie write function + ssize_t + cookie_write(void* cookie, const char* buf, size_t size) + { + FILE_Cookie* streams = reinterpret_cast(cookie); + + // Do the unformatted write + streams->ostr().write(buf, size); + + if (streams->ostr().good()) { + // Write operation succeeded, so we must've written size bytes + return size; + } else { + // Write operation failed + return -1; + } + } + + // Cookie seek function + int + cookie_seek(void* cookie, off64_t* offset, int whence) + { + FILE_Cookie* streams = reinterpret_cast(cookie); + + if (streams->is_input()) { + // If we have an input stream, seek it + switch (whence) { + case SEEK_SET: + streams->istr().seekg(*offset, std::ios::beg); + break; + case SEEK_CUR: + streams->istr().seekg(*offset, std::ios::cur); + break; + case SEEK_END: + streams->istr().seekg(*offset, std::ios::end); + break; + } + + if (!streams->istr().good()) { + // Seek failed + return 1; + } + } + + if (streams->is_output()) { + // If we have an output stream, seek it too + switch (whence) { + case SEEK_SET: + streams->ostr().seekp(*offset, std::ios::beg); + break; + case SEEK_CUR: + streams->ostr().seekp(*offset, std::ios::cur); + break; + case SEEK_END: + streams->ostr().seekp(*offset, std::ios::end); + } + + if (!streams->ostr().good()) { + // Seek failed + return 1; + } + } + + // Seek succeeded + return 0; + } + } + + // Make an input FILE_Cookie + FILE_Cookie::FILE_Cookie(std::istream& istr) + : m_istr(&istr), m_ostr(0) + { + cookie_io_functions_t io_funcs; + io_funcs.read = &cookie_read; + io_funcs.write = 0; + io_funcs.seek = &cookie_seek; + io_funcs.close = 0; + + m_file = fopencookie(reinterpret_cast(this), "r", io_funcs); + } + + // Make an output FILE_Cookie + FILE_Cookie::FILE_Cookie(std::ostream& ostr) + : m_istr(0), m_ostr(&ostr) + { + cookie_io_functions_t io_funcs; + io_funcs.read = 0; + io_funcs.write = &cookie_write; + io_funcs.seek = &cookie_seek; + io_funcs.close = 0; + + m_file = fopencookie(reinterpret_cast(this), "w", io_funcs); + } + + // Make an I/O FILE_Cookie + FILE_Cookie::FILE_Cookie(std::istream& istr, std::ostream& ostr) + : m_istr(&istr), m_ostr(&ostr) + { + cookie_io_functions_t io_funcs; + io_funcs.read = &cookie_read; + io_funcs.write = &cookie_write; + io_funcs.seek = &cookie_seek; + io_funcs.close = 0; + + m_file = fopencookie(reinterpret_cast(this), "r+", io_funcs); + } + + // Close the file + FILE_Cookie::~FILE_Cookie() { std::fclose(m_file); } + + FILE* FILE_Cookie::file() { return m_file; } + const FILE* FILE_Cookie::file() const { return m_file; } + + bool FILE_Cookie::is_input() const { return m_istr; } + bool FILE_Cookie::is_output() const { return m_ostr; } + + std::istream& FILE_Cookie::istr() { return *m_istr; } + const std::istream& FILE_Cookie::istr() const { return *m_istr; } + std::ostream& FILE_Cookie::ostr() { return *m_ostr; } + const std::ostream& FILE_Cookie::ostr() const { return *m_ostr; } +} diff --git a/libdimensionxx/cookie-tmpfile.cpp b/libdimensionxx/cookie-tmpfile.cpp new file mode 100644 index 0000000..ec320d8 --- /dev/null +++ b/libdimensionxx/cookie-tmpfile.cpp @@ -0,0 +1,136 @@ +/************************************************************************* + * Copyright (C) 2008 Tavian Barnes * + * * + * This file is part of The Dimension Library. * + * * + * The Dimension Library is free software; you can redistribute it and/ * + * or modify it under the terms of the GNU Lesser General Public License * + * as published by the Free Software Foundation; either version 3 of the * + * License, or (at your option) any later version. * + * * + * The Dimension Library is distributed in the hope that it will be * + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty * + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this program. If not, see * + * . * + *************************************************************************/ + +#include "dimensionxx.hpp" +#include + +// Use a tmpfile as a buffer for a C++/C I/O interface. + +namespace Dimension +{ + namespace + { + void + write_cookie(FILE* file, std::istream& istr) + { + unsigned int count, pos; + const unsigned int bs = 8192; + char buffer[bs]; + + pos = istr.tellg(); + istr.seekg(0); + while (true) { + istr.read(buffer, bs); + count = istr.gcount(); + + if (count != bs) { + if (istr.eof()) { + fwrite(buffer, 1, count, file); + break; + } else { + throw Dimension_Error("Error reading from input stream."); + } + } + + fwrite(buffer, 1, bs, file); + } + fseek(file, pos, SEEK_SET); + } + + void + read_cookie(std::ostream& ostr, FILE* file) + { + unsigned int count, pos; + const unsigned int bs = 8192; + char buffer[bs]; + + pos = ftell(file); + rewind(file); + while (true) { + count = fread(buffer, 1, bs, file); + if (count != bs) { + if (feof(file)) { + ostr.write(buffer, count); + break; + } else { + throw Dimension_Error("Error reading from temporary file."); + } + } + + ostr.write(buffer, bs); + } + ostr.seekp(pos); + } + } + + // Make an input FILE_Cookie + FILE_Cookie::FILE_Cookie(std::istream& istr) + : m_file(tmpfile()), m_istr(&istr), m_ostr(0) + { + if (!m_file) { + throw Dimension_Error("Error opening temporary file for C++/C I/O" + " interface."); + } + + write_cookie(m_file, *m_istr); + } + + // Make an output FILE_Cookie + FILE_Cookie::FILE_Cookie(std::ostream& ostr) + : m_file(tmpfile()), m_istr(0), m_ostr(&ostr) + { + if (!m_file) { + throw Dimension_Error("Error opening temporary file for C++/C I/O" + " interface."); + } + } + + // Make an I/O FILE_Cookie + FILE_Cookie::FILE_Cookie(std::iostream& iostr) + : m_file(tmpfile()), m_istr(&iostr), m_ostr(&iostr) + { + if (!m_file) { + throw Dimension_Error("Error opening temporary file for C++/C I/O" + " interface."); + } + + write_cookie(m_file, *m_istr); + } + + // Close the tmpfile, maybe syncing a C++ stream to it first + FILE_Cookie::~FILE_Cookie() { + if (is_output()) { + read_cookie(*m_ostr, m_file); + } + + std::fclose(m_file); + } + + FILE* FILE_Cookie::file() { return m_file; } + const FILE* FILE_Cookie::file() const { return m_file; } + + bool FILE_Cookie::is_input() const { return m_istr; } + bool FILE_Cookie::is_output() const { return m_ostr; } + + std::istream& FILE_Cookie::istr() { return *m_istr; } + const std::istream& FILE_Cookie::istr() const { return *m_istr; } + std::ostream& FILE_Cookie::ostr() { return *m_ostr; } + const std::ostream& FILE_Cookie::ostr() const { return *m_ostr; } +} diff --git a/libdimensionxx/cookie.cpp b/libdimensionxx/cookie.cpp deleted file mode 100644 index 962137a..0000000 --- a/libdimensionxx/cookie.cpp +++ /dev/null @@ -1,173 +0,0 @@ -/************************************************************************* - * Copyright (C) 2008 Tavian Barnes * - * * - * This file is part of The Dimension Library. * - * * - * The Dimension Library is free software; you can redistribute it and/ * - * or modify it under the terms of the GNU Lesser General Public License * - * as published by the Free Software Foundation; either version 3 of the * - * License, or (at your option) any later version. * - * * - * The Dimension Library is distributed in the hope that it will be * - * useful, but WITHOUT ANY WARRANTY; without even the implied warranty * - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * - * Lesser General Public License for more details. * - * * - * You should have received a copy of the GNU Lesser General Public * - * License along with this program. If not, see * - * . * - *************************************************************************/ - -#include "dimensionxx.hpp" -#include - -// The conundrum: libdimension and libdimension-* use C I/O, with FILE*'s. -// We want to use C++ I/O with std::i/ostreams. Unfortunately, there's no -// standard way to map between them, so we use the nonportable GNU stdio -// extension fopencookie(), which creates a FILE* with custom read/write/seek -// functions. BSD also has a similar function, funopen(), which we should -// use too. Failing in all that, we should fall back on a tmpfile() buffer. - -namespace Dimension -{ - namespace - { - // Cookie read function - ssize_t - cookie_read(void* cookie, char* buf, size_t size) - { - FILE_Cookie* streams = reinterpret_cast(cookie); - - // Do the unformatted read - streams->istr().read(buf, size); - - if (streams->istr().eof() || streams->istr().good()) { - return streams->istr().gcount(); // This returns 0 on an immediate EOF - // for us. - } else { - // Some non-EOF error - return -1; - } - } - - // Cookie write function - ssize_t - cookie_write(void* cookie, const char* buf, size_t size) - { - FILE_Cookie* streams = reinterpret_cast(cookie); - - // Do the unformatted write - streams->ostr().write(buf, size); - - if (streams->ostr().good()) { - // Write operation succeeded, so we must've written size bytes - return size; - } else { - // Write operation failed - return -1; - } - } - - // Cookie seek function - int - cookie_seek(void* cookie, off64_t* offset, int whence) - { - FILE_Cookie* streams = reinterpret_cast(cookie); - - if (streams->is_input()) { - // If we have an input stream, seek it - switch (whence) { - case SEEK_SET: - streams->istr().seekg(*offset, std::ios::beg); - break; - case SEEK_CUR: - streams->istr().seekg(*offset, std::ios::cur); - break; - case SEEK_END: - streams->istr().seekg(*offset, std::ios::end); - break; - } - - if (!streams->istr().good()) { - // Seek failed - return 1; - } - } - - if (streams->is_output()) { - // If we have an output stream, seek it too - switch (whence) { - case SEEK_SET: - streams->ostr().seekp(*offset, std::ios::beg); - break; - case SEEK_CUR: - streams->ostr().seekp(*offset, std::ios::cur); - break; - case SEEK_END: - streams->ostr().seekp(*offset, std::ios::end); - } - - if (!streams->ostr().good()) { - // Seek failed - return 1; - } - } - - // Seek succeeded - return 0; - } - } - - // Make an input FILE_Cookie - FILE_Cookie::FILE_Cookie(std::istream& istr) - : m_istr(&istr), m_ostr(0) - { - cookie_io_functions_t io_funcs; - io_funcs.read = &cookie_read; - io_funcs.write = 0; - io_funcs.seek = &cookie_seek; - io_funcs.close = 0; - - m_file = fopencookie(reinterpret_cast(this), "r", io_funcs); - } - - // Make an output FILE_Cookie - FILE_Cookie::FILE_Cookie(std::ostream& ostr) - : m_istr(0), m_ostr(&ostr) - { - cookie_io_functions_t io_funcs; - io_funcs.read = 0; - io_funcs.write = &cookie_write; - io_funcs.seek = &cookie_seek; - io_funcs.close = 0; - - m_file = fopencookie(reinterpret_cast(this), "w", io_funcs); - } - - // Make an I/O FILE_Cookie - FILE_Cookie::FILE_Cookie(std::istream& istr, std::ostream& ostr) - : m_istr(&istr), m_ostr(&ostr) - { - cookie_io_functions_t io_funcs; - io_funcs.read = &cookie_read; - io_funcs.write = &cookie_write; - io_funcs.seek = &cookie_seek; - io_funcs.close = 0; - - m_file = fopencookie(reinterpret_cast(this), "r+", io_funcs); - } - - // Close the file - FILE_Cookie::~FILE_Cookie() { std::fclose(m_file); } - - FILE* FILE_Cookie::file() { return m_file; } - const FILE* FILE_Cookie::file() const { return m_file; } - - bool FILE_Cookie::is_input() const { return m_istr; } - bool FILE_Cookie::is_output() const { return m_ostr; } - - std::istream& FILE_Cookie::istr() { return *m_istr; } - const std::istream& FILE_Cookie::istr() const { return *m_istr; } - std::ostream& FILE_Cookie::ostr() { return *m_ostr; } - const std::ostream& FILE_Cookie::ostr() const { return *m_ostr; } -} diff --git a/libdimensionxx/dimensionxx/cookie.hpp b/libdimensionxx/dimensionxx/cookie.hpp index a2a3324..df26b93 100644 --- a/libdimensionxx/dimensionxx/cookie.hpp +++ b/libdimensionxx/dimensionxx/cookie.hpp @@ -21,8 +21,7 @@ #ifndef DIMENSIONXX_COOKIE_HPP #define DIMENSIONXX_COOKIE_HPP -// Some internal magic to use C FILE* I/O with C++ streams. Currently this ties -// us to Linux and glibc, but in the future, this will be portable. +// Some internal magic to use C FILE* I/O with C++ streams. #include #include @@ -36,7 +35,7 @@ namespace Dimension public: FILE_Cookie(std::istream& istr); FILE_Cookie(std::ostream& ostr); - FILE_Cookie(std::istream& istr, std::ostream& ostr); + FILE_Cookie(std::iostream& iostr); ~FILE_Cookie(); FILE* file(); diff --git a/libdimensionxx/png.cpp b/libdimensionxx/png.cpp index 8a2770b..2d1d276 100644 --- a/libdimensionxx/png.cpp +++ b/libdimensionxx/png.cpp @@ -67,7 +67,7 @@ namespace Dimension m_written = true; // We've written the file now, don't do it again } - // Read a canvas from a PNG file. Uses the FILE_Cookie() interface to make a + // Read a canvas from a PNG file. Uses the FILE_Cookie() interface to make a // FILE* corresponding to an std::istream (including std::istringstream, etc). void PNG_Canvas::read() { -- cgit v1.2.3