Hi,
I keep wasting my time reimplementing the same stuff over and over
again.
So I decided to work on this topic now.
What do you think about this design?
interface:
" using a dict so that you can keep kind of parsing state as well.
let callback_dict = {}
fun callback_dict.receive_chars(line, pipe_end)
echoe "process sent line". a:pipe_end
endfun
fun callback_dict.prog_exited(status)
echo "prog exited with".a:status
endfun
let process = create_interactive_process(['prog','arg1','arg2'],
callback_dict)
or
let [pipe_end1, pipe_end2] = create_pipe()
" pass one threadsafe communication line end to a scripting language:
exec 'py communication_channel = '.string(pipe_end2)
exec 'ruby communication_channel = '.string(pipe_end2)
exec 'perl communication_channel = '.string(pipe_end2)
A pipe_end should be a new Vim type. Internally it has these settings:
int uniq_id # an id which can be passed to Python, Ruby, Perl backends
int in # fd (from man 2 pipe)
int out # fd (from man 2 pipe)
buffering options
Now you can run a Python or Ruby thread in background and feed commands
into Vim using a pipe. You can also run any debugger such as gdb, php,
ruby, python debugger without client server hacks.
Now I wrote this code to to test the piping stuff only:
What does it do?
- it forks and runs exec to start a sh script which echoes received
chars to both stderr and stdout. The sh script is gdb or such later.
Before running exec stdout and stdin is redirected so that we can control
it.
- another process is started to read from the created pipe to verify
everything works as expected.
Actually I do no longer need this fork.
I used it to test the piping code first
- I know that I still have to set the close on exec flag for those
pipes?
There is one issue: exec might fail eg because the application doesn't
exist. However at that point in time the fork was done.
Which is the best way to tell the main thread that something has gone
wrong?
Do I have to create yet another pipe to pass this piece of information?
This implementation will work on Linux. Do you know whether it works on
Windows equally well?
Marc Weber
#include <sys/wait.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
typedef int PIPE_END[2];
// TODO close on exec
// fork-exec implementation
// returns -1 if fork fails
// 0 on success ( TODOs !! )
// TODO redirect stderr as well?
int fork_interactive_process(pid_t * ppid, PIPE_END * pipe_r_w, char
*command, char *argv[])
{
pid_t pid;
pid = fork();
switch (pid) {
case -1:
return -1;
case 0: /* child */
fprintf(stderr,"fork-exec-child: started\n");
// TODO error message should be passed to Vim! (echoe !)
// use pipe as stdin:
if (dup2((*pipe_r_w)[0], STDIN_FILENO) == -1){
perror("error duplicating stdin\n");
exit(1);
}
fprintf(stderr,"fork-exec-child: stdin duped\n");
// use pipe as stdout:
if (dup2((*pipe_r_w)[1], STDOUT_FILENO) == -1){
perror("error duplicating stdout\n");
exit(1);
}
fprintf(stderr, "fork-exec-child: stdout duped\n");
fprintf(stderr, "fork-exec-child: launching cat\n");
// run application using *p for convinince (?)
if (-1 == execvp(command, argv)){
// TODO: print errno
fprintf(stderr,"failed running command %s\n", command);
exit(1);
}
}
if (pid)
*ppid = pid;
// parent
fprintf(stderr,"parent is continuing\n");
return 0;
}
void my_write(int fd, void *buf, size_t count){
if (!write(fd, buf, count)){
fprintf(stderr, "failed writing bytes!\n");
exit(1);
}
}
void my_read(int fd, void *buf, size_t count){
int readB = 0;
while (!readB){
readB = read(fd, buf, count);
if (!readB){
fprintf(stderr,"not received, sleeping \n");
sleep(1);
}
}
if (readB <= 0)
fprintf(stderr,"read error: %d\n", readB);
}
char nr2char(int i){
return 'A' + (i % 20);
}
int main(int argc, char *argv[])
{
// Have to use two pipes because depending on uniderectional pipes is
// not portable according to man 2 pipe
//
PIPE_END pipe_in, pipe_out; // as ssen from to be executed process
PIPE_END pipe_r_w;
unsigned int i;
char* catargs[4];
char * testcommand = "/bin/sh";
unsigned char inBuf, outBuf;
if (pipe(pipe_in) == -1) {
perror("pipe in");
exit(EXIT_FAILURE);
}
if (pipe(pipe_out) == -1) {
perror("pipe out");
exit(EXIT_FAILURE);
}
//close(pipe_in[1]); /* Close unused my_write end */
//close(pipe_out[1]);
pipe_r_w[1] = pipe_out[1];
pipe_r_w[0] = pipe_in[0];
pid_t subprocess;
catargs[0] = "sh";
catargs[1] = "-c";
catargs[2] = "while read -n1 c; do echo \"got: $c\" 1>&2; echo -n \"$c\";
done";
catargs[3] = NULL;
if (0 != fork_interactive_process(&subprocess, &pipe_r_w, testcommand,
&catargs)){
perror("failed running fork_interactive_process");
exit(0);
}
pid_t cpid = fork();
if (cpid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
#define MAX 10
#define WRITE_CHAR_MSG(msg, c) \
fprintf(stderr, msg); \
write(STDERR_FILENO, &c, 1); \
fprintf(stderr, "\n");
if (cpid == 0) { /* Child reads from pipe */
for (i = 0; i < MAX; i++) {
char c = nr2char(i);
WRITE_CHAR_MSG("read-process: reading char: ", c)
my_read(pipe_out[0], &inBuf, 1);
// sollten gleich sei
WRITE_CHAR_MSG("read-process: read: ", inBuf)
}
exit(0);
//_exit(EXIT_SUCCESS);
}
for (i = 0; i < MAX; i++) {
char c = nr2char(i);
WRITE_CHAR_MSG("writing char: ", c)
my_write(pipe_in[1], &c, 1);
}
outBuf = '\n';
my_write(pipe_in[1], &outBuf, 1);
close(pipe_in[1]);
close(pipe_in[2]);
int exitstatus;
fprintf(stderr,"waiting for child\n");
waitpid(cpid, &exitstatus, 0);
fprintf(stderr,"child terminated ? %d\n", exitstatus);
exit(0);
for (i = 0; i < 10; i++) {
fprintf(stderr,"%d\n", i);
outBuf = (unsigned char) i;
my_write(pipe_in[1], &outBuf, 1);
my_read(pipe_out[0], &inBuf, 1);
// sollten gleich sei
fprintf(stderr,"%d %d \n", inBuf, outBuf);
}
// close remaining opened pipe ends
close(pipe_in[0]);
close(pipe_out[1]);
exit(0);
}
--
You received this message from the "vim_dev" maillist.
For more information, visit http://www.vim.org/maillist.php