Source code:
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <conio.h>
#include <windows.h>
#include "addon.h"
static book* archivelib;
static book* noticelib;
const int MAX_LINE_LENGTH = (255);
void fileCheck(file sourcefile) { //Checks if a file exists
if (access(sourcefile.filename, F_OK) != false) {
textcolour(RED);
fprintf(stderr, "Error: %s does not exist.\n", sourcefile.filename);
exit(EXIT_FAILURE);
};
};
size_t recordcount(file source) { //Finds how many records are in a file
source.fp = fopen(source.filename, "r");
char ch;
size_t records = 0;
fseek(source.fp, START_OF_LINE, SEEK_SET);
while ((ch = fgetc(source.fp)) != EOF)
if (ch == '\n')
records++;
fclose(source.fp);
return records;
};
void textcolour(int colour) {
static int __BACKGROUND;
HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
GetConsoleScreenBufferInfo(h, &csbiInfo);
SetConsoleTextAttribute (GetStdHandle (STD_OUTPUT_HANDLE),
colour + (__BACKGROUND << 4));
}
void cls(HANDLE hConsole) {
CONSOLE_SCREEN_BUFFER_INFO csbi;
SMALL_RECT scrollRect;
COORD scrollTarget;
CHAR_INFO fill;
// Get the number of character cells in the current buffer.
if (!GetConsoleScreenBufferInfo(hConsole, &csbi)) {
return;
}
// Scroll the rectangle of the entire buffer.
scrollRect.Left = 0;
scrollRect.Top = 0;
scrollRect.Right = csbi.dwSize.X;
scrollRect.Bottom = csbi.dwSize.Y;
// Scroll it upwards off the top of the buffer with a magnitude of the entire height.
scrollTarget.X = 0;
scrollTarget.Y = (SHORT)(0 - csbi.dwSize.Y);
// Fill with empty spaces with the buffer's default text attribute.
fill.Char.UnicodeChar = TEXT(' ');
fill.Attributes = csbi.wAttributes;
// Do the scroll
ScrollConsoleScreenBuffer(hConsole, &scrollRect, NULL, scrollTarget, &fill);
// Move the cursor to the top left corner too.
csbi.dwCursorPosition.X = 0;
csbi.dwCursorPosition.Y = 0;
SetConsoleCursorPosition(hConsole, csbi.dwCursorPosition);
}
void clearscreen() {
HANDLE hStdout;
hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
cls(hStdout);
}
const book emptybook = {
.checkdigit = (0),
.ISBN = "empty",
.title = "empty",
.DOR = "empty",
.PersonDetails = { .firstname = "empty", .lastname = "empty", .fullname = "empty"},
.status = "empty",
};
static file notice = {
.filename = "NOTICE.txt",
.sizeofline = MAX_LINE_LENGTH,
.field = { .format = "%17[^|]|%55s|%10s|%10s|%21s|%10[^|]|%26[^\n]\n", .num = (7)},
};
static file archive = {
.filename = "ARCHIVE.txt",
.sizeofline = MAX_LINE_LENGTH,
.field = { .format = "%17[^|]|%55s|%10s|%10s|%21s|%10[^|]|%26[^\n]\n", .num = (7)},
};
void printBook(book bk) { //Print information on book bk
if (strcmp(bk.title, bk.PersonDetails.firstname) == 0) {
textcolour(RED);
fprintf(stderr, "Error: The book could not be printed\n");
sleep(2);
clearscreen();
return;
};
printf("|ISBN: %s\n", bk.ISBN);
printf("|Book: %s\n", bk.title);
printf("|Authour: %s\n", bk.PersonDetails.fullname);
printf("|Date of Release: %s\n", bk.DOR);
};
book* alloclib(file libfile) {
fileCheck(libfile); //check the file
libfile.records = recordcount(libfile); //find the number of records in file
book* lib = malloc(libfile.records * sizeof *lib);
if (lib == NULL) {
textcolour(RED);
fprintf(stderr, "Memory allocation failed!\n");
exit(EXIT_FAILURE);
};
return(lib);
}
int CmpBookRecs(const void* recA, const void* recB) { //Compare book records alphabetically
int result = 0;
book* bkRecA = (book*)recA;
book* bkRecB = (book*)recB;
//Compare the titles
result = strcmp(bkRecA->title,
bkRecB->title);
//If (titles match) compare the names
if (!result)
result = strcmp(bkRecA->PersonDetails.lastname,
bkRecB->PersonDetails.lastname);
//If (names match) compare the ISBN
if (!result)
result = strcmp(bkRecA->ISBN,
bkRecB->ISBN);
return (result); //Return the the result
};
book* loadlibrary(file libsource) { //Loads all the books in "libarchive"
printf("Loading library...\n");
sleep(2);
libsource.fp = fopen(libsource.filename, "r");
if (libsource.fp == NULL) {
textcolour(RED);
fprintf(stderr, "Error opening file.\n"); //Return an error if the file cannot be opened
exit(EXIT_FAILURE);
};
int read = 0; //read will be used to ensure each line/record is read correctly
int records = 0; //records will keep track of the number of Student records read from the file
char* token; //smaller char* broken from line
const char* del = "-"; //the delimiter the line is broken into a series of tokens by
//Read all records from the file and store them into the lib
book* lib = alloclib(libsource);
do {
//Check how many fields are in the file
read = fscanf(libsource.fp, libsource.field.format, lib[records].ISBN,
lib[records].title,
lib[records].PersonDetails.firstname,
lib[records].PersonDetails.lastname,
lib[records].PersonDetails.fullname,
lib[records].DOR);
//If fscanf read values from the file, assign values then successfully add an another record
if (read == libsource.field.num)
records++;
//If read is not equal the field size and it is not the end of file
if (read != libsource.field.num && !feof(libsource.fp)) {
textcolour(RED);
fprintf(stderr, "File format incorrect. %d fields read instead of %d\n", read, libsource.field.num);
sleep(2);
exit(EXIT_FAILURE);
};
//If there was an error reading from the file exit with an error message and status
if (ferror(libsource.fp)) {
textcolour(RED);
fprintf(stderr, "Error in reading file.\n");
exit(EXIT_FAILURE);
};
lib[records].checkdigit = atoi(strrchr(lib[records].ISBN, '-'));
} while (!feof(libsource.fp) && records < libsource.records);
textcolour(LIGHTGREEN);
printf("Sorting Library...\n");
sleep(2);
qsort(lib, records, sizeof(*lib), CmpBookRecs);
textcolour(GREEN);
printf("Library was sorted!\n\n");
//free(line);
fclose(libsource.fp);
printf("Library was successfully loaded...\n");
sleep(2);
return (lib);
};
int CheckISBN(char* ISBN) { //Calculates the check digit for 13 digit International Standard Book Number
int x[13];
int count = 0;
int i = 0;
while (count < strlen(ISBN) && ISBN[i] != '\0') {
if (isdigit(ISBN[i])) {
x[count] = ISBN[i] - '0'; // Convert character to integer
count++;
} i++;
}
int s = 0,
t = 0;
//Calculation
for (i = 0; i < 12; ++i) {
if (i % 2 == 0) {
t += (x[i] * 3);
} else {
t += x[i];
} s += t;
};
int r = s % 10;
return (10 - r);
};
book booksearch(file libsource, book* lib) { //Allows the user to search for a specify book based on its title or ISBN
book foundbook; //book to be found
char* search = malloc((MAX_LINE_LENGTH + 1) * sizeof *search); //search title
char information[MAX_LINE_LENGTH]; //book title in record
char* option;
bool flag = true;
do { //Infinite loop until input
printf("\n");
textcolour(BLUE);
printf(" | _____ ___ ____ ____ __ __ __ |\n");
printf(" | / ___/ / _] / || ` / ]| | | |\n");
printf(" | ( |_ / [_ | o || D ) / / | | | |\n");
printf(" | |__ || _]| || / / / | _ | |\n");
printf(" | / ` || [_ | _ || ` / |_ | | | |\n");
printf(" | ` || || | || . `` || | | |\n");
printf(" | |___||_____||__|__||__||_| |____||__|__| |\n");
printf(" | ___ ____ ______ ____ ___ ____ _____ |\n");
printf(" | / ` | ` | || | / ` | ` / ___/ |\n");
printf(" | | || o )| | | | | || _ |( |_ |\n");
printf(" | | O || _/ |_| |_| | | | O || | | |__ | |\n");
printf(" | | || | | | | | | || | | / ` | |\n");
printf(" | | || | | | | | | || | | ` | |\n");
printf(" | |___/ |__| |__| |____| |___/ |__|__| |___| |\n");
printf(" | |\n");
printf(" |- [");
textcolour(LIGHTBLUE);
printf("1");
textcolour(BLUE);
printf("] ISBN |\n");
printf(" |- [");
textcolour(LIGHTBLUE);
printf("2");
textcolour(BLUE);
printf("] Title |\n");
printf(" |- [");
textcolour(LIGHTBLUE);
printf("3");
textcolour(BLUE);
printf("] Exit |\n\n");
//User option
switch (getc(stdin)) {
case '1': option = "ISBN";
flag = false;
break;
case '2': option = "Title";
flag = false;
break;
case '3': return(emptybook); //Exit
break;
default : textcolour(RED); //Red
fprintf(stderr, "\nInvalid option. Try again.");
clearscreen();
break;
};
} while (flag == true);
//Prompt user for the book they want to search
textcolour(YELLOW);
clearscreen();
sleep(2);
printf("\nPlease enter the %s of the book you are searching for:\n", option);
scanf("%s", search);
//fgets(search, strlen(search), stdin);
//Remove newline character if exists
if ((strlen(search) > 0) && (search[strlen(search) - 1] == '\n'))
search[strlen(search) - 1] = '\0';
//Check if title is proper
if (strcmp(option, "Title") != 0) {
//Convert to search to all caps
for (int i = 0; search[i] != '\0'; i++) {
//If any character in the char* is not a character or char* is empty
if (isalpha(search[i]) == 0 || search == NULL)
goto impromperformat;
search[i] = toupper(search[i]);
};
};
//Check if ISBN is correct
if (strcmp(option, "ISBN") == 0) {
int q[5];
int read = sscanf(search, "%3d-%2d-%4d-%3d-%1d", q[0],q[1],q[2],q[3],q[4]);
if (read != 5) {
impromperformat:
textcolour(RED);
fprintf(stderr, "This not a %s. Returning...\n", option);
return(emptybook);
}
if (read == 5 && q[4] != CheckISBN(search)) {
textcolour(RED);
fprintf(stderr, "This not a proper ISBN. Returning...\n", option);
return(emptybook);
}
};
int records = 0; //the number of lines in the record
bool found = false; //if the the book was found
int linenumber = 0;
const char* del = "|"; //character that seperates each field
//While there are book records that have not been read
while (fgets(information, libsource.sizeofline, libsource.fp) != NULL && linenumber != notice.records) {
for (int i = 0; information[i] != '\n'; i++)
information[i] = toupper(information[i]);
//If the an occurrence of the substring, (search) in the string, (line) is found
if (strstr(information, search) != NULL) {
int read = sscanf(information, libsource.field.format, lib[records].ISBN,
lib[records].title,
lib[records].PersonDetails.firstname,
lib[records].PersonDetails.lastname,
lib[records].PersonDetails.fullname,
lib[records].DOR);
if (read != 5)
goto notfound;
foundbook.checkdigit = atoi(strrchr(foundbook.ISBN, '-'));
if (foundbook.checkdigit != CheckISBN(foundbook.ISBN))
goto notfound;
if (strcmp(foundbook.status, "Available") != 0) {
textcolour(RED);
fprintf(stderr, "This book is currently unavailable.\n");
return(emptybook);
};
textcolour(LIGHTGREEN);
printf("A match was found for %s!\n", search); //Book is found
found = true;
} linenumber++;
};
notfound:
//free(token); //Free up book title
free(search); //Free up search
if (found == false) {
textcolour(LIGHTRED);
printf("Sorry, %s is currently not available. Better add one to the bookshelf!\n", search);
sleep(2);
return(emptybook); //return an empty book if found is still false
};
};
void titleview(book* lib, size_t libsize) {
lib = alloclib(notice);
lib = loadlibrary(notice);
textcolour(CYAN);
printf("| These are all records of the book currently at the library: \n");
for (int records = 0; records < libsize; records++) {
while (lib[records].checkdigit != CheckISBN(lib[records].ISBN))
records++; //Go to next record skipping the incorrect record
textcolour(BLUE);
printf("%d) ", records);
textcolour(CYAN);
if (records % 2 == 0)
textcolour(LIGHTBLUE);
printBook(lib[records]);
sleep(2);
printf("\n");
};
};
void menu(void) { //Program Menu
book found;
do { //Infinite loop until input
printf("\n");
textcolour(YELLOW);
printf(" ___________________________________________________________________________________________________________________________________\n");
printf(" |- __ ___ __ __ _____ _ __ -|\n");
printf(" |- / |/ / ___ ___ _ ___/ / ___ _ __ _ __ ___ _ / / ___ / ___/ ___ __ _ __ _ __ __ ___ (_) / /_ __ __ -|\n");
printf(" |- / /|_/ / / -_)/ _ `// _ / / _ `| |/|/ /| |/ // _ `/ / / / -_) / /__ / _ ` / ' ` / ' `/ // / / _ ` / / / __/ / // / -|\n");
printf(" |- /_/ /_/ |__/ |_,_/ |_,_/ |___/|__,__/ |___/ |_,_/ /_/ |__/ |___/ |___//_/_/_//_/_/_/|_,_/ /_//_//_/ |__/ |_, / -|\n");
printf(" |- /___/ -|\n");
printf(" |- __ _ __ ____ __ -|\n");
printf(" |- / / (_) / / ____ ___ _ ____ __ __ / __/ __ __ ___ / /_ ___ __ _ -|\n");
printf(" |- / /__ / / / _ ` / __// _ `/ / __/ / // / _` ` / // / (_-</ __// -_) / ' ` -|\n");
printf(" |- /____//_/ /_.__//_/ |_,_/ /_/ |_, / /___/ |_, / /___/|__/ |__/ /_/_/_/ -|\n");
printf(" |- /___/ /___/ -|\n");
printf(" |- -|\n");
printf(" | [1] View Upcoming Titles |\n");
printf(" | [2] Book Search |\n");
printf(" | [3] Exit |\n");
//User option
switch (getc(stdin)) {
case '1': clearscreen();
sleep(2);
titleview(noticelib, notice.records);
break;
case '2': clearscreen();
sleep(2);
found = booksearch(archive, archivelib); //Allows user to search a book
printBook(found);
break;
case '3': exit(0); //Exit
break;
default : textcolour(RED);
fprintf(stderr, "Invalid option. Try again.");
sleep(2);
clearscreen();
};
} while(1);
};
int main() {
noticelib = alloclib(notice);
archivelib = alloclib(archive);
menu();
free(archivelib); //free the archivelib
free(noticelib); //free the noticelib
return 0;
};
And my custom header:
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <conio.h>
#include <windows.h>
#include <stdio.h>
#ifndef __ADDON_H__
#define __ADDON_H__
#define MAX_PATH_LENGTH (255)
const int FIRST_LINE = 1;
const int START_OF_LINE = 0;
typedef struct {
size_t records;
size_t sizeofline;
char* filename;
FILE* fp;
struct {
int num;
char* format;
} field;
} file;
typedef struct {
int checkdigit; //permits mathematical checking for accuracy
char ISBN[18]; //ISBN number
char title[76]; //Title of material
char DOR[11]; //Date of release for the item
struct {
char firstname[11]; //first name
char lastname[11]; //last name
char fullname[22]; //First + Middle + Last name of Authour
} PersonDetails; //details about a person
char status[27];
} book; //structure for a book
enum {
BLACK = 0,
BLUE,
GREEN,
CYAN,
RED,
MAGENTA,
BROWN,
LIGHTGRAY,
DARKGRAY,
LIGHTBLUE,
LIGHTGREEN,
LIGHTCYAN,
LIGHTRED,
LIGHTMAGENTA,
YELLOW,
WHITE
};
#endif //__ADDON_H__
I've been trying to get the number of fields that could successfully be scanned in my file as read in the function loadlibrary. However, it only reads two fields out of the intended 7.
Any corrections on how to fix the error or improve my code are welcome. Thank you!
Here is the data I am currently testing it on: ARCHIVE.txt
ISBN|Title|First Name|Last Name|Full Name|DOR|Status
978-51-7947-772-3|"Bleed Magic"|Orville|Baker|Orville Baker|15/04/2014|Available
978-05-0191-800-4|"Solar Waltz"|Alejandra|Bauer|Alejandra Bauer|30/07/1985|Unavailable - Rented
978-43-8687-725-8|"From Halsey to Hal's Island"|Jacklyn|Bruce|Jacklyn Bruce|09/05/1996|Available
978-36-2955-426-0|"Witch With Honor"|Miriam|Bryant|Miriam Bryant|14/07/1992|Unavailable - Damaged
978-95-2736-473-4|"Point A to Z"|Sang|Cherry|Sang Cherry|28/07/1987|Unavailable - Library use only
978-45-0742-523-8|"Priest named Sins"|Teddy|Decker|Teddy Decker|15/09/1989|Unavailable - Missing
978-27-1385-463-7|"Success of the Solstice"|Sandra|Duncan|Sandra Duncan|24/04/1989|Unavailable - In Process
978-31-8143-556-4|"What’s Over There?"|Leonel|Espinoza|Leonel Espinoza|25/03/2003|Available
978-95-5238-240-6|"The Amazing Adventures of Ice-Boy: Robots of Everest"|Ingrid|Guerra|Ingrid Guerra|24/06/2004|Available
978-85-7786-850-6|"One Boy And The World!"|Ezequiel|Hall|Ezequiel Hall|01/11/1994|Unavailable - Rented
978-28-5593-358-0|"Origin Of The Fog"|Nicky|Hancock|Nicky Hancock|23/08/2013|Available
978-33-2798-076-7|"Signs of the Past"|Lynwood|Hardin|Lynwood Hardin|27/02/1992|Available
978-23-8499-409-0|"Bleeding At The Past"|Renee|Johnston|Renee Johnston|07/05/1993|Available
978-32-9910-416-1|"Vision of Evil"|Jon|Kirby|Jon Kirby|16/02/2007|Unavailable - Rented
NOTICE.txt
ISBN|Title|First Name|Last Name|Full Name|DOR|Status
978-89-8818-596-4|"London Titans: As We Still Exist"|Kieth|Kirk|Kieth Kirk|08/08/1996|Unavailable - Coming Soon
978-30-7350-796-6|"Linger Longer"|Ester|Li|Ester Li|05/07/2022|Unavailable - Coming Soon
978-87-6109-805-4|"Till You, My Wallflower, Are Blooming"|Penny|Lucero|Penny Lucero|06/08/2018|Unavailable - Coming Soon
978-44-5008-783-5|"Fin and Tail, Claw and Tooth"|Bernadine|Mckinney|Bernadine Mckinney|18/10/2023|Unavailable - Coming Soon
978-60-3893-332-9|"Yes... Maybe? No!"|Vicky|Mills|Vicky Mills|05/02/2008|Unavailable - Coming Soon
978-55-4205-071-3|"Mermaids and Sirens"|Genevieve|Ochoa|Genevieve Ochoa|01/07/1987|Unavailable - Coming Soon
978-66-8758-265-7|"O Death, Where Is Thy Sting?"|Mohamed|Oconnell|Mohamed Oconnell|16/02/2021|Unavailable - Coming Soon
978-68-4196-147-2|"Behind the Door"|Hunter|Payne|Hunter Payne|19/05/2010|Unavailable - Coming Soon
978-60-6519-219-5|"Stay Hidden"|Saul|Powers|Saul Powers|22/12/2004|Unavailable - Coming Soon
978-90-3065-807-8|"The Things that I Never Will Learn"|Eva|Robertson|Eva Robertson|18/06/2007|Unavailable - Coming Soon
978-74-8013-934-5|"Humans With Us Untold Gods"|Suzette|Rosales|Suzette Rosales|25/02/1987|Unavailable - Coming Soon
978-87-4206-938-7|"Whispers of a Ghost"|Diego|Russo|Diego Russo|03/06/1996|Unavailable - Coming Soon
978-66-2724-397-6|"When Love Lasts"|Barbra|Sanders|Barbra Sanders|29/11/2018|Unavailable - Coming Soon
978-78-4619-100-0|"Pelicans We"|Sheila|Whitehead|Sheila Whitehead|18/11/1982|Unavailable - Coming Soon
978-09-4189-874-4|"Obsidian Leviathan"|Elwood|Wise|Elwood Wise|10/05/2023|Unavailable - Coming Soon
978-20-7104-860-0|"Of Fire and Silk"|Reyes|Wolf|Reyes Wolf|27/02/2001|Unavailable - Coming Soon
Your program fails for me `loadlibrary() for me with:
Number of fields read: 0
File format incorrect.
because argscheck()
is called with the pointers lib[records].ISBN
... lib[records].status
. As lib
is allocated with malloc()
those pointers are uninitialized and it cause undefined behavior in vfscanf()
which a pointer to "a character array that is long enough to hold the input sequence and the terminating null byte". On my system those pointers happen to be NULL and vfscanf()
returns 0. I suspect they are non-NULL on your system which will will likely cause memory corruption.
Here is the minimal example you should have posted:
#include <stdio.h>
#include <stdlib.h>
#define LEN 99
#define str(s) str2(s)
#define str2(s) #s
int main() {
FILE *f = fopen("ARCHIVE.txt", "r");
char *a[1]; // uninitialized
// a[0] = malloc(LEN+1);
int read = fscanf(f, "%" str(LEN) "s, ", a[0]);
printf("%d\n", read);
fclose(f);
}
Always specify a maximum field width when reading strings into an array that you allocate with scanf()
family otherwise long input will cause buffer overflows.
If your scanf()
support the 'm' character then scanf()
can allocate those arrays for you, i.e. scanf("%ms, ", &a[0])
. I would still specify a maximum field width to avoid a huge variable from being allocated if your input is untrusted. This will, btw, read a word (i.e. up to the first space) including the comma not what you expect.
I also suggest you separate I/O and parsing code. Both are tricky and mixing them makes things way harder. Every I/O call may fail (forever; like when you read EOF
). CSV files may contain embedded newlines so I suggest you just read the whole file into a buffer (either realloc()
the array as needed to assume a max size of the file).