1

Recently I started to write my first program in C to automate a process on a Linux device. The script is supposed to run the program ppd terminal on the terminal, read the first 2 lines and assess whether there is an error and finally execute the command startmining within ppd terminal (and to execute startmining every 5 minutes if there is no error found). However, I didn't know exactly how to set up a 2-way pipe to read and write to the running program external program (previously started executed on the following script with popen()).

I asked online for a solution to this problem and someone sent me a script (from this post) that they build that does exactly what I needed. However, I am new to writing C and during the past 2 days, I have attempted to implement this idea in my script without success.

Could someone help me understand how to implement the following script:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>

typedef struct pipe_rw
{
   pid_t cpid;
   int pipe_r[2];
   int pipe_w[2];
} RWPIPE;

static char *get_user_input(void)
{
   char buf[128];
   char *input;
   char ch;
   size_t len = 0;

   while((ch = fgetc(stdin)) != '\n' && ch != EOF && len < sizeof(buf) - 2)
      buf[len++] = ch;
   buf[len++] = '\n';
   buf[len] = '\0';

   input = malloc(sizeof(char) * (len + 1));
   strncpy(input, buf, (len + 1));
   printf("Got: [%s]\n", input);
   return input;
}

static int pclose_rw(RWPIPE *rwp)
{
   int status, ret = 0;

   if (rwp)
   {
      if (rwp->cpid > 0)
      {
         kill(rwp->cpid, SIGTERM);

         do {
            ret = waitpid(rwp->cpid, &status, WUNTRACED|WCONTINUED);
         } while (!WIFEXITED(status) && !WIFSIGNALED(status));
      }

      close(rwp->pipe_r[0]);
      close(rwp->pipe_w[2]);
      free(rwp);
   }

   return ret;
}

static RWPIPE *popen_rw(const char *command)
{
   RWPIPE *rwp = (RWPIPE *)malloc(sizeof(*rwp));
   if (rwp == NULL)
      return NULL;

   memset(rwp, 0x00, sizeof(*rwp));
   if (pipe(rwp->pipe_r) != 0 || pipe(rwp->pipe_w) != 0)
   {
      free(rwp);
      return NULL;
   }

   rwp->cpid = fork();
   if (rwp->cpid == -1)
   {
      free(rwp);
      return NULL;
   }

   if (rwp->cpid == 0)
   {
      dup2(rwp->pipe_w[0], STDIN_FILENO);
      dup2(rwp->pipe_r[2], STDOUT_FILENO);

      close(rwp->pipe_r[0]);
      close(rwp->pipe_r[2]);
      close(rwp->pipe_w[0]);
      close(rwp->pipe_w[2]);

      execl(command, command, NULL);
      fprintf(stderr, "Error: fail to exec command '%s'.\n", command);
      exit (1);
   }
   else
   {
      close(rwp->pipe_r[2]);
      close(rwp->pipe_w[0]);
   }

   return rwp;
}

static ssize_t read_p(RWPIPE *rwp, void *buf, size_t count)
{
   return read(rwp->pipe_r[0], buf, count);
}

static ssize_t write_p(RWPIPE *rwp, const void *buf, size_t count)
{
   return write(rwp->pipe_w[2], buf, count);
}

int main(void)
{
   char rbuf[BUFSIZ];
   int ret, len;
   char *string;

   signal(SIGPIPE, SIG_IGN);

   RWPIPE *rwp = popen_rw("./read_write");
   if (rwp == NULL)
   {
      printf("Error: fail to open command ..\n");
      return EXIT_FAILURE;
   }

   while (1)
   {
      memset(rbuf, 0x00, sizeof(rbuf));
      if (read_p(rwp, rbuf, sizeof(rbuf)) <= 0)
      {
         printf("No more input..\n");
         break;
      }
      printf("From child: [%s]\n", rbuf);

      string = get_user_input();
      len = strlen(string);
      printf("Length %d: [%s]\n", len, string);
      ret = write_p(rwp, string, len);
      if (ret != len)
      {
         fprintf(stderr, "Write %d bytes (expected %d) ..\n", ret, len);
         break;
      }
      printf("end cycle\n");
   }
   printf("End of loop\n");
   pclose_rw(rwp);

   return EXIT_SUCCESS;
}

into this script I have been developing:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>

#define MAX_LINE 350

//run the program inside rsnode directory
int main() {

    FILE *output;
    
    char buffer[MAX_LINE];
    int buffer_search;
    int read_line = 2;
    char error_message[] = "[ERROR]";
    
    output = popen("ppd terminal","r");
    sleep(5);

    bool keep_reading = true; 
    int current_line = 1;
    do {
        fgets(buffer, MAX_LINE, output);
        buffer_search = strncmp(error_message, buffer, 7);

        if (current_line > 2) {
            keep_reading = false;
        }
        else if (buffer_search == 0) {
            system("gnome-terminal -- ./smt1"); //this comand is to restart this same program on a new terminal
            system("ppd start");
            sleep(5);
                
            while (1);
        }
        current_line++;

    } while (keep_reading == true);
    
    sleep(30);

    system("ppd terminal && startmining");
    sleep(300);
    
    FILE *output2;
    char buffer2[MAX_LINE];
    char error_message2[] = "dial unix /home";
    int buffer_search2;
    int timer = 1;
    bool no_error = true;

    do {
        output2 = popen("startmining", "r");
        while (timer < 300) {
            fgets(buffer2, MAX_LINE, output2);
            buffer_search2 = strncmp(error_message2, buffer2, 15);
            if (buffer_search2 == 0) {
                system("exit");
                system("gnome-terminal -- ./smt1"); 
                system("exit");
                no_error = false;
                pclose(output2);
            }
            sleep (1);
            timer++;
        }
    } while (no_error == true);

    return 0;
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • Note that [`fgetc`](https://en.cppreference.com/w/c/io/fgetc) returns an **`int`**, which is actually very crucial for the comparison against the `int` value `EOF`. – Some programmer dude Mar 21 '22 at 11:38
  • Also note that the `system` function spawns a new shell, and runs the commands as if they would run in any other shell session.Which means that the function will not return until the command you execute have exited. – Some programmer dude Mar 21 '22 at 11:40
  • I am aware that system doesn't work form me because It waits for the system() command to finish wich in my case never does until there is an Error (which is not the intended). On the other hand, I don't really understand what you mean by the comparisson against the int value EOF but the codes gets to ```startmining``` , I get the error ```Error reading from pipe: EOF```or something similar which I do not understand what it means – Alvaro Ortiz de Lanzagorta Mar 21 '22 at 11:47
  • Do you know what do I need to change in my code to implement the idea of the first code, because I am completely lost :/. My main doubt Is how I can both read and write from another porgram at the same time – Alvaro Ortiz de Lanzagorta Mar 21 '22 at 11:51
  • The first program implements (badly I must say) code to run generic commands in the background, and to read their output, and to write to their input. You could use the functions it provided, but not without modifications (the `popen_rw` function doesn't support commands with arguments, for example). If you do (and solve the command argument problem) you could do something like `popen_rw("ppd", "terminal")` to invoke the `ppd` command with the `terminal` argument. Then use `read_p` to read the output of the command, and `write_p` to write input to the command. – Some programmer dude Mar 21 '22 at 11:56
  • @Someprogrammerdude so I understood correctly, with the command ```popen_rw()```I can both read and write from the program while it is still running. This is main main issue with this kind of commands, either they read from it once the sub process is finished or it cannot write while the subprocess is running, which after testing I believe is the problem with popen(). Also what do you mean by ```popen_rw```does not support command with arguments? – Alvaro Ortiz de Lanzagorta Mar 21 '22 at 12:06

0 Answers0