I am trying to do two things: read from a file descriptor into a struct and then I need to read into a memory location. For the first, I am using fread. For the second I am using read from unistd.h.
Is there anyway I can do both without needing to fopen, fclose, open, close and repeating like this? Am I able to use "read" to read into a struct?
CodePudding user response:
After calling fopen()
, you may get the file descriptor of a file pointer via fileno()
, like this:
int fd;
fd = fileno (fp);
Then you may use read
directly. Indeed, both fopen()
and open()
opens a file for a process, the former returns a pointer to the file while the latter returns the file descriptor. The FILE
type is just a struct
which has a field that records the underlying file descriptor.
In other words, on unix-like systems, fopen()
is implemented using open()
, so there must be a way to use both calls simultaneously.
As a note, on my system (wsl2), the definition of FILE
can be found under the path /usr/include/x86_64-linux-gnu/bits/types/struct_FILE.h
, which is something like this:
struct _IO_FILE
{
/* ... */
int _fileno;
/* ... */
};
CodePudding user response:
Yes, of course you can read data (here a string) into a struct:
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#define DATA_LEN 1024
struct s {
char data[DATA_LEN];
};
int main() {
int fd = open("1.c", O_RDONLY);
if(fd == -1) {
perror("file");
return 1;
}
struct s s;
ssize_t n = read(fd, s.data, DATA_LEN);
printf("%.*s\n", (int) n, s.data);
close(fd);
}
CodePudding user response:
Re: Is there anyway I can do both without needing to fopen, fclose, open, close and repeating like this?
Yes. You can obtain a file descriptor corresponding to the FILE *
with fileno()
. From the man page:
The function fileno() examines the argument stream and returns the integer file descriptor used to implement this stream. The file descriptor is still owned by stream and will be closed when fclose(3) is called.
As an example:
/**
* @brief Opens the file whose name is the string pointed to
* by file_name and associates a stream with it.
* @return A file descriptor corresponding to the stream on success,
* -1 elsewise.
*/
static int open_sesame (FILE *stream, const char *file_name)
{
stream = fopen (file_name, "r");
if (!stream) {
perror ("fopen()");
/* Handle error here */
return -1;
}
/* On failure, -1 is returned and errno is set to indicate
* the error.
*/
int fd = fileno (stream);
if (fd == -1) {
perror ("fileno()");
/* Handle error here */
return -1;
}
return fd;
}
This too will do:
/* read() will return -1 and set errno to EBADF¹
* if fileno() fails.
*/
if (read (fileno (fp), ..., ..., ...) == -1) {
perror ("read()");
/* Handle error here */
}
On the other hand, if you want to open a stream for a corresponding file descriptor, then there's fdopen()
. From the man page:
The fdopen() function shall associate a stream with a file descriptor.
A sample call:
/* Upon successful completion, fdopen() shall return
* a pointer to a stream; otherwise, a null pointer
* shall be returned and errno set to indicate the
* error.
*/
FILE *stream = fdopen (fd, "r");
if (!stream) {
perror ("fdopen()");
/* Handle error here */
}
Alternatively, as @Fe203 suggested, if you require two copies of the data, you can use the standard memcpy()
instead of reading from the file twice. Or as you're already using POSIX system calls, it might be more efficient to map the file into memory with mmap()
and work with it.
[1] — EBADF
: fd
is not a valid file descriptor or is not open for reading.