0
gcc (GCC) 4.7.0
c89

Hello,

I have trying to save a database of rows to a file (db.dat) using fwrite to write it and retrieve it using fread. However, when I retrive it with fread the data is empty.

I not sure if I should malloc some memory before getting the data. Which I have tried in my example

I am not showing free memory here, as this is just the code snippets.

Many thanks for any suggestions,

This is my structure of the database:
struct address {
    int id;
    int set;
    char *name;
    char *email;
};

struct database {
    struct address **rows;
    size_t data_size;
    size_t max_size;
};

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

Database open:

struct connection* database_open(const char *filename, char mode)
{
    struct connection *conn = NULL;

    conn = malloc(sizeof(struct connection));

    conn->db = malloc(sizeof(struct database));
    if(conn->db == NULL) {
        log_exit("Memory error allocating database", conn);
    }

    if(conn == NULL) {
        log_exit("Memory error allocating connection", conn);
    }

    printf("Create a new database\n");
    conn->fp = fopen(filename, "w");

    if(conn->fp == NULL) {
        log_exit("Failed to open file", conn);
    }

    return conn;
}

creating a database:

void database_create(struct connection *conn, int _max_size, int _data_size)
{
    int i = 0;
    struct address *db_row = NULL;

    /* Set the size of the database by assigning the max number of database rows */
    conn->db->max_size = _max_size;
    conn->db->data_size = _data_size;

    /* Allocate memory for rows and size */
    conn->db->rows = malloc(sizeof(struct address) * _max_size);
    if(!conn->db->rows) {
        log_exit("Memory error alocating rows", conn);
    }

    for(i = 0; i < _max_size; i++) {
        db_row = malloc(sizeof(struct address));

        db_row->id = i;
        db_row->set = 0;
        db_row->name = malloc(_data_size);
        db_row->email = malloc(_data_size);

        /* Add the row to the database */
        conn->db->rows[i] = db_row;
    }
}

Writing the database

void database_write(struct connection *conn)
{
    rewind(conn->fp);

    if(fwrite(conn->db, sizeof(struct database), 1, conn->fp) != 1) {
        log_exit("Failed to write to database.", conn);
    }

    if(fflush(conn->fp) != 0) 
    {
        log_exit("Cannot flush database.", conn);
    }
}

Then I then open the database for r+ and use fread. However, my data that I have set in the id and set variables are empty.

void database_load(struct connection *conn)
{
    /* Clear any errors from file pointer */
    clearerr(conn->fp);

    /* I have had to hack this just so that I can get the
       memory allocated before filling the structure not sure if this is the best way */
    conn->db->rows = malloc(sizeof(struct address) * 1); /* This would be set to how many rows I have - just experimenting with just one */
    conn->db->rows[0] = malloc(sizeof(struct address));
    conn->db->rows[0]->name = malloc(20);
    conn->db->rows[0]->email = malloc(20);

    if(fread(conn->db, sizeof(struct database), 1, conn->fp) != 1) {
        if(feof(conn->fp) != 0) {
            log_exit("Database end of file", conn);
        }

        if(ferror(conn->fp) != 0) {
            log_exit("Database cannot open", conn);
        }
    }    
}

==== EDIT ===

int main(int argc, char **argv)
{
    char *filename = NULL;
    char action = 0;
    int id = 0;
    int rows = 0;
    int size = 0;
    struct connection *conn = NULL;

    if(argc < 3) {
        log_exit("USAGE: ex17 <dbfile> <action> [ action params ]", conn);
    }

    filename = argv[1];
    action = argv[2][0];
    rows = atoi(argv[3]);
    size = atoi(argv[4]);

    conn = database_open(filename, action);

    if(argc > 3) {
        id = atoi(argv[3]);
    }

    switch(action) {
    case 'c':
        database_create(conn, rows, size);
        database_write(conn);
        break;

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

        database_set(conn, id, argv[4], argv[5]);
        database_write(conn);
        break;

    default:
        log_exit("Invalid action, only: c = create, g = get, d = del, l = list", conn);
    }

    database_close(conn);

    return 0;
}
ant2009
  • 27,094
  • 154
  • 411
  • 609
  • Does `database_set` actually set the values correctly? – Tony The Lion Sep 21 '12 at 09:40
  • I cannot get that far. As I need to open the database using the fread. Because I cannot do that, I can't go any further with this problem. Thanks. – ant2009 Sep 21 '12 at 10:18
  • I'm sorry, but you need to write to your file first, before you can read from it. Also I don't see your `database_load` being called in your code anywhere, so I can't tell where you're actually doing the reading. – Tony The Lion Sep 21 '12 at 11:04
  • This is a serialization problem. When you call `fwrite` to write `conn->db` you are writing the address stored in `rows` to file and not the actual structures themselves. You need to save the number of elements of `rows` to file, so you know how many to read in later, then loop through saving each structure. You also have to deal with the `char *` pointers inside the structures by saving their lengths and then writing their data, otherwise again you will just be saving memory addresses to the file and not the actual data. – Kludas Sep 21 '12 at 13:00

1 Answers1

1

Seeing your rows is a double pointer, you need to initialize the first pointer with the sizeof(pointer*), this:

conn->db->rows = malloc(sizeof(struct address) * 1); 

should be:

conn->db->rows = malloc(sizeof(struct address*));  //notice how struct address becomes struct address*

Same with this:

conn->db->rows = malloc(sizeof(struct address) * _max_size);

should be:

  conn->db->rows = malloc(sizeof(struct address*) * _max_size);
Tony The Lion
  • 61,704
  • 67
  • 242
  • 415
  • After making those changes. I am still having the same problem. set and id are still both zero'd after the fread. Didn't really make any different. Thanks. The id would increment based on the number of rows I want. However, this failed to do that. Many thanks for any more suggestions. – ant2009 Sep 21 '12 at 09:05
  • Have you stepped through it with the debugger? – Tony The Lion Sep 21 '12 at 09:17
  • Are you creating the same number of rows to read back into than you wrote to your database file in the first place? – Tony The Lion Sep 21 '12 at 09:17
  • I have gone through with gdb. After fread returns check what the contents are:(gdb) p *conn->db->rows[0] $35 = { id = 0, set = 0, name = 0x6022d0 "", email = 0x6022f0 "" I set the set parameter to 10 and then fwrite it. When I fread it. It is zero. Not sure what is going on there. However, is it this possible to use fread and fwrite with malloc'd memory. I have edited my question with my main function. Thanks. – ant2009 Sep 21 '12 at 09:32