diff --git a/lcthw/.ex17ec.argv b/lcthw/.ex17ec.argv new file mode 100644 index 0000000..3998e02 --- /dev/null +++ b/lcthw/.ex17ec.argv @@ -0,0 +1,2 @@ +ex17ec.db +l diff --git a/lcthw/ex17ec.c b/lcthw/ex17ec.c new file mode 100644 index 0000000..f7e0bcc --- /dev/null +++ b/lcthw/ex17ec.c @@ -0,0 +1,305 @@ +#include +#include +#include +#include +#include + +#define DEFAULT_MAX_DATA 512 +#define DEFAULT_MAX_ROWS 100 + +struct Address { + char *name; + char *email; +}; + +struct TableOfContents { + size_t n_rows; + size_t string_field_size; +}; + +struct Database { + struct Address *rows; +}; + +struct Connection { + FILE *file; + struct TableOfContents *toc; + struct Database *db; +}; + +void Database_close(struct Connection *conn); +struct Address *Database_get(struct Connection *conn, const char *email); + +void die(struct Connection *conn, const char *message) +{ + Database_close(conn); + + if (errno) { + perror(message); + } else { + printf("ERROR: %s\n", message); + } + + exit(1); +} + +void Address_print(struct Address *addr) +{ + if (!addr) { + return; + } + + printf("%s %s\n", addr->name, addr->email); +} + +size_t Database_addr_size(struct Connection *conn) +{ + return sizeof(struct Address) + + /* name */ conn->toc->string_field_size + + /* email */ conn->toc->string_field_size; +} + +size_t Database_size(struct Connection *conn) +{ + return sizeof(struct Database) + + (conn->toc->n_rows * Database_addr_size(conn)); +} + +size_t Database_n_rows(struct Connection *conn) +{ + return sizeof(conn->db->rows) / Database_addr_size(conn); +} + +void Database_load(struct Connection *conn) +{ + int rc = 0; + + rc = fread(conn->toc, sizeof(struct TableOfContents), 1, conn->file); + if (rc != 1) { + die(conn, "Failed to load TableOfContents."); + } + + rc = fread(conn->db, Database_size(conn), 1, conn->file); + if (rc != 1) { + die(conn, "Failed to load Database."); + } +} + +struct Connection *Database_open(const char *filename, char mode, size_t string_field_size) +{ + struct Connection *conn = malloc(sizeof(struct Connection)); + + if (!conn) { + die(conn, "Memory error allocating Connection"); + } + + conn->toc = malloc(sizeof(struct TableOfContents)); + if (!conn->toc) { + die(conn, "Memory error allocating TableOfContents"); + } + + conn->toc->string_field_size = string_field_size; + + conn->db = malloc(sizeof(struct Database)); + if (!conn->db) { + die(conn, "Memory error allocating Database"); + } + + if (mode == 'c') { + conn->file = fopen(filename, "w"); + + conn->toc->n_rows = 1; + + conn->db->rows = malloc(Database_addr_size(conn)); + if (!conn->db->rows) { + die(conn, "Memory error allocating Database rows"); + } + + } else { + conn->file = fopen(filename, "r+"); + + if (conn->file) { + Database_load(conn); + } + } + + if (!conn->file) { + die(conn, "Failed to open file"); + } + + return conn; +} + +void Database_close(struct Connection *conn) +{ + if (conn) { + if (conn->file) { + fclose(conn->file); + } + + if (conn->toc) { + free(conn->toc); + } + + if (conn->db) { + free(conn->db); + } + + free(conn); + } +} + +void Database_write(struct Connection *conn) +{ + rewind(conn->file); + + int rc = 0; + + rc = fwrite(conn->toc, sizeof(struct TableOfContents), 1, conn->file); + if (rc != 1) { + die(conn, "Failed to write TableOfContents."); + } + + rc = fwrite(conn->db, sizeof(struct Database), 1, conn->file); + if (rc != 1) { + die(conn, "Failed to write Database."); + } + + rc = fflush(conn->file); + if (rc == -1) { + die(conn, "Cannot flush"); + } +} + +void Database_set(struct Connection *conn, const char *name, const char *email) +{ + struct Address *addr = Database_get(conn, email); + if (!addr) { + addr = malloc(Database_addr_size(conn)); + conn->db->rows[Database_n_rows(conn)+1] = *addr; + } + + char *res = strncpy(addr->name, name, conn->toc->string_field_size); + if (!res) { + die(conn, "Name copy failed"); + } + + addr->name[conn->toc->string_field_size - 1] = '\0'; + + res = strncpy(addr->email, email, conn->toc->string_field_size); + if (!res) { + die(conn, "Email copy failed"); + } + + addr->email[conn->toc->string_field_size - 1] = '\0'; +} + +struct Address *Database_get(struct Connection *conn, const char *email) +{ + int i = 0; + struct Address *addr; + + for (i = 0; i < Database_n_rows(conn); i++) { + addr = &conn->db->rows[i]; + + if (strcmp(addr->email, email) == 0) { + return addr; + } + } + + return NULL; +} + +void Database_delete(struct Connection *conn, const char *email) +{ + struct Address *addr = Database_get(conn, email); + if (!addr) { + // already deleted + return; + } + + addr->email = NULL; + addr->name = NULL; +} + +void Database_list(struct Connection *conn) +{ + int i = 0; + struct Database *db = conn->db; + + for (i = 0; i < Database_n_rows(conn); i++) { + struct Address *cur = &db->rows[i]; + + Address_print(cur); + } +} + +size_t get_string_field_size() +{ + size_t string_field_size = DEFAULT_MAX_DATA; + + const char *string_field_size_string = getenv("EX17_MAX_DATA"); + if (string_field_size_string) { + int string_field_size_int = atoi(string_field_size_string); + string_field_size = string_field_size_int; + } + + return string_field_size; +} + +int main(int argc, char *argv[]) +{ + if (argc < 3) { + die(NULL, "USAGE: ex17 [action params]"); + } + + char *filename = argv[1]; + char action = argv[2][0]; + + size_t string_field_size = get_string_field_size(); + + struct Connection *conn = Database_open(filename, action, string_field_size); + + switch (action) { + case 'c': + Database_write(conn); + break; + + case 'g': + if (argc != 4) { + die(conn, "Need an `email` to get"); + } + + Address_print(Database_get(conn, argv[3])); + break; + + case 's': + if (argc != 5) { + die(conn, "Need a `name` and `email` to set"); + } + + Database_set(conn, argv[3], argv[4]); + Database_write(conn); + break; + + case 'd': + if (argc != 4) { + die(conn, "Need an `email` to delete"); + } + + Database_delete(conn, argv[3]); + Database_write(conn); + break; + + case 'l': + Database_list(conn); + break; + + default: + die(conn, "Invalid action: c=create, g=get, s=set, d=del, l=list"); + } + + Database_close(conn); + + return 0; +}