Ex17 (gdb) Failed to write to database. : Bad Address

Here is my code. I am stuck on this error. This happens after the command: ./ex17 db.dat c

In gdb, it says that the text file is busy, but it has just been created using the “w” parm in the Database_open() function.

I tried chmod 777 db.dat (which is really bad) and it won’t open. Any ideas?

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

// define macro variables to use for data size and rows number
#define MAX_DATA 511 + '\0'
#define MAX_ROWS 100

// this is a structure for an address, which is a data row in the database
struct Address
{
    int id;
    int set;
    char name[MAX_DATA];
    char email[MAX_DATA];
};

// a structure of a database that accepts up to 100 rows
struct Database
{
    struct Address rows[MAX_ROWS];
};

// the connection here consists of a file object (.dat) and a Database structure pointer
struct Connection
{
    FILE *file;
    struct Database *db;
};

// this is a function that handles errors, and prints a simple error message
void die(const char *message)
{
    if (errno)
    {
        perror(message);
    }
    else
    {
        printf("ERROR: %s\n", message);
    }

    exit(1);
}

// this function prints the address from the *addr pointer variable
// it prints out each of the fields using pointer notation ->
void Address_print(struct Address *addr)
{
    printf("%d %s %s", addr->id, addr->name, addr->email);
}

// the function loads the database, taking in a connection structure
// here we use fread and it takes 4 parms, a void *ptr (conn->db),
// the number of memory bytes (nmemb) from the stream pointed to 
// by the stream (1), and a FILE *stream. 
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.");
    }
}

// a function of type Connection structure that takes in a FILE *filename
// and a character. The mode decides if it is write-only, read-write, etc...
struct Connection *Database_open(const char *filename, char mode)
{
    // here we are creating a conn pointer of type Connection structure
    // and using malloc to get memory from the computer of the size
    // of the structure Connection
    struct Connection *conn = malloc(sizeof(struct Connection));
    if (!conn)
    {
        die("Memory error.");
    }

    // allocate the Database structure size to the conn->db
    conn->db = malloc(sizeof(struct Database));
    if (mode == 'c')
    {
        conn->file = fopen(filename, "w");
    }
    else
    {
        conn->file = fopen(filename, "r+");

        if (conn->file)
        {
            Database_load(conn);
        }
    }
    if (!conn->file)
    {
        die("Shitballs, failed to open the file.");
    }

    return conn;
}

// here we have a function that closes the database connection
// fairly straightforward
void Database_close(struct Connection *conn)
{
    if (conn)
    {
        if (conn->file)
        {
            fclose(conn->file);
        }

        if (conn->db)
        {
            free(conn->db);
        }
        free(conn);
    }
}

// this function allows us to write to the database using the 
// Connection structure
void Database_write(struct Connection *conn)
{
    // rewind() accepts a FILE *stream and simply sets the file
    // position indicator to the beginning of the file taken in
    // through conn->file	
    rewind(conn->file);

    // like fread(), this function uses the same parms but instead of
    // reading, it is writing to the FILE *stream provided in conn->file
    int rc = fwrite(conn->file, sizeof(struct Database), 1, conn->file);
    
    if (rc != 1)
    {
        die("Failed to write to database.");
    }
    
    // this function forces a discard of all buffer data from the user space
    rc = fflush(conn->file);
    if (rc != 1)
    {
        die("Cannot flush the database.");
    }
}

// this function creates the database using the Connection structure as a parameter
// it creates a prototype addr structure to initialize with the MAX_ROWS and sets the 
// .id to 0-99 for all rows in the skeleton database
void Database_create(struct Connection *conn)
{
    int i = 0;

    for (i = 0; i < MAX_ROWS; i++)
    {
        // make a prototype to initialize it
        struct Address addr = {.id = i, .set = 0};
        // then assign it
        conn->db->rows[i] = addr;
    }
}

// this function sets the id, name, email in the database with the arguments provided
// by the user. it is done by using 's' as the second parameter when running the program
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;

    // WARNING: bug, need to fix this
    char *res = strncpy(addr->name, name, MAX_DATA);
    // demonstrate the strncpy bug
    if (!res)
    {
        die("Name copy failed.");
    }

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


void Data_set(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_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 *current_address = &db->rows[i];

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

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

    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("Not that many records.");
    }

    switch(action)
    {
        case 'c':
            Database_create(conn);
            Database_write(conn);
            break;
        case 'g':
            if(argc != 4)
            {
                die("Need an id to 'get'");
            }
        case 's':
            if(argc != 6)
            {
                die("Need id, name, and 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;
}

When I run it in Valgrind with ‘db.dat c’ args I get this:

==22099== Memcheck, a memory error detector
==22099== Copyright © 2002-2017, and GNU GPL’d, by Julian Seward et al.
==22099== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==22099== Command: ./ex17 db.dat c
==22099==
==22099== Syscall param write(buf) points to uninitialised byte(s)
==22099== at 0x4F4E224: write (write.c:27)
==22099== by 0x4EC928C: _IO_file_write@@GLIBC_2.2.5 (fileops.c:1203)
==22099== by 0x4EC9BFE: new_do_write (fileops.c:457)
==22099== by 0x4EC9BFE: _IO_file_xsputn@@GLIBC_2.2.5 (fileops.c:1277)
==22099== by 0x4EBDA56: fwrite (iofwrite.c:39)
==22099== by 0x108C8A: Database_write (in /home/abbyrjones72/Documents/ZedShawLCTHW/ex17)
==22099== by 0x1090FE: main (in /home/abbyrjones72/Documents/ZedShawLCTHW/ex17)
==22099== Address 0x52483f4 is 4 bytes inside a block of size 552 alloc’d
==22099== at 0x4C31B0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==22099== by 0x4EBCF29: __fopen_internal (iofopen.c:65)
==22099== by 0x4EBCF29: fopen@@GLIBC_2.2.5 (iofopen.c:89)
==22099== by 0x108B93: Database_open (in /home/abbyrjones72/Documents/ZedShawLCTHW/ex17)
==22099== by 0x10907A: main (in /home/abbyrjones72/Documents/ZedShawLCTHW/ex17)
==22099==
==22099== Invalid read of size 1
==22099== at 0x4C391B8: mempcpy (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==22099== by 0x4ECC59B: _IO_default_xsputn (genops.c:404)
==22099== by 0x4EC9B02: _IO_file_xsputn@@GLIBC_2.2.5 (fileops.c:1287)
==22099== by 0x4EBDA56: fwrite (iofwrite.c:39)
==22099== by 0x108C8A: Database_write (in /home/abbyrjones72/Documents/ZedShawLCTHW/ex17)
==22099== by 0x1090FE: main (in /home/abbyrjones72/Documents/ZedShawLCTHW/ex17)
==22099== Address 0x52613f0 is 97,616 bytes inside an unallocated block of size 4,086,080 in arena “client”
==22099==
==22099== Invalid read of size 1
==22099== at 0x4C391C6: mempcpy (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==22099== by 0x4ECC59B: _IO_default_xsputn (genops.c:404)
==22099== by 0x4EC9B02: _IO_file_xsputn@@GLIBC_2.2.5 (fileops.c:1287)
==22099== by 0x4EBDA56: fwrite (iofwrite.c:39)
==22099== by 0x108C8A: Database_write (in /home/abbyrjones72/Documents/ZedShawLCTHW/ex17)
==22099== by 0x1090FE: main (in /home/abbyrjones72/Documents/ZedShawLCTHW/ex17)
==22099== Address 0x52613f2 is 97,618 bytes inside an unallocated block of size 4,086,080 in arena “client”
==22099==
ERROR: Cannot flush the database.
==22099==
==22099== HEAP SUMMARY:
==22099== in use at exit: 103,768 bytes in 3 blocks
==22099== total heap usage: 5 allocs, 2 frees, 108,888 bytes allocated
==22099==
==22099== LEAK SUMMARY:
==22099== definitely lost: 0 bytes in 0 blocks
==22099== indirectly lost: 0 bytes in 0 blocks
==22099== possibly lost: 0 bytes in 0 blocks
==22099== still reachable: 103,768 bytes in 3 blocks
==22099== suppressed: 0 bytes in 0 blocks
==22099== Rerun with --leak-check=full to see details of leaked memory
==22099==
==22099== For counts of detected and suppressed errors, rerun with: -v
==22099== Use --track-origins=yes to see where uninitialised values come from
==22099== ERROR SUMMARY: 801 errors from 3 contexts (suppressed: 0 from 0)

Little error in Database_write: The first parameter of fwrite must be conn->db.

Also, fflush returns 0 on success, so you need to change that error check.

1 Like

Thank you for this help. I guess the book was wrong, but I also had another error where I passed in ‘size_t 1’ as a parm in Database_load().

Thank you so much answering this question. I appreciate you.

A free service run by Zed A. Shaw for learncodethehardway.org.