diff options
Diffstat (limited to 'src/buffer.c')
-rw-r--r-- | src/buffer.c | 2187 |
1 files changed, 2187 insertions, 0 deletions
diff --git a/src/buffer.c b/src/buffer.c new file mode 100644 index 0000000..365d91c --- /dev/null +++ b/src/buffer.c @@ -0,0 +1,2187 @@ +/* + * 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. */ + +#include "cvs.h" +#include "buffer.h" +#include "pagealign_alloc.h" + +#if defined (SERVER_SUPPORT) || defined (CLIENT_SUPPORT) + +# include <sys/socket.h> + +/* 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 + +/* Local functions. */ +static void buf_default_memory_error (struct buffer *); +static struct buffer_data *get_buffer_data (void); + + + +/* Initialize a buffer structure. */ +struct buffer * +buf_initialize (type_buf_input input, + type_buf_output output, + type_buf_flush flush, + type_buf_block block, + type_buf_get_fd get_fd, + type_buf_shutdown shutdown, + type_buf_memory_error memory_error, + void *closure) +{ + struct buffer *buf; + + buf = xmalloc (sizeof (struct buffer)); + buf->data = NULL; + buf->last = NULL; + buf->nonblocking = false; + buf->input = input; + buf->output = output; + buf->flush = flush; + buf->block = block; + buf->get_fd = get_fd; + buf->shutdown = shutdown; + buf->memory_error = memory_error ? memory_error : buf_default_memory_error; + buf->closure = closure; + return buf; +} + + + +/* Free a buffer structure. */ +void +buf_free (struct buffer *buf) +{ + if (buf->closure != NULL) + { + free (buf->closure); + buf->closure = NULL; + } + buf_free_data (buf); + free (buf); +} + + + +/* Initialize a buffer structure which is not to be used for I/O. */ +struct buffer * +buf_nonio_initialize( void (*memory) (struct buffer *) ) +{ + return buf_initialize (NULL, NULL, NULL, NULL, NULL, NULL, memory, NULL); +} + + + +/* Default memory error handler. */ +static void +buf_default_memory_error (struct buffer *buf) +{ + error (1, 0, "out of memory"); +} + + + +/* Allocate more buffer_data structures. */ +/* Get a new buffer_data structure. */ +static struct buffer_data * +get_buffer_data (void) +{ + struct buffer_data *ret; + + ret = xmalloc (sizeof (struct buffer_data)); + ret->text = pagealign_xalloc (BUFFER_DATA_SIZE); + + 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 (struct buffer *buf) +{ + struct buffer_data *data; + + for (data = buf->data; data != NULL; data = data->next) + if (data->size > 0) + return 0; + return 1; +} + + + +# if defined (SERVER_FLOWCONTROL) || defined (PROXY_SUPPORT) +/* + * 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 (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 || PROXY_SUPPORT */ + + + +/* Add data DATA of length LEN to BUF. */ +void +buf_output (struct buffer *buf, const char *data, size_t 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 (struct buffer *buf, const char *string) +{ + buf_output (buf, string, strlen (string)); +} + + + +/* Add a single character to BUF. */ +void +buf_append_char (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); + } +} + + + +/* Free struct buffer_data's from the list starting with FIRST and ending at + * LAST, inclusive. + */ +static inline void +buf_free_datas (struct buffer_data *first, struct buffer_data *last) +{ + struct buffer_data *b, *n, *p; + b = first; + do + { + p = b; + n = b->next; + pagealign_free (b->text); + free (b); + b = n; + } while (p != last); +} + + + +/* + * 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 (struct buffer *buf) +{ + assert (buf->output != NULL); + + while (buf->data != NULL) + { + struct buffer_data *data; + + data = buf->data; + + if (data->size > 0) + { + int status; + size_t nbytes; + + status = (*buf->output) (buf->closure, data->bufp, data->size, + &nbytes); + if (status != 0) + { + /* Some sort of error. Discard the data, and return. */ + buf_free_data (buf); + 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; + buf_free_datas (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 (struct buffer *buf, bool block) +{ + int nonblocking; + int status; + + assert (buf->flush != NULL); + + 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 (struct buffer *buf) +{ + int status; + + if (buf->nonblocking) + return 0; + assert (buf->block != NULL); + status = (*buf->block) (buf->closure, 0); + if (status != 0) + return status; + buf->nonblocking = true; + return 0; +} + + + +/* + * Set buffer BUF to blocking I/O. Returns 0 for success or errno + * code. + */ +int +set_block (struct buffer *buf) +{ + int status; + + if (! buf->nonblocking) + return 0; + assert (buf->block != NULL); + status = (*buf->block) (buf->closure, 1); + if (status != 0) + return status; + buf->nonblocking = false; + 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 (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 specially 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 (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 (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; + } +} + + + +# ifdef PROXY_SUPPORT +/* Copy data structures and append them to a buffer. + * + * ERRORS + * Failure to allocate memory here is fatal. + */ +void +buf_copy_data (struct buffer *buf, struct buffer_data *data, + struct buffer_data *last) +{ + struct buffer_data *first, *new, *cur, *prev; + + assert (buf); + assert (data); + + prev = first = NULL; + cur = data; + while (1) + { + new = get_buffer_data (); + if (!new) error (1, errno, "Failed to allocate buffer data."); + + if (!first) first = new; + memcpy (new->text, cur->bufp, cur->size); + new->bufp = new->text; + new->size = cur->size; + new->next = NULL; + if (prev) prev->next = new; + if (cur == last) break; + prev = new; + cur = cur->next; + } + + buf_append_data (buf, first, new); +} +# endif /* PROXY_SUPPORT */ + + + +/* Dispose of any remaining data in the buffer. */ +void +buf_free_data (struct buffer *buffer) +{ + if (buf_empty_p (buffer)) return; + buf_free_datas (buffer->data, buffer->last); + buffer->data = buffer->last = NULL; +} + + + +/* Append the data in one buffer to another. This removes the data + * from the source buffer. + */ +void +buf_append_buffer (struct buffer *to, struct buffer *from) +{ + struct buffer_data *n; + + /* Copy the data pointer to the new buf. */ + buf_append_data (to, from->data, from->last); + + n = from->data; + while (n) + { + if (n == from->last) break; + n = n->next; + } + + /* Remove from the original location. */ + 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 + * success 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 (FILE *f, long int 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) + buf_free_datas (*retp, (*lastp)->next); + 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 + * success 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 (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) + buf_free_datas (*retp, (*lastp)->next); + return status; +} + + + +/* Return the number of bytes in a chain of buffer_data structures. */ +int +buf_chain_length (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 (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 (struct buffer *buf, size_t *countp) +{ + assert (buf->input != NULL); + + if (countp != NULL) + *countp = 0; + + while (1) + { + int status; + size_t get, 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 (struct buffer *buf, char **line, size_t *lenp) +{ + return buf_read_short_line (buf, line, lenp, SIZE_MAX); +} + + + +/* Like buf_read_line, but return -2 if no newline is found in MAX characters. + */ +int +buf_read_short_line (struct buffer *buf, char **line, size_t *lenp, + size_t max) +{ + assert (buf->input != NULL); + + *line = NULL; + + while (1) + { + size_t len, finallen, predicted_len; + 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; + if (xsum (len, finallen) >= max) return -2; + len += finallen; + break; + } + else if (xsum (len, data->size) >= max) return -2; + 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; + buf_free_datas (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 or MAX characters. */ + predicted_len = 0; + while (1) + { + int status; + size_t size, 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; + + predicted_len += nbytes; + 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; + } + if (xsum (len, predicted_len) >= max) return -2; + } + } +} + + + +/* + * 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 (struct buffer *buf, size_t want, char **retdata, size_t *got) +{ + assert (buf->input != NULL); + + while (buf->data != NULL && buf->data->size == 0) + { + struct buffer_data *next; + + next = buf->data->next; + buf_free_datas (buf->data, buf->data); + buf->data = next; + if (next == NULL) + buf->last = NULL; + } + + if (buf->data == NULL) + { + struct buffer_data *data; + int status; + size_t get, 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 (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 (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; + buf_free_datas (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*/ +} + + + +int +buf_get_fd (struct buffer *buf) +{ + if (buf->get_fd) + return (*buf->get_fd) (buf->closure); + return -1; +} + + + +/* Shut down a buffer. This returns 0 on success, or an errno code. */ +int +buf_shutdown (struct buffer *buf) +{ + if (buf->shutdown) return (*buf->shutdown) (buf); + 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) (void *fnclosure, const char *input, char *output, + size_t 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) (void *fnclosure, const char *input, char *output, + size_t size, size_t *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. */ + bool translated; + /* The amount of buffered data. */ + size_t holdsize; + /* The buffer allocated to hold the data. */ + char *holdbuf; + /* The size of holdbuf. */ + size_t 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 (void *, char *, size_t, size_t, size_t *); +static int packetizing_buffer_output (void *, const char *, size_t, size_t *); +static int packetizing_buffer_flush (void *); +static int packetizing_buffer_block (void *, bool); +static int packetizing_buffer_get_fd (void *); +static int packetizing_buffer_shutdown (struct buffer *); + + + +/* Create a packetizing buffer. */ +struct buffer * +packetizing_buffer_initialize (struct buffer *buf, + int (*inpfn) (void *, const char *, char *, + size_t), + int (*outfn) (void *, const char *, char *, + size_t, size_t *), + void *fnclosure, + void (*memory) (struct buffer *)) +{ + struct packetizing_buffer *pb; + + pb = 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_get_fd, + packetizing_buffer_shutdown, + memory, + pb); +} + + + +/* Input data from a packetizing buffer. */ +static int +packetizing_buffer_input (void *closure, char *data, size_t need, size_t size, + size_t *got) +{ + struct packetizing_buffer *pb = closure; + + *got = 0; + + if (pb->holdsize > 0 && pb->translated) + { + size_t 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 = false; + + data += copy; + need -= copy; + size -= copy; + *got = copy; + } + + while (need > 0 || *got == 0) + { + int status; + size_t get, nread, count, tcount; + char *bytes; + static char *stackoutbuf = NULL; + char *inbuf, *outbuf; + + if (!stackoutbuf) + stackoutbuf = xmalloc (BUFFER_DATA_SIZE + PACKET_SLOP); + + /* 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 <= BUFFER_DATA_SIZE + PACKET_SLOP) + 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 = true; + + 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 (void *closure, const char *data, size_t have, + size_t *wrote) +{ + struct packetizing_buffer *pb = closure; + static char *inbuf = NULL; /* These two buffers are static so that they + * depend on the size of BUFFER_DATA_SIZE yet + * still only be allocated once per run. + */ + static char *stack_outbuf = NULL; + struct buffer_data *outdata = NULL; /* Initialize to silence -Wall. Dumb. + */ + char *outbuf; + size_t size, translated; + int status; + + /* It would be easy to xmalloc a buffer, but I don't think this + case can ever arise. */ + assert (have <= BUFFER_DATA_SIZE); + + if (!inbuf) + { + inbuf = xmalloc (BUFFER_DATA_SIZE + 2); + stack_outbuf = xmalloc (BUFFER_DATA_SIZE + PACKET_SLOP + 4); + } + + 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. */ + assert (translated <= size + PACKET_SLOP); + + outbuf[0] = (translated >> 8) & 0xff; + outbuf[1] = translated & 0xff; + + if (outbuf == stack_outbuf) + buf_output (pb->buf, outbuf, translated + 2); + else + { + 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 (void *closure) +{ + struct packetizing_buffer *pb = 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 (void *closure, bool block) +{ + struct packetizing_buffer *pb = closure; + + if (block) + return set_block (pb->buf); + else + return set_nonblock (pb->buf); +} + + + +/* Return the file descriptor underlying any child buffers. */ +static int +packetizing_buffer_get_fd (void *closure) +{ + struct packetizing_buffer *cb = closure; + return buf_get_fd (cb->buf); +} + + + +/* Shut down a packetizing buffer. */ +static int +packetizing_buffer_shutdown (struct buffer *buf) +{ + struct packetizing_buffer *pb = buf->closure; + + return buf_shutdown (pb->buf); +} + + + +/* All server communication goes through buffer structures. Most of + the buffers are built on top of a file descriptor. This structure + is used as the closure field in a buffer. */ + +struct fd_buffer +{ + /* The file descriptor. */ + int fd; + /* Nonzero if the file descriptor is in blocking mode. */ + int blocking; + /* The child process id when fd is a pipe. */ + pid_t child_pid; + /* The connection info, when fd is a pipe to a server. */ + cvsroot_t *root; +}; + +static int fd_buffer_input (void *, char *, size_t, size_t, size_t *); +static int fd_buffer_output (void *, const char *, size_t, size_t *); +static int fd_buffer_flush (void *); +static int fd_buffer_block (void *, bool); +static int fd_buffer_get_fd (void *); +static int fd_buffer_shutdown (struct buffer *); + +/* Initialize a buffer built on a file descriptor. FD is the file + descriptor. INPUT is nonzero if this is for input, zero if this is + for output. MEMORY is the function to call when a memory error + occurs. */ + +struct buffer * +fd_buffer_initialize (int fd, pid_t child_pid, cvsroot_t *root, bool input, + void (*memory) (struct buffer *)) +{ + struct fd_buffer *n; + + n = xmalloc (sizeof *n); + n->fd = fd; + n->child_pid = child_pid; + n->root = root; + fd_buffer_block (n, true); + return buf_initialize (input ? fd_buffer_input : NULL, + input ? NULL : fd_buffer_output, + input ? NULL : fd_buffer_flush, + fd_buffer_block, fd_buffer_get_fd, + fd_buffer_shutdown, + memory, + n); +} + + + +/* The buffer input function for a buffer built on a file descriptor. + * + * In non-blocking mode, this function will read as many bytes as it can in a + * single try, up to SIZE bytes, and return. + * + * In blocking mode with NEED > 0, this function will read as many bytes as it + * can but will not return until it has read at least NEED bytes. + * + * In blocking mode with NEED == 0, this function will block until it can read + * either at least one byte or EOF, then read as many bytes as are available + * and return. At the very least, compress_buffer_shutdown depends on this + * behavior to read EOF and can loop indefinitely without it. + * + * ASSUMPTIONS + * NEED <= SIZE. + * + * INPUTS + * closure Our FD_BUFFER struct. + * data The start of our input buffer. + * need How many bytes our caller needs. + * size How many bytes are available in DATA. + * got Where to store the number of bytes read. + * + * OUTPUTS + * data Filled with bytes read. + * *got Number of bytes actually read into DATA. + * + * RETURNS + * errno On error. + * -1 On EOF. + * 0 Otherwise. + * + * ERRORS + * This function can return an error if fd_buffer_block(), or the system + * read() or select() calls do. + */ +static int +fd_buffer_input (void *closure, char *data, size_t need, size_t size, + size_t *got) +{ + struct fd_buffer *fb = closure; + int nbytes; + + assert (need <= size); + + *got = 0; + + if (fb->blocking) + { + int status; + fd_set readfds; + + /* Set non-block. */ + status = fd_buffer_block (fb, false); + if (status != 0) return status; + + FD_ZERO (&readfds); + FD_SET (fb->fd, &readfds); + do + { + int numfds; + + do { + /* This used to select on exceptions too, but as far + as I know there was never any reason to do that and + SCO doesn't let you select on exceptions on pipes. */ + numfds = fd_select (fb->fd + 1, &readfds, NULL, NULL, NULL); + if (numfds < 0 && errno != EINTR) + { + status = errno; + goto block_done; + } + } while (numfds < 0); + + nbytes = read (fb->fd, data + *got, size - *got); + + if (nbytes == 0) + { + /* End of file. This assumes that we are using POSIX or BSD + style nonblocking I/O. On System V we will get a zero + return if there is no data, even when not at EOF. */ + if (*got) + { + /* We already read some data, so return no error, counting + * on the fact that we will read EOF again next time. + */ + status = 0; + break; + } + else + { + /* Return EOF. */ + status = -1; + break; + } + } + + if (nbytes < 0) + { + /* Some error occurred. */ + if (!blocking_error (errno)) + { + status = errno; + break; + } + /* else Everything's fine, we just didn't get any data. */ + } + + *got += nbytes; + } while (*got < need); + +block_done: + if (status == 0 || status == -1) + { + int newstatus; + + /* OK or EOF - Reset block. */ + newstatus = fd_buffer_block (fb, true); + if (newstatus) status = newstatus; + } + return status; + } + + /* The above will always return. Handle non-blocking read. */ + nbytes = read (fb->fd, data, size); + + if (nbytes > 0) + { + *got = nbytes; + return 0; + } + + if (nbytes == 0) + /* End of file. This assumes that we are using POSIX or BSD + style nonblocking I/O. On System V we will get a zero + return if there is no data, even when not at EOF. */ + return -1; + + /* Some error occurred. */ + if (blocking_error (errno)) + /* Everything's fine, we just didn't get any data. */ + return 0; + + return errno; +} + + + +/* The buffer output function for a buffer built on a file descriptor. */ + +static int +fd_buffer_output (void *closure, const char *data, size_t have, size_t *wrote) +{ + struct fd_buffer *fd = closure; + + *wrote = 0; + + while (have > 0) + { + int nbytes; + + nbytes = write (fd->fd, data, have); + + if (nbytes <= 0) + { + if (! fd->blocking + && (nbytes == 0 || blocking_error (errno))) + { + /* A nonblocking write failed to write any data. Just + return. */ + return 0; + } + + /* Some sort of error occurred. */ + + if (nbytes == 0) + return EIO; + + return errno; + } + + *wrote += nbytes; + data += nbytes; + have -= nbytes; + } + + return 0; +} + + + +/* The buffer flush function for a buffer built on a file descriptor. */ +static int +fd_buffer_flush (void *closure) +{ + /* We don't need to do anything here. Our fd doesn't have its own buffer + * and syncing won't do anything but slow us down. + * + * struct fd_buffer *fb = closure; + * + * if (fsync (fb->fd) < 0 && errno != EROFS && errno != EINVAL) + * return errno; + */ + return 0; +} + + + +static struct stat devnull; +static int devnull_set = -1; + +/* The buffer block function for a buffer built on a file descriptor. */ +static int +fd_buffer_block (void *closure, bool block) +{ + struct fd_buffer *fb = closure; +# if defined (F_GETFL) && defined (O_NONBLOCK) && defined (F_SETFL) + int flags; + + flags = fcntl (fb->fd, F_GETFL, 0); + if (flags < 0) + return errno; + + if (block) + flags &= ~O_NONBLOCK; + else + flags |= O_NONBLOCK; + + if (fcntl (fb->fd, F_SETFL, flags) < 0) + { + /* + * BSD returns ENODEV when we try to set block/nonblock on /dev/null. + * BSDI returns ENOTTY when we try to set block/nonblock on /dev/null. + */ + struct stat sb; + int save_errno = errno; + bool isdevnull = false; + + if (devnull_set == -1) + devnull_set = stat ("/dev/null", &devnull); + + if (devnull_set >= 0) + /* Equivalent to /dev/null ? */ + isdevnull = (fstat (fb->fd, &sb) >= 0 + && sb.st_dev == devnull.st_dev + && sb.st_ino == devnull.st_ino + && sb.st_mode == devnull.st_mode + && sb.st_uid == devnull.st_uid + && sb.st_gid == devnull.st_gid + && sb.st_size == devnull.st_size + && sb.st_blocks == devnull.st_blocks + && sb.st_blksize == devnull.st_blksize); + if (isdevnull) + errno = 0; + else + { + errno = save_errno; + return errno; + } + } +# endif /* F_GETFL && O_NONBLOCK && F_SETFL */ + + fb->blocking = block; + + return 0; +} + + + +static int +fd_buffer_get_fd (void *closure) +{ + struct fd_buffer *fb = closure; + return fb->fd; +} + + + +/* The buffer shutdown function for a buffer built on a file descriptor. + * + * This function disposes of memory allocated for this buffer. + */ +static int +fd_buffer_shutdown (struct buffer *buf) +{ + struct fd_buffer *fb = buf->closure; + struct stat s; + bool closefd, statted; + + /* Must be an open pipe, socket, or file. What could go wrong? */ + if (fstat (fb->fd, &s) == -1) statted = false; + else statted = true; + /* Don't bother to try closing the FD if we couldn't stat it. This + * probably won't work. + * + * (buf_shutdown() on some of the server/child communication pipes is + * getting EBADF on both the fstat and the close. I'm not sure why - + * perhaps they were alredy closed somehow? + */ + closefd = statted; + + /* Flush the buffer if possible. */ + 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 + * 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. + */ + +/* FIXME: + * + * This mess of #ifdefs is hard to read. There must be some relation between + * the macros being checked which at least deserves comments - if + * SHUTDOWN_SERVER, NO_SOCKET_TO_FD, & START_RSH_WITH_POPEN_RW were completely + * independant, then the next few lines could easily refuse to compile. + * + * The note below about START_RSH_WITH_POPEN_RW never being set when + * SHUTDOWN_SERVER is defined means that this code would always break on + * systems with SHUTDOWN_SERVER defined and thus the comment must now be + * incorrect or the code was broken since the comment was written. + */ +# ifdef SHUTDOWN_SERVER + if (fb->root && fb->root->method != server_method) +# endif +# ifndef NO_SOCKET_TO_FD + { + /* shutdown() sockets */ + if (statted && S_ISSOCK (s.st_mode)) + shutdown (fb->fd, 0); + } +# endif /* NO_SOCKET_TO_FD */ +# ifdef START_RSH_WITH_POPEN_RW +/* Can't be set with SHUTDOWN_SERVER defined */ + /* FIXME: This is now certainly broken since pclose is defined by ANSI + * C to accept a FILE * argument. The switch will need to happen at a + * higher abstraction level to switch between initializing stdio & fd + * buffers on systems that need this (or maybe an fd buffer that keeps + * track of the FILE * could be used - I think flushing the stream + * before beginning exclusive access via the FD is OK. + */ + else if (fb->root && pclose (fb->fd) == EOF) + { + error (1, errno, "closing connection to %s", + fb->root->hostname); + closefd = false; + } +# 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 (fb->root && fb->root->method == server_method) + SHUTDOWN_SERVER (fb->fd); + else +# endif +# ifndef NO_SOCKET_TO_FD + /* shutdown() sockets */ + if (statted && S_ISSOCK (s.st_mode)) + shutdown (fb->fd, 1); +# else + { + /* I'm not sure I like this empty block, but the alternative + * is another nested NO_SOCKET_TO_FD switch as above. + */ + } +# endif /* NO_SOCKET_TO_FD */ + + buf->output = NULL; + } + + if (statted && closefd && close (fb->fd) == -1) + { + if (server_active) + { + /* Syslog this? */ + } +# ifdef CLIENT_SUPPORT + else if (fb->root) + error (1, errno, "closing down connection to %s", + fb->root->hostname); + /* EXITS */ +# endif /* CLIENT_SUPPORT */ + + error (0, errno, "closing down buffer"); + } + + /* If we were talking to a process, make sure it exited */ + if (fb->child_pid) + { + int w; + + do + w = waitpid (fb->child_pid, NULL, 0); + while (w == -1 && errno == EINTR); + if (w == -1) + error (1, errno, "waiting for process %d", fb->child_pid); + } + + free (buf->closure); + buf->closure = NULL; + + return 0; +} +#endif /* defined (SERVER_SUPPORT) || defined (CLIENT_SUPPORT) */ |