My goal is to control buffering of child process when executed with execvp.
More precisely, I want to redirect stdout and stderr to the same file descriptor (this is wanted behaviour, I cannot change that). But, how the default buffering mechanism work provokes unexpected behaviour. For example, when this python script is executed in your terminal:
print("test sdout recup")
print("test stderr recup", file=sys.stderr)
stdout is line buffered, so the first print is instantly flushed (print adds by default a newline in python) then stderr is unbuffered, and thus directly prompted which lead to:
test sdout recup
test stderr recup
When I execute the same script with my C code (see the code in the end), I got all the time:
test stderr recup
test sdout recup
Since stdout is not a terminal, (it's a pipe), stdout become fully buffered, while stderr is still unbuffered, leading to this order.
I need a way to control those modes (from C, not via another process) to preserve the same terminal output, and also later to unbuffer stdout as well (for another goal) but I really don't know how to do. I have seen some code that work with files pointer instead of file descriptors (FD) but I cannot find same functions for the field.
Also, I am not even sure that this is controllable from the parent process. So here I am.
Here is the main code: output.h:
#include <stddef.h>//size_t
typedef struct Output
{
char* out;
int status;
double times;
} Output;
Output* Output_new();
/*Return an correctly initialized Ouput in regard to the buffer size*/
size_t read_append_into_Output( int fd, Output* out, size_t* current_size );
/*Append the result in the output buffer and manage size properly(actualize constructor with new default size, prevent overflow...*/
executor.c:
#include "executor.h"
#include "argv.h"//buildarg
#include <unistd.h>//fork
#include <stdio.h>//pipe
#include <stdlib.h>//EXIT_SUCCESS
#include <sys/wait.h>
#include <string.h> //strlen
#include <errno.h>//perror
#define READ 0
#define WRITE 1
void child_life(char** argv){
/*Do child stuff*/
// char* expected[] = {"test.py", "test", NULL};
execvp(*argv, argv);
perror("Process creation failed");
}
//TODO better control over when to send in pipe
void parent_life(int read_fd, int write_fd, char** prompt, size_t prompt_number, Output* output){
//inject prompt
for (int i=0; i<prompt_number; i++){
write(write_fd, prompt[i], strlen(prompt[i]));//TODO dont call strlen and control ourself the size?
}
size_t readed=0;
size_t max_read=0;
while (max_read==readed){//we stop when we read less what we should or error
max_read= read_append_into_Output(read_fd, output,&readed);
}
output->out[readed]=0;
}
Output* executor_get_output(char* command, char** prompt, size_t prompt_number, double timout)
{
Output* output=Output_new();
int pipe_father[2];
int pipe_son[2];
pipe(pipe_father);
pipe(pipe_son);
pid_t cpid;
int argc;
char** argv= buildargv(command,&argc); // We do it here because code betwen fork and exec is dangerous (must not contain malloc for exemple)
cpid = fork();
if (cpid == 0) { /* Child reads from pipe */
/*Listening on father pipe*/
close(pipe_father[WRITE]); /* Close unused write end */
dup2(pipe_father[READ], STDIN_FILENO); /*Replace STDIN by our pipe*/
/*Redirecting stdout and stder to the write pipe*/
close(pipe_son[READ]);
dup2(pipe_son[WRITE], STDOUT_FILENO); /*Replace STDOUT by our pipe*/
dup2(pipe_son[WRITE], STDERR_FILENO);
child_life( argv);
//EXIT (executed only if exevp failed)
close(pipe_father[READ]);
close(pipe_son[WRITE]);
_exit(EXIT_FAILURE);
}
//Parent code
close(pipe_father[READ]); /* Close unused read end */
close(pipe_son[WRITE]);
parent_life( pipe_son[READ], pipe_father[WRITE], prompt, prompt_number, output);
//EXIT
close(pipe_father[WRITE]); /* Reader will see EOF */
waitpid(cpid, NULL,0); /* Wait for child terminaison*/
close (pipe_son[READ]);
return output;
}
You can find on github a convenient build to compile not shown dependencies of the code you saw plus a test to mess around if you want:
git clone -b dev https://github.com/crazyhouse33/authbreak.git
cd authbreak/build
cmake ..
make executor
This commands create the binary in bin/tests directory.
The associated test source code is in tests/execution/executor.c
The test runs an execution of such a showed python script and compare the obtained output with my already presented expectations. For some reason the test segfault when ran from make test (ctest) but now when you run it manually.
<enter your concise problem statement here>
in 30 words or less. – Brenna