The problem is trying to change the Address Struct’s array sizes. I’m not sure where I’m exactly supposed to access them to change.
I’ve tried to change MAX_DATA so that it’s not a constant. However, this throws a compiler error. Trying to change MAX_DATA within the running code doesn’t work either. I’m right now passing the value from commandline arguments, however I’m not sure how to proceed.
My questions are:
How do you dynamically resize the address struct array?
Where would you access this code from?
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#define MAX_DATA 512 //This is the Max Data size. However, this is used as
#define MAX_ROWS 100
#define OUTPUT_ON_NOTHING 1 //
struct Address { //Defines an address as a fixed size. This makes it less efficent but easier to store and read.
int id;
int set;
char name[MAX_DATA];
char email[MAX_DATA];
};
struct Database { //Database also a fixed size structure. Allows you to write entire thing to disk in one move later on.
struct Address rows[MAX_ROWS];
};
struct Connection {
FILE *file; //Introduced to new fucntions like fopen, fread, fclose, rewind . Defined by standard C library
struct Database *db;
};
//Forward Declarations, prevent chicken and egg problem. Where you're using a function before its defined. Allows keeping code more organized
void Database_close(struct Connection *conn);
void die(const char *message, struct Connection *conn) //In small programs can make a single fucntion that kills the program with an error if there's anything wrong. This case called the die function.
{
if(errno){ //Where you have an error return from a fuction, it will usually set an external variable called erno to say exactly what happened. These are just numbers.
perror(message); //Use perror to print the error message
} else {
printf("Closin connection to database...\n");
Database_close(conn);
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,sizeof(struct Database), 1, conn->file);
if(rc != 1)
die("Failed to load database.",conn);
}
struct Connection *Database_open(const char *filename, char mode)
{
struct Connection *conn = malloc(sizeof(struct Connection)); //Requests from the OS the required amount of memory.
if (!conn) //Null is O. So this is the same as asking if (ptr == NULL) die("fail!");
die("Memory error",conn);
conn->db = malloc(sizeof(struct Database));
if (!conn)
die("Memory error",conn);
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",conn);
return conn;
}
void Database_close(struct Connection *conn)
{
if(conn){
if(conn->file)
fclose(conn->file);
if(conn->db)
free(conn->db);
}
}
void Database_write(struct Connection *conn)
{
rewind(conn->file);
int rc = fwrite(conn->db,sizeof(struct Database), 1, conn->file);
if (rc != 1)
die("Failed to write to database",conn);
rc = fflush(conn->file);
if (rc == -1)
die("Cannot flush database.",conn);
}
/**
Creates a database with name and address of set length. If null is passed for length or less than 0 is passed. Then 512 is used.
*/
void Database_create(struct Connection *conn, int length)
{
int recievedLength = length;
if(length <= 0){
recievedLength = MAX_DATA;
}
int i = 0;
printf("%d",recievedLength);
for (i = 0; i < MAX_ROWS; i++)
{
//make a prototype to initialize it
struct Address addr = {.id = i,.set=0};
// then just assign it
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]; //Nested struct pointers. This code reads "get the i element of rows, which is in db, which in conn, then get the address of it"
if(addr->set)
die("Already set, delete it first",conn);
addr->set = 1;
// WARNING: bug, read the "How to Break It" and fix this
char *res = strncpy(addr->name,name, MAX_DATA);
// demonstrate the strncpy bug
if(!res)
die("Name copy failed",conn);
res = strncpy(addr->email, email, MAX_DATA);
if(!res)
die("Email copy failed",conn);
}
void Database_get(struct Connection *conn, int id)
{
struct Address *addr = &conn->db->rows[id];
if(addr->set) {
Address_print(addr);
} else {
die("ID is not set",conn);
}
}
void Database_delete(struct Connection *conn, int id)
{
struct Address addr = {.id = id, .set = 0};//Creating a temporarly local address, initalizng its id and set fields and then copying it into the rows array by assinging it tothe element I want.
//This trick maes sure that all the fields except set and id are initialized to zeros and it's actually easier to write.
//WARNING: Don't use memcpy to do these kinds of struct copying operatins. Modern C allows you to simply assing one struct to another and it'll handle the copying for you.
conn->db->rows[id] = addr;
}
void Database_list(struct Connection *conn)
{
int i = 0;
int numTimesOutputted = 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);
numTimesOutputted++; //WARNING: Possible Stack overflow
}
}
if(numTimesOutputted == 0){
if(OUTPUT_ON_NOTHING == 1){
printf("<Database Empty>\n");
}
}
}
int main(int argc, char *argv[])
{
//HEre I proccess complex argument. This isn't the BEST way to do it.
printf("%d",MAX_DATA);
if(argc < 3){
printf("Invalid action: c=create, g=get, s=set, d=del, l=list\n");
die("USAGE: ex17 <dbfile> <action> [action parms]",NULL);}
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]); //Converting a String to an int
if(id >= MAX_ROWS) die("There's not that many records.",NULL);
switch(action) {
case 'c':
if(argc > 3){
Database_create(conn,id );
}
Database_create(conn,-1);//Negative number or zero, default to MAX_DATA constant defined in the header of this file. //Allocating large memeory using Malloc. Malloc creates data on the heap. Whenever MALLOC is used. You're using heap memory. When you're creating variables. That's using stack memory.
Database_write(conn);
break;
case 'g':
if (argc != 4)
die("Need an id to get",conn);
Database_get(conn, id);
break;
case 's':
if (argc != 6)
die("Need id, name, email to set",conn);
Database_set(conn,id,argv[4],argv[5]);
Database_write(conn);
break;
case 'd':
if (argc != 4)
die("Need id to delete",conn);
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",conn);
}
Database_close(conn);
return 0;
}