Skip to content
Snippets Groups Projects
Select Git revision
  • a45c80a44c1647f2214267d567bde49886e4cc2a
  • main default
2 results

old.py

Blame
  • sdbsc.c 20.88 KiB
    #include <stdio.h>
    #include <stdlib.h>
    #include <fcntl.h> //c library for system call file routines
    #include <string.h>
    #include <sys/stat.h>
    #include <unistd.h>
    #include <stdbool.h>
    
    // database include files
    #include "db.h"
    #include "sdbsc.h"
    
    /*
     *  open_db
     *      dbFile:  name of the database file
     *      should_truncate:  indicates if opening the file also empties it
     *
     *  returns:  File descriptor on success, or ERR_DB_FILE on failure
     *
     *  console:  Does not produce any console I/O on success
     *            M_ERR_DB_OPEN on error
     *
     */
    int open_db(char *dbFile, bool should_truncate)
    {
        // Set permissions: rw-rw----
        // see sys/stat.h for constants
        mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
    
        // open the file if it exists for Read and Write,
        // create it if it does not exist
        int flags = O_RDWR | O_CREAT;
    
        if (should_truncate)
            flags += O_TRUNC;
    
        // Now open file
        int fd = open(dbFile, flags, mode);
    
        if (fd == -1)
        {
            // Handle the error
            printf(M_ERR_DB_OPEN);
            return ERR_DB_FILE;
        }
    
        return fd;
    }
    
    /*
     *  get_student
     *      fd:  linux file descriptor
     *      id:  the student id we are looking forname of the
     *      *s:  a pointer where the located (if found) student data will be
     *           copied
     *
     *  returns:  NO_ERROR       student located and copied into *s
     *            ERR_DB_FILE    database file I/O issue
     *            SRCH_NOT_FOUND student was not located in the database
     *
     *  console:  Does not produce any console I/O used by other functions
     */
    int get_student(int fd, int id, student_t *s)
    {
        // TODO
        off_t offset = id * STUDENT_RECORD_SIZE;
        if (lseek(fd, offset, SEEK_SET) == -1) {
            return ERR_DB_FILE;
        }
    
        ssize_t bytes_read = read(fd, s, STUDENT_RECORD_SIZE);
        if (bytes_read != STUDENT_RECORD_SIZE) {
            return ERR_DB_FILE;
        }
    
        if (memcmp(s, &EMPTY_STUDENT_RECORD, STUDENT_RECORD_SIZE) == 0) {
            return SRCH_NOT_FOUND;
        }
    
        return NO_ERROR;
    }
    
    /*
     *  add_student
     *      fd:     linux file descriptor
     *      id:     student id (range is defined in db.h )
     *      fname:  student first name
     *      lname:  student last name
     *      gpa:    GPA as an integer (range defined in db.h)
     *
     *  Adds a new student to the database.  After calculating the index for the
     *  student, check if there is another student already at that location.  A good
     *  way is to use something like memcmp() to ensure that the location for this
     *  student contains all zero byes indicating the space is empty.
     *
     *  returns:  NO_ERROR       student added to database
     *            ERR_DB_FILE    database file I/O issue
     *            ERR_DB_OP      database operation logically failed (aka student
     *                           already exists)
     *
     *
     *  console:  M_STD_ADDED       on success
     *            M_ERR_DB_ADD_DUP  student already exists
     *            M_ERR_DB_READ     error reading or seeking the database file
     *            M_ERR_DB_WRITE    error writing to db file (adding student)
     *
     */
    int add_student(int fd, int id, char *fname, char *lname, int gpa)
    {
        // TODO
        off_t offset = id * STUDENT_RECORD_SIZE;
        student_t exist;
    
        if (lseek(fd, offset, SEEK_SET) == -1) {
            printf(M_ERR_DB_READ);
            return ERR_DB_FILE;
        }
    
        ssize_t bytes_read = read(fd, &exist, STUDENT_RECORD_SIZE);
        if (bytes_read == 0) {
            memset(&exist, 0, sizeof(exist));
        } else if (bytes_read != STUDENT_RECORD_SIZE) {
            printf(M_ERR_DB_READ);
            return ERR_DB_FILE;
        }
    
        if (memcmp(&exist, &EMPTY_STUDENT_RECORD, STUDENT_RECORD_SIZE) != 0) {
            printf(M_ERR_DB_ADD_DUP, id);
            return ERR_DB_OP;
        }
    
        student_t new_student = {0};
        new_student.id = id;
        strncpy(new_student.fname, fname, sizeof(new_student.fname) - 1);
        strncpy(new_student.lname, lname, sizeof(new_student.lname) - 1);
        new_student.gpa = gpa;
    
        if (lseek(fd, offset, SEEK_SET) == -1) {
            printf(M_ERR_DB_WRITE);
            return ERR_DB_FILE;
        }
    
        if (write(fd, &new_student, STUDENT_RECORD_SIZE) != STUDENT_RECORD_SIZE) {
            printf(M_ERR_DB_WRITE);
            return ERR_DB_FILE;
        }
    
        printf(M_STD_ADDED, id);
        return NO_ERROR;
    }
    
    /*
     *  del_student
     *      fd:     linux file descriptor
     *      id:     student id to be deleted
     *
     *  Removes a student to the database.  Use the get_student() function to
     *  locate the student to be deleted. If there is a student at that location
     *  write an empty student record - see EMPTY_STUDENT_RECORD from db.h at
     *  that location.
     *
     *  returns:  NO_ERROR       student deleted from database
     *            ERR_DB_FILE    database file I/O issue
     *            ERR_DB_OP      database operation logically failed (aka student
     *                           not in database)
     *
     *
     *  console:  M_STD_DEL_MSG      on success
     *            M_STD_NOT_FND_MSG  student not in database, cant be deleted
     *            M_ERR_DB_READ      error reading or seeking the database file
     *            M_ERR_DB_WRITE     error writing to db file (adding student)
     *
     */
    int del_student(int fd, int id)
    {
        // TODO
        student_t student;
        int result = get_student(fd, id, &student);
    
        if (result == SRCH_NOT_FOUND) {
            printf(M_STD_NOT_FND_MSG, id);
            return ERR_DB_OP;
        } else if (result != NO_ERROR) {
            return ERR_DB_FILE;
        }
    
        off_t offset = id * STUDENT_RECORD_SIZE;
        if (lseek(fd, offset, SEEK_SET) == -1) {
            printf(M_ERR_DB_WRITE);
            return ERR_DB_FILE;
        }
    
        if (write(fd, &EMPTY_STUDENT_RECORD, STUDENT_RECORD_SIZE) != STUDENT_RECORD_SIZE) {
            printf(M_ERR_DB_WRITE);
            return ERR_DB_FILE;
        }
    
        printf(M_STD_DEL_MSG, id);
        return NO_ERROR;
    }
    
    /*
     *  count_db_records
     *      fd:     linux file descriptor
     *
     *  Counts the number of records in the database.  Start by reading the
     *  database at the beginning, and continue reading individual records
     *  until you it EOF.  EOF is when the read() syscall returns 0. Check
     *  if a slot is empty or previously deleted by investigating if all of
     *  the bytes in the record read are zeros - I would suggest using memory
     *  compare memcmp() for this. Create a counter variable and initialize it
     *  to zero, every time a non-zero record is read increment the counter.
     *
     *  returns:  <number>       returns the number of records in db on success
     *            ERR_DB_FILE    database file I/O issue
     *            ERR_DB_OP      database operation logically failed (aka student
     *                           not in database)
     *
     *
     *  console:  M_DB_RECORD_CNT  on success, to report the number of students in db
     *            M_DB_EMPTY       on success if the record count in db is zero
     *            M_ERR_DB_READ    error reading or seeking the database file
     *            M_ERR_DB_WRITE   error writing to db file (adding student)
     *
     */
    int count_db_records(int fd)
    {
        // TODO
        student_t student;
        int count = 0;
    
        if (lseek(fd, 0, SEEK_SET) == -1) {
            printf(M_ERR_DB_READ);
            return ERR_DB_FILE;
        }
    
        while (read(fd, &student, STUDENT_RECORD_SIZE) == STUDENT_RECORD_SIZE) {
            if (memcmp(&student, &EMPTY_STUDENT_RECORD, STUDENT_RECORD_SIZE) != 0) {
                count++;
            }
        }
    
        if (count == 0) {
            printf(M_DB_EMPTY);
        } else {
            printf(M_DB_RECORD_CNT, count);
        }
    
        return count;
    }
    
    /*
     *  print_db
     *      fd:     linux file descriptor
     *
     *  Prints all records in the database.  Start by reading the
     *  database at the beginning, and continue reading individual records
     *  until you it EOF.  EOF is when the read() syscall returns 0. Check
     *  if a slot is empty or previously deleted by investigating if all of
     *  the bytes in the record read are zeros - I would suggest using memory
     *  compare memcmp() for this. Be careful as the database might be empty.
     *  on the first real row encountered print the header for the required output:
     *
     *     printf(STUDENT_PRINT_HDR_STRING, "ID",
     *                  "FIRST_NAME", "LAST_NAME", "GPA");
     *
     *  then for each valid record encountered print the required output:
     *
     *     printf(STUDENT_PRINT_FMT_STRING, student.id, student.fname,
     *                    student.lname, calculated_gpa_from_student);
     *
     *  The code above assumes you are reading student records into a local
     *  variable named student that is of type student_t. Also dont forget that
     *  the GPA in the student structure is an int, to convert it into a real
     *  gpa divide by 100.0 and store in a float variable.
     *
     *  returns:  NO_ERROR       on success
     *            ERR_DB_FILE    database file I/O issue
     *
     *
     *  console:  <see above>      on success, print table or database empty
     *            M_ERR_DB_READ    error reading or seeking the database file
     *
     */
    int print_db(int fd)
    {
        // TODO
        student_t student;
        bool records_available = false;
    
        if (lseek(fd, 0, SEEK_SET) == -1) {
            printf(M_ERR_DB_READ);
            return ERR_DB_FILE;
        }
    
        while (read(fd, &student, STUDENT_RECORD_SIZE) == STUDENT_RECORD_SIZE) {
            if (memcmp(&student, &EMPTY_STUDENT_RECORD, STUDENT_RECORD_SIZE) != 0) {
                if (!records_available) {
                    printf(STUDENT_PRINT_HDR_STRING, "ID", "FIRST_NAME", "LAST_NAME", "GPA");
                    records_available = true;
                }
                printf(STUDENT_PRINT_FMT_STRING, student.id, student.fname, student.lname, student.gpa / 100.0);
            }
        }
    
        if (!records_available) {
            printf(M_DB_EMPTY);
        }
    
        return NO_ERROR;
    }
    
    /*
     *  print_student
     *      *s:   a pointer to a student_t structure that should
     *            contain a valid student to be printed
     *
     *  Start by ensuring that provided student pointer is valid.  To do this
     *  make sure it is not NULL and that s->id is not zero.  After ensuring
     *  that the student is valid, print it the exact way that is described
     *  in the print_db() function by first printing the header then the
     *  student data:
     *
     *     printf(STUDENT_PRINT_HDR_STRING, "ID",
     *                  "FIRST NAME", "LAST_NAME", "GPA");
     *
     *     printf(STUDENT_PRINT_FMT_STRING, s->id, s->fname,
     *                    student.lname, calculated_gpa_from_s);
     *
     *  Dont forget that  the GPA in the student structure is an int, to convert
     *  it into a real gpa divide by 100.0 and store in a float variable.
     *
     *  returns:  nothing, this is a void function
     *
     *
     *  console:  <see above>      on success, print table or database empty
     *            M_ERR_STD_PRINT  if the function argument s is NULL or if
     *                             s->id is zero
     *
     */
    void print_student(student_t *s)
    {
        // TODO
        if (s == NULL || s->id == 0) {
            printf(M_ERR_STD_PRINT);
            return;
        }
    
        printf(STUDENT_PRINT_HDR_STRING, "ID", "FIRST NAME", "LAST NAME", "GPA");
        printf(STUDENT_PRINT_FMT_STRING, s->id, s->fname, s->lname, s->gpa / 100.0);
    }
    
    /*
     *  NOTE IMPLEMENTING THIS FUNCTION IS EXTRA CREDIT
     *
     *  compress_db
     *      fd:     linux file descriptor
     *
     *  This assignment takes advantage of the way Linux handles sparse files
     *  on disk. Thus if there is a large hole between student records, Linux
     *  will not use any physical storage.  However, when a database record is
     *  deleted storage is used to write a blank - see EMPTY_STUDENT_RECORD from
     *  db.h - record.
     *
     *  Since Linux provides no way to delete data in the middle of a file, and
     *  deleted records take up physical storage, this function will compress the
     *  database by rewriting a new database file that only includes valid student
     *  records. There are a number of ways to do this, but since this is extra credit
     *  you need to figure this out on your own.
     *
     *  At a high level create a temporary database file then copy all valid students from
     *  the active database (passed in via fd) to the temporary file. When this is done
     *  rename the temporary database file to the name of the real database file. See
     *  the constants in db.h for required file names:
     *
     *         #define DB_FILE     "student.db"        //name of database file
     *         #define TMP_DB_FILE ".tmp_student.db"   //for extra credit
     *
     *  Note that you are passed in the fd of the database file to be compressed,
     *  it is very likely you will need to close it to overwrite it with the
     *  compressed version of the file.  To ensure the caller can work with the
     *  compressed file after you create it, it is a good design to return the fd
     *  of the new compressed file from this function
     *
     *  returns:  <number>       returns the fd of the compressed database file
     *            ERR_DB_FILE    database file I/O issue
     *
     *
     *  console:  M_DB_COMPRESSED_OK  on success, the db was successfully compressed.
     *            M_ERR_DB_OPEN    error when opening/creating temporary database file.
     *                             this error should also be returned after you
     *                             compressed the database file and if you are unable
     *                             to open it to pass the fd back to the caller
     *            M_ERR_DB_CREATE  error creating the db file. For instance the
     *                             inability to copy the temporary file back as
     *                             the primary database file.
     *            M_ERR_DB_READ    error reading or seeking the the db or tempdb file
     *            M_ERR_DB_WRITE   error writing to db or tempdb file (adding student)
     *
     */
    int compress_db(int fd)
    {
        // TODO
        close(fd);
    
        fd = open(DB_FILE, O_RDONLY);
        if (fd == -1) {
            printf(M_ERR_DB_OPEN);
            return ERR_DB_FILE;
        }
    
        // Open or make a temp database file in write mode
        int tmp_fd = open(TMP_DB_FILE, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
        if (tmp_fd == -1) {
            printf(M_ERR_DB_CREATE);
            close(fd);
            return ERR_DB_FILE;
        }
    
        student_t student;
        while (read(fd, &student, STUDENT_RECORD_SIZE) == STUDENT_RECORD_SIZE) {
            // Copy only non-empty records 
            if (memcmp(&student, &EMPTY_STUDENT_RECORD, STUDENT_RECORD_SIZE) != 0) {
                if (write(tmp_fd, &student, STUDENT_RECORD_SIZE) != STUDENT_RECORD_SIZE) {
                    printf(M_ERR_DB_WRITE);
                    close(fd);
                    close(tmp_fd);
                    return ERR_DB_FILE;
                }
            }
        }
    
        close(fd);
        close(tmp_fd);
    
        // Replace old database with the compressed one
        if (rename(TMP_DB_FILE, DB_FILE) == -1) {
            printf(M_ERR_DB_CREATE);
            return ERR_DB_FILE;
        }
    
        fd = open(DB_FILE, O_RDWR);
        if (fd == -1) {
            printf(M_ERR_DB_OPEN);
            return ERR_DB_FILE;
        }
    
        printf(M_DB_COMPRESSED_OK);
        return fd;
    }
    
    /*
     *  validate_range
     *      id:  proposed student id
     *      gpa: proposed gpa
     *
     *  This function validates that the id and gpa are in the allowable ranges
     *  as per the specifications.  It checks if the values are within the
     *  inclusive range using constents in db.h
     *
     *  returns:    NO_ERROR       on success, both ID and GPA are in range
     *              EXIT_FAIL_ARGS if either ID or GPA is out of range
     *
     *  console:  This function does not produce any output
     *
     */
    int validate_range(int id, int gpa)
    {
    
        if ((id < MIN_STD_ID) || (id > MAX_STD_ID))
            return EXIT_FAIL_ARGS;
    
        if ((gpa < MIN_STD_GPA) || (gpa > MAX_STD_GPA))
            return EXIT_FAIL_ARGS;
    
        return NO_ERROR;
    }
    
    /*
     *  usage
     *      exename:  the name of the executable from argv[0]
     *
     *  Prints this programs expected usage
     *
     *  returns:    nothing, this is a void function
     *
     *  console:  This function prints the usage information
     *
     */
    void usage(char *exename)
    {
        printf("usage: %s -[h|a|c|d|f|p|z] options.  Where:\n", exename);
        printf("\t-h:  prints help\n");
        printf("\t-a id first_name last_name gpa(as 3 digit int):  adds a student\n");
        printf("\t-c:  counts the records in the database\n");
        printf("\t-d id:  deletes a student\n");
        printf("\t-f id:  finds and prints a student in the database\n");
        printf("\t-p:  prints all records in the student database\n");
        printf("\t-x:  compress the database file [EXTRA CREDIT]\n");
        printf("\t-z:  zero db file (remove all records)\n");
    }
    
    // Welcome to main()
    int main(int argc, char *argv[])
    {
        char opt;      // user selected option
        int fd;        // file descriptor of database files
        int rc;        // return code from various operations
        int exit_code; // exit code to shell
        int id;        // userid from argv[2]
        int gpa;       // gpa from argv[5]
    
        // space for a student structure which we will get back from
        // some of the functions we will be writing such as get_student(),
        // and print_student().
        student_t student = {0};
    
        // This function must have at least one arg, and the arg must start
        // with a dash
        if ((argc < 2) || (*argv[1] != '-'))
        {
            usage(argv[0]);
            exit(1);
        }
    
        // The option is the first character after the dash for example
        //-h -a -c -d -f -p -x -z
        opt = (char)*(argv[1] + 1); // get the option flag
    
        // handle the help flag and then exit normally
        if (opt == 'h')
        {
            usage(argv[0]);
            exit(EXIT_OK);
        }
    
        // now lets open the file and continue if there is no error
        // note we are not truncating the file using the second
        // parameter
        fd = open_db(DB_FILE, false);
        if (fd < 0)
        {
            exit(EXIT_FAIL_DB);
        }
    
        // set rc to the return code of the operation to ensure the program
        // use that to determine the proper exit_code.  Look at the header
        // sdbsc.h for expected values.
    
        exit_code = EXIT_OK;
        switch (opt)
        {
        case 'a':
            //   arv[0] arv[1]  arv[2]      arv[3]    arv[4]  arv[5]
            // prog_name     -a      id  first_name last_name     gpa
            //-------------------------------------------------------
            // example:  prog_name -a 1 John Doe 341
            if (argc != 6)
            {
                usage(argv[0]);
                exit_code = EXIT_FAIL_ARGS;
                break;
            }
    
            // convert id and gpa to ints from argv.  For this assignment assume
            // they are valid numbers
            id = atoi(argv[2]);
            gpa = atoi(argv[5]);
    
            exit_code = validate_range(id, gpa);
            if (exit_code == EXIT_FAIL_ARGS)
            {
                printf(M_ERR_STD_RNG);
                break;
            }
    
            rc = add_student(fd, id, argv[3], argv[4], gpa);
            if (rc < 0)
                exit_code = EXIT_FAIL_DB;
    
            break;
    
        case 'c':
            //    arv[0] arv[1]
            // prog_name     -c
            //-----------------
            // example:  prog_name -c
            rc = count_db_records(fd);
            if (rc < 0)
                exit_code = EXIT_FAIL_DB;
            break;
    
        case 'd':
            //   arv[0]  arv[1]  arv[2]
            // prog_name     -d      id
            //-------------------------
            // example:  prog_name -d 100
            if (argc != 3)
            {
                usage(argv[0]);
                exit_code = EXIT_FAIL_ARGS;
                break;
            }
            id = atoi(argv[2]);
            rc = del_student(fd, id);
            if (rc < 0)
                exit_code = EXIT_FAIL_DB;
    
            break;
    
        case 'f':
            //    arv[0] arv[1]  arv[2]
            // prog_name     -f      id
            //-------------------------
            // example:  prog_name -f 100
            if (argc != 3)
            {
                usage(argv[0]);
                exit_code = EXIT_FAIL_ARGS;
                break;
            }
            id = atoi(argv[2]);
            rc = get_student(fd, id, &student);
    
            switch (rc)
            {
            case NO_ERROR:
                print_student(&student);
                break;
            case SRCH_NOT_FOUND:
                printf(M_STD_NOT_FND_MSG, id);
                exit_code = EXIT_FAIL_DB;
                break;
            default:
                printf(M_ERR_DB_READ);
                exit_code = EXIT_FAIL_DB;
                break;
            }
            break;
    
        case 'p':
            //    arv[0] arv[1]
            // prog_name     -p
            //-----------------
            // example:  prog_name -p
            rc = print_db(fd);
            if (rc < 0)
                exit_code = EXIT_FAIL_DB;
            break;
    
        case 'x':
            //    arv[0] arv[1]
            // prog_name     -x
            //-----------------
            // example:  prog_name -x
    
            // remember compress_db returns a fd of the compressed database.
            // we close it after this switch statement
            fd = compress_db(fd);
            if (fd < 0)
                exit_code = EXIT_FAIL_DB;
            break;
    
        case 'z':
            //    arv[0] arv[1]
            // prog_name     -x
            //-----------------
            // example:  prog_name -x
            // HINT:  close the db file, we already have fd
            //       and reopen db indicating truncate=true
            close(fd);
            fd = open_db(DB_FILE, true);
            if (fd < 0)
            {
                exit_code = EXIT_FAIL_DB;
                break;
            }
            printf(M_DB_ZERO_OK);
            exit_code = EXIT_OK;
            break;
        default:
            usage(argv[0]);
            exit_code = EXIT_FAIL_ARGS;
        }
    
        // dont forget to close the file before exiting, and setting the
        // proper exit code - see the header file for expected values
        close(fd);
        exit(exit_code);
    }