0

Basically I want to achieve something like this but in C:
output=$(echo -n 1234 | md5sum)
1234 is the input and md5sum is the whatever the program I want to run.

I've checked some SO questions but none seems to be exactly what I'm looking for or they don't provide a complete code example that I can test.

I've tried popen and was able to read from or write to another program but obviously it's not bidirectional.

Some posts seem to suggest pipe with dup2 or some other functions but they didn't explain how exactly to make them work.

uNiverselEgacy
  • 337
  • 3
  • 14
  • 2
    SO is not the only resource for getting working code. Sometimes people are just *writing* it themselves. Also function documentation can be found elsewhere. – Eugene Sh. Nov 30 '17 at 17:21
  • 1
    See [this](https://stackoverflow.com/a/20582916/841108) answer. It is relevant. Look also into other [syscalls(2)](http://man7.org/linux/man-pages/man2/syscalls.2.html). Study [pipe(7)](http://man7.org/linux/man-pages/man7/pipe.7.html)-s – Basile Starynkevitch Nov 30 '17 at 17:22
  • 3
    @EugeneSh. I'm not necessarily looking for working code but at least some ideas how the structure of the program looks like or basically how it's supposed to work. How are you supposed to write code if you have absolutely no idea what the flow of the program looks like? – uNiverselEgacy Nov 30 '17 at 17:33

2 Answers2

1

popen() could be used

The main question says it wants to achieve the equivalent of the shell command:

output=$(echo -n 1234 | md5sum)

and that can be written in C with popen():

#include <stdio.h>

int main(void)
{
    char output[64];
    FILE *fp = popen("echo -n 1234 | md5sum", "r");
    if (fp == 0)
    {
        fprintf(stderr, "failed to create pipeline\n");
        return 1;
    }
    size_t nbytes = fread(output, sizeof(char), sizeof(output), fp);
    pclose(fp);
    if (nbytes > 0)
    {
        printf("output=[%.*s]\n", (int)nbytes, output);
        return 0;
    }
    else
    {
        printf("no output available!\n");
        return 1;
    }
}

The output from this is:

output=[4c35fa2227e11ba8c892cbbb5d46417c  -
]

Note that this equivalent to the output from the first of the following commands:

$ printf "%s %s\n" -n 1234 | md5sum
4c35fa2227e11ba8c892cbbb5d46417c  -
$ printf "%s" 1234 | md5sum
81dc9bdb52d04dc20036dbd8313ed055  -
$

(Using echo -n isn't entirely portable. There's room to think you were expecting the output from the second command.)

If you want two-way communication

However, the headline question in the title suggests you want your program to write data to the md5sum command and read the response hash back again. That is certainly doable, but you have to work harder, and popen() is no longer appropriate. I'm too lazy not to use my preferred error reporting functions, which are available on GitHub in my SOQ — Stack Overflow Questions repository as files stderr.c and stderr.h in the LibSOQ directory. It makes error checking so much simpler when you simply write a test and make a function call if the test indicates failure.

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include "stderr.h"

static void fd_copy(int ifd, int ofd)
{
    char buffer[1024];
    ssize_t rbytes;
    while ((rbytes = read(ifd, buffer, sizeof(buffer))) > 0)
    {
        ssize_t wbytes;
        if ((wbytes = write(ofd,buffer, (size_t)rbytes)) != rbytes)
            err_syserr("short write %zd bytes instead of %zd expected: ", wbytes, rbytes);
    }
}

static void be_childish(int *to_child, int *to_parent)
{
    if (dup2(to_child[0], STDIN_FILENO) != STDIN_FILENO)
        err_syserr("failed to duplicate pipe to standard input: ");
    if (dup2(to_parent[1], STDOUT_FILENO) != STDOUT_FILENO)
        err_syserr("failed to duplicate pipe to standard output: ");
    close(to_child[1]);
    close(to_child[1]);
    close(to_parent[0]);
    close(to_parent[0]);
    char *cmd[] = { "md5sum", 0 };
    execvp(cmd[0], cmd);
    err_syserr("failed to execute command %s: ", cmd[0]);
    /*NOTREACHED*/
}

static void be_parental(const char *fname, int *to_child, int *to_parent)
{
    close(to_child[0]);
    close(to_parent[1]);
    int fd = open(fname, O_RDONLY);
    if (fd < 0) 
        err_syserr("failed to open file '%s' for reading: ", fname);
    fd_copy(fd, to_child[1]);
    close(fd);
    close(to_child[1]);

    char buffer[128];
    ssize_t rbytes = read(to_parent[0], buffer, sizeof(buffer));
    close(to_parent[0]);
    if (rbytes <= 0)
        err_syserr("read error (or unexpected EOF) from hash process: ");
    buffer[strspn(buffer, "0123456789abcdefABCDEF")] = '\0';
    printf("%s: MD5 %s\n", fname, buffer);
}

static void md5hash(const char *fname)
{
    int to_child[2];
    int to_parent[2];
    if (pipe(to_child) != 0 || pipe(to_parent) != 0)
        err_syserr("failed to create 2 pipes: ");
    int pid = fork();
    if (pid < 0)
        err_syserr("failed to fork: ");
    if (pid == 0)
        be_childish(to_child, to_parent);
    else
        be_parental(fname, to_child, to_parent);
}

int main(int argc, char **argv)
{
    err_setarg0(argv[0]);
    if (argc <= 1)
        err_usage("file [...]");

    for (int i = 1; i < argc; i++)
        md5hash(argv[i]);
    return 0;
}

Example output (program was pop73):

$ pop73 mm*
mm19.c: MD5 1d2207adec878f8f10d12e1ffb8bcc4b
mm23.c: MD5 c3948c5a80107fdbfac9ad755e7e6470
mm53.c: MD5 a0a24610400d900eb18408519678481e
mm59.c: MD5 1a5b1807c331dd1b5b6ce5f6ffed7c59
$ md5sum mm*
1d2207adec878f8f10d12e1ffb8bcc4b  mm19.c
c3948c5a80107fdbfac9ad755e7e6470  mm23.c
a0a24610400d900eb18408519678481e  mm53.c
1a5b1807c331dd1b5b6ce5f6ffed7c59  mm59.c
$

This confirms that the files were correctly copied to the child, and the output MD5 hashes match.

I like the be_childish() and be_parental() function names for programs involving child and parent processes. Using functions for discrete jobs makes the code simpler to understand and maintain in the long run. Note, too, how careful the code is to close file descriptors.

Rule of thumb: If you duplicate one end of a pipe to standard input or standard output (or standard error), you should close both ends of the pipe before continuing.

The be_childish() function exemplifies that rule of thumb. Before continuing to execute the command, it closes all 4 descriptors of the 2 pipes it is passed because it has copied one descriptor from each pipe to one of the standard streams.

Note the corollary: if you don't duplicate a file descriptor to one of the standard channels, you don't usually close both file descriptors of the pipe immediately. You do normally close one end of the pipe — the end that the current process won't use. But the other is left open until you've finished processing data, whatever that means. The be_parental() function exemplifies the corollary.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
0

Give some test case for your input & output, what you want to achieve with echo -n only n digits or anything else ? By some assumption I tried below.

xyz@xyz-PC:~/s_flow$ cat m5sum.c 
#include<stdio.h>
int main()
{
    int n;
    printf("enter n :\n");
    scanf("%d",&n);
    printf("n = %d \n",n);
    return 0;
}
xyz@xyz-PC:~/s_flow$ echo  12345 | ./m5sum
enter n :
n = 12345

You want output like above How we are getting using echo ?

echo  12345 | ./m5sum 
   |             |        => process1 output will be input for process 2 
process1       process2 

first thing you need one pipe for writing & reading, condition is that process1 shouldn't display output on screen(stdout) so for this you should close(1) & this should be input for process2 i.e process 2 shouldn't read from user(stdin) so do close(0).

Here is the my suggestion

int main()
{
        int p[2];
        pipe(p);
        if(fork()==0)//child
        {
                close(0);//closing std in
                dup(p[0]);//duplicating read end of pipe
                close(p[1]);//closing write end of pipe
                execlp("process1_cmd","cmd_option",NULL);
        }
        else
        {
                close(1);//close stdout
                dup(p[1]);// duplicating write end of pipe
                close(p[0]);//closing read end of pipe  
                execlp("process_2","process_2",NULL);
        }
}

process_1 & process_2 can be any predefined/user-defined input commands. I hope it will helps to accomplish your goal.

Achal
  • 11,821
  • 2
  • 15
  • 37
  • Note that you should really close both ends of the pipe after using `dup()` or `dup2()` to duplicate a pipe to standard input or standard output — see the 'Rule of Thumb' in my [answer](https://stackoverflow.com/a/47930248/15168). You're missing a `close()` before each `execlp()`. In some circumstances, not closing pipes properly can mean that one of the processes does not detect EOF properly because the write end of the pipe that they're reading from is still open — but no-one is ever going to write to it, and often they're reading the read end while they have the write end open, so no EOF. – Jonathan Leffler Dec 21 '17 at 17:59