Linux
Application
Development

Michael K. Johnson
Erik W. Troan

phones.c

/* phones.c -- simple phone database to illustrate Berkeley db */

/* This implements a very simple phone database. Full usage 
   information is given in the text. */

#include <alloca.h>
#include <db.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

void usage(void) {
    fprintf(stderr, "usage: phones -a [-f] <name> <phone>\n");
    fprintf(stderr, "              -d <name>\n");
    fprintf(stderr, "              -q <name>\n");
    fprintf(stderr, "              -l\n");
    exit(1);
}

/* Opens the database $HOME/.phonedb. If writeable is nonzero,
   the database is opened for updating. If writeable is 0, the
   database is opened read-only. An appropriate lock is put on
   the database in either case. */ 
DB * openDatabase(int writeable) {
    DB * db;
    char * filename;
    int flags;
    struct flock lock;

    /* Set up a lock on the entire file (l_len = 0 means
       lock the whole file) */
    lock.l_whence = SEEK_SET;
    lock.l_start = 0;
    lock.l_len = 0;

    /* Set the open and lock modes */
    if (writeable) {
        flags = O_CREAT | O_RDWR;
        lock.l_type = F_WRLCK;
    } else {
        flags = O_RDONLY;
        lock.l_type = F_RDLCK;
    }

    filename = alloca(strlen(getenv("HOME")) + 20);
    strcpy(filename, getenv("HOME"));
    strcat(filename, "/.phonedb");

    db = dbopen(filename, flags, 0666, DB_HASH, NULL);
    if (!db) {
        fprintf(stderr, "failed to open %s: %s\n", filename, 
                strerror(errno));
        return NULL;
    }

    /* Now that the database is open, lock it */
    if (fcntl(db->fd(db), F_SETLK, &lock)) {
        if (errno == EAGAIN) 
            fprintf(stderr, "the phone database is already "
                            "locked\n");
        else
            fprintf(stderr, "failed to lock database: %s\n", 
                            strerror(errno));
        return NULL;
    }

    return db;
}

/* add a new record to the database; this parses the
   command-line arguments directly */
int addRecord(int argc, char ** argv) {
    DB * db;
    char * name, * phone;
    DBT key, value;
    int rc = 0;
    int overwrite = 0;
   
    /* check for our parameters; -f means overwrite an
       existing entry, and the name and phone number should
       be all that remains */
    if (!argc) usage();
    if (!strcmp(argv[0], "-f")) {
        overwrite = 1;
        argc--, argv++;
    }

    if (argc != 2) usage();

    name = argv[0];
    phone = argv[1];

    /* open the database for writing */
    if (!(db = openDatabase(1))) return 1;

    key.data = name;
    /* the +1 writes the trailing '\0' to the file */
    key.size = strlen(name) + 1;

    /* if we shouldn't overwrite an existing entry, check
       to see if this name is already used */
    if (!overwrite) {
        rc = db->get(db, &key, &value, 0);
        if (rc == -1) {
            fprintf(stderr, "get failed: %s\n", strerror(errno));
            rc = 1;
        } else if (!rc) {
            fprintf(stderr, "%s already listed as %s\n", name, 
                    (char *) value.data);
            rc = 1;
        } else {
            rc = 0 ;
        }
    }

    /* if everything has worked so far, update the database */
    if (!rc) {
        value.data = phone;
        value.size = strlen(phone) + 1;

        if (db->put(db, &key, &value, 0)) {
            fprintf(stderr, "put failed: %s\n", strerror(errno));
            rc = 1;
        }
    }

    db->close(db);

    return rc;
}

/* looks up a name, and prints the phone number associated
   with it; parses the command line directly */
int queryRecord(int argc, char ** argv) {
    DB * db;
    DBT key, value;
    int rc;

    /* only one argument is expected, a name to look up */
    if (argc != 1) usage();

    /* open the database for reading */
    if (!(db = openDatabase(0))) return 1;

    /* set up the key to look up */
    key.data = argv[0];
    key.size = strlen(argv[0]) + 1;

    rc = db->get(db, &key, &value, 0);
    if (rc == -1) {
        fprintf(stderr, "get failed: %s\n", strerror(errno));
        rc = 1;
    } else if (rc) {
        fprintf(stderr, "%s is not listed\n", argv[0]);
        rc = 1;
    } else {
        printf("%s %s\n", argv[0], (char *) value.data);
        rc = 0;
    }

    db->close(db);

    return rc;
}

/* delete the specified record; the name is passed as a
   command-line argument */
int delRecord(int argc, char ** argv) {
    DB * db;
    DBT key;
    int rc;

    /* only a single argument is expected */
    if (argc != 1) usage();

    /* open the database for updating */
    if (!(db = openDatabase(1))) return 1;

    /* set up the key */
    key.data = argv[0];
    key.size = strlen(argv[0]) + 1;

    rc = db->del(db, &key, 0);
    if (rc == -1) {
        fprintf(stderr, "del failed: %s\n", strerror(errno));
        rc = 1;
    } else if (rc) {
        fprintf(stderr, "%s is not listed\n", argv[0]);
        rc = 1;
    }

    db->close(db);

    return rc;
}

/* lists all of the records in the database */
int listRecords(void) {
    DB * db;
    DBT key, value;
    int rc;
    int flags = R_FIRST;

    /* open the database read-only */
    if (!(db = openDatabase(0))) return 1;

    /* iterate over all of the records */
    while (!(rc = db->seq(db, &key, &value, flags))) {
        flags = R_NEXT;
        printf("%s %s\n", (char *) key.data, (char *) value.data);
    }

    if (rc == -1) {
        fprintf(stderr, "seq failed: %s\n", strerror(errno));
        rc = 1;
    } else {
        rc = 0;
    }

    db->close(db);

    return rc;
}

int main(int argc, char ** argv) {
    if (argc == 1) usage();

    /* look for a mode flag, and call the appropriate function
       with the remainder of the arguments */
    if (!strcmp(argv[1], "-a")) 
        return addRecord(argc - 2, argv + 2);
    else if (!strcmp(argv[1], "-q")) 
        return queryRecord(argc - 2, argv + 2);
    else if (!strcmp(argv[1], "-d")) 
        return delRecord(argc - 2, argv + 2);
    else if (!strcmp(argv[1], "-l")) {
        if (argc != 2) usage();
        return listRecords();
    }

    usage();  /* did not recognize any options */
    return 0; /* doesn't get here due to usage() */
}