mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 09:02:43 +00:00 
			
		
		
		
	LibC: Rewrite stdio
The new version uses buffering much more prominently, and hopefully performs better. It also uses something resembling C++ rather than plain C.
This commit is contained in:
		
							parent
							
								
									000a9cad34
								
							
						
					
					
						commit
						db30a2549e
					
				
					 2 changed files with 579 additions and 186 deletions
				
			
		|  | @ -34,20 +34,6 @@ | ||||||
| 
 | 
 | ||||||
| __BEGIN_DECLS | __BEGIN_DECLS | ||||||
| 
 | 
 | ||||||
| struct __STDIO_FILE { | typedef struct FILE FILE; | ||||||
|     int fd; |  | ||||||
|     int eof; |  | ||||||
|     int error; |  | ||||||
|     int mode; |  | ||||||
|     pid_t popen_child; |  | ||||||
|     char* buffer; |  | ||||||
|     size_t buffer_size; |  | ||||||
|     size_t buffer_index; |  | ||||||
|     int have_ungotten; |  | ||||||
|     char ungotten; |  | ||||||
|     char default_buffer[BUFSIZ]; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| typedef struct __STDIO_FILE FILE; |  | ||||||
| 
 | 
 | ||||||
| __END_DECLS | __END_DECLS | ||||||
|  |  | ||||||
|  | @ -28,6 +28,7 @@ | ||||||
| #include <AK/PrintfImplementation.h> | #include <AK/PrintfImplementation.h> | ||||||
| #include <AK/ScopedValueRollback.h> | #include <AK/ScopedValueRollback.h> | ||||||
| #include <AK/StdLibExtras.h> | #include <AK/StdLibExtras.h> | ||||||
|  | #include <AK/kmalloc.h> | ||||||
| #include <Kernel/Syscall.h> | #include <Kernel/Syscall.h> | ||||||
| #include <assert.h> | #include <assert.h> | ||||||
| #include <errno.h> | #include <errno.h> | ||||||
|  | @ -40,54 +41,510 @@ | ||||||
| #include <sys/wait.h> | #include <sys/wait.h> | ||||||
| #include <unistd.h> | #include <unistd.h> | ||||||
| 
 | 
 | ||||||
|  | struct FILE { | ||||||
|  | public: | ||||||
|  |     FILE(int fd, int mode) | ||||||
|  |         : m_fd(fd) | ||||||
|  |         , m_mode(mode) | ||||||
|  |     { | ||||||
|  |     } | ||||||
|  |     ~FILE(); | ||||||
|  | 
 | ||||||
|  |     static FILE* create(int fd, int mode); | ||||||
|  | 
 | ||||||
|  |     void setbuf(u8* data, int mode, size_t size) { m_buffer.setbuf(data, mode, size); } | ||||||
|  | 
 | ||||||
|  |     bool flush(); | ||||||
|  |     bool close(); | ||||||
|  | 
 | ||||||
|  |     int fileno() const { return m_fd; } | ||||||
|  |     bool eof() const { return m_eof; } | ||||||
|  | 
 | ||||||
|  |     int error() const { return m_error; } | ||||||
|  |     void clear_err() { m_error = 0; } | ||||||
|  | 
 | ||||||
|  |     size_t read(u8*, size_t); | ||||||
|  |     size_t write(const u8*, size_t); | ||||||
|  | 
 | ||||||
|  |     bool gets(u8*, size_t); | ||||||
|  |     bool ungetc(u8 byte) { return m_buffer.enqueue_front(byte); } | ||||||
|  | 
 | ||||||
|  |     int seek(long offset, int whence); | ||||||
|  |     long tell(); | ||||||
|  | 
 | ||||||
|  |     pid_t popen_child() { return m_popen_child; } | ||||||
|  |     void set_popen_child(pid_t child_pid) { m_popen_child = child_pid; } | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  | 
 | ||||||
|  |     struct Buffer { | ||||||
|  |         // A ringbuffer that also transparently implements ungetc().
 | ||||||
|  |     public: | ||||||
|  |         ~Buffer(); | ||||||
|  | 
 | ||||||
|  |         int mode() const { return m_mode; } | ||||||
|  |         void setbuf(u8* data, int mode, size_t size); | ||||||
|  |         // Make sure to call realize() before enqueuing any data.
 | ||||||
|  |         // Dequeuing can be attempted without it.
 | ||||||
|  |         void realize(int fd); | ||||||
|  |         void drop(); | ||||||
|  | 
 | ||||||
|  |         bool may_use() const { return m_ungotten || m_mode != _IONBF; } | ||||||
|  |         bool is_not_empty() const { return m_ungotten || !m_empty; } | ||||||
|  | 
 | ||||||
|  |         const u8* begin_dequeue(size_t& available_size) const; | ||||||
|  |         void did_dequeue(size_t actual_size); | ||||||
|  | 
 | ||||||
|  |         u8* begin_enqueue(size_t& available_size) const; | ||||||
|  |         void did_enqueue(size_t actual_size); | ||||||
|  | 
 | ||||||
|  |         bool enqueue_front(u8 byte); | ||||||
|  | 
 | ||||||
|  |     private: | ||||||
|  |         // Note: the fields here are arranged this way
 | ||||||
|  |         // to make sizeof(Buffer) smaller.
 | ||||||
|  |         u8* m_data { nullptr }; | ||||||
|  |         size_t m_capacity { BUFSIZ }; | ||||||
|  |         size_t m_begin { 0 }; | ||||||
|  |         size_t m_end { 0 }; | ||||||
|  | 
 | ||||||
|  |         int m_mode { -1 }; | ||||||
|  |         u8 m_unget_buffer { 0 }; | ||||||
|  |         bool m_ungotten : 1 { false }; | ||||||
|  |         bool m_data_is_malloced : 1 { false }; | ||||||
|  |         // When m_begin == m_end, we want to distinguish whether
 | ||||||
|  |         // the buffer is full or empty.
 | ||||||
|  |         bool m_empty : 1 { true }; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     // Read or write using the underlying fd, bypassing the buffer.
 | ||||||
|  |     ssize_t do_read(u8*, size_t); | ||||||
|  |     ssize_t do_write(const u8*, size_t); | ||||||
|  | 
 | ||||||
|  |     // Read some data into the buffer.
 | ||||||
|  |     bool read_into_buffer(); | ||||||
|  |     // Flush *some* data from the buffer.
 | ||||||
|  |     bool write_from_buffer(); | ||||||
|  | 
 | ||||||
|  |     int m_fd { -1 }; | ||||||
|  |     int m_mode { 0 }; | ||||||
|  |     int m_error { 0 }; | ||||||
|  |     bool m_eof { false }; | ||||||
|  |     pid_t m_popen_child { -1 }; | ||||||
|  |     Buffer m_buffer; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | FILE::~FILE() | ||||||
|  | { | ||||||
|  |     bool already_closed = m_fd == -1; | ||||||
|  |     ASSERT(already_closed); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | FILE* FILE::create(int fd, int mode) | ||||||
|  | { | ||||||
|  |     void* file = calloc(1, sizeof(FILE)); | ||||||
|  |     new (file) FILE(fd, mode); | ||||||
|  |     return (FILE*)file; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool FILE::close() | ||||||
|  | { | ||||||
|  |     bool flush_ok = flush(); | ||||||
|  |     int rc = ::close(m_fd); | ||||||
|  |     m_fd = -1; | ||||||
|  |     if (!flush_ok) { | ||||||
|  |         // Restore the original error from flush().
 | ||||||
|  |         errno = m_error; | ||||||
|  |     } | ||||||
|  |     return flush_ok && rc == 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool FILE::flush() | ||||||
|  | { | ||||||
|  |     if (m_mode & O_WRONLY && m_buffer.may_use()) { | ||||||
|  |         // When open for writing, write out all the buffered data.
 | ||||||
|  |         while (m_buffer.is_not_empty()) { | ||||||
|  |             bool ok = write_from_buffer(); | ||||||
|  |             if (!ok) | ||||||
|  |                 return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     if (m_mode & O_RDONLY) { | ||||||
|  |         // When open for reading, just drop the buffered data.
 | ||||||
|  |         m_buffer.drop(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ssize_t FILE::do_read(u8* data, size_t size) | ||||||
|  | { | ||||||
|  |     int nread = ::read(m_fd, data, size); | ||||||
|  | 
 | ||||||
|  |     if (nread < 0) { | ||||||
|  |         m_error = errno; | ||||||
|  |     } else if (nread == 0) { | ||||||
|  |         m_eof = true; | ||||||
|  |     } | ||||||
|  |     return nread; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ssize_t FILE::do_write(const u8* data, size_t size) | ||||||
|  | { | ||||||
|  |     int nwritten = ::write(m_fd, data, size); | ||||||
|  | 
 | ||||||
|  |     if (nwritten < 0) | ||||||
|  |         m_error = errno; | ||||||
|  |     return nwritten; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool FILE::read_into_buffer() | ||||||
|  | { | ||||||
|  |     m_buffer.realize(m_fd); | ||||||
|  | 
 | ||||||
|  |     size_t available_size; | ||||||
|  |     u8* data = m_buffer.begin_enqueue(available_size); | ||||||
|  |     // If we want to read, the buffer must have some space!
 | ||||||
|  |     ASSERT(available_size); | ||||||
|  | 
 | ||||||
|  |     ssize_t nread = do_read(data, available_size); | ||||||
|  | 
 | ||||||
|  |     if (nread <= 0) | ||||||
|  |         return false; | ||||||
|  | 
 | ||||||
|  |     m_buffer.did_enqueue(nread); | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool FILE::write_from_buffer() | ||||||
|  | { | ||||||
|  |     size_t size; | ||||||
|  |     const u8* data = m_buffer.begin_dequeue(size); | ||||||
|  |     // If we want to write, the buffer must have something in it!
 | ||||||
|  |     ASSERT(size); | ||||||
|  | 
 | ||||||
|  |     ssize_t nwritten = do_write(data, size); | ||||||
|  | 
 | ||||||
|  |     if (nwritten < 0) | ||||||
|  |         return false; | ||||||
|  | 
 | ||||||
|  |     m_buffer.did_dequeue(nwritten); | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | size_t FILE::read(u8* data, size_t size) | ||||||
|  | { | ||||||
|  |     size_t total_read = 0; | ||||||
|  | 
 | ||||||
|  |     while (size > 0) { | ||||||
|  |         size_t actual_size; | ||||||
|  | 
 | ||||||
|  |         if (m_buffer.may_use()) { | ||||||
|  |             // Let's see if the buffer has something queued for us.
 | ||||||
|  |             size_t queued_size; | ||||||
|  |             const u8* queued_data = m_buffer.begin_dequeue(queued_size); | ||||||
|  |             if (queued_size == 0) { | ||||||
|  |                 // Nothing buffered; we're going to have to read some.
 | ||||||
|  |                 bool read_some_more = read_into_buffer(); | ||||||
|  |                 if (read_some_more) { | ||||||
|  |                     // Great, now try this again.
 | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |                 return total_read; | ||||||
|  |             } | ||||||
|  |             actual_size = min(size, queued_size); | ||||||
|  |             memcpy(data, queued_data, actual_size); | ||||||
|  |             m_buffer.did_dequeue(actual_size); | ||||||
|  |         } else { | ||||||
|  |             // Read directly into the user buffer.
 | ||||||
|  |             ssize_t nread = do_read(data, size); | ||||||
|  |             if (nread <= 0) | ||||||
|  |                 return total_read; | ||||||
|  |             actual_size = nread; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         total_read += actual_size; | ||||||
|  |         data += actual_size; | ||||||
|  |         size -= actual_size; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return total_read; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | size_t FILE::write(const u8* data, size_t size) | ||||||
|  | { | ||||||
|  |     size_t total_written = 0; | ||||||
|  | 
 | ||||||
|  |     while (size > 0) { | ||||||
|  |         size_t actual_size; | ||||||
|  | 
 | ||||||
|  |         if (m_buffer.may_use()) { | ||||||
|  |             m_buffer.realize(m_fd); | ||||||
|  |             // Try writing into the buffer.
 | ||||||
|  |             size_t available_size; | ||||||
|  |             u8* buffer_data = m_buffer.begin_enqueue(available_size); | ||||||
|  |             if (available_size == 0) { | ||||||
|  |                 // There's no space in the buffer; we're going to free some.
 | ||||||
|  |                 bool freed_some_space = write_from_buffer(); | ||||||
|  |                 if (freed_some_space) { | ||||||
|  |                     // Great, now try this again.
 | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |                 return total_written; | ||||||
|  |             } | ||||||
|  |             actual_size = min(size, available_size); | ||||||
|  |             memcpy(buffer_data, data, actual_size); | ||||||
|  |             m_buffer.did_enqueue(actual_size); | ||||||
|  |             // See if we have to flush it.
 | ||||||
|  |             if (m_buffer.mode() == _IOLBF) { | ||||||
|  |                 bool includes_newline = memchr(data, '\n', actual_size); | ||||||
|  |                 if (includes_newline) | ||||||
|  |                     flush(); | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             // Write directly from the user buffer.
 | ||||||
|  |             ssize_t nwritten = do_write(data, size); | ||||||
|  |             if (nwritten < 0) | ||||||
|  |                 return total_written; | ||||||
|  |             actual_size = nwritten; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         total_written += actual_size; | ||||||
|  |         data += actual_size; | ||||||
|  |         size -= actual_size; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return total_written; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool FILE::gets(u8* data, size_t size) | ||||||
|  | { | ||||||
|  |     // gets() is a lot like read(), but it is different enough in how it
 | ||||||
|  |     // processes newlines and null-terminates the buffer that it deserves a
 | ||||||
|  |     // separate implementation.
 | ||||||
|  |     size_t total_read = 0; | ||||||
|  | 
 | ||||||
|  |     while (size > 1) { | ||||||
|  |         if (m_buffer.may_use()) { | ||||||
|  |             // Let's see if the buffer has something queued for us.
 | ||||||
|  |             size_t queued_size; | ||||||
|  |             const u8* queued_data = m_buffer.begin_dequeue(queued_size); | ||||||
|  |             if (queued_size == 0) { | ||||||
|  |                 // Nothing buffered; we're going to have to read some.
 | ||||||
|  |                 bool read_some_more = read_into_buffer(); | ||||||
|  |                 if (read_some_more) { | ||||||
|  |                     // Great, now try this again.
 | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |                 *data = 0; | ||||||
|  |                 return total_read > 0; | ||||||
|  |             } | ||||||
|  |             size_t actual_size = min(size - 1, queued_size); | ||||||
|  |             u8* newline = reinterpret_cast<u8*>(memchr(queued_data, '\n', actual_size)); | ||||||
|  |             if (newline) | ||||||
|  |                 actual_size = newline - queued_data + 1; | ||||||
|  |             memcpy(data, queued_data, actual_size); | ||||||
|  |             m_buffer.did_dequeue(actual_size); | ||||||
|  |             total_read += actual_size; | ||||||
|  |             data += actual_size; | ||||||
|  |             size -= actual_size; | ||||||
|  |             if (newline) | ||||||
|  |                 break; | ||||||
|  |         } else { | ||||||
|  |             // Sadly, we have to actually read these characters one by one.
 | ||||||
|  |             u8 byte; | ||||||
|  |             ssize_t nread = do_read(&byte, 1); | ||||||
|  |             if (nread <= 0) { | ||||||
|  |                 *data = 0; | ||||||
|  |                 return total_read > 0; | ||||||
|  |             } | ||||||
|  |             ASSERT(nread == 1); | ||||||
|  |             *data = byte; | ||||||
|  |             total_read++; | ||||||
|  |             data++; | ||||||
|  |             size--; | ||||||
|  |             if (byte == '\n') | ||||||
|  |                 break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     *data = 0; | ||||||
|  |     return total_read > 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int FILE::seek(long offset, int whence) | ||||||
|  | { | ||||||
|  |     bool ok = flush(); | ||||||
|  |     if (!ok) | ||||||
|  |         return -1; | ||||||
|  | 
 | ||||||
|  |     off_t off = lseek(m_fd, offset, whence); | ||||||
|  |     if (off < 0) { | ||||||
|  |         // Note: do not set m_error.
 | ||||||
|  |         return off; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     m_eof = false; | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | long FILE::tell() | ||||||
|  | { | ||||||
|  |     bool ok = flush(); | ||||||
|  |     if (!ok) | ||||||
|  |         return -1; | ||||||
|  | 
 | ||||||
|  |     return lseek(m_fd, 0, SEEK_CUR); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | FILE::Buffer::~Buffer() | ||||||
|  | { | ||||||
|  |     if (m_data_is_malloced) | ||||||
|  |         free(m_data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void FILE::Buffer::realize(int fd) | ||||||
|  | { | ||||||
|  |     if (m_mode == -1) | ||||||
|  |         m_mode = isatty(fd) ? _IOLBF : _IOFBF; | ||||||
|  | 
 | ||||||
|  |     if (m_mode != _IONBF && m_data == nullptr) { | ||||||
|  |         m_data = reinterpret_cast<u8*>(malloc(m_capacity)); | ||||||
|  |         m_data_is_malloced = true; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void FILE::Buffer::setbuf(u8* data, int mode, size_t size) | ||||||
|  | { | ||||||
|  |     drop(); | ||||||
|  |     m_mode = mode; | ||||||
|  |     if (data != nullptr) { | ||||||
|  |         m_data = data; | ||||||
|  |         m_capacity = size; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void FILE::Buffer::drop() | ||||||
|  | { | ||||||
|  |     if (m_data_is_malloced) { | ||||||
|  |         free(m_data); | ||||||
|  |         m_data = nullptr; | ||||||
|  |         m_data_is_malloced = false; | ||||||
|  |     } | ||||||
|  |     m_begin = m_end = 0; | ||||||
|  |     m_empty = true; | ||||||
|  |     m_ungotten = false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const u8* FILE::Buffer::begin_dequeue(size_t& available_size) const | ||||||
|  | { | ||||||
|  |     if (m_ungotten) { | ||||||
|  |         available_size = 1; | ||||||
|  |         return &m_unget_buffer; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (m_empty) { | ||||||
|  |         available_size = 0; | ||||||
|  |         return nullptr; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (m_begin < m_end) | ||||||
|  |         available_size = m_end - m_begin; | ||||||
|  |     else | ||||||
|  |         available_size = m_capacity - m_begin; | ||||||
|  | 
 | ||||||
|  |     return &m_data[m_begin]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void FILE::Buffer::did_dequeue(size_t actual_size) | ||||||
|  | { | ||||||
|  |     ASSERT(actual_size > 0); | ||||||
|  | 
 | ||||||
|  |     if (m_ungotten) { | ||||||
|  |         ASSERT(actual_size == 1); | ||||||
|  |         m_ungotten = false; | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     m_begin += actual_size; | ||||||
|  | 
 | ||||||
|  |     ASSERT(m_begin <= m_capacity); | ||||||
|  |     if (m_begin == m_capacity) { | ||||||
|  |         // Wrap around.
 | ||||||
|  |         m_begin = 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (m_begin == m_end) { | ||||||
|  |         m_empty = true; | ||||||
|  |         // As an optimization, move both pointers to the beginning of the
 | ||||||
|  |         // buffer, so that more consecutive space is available next time.
 | ||||||
|  |         m_begin = m_end = 0; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | u8* FILE::Buffer::begin_enqueue(size_t& available_size) const | ||||||
|  | { | ||||||
|  |     ASSERT(m_data != nullptr); | ||||||
|  | 
 | ||||||
|  |     if (m_begin < m_end || m_empty) | ||||||
|  |         available_size = m_capacity - m_end; | ||||||
|  |     else | ||||||
|  |         available_size = m_begin - m_end; | ||||||
|  | 
 | ||||||
|  |     return const_cast<u8*>(&m_data[m_end]); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void FILE::Buffer::did_enqueue(size_t actual_size) | ||||||
|  | { | ||||||
|  |     ASSERT(m_data != nullptr); | ||||||
|  |     ASSERT(actual_size > 0); | ||||||
|  | 
 | ||||||
|  |     m_end += actual_size; | ||||||
|  | 
 | ||||||
|  |     ASSERT(m_end <= m_capacity); | ||||||
|  |     if (m_end == m_capacity) { | ||||||
|  |         // Wrap around.
 | ||||||
|  |         m_end = 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     m_empty = false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool FILE::Buffer::enqueue_front(u8 byte) | ||||||
|  | { | ||||||
|  |     if (m_ungotten) { | ||||||
|  |         // Sorry, the place is already taken!
 | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     m_ungotten = true; | ||||||
|  |     m_unget_buffer = byte; | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| extern "C" { | extern "C" { | ||||||
| 
 | 
 | ||||||
| static FILE __default_streams[3]; | static u8 default_streams[3][sizeof(FILE)]; | ||||||
| FILE* stdin; | FILE* stdin = reinterpret_cast<FILE*>(&default_streams[0]); | ||||||
| FILE* stdout; | FILE* stdout = reinterpret_cast<FILE*>(&default_streams[1]); | ||||||
| FILE* stderr; | FILE* stderr = reinterpret_cast<FILE*>(&default_streams[2]); | ||||||
| 
 |  | ||||||
| void init_FILE(FILE& fp, int fd, int mode) |  | ||||||
| { |  | ||||||
|     fp.fd = fd; |  | ||||||
|     fp.buffer = fp.default_buffer; |  | ||||||
|     fp.buffer_size = BUFSIZ; |  | ||||||
|     fp.mode = mode; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static FILE* make_FILE(int fd) |  | ||||||
| { |  | ||||||
|     auto* fp = (FILE*)malloc(sizeof(FILE)); |  | ||||||
|     memset(fp, 0, sizeof(FILE)); |  | ||||||
|     init_FILE(*fp, fd, isatty(fd)); |  | ||||||
|     return fp; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| void __stdio_init() | void __stdio_init() | ||||||
| { | { | ||||||
|     stdin = &__default_streams[0]; |     new (stdin) FILE(0, O_RDONLY); | ||||||
|     stdout = &__default_streams[1]; |     new (stdout) FILE(1, O_WRONLY); | ||||||
|     stderr = &__default_streams[2]; |     new (stderr) FILE(2, O_WRONLY); | ||||||
|     init_FILE(*stdin, 0, isatty(0) ? _IOLBF : _IOFBF); |     stderr->setbuf(nullptr, _IONBF, 0); | ||||||
|     init_FILE(*stdout, 1, isatty(1) ? _IOLBF : _IOFBF); |  | ||||||
|     init_FILE(*stderr, 2, _IONBF); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int setvbuf(FILE* stream, char* buf, int mode, size_t size) | int setvbuf(FILE* stream, char* buf, int mode, size_t size) | ||||||
| { | { | ||||||
|  |     ASSERT(stream); | ||||||
|     if (mode != _IONBF && mode != _IOLBF && mode != _IOFBF) { |     if (mode != _IONBF && mode != _IOLBF && mode != _IOFBF) { | ||||||
|         errno = EINVAL; |         errno = EINVAL; | ||||||
|         return -1; |         return -1; | ||||||
|     } |     } | ||||||
|     stream->mode = mode; |     stream->setbuf(reinterpret_cast<u8*>(buf), mode, size); | ||||||
|     if (buf) { |  | ||||||
|         stream->buffer = buf; |  | ||||||
|         stream->buffer_size = size; |  | ||||||
|     } else { |  | ||||||
|         stream->buffer = stream->default_buffer; |  | ||||||
|         stream->buffer_size = BUFSIZ; |  | ||||||
|     } |  | ||||||
|     stream->buffer_index = 0; |  | ||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -103,14 +560,14 @@ void setlinebuf(FILE* stream) | ||||||
| 
 | 
 | ||||||
| int fileno(FILE* stream) | int fileno(FILE* stream) | ||||||
| { | { | ||||||
|     assert(stream); |     ASSERT(stream); | ||||||
|     return stream->fd; |     return stream->fileno(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int feof(FILE* stream) | int feof(FILE* stream) | ||||||
| { | { | ||||||
|     assert(stream); |     ASSERT(stream); | ||||||
|     return stream->eof; |     return stream->eof(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int fflush(FILE* stream) | int fflush(FILE* stream) | ||||||
|  | @ -119,47 +576,19 @@ int fflush(FILE* stream) | ||||||
|         dbg() << "FIXME: fflush(nullptr) should flush all open streams"; |         dbg() << "FIXME: fflush(nullptr) should flush all open streams"; | ||||||
|         return 0; |         return 0; | ||||||
|     } |     } | ||||||
|     if (!stream->buffer_index) |     return stream->flush() ? 0 : EOF; | ||||||
|         return 0; |  | ||||||
|     int rc = write(stream->fd, stream->buffer, stream->buffer_index); |  | ||||||
|     stream->buffer_index = 0; |  | ||||||
|     stream->error = 0; |  | ||||||
|     stream->eof = 0; |  | ||||||
|     stream->have_ungotten = false; |  | ||||||
|     stream->ungotten = 0; |  | ||||||
|     if (rc < 0) { |  | ||||||
|         stream->error = errno; |  | ||||||
|         return EOF; |  | ||||||
|     } |  | ||||||
|     return 0; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| char* fgets(char* buffer, int size, FILE* stream) | char* fgets(char* buffer, int size, FILE* stream) | ||||||
| { | { | ||||||
|     ASSERT(stream); |     ASSERT(stream); | ||||||
|     if (size == 0) { |     bool ok = stream->gets(reinterpret_cast<u8*>(buffer), size); | ||||||
|         return nullptr; |     return ok ? buffer : nullptr; | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     ssize_t nread = 0; |  | ||||||
|     while (nread < (size - 1)) { |  | ||||||
|         int ch = fgetc(stream); |  | ||||||
|         if (ch == EOF) |  | ||||||
|             break; |  | ||||||
|         buffer[nread++] = ch; |  | ||||||
|         if (ch == '\n') |  | ||||||
|             break; |  | ||||||
|     } |  | ||||||
|     if (nread) { |  | ||||||
|         buffer[nread] = '\0'; |  | ||||||
|         return buffer; |  | ||||||
|     } |  | ||||||
|     return nullptr; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int fgetc(FILE* stream) | int fgetc(FILE* stream) | ||||||
| { | { | ||||||
|     assert(stream); |     ASSERT(stream); | ||||||
|     char ch; |     char ch; | ||||||
|     size_t nread = fread(&ch, sizeof(char), 1, stream); |     size_t nread = fread(&ch, sizeof(char), 1, stream); | ||||||
|     if (nread == 1) |     if (nread == 1) | ||||||
|  | @ -230,28 +659,19 @@ ssize_t getline(char** lineptr, size_t* n, FILE* stream) | ||||||
| int ungetc(int c, FILE* stream) | int ungetc(int c, FILE* stream) | ||||||
| { | { | ||||||
|     ASSERT(stream); |     ASSERT(stream); | ||||||
|     if (c == EOF) |     bool ok = stream->ungetc(c); | ||||||
|         return EOF; |     return ok ? c : EOF; | ||||||
|     if (stream->have_ungotten) |  | ||||||
|         return EOF; |  | ||||||
|     stream->have_ungotten = true; |  | ||||||
|     stream->ungotten = c; |  | ||||||
|     stream->eof = false; |  | ||||||
|     return c; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int fputc(int ch, FILE* stream) | int fputc(int ch, FILE* stream) | ||||||
| { | { | ||||||
|     assert(stream); |     ASSERT(stream); | ||||||
|     assert(stream->buffer_index < stream->buffer_size); |     u8 byte = ch; | ||||||
|     stream->buffer[stream->buffer_index++] = ch; |     size_t nwritten = stream->write(&byte, 1); | ||||||
|     if (stream->buffer_index >= stream->buffer_size) |     if (nwritten == 0) | ||||||
|         fflush(stream); |  | ||||||
|     else if (stream->mode == _IONBF || (stream->mode == _IOLBF && ch == '\n')) |  | ||||||
|         fflush(stream); |  | ||||||
|     if (stream->eof || stream->error) |  | ||||||
|         return EOF; |         return EOF; | ||||||
|     return (u8)ch; |     ASSERT(nwritten == 1); | ||||||
|  |     return byte; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int putc(int ch, FILE* stream) | int putc(int ch, FILE* stream) | ||||||
|  | @ -266,11 +686,11 @@ int putchar(int ch) | ||||||
| 
 | 
 | ||||||
| int fputs(const char* s, FILE* stream) | int fputs(const char* s, FILE* stream) | ||||||
| { | { | ||||||
|     for (; *s; ++s) { |     ASSERT(stream); | ||||||
|         int rc = putc(*s, stream); |     size_t len = strlen(s); | ||||||
|         if (rc == EOF) |     size_t nwritten = stream->write(reinterpret_cast<const u8*>(s), len); | ||||||
|             return EOF; |     if (nwritten < len) | ||||||
|     } |         return EOF; | ||||||
|     return 1; |     return 1; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -284,88 +704,52 @@ int puts(const char* s) | ||||||
| 
 | 
 | ||||||
| void clearerr(FILE* stream) | void clearerr(FILE* stream) | ||||||
| { | { | ||||||
|     assert(stream); |     ASSERT(stream); | ||||||
|     stream->eof = false; |     stream->clear_err(); | ||||||
|     stream->error = 0; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int ferror(FILE* stream) | int ferror(FILE* stream) | ||||||
| { | { | ||||||
|     return stream->error; |     ASSERT(stream); | ||||||
|  |     return stream->error(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| size_t fread(void* ptr, size_t size, size_t nmemb, FILE* stream) | size_t fread(void* ptr, size_t size, size_t nmemb, FILE* stream) | ||||||
| { | { | ||||||
|     assert(stream); |     ASSERT(stream); | ||||||
|     if (!size) |     ASSERT(!Checked<size_t>::multiplication_would_overflow(size, nmemb)); | ||||||
|         return 0; |  | ||||||
| 
 | 
 | ||||||
|     ssize_t nread = 0; |     size_t nread = stream->read(reinterpret_cast<u8*>(ptr), size * nmemb); | ||||||
| 
 |  | ||||||
|     if (stream->have_ungotten) { |  | ||||||
|         // FIXME: Support ungotten character even if size != 1.
 |  | ||||||
|         ASSERT(size == 1); |  | ||||||
|         ((char*)ptr)[0] = stream->ungotten; |  | ||||||
|         stream->have_ungotten = false; |  | ||||||
|         --nmemb; |  | ||||||
|         if (!nmemb) |  | ||||||
|             return 1; |  | ||||||
|         ptr = &((char*)ptr)[1]; |  | ||||||
|         ++nread; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     ssize_t rc = read(stream->fd, ptr, nmemb * size); |  | ||||||
|     if (rc < 0) { |  | ||||||
|         stream->error = errno; |  | ||||||
|         return 0; |  | ||||||
|     } |  | ||||||
|     if (rc == 0) |  | ||||||
|         stream->eof = true; |  | ||||||
|     nread += rc; |  | ||||||
|     return nread / size; |     return nread / size; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| size_t fwrite(const void* ptr, size_t size, size_t nmemb, FILE* stream) | size_t fwrite(const void* ptr, size_t size, size_t nmemb, FILE* stream) | ||||||
| { | { | ||||||
|     assert(stream); |     ASSERT(stream); | ||||||
|     auto* bytes = (const u8*)ptr; |     ASSERT(!Checked<size_t>::multiplication_would_overflow(size, nmemb)); | ||||||
|     ssize_t nwritten = 0; | 
 | ||||||
|     for (size_t i = 0; i < (size * nmemb); ++i) { |     size_t nwritten = stream->write(reinterpret_cast<const u8*>(ptr), size * nmemb); | ||||||
|         int rc = fputc(bytes[i], stream); |  | ||||||
|         if (rc == EOF) |  | ||||||
|             break; |  | ||||||
|         ++nwritten; |  | ||||||
|     } |  | ||||||
|     return nwritten / size; |     return nwritten / size; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int fseek(FILE* stream, long offset, int whence) | int fseek(FILE* stream, long offset, int whence) | ||||||
| { | { | ||||||
|     assert(stream); |     ASSERT(stream); | ||||||
|     fflush(stream); |     return stream->seek(offset, whence); | ||||||
|     off_t off = lseek(stream->fd, offset, whence); |  | ||||||
|     if (off < 0) |  | ||||||
|         return off; |  | ||||||
|     stream->eof = false; |  | ||||||
|     stream->error = 0; |  | ||||||
|     stream->have_ungotten = false; |  | ||||||
|     stream->ungotten = 0; |  | ||||||
|     return 0; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| long ftell(FILE* stream) | long ftell(FILE* stream) | ||||||
| { | { | ||||||
|     assert(stream); |     ASSERT(stream); | ||||||
|     fflush(stream); |     return stream->tell(); | ||||||
|     return lseek(stream->fd, 0, SEEK_CUR); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int fgetpos(FILE* stream, fpos_t* pos) | int fgetpos(FILE* stream, fpos_t* pos) | ||||||
| { | { | ||||||
|     assert(stream); |     ASSERT(stream); | ||||||
|     assert(pos); |     ASSERT(pos); | ||||||
| 
 | 
 | ||||||
|     long val = ftell(stream); |     long val = stream->tell(); | ||||||
|     if (val == -1L) |     if (val == -1L) | ||||||
|         return 1; |         return 1; | ||||||
| 
 | 
 | ||||||
|  | @ -375,15 +759,16 @@ int fgetpos(FILE* stream, fpos_t* pos) | ||||||
| 
 | 
 | ||||||
| int fsetpos(FILE* stream, const fpos_t* pos) | int fsetpos(FILE* stream, const fpos_t* pos) | ||||||
| { | { | ||||||
|     assert(stream); |     ASSERT(stream); | ||||||
|     assert(pos); |     ASSERT(pos); | ||||||
|     return fseek(stream, (long) *pos, SEEK_SET); | 
 | ||||||
|  |     return stream->seek((long)*pos, SEEK_SET); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void rewind(FILE* stream) | void rewind(FILE* stream) | ||||||
| { | { | ||||||
|     ASSERT(stream); |     ASSERT(stream); | ||||||
|     int rc = fseek(stream, 0, SEEK_SET); |     int rc = stream->seek(0, SEEK_SET); | ||||||
|     ASSERT(rc == 0); |     ASSERT(rc == 0); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -492,7 +877,7 @@ void perror(const char* s) | ||||||
|     fprintf(stderr, "%s: %s\n", s, strerror(saved_errno)); |     fprintf(stderr, "%s: %s\n", s, strerror(saved_errno)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| FILE* fopen(const char* pathname, const char* mode) | static int parse_mode(const char* mode) | ||||||
| { | { | ||||||
|     int flags = 0; |     int flags = 0; | ||||||
|     // NOTE: rt is a non-standard mode which opens a file for read, explicitly
 |     // NOTE: rt is a non-standard mode which opens a file for read, explicitly
 | ||||||
|  | @ -510,13 +895,20 @@ FILE* fopen(const char* pathname, const char* mode) | ||||||
|     else if (!strcmp(mode, "a+") || !strcmp(mode, "ab+")) |     else if (!strcmp(mode, "a+") || !strcmp(mode, "ab+")) | ||||||
|         flags = O_RDWR | O_APPEND | O_CREAT; |         flags = O_RDWR | O_APPEND | O_CREAT; | ||||||
|     else { |     else { | ||||||
|         fprintf(stderr, "FIXME(LibC): fopen('%s', '%s')\n", pathname, mode); |         dbg() << "Unexpected mode _" << mode << "_"; | ||||||
|         ASSERT_NOT_REACHED(); |         ASSERT_NOT_REACHED(); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     return flags; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | FILE* fopen(const char* pathname, const char* mode) | ||||||
|  | { | ||||||
|  |     int flags = parse_mode(mode); | ||||||
|     int fd = open(pathname, flags, 0666); |     int fd = open(pathname, flags, 0666); | ||||||
|     if (fd < 0) |     if (fd < 0) | ||||||
|         return nullptr; |         return nullptr; | ||||||
|     return make_FILE(fd); |     return FILE::create(fd, flags); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| FILE* freopen(const char* pathname, const char* mode, FILE* stream) | FILE* freopen(const char* pathname, const char* mode, FILE* stream) | ||||||
|  | @ -529,20 +921,29 @@ FILE* freopen(const char* pathname, const char* mode, FILE* stream) | ||||||
| 
 | 
 | ||||||
| FILE* fdopen(int fd, const char* mode) | FILE* fdopen(int fd, const char* mode) | ||||||
| { | { | ||||||
|     UNUSED_PARAM(mode); |     int flags = parse_mode(mode); | ||||||
|     // FIXME: Verify that the mode matches how fd is already open.
 |     // FIXME: Verify that the mode matches how fd is already open.
 | ||||||
|     if (fd < 0) |     if (fd < 0) | ||||||
|         return nullptr; |         return nullptr; | ||||||
|     return make_FILE(fd); |     return FILE::create(fd, flags); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static inline bool is_default_stream(FILE* stream) | ||||||
|  | { | ||||||
|  |     return stream == stdin || stream == stdout || stream == stderr; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int fclose(FILE* stream) | int fclose(FILE* stream) | ||||||
| { | { | ||||||
|     fflush(stream); |     ASSERT(stream); | ||||||
|     int rc = close(stream->fd); |     bool ok = stream->close(); | ||||||
|     if (stream != &__default_streams[0] && stream != &__default_streams[1] && stream != &__default_streams[2]) |     ScopedValueRollback errno_restorer(errno); | ||||||
|  | 
 | ||||||
|  |     stream->~FILE(); | ||||||
|  |     if (!is_default_stream(stream)) | ||||||
|         free(stream); |         free(stream); | ||||||
|     return rc; | 
 | ||||||
|  |     return ok ? 0 : EOF; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int rename(const char* oldpath, const char* newpath) | int rename(const char* oldpath, const char* newpath) | ||||||
|  | @ -589,7 +990,13 @@ FILE* popen(const char* command, const char* type) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pid_t child_pid = fork(); |     pid_t child_pid = fork(); | ||||||
|     if (!child_pid) { |     if (child_pid < 0) { | ||||||
|  |         ScopedValueRollback rollback(errno); | ||||||
|  |         perror("fork"); | ||||||
|  |         close(pipe_fds[0]); | ||||||
|  |         close(pipe_fds[1]); | ||||||
|  |         return nullptr; | ||||||
|  |     } else if (child_pid == 0) { | ||||||
|         if (*type == 'r') { |         if (*type == 'r') { | ||||||
|             int rc = dup2(pipe_fds[1], STDOUT_FILENO); |             int rc = dup2(pipe_fds[1], STDOUT_FILENO); | ||||||
|             if (rc < 0) { |             if (rc < 0) { | ||||||
|  | @ -614,26 +1021,26 @@ FILE* popen(const char* command, const char* type) | ||||||
|         exit(1); |         exit(1); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     FILE* fp = nullptr; |     FILE* file = nullptr; | ||||||
|     if (*type == 'r') { |     if (*type == 'r') { | ||||||
|         fp = make_FILE(pipe_fds[0]); |         file = FILE::create(pipe_fds[0], O_RDONLY); | ||||||
|         close(pipe_fds[1]); |         close(pipe_fds[1]); | ||||||
|     } else if (*type == 'w') { |     } else if (*type == 'w') { | ||||||
|         fp = make_FILE(pipe_fds[1]); |         file = FILE::create(pipe_fds[1], O_WRONLY); | ||||||
|         close(pipe_fds[0]); |         close(pipe_fds[0]); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fp->popen_child = child_pid; |     file->set_popen_child(child_pid); | ||||||
|     return fp; |     return file; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int pclose(FILE* fp) | int pclose(FILE* stream) | ||||||
| { | { | ||||||
|     ASSERT(fp); |     ASSERT(stream); | ||||||
|     ASSERT(fp->popen_child != 0); |     ASSERT(stream->popen_child() != 0); | ||||||
| 
 | 
 | ||||||
|     int wstatus = 0; |     int wstatus = 0; | ||||||
|     int rc = waitpid(fp->popen_child, &wstatus, 0); |     int rc = waitpid(stream->popen_child(), &wstatus, 0); | ||||||
|     if (rc < 0) |     if (rc < 0) | ||||||
|         return rc; |         return rc; | ||||||
| 
 | 
 | ||||||
|  | @ -708,6 +1115,6 @@ FILE* tmpfile() | ||||||
|     // FIXME: instead of using this hack, implement with O_TMPFILE or similar
 |     // FIXME: instead of using this hack, implement with O_TMPFILE or similar
 | ||||||
|     unlink(tmp_path); |     unlink(tmp_path); | ||||||
| 
 | 
 | ||||||
|     return make_FILE(fd); |     return fdopen(fd, "rw"); | ||||||
| } | } | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Sergey Bugaev
						Sergey Bugaev