// This is part of the iostream library, providing input/output for C++. // Copyright (C) 1991, 1992 Per Bothner. // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public // License as published by the Free Software Foundation; either // version 2 of the License, or (at your option) any later version. // // This library 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 // Library General Public License for more details. // // You should have received a copy of the GNU Library General Public // License along with this library; if not, write to the Free // Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. #include "ioprivat.h" #include #include #include #include #include // An fstream can be in at most one of put mode, get mode, or putback mode. // Putback mode is a variant of get mode. // In a filebuf, there is only one current position, instead of two // separate get and put pointers. In get mode, the current posistion // is that of gptr(); in put mode that of pptr(). // The position in the buffer that corresponds to the position // in external file system is file_ptr(). // This is normally egptr(), except in putback mode, when it is _save_egptr. // If the field _fb._offset is >= 0, it gives the offset in // the file as a whole of the start of the buffer (base()). // PUT MODE: // If a filebuf is in put mode, pbase() is non-NULL and equal to base(). // Also, epptr() == ebuf(). // Also, eback() == gptr() && gptr() == egptr(). // The un-flushed character are those between pbase() and pptr(). // GET MODE: // If a filebuf is in get or putback mode, eback() != egptr(). // In get mode, the unread characters are between gptr() and egptr(). // The OS file position corresponds to that of egptr(). // PUTBACK MODE: // Putback mode is used to remember "excess" characters that have // been sputbackc'd in a separate putback buffer. // In putback mode, the get buffer points to the special putback buffer. // The unread characters are the characters between gptr() and egptr() // in the putback buffer, as well as the area between save_gptr() // and save_egptr(), which point into the original reserve buffer. // (The pointers save_gptr() and save_egptr() are the values // of gptr() and egptr() at the time putback mode was entered.) // The OS position corresponds to that of save_egptr(). // // LINE BUFFERED OUTPUT: // During line buffered output, pbase()==base() && epptr()==base(). // However, ptr() may be anywhere between base() and ebuf(). // This forces a call to filebuf::overflow(int C) on every put. // If there is more space in the buffer, and C is not a '\n', // then C is inserted, and pptr() incremented. // // UNBUFFERED STREAMS: // If a filebuf is unbuffered(), the _shortbuf[1] is used as the buffer. void filebuf::init() { _fb._fake = 0; _fb._offset = 0; _fb._fake = 0; xsetflags(_S_IS_FILEBUF); _fb._save_gptr = NULL; _link_in(); _fb._fileno = -1; } void streambuf::_un_link() { if (_flags & _S_LINKED) { streambuf **f; for (f = &_list_all; *f != NULL; f = &(*f)->xchain()) { if (*f == this) { *f = xchain(); break; } } _flags &= _S_LINKED; } } void streambuf::_link_in() { if (_flags & _S_LINKED) // Already linked. abort(); _flags |= _S_LINKED; xchain() = _list_all; _list_all = this; } filebuf::filebuf() { init(); } filebuf::filebuf(int fd) { init(); attach(fd); } filebuf::filebuf(int fd, char* p, size_t len) { init(); attach(fd); setbuf(p, len); } filebuf::~filebuf() { if (!(xflags() & _S_DELETE_DONT_CLOSE)) close(); _un_link(); } filebuf* filebuf::open(const char *filename, int mode, int prot) { if (is_open()) return NULL; int posix_mode; int read_write; if ((mode & (ios::in|ios::out)) == (ios::in|ios::out)) posix_mode = O_RDWR, read_write = _S_CAN_READ+_S_CAN_WRITE; else if (mode & (ios::out|ios::app)) posix_mode = O_WRONLY, read_write = _S_CAN_WRITE; else if (mode & (int)ios::in) posix_mode = O_RDONLY, read_write = _S_CAN_READ; else posix_mode = 0, read_write = 0; if ((mode & (int)ios::trunc) || mode == (int)ios::out) posix_mode |= O_TRUNC; if (mode & ios::app) posix_mode |= O_APPEND; if (!(mode & (int)ios::nocreate) && mode != ios::in) posix_mode |= O_CREAT; if (mode & (int)ios::noreplace) posix_mode |= O_EXCL; int fd = ::open(filename, posix_mode, prot); if (fd < 0) return NULL; _fb._fileno = fd; xsetflags(read_write); if (mode & ios::ate) { if (seekoff(0, ios::end) == EOF) return NULL; } return this; } filebuf* filebuf::open(const char *filename, const char *mode) { if (is_open()) return NULL; int oflags = 0, omode; int read_write; int oprot = 0666; switch (*mode++) { case 'r': omode = O_RDONLY; read_write = _S_CAN_READ; break; case 'w': omode = O_WRONLY; oflags = O_CREAT|O_TRUNC; read_write = _S_CAN_WRITE; break; case 'a': omode = O_WRONLY; oflags = O_CREAT|O_APPEND; read_write = _S_CAN_WRITE; break; default: errno = EINVAL; return NULL; } if (mode[0] == '+' || (mode[0] == 'b' && mode[1] == '+')) { omode = O_RDWR; read_write = _S_CAN_READ+_S_CAN_WRITE; } int fdesc = ::open(filename, omode|oflags, oprot); if (fdesc < 0) return NULL; _fb._fileno = fdesc; xsetflags(read_write); return this; } filebuf* filebuf::attach(int fd) { if (is_open()) return NULL; _fb._fileno = fd; xsetflags(_S_CAN_READ|_S_CAN_WRITE|_S_DELETE_DONT_CLOSE); return this; } int filebuf::overflow(int c) { if (pptr() == pbase() && c == EOF) return 0; if ((xflags() & _S_CAN_WRITE) == 0) // SET ERROR return EOF; if (is_reading()) { if (pptr() != gptr() && pptr() > pbase()) if (do_flush()) return EOF; setp(gptr(), ebuf()); setg(egptr(), egptr(), egptr()); } if (allocate() > 0) { if (xflags() & _S_LINE_BUF) setp(base(), base()); else setp(base(), ebuf()); setg(pbase(), pbase(), pbase()); } int flush_only; if (c == EOF) flush_only = 1, c = 0; else flush_only = 0; if (epptr() == pbase()) { // Line buffering if (pptr() < ebuf() && !flush_only) { xput_char(c); if (c != '\n') return (unsigned char)c; else flush_only = 1; } } size_t to_do = out_waiting(); if (to_do > 0) { char *ptr = pbase(); for (;;) { size_t count = sys_write(ptr, to_do); if (count == EOF) return EOF; _fb._offset += count; to_do -= count; if (to_do == 0) break; ptr += count; } if (xflags() & _S_LINE_BUF) setp(pbase(), pbase()); else setp(pbase(), epptr()); setg(egptr(), egptr(), egptr()); } if (flush_only) return c; else return sputc(c); } int filebuf::underflow() { #if 0 /* SysV does not make this test; take it out for compatibility */ if (fp->_flags & __SEOF) return (EOF); #endif if ((xflags() & _S_CAN_READ) == 0) // SET ERROR return EOF; retry: if (gptr() < egptr()) return *(unsigned char*)gptr(); if (_fb._save_gptr) { // Free old putback buffer. if (eback() != _fb._shortbuf) FREE_BUF(eback()); _fb._save_gptr = NULL; setg(base(), _fb._save_gptr, _fb._save_egptr); // Restore get area. goto retry; } allocate(); #if 0 /* if not already reading, have to be reading and writing */ if ((fp->_flags & __SRD) == 0) { if ((fp->_flags & __SRW) == 0) return (EOF); /* switch to reading */ if (fp->_flags & __SWR) { if (fflush(fp)) return (EOF); fp->_flags &= ~__SWR; fp->_w = 0; fp->_lbfsize = 0; } fp->_flags |= __SRD; } else { // We were reading. If there is an ungetc buffer, // we must have been reading from that. Drop it, // restoring the previous buffer (if any). If there // is anything in that buffer, return. if (HASUB(fp)) { FREEUB(fp); if ((fp->_r = fp->_ur) != 0) { fp->_p = fp->_up; return (0); } } } #endif if ((xflags() & _S_LINE_BUF) || unbuffered()) { // Flush all line buffered files before reading. streambuf::flush_all_linebuffered(); } if (pptr() > pbase()) if (do_flush()) return EOF; long count = sys_read(base(), ebuf() - base()); if (count <= 0) { if (count == 0) xsetflags(_S_EOF_SEEN); else xsetflags(_S_ERR_SEEN), count = 0; return EOF; } _fb._offset += count; setg(base(), base(), base() + count); return *(unsigned char*)gptr(); } int filebuf::pbackfail(int c) { if (pbase() != NULL) { // is writing() overflow(EOF); // FIXME: check for writing! } if (_fb._save_gptr == NULL) { // No putback buffer. _fb._save_gptr = gptr(); _fb._save_egptr = egptr(); setg(_fb._shortbuf, _fb._shortbuf+1, _fb._shortbuf+1); } else { // Increase size of existing putback buffer. size_t new_size; size_t old_size = egptr() - eback(); new_size = eback() == _fb._shortbuf ? 128 : 2 * old_size; char* new_buf = ALLOC_BUF(new_size); memcpy(new_buf+(new_size-old_size), eback(), old_size); if (eback() != _fb._shortbuf) FREE_BUF(eback()); setg(new_buf, new_buf+(new_size-old_size), new_buf+new_size); } gbump(-1); *gptr() = c; return (unsigned char)c; } int filebuf::do_flush() { if (egptr() != pbase()) { long new_pos = sys_seek(pbase()-egptr(), ios::cur); if (new_pos == -1) return EOF; } long to_do = pptr()-pbase(); char* ptr = pbase(); while (to_do > 0) { size_t count = sys_write(ptr, to_do); if (count == EOF) return EOF; if (_fb._offset >= 0) _fb._offset += count; to_do -= count; ptr += count; } setg(base(), pptr(), pptr()); setp(base(), base()); return 0; } int filebuf::sync() { // char* ptr = cur_ptr(); if (pptr() > pbase()) do_flush(); if (gptr() != egptr()) { if (sys_seek(gptr() - egptr(), ios::cur) == EOF) return EOF; } // FIXME: Handle putback mode! // setg(base(), ptr, ptr); return 0; } streampos filebuf::seekoff(streamoff offset, _seek_dir dir, int mode) { fpos_t result, new_offset, delta; long count; // Flush unwritten characters. // (This may do an unneeded write if we seek within the buffer. // But to be able to switch to reading, we would need to set // egptr to ptr. That can't be done in the current design, // which assumes file_ptr() is egptr. Anyway, since we probably // end up flushing when we close(), it doesn't make much difference.) if (pptr() > pbase()) do_flush(); if (unbuffered() || base() == NULL) goto dumb; // FIXME: What if buffer already allocated even though unbuffered() switch (dir) { case ios::cur: if (_fb._offset < 0) { _fb._offset = sys_seek(0, ios::cur); if (_fb._offset < 0) return EOF; } // Make offset absolute, assuming current pointer is file_ptr(). offset += _fb._offset; // Now adjust for unread/unflushed characters: // FIXME - this stuff needs more thought. #if 0 offset += cur_ptr() - file_ptr(); // FIXME - might be confused if there is a putback buffer. #else offset -= egptr() - gptr(); // Subtract unread characters, if any. if (_fb._save_gptr) // Putback mode offset -= _fb._save_egptr - _fb._save_gptr; // Normally, egptr()==pbase(), but if we earlier switched from // get to put mode, the following will (correctly) decrease offset. // However, it won't work if there is a putback buffer - FIXME! offset += pptr() - egptr(); // Add unflushed characters, in put mode. #endif dir = ios::beg; break; case ios::beg: break; case ios::end: struct stat st; if (sys_stat(&st) == 0 && (st.st_mode & S_IFMT) == S_IFREG) { offset += st.st_size; dir = ios::beg; } else goto dumb; } // At this point, dir==ios::beg. // FIXME: Handle case of there being a putback buffer! // If destination is within current buffer, optimize: if (_fb._offset >= 0) { fpos_t rel_offset = offset - _fb._offset + (file_ptr()-base()); // Offset relative to base(). if (rel_offset >= 0 && rel_offset <= egptr() - base()) { setg(base(), base() + rel_offset, egptr()); setp(base(), base()); return offset; } } // Try to seek to a block boundary, to improve kernal page management. new_offset = offset & ~(ebuf() - base() - 1); delta = offset - new_offset; if (delta > ebuf() - base()) { new_offset = offset; delta = 0; } result = sys_seek(new_offset, ios::beg); if (result < 0) return EOF; _fb._offset = result; count = sys_read(base(), ebuf()-base()); if (count < 0) return EOF; setg(base(), base(), base()+count); xflags(xflags() & ~ _S_EOF_SEEN); return result; dumb: if (_fb._save_gptr != NULL) { // Get rid of putback buffer. if (eback() != _fb._shortbuf) FREE_BUF(eback()); _fb._save_gptr = NULL; } result = sys_seek(offset, dir); if (result != EOF) { _flags &= ~_S_EOF_SEEN; } char* start = unbuffered() ? _fb._shortbuf : base(); setg(start, start, start); setp(start, start); return result; } filebuf* filebuf::close() { if (!is_open()) return NULL; // Flush. overflow(EOF); /* Free the buffer's storage. */ // Or should that be done only on destruction??? if (_base != NULL && !(_flags & _S_USER_BUF)) { FREE_BUF(_base); _base = NULL; } int status = sys_close(); _un_link(); _flags = _IO_MAGIC | _S_IS_FILEBUF; _fb._fileno = EOF; return status < 0 ? NULL : this; } long filebuf::sys_read(char* buf, size_t size) { return ::read(_fb._fileno, buf, size); } long filebuf::sys_seek(long offset, _seek_dir dir) { return ::lseek(fd(), offset, (int)dir); } long filebuf::sys_write(const void *buf, long n) { return ::write(fd(), buf, n); } int filebuf::sys_stat(void* st) { return ::_fstat(fd(), (struct stat*)st); } int filebuf::sys_close() { return ::close(fd()); } size_t filebuf::sputn(const char *s, size_t n) { // FIXME: OPTIMIZE THIS (specifically, when unbuffered()). return streambuf::sputn(s, n); } size_t filebuf::sgetn(char *s, size_t n) { // FIXME: OPTIMIZE THIS (specifically, when unbuffered()). return streambuf::sgetn(s, n); }