OK, I think I now have the first stone. A Process "class" (written in C99, because C++ is already irritating me now)
process.h
Code: Select all
#pragma once
#include <stdbool.h>
#include <stdio.h>
#include "unistd.h"
typedef struct {
pid_t pid;
FILE *in, *out;
} Process;
bool process_create(Process *p, const char *cmd);
bool process_destroy(Process *p);
bool process_readln(const Process *p, char *buf, size_t n);
bool process_writeln(const Process *p, char *buf);
process.c
Code: Select all
#include "process.h"
#include <sys/types.h>
#include <signal.h>
bool process_create(Process *p, const char *cmd)
{
int readpipe[2], writepipe[2];
#define PARENT_READ readpipe[0]
#define CHILD_WRITE readpipe[1]
#define CHILD_READ writepipe[0]
#define PARENT_WRITE writepipe[1]
if (pipe(readpipe) < 0 || pipe(writepipe) < 0)
return false;
p->pid = fork();
if (p->pid == 0) {
// in the child process
close(PARENT_WRITE);
close(PARENT_READ);
if (dup2(CHILD_READ, STDIN_FILENO) == -1)
return false;
close(CHILD_READ);
if (dup2(CHILD_WRITE, STDOUT_FILENO) == -1)
return false;
close(CHILD_WRITE);
if (execlp(cmd, cmd, NULL) == -1)
return false;
}
else if (p->pid > 0) {
// in the parent process
close(CHILD_READ);
close(CHILD_WRITE);
if (!(p->in = fdopen(PARENT_READ, "r")))
return false;
if (!(p->out = fdopen(PARENT_WRITE, "w")))
return false;
setvbuf(p->in, NULL, _IONBF, 0);
setvbuf(p->out, NULL, _IONBF, 0);
} else
// fork failed
return false;
return true;
}
bool process_destroy(Process *p)
{
// close the parent side of the pipes
fclose(p->in);
fclose(p->out);
// kill the child process
return kill(p->pid, SIGKILL) >= 0;
}
bool process_readln(const Process *p, char *buf, size_t n)
{
return fgets(buf, n, p->in) >= 0;
}
bool process_writeln(const Process *p, char *buf)
{
return fputs(buf, p->out) >= 0;
}
Now we can spawn and kill processes and talk to them in a high level manner:
Code: Select all
#include <process.h>
#include <string.h>
int main(int argc, char **argv)
{
Process p;
process_create(&p, "/home/lucas/Chess/DC/DC");
char buf[0x100];
process_writeln(&p, "uci\n");
while (true) {
process_readln(&p, buf, sizeof(buf));
printf(buf);
if (!strcmp(buf, "uciok\n"))
break;
}
process_destroy(&p);
}
And the output is just as expected:
Code: Select all
id name DiscoCheck 3.7.1
id author Lucas Braesch
option name Hash type spin default 32 min 1 max 8192
option name Verbose type check default true
option name AutoClearHash type check default false
option name TimeBuffer type spin default 100 min 0 max 5000
uciok
PS: functions returning a bool return false on error. I don't think any calling function of the process machinery needs to have more info on why the pipes could not be created, the process couldn't be forked etc. they just need to know if all is good or not.