Моя цель - контролировать буферизацию дочернего процесса при выполнении с execvp.
Точнее, я хочу перенаправить stdout и stderr в один и тот же файловый дескриптор (это желаемое поведение, я не могу его изменить). Но то, как работает механизм буферизации по умолчанию, вызывает неожиданное поведение. Например, когда этот скрипт python выполняется в вашем терминале:
print("test sdout recup")
print("test stderr recup", file=sys.stderr)
stdout буферизуется строкой, поэтому первая печать мгновенно сбрасывается (print добавляет по умолчанию новую строку в python), затем stderr небуферизован и, таким образом, напрямую запрашивается, что приводит к:
test sdout recup
test stderr recup
Когда я выполняю тот же скрипт с моим кодом C (см. код в конце), я получаю все время:
test stderr recup
test sdout recup
Поскольку stdout не является терминалом (это канал), stdout становится полностью буферизованным, в то время как stderr все еще не буферизован, что приводит к этому порядку.
Мне нужен способ управления этими режимами ( с C, а не с помощью другого процесса), чтобы сохранить тот же вывод терминала, а также позже, чтобы отменить буфер stdout (для другой цели), но я действительно не знаю, как это сделать. Я видел некоторый код, который работает с указателем файлов вместо файловых дескрипторов (FD), но я не могу найти те же функции для поля.
Кроме того, я даже не уверен, что это управляемо из родительского процесса. Вот и я.
Вот основной код: 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;
}
Вы можете найти на github удобная сборка для компиляции не показанных зависимостей кода, который вы видели, плюс тест, который вы можете возиться, если хотите:
git clone -b dev https://github.com/crazyhouse33/authbreak.git
cd authbreak/build
cmake ..
make executor
Эти команды создают двоичный файл в каталоге bin / tests.
связанный исходный код теста находится в tests / execute / executor. c
В ходе теста выполняется выполнение такого показанного python сценария и сравнивается полученный результат с моими уже представленными ожиданиями. Почему-то тестовый segfault запускался из make test (ctest), но теперь, когда вы запускаете его вручную.
Спасибо.