Exercise 17 Extra Credit 2

Hi everyone,

First thanks for your book “Learn C the Hard Way”!
I am French, I hope my english will be clear enought.
I am almost stuck (already) with Exercise 17 and the extra credit 2:“Change the code to accept parameters for MAX_DATA and MAX_ROWS, store them in a Database structure and write that to the file…”

I managed to get the code working with the following declarations:

struct address {
	int id;
	int set;
	char *name;
	char *email;
};

struct database {
	int max_rows;
	int max_data;
	struct address **rows;
};

struct connection {
	FILE *file;
	struct database *db;
};

I am wondering if the struct address **rows is the best way to answer your exercise? (I also managed to do the job with struct address *rows).
Here is my work:

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

struct address {
	int id;
	int set;
	char *name;
	char *email;
};

struct database {
	int max_rows;
	int max_data;
	struct address **rows;
};

struct connection {
	FILE *file;
	struct database *db;
};

void database_create(struct connection *, const int, const int);
void die(const char *);
void address_print(struct address *);
void database_load(struct connection *);
struct connection * database_open(const char *, char);
void database_close(struct connection *);
void database_write(struct connection  *);
void database_create(struct connection *, const int, const int);

void die(const char *message)
{
	if (errno) {
		perror(message);
	} else {
		printf("ERROR: %s\n", message);
	}
	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->max_rows, sizeof(int), 1, conn->file);
	if (rc != 1) {
		die("Failed to load database.");
	}

	rc = fread(&conn->db->max_data, sizeof(int), 1, conn->file);
	if (rc != 1) {
		die("Failed to load database.");
	}
	database_create(conn, conn->db->max_rows, conn->db->max_data);

	int i = 0;
	for (i = 0; i < conn->db->max_rows; i++) {
		rc = fread(&conn->db->rows[i]->id, sizeof(int), 1, conn->file);
		if (rc != 1) {
			die("Failed to load database.");
		}	
		rc = fread(&conn->db->rows[i]->set, sizeof(int), 1, conn->file);
		if (rc != 1) {
			die("Failed to load database.");
		}	
		rc = fread(conn->db->rows[i]->name, conn->db->max_data * sizeof(char), 1, conn->file);
		if (rc != 1) {
			die("Failed to load database.");
		}	
		rc = fread(conn->db->rows[i]->email, conn->db->max_data * sizeof(char), 1, conn->file);
		if (rc != 1) {
			die("Failed to load database.");
		}	
	}
}

struct connection * database_open(const char *filename, char mode)
{
	struct connection *conn = malloc(sizeof(struct connection));
	if (!conn) {
		die("Memory error.");
	} 

	conn->db = malloc(sizeof(struct database));
	if (!conn->db) {
		die("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("Failed to open the file.");
	}
	return conn;
}

void database_close(struct connection *conn)
{
	if (conn->file) {
		fclose(conn->file);
	}
	if (conn->db) {
		free(conn->db);
	}
	free(conn);
}

void database_write(struct connection  *conn)
{
	rewind(conn->file);
	int rc = fwrite(&conn->db->max_rows, sizeof(int), 1, conn->file);
	if (rc != 1) {
		die("Failed to write database.");
	}
	rc = fwrite(&conn->db->max_data, sizeof(int), 1, conn->file);
	if (rc != 1) {
		die("Failed to write database.");
	}
	int i = 0;
	for (i = 0; i < conn->db->max_rows; i++) {
		rc = fwrite(&conn->db->rows[i]->id, sizeof(int), 1, conn->file);
		if (rc != 1) {
			die("Failed to write database.");
		}	
		rc = fwrite(&conn->db->rows[i]->set, sizeof(int), 1, conn->file);
			if (rc != 1) {
			die("Failed to write database.");
		}
		rc = fwrite(conn->db->rows[i]->name, conn->db->max_data * sizeof(char), 1, conn->file);
			if (rc != 1) {
			die("Failed to write database.");
		}
		rc = fwrite(conn->db->rows[i]->email, conn->db->max_data * sizeof(char), 1, conn->file);
		if (rc != 1) {
			die("Failed to write database.");
		}
	}
	rc = fflush(conn->file);
	if (rc == -1) {
		die("Cannot flush database.");
	}
}

void database_create(struct connection *conn, const int MAX_ROWS, const int MAX_DATA)
{
	conn->db->max_rows = MAX_ROWS;
	conn->db->max_data = MAX_DATA;
	conn->db->rows = (struct address **)malloc(MAX_ROWS * sizeof(struct address *));
	int i = 0;
	for (i = 0; i < MAX_ROWS; i++) {
		conn->db->rows[i] = (struct address *)malloc(sizeof(struct address));
		conn->db->rows[i]->name = (char *)malloc(MAX_DATA * sizeof(char));
		conn->db->rows[i]->email = (char *)malloc(MAX_DATA * sizeof(char));
		conn->db->rows[i]->id = i;
		conn->db->rows[i]->set = 0;
		}

}

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("Already set, delete it first.");
	}
	addr->set = 1;
	char *res = strncpy(addr->name, name, conn->db->max_data);
	addr->name[conn->db->max_data-1] = '\0';
	if (!res) {
		die("Name copy failed.");
	}

	res = strncpy(addr->email, email, conn->db->max_data);
	addr->email[conn->db->max_data-1] = '\0';
	if(!res) {
		die("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("ID not set");
	}

}

void database_list(struct connection *conn)
{
	int i = 0;
	struct database *db = conn->db;
	for (i = 0; i < conn->db->max_rows; i++) {
		struct address *cur = db->rows[i];
		if (cur->set) {
			address_print(cur);
		}
	}
}

void database_delete(struct connection *conn, int id)
{
	conn->db->rows[id]->id = 0;
	conn->db->rows[id]->set = 0;
}

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

	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 >= conn->db->max_rows && action != 'c') {
		die("There's not that many records.");
	}

	switch (action) {
		case 'c':
			database_create(conn, 5, 10);
			database_write(conn);
		break;

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

		case 's':
			if (argc != 6) {
				die("Need id, name, email to set");
			}
			database_set(conn, id, argv[4], argv[5]);
			database_write(conn);
		break;

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

		case 'l':
			database_list(conn);
		break;
		default:
			die("Invalid action: c=create, g=get, s=set, d=del, l=list");
	}

	database_close(conn);

	return 0;
}

I still don’t understand why i can’t write/read a struct in one shot when one of the element is a pointer or pointer on pointer…

If you can help me a little bit I would appreciate a lot! I am not so confident with what I read online…

Have a good day!

Richard

Hi,

I am still waiting for any help…
I hope my question was clear enough, I guess many other people are facing issues with ex17 according to the number of views of this topic.
It’s a bit frustrating to have a look online each time you are stuck, because you can’t really know if the answer is really the one requested by the author.
I understand that there is no only one way to write a program but some guidelines would be useful to learn the right way.

Thanks for your help,
Richard

Depends. If you use an array of pointers, the data will be stored in individually allocated chunks of memory. That comes with additional costs for memory management and may be slightly less efficient if you need to access the data many times because it’s all over the place. On the upside, the array itself will be much smaller. Otherwise you’d have everything in a single big chunk.

In this case the data objects are small so it doesn’t really matter. But if your objects are large and you don’t know how many you’ll get beforehand, you should consider storing pointers and allocating each chunk individually when needed.

Because the data that the pointer, well, points to is in a different memory location and you have to decide how to handle that yourself.

1 Like

I am not sure Zed responds here anymore. I hope he is okay in the days of COVID. I love his teaching style and I think he writes excellent C code, from what I know in my limited experience.