606 lines
14 KiB
C
606 lines
14 KiB
C
/* 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 <config.h>
|
|
#endif /*HAVE_CONFIG_H*/
|
|
#include <glib/gi18n-lib.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif /*HAVE_UNISTD_H*/
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
|
|
#include <vips/vips.h>
|
|
|
|
#ifdef G_OS_WIN32
|
|
#include <io.h>
|
|
#endif /*G_OS_WIN32*/
|
|
|
|
#include <vips/debug.h>
|
|
#include <vips/internal.h>
|
|
|
|
#define MODE_WRITE CLOEXEC (BINARYIZE (O_WRONLY | 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" );
|
|
|
|
VIPS_FREEF( g_byte_array_unref, target->memory_buffer );
|
|
if( target->blob ) {
|
|
vips_area_unref( VIPS_AREA( target->blob ) );
|
|
target->blob = NULL;
|
|
}
|
|
|
|
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_WRITE, 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_byte_array_new();
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
static gint64
|
|
vips_target_write_real( VipsTarget *target, const void *data, size_t length )
|
|
{
|
|
VipsConnection *connection = VIPS_CONNECTION( target );
|
|
|
|
VIPS_DEBUG_MSG( "vips_target_write_real: %zd bytes\n", length );
|
|
|
|
return( write( connection->descriptor, data, length ) );
|
|
}
|
|
|
|
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->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 );
|
|
}
|
|
|
|
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->finished )
|
|
return( 0 );
|
|
|
|
if( target->memory_buffer )
|
|
g_byte_array_append( target->memory_buffer, data, length );
|
|
else
|
|
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_finish:
|
|
* @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.
|
|
*/
|
|
void
|
|
vips_target_finish( VipsTarget *target )
|
|
{
|
|
VipsTargetClass *class = VIPS_TARGET_GET_CLASS( target );
|
|
|
|
VIPS_DEBUG_MSG( "vips_target_finish:\n" );
|
|
|
|
if( target->finished )
|
|
return;
|
|
|
|
(void) vips_target_flush( target );
|
|
|
|
/* Move the target buffer into the blob so it can be read out.
|
|
*/
|
|
if( target->memory_buffer ) {
|
|
unsigned char *data;
|
|
size_t length;
|
|
|
|
length = target->memory_buffer->len;
|
|
data = g_byte_array_free( target->memory_buffer, FALSE );
|
|
target->memory_buffer = NULL;
|
|
vips_blob_set( target->blob,
|
|
(VipsCallbackFn) vips_area_free_cb, data, length );
|
|
}
|
|
else
|
|
class->finish( target );
|
|
|
|
target->finished = TRUE;
|
|
}
|
|
|
|
/**
|
|
* 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 )
|
|
{
|
|
unsigned char *data;
|
|
|
|
(void) vips_target_flush( target );
|
|
|
|
if( !target->memory_buffer ||
|
|
target->finished ) {
|
|
if( length )
|
|
*length = target->memory_buffer->len;
|
|
|
|
return( NULL );
|
|
}
|
|
|
|
if( length )
|
|
*length = target->memory_buffer->len;
|
|
data = g_byte_array_free( target->memory_buffer, FALSE );
|
|
target->memory_buffer = NULL;
|
|
|
|
/* We must have a valid byte array or finish will fail.
|
|
*/
|
|
target->memory_buffer = g_byte_array_new();
|
|
|
|
vips_target_finish( target );
|
|
|
|
return( 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: <function>printf()</function>-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 );
|
|
}
|
|
|