/*************************************************************** MPU-STRING.C This file contains source code of functions for UCS-2 string stream operations. PART OF : MPUIO - library . USAGE : Internal only . NOTE : String streams contain UCS-2 characters directly. No UTF-8 conversion is performed. Copyright (C) 2000 - 2026 by Andrey V.Kosteltsev. All Rights Reserved. ***************************************************************/ #ifdef HAVE_CONFIG_H #include #endif #include #include #include /*************************************************************** Publish a NUL-terminated memory-stream buffer and logical size. For fixed caller-owned streams this only maintains the trailing NUL; dynamic streams also update the user pointer and length. ***************************************************************/ static int __mpu_IO_str_publish_unlocked( mpu_FILE *stream ) { __mpu_IO_strfile *sf; sf = (__mpu_IO_strfile *)stream->_cookie; if( sf == (__mpu_IO_strfile *)0 ) { errno = EBADF; stream->_flags |= __MPU_IO_ERR_SEEN; return( mpu_EOF ); } if( sf->nul_terminated && sf->buffer != (__mpu_char16_t *)0 && sf->capacity != 0 ) { size_t end = sf->length; if( end >= sf->capacity ) end = sf->capacity - 1; sf->buffer[end] = (__mpu_char16_t)0; } if( sf->publish_buffer != (__mpu_char16_t **)0 ) *sf->publish_buffer = sf->buffer; if( sf->publish_size != (size_t *)0 ) *sf->publish_size = sf->length; return( 0 ); } /* End of __mpu_IO_str_publish_unlocked() */ /*************************************************************** Grow a dynamic UCS-2 memory stream to hold NEED code units. ***************************************************************/ static int __mpu_IO_str_grow_unlocked( mpu_FILE *stream, size_t need ) { __mpu_IO_strfile *sf; size_t capacity; __mpu_char16_t *buffer; sf = (__mpu_IO_strfile *)stream->_cookie; if( sf == (__mpu_IO_strfile *)0 || !sf->dynamic ) { errno = ENOSPC; stream->_flags |= __MPU_IO_ERR_SEEN; return( mpu_EOF ); } capacity = sf->capacity ? sf->capacity : 16; while( capacity < need ) { if( capacity > SIZE_MAX / 2 ) { capacity = need; break; } capacity *= 2; } if( capacity > SIZE_MAX / sizeof(__mpu_char16_t) ) { errno = EOVERFLOW; stream->_flags |= __MPU_IO_ERR_SEEN; return( mpu_EOF ); } buffer = (__mpu_char16_t *)realloc( sf->buffer, capacity * sizeof(__mpu_char16_t) ); if( buffer == (__mpu_char16_t *)0 ) { stream->_flags |= __MPU_IO_ERR_SEEN; return( mpu_EOF ); } sf->buffer = buffer; sf->capacity = capacity; return( 0 ); } /* End of __mpu_IO_str_grow_unlocked() */ /*************************************************************** Reject raw-byte underflow on a UCS-2 string stream. ***************************************************************/ static int __mpu_IO_str_underflow_unlocked( mpu_FILE *stream ) { stream->_flags |= __MPU_IO_ERR_SEEN; errno = EINVAL; return( mpu_EOF ); } /* End of __mpu_IO_str_underflow_unlocked() */ /*************************************************************** Reject raw-byte overflow on a UCS-2 string stream. ***************************************************************/ static int __mpu_IO_str_overflow_unlocked( mpu_FILE *stream, int c ) { (void)c; stream->_flags |= __MPU_IO_ERR_SEEN; errno = EINVAL; return( mpu_EOF ); } /* End of __mpu_IO_str_overflow_unlocked() */ /*************************************************************** Synchronize a UCS-2 string stream; no external I/O is required. ***************************************************************/ static int __mpu_IO_str_sync_unlocked( mpu_FILE *stream ) { return( __mpu_IO_str_publish_unlocked( stream ) ); } /* End of __mpu_IO_str_sync_unlocked() */ /*************************************************************** Finish string-stream backend state before stream destruction. ***************************************************************/ static int __mpu_IO_str_finish_unlocked( mpu_FILE *stream ) { __mpu_IO_strfile *sf; sf = (__mpu_IO_strfile *)stream->_cookie; if( sf != (__mpu_IO_strfile *)0 ) { if( __mpu_IO_str_publish_unlocked( stream ) == mpu_EOF ) return( mpu_EOF ); free( sf ); stream->_cookie = (void *)0; } return( 0 ); } /* End of __mpu_IO_str_finish_unlocked() */ /*************************************************************** Reject stdio byte-buffer configuration on UCS-2 string streams. ***************************************************************/ static int __mpu_IO_str_setbuf_unlocked( mpu_FILE *stream, unsigned char *buf, int mode, size_t size ) { (void)stream; (void)buf; (void)mode; (void)size; errno = EINVAL; return( -1 ); } /* End of __mpu_IO_str_setbuf_unlocked() */ static int __mpu_IO_str_flush_unlocked( mpu_FILE *stream ) { return( __mpu_IO_str_publish_unlocked( stream ) ); } /* End of __mpu_IO_str_flush_unlocked() */ static int __mpu_IO_str_close_unlocked( mpu_FILE *stream ) { (void)stream; return( 0 ); } /* End of __mpu_IO_str_close_unlocked() */ /*************************************************************** Reposition a UCS-2 memory stream. Fixed fmemopen streams may seek anywhere representable by their caller-owned buffer, while dynamic memstreams may seek beyond the current end. A later write fills any resulting gap with NUL code units. ***************************************************************/ static int __mpu_IO_str_seek_unlocked( mpu_FILE *stream, off_t offset, int whence ) { __mpu_IO_strfile *sf; uintmax_t base; uintmax_t magnitude; uintmax_t position; uintmax_t limit; sf = (__mpu_IO_strfile *)stream->_cookie; if( sf == (__mpu_IO_strfile *)0 ) { errno = EBADF; stream->_flags |= __MPU_IO_ERR_SEEN; return( -1 ); } switch( whence ) { case MPU_SEEK_SET: base = 0; break; case MPU_SEEK_CUR: base = (uintmax_t)sf->pos; break; case MPU_SEEK_END: base = (uintmax_t)sf->length; break; default: errno = EINVAL; return( -1 ); } if( offset < 0 ) { magnitude = (uintmax_t)(-(offset + 1)) + 1; if( magnitude > base ) { errno = EINVAL; return( -1 ); } position = base - magnitude; } else { if( (uintmax_t)offset > (uintmax_t)SIZE_MAX - base ) { errno = EOVERFLOW; return( -1 ); } position = base + (uintmax_t)offset; } if( sf->dynamic ) limit = (uintmax_t)SIZE_MAX - 1; else if( sf->nul_terminated ) limit = sf->capacity ? (uintmax_t)sf->capacity - 1 : 0; else limit = (uintmax_t)sf->capacity; if( position > limit ) { errno = EINVAL; return( -1 ); } sf->pos = (size_t)position; stream->_unget_count = 0; stream->_flags &= ~(__MPU_IO_READING | __MPU_IO_WRITING | __MPU_IO_EOF_SEEN); return( 0 ); } /* End of __mpu_IO_str_seek_unlocked() */ static off_t __mpu_IO_str_tell_unlocked( mpu_FILE *stream ) { __mpu_IO_strfile *sf = (__mpu_IO_strfile *)stream->_cookie; if( sf == (__mpu_IO_strfile *)0 ) { errno = EBADF; stream->_flags |= __MPU_IO_ERR_SEEN; return( (off_t)-1 ); } return( (off_t)sf->pos ); } /* End of __mpu_IO_str_tell_unlocked() */ /*************************************************************** Seek a UCS-2 string stream and return the resulting position. ***************************************************************/ static off_t __mpu_IO_str_seekoff_unlocked( mpu_FILE *stream, off_t offset, int whence ) { if( __mpu_IO_str_seek_unlocked( stream, offset, whence ) != 0 ) return( (off_t)-1 ); return( __mpu_IO_str_tell_unlocked( stream ) ); } /* End of __mpu_IO_str_seekoff_unlocked() */ /*************************************************************** Seek a UCS-2 string stream to an absolute position. ***************************************************************/ static off_t __mpu_IO_str_seekpos_unlocked( mpu_FILE *stream, off_t position ) { return( __mpu_IO_str_seekoff_unlocked( stream, position, MPU_SEEK_SET ) ); } /* End of __mpu_IO_str_seekpos_unlocked() */ static int __mpu_IO_str_get_char_unlocked( mpu_FILE *stream, __mpu_char16_t *wc ) { __mpu_IO_strfile *sf; if( stream->_flags & __MPU_IO_NO_READS ) { errno = EBADF; stream->_flags |= __MPU_IO_ERR_SEEN; return( mpu_EOF ); } sf = (__mpu_IO_strfile *)stream->_cookie; if( sf == (__mpu_IO_strfile *)0 ) { errno = EBADF; stream->_flags |= __MPU_IO_ERR_SEEN; return( mpu_EOF ); } /* Keep native UCS-2 streams in the same directional-state model as descriptor and cookie streams. */ stream->_flags &= ~__MPU_IO_WRITING; stream->_flags |= __MPU_IO_READING; if( sf->pos >= sf->length ) { stream->_flags |= __MPU_IO_EOF_SEEN; return( mpu_EOF ); } *wc = sf->buffer[sf->pos++]; return( 0 ); } /* End of __mpu_IO_str_get_char_unlocked() */ static int __mpu_IO_str_put_char_unlocked( __mpu_char16_t wc, mpu_FILE *stream ) { __mpu_IO_strfile *sf; if( stream->_flags & __MPU_IO_NO_WRITES ) { errno = EBADF; stream->_flags |= __MPU_IO_ERR_SEEN; return( mpu_EOF ); } if( wc >= (__mpu_char16_t)0xd800 && wc <= (__mpu_char16_t)0xdfff ) { errno = EILSEQ; stream->_flags |= __MPU_IO_ERR_SEEN; return( mpu_EOF ); } sf = (__mpu_IO_strfile *)stream->_cookie; if( sf == (__mpu_IO_strfile *)0 ) { errno = EBADF; stream->_flags |= __MPU_IO_ERR_SEEN; return( mpu_EOF ); } /* A successful text-output attempt establishes writing direction. */ stream->_flags &= ~__MPU_IO_READING; stream->_flags |= __MPU_IO_WRITING; if( (stream->_flags & __MPU_IO_COUNTED) && sf->pos >= sf->capacity ) { ++sf->pos; if( sf->pos > sf->length ) sf->length = sf->pos; return( 0 ); } /* Append-mode fmemopen writes are always placed at the current logical end, even after a successful seek changes the read position. */ if( stream->_flags & __MPU_IO_APPEND ) sf->pos = sf->length; if( sf->nul_terminated ) { if( sf->dynamic ) { if( sf->pos > SIZE_MAX - 2 || __mpu_IO_str_grow_unlocked( stream, sf->pos + 2 ) == mpu_EOF ) return( mpu_EOF ); } else if( sf->capacity == 0 || sf->pos + 1 >= sf->capacity ) { errno = ENOSPC; stream->_flags |= __MPU_IO_ERR_SEEN; return( mpu_EOF ); } } else if( sf->pos >= sf->capacity ) { errno = ENOSPC; stream->_flags |= __MPU_IO_ERR_SEEN; return( mpu_EOF ); } /* A write after seeking beyond the logical end creates a sparse text gap. Materialize it as NUL UCS-2 code units, matching the byte-zero gap semantics of libc open_memstream(). */ if( sf->pos > sf->length && sf->buffer != (__mpu_char16_t *)0 ) { size_t i; for( i = sf->length; i < sf->pos; ++i ) sf->buffer[i] = (__mpu_char16_t)0; } if( sf->buffer != (__mpu_char16_t *)0 ) sf->buffer[sf->pos] = wc; ++sf->pos; if( sf->pos > sf->length ) sf->length = sf->pos; if( sf->nul_terminated && sf->buffer != (__mpu_char16_t *)0 ) sf->buffer[sf->length] = (__mpu_char16_t)0; return( 0 ); } /* End of __mpu_IO_str_put_char_unlocked() */ static int __mpu_IO_str_get_byte_unlocked( mpu_FILE *stream ) { stream->_flags |= __MPU_IO_ERR_SEEN; errno = EINVAL; return( mpu_EOF ); } /* End of __mpu_IO_str_get_byte_unlocked() */ static int __mpu_IO_str_put_byte_unlocked( unsigned char c, mpu_FILE *stream ) { (void)c; stream->_flags |= __MPU_IO_ERR_SEEN; errno = EINVAL; return( mpu_EOF ); } /* End of __mpu_IO_str_put_byte_unlocked() */ mpu_FILE *__mpu_IO_str_open( __mpu_char16_t *buffer, size_t capacity, size_t length, unsigned int flags ) { struct __mpu_IO_FILE_plus *plus; mpu_FILE *stream; __mpu_IO_strfile *sf; if( (buffer == (__mpu_char16_t *)0 && capacity != 0) || length > capacity ) { errno = EINVAL; return( (mpu_FILE *)0 ); } plus = (struct __mpu_IO_FILE_plus *)calloc( 1, sizeof(*plus) ); if( plus == (struct __mpu_IO_FILE_plus *)0 ) return( (mpu_FILE *)0 ); stream = &plus->file; plus->vtable = &__mpu_IO_str_jumps; sf = (__mpu_IO_strfile *)calloc( 1, sizeof(__mpu_IO_strfile) ); if( sf == (__mpu_IO_strfile *)0 ) { free( plus ); return( (mpu_FILE *)0 ); } sf->buffer = buffer; sf->capacity = capacity; sf->length = length; sf->pos = 0; stream->_magic = __MPU_IO_MAGIC; stream->_flags = flags | __MPU_IO_STRING | __MPU_IO_USER_BUF; stream->_fileno = -1; stream->_cookie = sf; { int lock_error = __mpu_IO_lock_init( &stream->_lock ); if( lock_error != 0 ) { free( sf ); free( plus ); errno = lock_error; return( (mpu_FILE *)0 ); } } __mpu_IO_link_in( stream ); return( stream ); } /* End of __mpu_IO_str_open() */ mpu_FILE *__mpu_IO_str_open_readonly( const __mpu_char16_t *buffer, size_t length ) { return( __mpu_IO_str_open( (__mpu_char16_t *)buffer, length, length, __MPU_IO_NO_WRITES | __MPU_IO_NOLIST ) ); } /* End of __mpu_IO_str_open_readonly() */ mpu_FILE *__mpu_IO_str_open_counted( __mpu_char16_t *buffer, size_t capacity ) { mpu_FILE *stream; stream = __mpu_IO_str_open( buffer, capacity, 0, __MPU_IO_COUNTED | __MPU_IO_NOLIST ); return( stream ); } /* End of __mpu_IO_str_open_counted() */ /*************************************************************** Return the bounded UCS-2 string length within CAPACITY code units. ***************************************************************/ static size_t __mpu_IO_strnlen( const __mpu_char16_t *buffer, size_t capacity ) { size_t n; for( n = 0; n < capacity; ++n ) if( buffer[n] == (__mpu_char16_t)0 ) break; return( n ); } /* End of __mpu_IO_strnlen() */ /*************************************************************** Open caller-owned UCS-2 memory as a text stream. SIZE is the number of __mpu_char16_t code units including trailing-NUL space. ***************************************************************/ mpu_FILE *mpu_fmemopen( __mpu_char16_t *buffer, size_t size, const char *mode ) { unsigned int flags; int oflags; mpu_FILE *stream; __mpu_IO_strfile *sf; size_t length; if( buffer == (__mpu_char16_t *)0 || size == 0 ) { errno = EINVAL; return( (mpu_FILE *)0 ); } if( __mpu_IO_parse_mode( mode, &flags, &oflags, 0 ) < 0 ) return( (mpu_FILE *)0 ); (void)oflags; length = __mpu_IO_strnlen( buffer, size ); if( mode[0] == 'w' ) { length = 0; buffer[0] = (__mpu_char16_t)0; } stream = __mpu_IO_str_open( buffer, size, length, flags ); if( stream == (mpu_FILE *)0 ) return( (mpu_FILE *)0 ); sf = (__mpu_IO_strfile *)stream->_cookie; sf->nul_terminated = 1; if( mode[0] == 'a' ) sf->pos = sf->length; return( stream ); } /* End of mpu_fmemopen() */ /*************************************************************** Open a dynamically growing UCS-2 output memory stream with additional internal FLAGS controlling stream-list membership. ***************************************************************/ mpu_FILE *__mpu_IO_str_open_memstream( __mpu_char16_t **buffer, size_t *size, unsigned int flags ) { mpu_FILE *stream; __mpu_IO_strfile *sf; __mpu_char16_t *storage; if( buffer == (__mpu_char16_t **)0 || size == (size_t *)0 ) { errno = EINVAL; return( (mpu_FILE *)0 ); } storage = (__mpu_char16_t *)calloc( 16, sizeof(__mpu_char16_t) ); if( storage == (__mpu_char16_t *)0 ) return( (mpu_FILE *)0 ); stream = __mpu_IO_str_open( storage, 16, 0, __MPU_IO_NO_READS | __MPU_IO_MEMSTREAM | flags ); if( stream == (mpu_FILE *)0 ) { free( storage ); return( (mpu_FILE *)0 ); } sf = (__mpu_IO_strfile *)stream->_cookie; sf->nul_terminated = 1; sf->dynamic = 1; sf->publish_buffer = buffer; sf->publish_size = size; *buffer = storage; *size = 0; return( stream ); } /* End of __mpu_IO_str_open_memstream() */ /*************************************************************** Open a public dynamically growing UCS-2 output memory stream. *BUFFER and *SIZE are published by fflush() and fclose(). ***************************************************************/ mpu_FILE *mpu_open_memstream( __mpu_char16_t **buffer, size_t *size ) { return( __mpu_IO_str_open_memstream( buffer, size, 0 ) ); } /* End of mpu_open_memstream() */ const struct __mpu_IO_jump_t __mpu_IO_str_jumps = { .finish = __mpu_IO_str_finish_unlocked, .underflow = __mpu_IO_str_underflow_unlocked, .overflow = __mpu_IO_str_overflow_unlocked, .sync = __mpu_IO_str_sync_unlocked, .setbuf = __mpu_IO_str_setbuf_unlocked, .seekoff = __mpu_IO_str_seekoff_unlocked, .seekpos = __mpu_IO_str_seekpos_unlocked, .flush = __mpu_IO_str_flush_unlocked, .close = __mpu_IO_str_close_unlocked, .seek = __mpu_IO_str_seek_unlocked, .tell = __mpu_IO_str_tell_unlocked, .get_byte = __mpu_IO_str_get_byte_unlocked, .put_byte = __mpu_IO_str_put_byte_unlocked, .xsgetn = (size_t (*)(mpu_FILE *, void *, size_t))0, .xsputn = (size_t (*)(mpu_FILE *, const void *, size_t))0, .get_char = __mpu_IO_str_get_char_unlocked, .put_char = __mpu_IO_str_put_char_unlocked }; /*************************************************************** Hide internal symbols: ***************************************************************/ __mpu_hidden_decl(__mpu_IO_str_jumps); __mpu_hidden_decl(__mpu_IO_str_open_memstream); __mpu_hidden_decl(__mpu_IO_str_open); __mpu_hidden_decl(__mpu_IO_str_open_counted); __mpu_hidden_decl(__mpu_IO_str_open_readonly); /* End of hide internal symbols. ***************************************************************/ /* End of mpu-string.c */