Skip to content

Commit

Permalink
add some GLIBC compatibility, cant fix darwin though. it's too broken
Browse files Browse the repository at this point in the history
  • Loading branch information
mulle-nat committed Nov 25, 2024
1 parent 59d897a commit e0c82b6
Show file tree
Hide file tree
Showing 27 changed files with 756 additions and 186 deletions.
272 changes: 183 additions & 89 deletions src/mulle-buffer-stdio.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,135 @@
#include <stdio.h>


//
// r : USEFUL: readonly.
// w : USEFUL: writeonly
// a : writeonly, seek to first null byte
// r+ : USEFUL: read and write
// w+ : read and write, The buffer contents are truncated (i.e., '\0' is placed in the first byte of the buffer).
// a+ : read and write, seek to first null byte
//

// darwin:
// The fmemopen() function associates the buffer given by the buf and size
// arguments with a stream. The buf argument is either a null pointer or a
// pointer to a buffer that is at least size bytes long. If a null pointer
// is specified as the buf argument, fmemopen() allocates size bytes of
// memory, and this allocation is automatically freed when the stream is
// closed. If a non-null pointer is specified, the caller retains ownership
// of the buffer and is responsible for disposing of it after the stream has
// been closed. Buffers can be opened in text-mode (default) or binary-mode
// (if “b” is present in the second or third position of the mode argument).
// Buffers opened in text-mode make sure that writes are terminated with a
// NULL byte, if the last write hasn't filled up the whole buffer. Buffers
// opened in binary-mode never append a NULL byte.

void *mulle_buffer_fmemopen( void *bytes, size_t length, const char *mode)
{
struct mulle_buffer *buffer;
int append;
int truncate;
unsigned int bits;

if( ! mode)
{
errno = EINVAL;
return( NULL);
}

// darwin is just stupid broken, but it makes no sense to support it
// for test conformance, since its a bug
// #ifdef __APPLE__
// if( ! bytes || ! length)
// {
// errno = EINVAL;
// return( NULL);
// }
// #endif

bits = MULLE_BUFFER_IS_TEXT;
append = 0;
truncate = 0;

switch( *mode++)
{
case 'r' : bits |= MULLE_BUFFER_IS_READONLY;
break;
case 'a' : append = 1; // fall thru
case 'w' : bits |= MULLE_BUFFER_IS_WRITEONLY;
break;
default : goto error_einval;
}

for(;;)
{
switch( *mode++)
{
case 'b' : bits &= ~MULLE_BUFFER_IS_TEXT;
continue;
case '+' : bits &= ~MULLE_BUFFER_IS_READONLY|MULLE_BUFFER_IS_WRITEONLY;
truncate = 1;
continue;
case '\0' : goto done_parse;
default : goto error_einval;
}
}

done_parse:
buffer = mulle_buffer_alloc( NULL);

// The fmemopen() function associates the buffer given by the buf and size
// arguments with a stream. The buf argument is either a null pointer or a
// pointer to a buffer that is at least size bytes long.
// If a null pointer is specified as the buf argument, fmemopen() allocates
// size bytes of memory, and this allocation is automatically freed when
// the stream is closed. If a non-null pointer is specified, the caller
// retains ownership of the buffer and is responsible for disposing of it
// after the stream has been closed.
//
if( bytes)
mulle_buffer_init_with_static_bytes( buffer,
bytes,
length,
NULL);
else
mulle_buffer_init( buffer, length, NULL);

buffer->_type |= bits;
buffer->_type |= MULLE_BUFFER_IS_INFLEXIBLE;

//
// fmemopen has retarded semantics IMO
//
// In append mode, if no null byte is found within the buffer, then the
// initial position is length+1. (I don't think we can do this, we can just
// do length)
//
if( append)
{
while( buffer->_curr < buffer->_sentinel)
{
if( *buffer->_curr == '\0')
break;
++buffer->_curr;
}
}
else
if( truncate && (bits != MULLE_BUFFER_IS_READONLY))
{
if( ! mulle_buffer_is_full( buffer))
*buffer->_curr = '\0';
}

return( buffer);

error_einval:
errno = EINVAL;
return( NULL);
}



//
// STDIO callback mimicry
//
Expand Down Expand Up @@ -66,6 +195,13 @@ int mulle_buffer_fseek( void *buffer, long seek, int mode)
return( -1);
}

// compatibility ...
if( mode == SEEK_END && (((struct mulle__buffer *) buffer)->_type & MULLE_BUFFER_IS_WRITEONLY))
{
errno = ENOSPC;
return( -1);
}

rval = _mulle__buffer_set_seek( (struct mulle__buffer *) buffer, seek, mode);
if( rval)
errno = EINVAL;
Expand Down Expand Up @@ -101,6 +237,7 @@ int mulle_buffer_fputc( int c, void *buffer)
{
if( ! buffer)
return( EOF);

if( mulle_buffer_is_readonly( buffer))
{
errno = EBADF;
Expand All @@ -110,22 +247,42 @@ int mulle_buffer_fputc( int c, void *buffer)
_mulle__buffer_add_byte( (struct mulle__buffer *) buffer,
(unsigned char) c,
((struct mulle_buffer *) buffer)->_allocator);
return( (unsigned char) c);

#ifdef __GLIBC__
// stupid code for fmemopen compatibility (as observed on linux)
if( mulle_buffer_is_inflexible( buffer))
return( (unsigned char) c);
#endif

return( _mulle__buffer_has_overflown( buffer) ? EOF : (unsigned char) c);
}


int mulle_buffer_fputs( const char *s, void *buffer)
{
size_t before;

if( ! buffer)
return( 0);

if( mulle_buffer_is_readonly( buffer))
{
errno = EBADF;
return( EOF);
}

before = mulle_buffer_get_length( buffer);
mulle_buffer_add_c_string( buffer, (char *) s);
return( 0);

#ifdef __GLIBC__
// stupid code for fmemopen compatibility (as observed on linux)
if( mulle_buffer_is_inflexible( buffer))
return( (int) (mulle_buffer_get_length( buffer) - before));
#endif

return( _mulle__buffer_has_overflown( buffer)
? EOF
: (int) (mulle_buffer_get_length( buffer) - before));
}


Expand All @@ -137,6 +294,14 @@ int mulle_buffer_fflush( void *buffer)
return( EOF);
}

// When a stream that has been opened for writing is flushed
// a null byte is written at the end of
// the buffer if there is space. The caller should ensure that an extra
// byte is available in the buffer (and that size counts that byte) to allow
// for this.
if( ((struct mulle__buffer *) buffer)->_type & MULLE_BUFFER_IS_TEXT)
_mulle__buffer_zero_last_byte_no_truncate( (struct mulle__buffer *) buffer);

if( _mulle__buffer_is_flushable( (struct mulle__buffer *) buffer))
_mulle__buffer_flush( (struct mulle__buffer *) buffer);
return( 0);
Expand All @@ -150,7 +315,7 @@ size_t mulle_buffer_fwrite( void *src, size_t size, size_t nmemb, void *buffe
void *dst;

if( ! buffer)
goto zero;
return( EOF); // seems the compatible way (no errno) dont ask

if( mulle_buffer_is_readonly( buffer))
{
Expand All @@ -164,9 +329,21 @@ size_t mulle_buffer_fwrite( void *src, size_t size, size_t nmemb, void *buffe
bytes_to_write = size * nmemb;
dst = mulle_buffer_advance( buffer, bytes_to_write);

// ain't doing partial writes
// ain't doing partial writes, except if inflexible, then it gets weird
// for compatibility

if( ! dst)
{
#ifdef __GLIBC__
if( mulle_buffer_is_inflexible( buffer))
{
size_t remaining;

remaining = mulle_buffer_remaining_length( buffer);
mulle_buffer_add_bytes( buffer, src, remaining);
return( nmemb); // (sic!) turbo retarded
}
#endif
errno = ENOSPC;
return( 0);
}
Expand All @@ -180,99 +357,16 @@ size_t mulle_buffer_fwrite( void *src, size_t size, size_t nmemb, void *buffe
}


//
// r : USEFUL: readonly.
// w : USEFUL: writeonly
// a : writeonly, seek to first null byte
// r+ : USEFUL: read and write
// w+ : read and write, The buffer contents are truncated (i.e., '\0' is placed in the first byte of the buffer).
// a+ : read and write, seek to first null byte
//
void *mulle_buffer_fmemopen( void *bytes, size_t length, const char *mode)
{
struct mulle_buffer *buffer;
int append;
int truncate;
unsigned int bits;

if( ! mode)
{
errno = EINVAL;
return( NULL);
}

bits = 0;
append = 0;
truncate = 0;

switch( *mode++)
{
case 'r' : if( ! bytes)
goto error_einval;
bits = MULLE_BUFFER_IS_READONLY;
break;
case 'a' : append = 1; // fall thru
case 'w' : bits = MULLE_BUFFER_IS_WRITEONLY;
break;
default : goto error_einval;
}

switch( *mode++)
{
case '+' : bits = 0;
truncate = 1;
case '\0' : break;
default : goto error_einval;
}

buffer = mulle_buffer_create( NULL);

if( bytes)
mulle_buffer_init_with_static_bytes( buffer,
bytes,
length,
NULL);
buffer->_type |= bits;

//
// fmemopen has retarded semantics IMO
//
// In append mode, if no null byte is found within the buffer, then the
// initial position is length+1. (I don't think we can do this, we can just
// do length)
//
if( append)
{
while( buffer->_curr < buffer->_sentinel)
{
if( *buffer->_curr == '\0')
break;
++buffer->_curr;
}
}
else
if( truncate && (bits != MULLE_BUFFER_IS_READONLY))
{
if( ! mulle_buffer_is_full( buffer))
*buffer->_curr = '\0';
}

return( buffer);

error_einval:
errno = EINVAL;
return( NULL);
}


int mulle_buffer_fclose( void *buffer)
{
mulle_buffer_fflush( buffer);
mulle_buffer_destroy( buffer);
return( 0);
}


off_t mulle_buffer_lseek( void *buffer, off_t offset, int mode)
off_t mulle_buffer_lseek( void *buffer, off_t offset, int mode)
{
int rval;
long seek;
Expand Down
4 changes: 4 additions & 0 deletions src/mulle-buffer-stdio.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@
* r+ Open the stream for reading and writing.
* w+ Open the stream for reading and writing. The buffer contents are truncated (i.e., '\0' is placed in the first byte of the buffer).
* a+ Append; open the stream for reading and writing, with the initial buffer position set to the first null byte.
*
* Unfortunately in a cross-platform scenario (at least darwin and linux,
* the fmemopen interface is super flakey and unpredictable when it comes
* to seeking and writing in various modes).
*/
MULLE__FPRINTF_GLOBAL
void *mulle_buffer_fmemopen( void *buf, size_t size, const char *mode);
Expand Down
18 changes: 18 additions & 0 deletions test/20_mimicry/broken-darwin.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#include <stdio.h>


int main( void)
{
FILE *fp;

fp = fmemopen( NULL, 100, "w");
if( ! fp)
{
perror( "fmemopen:");
return( 1);
}
fclose( fp);
return( 0);
}


1 change: 1 addition & 0 deletions test/20_mimicry/broken-darwin.errors.darwin
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fmemopen:
Loading

0 comments on commit e0c82b6

Please sign in to comment.