fileno for closed file - c

I have a function like this which aims to read a file:
int foo(FILE* f)
I want to use flock in order to prevent TOCTTOU. flock requires a file descriptor as an integer. I can get this using fileno(file). The implementation of foo therefore might look like this:
int foo(FILE* f) {
if(!f) return -1;
int fd = fileno(f);
if(fd < 0) return -1;
flock(fd, LOCK_EX);
//do all the reading stuff and so on.
}
However, the evil user might do something like this:
FILE* test;
test = fopen("someexistingfile.txt", "r");
fclose(test);
foo(test);
Then I have a problem because fileno will do invalid reads according to valgrind because it assumes that the file is open.
Any ideas on how to check whether the file is closed?

C11 n1570 7.21.3p4
A file may be disassociated from a controlling stream by closing the file. Output streams are flushed (any unwritten buffer contents are transmitted to the host environment) before the stream is disassociated from the file. The value of a pointer to a FILE object is indeterminate after the associated file is closed (including the standard text streams). Whether a file of zero length (on which no characters have been written by an output stream) actually exists is implementation-defined.
After fclose the use of the value of a FILE * in library functions leads to undefined behaviour. The value of the pointer cannot be used safely for anything at all until reassigned.
In other words, you cannot do really anything at all to discern whether the FILE * value you've given refers to a valid open file or not... well except for testing against NULL - if the value of the pointer is NULL it certainly cannot point to an open stream.

Related

Writing to file using setvbuf, conditionally discard buffer contents

I would like to write a simple API which
allows the user to open a file.
let the user write data to the file
track the write calls and sanity check the written data after each write call.
prevents the data from beeing written to disk if it is not valid -> discard(file)
As a starting point i wrote the test program below, which opens a file in fully buffered "rb+" mode using fopen and setvbuf.
The stream is opened in fully buffered mode for the following reason:
http://www.cplusplus.com/reference/cstdio/setvbuf/
mode
Specifies a mode for file buffering.
Three special macro constants [...]:
_IOFBF Full buffering: On output, data is written once the buffer is full (or flushed). On Input, the buffer is filled when an input
operation is requested and the buffer is empty.
My testprogram contains comments where a validity check could be placed and where the buffer contents should be discarded.
My question is how do i accomplish the discard(file) operation which means the step of getting rid of invalid buffer contents ?
The idea behind this is to assemble some data in the buffer, do a regular validity check after each or several write operations and write the data to disk only, if the data is valid.
Therefore i would need to discard the buffer, if the validity check fails.
When the validity check passes, the whole buffer contents should be written to the file.
My code draft looks like in the following. This is a simplified example:
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
int main(void)
{
static uint8_t buffer[10000];
/* The following would be part of mylib_init */
FILE *file = fopen("test", "wb+");
if (file == NULL){
print ("open error!");
exit(-1);
}
if ( 0 != setvbuf(file , buffer, _IOFBF , sizeof(buffer) ) ){
print("Could not set buffer!");
fclose(file);
exit (-2);
}
/* The following would be part of mylib_write_data.
Each write and check resembles one func call */
// Pretend the user writes some data into the file
// ...
// fwrite(x)
if (data_in_buffer_not_valid(buffer)){
discard(file);
}
// ...
// fwrite(y)
//
if (data_in_buffer_not_valid(buffer)){
discard(file);
}
// ...
// fwrite(z)
// ...
// The following would be part of mylib_exit
// Cleanup stuff
fclose(file)
return 0;
}
If you want to have some like "scratch" temporary file that you want to write your data into and then retrieve them later, then the portable interface would be tmpfile() - it's an interface created just for that. Write to that file, rewind if you want, and when you're ready, rewind it and read from it block by block to another file.
On linux you may use fmemopen and fopencookie to write to a buffer via FILE* - these functions are not available on windows.
I would also strongly consider just creating your own interface that would store the result in memory. Writing an interface like struct mystream; mystream_init(struct mystream *); mystream_printf(struct mystream *, const char *fmt, ...); etc. is some of the tasks you sometimes do in C when fopencookie is not available. And consider writing the interface for storing data, so that instead of calling fwrite you would actually call the function that would check the data and write them and process them along the way.
As for setvbuf, note the standard. From C11 7.21.3p3:
When a stream is unbuffered, characters are intended to appear from the source or at the destination as soon as possible. Otherwise characters may be accumulated and transmitted to or from the host environment as a block. When a stream is fully buffered, [...]. When a stream is line buffered, [...] Support for these characteristics is implementation-defined, and may be affected via the setbuf and setvbuf functions.
And these buffering modes may just be not supported at all. And from C11 7.21.5.6:
The setvbuf function may be used only after the stream pointed to by stream has been associated with an open file and before any other operation (other than an unsuccessful call to setvbuf) is performed on the stream. [...] The contents of the array at any time are indeterminate.
You can't count on anything what will be the content of the buffer. Do not expecting any data there.

Why does fopen("/dev/null", "w") returns standard file descriptor on AIX?

We have a code as part of bigger application where we are trying to fopen() a /dev/null and return the file pointer. This code use to return a non-standard file descriptor earlier ( probably with AIX 6.1 or lower version ).
It appears that after migrating/upgrading to AIX 7.1, the above code returns a standard file descriptor.
I was wondering if there is any fundamental change that happened to AIX 7.1 version that could potentially affect the fopen() system call?
I believe there was no source code change to the application that could result in the above change in fopen() output.
I tried with a simple, sample code (outside of my application) that does fopen on /dev/null; this seems to return fd of 3 always. But in my application, it returns 1.
So, I am not able to understand where the problem is.
FILE *fp = fopen("/dev/null", "w");
fprintf(stdout, "fd = %d\n", fileno(fp)); // --> this prints 1 in my application, but print 3 in a sample code.
Sorry about the confusion. The below is the actual function:
FILE* GetDevNull()
{
FILE* filesToClose[3];
int count = 0;
FILE* fp = fopen("/dev/null", "a");
while(fp && fileno(fp) <= 3)
{
filesToClose[count++]=fp;
fp = fopen("/dev/null", "a"); // this returns fileno(fp)=1 (STDOUT)
}
while(count)
fclose(filesToClose[--count]); // STDOUT is closed here
return fp;
}
As I understand, in case fopen returns 0, 1 or 2, they just cache the corresponding file pointers and then they close before returning an other file pointer ( that has a fd more than 2 ).
The background is this:
FILE* fplog = fopen("my.log", "w");
dup2(fileno(fplog), STDOUT_FILENO); // happening else where in the application
After the above, following is getting called.
FILE* fpnull = GetDevNull();
After the above call to GetDevNull(), now STDOUT doesn't point to my.log but to /dev/null.
So, the question is, why fopen() in GetDevNull() is returning 1 ? This appears to be happening after we migrated to AIX 7.1. So, I was wondering if there was any major update to AIX 7.1 that could have affected this?
I tried with a simple, sample code (outside of my application) that
does fopen on /dev/null; this seems to return fd of 3 always. But in
my application, it returns 1. So, I am not able to understand where
the problem is.
If fopen("/any/path", "any mode") succeeds and the associated file has file number 1, then it must be the case that the program previously closed file number 1, possibly by closing its stdout stream, or that it started started with it closed. The file numbers for the standard streams are not inherently special. If one of them is available when you open a file then the file will get that number for its file descriptor, because the system uses the lowest available file descriptor number. This is intended behavior.

Reading from file in C using fread

I'm learning how to read content from a file in C. And I manage to scrape through the following code.
#include <stdio.h>
#include <stdlib.h>
void read_content(FILE *file) {
char *x = malloc(20);
// read first 20 char
int read = fread(x,sizeof(char),20,file);
if (read != 20) {
printf("Read could not happen\n");
}
else {
printf("the content read is %s",x);
}
free(x);
return;
}
int main(int argc,char *argv[]) {
FILE *fp;
fp = fopen("test.txt","w+");
read_content(fp);
fclose(fp);
return 0;
}
But for some reason (which I'm not able to understand) I see the read bytes count as 0.
The problem is that you open the file with the w+ mode. There are two possibilities:
if the file doesn't exist, it will be created empty. Reading from it immediately gives end of file resulting in fread() returning 0.
if the file does exist, it will be truncated i.e. changed into an empty file. Reading from it immediately gives end of file resulting in fread() returning 0.
If you just want to read from the file (as per your example), open it in mode r. If you want to read and write without destroying its existing content, use mode r+.
Whatever mode you choose, always check that fopen() returns non null and print the error if it returns null (this is not the cause of your problem but is best practice).
From Man Page w+ flag:
Open for reading and writing. The file is created if it does
not exist, otherwise it is truncated.
You are probably trying to open a file which doesn't exist at the path you provided, or is read-only as #WhozCraig suggested in comment. This means a new file is being created, an empty file! hence you are seeing 0 bytes read.
To sum up, The fopen is failing, in that case you need to check the return value if it is equal to -1.
To find what was the error, you can check the errno as it is set to
indicate the error.
If you are only intending to read, open the file with r flag instead of w+
The problem lies within this line of code:
fp = fopen("test.txt","w+")
the "w+" mode, clear the previous content of the file and the file will be empty when you just going to read the file without writing anything to it. Hence, it is printing "Read could not happen" because you are trying to read an empty file.
I would suggest you to use "r+" mode, if you are willing to read and then write into the file. Otherwise, r mode is good enough for simple reading of a file.

Logging fails if program stopped using ctrl + c

I have written a C program in which I am logging the results to a file. There is an infinite while loop - this is a requirement. To debug the code, I need to look at the log file, but as the program is running, I don't see anything written there. Closing the program forcibly using ctrl+C does not help either. I see nothing written on the file.
I am using simple fopen and fprintf functions to read the file in write mode and write to it.
FILE *fp = fopen("filename.txt", "w");
fprintf(fp, "this wants itself to be written the moment this statement is executed\n");
PS: There is no bug in the code. If I put a terminating condition in while loop and program exits gracefully, I do see things written in the log file.
A difference between printing to a console and printing to a file is that streams are line buffered by default when attached to the console, but block buffered when attached to a file. Change your code to:
FILE *fp = fopen("filename.txt", "w");
setvbuf(fp,0,_IOLBF,0);
fprintf(fp, "this wants itself to be written the moment this statement is executed\n");
and your output will be line buffered even though the stream is attached to a file. You can also do unbuffered streams.
[EDIT: ]
Ref C11 7.21.5.6:
Synopsis
#include <stdio.h>
int setvbuf(FILE * restrict stream,
char * restrict buf,
int mode, size_t size);
Description
The setvbuf function may be used only after the stream pointed to by
stream has been associated with an open file and before any other
operation (other than an unsuccessful call to setvbuf) is performed on
the stream. The argument mode determines how stream will be buffered,
as follows: _IOFBF causes input/output to be fully buffered; _IOLBF
causes input/output to be line buffered; _IONBF causes input/output to
be unbuffered. If buf is not a null pointer, the array it points to
may be used instead of a buffer allocated by the setvbuf function
and the argument size specifies the size of the array; otherwise, size
may determine the size of a buffer allocated by the setvbuf function.
The contents of the array at any time are indeterminate.
Returns
The setvbuf function returns zero on success, or nonzero if an invalid
value is given for mode or if the request cannot be honored.
You should to see the the function fopen(),if you fopen a file with "w" mode,it means if this file exist,clear this file and then write.I think you should use "a+" mode to append data in the end.

Using fseek with a file pointer that points to stdin

Depending on command-line arguments, I'm setting a file pointer to point either towards a specified file or stdin (for the purpose of piping). I then pass this pointer around to a number of different functions to read from the file. Here is the function for getting the file pointer:
FILE *getFile(int argc, char *argv[]) {
FILE *myFile = NULL;
if (argc == 2) {
myFile = fopen(argv[1], "r");
if (myFile == NULL)
fprintf(stderr, "File \"%s\" not found\n", argv[1]);
}
else
myFile = stdin;
return myFile;
}
When it's pointing to stdin, fseek does not seem to work. By that, I mean I use it and then use fgetc and I get unexpected results. Is this expected behavior, and if so, how do I move to different locations in the stream?
For example:
int main(int argc, char *argv[]) {
FILE *myFile = getFile(argc, argv); // assume pointer is set to stdin
int x = fgetc(myFile); // expected result
int y = fgetc(myFile); // expected result
int z = fgetc(myFile); // expected result
int foo = bar(myFile); // unexpected result
return 0;
}
int bar(FILE *myFile) {
fseek(myFile, 4, 0);
return fgetc(myFile);
}
Yes, it's perfectly normal that fseek won't work on stdin -- it'll normally only work on a disk file, or something reasonably similar.
Though it's really a POSIX thing, you can typically use if (isatty(fileno(myFile))) to get at least a pretty good idea of whether seeking will work in a particular file. In some cases, isatty and/or fileno will have a leading underscore (e.g., IIRC the versions provided with Microsoft's compilers do).
Fseek() is based on lseek(), and the lseek man page discusses possible errors, including:
[ESPIPE] Fildes is associated with a pipe, socket, or FIFO.
If stdin is connected to a pseudo tty, I believe it will have socket behavior.
Here is the relevant entry in the ANSI standard concerning the fseek function:
For a text stream, either offset shall be zero, or offset shall be a value returned by an earlier successful call to the ftell function on a stream associated with the same file and whence shall be SEEK_SET
So, possible but with some limitations

Resources