I'm writing a code that will count the number of processes who parent process is init
. Called fork and had the child use an exec
function and pass its output through a pipe back to the parent. All seemed fine on that end, but when I used fdopen
on the parent's read-end of the pipe, followed by fscanf
the program crashed, even though the FILE
stream is not NULL
. I placed checks on every function call.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
static void fatalError(char *message);
int main(int argc, char *argv[])
{
int total, initCount = 0;
int pipeToParent[2];
pid_t pid;
FILE *file;
if (pipe(pipeToParent) < 0)
fatalError("pipe() error");
if ((pid = fork()) < 0)
fatalError("fork() error");
else if (pid == 0) {
if (close(pipeToParent[0]) < 0)
fatalError("close() error");
if (dup2(pipeToParent[1], STDOUT_FILENO) < 0)
fatalError("dup2() error");
execlp("ps", "ps", "ahxo", "ppid", NULL);
fatalError("exec() error");
}
if (close(pipeToParent[1]) < 0)
fatalError("close() error");
wait(NULL);
if ((file = fdopen(pipeToParent[0], "r")) == NULL)
fatalError("fdopen() error");
for (total = 0; fscanf(file, "%d", &pid) != EOF; total++)
if (pid == 1)
initCount++;
if (fclose(file) < 0)
fatalError("fclose() error");
printf("%.2f%%\n", (float) initCount * 100 / total);
exit(EXIT_SUCCESS);
}
static void fatalError(char *message) {
perror(message);
exit(EXIT_FAILURE);
}
Running GDB gave this:
Program received signal SIGSEGV, Segmentation fault.
__isoc99_fscanf (stream=0x55757260, format=0x555555554c44 "%d") at isoc99_fscanf.c:30
30 isoc99_fscanf.c: No such file or directory.
And the Makefile warnings:
gcc -std=c11 -g -Wall -pedantic init.c -o init
init.c: In function ‘main’:
init.c:55:14: warning: implicit declaration of function ‘fdopen’; did you mean ‘fopen’? [-Wimplicit-function-declaration]
if ((file = fdopen(pipeToParent[0], "r")) == NULL)
^~~~~~
fopen
init.c:55:12: warning: assignment makes pointer from integer without a cast [-Wint-conversion]
if ((file = fdopen(pipeToParent[0], "r")) == NULL)
^
In C if no previous declaration of function exist it is assumed the function returns an int
. If the compiler assumes the function returns an int
while it returns FILE *
and if sizeof(FILE*) < sizeof(int)
the return value is truncated and is invalid. Thus you get internal glibc errors as the pointer passed to fscanf
is truncated and invalid.
From man fdopen you can read:
fdopen(): _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE
These are the macros that you need to define to have fdopen()
declaration in your program. You need to define them before any inclusion. I usually just define _GNU_SOURCE
which defines _POSIX_C_SOURCE
etc. in features.h
.
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
.. rest of the program ...
For more information see future test macros .
When using gcc
you can also do gcc -std=gnu11
or other -std=gnu*
. -std=c11
is the same as -std=gnu11
except the macro _GNU_SOURCE
is predefined when compiling.
Per the Linux man page for fdopen()
, Linux/glibc requires definition of the _POSIX_C_SOURCE
macro:
Feature Test Macro Requirements for glibc (see feature_test_macros(7) ):
fdopen(): _POSIX_C_SOURCE
Otherwise, you get an implicit declaration, which as already noted means the function is assumed to return int
. And the pointer that fdopen()
actually returns likely doesn't fit into that int
, so it's truncated.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.