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

Raspunde prin e-mail lui