/* A byte source/sink .. it can be a pipe, file descriptor, memory area, * socket, node.js stream, etc. * * J.Cupitt, 19/6/14 * * 26/11/20 * - use _setmode() on win to force binary write for previously opened * descriptors */ /* This file is part of VIPS. VIPS 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 2 of the License, or (at your option) any later version. This program 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /* These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk */ /* #define VIPS_DEBUG */ #ifdef HAVE_CONFIG_H #include #endif /*HAVE_CONFIG_H*/ #include #include #include #ifdef HAVE_UNISTD_H #include #endif /*HAVE_UNISTD_H*/ #include #include #include #include #include #include #ifdef G_OS_WIN32 #include #endif /*G_OS_WIN32*/ #include #include /* libtiff needs to be able to seek and read back output files, unfortunately, * so we must open read-write. */ #define MODE_READWRITE CLOEXEC (BINARYIZE (O_RDWR | O_CREAT | O_TRUNC)) G_DEFINE_TYPE( VipsTarget, vips_target, VIPS_TYPE_CONNECTION ); static void vips_target_finalize( GObject *gobject ) { VipsTarget *target = VIPS_TARGET( gobject ); VIPS_DEBUG_MSG( "vips_target_finalize:\n" ); if( target->memory_buffer ) { g_string_free( target->memory_buffer, TRUE ); target->memory_buffer = NULL; } if( target->blob ) { vips_area_unref( VIPS_AREA( target->blob ) ); target->blob = NULL; } if( target->delete_on_close && target->delete_on_close_filename ) g_unlink( target->delete_on_close_filename ); VIPS_FREE( target->delete_on_close_filename ); G_OBJECT_CLASS( vips_target_parent_class )->finalize( gobject ); } static int vips_target_build( VipsObject *object ) { VipsConnection *connection = VIPS_CONNECTION( object ); VipsTarget *target = VIPS_TARGET( object ); VIPS_DEBUG_MSG( "vips_target_build: %p\n", connection ); if( VIPS_OBJECT_CLASS( vips_target_parent_class )->build( object ) ) return( -1 ); if( vips_object_argument_isset( object, "filename" ) && vips_object_argument_isset( object, "descriptor" ) ) { vips_error( vips_connection_nick( connection ), "%s", _( "don't set 'filename' and 'descriptor'" ) ); return( -1 ); } if( connection->filename ) { const char *filename = connection->filename; int fd; /* 0644 is rw user, r group and other. */ if( (fd = vips_tracked_open( filename, MODE_READWRITE, 0644 )) == -1 ) { vips_error_system( errno, vips_connection_nick( connection ), "%s", _( "unable to open for write" ) ); return( -1 ); } connection->tracked_descriptor = fd; connection->descriptor = fd; } else if( vips_object_argument_isset( object, "descriptor" ) ) { connection->descriptor = dup( connection->descriptor ); connection->close_descriptor = connection->descriptor; #ifdef G_OS_WIN32 /* Windows will create eg. stdin and stdout in text mode. * We always write in binary mode. */ _setmode( connection->descriptor, _O_BINARY ); #endif /*G_OS_WIN32*/ } else if( target->memory ) target->memory_buffer = g_string_sized_new( VIPS_TARGET_BUFFER_SIZE ); return( 0 ); } static gint64 vips_target_write_real( VipsTarget *target, const void *data, size_t length ) { VipsConnection *connection = VIPS_CONNECTION( target ); gint64 result; VIPS_DEBUG_MSG( "vips_target_write_real: %zd bytes\n", length ); if( target->memory_buffer ) { VIPS_DEBUG_MSG( "vips_target_write_real: to position %zd\n", target->position ); g_string_overwrite_len( target->memory_buffer, target->position, data, length ); target->position += length; result = length; } else result = write( connection->descriptor, data, length ); return( result ); } static off_t vips_target_seek_real( VipsTarget *target, off_t offset, int whence ) { VipsConnection *connection = VIPS_CONNECTION( target ); const char *nick = vips_connection_nick( connection ); off_t new_position; VIPS_DEBUG_MSG( "vips_target_seek_real: offset = %ld, whence = %d\n", offset, whence ); if( target->memory_buffer ) { switch( whence ) { case SEEK_SET: new_position = offset; break; case SEEK_CUR: new_position = target->position + offset; break; case SEEK_END: new_position = target->memory_buffer->len + offset; break; default: vips_error( nick, "%s", _( "bad 'whence'" ) ); return( -1 ); } if( new_position > target->memory_buffer->len ) g_string_set_size( target->memory_buffer, new_position ); target->position = new_position; } else new_position = lseek( connection->descriptor, offset, whence ); return( new_position ); } static gint64 vips_target_read_real( VipsTarget *target, void *data, size_t length ) { gint64 bytes_read; VIPS_DEBUG_MSG( "vips_target_read_real: %zd bytes\n", length ); if( target->memory_buffer ) { bytes_read = VIPS_MIN( length, target->memory_buffer->len - target->position ); VIPS_DEBUG_MSG( " %zd bytes from memory\n", bytes_read ); memcpy( data, target->memory_buffer->str + target->position, bytes_read ); target->position += bytes_read; } else { VipsConnection *connection = VIPS_CONNECTION( target ); int fd = connection->descriptor; do { bytes_read = read( fd, data, length ); } while( bytes_read < 0 && errno == EINTR ); } VIPS_DEBUG_MSG( " read %zd bytes\n", bytes_read ); return( bytes_read ); } static int vips_target_end_real( VipsTarget *target ) { VIPS_DEBUG_MSG( "vips_target_finish_real:\n" ); return( 0 ); } static void vips_target_finish_real( VipsTarget *target ) { VIPS_DEBUG_MSG( "vips_target_finish_real:\n" ); } static void vips_target_class_init( VipsTargetClass *class ) { GObjectClass *gobject_class = G_OBJECT_CLASS( class ); VipsObjectClass *object_class = VIPS_OBJECT_CLASS( class ); gobject_class->finalize = vips_target_finalize; gobject_class->set_property = vips_object_set_property; gobject_class->get_property = vips_object_get_property; object_class->nickname = "target"; object_class->description = _( "Target" ); object_class->build = vips_target_build; class->write = vips_target_write_real; class->read = vips_target_read_real; class->seek = vips_target_seek_real; class->end = vips_target_end_real; class->finish = vips_target_finish_real; VIPS_ARG_BOOL( class, "memory", 3, _( "Memory" ), _( "File descriptor should output to memory" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsTarget, memory ), FALSE ); /* SET_ALWAYS means that blob is set by C and the obj system is not * involved in creation or destruction. It can be read at any time. */ VIPS_ARG_BOXED( class, "blob", 4, _( "Blob" ), _( "Blob to save to" ), VIPS_ARGUMENT_SET_ALWAYS, G_STRUCT_OFFSET( VipsTarget, blob ), VIPS_TYPE_BLOB ); } static void vips_target_init( VipsTarget *target ) { target->blob = vips_blob_new( NULL, NULL, 0 ); target->write_point = 0; } /** * vips_target_new_to_descriptor: * @descriptor: write to this file descriptor * * Create a target attached to a file descriptor. * @descriptor is kept open until the target is finalized. * * See also: vips_target_new_to_file(). * * Returns: a new target. */ VipsTarget * vips_target_new_to_descriptor( int descriptor ) { VipsTarget *target; VIPS_DEBUG_MSG( "vips_target_new_to_descriptor: %d\n", descriptor ); target = VIPS_TARGET( g_object_new( VIPS_TYPE_TARGET, "descriptor", descriptor, NULL ) ); if( vips_object_build( VIPS_OBJECT( target ) ) ) { VIPS_UNREF( target ); return( NULL ); } return( target ); } /** * vips_target_new_to_file: * @filename: write to this file * * Create a target attached to a file. * * Returns: a new target. */ VipsTarget * vips_target_new_to_file( const char *filename ) { VipsTarget *target; VIPS_DEBUG_MSG( "vips_target_new_to_file: %s\n", filename ); target = VIPS_TARGET( g_object_new( VIPS_TYPE_TARGET, "filename", filename, NULL ) ); if( vips_object_build( VIPS_OBJECT( target ) ) ) { VIPS_UNREF( target ); return( NULL ); } return( target ); } /** * vips_target_new_to_memory: * * Create a target which will write to a memory area. Read from @blob to get * memory. * * See also: vips_target_new_to_file(). * * Returns: a new #VipsConnection */ VipsTarget * vips_target_new_to_memory( void ) { VipsTarget *target; VIPS_DEBUG_MSG( "vips_target_new_to_memory:\n" ); target = VIPS_TARGET( g_object_new( VIPS_TYPE_TARGET, "memory", TRUE, NULL ) ); if( vips_object_build( VIPS_OBJECT( target ) ) ) { VIPS_UNREF( target ); return( NULL ); } return( target ); } /** * vips_target_new_temp: * @based_on: base the temporary target on this target * * Create a temporary target -- either a temporary file on disc, or an area in * memory, depending on what sort of target @based_on is. * * See also: vips_target_new_to_file(). * * Returns: a new target. */ VipsTarget * vips_target_new_temp( VipsTarget *based_on ) { VipsTarget *target; VIPS_DEBUG_MSG( "vips_target_new_temp: %p\n", based_on ); if( vips_connection_filename( VIPS_CONNECTION( based_on ) ) ) { int descriptor; char *filename; if( !(filename = vips__temp_name( "%s.target" )) ) return( NULL ); if( (descriptor = vips__open_image_write( filename, TRUE )) < 0 ) { g_free( filename ); return( NULL ); } if( !(target = vips_target_new_to_descriptor( descriptor )) ) { g_free( filename ); vips_tracked_close( descriptor ); return( NULL ); } vips_tracked_close( descriptor ); target->delete_on_close = TRUE; target->delete_on_close_filename = filename; } else target = vips_target_new_to_memory(); return( target ); } static int vips_target_write_unbuffered( VipsTarget *target, const void *data, size_t length ) { VipsTargetClass *class = VIPS_TARGET_GET_CLASS( target ); VIPS_DEBUG_MSG( "vips_target_write_unbuffered:\n" ); if( target->ended ) return( 0 ); while( length > 0 ) { gint64 bytes_written; bytes_written = class->write( target, data, length ); /* n == 0 isn't strictly an error, but we treat it as * one to make sure we don't get stuck in this loop. */ if( bytes_written <= 0 ) { vips_error_system( errno, vips_connection_nick( VIPS_CONNECTION( target ) ), "%s", _( "write error" ) ); return( -1 ); } length -= bytes_written; data += bytes_written; } return( 0 ); } static int vips_target_flush( VipsTarget *target ) { g_assert( target->write_point >= 0 ); g_assert( target->write_point <= VIPS_TARGET_BUFFER_SIZE ); VIPS_DEBUG_MSG( "vips_target_flush:\n" ); if( target->write_point > 0 ) { if( vips_target_write_unbuffered( target, target->output_buffer, target->write_point ) ) return( -1 ); target->write_point = 0; } return( 0 ); } /** * vips_target_write: * @target: target to operate on * @buffer: bytes to write * @length: length of @buffer in bytes * * Write @length bytes from @buffer to the output. * * Returns: 0 on success, -1 on error. */ int vips_target_write( VipsTarget *target, const void *buffer, size_t length ) { VIPS_DEBUG_MSG( "vips_target_write: %zd bytes\n", length ); if( length > VIPS_TARGET_BUFFER_SIZE - target->write_point && vips_target_flush( target ) ) return( -1 ); if( length > VIPS_TARGET_BUFFER_SIZE - target->write_point ) { /* Still too large? Do an unbuffered write. */ if( vips_target_write_unbuffered( target, buffer, length ) ) return( -1 ); } else { memcpy( target->output_buffer + target->write_point, buffer, length ); target->write_point += length; } return( 0 ); } /** * vips_target_read: * @target: target to operate on * @buffer: store bytes here * @length: length of @buffer in bytes * * Read up to @length bytes from @target and store the bytes in @buffer. * Return the number of bytes actually read. If all bytes have been read from * the file, return 0. * * Arguments exactly as read(2). * * Reading froma target sounds weird, but libtiff needs this for * multi-page writes. This method will fail for targets like pipes. * * Returns: the number of bytes read, 0 on end of file, -1 on error. */ gint64 vips_target_read( VipsTarget *target, void *buffer, size_t length ) { VipsTargetClass *class = VIPS_TARGET_GET_CLASS( target ); VIPS_DEBUG_MSG( "vips_target_read: %zd bytes\n", length ); if( vips_target_flush( target ) ) return( -1 ); return( class->read( target, buffer, length ) ); } /** * vips_target_seek: * @target: target to operate on * @position: position to seek to * @whence: seek relative to beginning, offset, or end * * Seek the target. This behaves exactly as lseek(2). * * Seeking a target sounds weird, but libtiff needs this. This method will * fail for targets like pipes. * * Returns: the new seek position, -1 on error. */ off_t vips_target_seek( VipsTarget *target, off_t position, int whence ) { VipsTargetClass *class = VIPS_TARGET_GET_CLASS( target ); off_t new_position; VIPS_DEBUG_MSG( "vips_target_seek: pos = %ld, whence = %d\n", position, whence ); if( vips_target_flush( target ) ) return( -1 ); new_position = class->seek( target, position, whence ); VIPS_DEBUG_MSG( "vips_target_seek: new_position = %ld\n", new_position ); return( new_position ); } /** * vips_target_end: * @target: target to operate on * @buffer: bytes to write * @length: length of @buffer in bytes * * Call this at the end of write to make the target do any cleaning up. You * can call it many times. * * After a target has been finished, further writes will do nothing. * * Returns: 0 on success, -1 on error. */ int vips_target_end( VipsTarget *target ) { VipsTargetClass *class = VIPS_TARGET_GET_CLASS( target ); VIPS_DEBUG_MSG( "vips_target_end:\n" ); if( target->ended ) return( 0 ); if( vips_target_flush( target ) ) return( -1 ); /* Move the target buffer into the blob so it can be read out. */ if( target->memory_buffer ) { const char *data; size_t length; length = target->memory_buffer->len; data = g_string_free( target->memory_buffer, FALSE ); target->memory_buffer = NULL; vips_blob_set( target->blob, (VipsCallbackFn) vips_area_free_cb, data, length ); } else { if( class->end( target ) ) return( -1 ); } target->ended = TRUE; return( 0 ); } /** * vips_target_finish: * @target: target to operate on * @buffer: bytes to write * @length: length of @buffer in bytes * * Deprecated in favour of vips_target_end(). */ void vips_target_finish( VipsTarget *target ) { (void) vips_target_end( target ); } /** * vips_target_steal: * @target: target to operate on * @length: return number of bytes of data * * Memory targets only (see vips_target_new_to_memory()). Steal all data * written to the target so far, and finish it. * * You must free the returned pointer with g_free(). * * The data is NOT automatically null-terminated. vips_target_putc() a '\0' * before calling this to get a null-terminated string. * * Returns: (array length=length) (element-type guint8) (transfer full): the * data */ unsigned char * vips_target_steal( VipsTarget *target, size_t *length ) { const char *data; (void) vips_target_flush( target ); if( !target->memory_buffer || target->ended ) { if( length ) *length = target->memory_buffer->len; return( NULL ); } if( length ) *length = target->memory_buffer->len; data = g_string_free( target->memory_buffer, FALSE ); target->memory_buffer = NULL; /* We must have a valid byte array or end will fail. */ target->memory_buffer = g_string_sized_new( 0 ); if( vips_target_end( target ) ) return( NULL ); return( (unsigned char *) data ); } /** * vips_target_steal_text: * @target: target to operate on * * As vips_target_steal_text(), but return a null-terminated string. * * Returns: (transfer full): target contents as a null-terminated string. */ char * vips_target_steal_text( VipsTarget *target ) { vips_target_putc( target, '\0' ); return( (char *) vips_target_steal( target, NULL ) ); } /** * vips_target_putc: * @target: target to operate on * @ch: character to write * * Write a single character @ch to @target. See the macro VIPS_TARGET_PUTC() * for a faster way to do this. * * Returns: 0 on success, -1 on error. */ int vips_target_putc( VipsTarget *target, int ch ) { VIPS_DEBUG_MSG( "vips_target_putc: %d\n", ch ); if( target->write_point >= VIPS_TARGET_BUFFER_SIZE && vips_target_flush( target ) ) return( -1 ); target->output_buffer[target->write_point++] = ch; return( 0 ); } /** * vips_target_writes: * @target: target to operate on * @str: string to write * * Write a null-terminated string to @target. * * Returns: 0 on success, and -1 on error. */ int vips_target_writes( VipsTarget *target, const char *str ) { return( vips_target_write( target, (unsigned char *) str, strlen( str ) ) ); } /** * vips_target_writef: * @target: target to operate on * @fmt: printf()-style format string * @...: arguments to format string * * Format the string and write to @target. * * Returns: 0 on success, and -1 on error. */ int vips_target_writef( VipsTarget *target, const char *fmt, ... ) { va_list ap; char *line; int result; va_start( ap, fmt ); line = g_strdup_vprintf( fmt, ap ); va_end( ap ); result = vips_target_writes( target, line ); g_free( line ); return( result ); } /** * vips_target_write_amp: * @target: target to operate on * @str: string to write * * Write @str to @target, but escape stuff that xml hates in text. Our * argument string is utf-8. * * XML rules: * * - We must escape &<> * - Don't escape \n, \t, \r * - Do escape the other ASCII codes. * * Returns: 0 on success, -1 on error. */ int vips_target_write_amp( VipsTarget *target, const char *str ) { const char *p; for( p = str; *p; p++ ) if( *p < 32 && *p != '\n' && *p != '\t' && *p != '\r' ) { /* You'd think we could output "%x;", but xml * 1.0 parsers barf on that. xml 1.1 allows this, but * there are almost no parsers. * * U+2400 onwards are unicode glyphs for the ASCII * control characters, so we can use them -- thanks * electroly. */ if( vips_target_writef( target, "&#x%04x;", 0x2400 + *p ) ) return( -1 ); } else if( *p == '<' ) { if( vips_target_writes( target, "<" ) ) return( -1 ); } else if( *p == '>' ) { if( vips_target_writes( target, ">" ) ) return( -1 ); } else if( *p == '&' ) { if( vips_target_writes( target, "&" ) ) return( -1 ); } else { if( VIPS_TARGET_PUTC( target, *p ) ) return( -1 ); } return( 0 ); }