How does fgets() keep track of what line it's on? - c

This code correctly reads a file line-by-line, stores each line in line[] and prints it.
int beargit_add(const char* filename) {
FILE* findex = fopen(".beargit/.index", "r");
char line[FILENAME_SIZE];
while(fgets(line, sizeof(line), findex)) {
strtok(line, "\n");
fprintf(stdout, "%s\n", line);
}
fclose(findex);
return 0;
}
However, I am baffled as to why using fgets() in the while loop actually reads the file line-by-line. I am brand new to C after having learned Python and Java.
Since each call to fgets() is independent, where is C remembering which line it is currently on each time you call it? I thought it might have to do with changing the value FILE* index points to, but you are passing the pointer into fgets() by value so it could not be modified.
Any help in understanding the magic of C is greatly appreciated!

It's not fgets keep track, findex does this for you
findex is a FILE type, which includes infomation about a open file such as file descriptor, file offset
FILE is a encapsulation of I/O in OS.
more about: FILE
Object type that identifies a stream and contains the information needed to control it, including a pointer to its buffer, its position indicator and all its state indicators.
and the offset is to keep track of the file, each time you read the file, it starts from the offset. All these work are done by FILE, it do for you, and for fgets
more info about offset offset wiki

I thought it might have to do with changing the value FILE* index points to
it's not the value of the pointer itself that is changed. The pointer points to a structure (of type FILE) which contains information about the stream associated with that structure/pointer. Presumably, one of the members of that structure contains a "cursor" that points to the next byte to be read.
Or maybe it's just got a file descriptor int (like on many Unices) and I/O functions just call out to the kernel in order to obtain information about the file descriptor, including the current position.

Related

How can i read a specific char inside a file?

This would be enough to read the first character 'a' inside fp
file.txt
abcdef
// readchar.c
FILE *fp = fopen("file.txt", "r");
int c = fgetc(fp);
but how can i read (e.g.) the 3rd character?
A text file is a kind of sequential file. To read a specific item, you must read everything preceding it in the file.
Operating systems let you read a file anywhere by moving a so called file pointer. The position in the file where read take place can be changed by seeking into the file. There are several ways to handle file I/O. The one you found invoke fopen to open the file and get a file pointer, fgetcto read the next character where the file pointer points and advance it by one character. You have fgetsto read a complete line. fseek to more the file pointer somewhere else. fcloseto close the file. And other similar function.
Back to the text file. Assuming we have a file containing: two lines containing:
Hello world,
Programming rocks!
If you want to read the 5th character of the first line, it is easy: just position the file pointer with fseek to the 5th position in the file (the first character is at position zero). Then read it with fgetc.
Now if you need to read the 5th character of the second line, whatever the first line is, you cannot use fseek because you don't know the length of the first line without reading the line first.
To read the Nth character on the Mth line, you must read M lines, throwing away data except the last one (You simply read all line in a for/loop into the same buffer). And then access the Nth character in the buffer where you just read the last line. Make that buffer an array of char and you have direct access to the Nth character.

fgets not reading the beginning of a line

I am having trouble reading a few lines of text from a file using fgets. The file is some basic user data that is written to a file within the bundle the first time the plugin is launched. Any subsequent launch of the plugin should result in the user data being read and cross referenced to check the users authenticity.
The data is always 3 lines long and is written with frwite exactly as it should be and is opened with fopen.
My original theory was to just call fgets 3 times reading each line into it's own char array which is part of a data struct. The problem is the first line is read correctly, the second line is read as though the position indicator starts on the next line but offset by the number of characters read from line 1. The third line is then not read at all.
fgets is not returning any errors and is behaving as though it has read the data it should have so i'm obviously missing something.
Anyway here's a portion of my code hopefully someone can some shed some light on my mistakes!
int length;
fgets(var.n, 128, regFile);
length = strlen(var.n);
var.n[length-1] = NULL;
fgets(var.em, 128, regFile);
length = strlen(var.em);
var.em[length-1] = NULL;
fgets(var.k, 128, regFile);
length = strlen(var.k);
var.k[length-1] = NULL;
fclose(regFile);
Setting the last character in each string to NULL is just to remove the /n
This sequence of code outputs the whole of line 1, the second half of line 2 and none of line 3.
Thanks to #alvits for the answer to this one:
fwrite() is not compatible with fgets(). Files created using fwrite() should use fread() to read them ?>back in. Both fwrite() and fread() operates on binary streams unless explicitly converted to and from >strings. fgets() is compatible with fputs(), both operates on strings.
I used fputs() to write my data instead and it read back in perfectly.
In POSIX systems, including Linux, there is no differentiation between binary and text files. When opening a file stream, the b flag is ignored. This is described in fopen().
You might ask "how would you differentiate text from binary files?". The contents differentiate them. How the contents are written makes them a binary or text file.
Look at the signature size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream). You'll notice that it writes the contents of *ptr with size describing the size of each members, nmemb. The written stream is not converted to string. If you were to write 97 it will write the binary 97 which in ascii is A. Binary data does not obey string terminations. Presence of \n and \0 in data is literally written as is.
Now look at the signature int fputs(const char *s, FILE *stream). It writes the string content of *s. If you were to write 97, it will have to be a string "97" which is not A. String termination is obeyed. \n is automatically converted to the O/S supported newline (CRLF or LF).
You can coerce fwrite() to behave like fputs() but not the other way around. For example, if you declare ptr as a pointer to string and calculate the size exactly as the length of the content excluding string terminator, you'll be able to write it out as text instead of binary. You will also need to handle \0 and \n and convert them to O/S supported newline. Writing the entire string buffer will write everything including and past the string terminators.

C, format file for data of HTTP response

I have no experience with fscanf() and very little with functions for FILE. I have code that correctly determines if a client requested an existing file (using stat() and it also ensures it is not a directory). I will omit this part because it is working fine.
My goal is to send a string back to the client with a HTTP header (a string) and the correctly read data, which I would imagine has to become a string at some point to be concatenated with the header for sending back. I know that + is not valid C, but for simplicity I would like to send this: headerString+dataString.
The code below does seem to work for text files but not images. I was hoping that reading each character individually would solve the problem but it does not. When I point a browser (Firefox) at my server looking for an image it tells me "The image (the name of the image) cannot be displayed because it contains errors.".
This is the code that is supposed to read a file into httpData:
int i = 0;
FILE* file;
file = fopen(fullPath, "r");
if (file == NULL) errorMessageExit("Failed to open file");
while(!feof(file)) {
fscanf(file, "%c", &httpData[i]);
i++;
}
fclose(file);
printf("httpData = %s\n", httpData);
Edit: This is what I send:
char* httpResponse = malloc((strlen(httpHeader)+strlen(httpData)+1)*sizeof(char));
strcpy(httpResponse, httpHeader);
strcat(httpResponse, httpData);
printf("HTTP response = %s\n", httpResponse);
The data part produces ???? for the image but correct html for an html file.
Images contain binary data. Any of the 256 distinct 8-bit patterns may appear in the image including, in particular, the null byte, 0x00 or '\0'. On some systems (notably Windows), you need to distinguish between text files and binary files, using the letter b in the standard I/O fopen() call (works fine on Unix as well as Windows). Given that binary data can contain null bytes, you can't use strcpy() et al to copy chunks of data around since the str*() functions stop copying at the first null byte. Therefore, you have to use the mem*() functions which take a start position and a length, or an equivalent.
Applied to your code, printing the binary httpData with %s won't work properly; the %s will stop at the first null byte. Since you have used stat() to verify the existence of the file, you also have a size for the file. Assuming you don't have to deal with dynamically changing files, that means you can allocate httpData to be the correct size. You can also pass the size to the reading code. This also means that the reading code can use fread() and the writing code can use fwrite(), saving on character-by-character I/O.
Thus, we might have a function:
int readHTTPData(const char *filename, size_t size, char *httpData)
{
FILE *fp = fopen(filename, "rb");
size_t n;
if (fp == 0)
return E_FILEOPEN;
n = fread(httpData, size, 1, fp);
fclose(fp);
if (n != 1)
return E_SHORTREAD;
fputs("httpData = ", stdout);
fwrite(httpData, size, 1, stdout);
putchar('\n');
return 0;
}
The function returns 0 on success, and some predefined (negative?) error numbers on failure. Since memory allocation is done before the routine is called, it is pretty simple:
Open the file; report error if that fails.
Read the file in a single operation.
Close the file.
Report error if the read did not get all the data that was expected.
Report on the data that was read (debugging only — and printing binary data to standard output raw is not the best idea in the world, but it parallels what the code in the question does).
Report on success.
In the original code, there is a loop:
int i = 0;
...
while(!feof(file)) {
fscanf(file, "%c", &httpData[i]);
i++;
}
This loop has a lot of problems:
You should not use feof() to test whether there is more data to read. It reports whether an EOF indication has been given, not whether it will be given.
Consequently, when the last character has been read, the feof() reports 'false', but the fscanf() tries to read the next (non-existent) character, adds it to the buffer (probably as a letter such as ÿ, y-umlaut, 0xFF, U+00FF, LATIN SMALL LETTER Y WITH DIAERESIS).
The code makes no check on how many characters have been read, so it has no protection against buffer overflow.
Using fscanf() to read a single character is a lot of overhead compared to getc().
Here's a more nearly correct version of the code, assuming that size is the number of bytes allocated to httpData.
int i = 0;
int c;
while ((c = getc(file)) != EOF && i < size)
httpData[i++] = c;
You could check that you get EOF when you expect it. Note that the fread() code does the size checking inside the fread() function. Also, the way I wrote the arguments, it is an all-or-nothing proposition — either all size bytes are read or everything is treated as missing. If you want byte counts and are willing to tolerate or handle short reads, you can reverse the order of the size arguments. You could also check the return from fwrite() if you wanted to be sure it was all written, but people tend to be less careful about checking that output succeeded. (It is almost always crucial to check that you got the input you expected, though — don't skimp on input checking.)
At some point, for plain text data, you need to think about CRLF vs NL line endings. Text files handle that automatically; binary files do not. If the data to be transferred is image/png or something similar, you probably don't need to worry about this. If you're on Unix and dealing with text/plain, you may have to worry about CRLF line endings (but I'm not an expert on this — I've not done low-level HTTP stuff recently (not in this millennium), so the rules may have changed).

fread doesn't work how it should

I try to read a file to which my FILE* fp points and I want to know where the end of the file is. Therefore I use fseek(); At the end of the file, I want to write data from my structure data.
void printData(FILE *fp)
{
struct data tmp;
fseek(fp,0,SEEK_END);
while(fread(&tmp,sizeof(struct data),1,fp) > 0)
{
puts("test2");
printf("Vorname: %s\n",tmp.vorname);
printf("Nachname: %s\n",tmp.name);
printf("Adresse: %s\n",tmp.adresse);
}
}
This is how my structure is defined:
struct data
{
char name[30];
char vorname[20];
char adresse[50];
};
My Problem is, that the while loop isn't executed even once. Do I forgot something?
fseek(fp,0,SEEK_END) positions the file pointer at the end of the file (starting point end of the file offset 0), when you then try to read from the file fread of course doesn't read anything.
instead open the file in append mode and fwrite the number of records, these will be appended to the file.
The third variable '1' in fread actually indicates number of items to be read and you are just reading one item. Refer fread document for this:
http://pubs.opengroup.org/onlinepubs/009696899/functions/fread.html
After seeking to the end-of-file you won't be able to read anything. If you just want to know the file size, you can mybe use fstat() instead, or you do the fseek() after reading what you wanted to read, thad depends on what you're trying to achieve.
fread() is used for reading contents from file not for writing.
Use fwrite() for writing contents to file.
Like:
fwrite(&tmp , 1 , sizeof(struct data) , fp );
Read more about:
fread() and fwrite()
You are seeking to the start of the file, since you're setting the offset to 0. That doesn't sound like what you want to be doing, but on the other hand seeking to the end and then trying to read would also fail. I'm confused. :/
Could it be that you meant fwrite(), rather than `fread()? Not likely since the rest of the code prints the results after the I/O, which is logical for reading but not for writing.
It would be helpful with more information, like your file is opened and what it does contain when you run the program.

Read file in array line by line

Can you set any index of array as starting index i.e where to read from file? I was afraid if the buffer might get corrupted in the process.
#include <stdio.h>
int main()
{
FILE *f = fopen("C:\\dummy.txt", "rt");
char lines[30]; //large enough array depending on file size
fpos_t index = 0;
while(fgets(&lines[index], 10, f)) //line limit is 10 characters
{
fgetpos (f, &index );
}
fclose(f);
}
You can, but since your code is trying to read the full contents of the file, you can do that much more directly with fread:
char lines[30];
// Will read as much of the file as can fit into lines:
fread(lines, sizeof(*lines), sizeof(lines) / sizeof(*lines), f);
That said, if you really wanted to read line by line and do it safely, you should change your fgets line to:
// As long as index < sizeof(lines), guaranteed not to overflow buffer
fgets(&lines[index], sizeof(lines) - index, f);
Not like this no. There is a function called fseek that will take you to a different location in the file.
Your code will read the file into a different part of the buffer (rather than reading a different part of the file).
lines[index] is the index'th character of the array lines. Its address is not the index'th line.
If you want to skip to a particular line, say 5, then in order to read the 5th line, read 4 lines and do nothing with them, them read the next line and do something with it.
If you need to skip to a particular BYTE within a file, then what you want to use is fseek().
Also: be careful that the number of bytes that you tell fgets to read for you (10) is the same as the size of the array you are putting the line into (30) - so this is not the case right now.
If you need to read a part of a line starting from a certain character within that line, you still need to read the whole line, then just choose to use a chunk of it starting someplace other than the beginning.
Both of these examples are like requesting a part of a document from a website or a library - they're not going to tear out a page for you, you get the whole document, and you have to flip to what you want.

Resources