/* tab:8 * * read_line.c - line-by-line parsing utility (buffered, MT-safe) * * "Copyright (c) 1999 by Steven S. Lumetta." * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose, without fee, and without written agreement is * hereby granted, provided that the above copyright notice and the following * two paragraphs appear in all copies of this software. * * IN NO EVENT SHALL THE AUTHOR OR THE UNIVERSITY OF ILLINOIS BE LIABLE TO * ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL * DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, * EVEN IF THE AUTHOR AND/OR THE UNIVERSITY OF ILLINOIS HAS BEEN ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * THE AUTHOR AND THE UNIVERSITY OF ILLINOIS SPECIFICALLY DISCLAIM ANY * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND NEITHER THE AUTHOR NOR * THE UNIVERSITY OF ILLINOIS HAS NO OBLIGATION TO PROVIDE MAINTENANCE, * SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS." * * Author: Steve Lumetta * Version: 1 * Creation Date: Wed Feb 17 18:19:51 1999 * Filename: read_line.c * History: * SL 1 Wed Feb 17 18:19:51 1999 * First written. */ #ident "$Id$" /* NOTE: pthread.h must be included before errno.h for correct errno location definition. Alternatively, define _REENTRANT as a compiler flag. */ #include #include #include #include #include "local_assert.h" #include "exit_codes.h" #include "print_error.h" #define READ_LINE_BUF_LEN 8192 /* buffer size for incoming data */ #define NO_CHAR_SKIP -1 /* a constant that cannot match a character */ /* data buffer for a file descriptor. A list of buffers is maintained for each thread, with the buffers indexed by the fd field and linked by the next field. The head of the list is kept in thread-specific data (pthread_getspecific) under the read_line_key. */ typedef struct read_line_data_t read_line_data_t; struct read_line_data_t { unsigned char data[READ_LINE_BUF_LEN]; /* the data */ int start; /* start index of data not yet */ /* returned */ int end; /* end index of data not yet */ /* returned */ int skip; /* skip this character if next */ int fd; /* the file descriptor */ read_line_data_t* next; /* the thread's next data */ /* buffer */ }; /* Create the thread-specific data key used to access the data buffers. */ static void create_read_line_key (); /* Free the data buffers associated with a thread. */ static void free_buffers (read_line_data_t* rl_buf); ASSERT_STRING; /* Static copy of file name for assertions. */ /* The thread-specific data key to the data buffers and a synchronization variable to protect the key's initialization. */ static pthread_once_t read_line_init = {PTHREAD_ONCE_INIT}; static pthread_key_t read_line_key; char* read_line (int fd, char* buf, int len) { read_line_data_t* first_buf = NULL; /* the thread's first data buffer */ read_line_data_t* rl_buf; /* the data buffer for the fd passed */ int errval; /* records error return values */ /* Check that the caller is sane. */ ASSERT (fd > 0 && buf != NULL && len > 0); /* Create the thread-specific data key. The first thread to enter this call creates the key. Other threads block at the call if they reach it before the creation is complete. After the key exists, the call does nothing. */ pthread_once (&read_line_init, create_read_line_key); /* Find the first data buffer in the thread's linked list of buffers. */ if ((rl_buf = pthread_getspecific (read_line_key)) != NULL) { /* The thread has some buffers already. Save a pointer to the first. */ first_buf = rl_buf; /* Walk the list and find the right buffer, or leave rl_buf as NULL. */ while (rl_buf != NULL) if (rl_buf->fd == fd) break; } /* If the thread has no buffer for this descriptor, create one. */ if (rl_buf == NULL) { /* Allocate the buffer. */ if ((rl_buf = malloc (sizeof (read_line_data_t))) == NULL) { print_error (errno, "read_line/malloc"); return NULL; } /* Fill in the fields. */ rl_buf->start = 0; rl_buf->end = 0; rl_buf->skip = NO_CHAR_SKIP; rl_buf->fd = fd; rl_buf->next = first_buf; /* Link previous buffers behind the new one. */ /* The new buffer now leads the thread's list of buffers. */ if ((errval = pthread_setspecific (read_line_key, rl_buf)) != 0) { print_error (errval, "read_line/failed to set key value"); free (rl_buf); return NULL; } } /* Loop forever, reading data and copying it into the return buffer. */ while (1) { int space = len - 1; /* space remaining (must copy EOS at end) */ unsigned char* fill = buf; /* fill pointer to return buffer */ int n_read; /* number read during one call to read */ /* Does the data buffer still have data? */ if (rl_buf->end > 0) { int i = rl_buf->start; /* the start index of the available data */ int end = rl_buf->end; /* the end index of the available data */ char* copy = &rl_buf->data[i]; /* copy pointer to data buffer */ /* Possibly skip the second character in a CR-LF or LF-CR end-of-line sequence. */ if (rl_buf->skip == (int)*copy) { rl_buf->skip = NO_CHAR_SKIP; i++; copy++; } /* Loop over the data in the data buffer. */ for ( ; i < end; i++) { /* If the return buffer is full, return it. */ if (space == 0) { *fill = 0; rl_buf->start = i; return buf; } /* If the next character is CR or LF, return the line. */ if (*copy == '\r' || *copy == '\n') { *fill = 0; /* Check for CR-LF and LF-CR sequences. If no extra characters remain in the data buffer, we must remember to skip the character next time. Attempting to read more data may fail, while simply ignoring the combination may result in returning spurious blank lines. */ if (i + 1 < end) { if ((*(copy + 1) == '\r' || *(copy + 1) == '\n') && *copy != *(copy + 1)) i++; } else rl_buf->skip = (*copy == '\r' ? '\n' : '\r'); /* If the data buffer is now empty, mark it as such. Otherwise, save the start pointer for the next call. */ if (++i == end) rl_buf->start = rl_buf->end = 0; else rl_buf->start = i; /* Return the line. */ return buf; } /* Copy the current character and decrement the remaining space. */ *fill++ = *copy++; space--; } } /* Read more data. First mark the buffer as starting at index 0. */ rl_buf->start = 0; /* Read until successful, we find an error, or the descriptor is closed. Ignore interrupts. */ do { n_read = read (fd, rl_buf->data, READ_LINE_BUF_LEN); } while (n_read == -1 && errno == EINTR); /* In the case of error or closure, return a partial line if one exists. Otherwise, return failure (NULL). In either case, first mark the data buffer as empty. */ if (n_read < 1) { rl_buf->end = 0; if (fill == (unsigned char*)buf) return NULL; *fill = 0; return buf; } /* Mark the end index of the data buffer and loop around to copy the data into the return buffer. */ rl_buf->end = n_read; } } /* create_read_line_key is called exactly once when a thread first calls read_line. */ static void create_read_line_key () { int errval; /* If the creation fails, panic! */ if ((errval = pthread_key_create (&read_line_key, (void (*) (void*))free_buffers)) != 0) { print_error (errval, "failed to create read_line key"); exit (EXIT_PANIC); } } /* free_buffers is called whenever a thread with data buffers exits. */ static void free_buffers (read_line_data_t* rl_buf) { read_line_data_t* next; /* Walk through the list of buffers, freeing them all. */ while (rl_buf != NULL) { next = rl_buf->next; free (rl_buf); rl_buf = next; } }