다른 프로그램의 표준 입력과 출력을 다 가로채는 방법?

raymundo의 이미지

안녕하세요,

A라는 프로그램이 있는데, 이것은 아주 간단하게 표준출력 한 번 표준입력 한 번 입출력을 하고 끝납니다.

printf("%d", 100);
scanf("%d", &num);

이 프로그램은 건드리지 않고, 이 프로그램의 출력을 읽고, 그 출력에 따라 적절한 입력값을 결정해서 넣어주는 프로그램을 만들고 싶습니다.
즉 사람이 눈으로 보고 키보드로 입력하는 걸 대신하는 건데...

제가 시도한 방법은

1) 셀에서 mkfifo 를 써서 네임드 파이프 두 개를 만들고

2) 파이프 하나에서 읽고, 다른 파이프로 쓰는 프로그램 B를 만들고:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
 
#define FIFO_READ  "./fifo_read"
#define FIFO_WRITE "./fifo_write"
#define BUF_SIZE 100
 
int main() {
    int fd_r, fd_w;
    FILE *p_r, *p_w;
    char buf[BUF_SIZE];
 
    // 두 개의 파이프를 열고
    fd_r = open( FIFO_READ, O_RDONLY );
    fd_w = open( FIFO_WRITE, O_WRONLY );
 
    // fd_r 파이프에서 읽고
    memset( buf, 0, BUF_SIZE );
    read( fd_r, buf, BUF_SIZE );
    printf("%s\n", buf);
 
 
    // fd_w 파이프로는 쓰고
    strcpy(buf, "123");
    write( fd_w, buf, strlen(buf) );
}

3) 이제 A 프로그램과 B 프로그램을 파이프를 매개로 연결하기 위해서 두 창에서 각각

$ ./B                             # 한쪽에서 B를 띄우고

$ ./A > fifo_read < fifo_write    # 표준 입출력 리다이렉션 (파이프 이름을 B 입장에서 지었더니 여긴 좀 이상하군요)

이렇게 막상 해보니...

A가 종료되기 전에는 파이프로 전송이 이뤄지지 않더군요. 그래서 B가 read에서 하염없이 멈춥니다.

그런데 A가 종료되려면 scanf를 통과해야 하는데 거기에 fifo_write 파이프를 넣어주려면 B가 진행을 해야 하니...
제대로 교착 상태가 되어 버리는군요.

A프로그램을 손댈 수 없는 상황에서 어떻게 해결할 수 있을까요. 조언을 부탁드리겠습니다.

익명 사용자의 이미지

*중요 : A 프로그램에는 fflush(stdout) 이 선언되어 있어야 합니다.

A 프로그램
#include
int main()
{
int i;
printf("%d\n",100);
fflush(stdout);
scanf("%d",&i);
printf("i: %d\n",i);
fflush(stdout);
return 0;
}

B프로그램
#include
#define BUF_SIZE 1024
int main()
{
FILE *fp;
char buf[BUF_SIZE];
int i;
fp = popen("./a.out","w");
if(fp == NULL){
perror("popen() fail\n");
return -1;
}
fgets(buf,BUF_SIZE,fp);
sscanf(buf,"%d",&i);
fprintf(fp,"%d",50);
fgets(buf,BUF_SIZE,fp);
pclose(fp);
return 0;
}

A프로그램을 미리(따로) 실행시킬 필요없이 B를 실행시키면 내부적으로 A를 실행합니다

raymundo의 이미지

답변 감사합니다. 그런데 적어주신 코드로는 잘 안 되는군요 ^^;

질문글에 제가 시도했던 방법을 충분히 적지 못했는데, popen을 쓰지 못했던 이유가,
입력과 출력 둘 다를 B가 처리하고자 하는건데 popen은 read 아니면 write 밖에 안 되니까요.
저 코드에서는 (혹시나 해서 다시 저 코드도 시도해봤습니다)
A의 출력을 B가 가로채지 못해서 fgets가 실패합니다. 자연히 sscanf도 실패하고 i 값도 갱신이 안되고요.
A의 출력은 그냥 스크린에 뜨지요.

popen의 모드를 "r"로 하면 출력은 잘 가로채지만 이번에는 fprintf 쪽이 실패하고, A의 scanf 는 키보드로 넣어줘야 합니다.

popen을 입력용 출력용 따로 해서 두 번 호출하는 것도 해봤지만, 예상할 수 있다시피 A 프로세스가 두 개 실행됩니다.

물론 popen을 써서 해결하는 방법이 있는데 제가 찾지 못한 걸 수도 있습니다만, 그래서 그 다음 시도가 본문에 적었던 네임드 파이프였는데... 본문에 적은 것처럼 막혔던 거죠.

하지만 지금 답글 덕분에 fflush(stdout)가 A에 있으면 본문에 적었던 코드로 성공하는 걸 확인했습니다. 그래서 일단 한 단계는 진전을 했네요 :-)

제가 이 질문을 다른 곳에서 보고는 직접 해보다가 막혀서 질문드리는 건데, 원질문자분의 상황이 어떤지 모르겠지만 A를 수정할 수는 없다고 하더라고요.
그래서 저도 A를 건드리지 않고 해결해보고 싶었거든요.

적어도 그 원질문자가 가지고 있는 A코드가 혹시라도 fflush를 사용하고 있다면 해결이 되겠네요.

셀에서 A를 실행할 때 출력의 버퍼링을 못하게 할 수 있다면 fflush가 없어도 되지 않을까 싶기도 한데 말이죠.

좋은 하루 되세요!

익명 사용자의 이미지

원프로그램을 수정할 수 없다면

setbuf 함수로 해결할 수 있습니다.

#include
#define BUF_SIZE 1024
int main()
{
FILE *fp;
char buf[BUF_SIZE];
int i;
setbuf(stdout,NULL);
fp = popen("./a.out","w");
if(fp == NULL){
perror("popen() fail\n");
return -1;
}
fgets(buf,BUF_SIZE,fp);
sscanf(buf,"%d",&i);
fprintf(fp,"%d",50);
fgets(buf,BUF_SIZE,fp);
pclose(fp);
return 0;
}

익명 사용자의 이미지

간단하게 목적 만을 이야기하자면, 매크로프로그램 만들고 싶으시단 거죠?

expect가 제격일 겁니다.

tcl/tk 에 익숙하지 않더라도 아마 간단한 정도는 쉽게 구현할 수 있을 겁니다.

raymundo의 이미지

감사합니다. 물론 expect가 딱 좋은 해결책인데, 원질문자가 C로 하는 법을 물었던 거라서 저도 C로 해결해보고 싶은 거라서요. :-)

좋은 하루 되세요!

ymir의 이미지

$ cat calc.c
#include <stdio.h>
 
int main(void)
{
        int num;
 
        scanf("%d", &num);
        printf("%d + %d = %d\n", num, num, num+num);
 
        return 0;
}
$ gcc -g -W -Wall -o calc calc.c

$ cat pp.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
int main(void)
{
    int pipe_r[2], pipe_w[2];
    pid_t cpid;
    char buf;
 
    if (pipe(pipe_r) != 0 || pipe(pipe_w) != 0)
    {
        perror("pipe");
        return EXIT_FAILURE;
    }
 
    cpid = fork();
    if (cpid == -1)
    {
        perror("fork");
        return EXIT_FAILURE;
    }
 
    if (cpid == 0)
    {
        dup2(pipe_w[0], STDIN_FILENO);
        dup2(pipe_r[1], STDOUT_FILENO);
 
        close(pipe_r[0]);
        close(pipe_r[1]);
        close(pipe_w[0]);
        close(pipe_w[1]);
 
        execl("calc", "calc", NULL);
    }
    else
    {
        close(pipe_r[1]);
        close(pipe_w[0]);
 
        write (pipe_w[1], "100\n", 4);
        while (read(pipe_r[0], &buf, 1) > 0)
            write(STDOUT_FILENO, &buf, 1);
 
        close(pipe_r[0]);
        close(pipe_w[1]);
    }
 
    return EXIT_SUCCESS;
}
 
$ gcc -g -W -Wall pp.c
$ ./a.out
100 + 100 = 200

- child 와 parent 를 바꿔 쓴거 바로잡음.. ;;

되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』

raymundo의 이미지

감사합니다.

그런데 해보니까 문제가요...

A 쪽 동작이 scanf - printf 순이 아니라 그 반대인 경우이고,
B 에서 이걸 그냥 답글처럼 write, read 순으로 하면 잘돌아가는데 (A와 B가 둘 다 출력-입력 순으로 동작하는 이상한 모양새이지만 그래도 제대로 돌아가더군요)
B 에서는 반드시 read - write 순으로 해야 합니다.
(질문글에서는 자세히 안 썼는데, A가 숫자 두 개를 던져주면 B가 그걸 읽고 덧셈을 한 후 결과를 A에게 입력해주어 채점하려는 거라서)

그래서 B를 read - write 순으로 고쳤더니, 여전히 유사한 문제가 나옵니다.

1) A쪽에서 printf 후에 fflush 를 하지 않으면 아예 B가 전혀 읽지를 못 함

2) A쪽에서 printf 후에 fflush 를 하면, B가 읽고 화면에 출력까지는 하는데, 더 읽을 게 없을 때 read 가 블로킹되어 버리는지 while 문 밖으로 나가질 못함

결국 중간에 더 진행이 안 된 상태로 머무르게 되네요.

이리저리 수정해보면서 해 봤는데,

1) A는 역시 printf()직후에 fflush 가 있어야 함 - 없으면 아예 B가 읽지 못함
2) B는 read 를 while 을 써서 반복하지 않고, 딱 한 번만 긴 버퍼에 읽게 함 - 그래야 다음 진행이 가능

그러면 원하는 대로 동작하더군요. 제가 본문에 쓴 방법은 A와 B를 각각 실행해야 했는데, 이건 B만 실행해도 되니까 한결 편해지긴 했습니다. :-) 다시 한번 감사합니다.

(저 read 블로킹을 해결해보려고 non blocking 옵션도 넣으려고 해보고 하려는데 영 잘 안 되네요)

좋은 하루 되세요!

ymir의 이미지

A 에 데이터를 던져주고, 결과를 받는 걸로 착각했네요.
순서를 바꿔보니, A 가 write 를 호출하지 않고, 바로 read 에서 block 되어 버리던데..
말씀하신대로 printf 가 buffering 되어버리네요. (\n 이 있는데도 buffering 되는군요..;;)

결국 A 에서 fflush() 를 쓰던지, setbuf 로 unbuffering mode 로 바꿔줘야 하네요.
A 를 수정하지 않고, B 단독으로 해볼만한 거는 못 찾겠네요.

$ cat calc.c
#include <stdio.h>
#include <unistd.h>
 
static int fdump(int n)
{
        FILE *fp = fopen("result.txt", "a");
        if (fp)
        {
                char buf[BUFSIZ];
                snprintf(buf, sizeof(buf), "%d\n", n);
                fputs(buf, fp);
                fclose(fp);
        }
 
        return fp?0:-1;
}
 
int main(void)
{
        int x, i=10;
        setbuf(stdout, NULL);
 
        while (--i)
        {
                printf("%d\n", i);
                scanf("%d", &x);
                fdump(x);
        }
 
        return 0;
}
 
$ gcc -g -W -Wall -o calc calc.c

$ cat popen_rw.c
#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;
 
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[1]);
                free(rwp);
        }
 
        return ret;
}
 
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[1], STDOUT_FILENO);
 
                close(rwp->pipe_r[0]);
                close(rwp->pipe_r[1]);
                close(rwp->pipe_w[0]);
                close(rwp->pipe_w[1]);
 
                execl(command, command, NULL);
                printf("Error: fail to exec command - %s ..\n", command);
                exit (1);
        }
        else
        {
                close(rwp->pipe_r[1]);
                close(rwp->pipe_w[0]);
        }
 
        return rwp;
}
 
ssize_t read_p(RWPIPE *rwp, void *buf, size_t count)
{
        return read(rwp->pipe_r[0], buf, count);
}
 
ssize_t write_p(RWPIPE *rwp, const void *buf, size_t count)
{
        return write(rwp->pipe_w[1], buf, count);
}
 
int main(void)
{
        char rbuf[BUFSIZ], wbuf[BUFSIZ];
        int ret, len, n = 0;
 
        RWPIPE *rwp = popen_rw("./calc");
        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)) < 1)
                {
                        printf("No more input..\n");
                        break;
                }
                n = atoi(rbuf);
 
                len = snprintf(wbuf, sizeof(wbuf), "%d\n", n * n);
                ret = write_p(rwp, wbuf, len);
                if (ret != len)
                {
                        printf("Write %d bytes (expected %d) ..\n", ret, len);
                        break;
                }
        }
        pclose_rw(rwp);
 
        return EXIT_SUCCESS;
}
$ gcc -g -W -Wall popen_rw.c
$ ls result.txt
ls: cannot access result.txt: No such file or directory
$ ./a.out
No more input..
$ cat result.txt
81
64
49
36
25
16
9
4
1

되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』

study의 이미지

원래 제가 하려던 목적은 덕분에 잘 해결이되었는데요,
우연히 popen() 소스코드를 봤는데, 위의 popen_rw()와 상당히 비슷하더라구요.

switch (pid = vfork()) {
   case -1:            /* error */
      (void) close(pdes[0]);
      (void) close(pdes[1]);
      return (NULL);
      /* NOTREACHED */
   case 0:             /* child */
      if (*type == 'r') {
         if (pdes[1] != fileno(stdout)) {
            (void) dup2(pdes[1], fileno(stdout));
            (void) close(pdes[1]);
         }
         (void) close(pdes[0]);
      } else {
         if (pdes[0] != fileno(stdin)) {
            (void) dup2(pdes[0], fileno(stdin));
            (void) close(pdes[0]);
         }
         (void) close(pdes[1]);
      }
      execl("/bin/sh", "sh", "-c", program, NULL);
      _exit(127);
      /* NOTREACHED */
}
/* parent; assume fdopen can't fail...  */
if (*type == 'r') {
   iop = fdopen(pdes[0], type);
   (void) close(pdes[1]);
} else {
   iop = fdopen(pdes[1], type);
   (void) close(pdes[0]);
}
pids[fileno(iop)] = pid;
return (iop);
<code>
 
그런데 여기서 fdopen()을 사용하는 이유가 궁금해졌습니다.
위의 popen_rw()에서는 fdopen()을 사용하지 않으셨었잖아요.
popen()에서 fdopen()을 사용하는 어떤 이유가 있는건지를 잘 몰라서요 질문드립니다.
 
일단 popen()처럼 만들어서 file pointer를 return하면 fgets()나 fputs()같은 함수를 이용할 
수 있게되는 것은 알겠는데요.
이렇게 하는 것과 popen_rw()처럼 pipe에 read/write를 하는 것과 어떤차이가 있는건지요?
 
갑자기 엉뚱한 질문을 하는 건아닌지 싶네요 ..

bushi의 이미지

[bushi@rose HYM]$ cat A.c
#include <stdio.h>
 
int main(int argc, char **argv)
{
	printf("100");
 
	int num;
	if (scanf("%d", &num) == 1)
		printf("num is %d\n", num);
 
	return 0;
}
[bushi@rose HYM]$ gcc -o A A.c
[bushi@rose HYM]$ ./wrap_cmd 
child sent 'num is 100
'
(thread died)
(parent died)
[bushi@rose HYM]$
댓글 첨부 파일: 
첨부파일 크기
Plain text icon wrap_cmd.c.txt4.8 KB
ymir의 이미지

혹시나 해서 expect 소스 들여다 보다가, 좀 지저분해서 은근히 짜증났었는데.. 깔끔하네요.
덕분에 ptmx 쓰는 법에 대해 잘 배웠습니다. 감사합니다.

되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』

raymundo의 이미지

오... 감사합니다.

원래의 A 코드에 맞게 좀 고쳐보고 있는데, (코드를 잘 이해를 못해서 여기가 입출력이다 싶은 부분만)
recv_from_child 가 상당히 많이 불리더군요. wrap_cmd 가 보내는 것도 다시 여기에서 읽히기도 하고.
buf 를 매번 출력시켜보면 정체를 알 수 없는 내용이 잔뜩 출력되던데 이게 뭔지 이해하는 데 시간이 좀 걸리겠어요.

하지만 원하는 대로 숫자 두개를 받고 더해서 더한 결과를 A로 보내어 결과를 다시 출력받는 것까지 제대로 되었습니다.
A에 fflush가 없어도 잘 되는군요.

다시 한 번 감사합니다!

좋은 하루 되세요!

study의 이미지

제가 해보고 있는 것은 아래와 같이 string을 받아서 다시 출력하는 것인데요.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
int main(void)
{
   char str[30];
 
   printf("Input string : ");
   fflush(stdout);
   scanf("%s", &str);
   fflush(stdout);
 
   printf("entered string is %s\n", str);
   return 0;
}

위에 보여주신 코드를 거의 대부분 사용해서 아래와 같이 만들어 봤습니다.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <ncurses.h>
 
typedef struct pipe_rw
{
   pid_t cpid;
   int pipe_r[2];
   int pipe_w[2];
} RWPIPE;
 
char *get_user_input(void)
{
   char buf[128];
   char *input;
   char ch;
   int n;
   int len = 0;
 
   memset(buf, 0x0, 128);
   while(1)
   {
      ch = getchar();
 
      if (ch == 0xa)
      {
         break;
      }
 
      buf[len] = ch;
      len++;
   }
 
   buf[len] = '\0';
   input = malloc(sizeof(char) * (len+1));
   strncpy(input, buf, (len+1));
 
   return input;
}
 
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[1]);
      free(rwp);
   }
 
   return ret;
}
 
 
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[1], STDOUT_FILENO);
 
      close(rwp->pipe_r[0]);
      close(rwp->pipe_r[1]);
      close(rwp->pipe_w[0]);
      close(rwp->pipe_w[1]);
 
      execl(command, command, NULL);
      printf("Error: fail to exec command - %s ..\n", command);
      exit (1);
   }
   else
   {
      close(rwp->pipe_r[1]);
      close(rwp->pipe_w[0]);
   }
 
   return rwp;
}
 
 
ssize_t read_p(RWPIPE *rwp, void *buf, size_t count)
{
   return read(rwp->pipe_r[0], buf, count);
}
 
ssize_t write_p(RWPIPE *rwp, const void *buf, size_t count)
{
   return write(rwp->pipe_w[1], buf, count);
}
 
int main(void)
{
   char rbuf[BUFSIZ], wbuf[BUFSIZ];
   int ret, len, n = 0;
   char *string;
 
   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)) < 1)
      {
         printf("No more input..\n");
         break;
      }
      printf("%s", rbuf);
 
      string = get_user_input();
      ret = write_p(rwp, string, len);
      printf("ret = %d\n", ret);
      if (ret != len)
      {
         printf("Write %d bytes (expected %d) ..\n", ret, len);
         break;
      }
   }
   pclose_rw(rwp);
 
   return EXIT_SUCCESS;
}

그런데 실행예를 보면 아래와 같이 입력을 받았지만 원래의 program으로 다시 write하는 곳에서 실패를
하는 것 같아요.

$ ./a.out
Input string : 12345
ret = 0
 
 
^C

혹시 시간되시면, 조언을 좀 부탁드리고 싶습니다.

ymir의 이미지

read_write 가 scanf() 로 입력을 받고 있으니, 입력되는 문자열에 '\n' 까지 포함되어야 합니다.
get_user_input() 에서 문자열의 마지막 부분에 '\n' 을 넣어주면 될 것 같고..

두 번째의 ret = write_p(rwp, string, len); 에서.. len 에 아무런 값도 들어 있지 않습니다.
호출하기 전에 len = strlen(string); 한 줄 넣어주면 될 것 같네요.

되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』

study의 이미지

말씀해주신대로 2줄을 수정했습니다.

read_write()의 마지막에 buf[len] = '\0';을 buf[len] = '\n'으로 수정했구요.
main()에서 write_p를 호출하기 전에
len = strlen(string)을 추가했습니다.

그런데 아직도 뭔가 문제가 있는 모양이에요 ..
실행예가 다음과 같습니다.

$ ./a.out
Input string : 12345
ret = 6

^C

ret값이 제대로 인 것을 보면 write가 된 것 같은데
read_write로 부터 entered string is %s 가 출력이 안되네요..
이건 왜 그럴까요?

study의 이미지

알려주신 2줄 수정을 해보니 잘되네요 ^^

수정한 내용은요 ~
일단 read_write를 아래와 같이 수정했구요

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
 
int main(void)
{
   char str[10];
   char cmd_buf[128];
   int i = 0;
 
   printf("Input string : ");
   fflush(stdout);
   fgets(str, 10, stdin);  /* string read using fgets includes LF */
// scanf("%s", &str);   /* string read using scanf does not include LF */
 
   fflush(stdout);
   printf("Entered string is %s\n", str);
 
   return 0;
}
<code>
 
get_user_input()은 아래와 같이 수정했습니다.
 
<code>
char *get_user_input(void)
{
   char buf[128];
   char *input;
   char ch;
   int n;
   int len = 0;
 
   memset(buf, 0x0, 128);
   while(1)
   {
      ch = fgetc(stdin);
 
      if (ch == 0xa)
      {
         break;
      }
 
      buf[len] = ch;
      len++;
   }
 
   buf[len] = '\n';
   input = malloc(sizeof(char) * (len+1));
   strncpy(input, buf, (len+1));
 
   return input;
}
<code>
 
그리고 main()은 아래와 같이 수정했습니다.
 
<code>
int main(void)
{
   char rbuf[BUFSIZ], wbuf[BUFSIZ];
   char *string;
   int ret, len, n = 0;
   char ch = 0x0;
 
   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)) < 1)
      {
         printf("No more input..\n");
         break;
      }
      printf("%s", rbuf);
 
      string = get_user_input();
      len = strlen(string);
      ret = write_p(rwp, string, len);
      if (ret != len)
      {
         printf("Write %d bytes (expected %d) ..\n", ret, len);
         break;
      }
   }
   pclose_rw(rwp);
 
   return EXIT_SUCCESS;
}
<code>
study의 이미지

원래의 예제는 이제 잘되는데요,
제가만들 read_write 말고 리눅스의 /bin/passwd를 사용해서 password입력을 가로채보려고 했는데
그건 안되네요.

/bin/passwd를 실행하면
제일먼제 New password:라는 출력이 나오는데요.
이건 pipe를 통해서 출력이 안되나봐요 .

printf("%s", rbuf); 대신에 printf("---%s", rbuf)를 사용해봤는데
결과는 ---New password: 가 찍히지 않고, New password가 찍히네요.

이건 왜 그런걸까요?

ymir의 이미지

https://kldp.org/comment/622517#comment-622517

passwd 명령의 prompt 는 stderr 로 출력됩니다.

passwd 를 컨트롤 하실거면 linux-pam 이나 setspent, getspent, .. 류의 함수를 이용해서..
passwd 와 유사한 프로그램을 만드시는게 나을겁니다.

되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』

study의 이미지

scp 도 stderr로 출력되나요?
위의 프로그램을 써서 scp도 한번 해봤는데, scp도 pipe로 출력이 안되더라구요.

ymir의 이미지

ssh 는 직접 tty 를 열어서 읽고 씁니다. (scp 가 내부적으로 ssh 를 호출함)

$ strace -f -s 256 scp a pi@rpi2:
....
[pid  2955] execve("/usr/bin/ssh", ["/usr/bin/ssh", "-x", "-oForwardAgent=no", "-oPermitLocalCommand=no",
  "-oClearAllForwardings=yes", "-l", "pi", "--", "rpi2", "scp -t ."], [/* 32 vars */]) = 0
....
[pid  2955] open("/dev/tty", O_RDWR|O_LARGEFILE) = 4
[pid  2955] rt_sigaction(SIGALRM, {0x800cf460, [], 0}, {SIG_DFL, [], 0}, 8) = 0
[pid  2955] rt_sigaction(SIGHUP, {0x800cf460, [], 0}, {SIG_DFL, [], 0}, 8) = 0
[pid  2955] rt_sigaction(SIGINT, {0x800cf460, [], 0}, {SIG_DFL, [], 0}, 8) = 0
[pid  2955] rt_sigaction(SIGPIPE, {0x800cf460, [], 0}, {SIG_IGN, [], 0}, 8) = 0
[pid  2955] rt_sigaction(SIGQUIT, {0x800cf460, [], 0}, {SIG_DFL, [], 0}, 8) = 0
[pid  2955] rt_sigaction(SIGTERM, {0x800cf460, [], 0}, {SIG_DFL, [], 0}, 8) = 0
[pid  2955] rt_sigaction(SIGTSTP, {0x800cf460, [], 0}, {SIG_DFL, [], 0}, 8) = 0
[pid  2955] rt_sigaction(SIGTTIN, {0x800cf460, [], 0}, {SIG_DFL, [], 0}, 8) = 0
[pid  2955] rt_sigaction(SIGTTOU, {0x800cf460, [], 0}, {SIG_DFL, [], 0}, 8) = 0
[pid  2955] ioctl(4, TCGETS, {B9600 opost isig icanon echo ...}) = 0
[pid  2955] ioctl(4, TCGETS, {B9600 opost isig icanon echo ...}) = 0
[pid  2955] ioctl(4, SNDCTL_TMR_CONTINUE or TCSETSF, {B9600 opost isig icanon -echo ...}) = 0
[pid  2955] ioctl(4, TCGETS, {B9600 opost isig icanon -echo ...}) = 0
[pid  2955] write(4, "pi@rpi2's password: ", 20) = 20
pi@rpi2's password: 
[pid  2955] read(4, 
...

되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』

study의 이미지

원래 제가 하려던 목적은 덕분에 잘 해결이되었는데요,
우연히 popen() 소스코드를 봤는데, 위의 popen_rw()와 상당히 비슷하더라구요.

switch (pid = vfork()) {
case -1: /* error */
(void) close(pdes[0]);
(void) close(pdes[1]);
return (NULL);
/* NOTREACHED */
case 0: /* child */
if (*type == 'r') {
if (pdes[1] != fileno(stdout)) {
(void) dup2(pdes[1], fileno(stdout));
(void) close(pdes[1]);
}
(void) close(pdes[0]);
} else {
if (pdes[0] != fileno(stdin)) {
(void) dup2(pdes[0], fileno(stdin));
(void) close(pdes[0]);
}
(void) close(pdes[1]);
}
execl("/bin/sh", "sh", "-c", program, NULL);
_exit(127);
/* NOTREACHED */
}
/* parent; assume fdopen can't fail... */
if (*type == 'r') {
iop = fdopen(pdes[0], type);
(void) close(pdes[1]);
} else {
iop = fdopen(pdes[1], type);
(void) close(pdes[0]);
}
pids[fileno(iop)] = pid;
return (iop);

 
그런데 여기서 fdopen()을 사용하는 이유가 궁금해졌습니다.
위의 popen_rw()에서는 fdopen()을 사용하지 않으셨었잖아요.
popen()에서 fdopen()을 사용하는 어떤 이유가 있는건지를 잘 몰라서요 질문드립니다.
 
일단 popen()처럼 만들어서 file pointer를 return하면 fgets()나 fputs()같은 함수를 이용할 
수 있게되는 것은 알겠는데요.
이렇게 하는 것과 popen_rw()처럼 pipe에 read/write를 하는 것과 어떤차이가 있는건지요?
 
갑자기 엉뚱한 질문을 하는 건아닌지 싶네요 ..
bushi의 이미지

popen() 의 원형은 IEEE Std 1003.1-2008 에 다음처럼 정의되어 있습니다.

FILE *popen(const char *command, const char *mode);

어째서 반환형이 int 가 아니라 FILE * 로 정해졌냐는 뜻의 질문이라면, 역사공부를 하면서 자료를 찾아보시면 되겠습니다.
원전인 SVID(System V Interface Definition) 은 AT&T UNIX System V 의 그것을 뜻합니다.

개인적으론, read-only 와 write-only 라는 제한을 그나마 비슷하게라도 구현가능하게 해주는 게 FILE * 이기 때문이라 생각합니다.
fopen() 의 원형은

FILE *fopen(const char *restrict pathname, const char *restrict mode);

이고, fopen() 을 아는 사람이라면 popen() 에 대해 별 의구심이 들지 않을 테죠.
ymir의 이미지

뭐를 쓰던지 그다지 큰 차이는 없습니다.
Stream I/O 든 Low Level I/O 든 적당히 취향에 따라 고르면 되는 문제일 뿐..
popen_rw() 의 이름을 popen() 에서 따오긴 했지만, process open 이라는 컨셉만 가져 왔을 뿐..
실제로는 open/read/write 와 유사한 wrapper 를 만들기 위해 그냥 fd 를 리턴하도록 한 겁니다.

되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.