Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
C
CS283
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package registry
Container registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Charles Barnwell
CS283
Commits
8dcaf028
Commit
8dcaf028
authored
3 months ago
by
Charles Barnwell
Browse files
Options
Downloads
Patches
Plain Diff
Upload New File
parent
6d9fd6c0
Branches
Branches containing commit
No related tags found
No related merge requests found
Changes
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
2-StudentDB/sdbsc.c
+587
-0
587 additions, 0 deletions
2-StudentDB/sdbsc.c
with
587 additions
and
0 deletions
2-StudentDB/sdbsc.c
0 → 100644
+
587
−
0
View file @
8dcaf028
#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 for the name of
* *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
){
if
(
fd
==
-
1
)
return
ERR_DB_FILE
;
int
offset
=
id
*
STUDENT_RECORD_SIZE
;
lseek
(
fd
,
offset
,
SEEK_SET
);
student_t
student
;
int
bytes_read
=
read
(
fd
,
&
student
,
STUDENT_RECORD_SIZE
);
if
(
bytes_read
!=
STUDENT_RECORD_SIZE
)
{
return
ERR_DB_FILE
;
}
if
(
student
.
id
==
DELETED_STUDENT_ID
)
{
return
SRCH_NOT_FOUND
;
}
*
s
=
student
;
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
){
if
(
fd
==
-
1
)
return
ERR_DB_FILE
;
int
offset
=
id
*
STUDENT_RECORD_SIZE
;
lseek
(
fd
,
offset
,
SEEK_SET
);
student_t
existing_student
;
int
bytes_read
=
read
(
fd
,
&
existing_student
,
STUDENT_RECORD_SIZE
);
if
(
bytes_read
==
STUDENT_RECORD_SIZE
&&
existing_student
.
id
!=
DELETED_STUDENT_ID
)
{
printf
(
M_ERR_DB_ADD_DUP
,
id
);
return
ERR_DB_OP
;
}
student_t
new_student
=
{
id
,
""
,
""
,
gpa
};
strncpy
(
new_student
.
fname
,
fname
,
sizeof
(
new_student
.
fname
)
-
1
);
strncpy
(
new_student
.
lname
,
lname
,
sizeof
(
new_student
.
lname
)
-
1
);
lseek
(
fd
,
offset
,
SEEK_SET
);
int
bytes_written
=
write
(
fd
,
&
new_student
,
STUDENT_RECORD_SIZE
);
if
(
bytes_written
!=
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
){
if
(
fd
==
-
1
)
return
ERR_DB_FILE
;
int
offset
=
id
*
STUDENT_RECORD_SIZE
;
lseek
(
fd
,
offset
,
SEEK_SET
);
student_t
student
;
int
bytes_read
=
read
(
fd
,
&
student
,
STUDENT_RECORD_SIZE
);
if
(
bytes_read
!=
STUDENT_RECORD_SIZE
||
student
.
id
==
DELETED_STUDENT_ID
)
{
printf
(
M_STD_NOT_FND_MSG
,
id
);
return
ERR_DB_OP
;
}
student
.
id
=
DELETED_STUDENT_ID
;
lseek
(
fd
,
offset
,
SEEK_SET
);
write
(
fd
,
&
student
,
STUDENT_RECORD_SIZE
);
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
){
if
(
fd
==
-
1
)
return
ERR_DB_FILE
;
int
count
=
0
;
student_t
student
;
lseek
(
fd
,
0
,
SEEK_SET
);
while
(
read
(
fd
,
&
student
,
STUDENT_RECORD_SIZE
)
==
STUDENT_RECORD_SIZE
)
{
if
(
student
.
id
!=
DELETED_STUDENT_ID
)
{
count
++
;
}
}
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
){
if
(
fd
==
-
1
)
return
ERR_DB_FILE
;
student_t
student
;
lseek
(
fd
,
0
,
SEEK_SET
);
printf
(
STUDENT_PRINT_HDR_STRING
,
"ID"
,
"FIRST NAME"
,
"LAST NAME"
,
"GPA"
);
while
(
read
(
fd
,
&
student
,
STUDENT_RECORD_SIZE
)
==
STUDENT_RECORD_SIZE
)
{
if
(
student
.
id
!=
DELETED_STUDENT_ID
)
{
printf
(
STUDENT_PRINT_FMT_STRING
,
student
.
id
,
student
.
fname
,
student
.
lname
,
student
.
gpa
/
100
.
0
);
}
}
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
){
if
(
s
==
NULL
||
s
->
id
==
DELETED_STUDENT_ID
)
{
printf
(
M_ERR_STD_PRINT
);
return
;
}
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
){
printf
(
NOT_IMPLEMENTED_YET
);
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
);
}
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment