Permalink
Browse files

Replace getline() with provided fline() for non-POSIX compilers.

Visual C does not provide getline(), nor fgetln().
  • Loading branch information...
1 parent b7dd0f3 commit ef23812ce19cdc641ff6d4f2c5a8c5dcac993887 @madler committed May 15, 2016
Showing with 250 additions and 39 deletions.
  1. +3 −4 README.md
  2. +111 −0 fline.c
  3. +101 −0 fline.h
  4. +35 −35 spoof.c
View
@@ -20,10 +20,9 @@ the length of the message.
Installation
------------
-Simply compile and link with a standard C compiler and library. ("standard"
-here is defined as C99 with POSIX functionality. Alas, this excludes Visual C.)
-spoof.c is a command line program that takes input from stdin and produces
-output on stdout.
+Simply compile and link spoof.c and fline.c with a standard C compiler and
+library. "standard" here is defined as C99. spoof.c is a command line program
+that takes input from stdin and produces output on stdout.
Tests
-----
View
@@ -0,0 +1,111 @@
+// Copyright (C) 2016 Mark Adler
+// See fline.h for a description of these functions and the license.
+
+#include <stdio.h> // size_t, FILE, NULL, fread()
+#include <stdlib.h> // malloc(), free(), realloc()
+#include <string.h> // memchr(), memcpy()
+#include "fline.h"
+
+// Internal structure for line reading. This structure is not visible outside
+// of this source file.
+struct fline_s {
+ char *buf; // input buffer -- returned line is entirely in here
+ size_t size; // allocated size of the input buffer, which can grow
+ size_t have; // index right after end of available data in buf
+ size_t pos; // index of start of available data in buf
+ FILE *in; // input file, set to NULL when EOF is reached
+};
+
+void fline_reuse(fline_t *state, FILE *in) {
+ state->have = 0;
+ state->pos = 0;
+ state->in = in;
+}
+
+fline_t *fline_start(FILE *in) {
+ fline_t *state = malloc(sizeof(fline_t));
+ if (state == NULL)
+ return NULL; // out of memory
+ state->size = 32768U; // must be > 1 and a power of 2
+ state->buf = malloc(state->size);
+ if (state->buf == NULL) {
+ free(state);
+ return NULL; // out of memory
+ }
+ fline_reuse(state, in);
+ return state;
+}
+
+void fline_end(fline_t *state) {
+ if (state != NULL) {
+ if (state->buf != NULL)
+ free(state->buf);
+ free(state);
+ }
+}
+
+char *fline_delim(fline_t *state, size_t *len, int delim) {
+ // if the buffer is empty, fill it with input data
+ if (state->pos == state->have) {
+ if (state->in == NULL) {
+ *len = 0; // end of file, return 0
+ return state->buf;
+ }
+ state->pos = 0;
+ state->have = fread(state->buf, 1, state->size, state->in);
+ if (state->have < state->size)
+ state->in = NULL; // end of file reached
+ }
+
+ // scan and read input until a delimiter is found or end of file
+ size_t next = state->pos; // start of unscanned input
+ for (;;) {
+ // scan for the delimiter in the available and unscanned input
+ char *hit = delim < 0 || delim > 255 ? NULL :
+ memchr(state->buf + next, delim, state->have - next);
+
+ // if found or if at end of file, return the line
+ if (hit != NULL || state->in == NULL) {
+ size_t beg = state->pos;
+ state->pos = hit == NULL ? state->have : (hit - state->buf) + 1;
+ *len = state->pos - beg;
+ return state->buf + beg;
+ }
+
+ // if the first half of the buffer is now unused, then move the second
+ // half of the buffer to the first half (move only the available input)
+ next = state->size >> 1;
+ if (state->pos >= next) {
+ memcpy(state->buf + state->pos - next, state->buf + state->pos,
+ state->size - state->pos);
+ state->pos -= next;
+ }
+
+ // otherwise allocate more memory, doubling the size of the buffer
+ else {
+ next = state->size;
+ if ((next << 1) == 0)
+ return NULL; // size_t overflow
+ char *mem = realloc(state->buf, next << 1);
+ if (mem == NULL)
+ return NULL; // out of memory
+ state->buf = mem;
+ state->size = next << 1;
+ }
+
+ // either way, load more input into the second half of the buffer,
+ // then search again for a delimiter
+ state->have = next + fread(state->buf + next, 1, next, state->in);
+ if (state->have < state->size)
+ state->in = NULL; // end of file reached
+ }
+}
+
+char *fline(fline_t *state, size_t *len) {
+ return fline_delim(state, len, '\n');
+}
+
+char *fline_remains(fline_t *state, size_t *len) {
+ *len = state->have - state->pos;
+ return state->buf + state->pos;
+}
View
@@ -0,0 +1,101 @@
+/* fline -- read lines from a file
+
+ Copyright (C) 2016 Mark Adler
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+ Mark Adler
+ madler@alumni.caltech.edu
+
+ */
+
+// Read delimited lines from a file, fast. This is more than twice as fast as
+// BSD fgetln() or POSIX getline(), as measured on Darwin. fline is written to
+// be compatible with the C99 standard, not relying on POSIX or other standards
+// outside of C99.
+
+#include <stdio.h> // FILE, size_t
+
+// Type for an fline state. A pointer to this state is returned or used by all
+// of the routines here.
+typedef struct fline_s fline_t;
+
+// Return a new fline state, a pointer to the fline_t type. This allocates
+// memory for the state and assigns the file 'in' to the state, which must be
+// readable. Reading will start at in's current file pointer. fline_start()
+// returns NULL if it was not able to allocate memory.
+fline_t *fline_start(FILE *in);
+
+// Return a line from state, where lines are separated by '\n' characters. The
+// returned line includes the '\n', unless it is the last line in the file, and
+// the file doesn't end with a '\n'. fline() returns a pointer to the line, and
+// sets *len to the length of the line. If fline() returns non-NULL and *len is
+// zero, then the end of the file has been reached, or there was a read error.
+// ferror(in) can be used to distinguish between those, as for fread(). After
+// that, subsequent calls with state will continue to return zero in *len. The
+// file provided to the state is not closed.
+//
+// The returned line is no longer available when fline() or fline_delim() is
+// called with the same state to get the next line, or fline_end() is called to
+// release the state.
+//
+// The line is not a C string, i.e. it is not terminated with a null ('\0')
+// character, and may in fact have embedded nulls from the file.
+//
+// The contents of the line may be modified by the caller, within the bounds of
+// what was returned. In the cases that the returned line does not have a
+// terminating '\n' or a zero length is returned, the caller may modify the
+// byte after the returned line. This allows the caller to always either
+// replace the terminating '\n' with a null ('\0'), or append a terminating
+// null to the last line without a '\n'. The caller should then also consider
+// replacing any embedded nulls in the line with some other byte value, in
+// order to assure correct use of the line as a C string.
+//
+// in's file pointer must not be changed between fline() calls.
+//
+// If fline() returns NULL, then there was a memory allocation error or a
+// size_t overflow. In that case, *len is not set, and the state remains valid.
+// If it was a memory allocation error, then the operation can be retried after
+// more memory is made available. The largest line length that can be assured
+// to be readable by fline() without a size_t overflow is (SIZE_MAX >> 2) + 1.
+// If size_t is 32 bits, then the assured limit is a 1 GiB line.
+char *fline(fline_t *state, size_t *len);
+
+// fline_delim() does what fline() does, but a delimiter other than '\n' can be
+// provided as delim. Calls to fline() and fline_delim(), as well as calls to
+// fline_delim() with different delimiters can be mixed, simply changing the
+// delimiter searched for on each call.
+//
+// If delim is not in the range 0..255, then the remainder of the entire file
+// is returned. For that case, note the returned line length limit in the
+// fline() description above.
+char *fline_delim(fline_t *state, size_t *len, int delim);
+
+// Return a pointer to and set *len to the length of the unused contents of the
+// buffer in state. This can be used to recover unused data read from the file,
+// when done reading lines while still in the middle of the file. The returned
+// data remains available under the same conditions as for a returned line.
+// This does not change the state.
+char *fline_remains(fline_t *state, size_t *len);
+
+// Reuse an fline state, assigning the file 'in'. This clears the state, but
+// retains the allocated buffer memory (sans contents) from the previous use.
+void fline_reuse(fline_t *state, FILE *in);
+
+// End an fline state, freeing the allocated buffer and the state itself. The
+// file that was provided for state is not closed.
+void fline_end(fline_t *state);
View
@@ -221,6 +221,7 @@
#include <stdlib.h>
#include <string.h>
#include <assert.h>
+#include "fline.h"
#define local static
@@ -612,40 +613,35 @@ local inline int decimal_digits(range_t n)
#ifndef NOMAIN /* for testing */
-/* Return a null-terminated line of input from in, stripping any comments and
- skipping blank lines. Also replace any nulls with spaces so the line can be
- terminated by a null. A comment starts where the first hash (#) character
- appears anywhere in the line, and ends at the end of the line. A returned
- empty line indicates EOF or error. *line should be initialized to NULL, and
- *size to 0. When done with getinput(), *line should be freed. */
-local inline char *getinput(char **line, size_t *size, FILE *in)
+/* Return a null-terminated line of input from state, stripping any comments
+ and skipping blank lines. Also replace any nulls with spaces so the line
+ can be terminated by a null. A comment starts where the first hash (#)
+ character appears anywhere in the line, and ends at the end of the line. A
+ returned empty line indicates EOF or error. */
+local inline char *getinput(fline_t *state)
{
- ssize_t len;
+ size_t len;
int ch;
- char *loc;
+ char *line, *loc;
do {
- len = getline(line, size, in);
- if (len == -1) {
- if (*size == 0) {
- *size = 1;
- *line = alloc(*line, *size);
- }
- len = 0;
+ line = fline(state, &len);
+ if (line == NULL)
+ fail("out of memory");
+ if (len == 0)
break;
- }
- loc = *line;
- while ((loc = memchr(loc, 0, len - (loc - *line))) != NULL)
- *loc++ = ' ';
- loc = memchr(*line, '#', len);
+ loc = memchr(line, '#', len);
if (loc != NULL)
- len = loc - *line;
- while (len && ((ch = (*line)[len - 1]) == ' ' || ch == '\t' ||
+ len = loc - line;
+ loc = line;
+ while ((loc = memchr(loc, 0, len - (loc - line))) != NULL)
+ *loc++ = ' ';
+ while (len && ((ch = line[len - 1]) == ' ' || ch == '\t' ||
ch == '\n' || ch == '\r'))
len--;
} while (len == 0);
- (*line)[len] = 0;
- return *line;
+ line[len] = 0;
+ return line;
}
/* Read sequence length, bit positions, and desired crc difference from stdin.
@@ -657,6 +653,7 @@ int main(void)
word_t crc; /* calculated crc to check solution */
int ret; /* general function return value */
FILE *in = stdin; /* input file */
+ fline_t *state; /* state for fline() */
model_t model; /* CRC model */
word_t want; /* desired crc */
range_t len; /* length of sequence in bytes */
@@ -665,13 +662,16 @@ int main(void)
int pos; /* position of bit to potentially flip */
int locs; /* number of bit locations to look at */
int flips; /* number of bit locations to invert */
- char *line = NULL; /* input line */
- size_t size = 0; /* allocated size of input line */
+ char *line; /* input line */
int n; /* position from sscanf() */
- char *rest; /* remainder of input line to process */
+
+ /* set up input */
+ state = fline_start(in);
+ if (state == NULL)
+ fail("out of memory");
/* read crc description */
- ret = sscanf(getinput(&line, &size, in), " %hd %hd %" WORDFMT,
+ ret = sscanf(getinput(state), " %hd %hd %" WORDFMT,
&model.dim, &model.ref, &model.poly);
if (ret == 3 && model.dim > WORDBITS)
fail("CRC too long for crc integer type spoof was compiled with");
@@ -682,7 +682,7 @@ int main(void)
fail("invalid polynomial (you may need to reverse the bits)");
/* read desired crc difference and number of bytes in the sequence */
- ret = sscanf(getinput(&line, &size, in), " %" WORDFMT " %" RANGEFMT,
+ ret = sscanf(getinput(state), " %" WORDFMT " %" RANGEFMT,
&want, &len);
if (ret < 1 || want > ONES(model.dim))
fail("invalid target CRC");
@@ -693,13 +693,13 @@ int main(void)
k = model.dim << 1;
loci = alloc(NULL, k * sizeof(struct locus));
locs = 0;
- while ((ret = sscanf(getinput(&line, &size, in), " %" RANGEFMT "%n",
+ while ((ret = sscanf(line = getinput(state), " %" RANGEFMT "%n",
&off, &n)) > 0) {
if (off >= len)
fail("invalid bit location offset");
- rest = line + n;
- while ((ret = sscanf(rest, "%d%n", &pos, &n)) > 0) {
- rest += n;
+ line += n;
+ while ((ret = sscanf(line, "%d%n", &pos, &n)) > 0) {
+ line += n;
if (pos < 0 || pos > 7)
fail("invalid bit position");
if (locs == k) {
@@ -711,7 +711,7 @@ int main(void)
locs++;
}
}
- free(line);
+ fline_end(state);
if (locs < model.dim)
fail("need at least n bit locations for an n-bit CRC");
loci = alloc(loci, locs * sizeof(struct locus));

0 comments on commit ef23812

Please sign in to comment.