diff options
Diffstat (limited to 'contrib/cvs/src/buffer.c')
-rw-r--r-- | contrib/cvs/src/buffer.c | 1980 |
1 files changed, 1980 insertions, 0 deletions
diff --git a/contrib/cvs/src/buffer.c b/contrib/cvs/src/buffer.c new file mode 100644 index 0000000..db2bea0 --- /dev/null +++ b/contrib/cvs/src/buffer.c @@ -0,0 +1,1980 @@ +/* + * Copyright (C) 1996-2005 The Free Software Foundation, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program 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 for more details. + */ + +/* Code for the buffer data structure. */ + +/* $FreeBSD$ */ + +#include <assert.h> +#include "cvs.h" +#include "buffer.h" + +#if defined (SERVER_SUPPORT) || defined (CLIENT_SUPPORT) + +#ifdef HAVE_WINSOCK_H +# include <winsock.h> +#else +# include <sys/socket.h> +#endif + +/* OS/2 doesn't have EIO. FIXME: this whole notion of turning + a different error into EIO strikes me as pretty dubious. */ +#if !defined (EIO) +#define EIO EBADPOS +#endif + +/* Linked list of available buffer_data structures. */ +static struct buffer_data *free_buffer_data; + +/* Local functions. */ +static void buf_default_memory_error PROTO ((struct buffer *)); +static void allocate_buffer_datas PROTO((void)); +static struct buffer_data *get_buffer_data PROTO((void)); + +/* Initialize a buffer structure. */ + +struct buffer * +buf_initialize (input, output, flush, block, shutdown, memory, closure) + int (*input) PROTO((void *, char *, int, int, int *)); + int (*output) PROTO((void *, const char *, int, int *)); + int (*flush) PROTO((void *)); + int (*block) PROTO((void *, int)); + int (*shutdown) PROTO((struct buffer *)); + void (*memory) PROTO((struct buffer *)); + void *closure; +{ + struct buffer *buf; + + buf = (struct buffer *) xmalloc (sizeof (struct buffer)); + buf->data = NULL; + buf->last = NULL; + buf->nonblocking = 0; + buf->input = input; + buf->output = output; + buf->flush = flush; + buf->block = block; + buf->shutdown = shutdown; + buf->memory_error = memory ? memory : buf_default_memory_error; + buf->closure = closure; + return buf; +} + +/* Free a buffer structure. */ + +void +buf_free (buf) + struct buffer *buf; +{ + if (buf->closure != NULL) + { + free (buf->closure); + buf->closure = NULL; + } + if (buf->data != NULL) + { + buf->last->next = free_buffer_data; + free_buffer_data = buf->data; + } + free (buf); +} + +/* Initialize a buffer structure which is not to be used for I/O. */ + +struct buffer * +buf_nonio_initialize (memory) + void (*memory) PROTO((struct buffer *)); +{ + return (buf_initialize + ((int (*) PROTO((void *, char *, int, int, int *))) NULL, + (int (*) PROTO((void *, const char *, int, int *))) NULL, + (int (*) PROTO((void *))) NULL, + (int (*) PROTO((void *, int))) NULL, + (int (*) PROTO((struct buffer *))) NULL, + memory, + (void *) NULL)); +} + +/* Default memory error handler. */ + +static void +buf_default_memory_error (buf) + struct buffer *buf; +{ + error (1, 0, "out of memory"); +} + +/* Allocate more buffer_data structures. */ + +static void +allocate_buffer_datas () +{ + struct buffer_data *alc; + char *space; + int i; + + /* Allocate buffer_data structures in blocks of 16. */ +#define ALLOC_COUNT (16) + + alc = xmalloc (ALLOC_COUNT * sizeof (struct buffer_data)); + space = (char *) valloc (ALLOC_COUNT * BUFFER_DATA_SIZE); + if (!space) + { + free (alc); + return; + } + for (i = 0; i < ALLOC_COUNT; i++, alc++, space += BUFFER_DATA_SIZE) + { + alc->next = free_buffer_data; + free_buffer_data = alc; + alc->text = space; + } +} + +/* Get a new buffer_data structure. */ + +static struct buffer_data * +get_buffer_data () +{ + struct buffer_data *ret; + + if (free_buffer_data == NULL) + { + allocate_buffer_datas (); + if (free_buffer_data == NULL) + return NULL; + } + + ret = free_buffer_data; + free_buffer_data = ret->next; + return ret; +} + + + +/* See whether a buffer and its file descriptor is empty. */ +int +buf_empty (buf) + struct buffer *buf; +{ + /* Try and read any data on the file descriptor first. + * We already know the descriptor is non-blocking. + */ + buf_input_data (buf, NULL); + return buf_empty_p (buf); +} + + + +/* See whether a buffer is empty. */ +int +buf_empty_p (buf) + struct buffer *buf; +{ + struct buffer_data *data; + + for (data = buf->data; data != NULL; data = data->next) + if (data->size > 0) + return 0; + return 1; +} + + + +#ifdef SERVER_FLOWCONTROL +/* + * Count how much data is stored in the buffer.. + * Note that each buffer is a xmalloc'ed chunk BUFFER_DATA_SIZE. + */ + +int +buf_count_mem (buf) + struct buffer *buf; +{ + struct buffer_data *data; + int mem = 0; + + for (data = buf->data; data != NULL; data = data->next) + mem += BUFFER_DATA_SIZE; + + return mem; +} +#endif /* SERVER_FLOWCONTROL */ + +/* Add data DATA of length LEN to BUF. */ + +void +buf_output (buf, data, len) + struct buffer *buf; + const char *data; + int len; +{ + if (buf->data != NULL + && (((buf->last->text + BUFFER_DATA_SIZE) + - (buf->last->bufp + buf->last->size)) + >= len)) + { + memcpy (buf->last->bufp + buf->last->size, data, len); + buf->last->size += len; + return; + } + + while (1) + { + struct buffer_data *newdata; + + newdata = get_buffer_data (); + if (newdata == NULL) + { + (*buf->memory_error) (buf); + return; + } + + if (buf->data == NULL) + buf->data = newdata; + else + buf->last->next = newdata; + newdata->next = NULL; + buf->last = newdata; + + newdata->bufp = newdata->text; + + if (len <= BUFFER_DATA_SIZE) + { + newdata->size = len; + memcpy (newdata->text, data, len); + return; + } + + newdata->size = BUFFER_DATA_SIZE; + memcpy (newdata->text, data, BUFFER_DATA_SIZE); + + data += BUFFER_DATA_SIZE; + len -= BUFFER_DATA_SIZE; + } + + /*NOTREACHED*/ +} + +/* Add a '\0' terminated string to BUF. */ + +void +buf_output0 (buf, string) + struct buffer *buf; + const char *string; +{ + buf_output (buf, string, strlen (string)); +} + +/* Add a single character to BUF. */ + +void +buf_append_char (buf, ch) + struct buffer *buf; + int ch; +{ + if (buf->data != NULL + && (buf->last->text + BUFFER_DATA_SIZE + != buf->last->bufp + buf->last->size)) + { + *(buf->last->bufp + buf->last->size) = ch; + ++buf->last->size; + } + else + { + char b; + + b = ch; + buf_output (buf, &b, 1); + } +} + +/* + * Send all the output we've been saving up. Returns 0 for success or + * errno code. If the buffer has been set to be nonblocking, this + * will just write until the write would block. + */ + +int +buf_send_output (buf) + struct buffer *buf; +{ + if (buf->output == NULL) + abort (); + + while (buf->data != NULL) + { + struct buffer_data *data; + + data = buf->data; + + if (data->size > 0) + { + int status, nbytes; + + status = (*buf->output) (buf->closure, data->bufp, data->size, + &nbytes); + if (status != 0) + { + /* Some sort of error. Discard the data, and return. */ + + buf->last->next = free_buffer_data; + free_buffer_data = buf->data; + buf->data = NULL; + buf->last = NULL; + + return status; + } + + if (nbytes != data->size) + { + /* Not all the data was written out. This is only + permitted in nonblocking mode. Adjust the buffer, + and return. */ + + assert (buf->nonblocking); + + data->size -= nbytes; + data->bufp += nbytes; + + return 0; + } + } + + buf->data = data->next; + data->next = free_buffer_data; + free_buffer_data = data; + } + + buf->last = NULL; + + return 0; +} + +/* + * Flush any data queued up in the buffer. If BLOCK is nonzero, then + * if the buffer is in nonblocking mode, put it into blocking mode for + * the duration of the flush. This returns 0 on success, or an error + * code. + */ + +int +buf_flush (buf, block) + struct buffer *buf; + int block; +{ + int nonblocking; + int status; + + if (buf->flush == NULL) + abort (); + + nonblocking = buf->nonblocking; + if (nonblocking && block) + { + status = set_block (buf); + if (status != 0) + return status; + } + + status = buf_send_output (buf); + if (status == 0) + status = (*buf->flush) (buf->closure); + + if (nonblocking && block) + { + int blockstat; + + blockstat = set_nonblock (buf); + if (status == 0) + status = blockstat; + } + + return status; +} + +/* + * Set buffer BUF to nonblocking I/O. Returns 0 for success or errno + * code. + */ + +int +set_nonblock (buf) + struct buffer *buf; +{ + int status; + + if (buf->nonblocking) + return 0; + if (buf->block == NULL) + abort (); + status = (*buf->block) (buf->closure, 0); + if (status != 0) + return status; + buf->nonblocking = 1; + return 0; +} + +/* + * Set buffer BUF to blocking I/O. Returns 0 for success or errno + * code. + */ + +int +set_block (buf) + struct buffer *buf; +{ + int status; + + if (! buf->nonblocking) + return 0; + if (buf->block == NULL) + abort (); + status = (*buf->block) (buf->closure, 1); + if (status != 0) + return status; + buf->nonblocking = 0; + return 0; +} + +/* + * Send a character count and some output. Returns errno code or 0 for + * success. + * + * Sending the count in binary is OK since this is only used on a pipe + * within the same system. + */ + +int +buf_send_counted (buf) + struct buffer *buf; +{ + int size; + struct buffer_data *data; + + size = 0; + for (data = buf->data; data != NULL; data = data->next) + size += data->size; + + data = get_buffer_data (); + if (data == NULL) + { + (*buf->memory_error) (buf); + return ENOMEM; + } + + data->next = buf->data; + buf->data = data; + if (buf->last == NULL) + buf->last = data; + + data->bufp = data->text; + data->size = sizeof (int); + + *((int *) data->text) = size; + + return buf_send_output (buf); +} + +/* + * Send a special count. COUNT should be negative. It will be + * handled speciallyi by buf_copy_counted. This function returns 0 or + * an errno code. + * + * Sending the count in binary is OK since this is only used on a pipe + * within the same system. + */ + +int +buf_send_special_count (buf, count) + struct buffer *buf; + int count; +{ + struct buffer_data *data; + + data = get_buffer_data (); + if (data == NULL) + { + (*buf->memory_error) (buf); + return ENOMEM; + } + + data->next = buf->data; + buf->data = data; + if (buf->last == NULL) + buf->last = data; + + data->bufp = data->text; + data->size = sizeof (int); + + *((int *) data->text) = count; + + return buf_send_output (buf); +} + +/* Append a list of buffer_data structures to an buffer. */ + +void +buf_append_data (buf, data, last) + struct buffer *buf; + struct buffer_data *data; + struct buffer_data *last; +{ + if (data != NULL) + { + if (buf->data == NULL) + buf->data = data; + else + buf->last->next = data; + buf->last = last; + } +} + +/* Append the data on one buffer to another. This removes the data + from the source buffer. */ + +void +buf_append_buffer (to, from) + struct buffer *to; + struct buffer *from; +{ + buf_append_data (to, from->data, from->last); + from->data = NULL; + from->last = NULL; +} + +/* + * Copy the contents of file F into buffer_data structures. We can't + * copy directly into an buffer, because we want to handle failure and + * succeess differently. Returns 0 on success, or -2 if out of + * memory, or a status code on error. Since the caller happens to + * know the size of the file, it is passed in as SIZE. On success, + * this function sets *RETP and *LASTP, which may be passed to + * buf_append_data. + */ + +int +buf_read_file (f, size, retp, lastp) + FILE *f; + long size; + struct buffer_data **retp; + struct buffer_data **lastp; +{ + int status; + + *retp = NULL; + *lastp = NULL; + + while (size > 0) + { + struct buffer_data *data; + int get; + + data = get_buffer_data (); + if (data == NULL) + { + status = -2; + goto error_return; + } + + if (*retp == NULL) + *retp = data; + else + (*lastp)->next = data; + data->next = NULL; + *lastp = data; + + data->bufp = data->text; + data->size = 0; + + if (size > BUFFER_DATA_SIZE) + get = BUFFER_DATA_SIZE; + else + get = size; + + errno = EIO; + if (fread (data->text, get, 1, f) != 1) + { + status = errno; + goto error_return; + } + + data->size += get; + size -= get; + } + + return 0; + + error_return: + if (*retp != NULL) + { + (*lastp)->next = free_buffer_data; + free_buffer_data = *retp; + } + return status; +} + +/* + * Copy the contents of file F into buffer_data structures. We can't + * copy directly into an buffer, because we want to handle failure and + * succeess differently. Returns 0 on success, or -2 if out of + * memory, or a status code on error. On success, this function sets + * *RETP and *LASTP, which may be passed to buf_append_data. + */ + +int +buf_read_file_to_eof (f, retp, lastp) + FILE *f; + struct buffer_data **retp; + struct buffer_data **lastp; +{ + int status; + + *retp = NULL; + *lastp = NULL; + + while (!feof (f)) + { + struct buffer_data *data; + int get, nread; + + data = get_buffer_data (); + if (data == NULL) + { + status = -2; + goto error_return; + } + + if (*retp == NULL) + *retp = data; + else + (*lastp)->next = data; + data->next = NULL; + *lastp = data; + + data->bufp = data->text; + data->size = 0; + + get = BUFFER_DATA_SIZE; + + errno = EIO; + nread = fread (data->text, 1, get, f); + if (nread == 0 && !feof (f)) + { + status = errno; + goto error_return; + } + + data->size = nread; + } + + return 0; + + error_return: + if (*retp != NULL) + { + (*lastp)->next = free_buffer_data; + free_buffer_data = *retp; + } + return status; +} + +/* Return the number of bytes in a chain of buffer_data structures. */ + +int +buf_chain_length (buf) + struct buffer_data *buf; +{ + int size = 0; + while (buf) + { + size += buf->size; + buf = buf->next; + } + return size; +} + +/* Return the number of bytes in a buffer. */ + +int +buf_length (buf) + struct buffer *buf; +{ + return buf_chain_length (buf->data); +} + +/* + * Read an arbitrary amount of data into an input buffer. The buffer + * will be in nonblocking mode, and we just grab what we can. Return + * 0 on success, or -1 on end of file, or -2 if out of memory, or an + * error code. If COUNTP is not NULL, *COUNTP is set to the number of + * bytes read. + */ + +int +buf_input_data (buf, countp) + struct buffer *buf; + int *countp; +{ + if (buf->input == NULL) + abort (); + + if (countp != NULL) + *countp = 0; + + while (1) + { + int get; + int status, nbytes; + + if (buf->data == NULL + || (buf->last->bufp + buf->last->size + == buf->last->text + BUFFER_DATA_SIZE)) + { + struct buffer_data *data; + + data = get_buffer_data (); + if (data == NULL) + { + (*buf->memory_error) (buf); + return -2; + } + + if (buf->data == NULL) + buf->data = data; + else + buf->last->next = data; + data->next = NULL; + buf->last = data; + + data->bufp = data->text; + data->size = 0; + } + + get = ((buf->last->text + BUFFER_DATA_SIZE) + - (buf->last->bufp + buf->last->size)); + + status = (*buf->input) (buf->closure, + buf->last->bufp + buf->last->size, + 0, get, &nbytes); + if (status != 0) + return status; + + buf->last->size += nbytes; + if (countp != NULL) + *countp += nbytes; + + if (nbytes < get) + { + /* If we did not fill the buffer, then presumably we read + all the available data. */ + return 0; + } + } + + /*NOTREACHED*/ +} + +/* + * Read a line (characters up to a \012) from an input buffer. (We + * use \012 rather than \n for the benefit of non Unix clients for + * which \n means something else). This returns 0 on success, or -1 + * on end of file, or -2 if out of memory, or an error code. If it + * succeeds, it sets *LINE to an allocated buffer holding the contents + * of the line. The trailing \012 is not included in the buffer. If + * LENP is not NULL, then *LENP is set to the number of bytes read; + * strlen may not work, because there may be embedded null bytes. + */ + +int +buf_read_line (buf, line, lenp) + struct buffer *buf; + char **line; + int *lenp; +{ + if (buf->input == NULL) + abort (); + + *line = NULL; + + while (1) + { + int len, finallen = 0; + struct buffer_data *data; + char *nl; + + /* See if there is a newline in BUF. */ + len = 0; + for (data = buf->data; data != NULL; data = data->next) + { + nl = memchr (data->bufp, '\012', data->size); + if (nl != NULL) + { + finallen = nl - data->bufp; + len += finallen; + break; + } + len += data->size; + } + + /* If we found a newline, copy the line into a memory buffer, + and remove it from BUF. */ + if (data != NULL) + { + char *p; + struct buffer_data *nldata; + + p = xmalloc (len + 1); + if (p == NULL) + return -2; + *line = p; + + nldata = data; + data = buf->data; + while (data != nldata) + { + struct buffer_data *next; + + memcpy (p, data->bufp, data->size); + p += data->size; + next = data->next; + data->next = free_buffer_data; + free_buffer_data = data; + data = next; + } + + memcpy (p, data->bufp, finallen); + p[finallen] = '\0'; + + data->size -= finallen + 1; + data->bufp = nl + 1; + buf->data = data; + + if (lenp != NULL) + *lenp = len; + + return 0; + } + + /* Read more data until we get a newline. */ + while (1) + { + int size, status, nbytes; + char *mem; + + if (buf->data == NULL + || (buf->last->bufp + buf->last->size + == buf->last->text + BUFFER_DATA_SIZE)) + { + data = get_buffer_data (); + if (data == NULL) + { + (*buf->memory_error) (buf); + return -2; + } + + if (buf->data == NULL) + buf->data = data; + else + buf->last->next = data; + data->next = NULL; + buf->last = data; + + data->bufp = data->text; + data->size = 0; + } + + mem = buf->last->bufp + buf->last->size; + size = (buf->last->text + BUFFER_DATA_SIZE) - mem; + + /* We need to read at least 1 byte. We can handle up to + SIZE bytes. This will only be efficient if the + underlying communication stream does its own buffering, + or is clever about getting more than 1 byte at a time. */ + status = (*buf->input) (buf->closure, mem, 1, size, &nbytes); + if (status != 0) + return status; + + buf->last->size += nbytes; + + /* Optimize slightly to avoid an unnecessary call to + memchr. */ + if (nbytes == 1) + { + if (*mem == '\012') + break; + } + else + { + if (memchr (mem, '\012', nbytes) != NULL) + break; + } + } + } +} + +/* + * Extract data from the input buffer BUF. This will read up to WANT + * bytes from the buffer. It will set *RETDATA to point at the bytes, + * and set *GOT to the number of bytes to be found there. Any buffer + * call which uses BUF may change the contents of the buffer at *DATA, + * so the data should be fully processed before any further calls are + * made. This returns 0 on success, or -1 on end of file, or -2 if + * out of memory, or an error code. + */ + +int +buf_read_data (buf, want, retdata, got) + struct buffer *buf; + int want; + char **retdata; + int *got; +{ + if (buf->input == NULL) + abort (); + + while (buf->data != NULL && buf->data->size == 0) + { + struct buffer_data *next; + + next = buf->data->next; + buf->data->next = free_buffer_data; + free_buffer_data = buf->data; + buf->data = next; + if (next == NULL) + buf->last = NULL; + } + + if (buf->data == NULL) + { + struct buffer_data *data; + int get, status, nbytes; + + data = get_buffer_data (); + if (data == NULL) + { + (*buf->memory_error) (buf); + return -2; + } + + buf->data = data; + buf->last = data; + data->next = NULL; + data->bufp = data->text; + data->size = 0; + + if (want < BUFFER_DATA_SIZE) + get = want; + else + get = BUFFER_DATA_SIZE; + status = (*buf->input) (buf->closure, data->bufp, get, + BUFFER_DATA_SIZE, &nbytes); + if (status != 0) + return status; + + data->size = nbytes; + } + + *retdata = buf->data->bufp; + if (want < buf->data->size) + { + *got = want; + buf->data->size -= want; + buf->data->bufp += want; + } + else + { + *got = buf->data->size; + buf->data->size = 0; + } + + return 0; +} + +/* + * Copy lines from an input buffer to an output buffer. This copies + * all complete lines (characters up to a newline) from INBUF to + * OUTBUF. Each line in OUTBUF is preceded by the character COMMAND + * and a space. + */ + +void +buf_copy_lines (outbuf, inbuf, command) + struct buffer *outbuf; + struct buffer *inbuf; + int command; +{ + while (1) + { + struct buffer_data *data; + struct buffer_data *nldata; + char *nl; + int len; + + /* See if there is a newline in INBUF. */ + nldata = NULL; + nl = NULL; + for (data = inbuf->data; data != NULL; data = data->next) + { + nl = memchr (data->bufp, '\n', data->size); + if (nl != NULL) + { + nldata = data; + break; + } + } + + if (nldata == NULL) + { + /* There are no more lines in INBUF. */ + return; + } + + /* Put in the command. */ + buf_append_char (outbuf, command); + buf_append_char (outbuf, ' '); + + if (inbuf->data != nldata) + { + /* + * Simply move over all the buffers up to the one containing + * the newline. + */ + for (data = inbuf->data; data->next != nldata; data = data->next) + ; + data->next = NULL; + buf_append_data (outbuf, inbuf->data, data); + inbuf->data = nldata; + } + + /* + * If the newline is at the very end of the buffer, just move + * the buffer onto OUTBUF. Otherwise we must copy the data. + */ + len = nl + 1 - nldata->bufp; + if (len == nldata->size) + { + inbuf->data = nldata->next; + if (inbuf->data == NULL) + inbuf->last = NULL; + + nldata->next = NULL; + buf_append_data (outbuf, nldata, nldata); + } + else + { + buf_output (outbuf, nldata->bufp, len); + nldata->bufp += len; + nldata->size -= len; + } + } +} + +/* + * Copy counted data from one buffer to another. The count is an + * integer, host size, host byte order (it is only used across a + * pipe). If there is enough data, it should be moved over. If there + * is not enough data, it should remain on the original buffer. A + * negative count is a special case. if one is seen, *SPECIAL is set + * to the (negative) count value and no additional data is gathered + * from the buffer; normally *SPECIAL is set to 0. This function + * returns the number of bytes it needs to see in order to actually + * copy something over. + */ + +int +buf_copy_counted (outbuf, inbuf, special) + struct buffer *outbuf; + struct buffer *inbuf; + int *special; +{ + *special = 0; + + while (1) + { + struct buffer_data *data; + int need; + union + { + char intbuf[sizeof (int)]; + int i; + } u; + char *intp; + int count; + struct buffer_data *start; + int startoff; + struct buffer_data *stop; + int stopwant; + + /* See if we have enough bytes to figure out the count. */ + need = sizeof (int); + intp = u.intbuf; + for (data = inbuf->data; data != NULL; data = data->next) + { + if (data->size >= need) + { + memcpy (intp, data->bufp, need); + break; + } + memcpy (intp, data->bufp, data->size); + intp += data->size; + need -= data->size; + } + if (data == NULL) + { + /* We don't have enough bytes to form an integer. */ + return need; + } + + count = u.i; + start = data; + startoff = need; + + if (count < 0) + { + /* A negative COUNT is a special case meaning that we + don't need any further information. */ + stop = start; + stopwant = 0; + } + else + { + /* + * We have an integer in COUNT. We have gotten all the + * data from INBUF in all buffers before START, and we + * have gotten STARTOFF bytes from START. See if we have + * enough bytes remaining in INBUF. + */ + need = count - (start->size - startoff); + if (need <= 0) + { + stop = start; + stopwant = count; + } + else + { + for (data = start->next; data != NULL; data = data->next) + { + if (need <= data->size) + break; + need -= data->size; + } + if (data == NULL) + { + /* We don't have enough bytes. */ + return need; + } + stop = data; + stopwant = need; + } + } + + /* + * We have enough bytes. Free any buffers in INBUF before + * START, and remove STARTOFF bytes from START, so that we can + * forget about STARTOFF. + */ + start->bufp += startoff; + start->size -= startoff; + + if (start->size == 0) + start = start->next; + + if (stop->size == stopwant) + { + stop = stop->next; + stopwant = 0; + } + + while (inbuf->data != start) + { + data = inbuf->data; + inbuf->data = data->next; + data->next = free_buffer_data; + free_buffer_data = data; + } + + /* If COUNT is negative, set *SPECIAL and get out now. */ + if (count < 0) + { + *special = count; + return 0; + } + + /* + * We want to copy over the bytes from START through STOP. We + * only want STOPWANT bytes from STOP. + */ + + if (start != stop) + { + /* Attach the buffers from START through STOP to OUTBUF. */ + for (data = start; data->next != stop; data = data->next) + ; + inbuf->data = stop; + data->next = NULL; + buf_append_data (outbuf, start, data); + } + + if (stopwant > 0) + { + buf_output (outbuf, stop->bufp, stopwant); + stop->bufp += stopwant; + stop->size -= stopwant; + } + } + + /*NOTREACHED*/ +} + +/* Shut down a buffer. This returns 0 on success, or an errno code. */ + +int +buf_shutdown (buf) + struct buffer *buf; +{ + if (buf->shutdown) + return (*buf->shutdown) (buf); + return 0; +} + + + +/* The simplest type of buffer is one built on top of a stdio FILE. + For simplicity, and because it is all that is required, we do not + implement setting this type of buffer into nonblocking mode. The + closure field is just a FILE *. */ + +static int stdio_buffer_input PROTO((void *, char *, int, int, int *)); +static int stdio_buffer_output PROTO((void *, const char *, int, int *)); +static int stdio_buffer_flush PROTO((void *)); +static int stdio_buffer_shutdown PROTO((struct buffer *buf)); + + + +/* Initialize a buffer built on a stdio FILE. */ +struct stdio_buffer_closure +{ + FILE *fp; + int child_pid; +}; + + + +struct buffer * +stdio_buffer_initialize (fp, child_pid, input, memory) + FILE *fp; + int child_pid; + int input; + void (*memory) PROTO((struct buffer *)); +{ + struct stdio_buffer_closure *bc = xmalloc (sizeof (*bc)); + + bc->fp = fp; + bc->child_pid = child_pid; + + return buf_initialize (input ? stdio_buffer_input : NULL, + input ? NULL : stdio_buffer_output, + input ? NULL : stdio_buffer_flush, + (int (*) PROTO((void *, int))) NULL, + stdio_buffer_shutdown, + memory, + (void *) bc); +} + +/* Return the file associated with a stdio buffer. */ +FILE * +stdio_buffer_get_file (buf) + struct buffer *buf; +{ + struct stdio_buffer_closure *bc; + + assert(buf->shutdown == stdio_buffer_shutdown); + + bc = (struct stdio_buffer_closure *) buf->closure; + + return(bc->fp); +} + +/* The buffer input function for a buffer built on a stdio FILE. */ + +static int +stdio_buffer_input (closure, data, need, size, got) + void *closure; + char *data; + int need; + int size; + int *got; +{ + struct stdio_buffer_closure *bc = (struct stdio_buffer_closure *) closure; + int nbytes; + + /* Since stdio does its own buffering, we don't worry about + getting more bytes than we need. */ + + if (need == 0 || need == 1) + { + int ch; + + ch = getc (bc->fp); + + if (ch == EOF) + { + if (feof (bc->fp)) + return -1; + else if (errno == 0) + return EIO; + else + return errno; + } + + *data = ch; + *got = 1; + return 0; + } + + nbytes = fread (data, 1, need, bc->fp); + + if (nbytes == 0) + { + *got = 0; + if (feof (bc->fp)) + return -1; + else if (errno == 0) + return EIO; + else + return errno; + } + + *got = nbytes; + + return 0; +} + +/* The buffer output function for a buffer built on a stdio FILE. */ + +static int +stdio_buffer_output (closure, data, have, wrote) + void *closure; + const char *data; + int have; + int *wrote; +{ + struct stdio_buffer_closure *bc = (struct stdio_buffer_closure *) closure; + + *wrote = 0; + + while (have > 0) + { + int nbytes; + + nbytes = fwrite (data, 1, have, bc->fp); + + if (nbytes != have) + { + if (errno == 0) + return EIO; + else + return errno; + } + + *wrote += nbytes; + have -= nbytes; + data += nbytes; + } + + return 0; +} + + + +/* The buffer flush function for a buffer built on a stdio FILE. */ +static int +stdio_buffer_flush (closure) + void *closure; +{ + struct stdio_buffer_closure *bc = (struct stdio_buffer_closure *) closure; + + if (fflush (bc->fp) != 0) + { + if (errno == 0) + return EIO; + else + return errno; + } + + return 0; +} + + + +static int +stdio_buffer_shutdown (buf) + struct buffer *buf; +{ + struct stdio_buffer_closure *bc = buf->closure; + struct stat s; + int closefp, statted; + + /* Must be a pipe or a socket. What could go wrong? + * Well, apparently for disconnected clients under AIX, the + * fstat() will return -1 on the server if the client has gone + * away. + */ + if (fstat(fileno(bc->fp), &s) == -1) statted = 0; + else statted = 1; + closefp = statted; + + /* Flush the buffer if we can */ + if (buf->flush) + { + buf_flush (buf, 1); + buf->flush = NULL; + } + + if (buf->input) + { + /* There used to be a check here for unread data in the buffer of on + * the pipe, but it was deemed unnecessary and possibly dangerous. In + * some sense it could be second-guessing the caller who requested it + * closed, as well. + */ + +# ifdef SHUTDOWN_SERVER + if (current_parsed_root->method != server_method) +# endif +# ifndef NO_SOCKET_TO_FD + { + /* shutdown() sockets */ + if (statted && S_ISSOCK (s.st_mode)) + shutdown (fileno (bc->fp), 0); + } +# endif /* NO_SOCKET_TO_FD */ +# ifdef START_RSH_WITH_POPEN_RW + /* Can't be set with SHUTDOWN_SERVER defined */ + else if (pclose (bc->fp) == EOF) + { + error (0, errno, "closing connection to %s", + current_parsed_root->hostname); + closefp = 0; + } +# endif /* START_RSH_WITH_POPEN_RW */ + + buf->input = NULL; + } + else if (buf->output) + { +# ifdef SHUTDOWN_SERVER + /* FIXME: Should have a SHUTDOWN_SERVER_INPUT & + * SHUTDOWN_SERVER_OUTPUT + */ + if (current_parsed_root->method == server_method) + SHUTDOWN_SERVER (fileno (bc->fp)); + else +# endif +# ifndef NO_SOCKET_TO_FD + /* shutdown() sockets */ + if (statted && S_ISSOCK (s.st_mode)) + shutdown (fileno (bc->fp), 1); +# else + { + /* I'm not sure I like this empty block, but the alternative + * is a another nested NO_SOCKET_TO_FD switch above. + */ + } +# endif /* NO_SOCKET_TO_FD */ + + buf->output = NULL; + } + + if (statted && closefp && fclose (bc->fp) == EOF) + { + if (server_active) + { + /* Syslog this? */ + } +# ifdef CLIENT_SUPPORT + /* We are already closing the connection. + * On error, print a warning and try to + * continue to avoid infinte loops. + */ + else + error (0, errno, + "closing down connection to %s", + current_parsed_root->hostname); +# endif /* CLIENT_SUPPORT */ + } + + /* If we were talking to a process, make sure it exited */ + if (bc->child_pid) + { + int w; + + do + w = waitpid (bc->child_pid, (int *) 0, 0); + while (w == -1 && errno == EINTR); + + /* We are already closing the connection. + * On error, print a warning and try to + * continue to avoid infinte loops. + */ + if (w == -1) + error (0, errno, "waiting for process %d", bc->child_pid); + } + return 0; +} + + + +/* Certain types of communication input and output data in packets, + where each packet is translated in some fashion. The packetizing + buffer type supports that, given a buffer which handles lower level + I/O and a routine to translate the data in a packet. + + This code uses two bytes for the size of a packet, so packets are + restricted to 65536 bytes in total. + + The translation functions should just translate; they may not + significantly increase or decrease the amount of data. The actual + size of the initial data is part of the translated data. The + output translation routine may add up to PACKET_SLOP additional + bytes, and the input translation routine should shrink the data + correspondingly. */ + +#define PACKET_SLOP (100) + +/* This structure is the closure field of a packetizing buffer. */ + +struct packetizing_buffer +{ + /* The underlying buffer. */ + struct buffer *buf; + /* The input translation function. Exactly one of inpfn and outfn + will be NULL. The input translation function should + untranslate the data in INPUT, storing the result in OUTPUT. + SIZE is the amount of data in INPUT, and is also the size of + OUTPUT. This should return 0 on success, or an errno code. */ + int (*inpfn) PROTO((void *fnclosure, const char *input, char *output, + int size)); + /* The output translation function. This should translate the + data in INPUT, storing the result in OUTPUT. The first two + bytes in INPUT will be the size of the data, and so will SIZE. + This should set *TRANSLATED to the amount of translated data in + OUTPUT. OUTPUT is large enough to hold SIZE + PACKET_SLOP + bytes. This should return 0 on success, or an errno code. */ + int (*outfn) PROTO((void *fnclosure, const char *input, char *output, + int size, int *translated)); + /* A closure for the translation function. */ + void *fnclosure; + /* For an input buffer, we may have to buffer up data here. */ + /* This is non-zero if the buffered data has been translated. + Otherwise, the buffered data has not been translated, and starts + with the two byte packet size. */ + int translated; + /* The amount of buffered data. */ + int holdsize; + /* The buffer allocated to hold the data. */ + char *holdbuf; + /* The size of holdbuf. */ + int holdbufsize; + /* If translated is set, we need another data pointer to track + where we are in holdbuf. If translated is clear, then this + pointer is not used. */ + char *holddata; +}; + +static int packetizing_buffer_input PROTO((void *, char *, int, int, int *)); +static int packetizing_buffer_output PROTO((void *, const char *, int, int *)); +static int packetizing_buffer_flush PROTO((void *)); +static int packetizing_buffer_block PROTO((void *, int)); +static int packetizing_buffer_shutdown PROTO((struct buffer *)); + +/* Create a packetizing buffer. */ + +struct buffer * +packetizing_buffer_initialize (buf, inpfn, outfn, fnclosure, memory) + struct buffer *buf; + int (*inpfn) PROTO ((void *, const char *, char *, int)); + int (*outfn) PROTO ((void *, const char *, char *, int, int *)); + void *fnclosure; + void (*memory) PROTO((struct buffer *)); +{ + struct packetizing_buffer *pb; + + pb = (struct packetizing_buffer *) xmalloc (sizeof *pb); + memset (pb, 0, sizeof *pb); + + pb->buf = buf; + pb->inpfn = inpfn; + pb->outfn = outfn; + pb->fnclosure = fnclosure; + + if (inpfn != NULL) + { + /* Add PACKET_SLOP to handle larger translated packets, and + add 2 for the count. This buffer is increased if + necessary. */ + pb->holdbufsize = BUFFER_DATA_SIZE + PACKET_SLOP + 2; + pb->holdbuf = xmalloc (pb->holdbufsize); + } + + return buf_initialize (inpfn != NULL ? packetizing_buffer_input : NULL, + inpfn != NULL ? NULL : packetizing_buffer_output, + inpfn != NULL ? NULL : packetizing_buffer_flush, + packetizing_buffer_block, + packetizing_buffer_shutdown, + memory, + pb); +} + +/* Input data from a packetizing buffer. */ + +static int +packetizing_buffer_input (closure, data, need, size, got) + void *closure; + char *data; + int need; + int size; + int *got; +{ + struct packetizing_buffer *pb = (struct packetizing_buffer *) closure; + + *got = 0; + + if (pb->holdsize > 0 && pb->translated) + { + int copy; + + copy = pb->holdsize; + + if (copy > size) + { + memcpy (data, pb->holddata, size); + pb->holdsize -= size; + pb->holddata += size; + *got = size; + return 0; + } + + memcpy (data, pb->holddata, copy); + pb->holdsize = 0; + pb->translated = 0; + + data += copy; + need -= copy; + size -= copy; + *got = copy; + } + + while (need > 0 || *got == 0) + { + int get, status, nread, count, tcount; + char *bytes; + char stackoutbuf[BUFFER_DATA_SIZE + PACKET_SLOP]; + char *inbuf, *outbuf; + + /* If we don't already have the two byte count, get it. */ + if (pb->holdsize < 2) + { + get = 2 - pb->holdsize; + status = buf_read_data (pb->buf, get, &bytes, &nread); + if (status != 0) + { + /* buf_read_data can return -2, but a buffer input + function is only supposed to return -1, 0, or an + error code. */ + if (status == -2) + status = ENOMEM; + return status; + } + + if (nread == 0) + { + /* The buffer is in nonblocking mode, and we didn't + manage to read anything. */ + return 0; + } + + if (get == 1) + pb->holdbuf[1] = bytes[0]; + else + { + pb->holdbuf[0] = bytes[0]; + if (nread < 2) + { + /* We only got one byte, but we needed two. Stash + the byte we got, and try again. */ + pb->holdsize = 1; + continue; + } + pb->holdbuf[1] = bytes[1]; + } + pb->holdsize = 2; + } + + /* Read the packet. */ + + count = (((pb->holdbuf[0] & 0xff) << 8) + + (pb->holdbuf[1] & 0xff)); + + if (count + 2 > pb->holdbufsize) + { + char *n; + + /* We didn't allocate enough space in the initialize + function. */ + + n = xrealloc (pb->holdbuf, count + 2); + if (n == NULL) + { + (*pb->buf->memory_error) (pb->buf); + return ENOMEM; + } + pb->holdbuf = n; + pb->holdbufsize = count + 2; + } + + get = count - (pb->holdsize - 2); + + status = buf_read_data (pb->buf, get, &bytes, &nread); + if (status != 0) + { + /* buf_read_data can return -2, but a buffer input + function is only supposed to return -1, 0, or an error + code. */ + if (status == -2) + status = ENOMEM; + return status; + } + + if (nread == 0) + { + /* We did not get any data. Presumably the buffer is in + nonblocking mode. */ + return 0; + } + + if (nread < get) + { + /* We did not get all the data we need to fill the packet. + buf_read_data does not promise to return all the bytes + requested, so we must try again. */ + memcpy (pb->holdbuf + pb->holdsize, bytes, nread); + pb->holdsize += nread; + continue; + } + + /* We have a complete untranslated packet of COUNT bytes. */ + + if (pb->holdsize == 2) + { + /* We just read the entire packet (the 2 bytes in + PB->HOLDBUF are the size). Save a memcpy by + translating directly from BYTES. */ + inbuf = bytes; + } + else + { + /* We already had a partial packet in PB->HOLDBUF. We + need to copy the new data over to make the input + contiguous. */ + memcpy (pb->holdbuf + pb->holdsize, bytes, nread); + inbuf = pb->holdbuf + 2; + } + + if (count <= sizeof stackoutbuf) + outbuf = stackoutbuf; + else + { + outbuf = xmalloc (count); + if (outbuf == NULL) + { + (*pb->buf->memory_error) (pb->buf); + return ENOMEM; + } + } + + status = (*pb->inpfn) (pb->fnclosure, inbuf, outbuf, count); + if (status != 0) + return status; + + /* The first two bytes in the translated buffer are the real + length of the translated data. */ + tcount = ((outbuf[0] & 0xff) << 8) + (outbuf[1] & 0xff); + + if (tcount > count) + error (1, 0, "Input translation failure"); + + if (tcount > size) + { + /* We have more data than the caller has provided space + for. We need to save some of it for the next call. */ + + memcpy (data, outbuf + 2, size); + *got += size; + + pb->holdsize = tcount - size; + memcpy (pb->holdbuf, outbuf + 2 + size, tcount - size); + pb->holddata = pb->holdbuf; + pb->translated = 1; + + if (outbuf != stackoutbuf) + free (outbuf); + + return 0; + } + + memcpy (data, outbuf + 2, tcount); + + if (outbuf != stackoutbuf) + free (outbuf); + + pb->holdsize = 0; + + data += tcount; + need -= tcount; + size -= tcount; + *got += tcount; + } + + return 0; +} + +/* Output data to a packetizing buffer. */ + +static int +packetizing_buffer_output (closure, data, have, wrote) + void *closure; + const char *data; + int have; + int *wrote; +{ + struct packetizing_buffer *pb = (struct packetizing_buffer *) closure; + char inbuf[BUFFER_DATA_SIZE + 2]; + char stack_outbuf[BUFFER_DATA_SIZE + PACKET_SLOP + 4]; + struct buffer_data *outdata = NULL; + char *outbuf; + int size, status, translated; + + if (have > BUFFER_DATA_SIZE) + { + /* It would be easy to xmalloc a buffer, but I don't think this + case can ever arise. */ + abort (); + } + + inbuf[0] = (have >> 8) & 0xff; + inbuf[1] = have & 0xff; + memcpy (inbuf + 2, data, have); + + size = have + 2; + + /* The output function is permitted to add up to PACKET_SLOP + bytes, and we need 2 bytes for the size of the translated data. + If we can guarantee that the result will fit in a buffer_data, + we translate directly into one to avoid a memcpy in buf_output. */ + if (size + PACKET_SLOP + 2 > BUFFER_DATA_SIZE) + outbuf = stack_outbuf; + else + { + outdata = get_buffer_data (); + if (outdata == NULL) + { + (*pb->buf->memory_error) (pb->buf); + return ENOMEM; + } + + outdata->next = NULL; + outdata->bufp = outdata->text; + + outbuf = outdata->text; + } + + status = (*pb->outfn) (pb->fnclosure, inbuf, outbuf + 2, size, + &translated); + if (status != 0) + return status; + + /* The output function is permitted to add up to PACKET_SLOP + bytes. */ + if (translated > size + PACKET_SLOP) + abort (); + + outbuf[0] = (translated >> 8) & 0xff; + outbuf[1] = translated & 0xff; + + if (outbuf == stack_outbuf) + buf_output (pb->buf, outbuf, translated + 2); + else + { + /* if ((have + PACKET_SLOP + 4) > BUFFER_DATA_SIZE), then + outdata may be NULL. */ + if (outdata == NULL) + abort (); + + outdata->size = translated + 2; + buf_append_data (pb->buf, outdata, outdata); + } + + *wrote = have; + + /* We will only be here because buf_send_output was called on the + packetizing buffer. That means that we should now call + buf_send_output on the underlying buffer. */ + return buf_send_output (pb->buf); +} + + + +/* Flush data to a packetizing buffer. */ +static int +packetizing_buffer_flush (closure) + void *closure; +{ + struct packetizing_buffer *pb = (struct packetizing_buffer *) closure; + + /* Flush the underlying buffer. Note that if the original call to + buf_flush passed 1 for the BLOCK argument, then the buffer will + already have been set into blocking mode, so we should always + pass 0 here. */ + return buf_flush (pb->buf, 0); +} + + + +/* The block routine for a packetizing buffer. */ +static int +packetizing_buffer_block (closure, block) + void *closure; + int block; +{ + struct packetizing_buffer *pb = (struct packetizing_buffer *) closure; + + if (block) + return set_block (pb->buf); + else + return set_nonblock (pb->buf); +} + +/* Shut down a packetizing buffer. */ + +static int +packetizing_buffer_shutdown (buf) + struct buffer *buf; +{ + struct packetizing_buffer *pb = (struct packetizing_buffer *) buf->closure; + + return buf_shutdown (pb->buf); +} + +#endif /* defined (SERVER_SUPPORT) || defined (CLIENT_SUPPORT) */ |