#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; }