Store strings in binary file without them being overwritten - c

So first of all, here is the relevant part of my code and I will then proceed to explain.
scanf("%d", &option);
fflush (stdin);
//Insert
if(option==1){
printf("enter name\n");
fgets(name,MAX_SIZE,stdin);
printf("name: %s",name);
char str[]="alpha one";
fd=open(argv[1],O_RDWR|O_CREAT|O_TRUNC,S_IRWXU);
write(fd,str,strlen(str) + 1);
pcounter = updateCounter(pcounter, str);
lseek(fd, 0, SEEK_END);
char passpos[5];
sprintf(passpos,"%d",pcounter); //int to string
fd=open(argv[2],O_RDWR|O_CREAT|O_TRUNC,S_IRWXU);
write(fd,passpos,3);
}
The reason I am redefining str as "alpha one" is because, even though I am trying to clear the buffer after scanf with fflush, fgets still gets a "/n" as input (I assume) and when I print "name" I get blank. Any help with this would be valuable even though its not the main issue.
This is all in a loop, so I thought if I keep pressing "1" when prompted, my binary file should look like: alpha onealpha one alpha one ...
However, it looks like: alpha one, no matter how many times I press 1.
Same thing with my other binary file that stores the counter, it gets updated correctly so after the first loop it has "9" stored, but after the second only "18" when I would be expecting: 9 18
I tried removing lseek altogether, and also setting it to CURR instead of END. Same results.
I have max warnings turned on and I get none when compiling.
I should point out that this is homework so I cannot use fopen etc, only system commands.
Here is the updateCounter function in case someone wants to run it:
int updateCounter(int pcounter, char *str){
int m,charcount = 0;
for(m=0; str[m]; m++) {
charcount ++;
}
printf("chars: %d \n", charcount);
pcounter = pcounter + charcount;
printf("pcounter = %d \n", pcounter);
return pcounter;
}
Thanks for any help.

You are opening the file with the O_TRUNC flag. This tells the computer to delete all the data from the file. If you don't want to delete all the data from the file then don't use O_TRUNC.
Also, make sure to seek to the end of the file before writing any data. Otherwise, you will overwrite the data at the beginning of the file instead. You could use lseek to seek to the end, or you could also use the O_APPEND flag when opening the file to automatically seek to the end.

Related

Check multiple files with "strstr" and "fopen" in C

Today I decided to learn to code for the first time in my life. I decided to learn C. I have created a small program that checks a txt file for a specific value. If it finds that value then it will tell you that that specific value has been found.
What I would like to do is that I can put multiple files go through this program. I want this program to be able to scan all files in a folder for a specific string and display what files contain that string (basically a file index)
I just started today and I'm 15 years old so I don't know if my assumptions are correct on how this can be done and I'm sorry if it may sound stupid but I have been thinking of maybe creating a thread for every directory I put into this program and each thread individually runs that code on the single file and then it displays all the directories in which the string can be found.
I have been looking into threading but I don't quite understand it. Here's the working code for one file at a time. Does anyone know how to make this work as I want it?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
//searches for this string in a txt file
char searchforthis[200];
//file name to display at output
char ch, file_name[200];
FILE *fp;
//Asks for full directory of txt file (example: C:\users\...) and reads that file.
//fp is content of file
printf("Enter name of a file you wish to check:\n");
gets(file_name);
fp = fopen(file_name, "r"); // read mode
//If there's no data inside the file it displays following error message
if (fp == NULL)
{
perror("Error while opening the file.\n");
exit(EXIT_FAILURE);
}
//asks for string (what has to be searched)
printf("Enter what you want to search: \n");
scanf("%s", searchforthis);
char* p;
// Find first occurrence of searchforthis in fp
p = strstr(searchforthis, fp);
// Prints the result
if (p) {
printf("This Value was found in following file:\n%s", file_name);
} else
printf("This Value has not been found.\n");
fclose(fp);
return 0;
}
This line,
p = strstr(searchforthis, fp);
is wrong. strstr() is defined as, char *strstr(const char *haystack, const char *needle), no file pointers in it.
Forget about gets(), its prone to overflow, reference, Why is the gets function so dangerous that it should not be used?.
Your scanf("%s",...) is equally dangerous to using gets() as you don't limit the character to be read. Instead, you could re-format it as,
scanf("%199s", searchforthis); /* 199 characters + \0 to mark the end of the string */
Also check the return value of scanf() , in case an input error occurs, final code should look like this,
if (scanf("%199s", searchforthis) != 1)
{
exit(EXIT_FAILURE);
}
It is even better, if you use fgets() for this, though keep in mind that fgets() will also save the newline character in the buffer, you are going to have to strip it manually.
To actually perform checks on the file, you have to read the file line by line, by using a function like, fgets() or fscanf(), or POSIX getline() and then use strstr() on each line to determine if you have a match or not, something like this should work,
char *p;
char buff[500];
int flag = 0, lines = 1;
while (fgets(buff, sizeof(buff), fp) != NULL)
{
size_t len = strlen(buff); /* get the length of the string */
if (len > 0 && buff[len - 1] == '\n') /* check if the last character is the newline character */
{
buff[len - 1] = '\0'; /* place \0 in the place of \n */
}
p = strstr(buff, searchforthis);
if (p != NULL)
{
/* match - set flag to 1 */
flag = 1;
break;
}
}
if (flag == 0)
{
printf("This Value has not been found.\n");
}
else
{
printf("This Value was found in following file:\n%s", file_name);
}
flag is used to determine whether or not searchforthis exists in the file.
Side note, if the line contains more than 499 characters, you will need a larger buffer, or a different function, consider getline() for that case, or even a custom one reading character by character.
If you want to do this for multiple files, you have to place the whole process in a loop. For example,
for (int i = 0; i < 5; i++) /* this will execute 5 times */
{
printf("Enter name of a file you wish to check:\n");
...
}

My program creates a file named date.in but it is not inserting all the numbers

Write a C program that reads from the keyboard a natural number n
with up to 9 digits and creates the text file data.out containing the
number n and all its non-zero prefixes, in a single line, separated by
a space, in order decreasing in value. Example: for n = 10305 the data
file.out will contain the numbers: 10305 1030 103 10 1.
This is what I made:
#include <stdio.h>
int main()
{
int n;
FILE *fisier;
fisier=fopen("date.in","w");
printf("n= \n");
scanf("%d",&n);
fprintf(fisier,"%d",n);
while(n!=0)
{
fisier=fopen("date.in","r");
n=n/10;
fprintf(fisier,"%d",n);
}
fclose(fisier);
}
Few things:
Function calls may return error. You need to check that every time.
fisier=fopen("date.in","w");
This should have been followed by an error check. To understand more on what it return, first thing you should do is read the man page for that function. See man page for fopen(). If there is an error in opening the file, it will return NULL and errno is set to a value which indicates what error occurred.
if (NULL == fisier)
{
// Error handling code
;
}
Your next requirement is separating the numbers by a space. There isn't one. The following should do it.
fprintf(fisier, "%d ", n);
The next major problem is opening the file in a loop. Its like you are trying to open a door which is already open.
fisier=fopen("date.in","r");
if(NULL == fisier)
{
// Error handling code
;
}
while(n!=0)
{
n=n/10;
fprintf(fisier,"%d",n);
}
fclose(fisier);
A minor issue that you aren't checking is the number is not having more than 9 digits.
if(n > 999999999)
is apt after you get a number. If you want to deal with negative numbers as well, you can modify this condition the way you want.
In a nutshell, at least to start with, the program should be something similar to this:
#include <stdio.h>
// Need a buffer to read the file into it. 64 isn't a magic number.
// To print a 9 digit number followed by a white space and then a 8 digit number..
// and so on, you need little less than 64 bytes.
// I prefer keeping the memory aligned to multiples of 8.
char buffer[64];
int main(void)
{
size_t readBytes = 0;
int n = 0;
printf("\nEnter a number: ");
scanf("%d", &n);
// Open the file
FILE *pFile = fopen("date.in", "w+");
if(NULL == pFile)
{
// Prefer perror() instead of printf() for priting errors
perror("\nError: ");
return 0;
}
while(n != 0)
{
// Append to the file
fprintf(pFile, "%d ", n);
n = n / 10;
}
// Done, close the file
fclose(pFile);
printf("\nPrinting the file: ");
// Open the file
pFile = fopen("date.in", "r");
if(NULL == pFile)
{
// Prefer perror() instead of printf() for priting errors
perror("\nError: ");
return 0;
}
// Read the file
while((readBytes = fread(buffer, 1, sizeof buffer, pFile)) > 0)
{
// Preferably better way to print the contents of the file on stdout!
fwrite(buffer, 1, readBytes, stdout);
}
printf("\nExiting..\n\n");
return 0;
}
Remember: The person reading your code may not be aware of all the requirements, so comments are necessary. Secondly, I understand english to a decent level but I don't know what 'fisier' means. Its recommended to name variables in such a way that its easy to understand the purpose of the variable. For example, pFile is a pointer to a file. p in the variable immediately gives an idea that its a pointer.
Hope this helps!
To draw a conclusion from all the comments:
fopen returns a file handle when successfull and NULL otherwise. Opening a file twice might result in an error (it does on my machine), such that fisier is set to NULL inside the loop. Obvioulsy fprintf to NULL wont do anything.
You only need to call fopen once, so remove it from the loop. After that it will work as intended.
It's alwas good to check if the fopen succeeded or not:
FILE *fisier;
fisier=fopen("date.in","w");
if(!fisier) { /* handle error */ }
You print no spaces between the numbers. Maybe that's intended, but maybe
fprintf(fisier,"%d ",n);
would be better.

Why is My Program Unable To Read the Data from My File

My Program Works Mostly, except for When I try to read the Total Data that was Entered
#include <stdio.h>
#include <string.h>
#define bufferSize 300
char name[50], gift[50], list[300], end[50], *result;
int i;
int main()
{
FILE *appendPlace = fopen("NamesAndGifts.txt", "a");
FILE *readData = fopen("NamesAndGifts.txt", "r"); //my place for reading data
printf("This is a Gift Entering System for 3 People Only\nThe Name of the Person then their Gift Description will be taken\nThe Gift Description is Basically the Gift Name");
while (strcmp(end, "END") != 0) {
printf("\nEnter Name of Person %d or type 'END' to Exit Sequence\n", i + 1);
scanf("%s", &end);
if (strcmp(end, "END") != 0) {
strcpy(name, end);
printf("Now Enter Gift Description (Gift Name) of Person %d\n", i + 1);
scanf("%s", &gift);
strcat(list, "\n");
strcat(list, "Name: ");
strcat(list, name);
strcat(list, "\n");
strcat(list, "Gift: ");
strcat(list, gift);
strcat(list, "\n");
}
i++;
}
printf("The Gift Entering System (Names and their respective Gifts) is Below:\n");
printf("%s", list);
fputs(list, appendPlace);
fclose(appendPlace);
//below I attempt to read file Data to be able to print file's Data into running program
fscanf(readData, "%s", result);
printf("\n\n\n\n\n\n\nTotal Data in File Below:\n%s", result);
fclose(readData);
}
I tried out doing just file reading, and it seems that reading from the file like that can only read data that is not separated by (space bar) or (enter)
Is there a way to Solve this?
So there are 2 problems in your code.
result has no memory allocated to it. Since it is a global variable, it is initialized to 0, aka a NULL pointer. So your scanf() sees that and the reading fails and so does printf() and prints "(null)". The solution there is to allocate memory in result either by making it a static array or by using malloc().
Even if you fix the first problem, however, it will still not work as expected as fscanf() will stop reading input after the first whitespace is encountered. Since you want the whole (file) input to be read, you have four options:
Read character by character (not advisable for performance reasons but perhaps the easiest to implement)
read line by line (fairly a standard way)
read chunk by chunk given some pre-allocated buffer or
read the whole file at once (not advised for big files)
The functions to use are fgetc(), getline(), fread(). Additionally, you can find the size of the file by following this question

Trying to read a file line by line in C but the while loop never terminates

My class requires me to write an operating system simulator in C. The first project was to read in two files, a config file and a metadata file. My first attempt at running the finished code led to a segmentation fault (which I believe I solved). Now I'm having an issue with my fgets statement reading line by line. It's reading every line, but it never stops reading lines. It reads through the file indefinitely until I end the process. I put a printf statement in the while loop so I can see what it's reading, and it does go through each line.
int readCfgFile (char *filename, CONFIG *filedata)
{
char buffer[255], trash[255];
FILE *cfgfile = malloc(sizeof cfgfile);
cfgfile = fopen(filename, "r"); // Assumed where segfault was
if(!cfgfile)
{
return -2; // Error opening file
}
char *schedulingCode = malloc(8);
char *logToCode = malloc(200);
char *tempLine; // Suggested to me as a fix (the using a separate variable part)
fgets(trash, 255, cfgfile); //Trash the first line of the file
tempLine = fgets(buffer, 255, cfgfile);
while(tempLine != NULL)
{
sscanf(buffer, "Version/Phase: %d", &filedata->version);
sscanf(buffer, "File Path: %s", filedata->metaPath);
sscanf(buffer, "CPU Scheduling Code: %s", schedulingCode);
sscanf(buffer, "Quantum Time (cycles): %d", &filedata->quantum);
sscanf(buffer, "Memory Available (KB): %d", &filedata->memory);
sscanf(buffer, "Processor Cycle Time (msec): %d", &filedata->processTime);
sscanf(buffer, "I/O Cycle Time (msec): %d", &filedata->ioTime);
sscanf(buffer, "Log To: %s", logToCode);
sscanf(buffer, "Log File Path: %s", filedata->logPath);
tempLine = fgets(buffer, 255, cfgfile);
if(tempLine == NULL)
{
break;
}
printf("%s\n", buffer);
}
The file looks like this:
Start Simulator Configuration File
Version/Phase: 1.0
File Path: Test_3.mdf
CPU Scheduling Code: NONE
Quantum Time (cycles): 55
Memory Available (KB): 667
Processor Cycle Time (msec): 10
I/O Cycle Time (msec): 20
Log To: Monitor
Log File Path: logfile_1.lgf
End Simulator Configuration File.
Does anyone have any idea why the while loop keeps going on forever?
When you say the loop runs forever do you keep getting some output for the statement printf("%s\n", buffer); just before the loop termination. Is so what's being printed?
I'd suggest to test the loop logic independent of sscanf code. Following block should work as such (do not need extra check etc.)
fgets(buffer, 255, cfgfile); // just to skip the first line
while( (tempLine = fgets(buffer, 255, cfgfile)) != NULL)
{
// all the scanfs to read from tempLine/buffer
..
}
With that in place you may want to add in your sscanfs and progressively go from there. Some minor comments around FILE *cfgfile = malloc(sizeof cfgfile);. This is not required because the call to fopen will return a pointer to FILE and you'll end up leaking the malloc'd cfgfile. Also not sure if this is the complete code or not but logToCode and schedulingCode will also end up getting leaked.
Welcome to SO! Note that you really need an MCVE for questions like this... Also, do not update your question to change the code; this changes the question, which renders all previous answers to it invalid. If you're going to ask a new question, ask it as a new question!
char *tempLine = malloc(sizeof tempLine); // Suggested to me as a fix
I don't trust that fix. Why allocate a block of memory pointing at a char, that has the size of a char *? How unusual... There are several other variables in your code that are initialised like this, and it seems like guess-work. Your guess-work is likely causing problems.
tempLine = fgets(buffer, 255, cfgfile); then overwrites the pointer to that allocation with buffer or NULL, which results in a memory leak. Whoever suggested that fix to you needs to learn about valgrind (as do you).
Nonetheless, if you ever wish to trash a line of input, that'd be better achieved without an intermediate buffer like so:
fscanf(cfgfile, "%*[^\n]");
fgetc(cfgfile);
I'm curious, why is it you appear to be using %d to read floating point data?
sscanf(buffer, "Version/Phase: %d", ... // Version/Phase: 1.0
If only I could see the type of filedata->version, I might be able to verify this as an error...
You claimed to have updated this code, yet I can't see the update:
tempLine = fgets(buffer, 255, cfgfile);
if(buffer == NULL) // Even checking to see if buffer was null doesn't work
There's an error here, and to identify it, I want you to show me how fgets could assign buffer = NULL; like you seem to expect, in this testcase:
#include <stdio.h>
char *fgets_fake(char *b, int b_length, FILE *f) {
/* INSERT CODE HERE! */
/* Show me how `fgets` can assign `buffer = NULL;` here */
return b;
}
int main(void) {
char *buffer = "HELLO WORLD!";
fgets_fake(buffer, 0, 0);
printf("Is buffer NULL? %s\n", b ? "no" : "yes");
}
You probably meant if (tempLine == NULL), since that can be reassigned (following the return).

Whats wrong with fread in this program?

I'm intermediate student of C. I'm trying to make a bank management program but first I need to make a login program, so I created one of the following. As I've recently learned about file I/O in C and don't know much about fread and fwrite. I have a file (data.txt) which format if as following.
user1 1124
user2 3215
user3 5431
In the following program I've asked user to input user name and pin(4-digit password) and copy file data into a structure then compare these two for verifying information.
What is wrong with my program and how to make fread work properly. And is the formating in data.txt file all right or should I change it.
Thanks in advance...
#include<stdio.h>
#include<ctype.h>
#include<string.h>
struct user_account {
char u_name[30];
int u_pin;
} log_in;
int login()
{
int start;
int i, n;
int t_pin[4]; // TEMPORARY INT PIN for storing pin inputed by user
char t_name[30]; // TEMPORARY STRING for storing name inputed by user
FILE *fp;
fp = fopen("data.txt","rb"); // Opening record file
if(fp == NULL)
{
puts("Unable to open file!");
return 1;
}
start : {
printf("User Name : ");
scanf("%s",&t_name);
printf("Pin Code : ");
for(i = 0; i < 4; i++) { // This loop is for hiding input pin
n = getch();
if(isdigit(n)) {
t_pin[i] = n;
printf("*"); }
else {
printf("\b");
i--;
}
}
fread(&log_in,sizeof(log_in),1,fp);
// Comparing user name and pin with info in the structure copied from the file
if(strcmp(log_in.u_name, t_name) == 0 && log_in.u_pin == t_pin)
puts("Login successful! Welcome User");
else {
printf("\nIncorrect Information!\n");
printf("Press any key to log in again...");
getch();
system("cls");
goto start; }
}
}
int main()
{
int login();
return 0;
}
The problem is that you have an ASCII/text file but you are trying to use fread to read directly into a structure; this requires a binary formatted file. fread cannot do format conversion. Use fscanf instead:
fscanf(fp, "%s %d", &log_in.u_name, &log_in.u_pin);
Problems that I see:
The following line is incorrect.
scanf("%s",&t_name);
You need to use:
scanf("%29s",t_name);
fread is not the right solution given the file format. fread works when the file is in binary format.
Given the format of your input file, you need to use:
scanf("%29s %d", log_in.uname, &log_in.u_pin);
Comparing the pins using log_in.u_pin == t_pin should produce a compiler error. The type of log_in.u_pin is int while the type of t_pin is int [4]. You will have to change your strategy for getting the integer value from t_pin.
You can use something like:
int pin = 0;
for (i = 0; i < 4; ++i )
{
pin = 10*pin + t_pin[i];
}
However, for that to work, you have store the proper integer values in t_pin. When the character you read is '1', you have to store 1. Either that, or you will have to change the above for loop to:
pin = 10*pin + t_pin[i]-'0';
The way are using goto start to loop in your function is not correct. You haven't read all the user ID and pins to from the input file and compared them. The way you have it, you will end up doing:
Read the user name and pin
Check against the first entry in the file. If they don't match...
Read the user name and pin again.
Check against the next entry in the file. If they don't match...
etc.
You want to:
Read the user name and pin
Check against the first entry in the file. If they don't match...
Check against the next entry in the file. If they don't match...
Check against the next entry in the file. If they don't match...
etc.
a) fread is used to read fixed sized (in bytes) records. While that is an option, you might find that fscanf is more suited to your use case.
b) goto has its uses, arguably, but in your particular case a while loop is far more appropriate as it make the intention clearer and is less susceptible to misuse.
c) The crux of your issue is reading the file. You only read in and check against a single record from your file per pass and once all records are read you will be getting EOF rather than additional records.
You have a few options. You might read the entire user table into a list prior to your login loop. When a user logs in you must iterate through the list until a match or end of list is found. Alternatively you might loop through the entire file on each user attempt looking for a match and seek to the back to the beginning of the file when you are done. In either case you must check for and handle read errors.
d) Finally when a non-digit is entered you will probably find that backspace isn't what you had in mind. Also you might consider allowing the user to press backspace while entering the pin.

Resources