Can you give any comments on this code?
I used one goto, is it bad?
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <assert.h>
#define NOT_NULL 1
#define NUMERIC 2
#define MULTI_LINE 4
/* Describes one field of a record */
struct field_desc {
char *name;
int length;
int type;
};
#define FIELD_PROMPT "%s (%d): "
struct record {
size_t count;
struct field_desc *desc;
char **entries;
};
struct field_desc movies_desc[] = {
{ "id", 13, NOT_NULL | NUMERIC },
{ "title", 50, NOT_NULL },
{ "genre", 15, NOT_NULL },
{ "length", 3, NOT_NULL | NUMERIC },
{ "description", 65535, MULTI_LINE },
{ "owner", 10, NOT_NULL },
{ "available", 1, NOT_NULL | NUMERIC },
{ "customer", 30, 0 } };
#define MOVIES_LENGTH (sizeof(movies_desc)/sizeof(movies_desc[0]))
char *readl(size_t length, int multiline, char stop)
{
const size_t bufsize = 32;
int c, lastc;
size_t index;
size_t size;
char *str, *tmp;
size = 0;
str = NULL;
if (multiline)
lastc = '\n';
else
lastc = 0;
index = 0;
while ((c = getchar()) != EOF && index < length) {
if (index + 1 >= size) {
size += bufsize;
if (size > length + 1)
size = length + 1;
tmp = realloc(str, size);
if (tmp == NULL) {
if (str)
free(str);
return NULL;
}
str = tmp;
}
/* Will only be true if multiline */
if (c == stop && lastc == '\n') {
if (stop == '\n')
break;
if ((c = getchar()) == '\n' || c == EOF)
break;
else {
ungetc(c, stdin);
c = stop;
}
}
if (c == '\n' && !multiline)
break;
assert(index + 1 < size);
str[index++] = c;
lastc = c;
}
if (c == EOF && index == 0)
return NULL;
assert(str != NULL);
assert(index < size);
str[index] = '\0';
/* Remove trailing '\n's (multiline only) */
if (index != 0 && lastc == '\n')
str[index - 1] = '\0';
/* Remove remaining characters on the line. */
while(c != '\n' && c != EOF)
c = getchar();
return str;
}
/* This function assumes sticky eof. That means,
* if an EOF is encountered, it will be read again
* from getchar() at the next call to this function. */
char *read_multiline(size_t length)
{
return readl(length, 1, '\n');
}
/* Reads one line of max length and discards additional
* characters on the line. Intended for interactive use */
char *read_line(size_t length)
{
return readl(length, 0, 0);
}
int isnumeric(const char *str)
{
if (!str)
return 0;
while (*str) {
if (!isdigit(*str) && *str != '\n')
return 0;
str++;
}
return 1;
}
static int validate_entry(const char *entry, struct field_desc desc)
{
if ((desc.type & NUMERIC) && !isnumeric(entry)) {
fprintf(stderr, "Error: Value must be numeric\n");
return -1;
}
if ((desc.type & NOT_NULL) && entry[0] == '\0') {
fprintf(stderr, "Error: Value must not be null\n");
return -1;
}
return 0;
}
static void field_prompt(struct field_desc desc)
{
printf(FIELD_PROMPT, desc.name, desc.length);
fflush(stdout);
}
static int get_field(char **entry, struct field_desc desc)
{
if (desc.type & MULTI_LINE)
*entry = read_multiline(desc.length);
else
*entry = read_line(desc.length);
if (*entry == NULL)
return EOF;
return 0;
}
int set_record_type(struct record *r, struct field_desc *desc, size_t
length)
{
size_t i;
r->count = length;
r->desc = desc;
r->entries = malloc(length * sizeof(char *));
if (r->entries == NULL)
return -1;
/* Init all data to NULL */
for (i = 0; i < length; i++)
r->entries[i] = NULL;
return 0;
}
/* Fills record with input from user */
int fill_record(struct record r)
{
size_t i;
for (i = 0; i < r.count; i++) {
field_prompt(r.desc[i]);
if (get_field(&r.entries[i], r.desc[i]) == EOF)
goto atEOF;
while (validate_entry(r.entries[i], r.desc[i]) != 0) {
free(r.entries[i]);
field_prompt(r.desc[i]);
if (get_field(&r.entries[i], r.desc[i]) == EOF)
goto atEOF;
}
}
return 0;
atEOF:
while (i--) {
free(r.entries[i]);
r.entries[i] = NULL;
}
return EOF;
}
void print_record(struct record r)
{
size_t i;
for (i = 0; i < r.count; i++) {
printf("%s:\"%s\"\n", r.desc[i].name, r.entries[i]);
}
}
void free_record(struct record r)
{
size_t i;
for (i = 0; i < r.count; i++) {
if (r.entries[i])
free(r.entries[i]);
}
free(r.entries);
}
int main(void)
{
struct record movies_record;
set_record_type(&movies_record, movies_desc, MOVIES_LENGTH);
fill_record(movies_record);
print_record(movies_record);
free_record(movies_record);
return EXIT_SUCCESS;
}