/*************************************************************** MPU-FILE.C This file contains source code of functions for basic FILE operations. PART OF : MPUIO - library . USAGE : Internal and external . NOTE : NONE . Copyright (C) 2000 - 2026 by Andrey V.Kosteltsev. All Rights Reserved. ***************************************************************/ #ifdef HAVE_CONFIG_H #include #endif #include #include #include static struct __mpu_IO_FILE_plus __mpu_IO_stdin_plus = { .file = { ._magic = __MPU_IO_MAGIC, ._flags = __MPU_IO_NO_WRITES | __MPU_IO_STATIC, ._fileno = STDIN_FILENO, }, .vtable = &__mpu_IO_file_jumps }; static struct __mpu_IO_FILE_plus __mpu_IO_stdout_plus = { .file = { ._magic = __MPU_IO_MAGIC, ._flags = __MPU_IO_NO_READS | __MPU_IO_LINE_BUF | __MPU_IO_STATIC, ._fileno = STDOUT_FILENO, }, .vtable = &__mpu_IO_file_jumps }; static struct __mpu_IO_FILE_plus __mpu_IO_stderr_plus = { .file = { ._magic = __MPU_IO_MAGIC, ._flags = __MPU_IO_NO_READS | __MPU_IO_UNBUFFERED | __MPU_IO_STATIC, ._fileno = STDERR_FILENO, }, .vtable = &__mpu_IO_file_jumps }; mpu_FILE *mpu_stdin = &__mpu_IO_stdin_plus.file; mpu_FILE *mpu_stdout = &__mpu_IO_stdout_plus.file; mpu_FILE *mpu_stderr = &__mpu_IO_stderr_plus.file; pthread_mutex_t __mpu_IO_list_lock = PTHREAD_MUTEX_INITIALIZER; mpu_FILE *__mpu_IO_list_all = (mpu_FILE *)0; static pthread_once_t __mpu_IO_once = PTHREAD_ONCE_INIT; /*************************************************************** Build a stable snapshot of the current public stream list. Each returned stream is pinned until __mpu_IO_release_pin() is called, so mpu_fclose() cannot destroy it meanwhile. ***************************************************************/ static int __mpu_IO_snapshot( mpu_FILE ***snapshot, size_t *count ) { mpu_FILE **list; mpu_FILE *p; size_t n = 0; size_t i = 0; if( snapshot == (mpu_FILE ***)0 || count == (size_t *)0 ) { errno = EINVAL; return( mpu_EOF ); } *snapshot = (mpu_FILE **)0; *count = 0; pthread_mutex_lock( &__mpu_IO_list_lock ); for( p = __mpu_IO_list_all; p != (mpu_FILE *)0; p = p->_next ) ++n; if( n == 0 ) { pthread_mutex_unlock( &__mpu_IO_list_lock ); return( 0 ); } if( n > SIZE_MAX / sizeof(*list) ) { pthread_mutex_unlock( &__mpu_IO_list_lock ); errno = EOVERFLOW; return( mpu_EOF ); } list = (mpu_FILE **)malloc( n * sizeof(*list) ); if( list == (mpu_FILE **)0 ) { pthread_mutex_unlock( &__mpu_IO_list_lock ); return( mpu_EOF ); } for( p = __mpu_IO_list_all; p != (mpu_FILE *)0; p = p->_next ) { ++p->_pins; list[i++] = p; } pthread_mutex_unlock( &__mpu_IO_list_lock ); *snapshot = list; *count = i; return( 0 ); } /* End of __mpu_IO_snapshot() */ /*************************************************************** Drop one lifetime pin acquired by __mpu_IO_snapshot(). A dynamically allocated stream closed while pinned is destroyed only after the final snapshot user releases it. ***************************************************************/ static void __mpu_IO_release_pin( mpu_FILE *stream ) { int free_stream = 0; pthread_mutex_lock( &__mpu_IO_list_lock ); if( stream->_pins != 0 ) --stream->_pins; if( stream->_closing == 2 && stream->_pins == 0 && !(stream->_flags & __MPU_IO_STATIC) ) { stream->_closing = 3; free_stream = 1; } pthread_mutex_unlock( &__mpu_IO_list_lock ); if( free_stream ) { pthread_mutex_destroy( &stream->_lock ); free( stream ); } return; } /* End of __mpu_IO_release_pin() */ /*************************************************************** Initialize one recursive stream mutex. Recursive locking is required by mpu_flockfile() followed by ordinary stream calls. ***************************************************************/ int __mpu_IO_lock_init( pthread_mutex_t *lock ) { pthread_mutexattr_t attr; int ret; ret = pthread_mutexattr_init( &attr ); if( ret != 0 ) return( ret ); ret = pthread_mutexattr_settype( &attr, PTHREAD_MUTEX_RECURSIVE ); if( ret == 0 ) ret = pthread_mutex_init( lock, &attr ); (void)pthread_mutexattr_destroy( &attr ); return( ret ); } /* End of __mpu_IO_lock_init() */ static void __mpu_IO_init_standard_streams( void ) { if( __mpu_IO_lock_init( &mpu_stdin->_lock ) != 0 ) mpu_stdin->_magic = 0; else __mpu_IO_link_in( mpu_stdin ); if( __mpu_IO_lock_init( &mpu_stdout->_lock ) != 0 ) mpu_stdout->_magic = 0; else __mpu_IO_link_in( mpu_stdout ); if( __mpu_IO_lock_init( &mpu_stderr->_lock ) != 0 ) mpu_stderr->_magic = 0; else __mpu_IO_link_in( mpu_stderr ); return; } /* End of __mpu_IO_init_standard_streams() */ static void __mpu_IO_init( void ) { (void)pthread_once( &__mpu_IO_once, __mpu_IO_init_standard_streams ); return; } /* End of __mpu_IO_init() */ /*************************************************************** Decode a stdio-style mode string into stream and open flags. ***************************************************************/ int __mpu_IO_parse_mode( const char *mode, unsigned int *flags, int *oflags, int for_open ) { unsigned int f = 0; int o; int plus_seen = 0; int b_seen = 0; const char *p; if( mode == (const char *)0 || *mode == '\0' || flags == (unsigned int *)0 || oflags == (int *)0 ) { errno = EINVAL; return( -1 ); } switch( *mode ) { case 'r': o = O_RDONLY; f |= __MPU_IO_NO_WRITES; break; case 'w': o = O_WRONLY; if( for_open ) o |= O_CREAT | O_TRUNC; f |= __MPU_IO_NO_READS; break; case 'a': o = O_WRONLY | O_APPEND; if( for_open ) o |= O_CREAT; f |= __MPU_IO_NO_READS | __MPU_IO_APPEND; break; default: errno = EINVAL; return( -1 ); } /* Accept only the ISO C suffix grammar: one optional '+' and one optional 'b', in either order. Reject unknown or repeated flags. */ for( p = mode + 1; *p != '\0'; ++p ) { if( *p == '+' ) { if( plus_seen ) { errno = EINVAL; return( -1 ); } plus_seen = 1; } else if( *p == 'b' ) { if( b_seen ) { errno = EINVAL; return( -1 ); } b_seen = 1; } else { errno = EINVAL; return( -1 ); } } if( plus_seen ) { o &= ~(O_RDONLY | O_WRONLY); o |= O_RDWR; f &= ~(__MPU_IO_NO_READS | __MPU_IO_NO_WRITES); } *flags = f; *oflags = o; return( 0 ); } /* End of __mpu_IO_parse_mode() */ int __mpu_IO_valid( mpu_FILE *stream ) { __mpu_IO_init(); if( stream == (mpu_FILE *)0 ) { errno = EINVAL; return( 0 ); } if( stream->_magic != __MPU_IO_MAGIC ) { errno = EBADF; return( 0 ); } return( 1 ); } /* End of __mpu_IO_valid() */ void __mpu_IO_link_in( mpu_FILE *stream ) { if( stream->_flags & __MPU_IO_NOLIST ) return; pthread_mutex_lock( &__mpu_IO_list_lock ); stream->_next = __mpu_IO_list_all; __mpu_IO_list_all = stream; pthread_mutex_unlock( &__mpu_IO_list_lock ); return; } /* End of __mpu_IO_link_in() */ /*************************************************************** Remove STREAM from the global stream list. The caller must already hold __mpu_IO_list_lock. ***************************************************************/ void __mpu_IO_unlink_locked( mpu_FILE *stream ) { mpu_FILE **p; for( p = &__mpu_IO_list_all; *p != (mpu_FILE *)0; p = &(*p)->_next ) { if( *p == stream ) { *p = stream->_next; stream->_next = (mpu_FILE *)0; break; } } return; } /* End of __mpu_IO_unlink_locked() */ /*************************************************************** Remove STREAM from the global stream list while serializing the list operation. ***************************************************************/ void __mpu_IO_unlink( mpu_FILE *stream ) { pthread_mutex_lock( &__mpu_IO_list_lock ); __mpu_IO_unlink_locked( stream ); pthread_mutex_unlock( &__mpu_IO_list_lock ); return; } /* End of __mpu_IO_unlink() */ /*************************************************************** Allocate a FILE_plus object for a POSIX file descriptor and attach the real-file operation jump table. ***************************************************************/ mpu_FILE *__mpu_IO_file_open( int fd, unsigned int flags ) { struct __mpu_IO_FILE_plus *plus; mpu_FILE *stream; 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_file_jumps; stream->_magic = __MPU_IO_MAGIC; stream->_flags = flags; stream->_fileno = fd; { int lock_error = __mpu_IO_lock_init( &stream->_lock ); if( lock_error != 0 ) { free( plus ); errno = lock_error; return( (mpu_FILE *)0 ); } } __mpu_IO_init(); __mpu_IO_link_in( stream ); return( stream ); } /* End of __mpu_IO_file_open() */ /*************************************************************** Open a UTF-8 named file and attach it to a new MPU stream. ***************************************************************/ mpu_FILE *mpu_fopen( const char *filename, const char *mode ) { unsigned int flags; int oflags; int fd; if( filename == (const char *)0 ) { errno = EINVAL; return( (mpu_FILE *)0 ); } if( __mpu_IO_parse_mode( mode, &flags, &oflags, 1 ) < 0 ) return( (mpu_FILE *)0 ); fd = open( filename, oflags, 0666 ); if( fd < 0 ) return( (mpu_FILE *)0 ); { mpu_FILE *stream = __mpu_IO_file_open( fd, flags ); if( stream == (mpu_FILE *)0 ) { int saved_errno = errno; close( fd ); errno = saved_errno; } return( stream ); } } /* End of mpu_fopen() */ /*************************************************************** Attach an existing POSIX descriptor to a new MPU stream. The descriptor is closed by mpu_fclose() after success. ***************************************************************/ mpu_FILE *mpu_fdopen( int fd, const char *mode ) { unsigned int flags; int mode_flags; int fd_flags; int accmode; if( fd < 0 ) { errno = EBADF; return( (mpu_FILE *)0 ); } if( __mpu_IO_parse_mode( mode, &flags, &mode_flags, 0 ) < 0 ) return( (mpu_FILE *)0 ); fd_flags = fcntl( fd, F_GETFL ); if( fd_flags < 0 ) return( (mpu_FILE *)0 ); accmode = fd_flags & O_ACCMODE; if( !(flags & __MPU_IO_NO_READS) && accmode == O_WRONLY ) { errno = EINVAL; return( (mpu_FILE *)0 ); } if( !(flags & __MPU_IO_NO_WRITES) && accmode == O_RDONLY ) { errno = EINVAL; return( (mpu_FILE *)0 ); } if( flags & __MPU_IO_APPEND ) { if( !(fd_flags & O_APPEND) && fcntl( fd, F_SETFL, fd_flags | O_APPEND ) < 0 ) return( (mpu_FILE *)0 ); } return( __mpu_IO_file_open( fd, flags ) ); } /* End of mpu_fdopen() */ /*************************************************************** Reopen an existing real-file stream on FILENAME. The stream object and its recursive lock are preserved on success. ***************************************************************/ mpu_FILE *mpu_freopen( const char *filename, const char *mode, mpu_FILE *stream ) { unsigned int flags; unsigned int keep_flags; int oflags; int fd; int oldfd; int saved_errno = 0; if( filename == (const char *)0 || !__mpu_IO_valid( stream ) ) { if( filename == (const char *)0 ) errno = EINVAL; return( (mpu_FILE *)0 ); } if( stream->_flags & (__MPU_IO_STRING | __MPU_IO_COOKIE | __MPU_IO_PIPE) ) { errno = EINVAL; return( (mpu_FILE *)0 ); } if( __mpu_IO_parse_mode( mode, &flags, &oflags, 1 ) < 0 ) return( (mpu_FILE *)0 ); pthread_mutex_lock( &stream->_lock ); if( (stream->_flags & __MPU_IO_WRITING) && __mpu_IO_sync_unlocked( stream ) == mpu_EOF ) saved_errno = errno; oldfd = stream->_fileno; if( oldfd >= 0 && close( oldfd ) < 0 && saved_errno == 0 ) saved_errno = errno; stream->_fileno = -1; fd = open( filename, oflags, 0666 ); if( fd < 0 ) { if( saved_errno == 0 ) saved_errno = errno; /* The previous association has already been closed. Keep the allocated stream object in a well-defined closed state so the caller may inspect its error indicator and safely fclose() it. */ keep_flags = stream->_flags & (__MPU_IO_STATIC | __MPU_IO_USER_BUF | __MPU_IO_LINE_BUF | __MPU_IO_UNBUFFERED); stream->_flags = keep_flags | __MPU_IO_NO_READS | __MPU_IO_NO_WRITES | __MPU_IO_ERR_SEEN; stream->_read_base = stream->_buf_base; stream->_read_ptr = stream->_buf_base; stream->_read_end = stream->_buf_base; stream->_write_base = stream->_buf_base; stream->_write_ptr = stream->_buf_base; stream->_write_end = stream->_buf_base; stream->_unget_count = 0; pthread_mutex_unlock( &stream->_lock ); errno = saved_errno; return( (mpu_FILE *)0 ); } keep_flags = stream->_flags & (__MPU_IO_STATIC | __MPU_IO_USER_BUF | __MPU_IO_LINE_BUF | __MPU_IO_UNBUFFERED); stream->_flags = flags | keep_flags; stream->_fileno = fd; stream->_read_base = stream->_buf_base; stream->_read_ptr = stream->_buf_base; stream->_read_end = stream->_buf_base; stream->_write_base = stream->_buf_base; stream->_write_ptr = stream->_buf_base; stream->_write_end = stream->_buf_base; stream->_unget_count = 0; __MPU_IO_PLUS( stream )->vtable = &__mpu_IO_file_jumps; pthread_mutex_unlock( &stream->_lock ); if( saved_errno != 0 ) errno = saved_errno; return( stream ); } /* End of mpu_freopen() */ /*************************************************************** Create an anonymous temporary read/write stream. The directory entry is removed immediately after mkstemp() succeeds. ***************************************************************/ mpu_FILE *mpu_tmpfile( void ) { char name[] = "/tmp/libmpuio-XXXXXX"; int fd; mpu_FILE *stream; fd = mkstemp( name ); if( fd < 0 ) return( (mpu_FILE *)0 ); if( unlink( name ) < 0 ) { int saved_errno = errno; close( fd ); errno = saved_errno; return( (mpu_FILE *)0 ); } stream = mpu_fdopen( fd, "w+" ); if( stream == (mpu_FILE *)0 ) { int saved_errno = errno; close( fd ); errno = saved_errno; } return( stream ); } /* End of mpu_tmpfile() */ int mpu_fclose( mpu_FILE *stream ) { unsigned int static_stream; int free_stream = 0; int saved_errno = 0; int ret = 0; if( !__mpu_IO_valid( stream ) ) return( mpu_EOF ); /* Remove STREAM from future snapshots before closing its backend. Existing snapshots keep lifetime pins, so the FILE object and its mutex remain alive until their final user has moved past STREAM. */ __mpu_IO_init(); pthread_mutex_lock( &__mpu_IO_list_lock ); if( stream->_closing != 0 ) { pthread_mutex_unlock( &__mpu_IO_list_lock ); errno = EBADF; return( mpu_EOF ); } stream->_closing = 1; __mpu_IO_unlink_locked( stream ); pthread_mutex_unlock( &__mpu_IO_list_lock ); pthread_mutex_lock( &stream->_lock ); static_stream = stream->_flags & __MPU_IO_STATIC; if( (stream->_flags & __MPU_IO_WRITING) && __mpu_IO_sync_unlocked( stream ) == mpu_EOF ) { ret = mpu_EOF; saved_errno = errno; } if( __mpu_IO_finish_unlocked( stream ) == mpu_EOF ) { if( ret == 0 ) saved_errno = errno; ret = mpu_EOF; } if( __MPU_IO_JUMPS( stream ) != (const struct __mpu_IO_jump_t *)0 && __MPU_IO_JUMPS( stream )->close != (int (*)(mpu_FILE *))0 && (*__MPU_IO_JUMPS( stream )->close)( stream ) == mpu_EOF ) { if( ret == 0 ) saved_errno = errno; ret = mpu_EOF; } if( !(stream->_flags & __MPU_IO_STRING) && stream->_buf_base != (unsigned char *)0 && !(stream->_flags & __MPU_IO_USER_BUF) ) free( stream->_buf_base ); stream->_buf_base = (unsigned char *)0; stream->_buf_end = (unsigned char *)0; stream->_magic = 0; pthread_mutex_unlock( &stream->_lock ); /* Publishing state 2 under the list lock tells the final pin releaser that backend shutdown is complete and deferred destruction is safe. */ pthread_mutex_lock( &__mpu_IO_list_lock ); stream->_closing = 2; if( !static_stream && stream->_pins == 0 ) { stream->_closing = 3; free_stream = 1; } pthread_mutex_unlock( &__mpu_IO_list_lock ); if( free_stream ) { pthread_mutex_destroy( &stream->_lock ); free( stream ); } if( saved_errno ) errno = saved_errno; return( ret ); } /* End of mpu_fclose() */ /*************************************************************** Flush one STREAM without acquiring its lock. STREAM must not be NULL; use mpu_fflush() for the all-streams operation. ***************************************************************/ int mpu_fflush_unlocked( mpu_FILE *stream ) { if( !__mpu_IO_valid( stream ) ) return( mpu_EOF ); return( __mpu_IO_sync_unlocked( stream ) ); } /* End of mpu_fflush_unlocked() */ int mpu_fflush( mpu_FILE *stream ) { int ret = 0; int saved_errno = 0; __mpu_IO_init(); if( stream == (mpu_FILE *)0 ) { mpu_FILE **snapshot; size_t count; size_t i; if( __mpu_IO_snapshot( &snapshot, &count ) == mpu_EOF ) return( mpu_EOF ); for( i = 0; i < count; ++i ) { mpu_FILE *p = snapshot[i]; pthread_mutex_lock( &p->_lock ); if( p->_magic == __MPU_IO_MAGIC && (p->_flags & __MPU_IO_WRITING) && __mpu_IO_sync_unlocked( p ) == mpu_EOF ) { /* Continue flushing the remaining streams, but preserve the first error reported by the all-streams operation. */ if( saved_errno == 0 ) saved_errno = errno; ret = mpu_EOF; } pthread_mutex_unlock( &p->_lock ); __mpu_IO_release_pin( p ); } free( snapshot ); if( saved_errno != 0 ) errno = saved_errno; return( ret ); } if( !__mpu_IO_valid( stream ) ) return( mpu_EOF ); pthread_mutex_lock( &stream->_lock ); ret = mpu_fflush_unlocked( stream ); pthread_mutex_unlock( &stream->_lock ); return( ret ); } /* End of mpu_fflush() */ /*************************************************************** Flush every currently writing line-buffered stream. Continue after failures and preserve errno from the first failed stream. ***************************************************************/ void mpu_flushlbf( void ) { mpu_FILE **snapshot; size_t count; size_t i; int entry_errno = errno; int saved_errno = 0; __mpu_IO_init(); if( __mpu_IO_snapshot( &snapshot, &count ) == mpu_EOF ) return; for( i = 0; i < count; ++i ) { mpu_FILE *p = snapshot[i]; pthread_mutex_lock( &p->_lock ); if( p->_magic == __MPU_IO_MAGIC && (p->_flags & (__MPU_IO_WRITING | __MPU_IO_LINE_BUF)) == (__MPU_IO_WRITING | __MPU_IO_LINE_BUF) && __mpu_IO_sync_unlocked( p ) == mpu_EOF ) { /* There is no return value, so retain the first errno while still visiting the remaining line-buffered streams. */ if( saved_errno == 0 ) saved_errno = errno; } pthread_mutex_unlock( &p->_lock ); __mpu_IO_release_pin( p ); } free( snapshot ); errno = saved_errno != 0 ? saved_errno : entry_errno; return; } /* End of mpu_flushlbf() */ /*************************************************************** Discard buffered STREAM input/output state without backend I/O and without acquiring the stream lock. EOF/error indicators and the current read/write direction are preserved. ***************************************************************/ int mpu_fpurge_unlocked( mpu_FILE *stream ) { if( !__mpu_IO_valid( stream ) ) return( mpu_EOF ); /* File and cookie backends buffer external bytes. String streams operate directly on UCS-2 storage and therefore have no generic byte buffer to discard. */ if( !(stream->_flags & __MPU_IO_STRING) ) { if( stream->_flags & __MPU_IO_READING ) { if( stream->_read_end != (unsigned char *)0 ) stream->_read_ptr = stream->_read_end; } if( stream->_flags & __MPU_IO_WRITING ) { if( stream->_write_base != (unsigned char *)0 ) stream->_write_ptr = stream->_write_base; } } /* Public/scanner UCS-2 pushback is generic to every backend. */ stream->_unget_count = 0; return( 0 ); } /* End of mpu_fpurge_unlocked() */ /*************************************************************** Discard buffered STREAM state without synchronizing the backend. ***************************************************************/ int mpu_fpurge( mpu_FILE *stream ) { int ret; if( !__mpu_IO_valid( stream ) ) return( mpu_EOF ); pthread_mutex_lock( &stream->_lock ); ret = mpu_fpurge_unlocked( stream ); pthread_mutex_unlock( &stream->_lock ); return( ret ); } /* End of mpu_fpurge() */ /*************************************************************** Return STREAM file descriptor without acquiring its lock. ***************************************************************/ int mpu_fileno_unlocked( mpu_FILE *stream ) { if( !__mpu_IO_valid( stream ) ) return( -1 ); if( stream->_flags & __MPU_IO_STRING || stream->_fileno < 0 ) { errno = EBADF; return( -1 ); } return( stream->_fileno ); } /* End of mpu_fileno_unlocked() */ int mpu_fileno( mpu_FILE *stream ) { int ret; if( !__mpu_IO_valid( stream ) ) return( -1 ); pthread_mutex_lock( &stream->_lock ); ret = mpu_fileno_unlocked( stream ); pthread_mutex_unlock( &stream->_lock ); return( ret ); } /* End of mpu_fileno() */ /*************************************************************** Return STREAM EOF indicator without acquiring its lock. ***************************************************************/ int mpu_feof_unlocked( mpu_FILE *stream ) { if( !__mpu_IO_valid( stream ) ) return( 0 ); return( (stream->_flags & __MPU_IO_EOF_SEEN) != 0 ); } /* End of mpu_feof_unlocked() */ int mpu_feof( mpu_FILE *stream ) { int ret; if( !__mpu_IO_valid( stream ) ) return( 0 ); pthread_mutex_lock( &stream->_lock ); ret = mpu_feof_unlocked( stream ); pthread_mutex_unlock( &stream->_lock ); return( ret ); } /* End of mpu_feof() */ /*************************************************************** Return STREAM error indicator without acquiring its lock. ***************************************************************/ int mpu_ferror_unlocked( mpu_FILE *stream ) { if( !__mpu_IO_valid( stream ) ) return( 1 ); return( (stream->_flags & __MPU_IO_ERR_SEEN) != 0 ); } /* End of mpu_ferror_unlocked() */ int mpu_ferror( mpu_FILE *stream ) { int ret; if( !__mpu_IO_valid( stream ) ) return( 1 ); pthread_mutex_lock( &stream->_lock ); ret = mpu_ferror_unlocked( stream ); pthread_mutex_unlock( &stream->_lock ); return( ret ); } /* End of mpu_ferror() */ /*************************************************************** Clear STREAM EOF/error indicators without acquiring its lock. ***************************************************************/ void mpu_clearerr_unlocked( mpu_FILE *stream ) { if( !__mpu_IO_valid( stream ) ) return; stream->_flags &= ~(__MPU_IO_EOF_SEEN | __MPU_IO_ERR_SEEN); return; } /* End of mpu_clearerr_unlocked() */ void mpu_clearerr( mpu_FILE *stream ) { if( !__mpu_IO_valid( stream ) ) return; pthread_mutex_lock( &stream->_lock ); mpu_clearerr_unlocked( stream ); pthread_mutex_unlock( &stream->_lock ); return; } /* End of mpu_clearerr() */ int __mpu_IO_file_close_unlocked( mpu_FILE *stream ) { int ret; /* A failed mpu_freopen() leaves a valid but already-disassociated stream object. Closing that object is a successful final cleanup. */ if( stream->_fileno < 0 ) return( 0 ); ret = close( stream->_fileno ); if( ret < 0 ) { stream->_flags |= __MPU_IO_ERR_SEEN; return( mpu_EOF ); } stream->_fileno = -1; return( 0 ); } /* End of __mpu_IO_file_close_unlocked() */ const struct __mpu_IO_jump_t __mpu_IO_file_jumps = { .finish = __mpu_IO_file_finish_unlocked, .underflow = __mpu_IO_file_underflow_unlocked, .overflow = __mpu_IO_file_overflow_unlocked, .sync = __mpu_IO_file_sync_unlocked, .setbuf = __mpu_IO_file_setbuf_unlocked, .seekoff = __mpu_IO_file_seekoff_unlocked, .seekpos = __mpu_IO_file_seekpos_unlocked, .flush = __mpu_IO_file_flush_unlocked, .close = __mpu_IO_file_close_unlocked, .seek = __mpu_IO_file_seek_unlocked, .tell = __mpu_IO_file_tell_unlocked, .get_byte = __mpu_IO_file_get_byte_unlocked, .put_byte = __mpu_IO_file_put_byte_unlocked, .xsgetn = __mpu_IO_file_xsgetn_unlocked, .xsputn = __mpu_IO_file_xsputn_unlocked, .get_char = __mpu_UTF8_decode_unlocked, .put_char = __mpu_UTF8_encode_unlocked }; /*************************************************************** Open COMMAND through /bin/sh and return a unidirectional descriptor-backed stream. MODE is exactly "r" or "w". ***************************************************************/ mpu_FILE *mpu_popen( const char *command, const char *mode ) { int pipefd[2]; int parent_fd; int child_fd; int target_fd; pid_t pid; mpu_FILE *stream; if( command == (const char *)0 || mode == (const char *)0 || !((mode[0] == 'r' || mode[0] == 'w') && mode[1] == '\0') ) { errno = EINVAL; return( (mpu_FILE *)0 ); } if( pipe( pipefd ) < 0 ) return( (mpu_FILE *)0 ); if( mode[0] == 'r' ) { parent_fd = pipefd[0]; child_fd = pipefd[1]; target_fd = STDOUT_FILENO; } else { parent_fd = pipefd[1]; child_fd = pipefd[0]; target_fd = STDIN_FILENO; } { int fdflags = fcntl( parent_fd, F_GETFD ); if( fdflags < 0 || fcntl( parent_fd, F_SETFD, fdflags | FD_CLOEXEC ) < 0 ) { int saved_errno = errno; close( pipefd[0] ); close( pipefd[1] ); errno = saved_errno; return( (mpu_FILE *)0 ); } } pid = fork(); if( pid < 0 ) { int saved_errno = errno; close( pipefd[0] ); close( pipefd[1] ); errno = saved_errno; return( (mpu_FILE *)0 ); } if( pid == 0 ) { close( parent_fd ); if( child_fd != target_fd ) { if( dup2( child_fd, target_fd ) < 0 ) _exit( 127 ); close( child_fd ); } execl( "/bin/sh", "sh", "-c", command, (char *)0 ); _exit( 127 ); } close( child_fd ); stream = mpu_fdopen( parent_fd, mode ); if( stream == (mpu_FILE *)0 ) { int saved_errno = errno; int status; close( parent_fd ); while( waitpid( pid, &status, 0 ) < 0 && errno == EINTR ) ; errno = saved_errno; return( (mpu_FILE *)0 ); } stream->_flags |= __MPU_IO_PIPE; stream->_cookie = (void *)(intptr_t)pid; return( stream ); } /* End of mpu_popen() */ /*************************************************************** Close a stream returned by mpu_popen() and wait for the child. The returned value is the wait status, as for POSIX pclose(). ***************************************************************/ int mpu_pclose( mpu_FILE *stream ) { pid_t pid; int close_errno = 0; int status; pid_t ret; if( !__mpu_IO_valid( stream ) || !(stream->_flags & __MPU_IO_PIPE) ) { errno = EINVAL; return( -1 ); } pid = (pid_t)(intptr_t)stream->_cookie; if( mpu_fclose( stream ) == mpu_EOF ) close_errno = errno; do { ret = waitpid( pid, &status, 0 ); } while( ret < 0 && errno == EINTR ); if( ret < 0 ) return( -1 ); if( close_errno != 0 ) { errno = close_errno; return( -1 ); } return( status ); } /* End of mpu_pclose() */ /*************************************************************** Hide internal symbols: ***************************************************************/ __mpu_hidden_decl(__mpu_IO_file_close_unlocked); __mpu_hidden_decl(__mpu_IO_file_jumps); __mpu_hidden_decl(__mpu_IO_file_open); __mpu_hidden_decl(__mpu_IO_link_in); __mpu_hidden_decl(__mpu_IO_list_all); __mpu_hidden_decl(__mpu_IO_list_lock); __mpu_hidden_decl(__mpu_IO_lock_init); __mpu_hidden_decl(__mpu_IO_parse_mode); __mpu_hidden_decl(__mpu_IO_unlink); __mpu_hidden_decl(__mpu_IO_unlink_locked); __mpu_hidden_decl(__mpu_IO_valid); /* End of hide internal symbols. ***************************************************************/ /* End of mpu-file.c */