저는 현재 C에서 의사 터미널 기능을 배우고 있습니다. pty의 마스터와 슬레이브를 생성할 수 있습니다. 포크 후에 stdin, stdout, stderr을 fd 슬레이브로 설정했습니다. exec 후에도 아이가 stdin(fds) 비밀번호를 읽는 대신 터미널에서 비밀번호를 묻는 메시지를 표시합니다. 이 동작을 일으키는 프로세스가 복제되면 ssh
다른 프로그램이 예상대로 작동합니다.
원인을 찾기 전에 setsid(); ioctl(0, TIOCSCTTY, 1);
하위 프로세스에서 사용할 때 이 문제를 해결할 수 있습니까? 이것이 예상되는 동작입니까?execvp
#define _XOPEN_SOURCE 600
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#define __USE_BSD
#include <termios.h>
#include <sys/select.h>
#include <sys/ioctl.h>
#include <string.h>
int main(int ac, char *av[])
{
int fdm, fds;
int rc;
char input[1024], output[150];
// Check arguments
if (ac <= 1)
{
fprintf(stderr, "Usage: %s program_name [parameters]\n", av[0]);
exit(1);
}
fdm = posix_openpt(O_RDWR);
if (fdm < 0)
{
fprintf(stderr, "Error %d on posix_openpt()\n", errno);
return 1;
}
rc = grantpt(fdm);
if (rc != 0)
{
fprintf(stderr, "Error %d on grantpt()\n", errno);
return 1;
}
rc = unlockpt(fdm);
if (rc != 0)
{
fprintf(stderr, "Error %d on unlockpt()\n", errno);
return 1;
}
// Open the slave side ot the PTY
fds = open(ptsname(fdm), O_RDWR);
// Create the child process
if (fork())
{
fd_set fd_in;
// FATHER
// Close the slave side of the PTY
close(fds);
int pass_entered = 0;
while (1)
{
FD_ZERO(&fd_in);
FD_SET(fdm, &fd_in);
rc = select(fdm + 1, &fd_in, NULL, NULL, NULL);
// If data on master side of PTY
if (FD_ISSET(fdm, &fd_in))
{
rc = read(fdm, output, sizeof(input));
if (rc > 0)
{
// Send data on standard output
if (!pass_entered)
{
write(fdm, "password\n", 10);
pass_entered = 1;
}
write(2, "<<", 2);
write(2, output, rc);
write(2, ">>", 2);
int n = write(fdm, "id\n", 3);
}
}
}
}
else
{
// CHILD
// Close the master side of the PTY
close(fdm);
// The slave side of the PTY becomes the standard input and outputs of the child process
close(0); // Close standard input (current terminal)
close(1); // Close standard output (current terminal)
close(2); // Close standard error (current terminal)
dup2(fds, 0); // PTY becomes standard input (0)
dup2(fds, 1); // PTY becomes standard output (1)
dup2(fds, 2); // PTY becomes standard error (2)
close(fds);
// setsid();
// ioctl(0, TIOCSCTTY, 1);
{
char **child_av;
int i;
// Build the command line
child_av = (char **)malloc(ac * sizeof(char *));
for (i = 1; i < ac; i++)
{
child_av[i - 1] = strdup(av[i]);
}
child_av[i - 1] = NULL;
rc = execvp(child_av[0], child_av);
}
return 1;
}
return 0;
} // main
// gcc test_pty.c && ./a.out python3 /tty_test.py //works
// gcc test_pty.c && ./a.out su root //works
// gcc test_pty.c && ./a.out ssh user@localhost //fails
답변1
"Unix 환경의 고급 프로그래밍" (APUE)는 pty를 설정하는 데 사용되는 다른 코드를 보여줍니다. 특히 하위 프로세스는언제나setsid(2)
새 세션을 생성하려면 a를 실행하세요 . 또한 FreeBSD(및 다른 *BSD 시스템)는 TIOCSCTTY
제어 터미널을 설정하기 위해 호출해야 한다고 명시되어 있습니다.
// from http://www.apuebook.com/src.3e.tar.gz -- lib/ptyfork.c
if ((pid = fork()) < 0) {
return(-1);
} else if (pid == 0) { /* child */
if (setsid() < 0)
err_sys("setsid error");
/*
* System V acquires controlling terminal on open().
*/
if ((fds = ptys_open(pts_name)) < 0)
err_sys("can't open slave pty");
close(fdm); /* all done with master in child */
#if defined(BSD)
/*
* TIOCSCTTY is the BSD way to acquire a controlling terminal.
*/
if (ioctl(fds, TIOCSCTTY, (char *)0) < 0)
err_sys("TIOCSCTTY error");
#endif
샘플 코드 외에도 ptym_open
APUE 코드가 언제 무엇을 수행하는지 정확히 이해하려면 및 코드를 찾기 위해 더 깊이 파고들어야 합니다.ptys_open
pty/main.c
하위 실행 코드가 불필요하게 복잡해 보입니다.
{
char **child_av;
int i;
// Build the command line
child_av = (char **)malloc(ac * sizeof(char *));
for (i = 1; i < ac; i++)
{
child_av[i - 1] = strdup(av[i]);
}
child_av[i - 1] = NULL;
rc = execvp(child_av[0], child_av);
}
다음으로 대체될 수 있음
av++;
execvp(av[0], av);
기존 매개변수를 반복하는 대신 직접 사용합니다. 대안으로 APUE는
execvp(argv[optind], &argv[optind]);
그들은 옵션을 처리했기 때문입니다 getopt
.