Day 1 of breaking Ex17 got me a decent start on the extra credit

While I get a little over my head with the Ex17 extra credit for working the variable sizes into the database file, but I had a really productive day breaking Ex17 about as horribly as anyone can. The first hurdle I’m finally over is making everything use the Database rows as a pointer instead of a fixed sized declaration. I think I nailed all the memory errors in Valgrind too. I thought I would share and shout out for any feedback if anyone else is working on this or has before. I obviously still have a lot of work to do. But I like to think it’s coming together nicely so far.

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

#define MAX_DATA 512
#define MAX_ROWS 100

struct Address {
    int id;
    int set;
    char name[MAX_DATA];
    char email[MAX_DATA];
};

struct Database {
    size_t max_data;
    size_t max_rows;
	struct Address *rows;
};

struct Connection {
    FILE *file;
    struct Database *db;
};

void Database_close(struct Connection *conn);

void die(struct Connection *conn, const char *message)
{
    if (errno) {
        perror(message);
    } else {
        printf("ERROR: %s\n", message);
    }

    Database_close(conn);
    exit(1);
}

void Address_print(struct Address *addr)
{
    printf("%d %s %s\n", addr->id, addr->name, addr->email);
}

void Database_load(struct Connection *conn)
{

    int rc = fread(conn->db, sizeof(struct Database), 1, conn->file);
    if (rc != 1)
        die(conn, "Failed to load database.");
    
    conn->db->rows = (struct Address *)malloc(sizeof(struct Address) * MAX_ROWS);
    if (!conn->db->rows)
        die(conn, "memory error: rows");

    rc = fread(conn->db->rows, sizeof(struct Address) * MAX_ROWS, 1, conn->file);
    if (rc != 1)
        die(conn, "Failed to load rows.");
    
}

struct Connection *Database_open(const char *filename, char mode)
{
    struct Connection *conn = malloc(sizeof(struct Connection));
    if (!conn)
        die(conn, "Memory error");
    
    conn->db = calloc(1, sizeof(struct Database));
    
    if (!conn->db)
        die(conn, "Memory error");
    
    if (mode == 'c') {
        conn->file = fopen(filename, "w");
    } else {
        conn->file = fopen(filename, "r+");

        if (conn->file) {
            Database_load(conn);
        }
    }

    if (!conn->file)
        die(conn, "Failed to open the file");

    return conn;
}

void Database_close(struct Connection *conn)
{
    if (conn) {
        if (conn->file)
            fclose(conn->file);
        if (conn->db->rows)
            free(conn->db->rows);
        if (conn->db)
            free(conn->db);
        free(conn);
    }
}

void Database_write(struct Connection *conn)
{
    rewind(conn->file);
    if (errno)
        die(conn, "rewind error");

    int rc = fwrite(conn->db, sizeof(struct Database), 1, conn->file);
    if (rc != 1)
        die(conn, "Failed to write database.");

    rc = fwrite(conn->db->rows, (sizeof(struct Address) * MAX_ROWS), 1, conn->file);
    if (rc != 1)
        die(conn, "Failed to write rows.");

    rc = fflush(conn->file);
    if (rc == -1)
        die(conn, "Cannot flush database.");
}

void Database_create(struct Connection *conn)
{
    int i = 0;
    conn->db->rows = (struct Address *)malloc(sizeof(struct Address) * MAX_ROWS);
    
    for (i = 0; i < MAX_ROWS; i++) {
        struct Address addr = {.id = i,.set = 0 };
        strncpy(addr.name, " ", MAX_DATA);
        strncpy(addr.email, " ", MAX_DATA);
        conn->db->rows[i] = addr;
    }
}

void Database_set(struct Connection *conn, int id, const char *name,
        const char *email)
{
    struct Address *addr = &conn->db->rows[id];
    if (addr->set)
        die(conn, "Already set, delete it first");
    addr->set = 1;

    char *res = strncpy(addr->name, name, MAX_DATA);
    if (!res)
        die(conn, "Name copy failed");

    res = strncpy(addr->email, email, MAX_DATA);
    if (!res)
        die(conn, "Email copy failed");
}

void Database_get(struct Connection *conn, int id)
{
    struct Address *addr = &conn->db->rows[id];

    if (addr->set) {
        Address_print(addr);
    } else {
        die(conn, "ID is not set");
    }
}

void Database_delete(struct Connection *conn, int id)
{
    struct Address addr = {.id = id,.set = 0 };
    conn->db->rows[id] = addr;
}

void Database_list(struct Connection *conn)
{
    int i = 0;
    struct Database *db = conn->db;

    for (i = 0; i < MAX_ROWS; i++) {
        struct Address *cur = &db->rows[i];

        if (cur->set) {
            Address_print(cur);
        }
    }
}

int main(int argc, char *argv[])
{
    if (argc < 3)
        die(NULL, "USAGE: ex17 <dbfile> <action> [action params]");

    char *filename = argv[1];
    char action = argv[2][0];
    struct Connection *conn = Database_open(filename, action);
    int id = 0;

    if (argc > 3) id = atoi(argv[3]);
    if (id >= MAX_ROWS) die(conn, "There's not that many records.");

    switch (action) {
        case 'c':
            Database_create(conn);
            Database_write(conn);
            break;

        case 'g':
            if (argc != 4)
                die(conn, "Need an id to get");

            Database_get(conn, id);
            break;

        case 's':
            if (argc != 6)
                die(conn, "Need id, name, email to set");

            Database_set(conn, id, argv[4], argv[5]);
            Database_write(conn);
            break;

        case 'd':
            if (argc != 4)
                die(conn, "Need id to delete");

            Database_delete(conn, id);
            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;
}
1 Like

Awesome. Later in the book you learn about my Awesome Debug Macros and they’ll clean this code up very well. About the only other thing you could try is feeding a garbage file to it and see what breaks.

1 Like

Debugging is definitely priceless. I’ve never used gdb because I never understood it nor did I develop in a language that would be such a headache without it. But it’s been fun and educational. Definitely looking forward to those debugging macro’s later. But I’m not disappointed that I did most of this the hard way without them. I’ve learned a lot from making a mess of things. And I’m not done yet with ex17. I think I’ll have a pretty good grasp of how awesome those macro’s really are when I finally get there :slight_smile:

Now I officially have the name and email converted to pointers and all Valgrind errors fixed. I have to admit that it’s easy to overthink this one and make the code more confusing. But doing this extra credit definitely puts into practice a lot of the things I learned so far and is sharpening my debugging skills. Well worth the initial frustration lol.

Woot! I finally did it. Storing the sizes in the file. Setting them via commandline, defaulting to 512 & 100 if not specified. And all memory errors resolved. Man that feels good. Onward!

2 Likes

Sweet! Yes, I tell people now that you should learn C because it’s so broken it makes your debugging skills strong.

1 Like

:laughing: exactly! And it really has. I’m starting to really learn my way around in the debugging tools. And it keeps me reflecting on the basics because sometimes it makes you question everything you know because everything LOOKS right, but is horribly wrong in some (tiny?) way.

I officially finished ALL the extra credit for ex17 too. Just getting around to ex18 today. I’m already excited about that one.

Oh I got to ex19 and learned about the macros. Oh I learned about predefined ones, variadic ones, functional ones, conditionals, and so on. Ohhh I do love these macro’s. There’s just no other word for it is there? They are awesome.

Your advanced debugging technique is the one n same that I myself have been using to great effect for years as a web developer. It extended into other languages very easily. But now I’m finally seeing how to get some real power out of it in C with macros/etc. I’m lovin this stuff. But I have no regrets doing things the hard way so far. I got a reasonably clear idea of what it takes to get code working without them.

2 Likes

I’m actually on ex23 now doing the extra credit. Talk about training the macro skills.