1
zero-epwing-go/linebuf.c

407 lines
9.8 KiB
C

/*
* copyright (c) 1997-2005 Motoyuki Kasahara
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the project nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <stdio.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include "linebuf.h"
/*
* Initialize `linebuffer'.
*/
void
initialize_line_buffer(Line_Buffer *line_buffer)
{
line_buffer->buffer[0] = '\0';
line_buffer->file = -1;
line_buffer->cache_length = 0;
line_buffer->timeout = 0;
}
/*
* Finalize `line_buffer'.
*/
void
finalize_line_buffer(Line_Buffer *line_buffer)
{
line_buffer->buffer[0] = '\0';
line_buffer->file = -1;
line_buffer->cache_length = 0;
line_buffer->timeout = 0;
}
/*
* Set timeout seconds.
*/
void
set_line_buffer_timeout(Line_Buffer *line_buffer, int timeout)
{
line_buffer->timeout = timeout;
}
/*
* Bind `file' to `line_buffer'.
*/
void
bind_file_to_line_buffer(Line_Buffer *line_buffer, int file)
{
if (line_buffer->file < 0)
initialize_line_buffer(line_buffer);
line_buffer->file = file;
}
/*
* Return file descriptor bound to `line_buffer'.
* Return -1 when no file is bound.
*/
int
file_bound_to_line_buffer(Line_Buffer *line_buffer)
{
return line_buffer->file;
}
/*
* Discard cache data in `line_buffer'.
*/
void
discard_cache_in_line_buffer(Line_Buffer *line_buffer)
{
line_buffer->cache_length = 0;
}
/*
* Get the length of cache data in `line_buffer'.
*/
size_t
cache_length_in_line_buffer(Line_Buffer *line_buffer)
{
return line_buffer->cache_length;
}
/*
* Read a line from the file bound to `line_buffer', and copy the read
* line to `line'. It reads at most `max_line_length' bytes.
*
* The function recognizes both "\n" and "\r\n" as end of line.
* "\n" or "\r\n" is not copied to `buffer', but "\0" is added, instead.
*
* The function returns the number of characters in the line, upon
* successful. It doesn't count "\n" or "\r\n" in the tail of the line,
* so that 0 is returned for an empty line, and the line length doesn't
* exceed one less than `max_line_length'.
*
* If EOF is received or an error occurs, -1 is returned. If the
* line is too long, `max_line_length' is returned.
*/
ssize_t
read_line_buffer(Line_Buffer *line_buffer, char *line, size_t max_line_length)
{
char *line_p;
char *newline;
size_t search_length;
size_t additional_length;
size_t line_length;
fd_set fdset;
struct timeval timeval;
int select_result;
ssize_t read_result;
/*
* Return -1 if no file is bound, or if `max_line_length' is 0.
*/
if (line_buffer->file < 0)
return -1;
if (max_line_length == 0)
return -1;
/*
* Read a file until newline is appeared.
*/
line_length = 0;
line_p = line;
for (;;) {
if (0 < line_buffer->cache_length) {
/*
* Find a newline in the cache data.
*/
if (max_line_length - line_length < line_buffer->cache_length)
search_length = max_line_length - line_length;
else
search_length = line_buffer->cache_length;
newline = (char *)memchr(line_buffer->buffer, '\n', search_length);
/*
* Append cache data in front of the newline to `line'.
*/
if (newline != NULL)
additional_length = newline - line_buffer->buffer + 1;
else
additional_length = search_length;
memcpy(line_p, line_buffer->buffer, additional_length);
line_p += additional_length;
line_length += additional_length;
line_buffer->cache_length -= additional_length;
/*
* If cache data not copied to `line' are remained in the
* buffer, we move them to the beginning of the buffer.
*/
memmove(line_buffer->buffer,
line_buffer->buffer + additional_length,
line_buffer->cache_length);
if (newline != NULL)
break;
}
/*
* Check for the length of the current line. Return if the
* line is too long.
*
* Note that the following conditional expression can be
* substituted to (line_buffer->cache_length != 0), because
* remained cache data mean that the line is too long.
*/
if (max_line_length <= line_length)
return line_length;
/*
* Call select().
*/
errno = 0;
FD_ZERO(&fdset);
FD_SET(line_buffer->file, &fdset);
if (line_buffer->timeout == 0) {
select_result = select(line_buffer->file + 1, &fdset, NULL, NULL,
NULL);
} else {
timeval.tv_sec = line_buffer->timeout;
timeval.tv_usec = 0;
select_result = select(line_buffer->file + 1, &fdset, NULL, NULL,
&timeval);
}
if (select_result < 0) {
if (errno == EINTR)
continue;
return -1;
} else if (select_result == 0) {
return -1;
}
/*
* Read from a file. (No cache data are remaind.)
*/
errno = 0;
read_result = recv(line_buffer->file, line_buffer->buffer,
LINEBUF_BUFFER_SIZE, 0);
if (read_result < 0) {
if (errno == EINTR)
continue;
return -1;
} else if (read_result == 0) {
if (line_length == 0) {
return -1;
}
return line_length;
}
line_buffer->cache_length += read_result;
}
/*
* Overwrite `\n' with `\0'.
*/
line_p--;
*line_p = '\0';
line_length--;
/*
* If the line is end with `\r\n', remove not only `\n' but `\r'.
*/
if (0 < line_length && *(line_p - 1) == '\r') {
line_p--;
*line_p = '\0';
line_length--;
}
return line_length;
}
/*
* Read just `stream_length' bytes from the file bound to `line_buffer',
* and copy the read bytes to `stream'.
*
* Unlike read_line_buffer(), it doesn't append `\0' to the read data,
* nor remove newline character in the read data.
*
* If it succeeds, the number of bytes actually read is returned.
* If EOF is received or an error occurs, -1 is returned.
*/
ssize_t
binary_read_line_buffer(Line_Buffer *line_buffer, char *stream,
size_t stream_length)
{
char *stream_p;
size_t done_length;
fd_set fdset;
struct timeval timeval;
int select_result;
ssize_t read_result;
/*
* Return -1 if no file is bound.
*/
if (line_buffer->file < 0)
return -1;
/*
* Return 0 if `stream_length' is 0.
*/
if (stream_length == 0)
return 0;
/*
* Test whether cache data are left in `line_buffer->buffer'.
* If they are, copy them to `stream'.
*/
stream_p = stream;
done_length = 0;
if (0 < line_buffer->cache_length) {
if (stream_length <= line_buffer->cache_length)
done_length = stream_length;
else
done_length = line_buffer->cache_length;
memcpy(stream_p, line_buffer->buffer, done_length);
stream_p += done_length;
line_buffer->cache_length -= done_length;
memmove(line_buffer->buffer,
line_buffer->buffer + done_length,
line_buffer->cache_length);
}
/*
* Read the file until the number of read bytes (`done_length') is
* reached to `stream_length'.
*/
while (done_length < stream_length) {
/*
* Call select().
*/
errno = 0;
FD_ZERO(&fdset);
FD_SET(line_buffer->file, &fdset);
if (line_buffer->timeout == 0) {
select_result = select(line_buffer->file + 1, NULL, &fdset, NULL,
NULL);
} else {
timeval.tv_sec = line_buffer->timeout;
timeval.tv_usec = 0;
select_result = select(line_buffer->file + 1, NULL, &fdset, NULL,
&timeval);
}
if (select_result < 0) {
if (errno == EINTR)
continue;
return -1;
} else if (select_result == 0) {
return -1;
}
/*
* Read from a file.
*/
errno = 0;
read_result = recv(line_buffer->file, stream_p,
stream_length - done_length, 0);
if (read_result < 0) {
if (errno == EINTR)
continue;
return read_result;
} else if (read_result == 0) {
if (done_length == 0) {
return -1;
}
return done_length;
}
stream_p += read_result;
done_length += read_result;
}
return stream_length;
}
/*
* Skip too long line read by read_line_buffer().
*
* If a line read by read_line_buffer() doesn't contain a newline
* character, the line is too long. This function reads and discards
* the rest of the line.
*
* If EOF is received or an error occurs, -1 is returned.
* Otherwise, 0 is returned.
*/
int
skip_line_buffer(Line_Buffer *line_buffer)
{
ssize_t line_length;
/*
* Read data until the end of the line is found.
*/
for (;;) {
line_length = read_line_buffer(line_buffer, line_buffer->buffer,
LINEBUF_BUFFER_SIZE);
if (line_length < 0)
return -1;
if (line_length < LINEBUF_BUFFER_SIZE)
break;
}
return 0;
}