blob: 41a3e2bcc2fb1358949309cf48d048dc25c5e849 [file] [log] [blame]
/*
* Copyright 2000-2003 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code 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 General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*
*/
#include <stdio.h>
// This file is currently used for os/solaris/agent too. At some point in time
// the source will be reorganized to avoid these ifdefs.
#ifdef __sun
#include <string.h>
#include <inttypes.h>
#include <sys/byteorder.h>
#endif
#include "IOBuf.hpp"
// Formats for printing pointers
#ifdef _LP64
# define INTPTR_FORMAT "0x%016lx"
#else /* ! _LP64 */
# define INTPTR_FORMAT "0x%08lx"
#endif /* _LP64 */
// Uncomment the #define below to get messages on stderr
// #define DEBUGGING
IOBuf::IOBuf(int inLen, int outLen) {
inBuf = new Buffer(inLen);
outBuf = new Buffer(outLen);
fd = INVALID_SOCKET;
outHandle = NULL;
usingSocket = true;
reset();
}
IOBuf::~IOBuf() {
delete inBuf;
delete outBuf;
}
void
IOBuf::setSocket(SOCKET sock) {
fd = sock;
usingSocket = true;
}
// Reading/writing files is only needed and used on windows.
#ifdef WIN32
void
IOBuf::setOutputFileHandle(HANDLE handle) {
outHandle = handle;
usingSocket = false;
}
#endif
void
IOBuf::reset() {
gotDataLastTime = false;
state = TEXT_STATE;
binPos = 0;
binLength = 0;
}
IOBuf::ReadLineResult
IOBuf::tryReadLine() {
return doReadLine(false);
}
char*
IOBuf::readLine() {
ReadLineResult rr = doReadLine(true);
if (rr != RL_GOT_DATA) {
return NULL;
}
return getLine();
}
IOBuf::ReadLineResult
IOBuf::doReadLine(bool shouldWait) {
if (!usingSocket) {
return IOBuf::RL_ERROR;
}
if (gotDataLastTime) {
curLine.clear();
}
int c;
do {
c = readChar(shouldWait);
if (c >= 0) {
Action act = processChar((char) c);
if (act == GOT_LINE) {
curLine.push_back('\0');
gotDataLastTime = true;
return IOBuf::RL_GOT_DATA;
} else if (act == SKIP_EOL_CHAR) {
// Do nothing
} else {
curLine.push_back((char) c);
}
}
} while (shouldWait || c >= 0);
gotDataLastTime = false;
return IOBuf::RL_NO_DATA;
}
bool
IOBuf::flushImpl(bool moreDataToCome) {
int numWritten = 0;
#ifdef WIN32
// When running on Windows and using IOBufs for inter-process
// communication, we need to write metadata into the stream
// indicating how many bytes are coming down. Five bytes are written
// per flush() call, four containing the integer number of bytes
// coming (not including the five-byte header) and one (a 0 or 1)
// indicating whether there is more data coming.
if (!usingSocket) {
int numToWrite = outBuf->drainRemaining();
char moreToCome = (moreDataToCome ? 1 : 0);
DWORD numBytesWritten;
if (!WriteFile(outHandle, &numToWrite, sizeof(int), &numBytesWritten, NULL)) {
return false;
}
if (numBytesWritten != sizeof(int)) {
return false;
}
if (!WriteFile(outHandle, &moreToCome, 1, &numBytesWritten, NULL)) {
return false;
}
if (numBytesWritten != 1) {
return false;
}
}
#endif
while (outBuf->drainRemaining() != 0) {
#ifdef DEBUGGING
fprintf(stderr, "Flushing %d bytes\n", outBuf->drainRemaining());
#endif
if (usingSocket) {
numWritten = send(fd, outBuf->drainPos(), outBuf->drainRemaining(), 0);
} else {
#ifdef WIN32
DWORD numBytesWritten;
if (!WriteFile(outHandle, outBuf->drainPos(), outBuf->drainRemaining(), &numBytesWritten, NULL)) {
numWritten = -1;
} else {
numWritten = numBytesWritten;
}
#endif
}
if (numWritten != -1) {
#ifdef DEBUGGING
fprintf(stderr, "Flushed %d bytes\n", numWritten);
#endif
outBuf->incrDrainPos(numWritten);
} else {
return false;
}
}
outBuf->compact();
return true;
}
int
IOBuf::readChar(bool block) {
do {
int c = inBuf->readByte();
if (c >= 0) {
return c;
}
// See whether we need to compact the input buffer
if (inBuf->remaining() < inBuf->size() / 2) {
inBuf->compact();
}
// See whether socket is ready
fd_set fds;
FD_ZERO(&fds);
FD_SET(fd, &fds);
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 0;
if (block || select(1 + fd, &fds, NULL, NULL, &timeout) > 0) {
if (block || FD_ISSET(fd, &fds)) {
#ifdef DEBUGGING
int b = (block ? 1 : 0);
fprintf(stderr, "calling recv: block = %d\n", b);
#endif
// Read data from socket
int numRead = recv(fd, inBuf->fillPos(), inBuf->remaining(), 0);
if (numRead < 0) {
#ifdef DEBUGGING
fprintf(stderr, "recv failed\n");
#endif
return -1;
}
inBuf->incrFillPos(numRead);
}
}
} while (block);
return inBuf->readByte();
}
char*
IOBuf::getLine() {
#ifdef DEBUGGING
fprintf(stderr, "Returning (first 10 chars) \"%.10s\"\n", curLine.begin());
#endif
return curLine.begin();
}
bool
IOBuf::flush() {
return flushImpl(false);
}
bool
IOBuf::writeString(const char* str) {
int len = strlen(str);
if (len > outBuf->size()) {
return false;
}
if (len > outBuf->remaining()) {
if (!flushImpl(true)) {
return false;
}
}
// NOTE we do not copy the null terminator of the string.
strncpy(outBuf->fillPos(), str, len);
outBuf->incrFillPos(len);
return true;
}
bool
IOBuf::writeInt(int val) {
char buf[128];
sprintf(buf, "%d", val);
return writeString(buf);
}
bool
IOBuf::writeUnsignedInt(unsigned int val) {
char buf[128];
sprintf(buf, "%u", val);
return writeString(buf);
}
bool
IOBuf::writeBoolAsInt(bool val) {
if (val) {
return writeString("1");
} else {
return writeString("0");
}
}
bool
IOBuf::writeAddress(void* val) {
char buf[128];
sprintf(buf, INTPTR_FORMAT, val);
return writeString(buf);
}
bool
IOBuf::writeSpace() {
return writeString(" ");
}
bool
IOBuf::writeEOL() {
return writeString("\n\r");
}
bool
IOBuf::writeBinChar(char c) {
return writeBinBuf((char*) &c, sizeof(c));
}
bool
IOBuf::writeBinUnsignedShort(unsigned short i) {
i = htons(i);
return writeBinBuf((char*) &i, sizeof(i));
}
bool
IOBuf::writeBinUnsignedInt(unsigned int i) {
i = htonl(i);
return writeBinBuf((char*) &i, sizeof(i));
}
bool
IOBuf::writeBinBuf(char* buf, int size) {
while (size > 0) {
int spaceRemaining = outBuf->remaining();
if (spaceRemaining == 0) {
if (!flushImpl(true)) {
return false;
}
spaceRemaining = outBuf->remaining();
}
int toCopy = (size > spaceRemaining) ? spaceRemaining : size;
memcpy(outBuf->fillPos(), buf, toCopy);
outBuf->incrFillPos(toCopy);
buf += toCopy;
size -= toCopy;
if (size > 0) {
if (!flushImpl(true)) {
return false;
}
}
}
return true;
}
#ifdef WIN32
IOBuf::FillState
IOBuf::fillFromFileHandle(HANDLE fh, DWORD* numBytesRead) {
int totalToRead;
char moreToCome;
outBuf->compact();
DWORD numRead;
if (!ReadFile(fh, &totalToRead, sizeof(int), &numRead, NULL)) {
return FAILED;
}
if (numRead != sizeof(int)) {
return FAILED;
}
if (!ReadFile(fh, &moreToCome, 1, &numRead, NULL)) {
return FAILED;
}
if (numRead != 1) {
return FAILED;
}
if (outBuf->remaining() < totalToRead) {
return FAILED;
}
int tmp = totalToRead;
while (totalToRead > 0) {
if (!ReadFile(fh, outBuf->fillPos(), totalToRead, &numRead, NULL)) {
return FAILED;
}
outBuf->incrFillPos((int) numRead);
totalToRead -= numRead;
}
*numBytesRead = tmp;
return ((moreToCome == 0) ? DONE : MORE_DATA_PENDING);
}
#endif
bool
IOBuf::isBinEscapeChar(char c) {
return (c == '|');
}
IOBuf::Action
IOBuf::processChar(char c) {
Action action = NO_ACTION;
switch (state) {
case TEXT_STATE: {
// Looking for text char, bin escape char, or EOL
if (isBinEscapeChar(c)) {
#ifdef DEBUGGING
fprintf(stderr, "[a: '%c'] ", inBuf[0]);
#endif
binPos = 0;
#ifdef DEBUGGING
fprintf(stderr, "[b: '%c'] ", inBuf[0]);
#endif
binLength = 0;
#ifdef DEBUGGING
fprintf(stderr, "[c: '%c'] ", inBuf[0]);
#endif
state = BIN_STATE;
#ifdef DEBUGGING
fprintf(stderr, "[d: '%c'] ", inBuf[0]);
#endif
#ifdef DEBUGGING
fprintf(stderr, "\nSwitching to BIN_STATE\n");
#endif
} else if (isEOL(c)) {
state = EOL_STATE;
action = GOT_LINE;
#ifdef DEBUGGING
fprintf(stderr, "\nSwitching to EOL_STATE (GOT_LINE)\n");
#endif
}
#ifdef DEBUGGING
else {
fprintf(stderr, "'%c' ", c);
fflush(stderr);
}
#endif
break;
}
case BIN_STATE: {
// Seeking to finish read of input
if (binPos < 4) {
int cur = c & 0xFF;
binLength <<= 8;
binLength |= cur;
++binPos;
} else {
#ifdef DEBUGGING
fprintf(stderr, "Reading binary byte %d of %d\n",
binPos - 4, binLength);
#endif
++binPos;
if (binPos == 4 + binLength) {
state = TEXT_STATE;
#ifdef DEBUGGING
fprintf(stderr, "Switching to TEXT_STATE\n");
#endif
}
}
break;
}
case EOL_STATE: {
// More EOL characters just cause us to re-enter this state
if (isEOL(c)) {
action = SKIP_EOL_CHAR;
} else if (isBinEscapeChar(c)) {
binPos = 0;
binLength = 0;
state = BIN_STATE;
} else {
state = TEXT_STATE;
#ifdef DEBUGGING
fprintf(stderr, "'%c' ", c);
fflush(stderr);
#endif
}
break;
}
} // switch
return action;
}
bool
IOBuf::isEOL(char c) {
#ifdef WIN32
return ((c == '\n') || (c == '\r'));
#elif defined(__sun)
return c == '\n';
#else
#error Please port isEOL() to your platform
return false;
#endif
}