Code:
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
/* If defined, this program will accept any newline convention in input.
* Use "b" flag when opening the input streams. */
#define ANY_NEWLINE
/* If defined, this program uses horizontal tabs, '\t', to separate the two strings
* on each record. */
#define TAB_SEPARATOR
typedef struct {
int number;
int rating;
char *comp_name;
char *comp_type;
} review_t;
/* A C89 implementation of POSIX.1-2008 getline() function.
* See http://www.kernel.org/doc/man-pages/online/pages/man3/getline.3.html
*/
static ssize_t getline(char **const lineptr, size_t *const sizeptr, FILE *const in)
{
char *line, *temp;
size_t size;
size_t used = 0;
int c, saved_errno;
if (!lineptr || !sizeptr || !in) {
errno = EINVAL;
return (ssize_t)-1;
}
if (*sizeptr > 0) {
line = *lineptr;
size = *sizeptr;
} else {
line = NULL;
size = 0;
}
saved_errno = errno;
while (1) {
/* Need to grow the buffer? */
if (used + 2 >= size) {
/* Size policy: */
if (used < 1024)
size = (used | 127) + 129;
else
if (used < 1048574)
size = (used * 5) / 4;
else
size = (used | 131071) + 131073;
/* Reallocate. */
temp = realloc(line, size);
if (!temp) {
if (line)
line[used] = '\0';
errno = ENOMEM;
return (ssize_t)-1;
}
line = temp;
/* Update pointer and size. */
*lineptr = line;
*sizeptr = size;
}
/* Since fgets() does not work with embedded NULs,
* we need to read input character by character. */
c = getc(in);
/* End of input? */
if (c == EOF) {
line[used] = '\0';
if (used == 0) {
errno = 0;
return (ssize_t)-1;
} else {
errno = saved_errno;
return (ssize_t)used;
}
}
/* Add char to buffer. */
line[used++] = c;
/* Newline? */
#ifdef ANY_NEWLINE
if (c == '\n') {
c = getc(in);
if (c == '\r')
line[used++] = c;
else
if (c != EOF)
ungetc(c, in);
line[used] = '\0';
errno = saved_errno;
return used;
} else
if (c == '\r') {
c = getc(in);
if (c == '\n')
line[used++] = c;
else
if (c != EOF)
ungetc(c, in);
line[used] = '\0';
errno = saved_errno;
return used;
}
#else
if (c == '\n') {
line[used] = '\0';
errno = saved_errno;
return (ssize_t)used;
}
#endif
}
}
/* A C89 implementation of POSIX.1-2008 strndup() function.
* See http://www.kernel.org/doc/man-pages/online/pages/man3/strndup.3.html
*/
static char *strndup(const char *const string, const size_t length)
{
char *result;
result = malloc(length + 1);
if (!result) {
errno = ENOMEM;
return NULL;
}
strncpy(result, string, length);
result[length] = '\0';
return result;
}
int main(void)
{
const char *file_name = "review.txt";
FILE *file;
char *line_data = NULL;
size_t line_size = 0;
ssize_t line_len;
unsigned long line_number = 0UL;
review_t *review_array = NULL;
size_t review_count = 0;
size_t review_allocated = 0;
int number, rating;
int name_start, name_end, type_start, type_end;
int status;
size_t i;
#ifdef ANY_NEWLINE
file = fopen(file_name, "rb");
#else
file = fopen(file_name, "r");
#endif
if (!file) {
fprintf(stderr, "%s: %s.\n", file_name, strerror(errno));
return 1;
}
while (1) {
line_len = getline(&line_data, &line_size, file);
if (line_len < (ssize_t)1) {
/* Error or end of file? */
if (ferror(file) || !feof(file))
status = EIO;
else
status = 0;
/* Break out of while(1) loop. */
break;
}
/* A new line was successfully read. */
line_number++;
/* Set last %n to -1, so we can detect whether it was parsed or not. */
type_end = -1;
/* Try parsing the input line. */
#ifdef TAB_SEPARATOR
if (sscanf(line_data, " %d %d %n%*[^\t\r\n]%n %n%*[^\t\r\n]%n",
#else
if (sscanf(line_data, " %d %d %n%*s%n %n%*s%n",
#endif
&number, &rating,
&name_start, &name_end,
&type_start, &type_end) < 2 || type_end == -1) {
fprintf(stderr, "%s: Line %lu: Cannot parse line.\n", file_name, line_number);
status = EINVAL;
break;
}
/* Need to reallocate for more reviews? */
if (review_count >= review_allocated) {
review_t *temp;
size_t size = review_count + 256;
temp = realloc(review_array, size * sizeof *review_array);
if (!temp) {
fprintf(stderr, "%s: Line %lu: Out of memory.\n", file_name, line_number);
status = ENOMEM;
break;
}
review_array = temp;
review_allocated = size;
}
/* Populate the entry. */
review_array[review_count].number = number;
review_array[review_count].rating = rating;
review_array[review_count].comp_name = strndup(line_data + name_start, name_end - name_start);
review_array[review_count].comp_type = strndup(line_data + type_start, type_end - type_start);
if (!review_array[review_count].comp_name || !review_array[review_count].comp_type) {
fprintf(stderr, "%s: Line %lu: Out of memory.\n", file_name, line_number);
status = ENOMEM;
break;
}
review_count++;
}
/* Discard the line buffer. */
free(line_data);
line_data = NULL;
line_size = 0;
/* Close the input file. */
if (fclose(file))
if (!status)
status = EIO;
/* We could abort here when status is nonzero, i.e.
if (status)
return 1;
but for this example I'll let it continue even then. */
if (review_count > 0) {
printf("Read %lu records:\n", (unsigned long)review_count);
for (i = 0; i < review_count; i++)
printf("%7lu. number = %d, rating = %d, name = \"%s\", type = \"%s\".\n",
1UL + (unsigned long)i,
review_array[i].number,
review_array[i].rating,
review_array[i].comp_name,
review_array[i].comp_type);
} else
printf("No records read.\n");
/* Being anal, we discard the arrays since they're no longer needed. */
free(review_array);
review_array = NULL;
review_count = 0;
review_allocated = 0;
return 0;
}
I don't know where to start explaining it, and I'm really too lazy to go over it line by line, but if you have any specific spots you want me to explain (why the code does what it does, and how a specific part works), I'd be happy to try to.