16.6.4. Примеры псевдотерминалов
16.6.4. Примеры псевдотерминалов
Возможно, одной из самых простых программ, которая может быть написана для использования pty, является программа, открывающая пару pty и запускающая оболочку на подчиненном компоненте pty, соединяя его с управляющим устройством pty. Написав эту программу, вы можете расширять ее любым подходящим способом, forkptytest.с является примером использования функции forkpty(), a ptytest.с — это пример, который использует функции, определенные в ptypair.с, и является несколько более сложным.
1: /* forkptytest.с */
2:
3: #include <errno.h>
4: #include <signal.h>
5: #include <stdio.h>
6: #include <stdlib.h>
7: #include <sys/ioctl.h>
8: #include <sys/poll.h>
9: #include <termios.h>
10: #include <unistd.h>
11: #include <pty.h>
12:
13:
14: volatile int propagate_sigwinch = 0;
15:
16: /* sigwinch_handler
17: * распространяет изменения размеров окна из входного файлового
18: * дескриптора на ведущую сторону pty.
19: */
20: void sigwinch_handler(int signal) {
21: propagate_sigwinch = 1;
22: }
23:
24:
25: /* forkptytest пытается открыть пару pty с запуском оболочки
26: * на подчиненной стороне pty.
27: */
28: int main(void) {
29: int master;
30: int pid;
31: struct pollfd ufds[2];
32: int i;
33: #define BUFSIZE 1024
34: char buf[1024];
35: struct termios ot, t;
36: struct winsize ws;
37: int done = 0;
38: struct sigaction act;
39:
40: if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0) {
41: perror("ptypair: не удается получить размеры окна");
42: exit(1);
43: }
44:
45: if ((pid = forkpty(&master, NULL, NULL, &ws)) < 0) {
46: perror("ptypair");
47: exit(1);
48: }
49:
50: if (pid == 0) {
51: /* запустить оболочку */
52: execl("/bin/sh", "/bin/sh", 0);
53:
54: /* сюда управление никогда не попадет */
55: exit(1);
56: }
57:
58: /* родительский процесс */
59: /* установить обработчик SIGWINCH */
60: act.sa_handler = sigwinch_handler;
61: sigemptyset(&(act.sa_mask));
62: act.sa_flags = 0;
63: if (sigaction(SIGWINCH, &act, NULL) < 0) {
64: perror("ptypair: невозможно обработать SIGWINCH");
65: exit(1);
66: }
67:
68: /* Обратите внимание, что настройки termios устанавливаются только
69: * для стандартного ввода; ведущая сторона pty НЕ является tty.
70: */
71: tcgetattr(STDIN_FILENO, &ot);
72: t = ot;
73: t.c_lflag &= ~(ICANON | ISIG | ECHO | ECHOCTL | ECHOE |
74: ECHOK | ECHOKE | ECHONL | ECHOPRT);
75: t.c_iflag |= IGNBRK;
76: t.c_cc[VMIN] = 1;
77: t.c_cc[VTIME] = 0;
78: tcsetattr(STDIN_FILENO, TCSANOW, &t);
79:
80: /* Этот код взят без изменений из robin.с
81: * Если дочерний процесс завершается, читающая ведущая сторона
82: * дoлжнa вернуть -1 и завершиться.
83: */
84: ufds[0].fd = STDIN_FILENO;
85: ufds[0].events = POLLIN;
86: ufds[1].fd = master;
87: ufds[1].events = POLLIN;
88:
89: do {
90: int r;
91:
92: r = poll(ufds, 2, -1);
93: if ((rs < 0) && (errno != EINTR)) {
94: done = 1;
95: break;
96: }
97:
98: /* сначала проверить возможность завершения */
99: if ((ufds[0].revents | ufds[1].revents) &
100: (POLLERR | POLLHUP | POLLNVAL)) {
101: done = 1;
102: break;
103: }
104:
105: if (propagate_sigwinch) {
106: /* обработчик сигналов запросил распространение SIGWINCH */
107: if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0) {
108: perror("ptypair: не удается получить размеры окна");
109: }
110: if (ioctl(master, TIOCSWINSZ, &ws) < 0) {
111: perror("не удается восстановить размеры окна");
112: }
113:
114: /* не делать этого снова до поступления следующего SIGWINCH */
115: propagate_sigwinch = 0;
116:
117: /* опрос мог быть прерван SIGWINCH,
118: * потому повторить попытку.
119: */
120: continue;
121: }
122:
123: if (ufds[1].revents & POLLIN) {
124: i = read (master, buf, BUFSIZE);
125: if (i >= 1) {
126: write(STDOUT_FILENO, buf, i);
127: } else {
128: done = 1;
129: }
130: }
131:
132: if (ufds[0].revents & POLLIN) {
133: i = read (STDIN_FILENO, buf, BUFSIZE);
134: if (i >= 1) {
135: write(master, buf, i);
136: } else {
137: done = 1;
138: }
139: }
140:
141: } while (!done);
142:
143: tcsetattr(STDIN_FILENO, TCSANOW, &ot);
144: exit(0);
145: }
Программа forkptytest.с делает очень немногое из того, чего вы раньше не видели. Обработка сигналов рассматривается в главе 12, а цикл poll() почти полностью переписан из кода robin.с, представленного ранее в этой главе (за исключением обработки управляющих символов), равно как и код, модифицирующий настройки termios.
Остается лишь объяснить распространение изменений размеров окна.
В строке 105 после завершения poll() мы проверяем, является ли причиной завершения poll() сигнал SIGWINCH, доставляемый функции sigwinch_handler в строке 20. Если это так, необходимо получить новый размер текущего окна из стандартного ввода и распространить его в pty подчиненного компонента. Установкой размера окна SIGWINCH передается автоматически процессу, работающему на pty; мы не должны явно передавать SIGWINCH этому процессу.
Теперь для сравнения посмотрите, насколько усложняется этот код в случае использования функций, определенных в ptypair.с.
1: /* ptytest.с */
2:
3: #include <errno.h>
4: #include <fcntl.h>
5: #include <signal.h>
6: #include <stdio.h>
7: #include <stdlib.h>
8: #include <string.h>
9: #include <sys/ioctl.h>
10: #include <sys/poll.h>
11: #include <sys/stat.h>
12: #include <termios.h>
13: #include <unistd.h>
14: #include "ptypair.h"
15:
16:
17: volatile int propagate_sigwinch = 0;
18:
19: /* sigwinch_handler
20: * распространяет изменения размеров окна из входного файлового
21: * дескриптора на ведущую сторону pty.
22: */
23: void sigwinch_handler(int signal) {
24: propagate_sigwinch = 1;
25: }
26:
27:
28: /* ptytest пытается открыть пару pty с запуском оболочки
29: * на подчиненной стороне pty.
30: */
31: int main(void) {
32: int master;
33: int pid;
34: char * name;
35: struct pollfd ufds[2];
36: int i;
37: #define BUFSIZE 1024
38: char buf[1024];
39: struct termios ot, t;
40: struct winsize ws;
41: int done = 0;
42: struct sigaction act;
43:
44: if ((master = get_master_pty(&name)) < 0) {
45: perror("ptypair: не удается открыть ведущее устройство pty");
46: exit(1);
47: }
48:
49: /* установить обработчик SIGWINCH */
50: act.sa_handler = sigwinch_handler;
51: sigemptyset(&(act.sa_mask));
52: act.sa_flags = 0;
53: if (sigaction (SIGWINCH, &act, NULL) < 0) {
54: perror("ptypair: невозможно обработать SIGWINCH");
55: exit(1);
56: }
57:
58: if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0) {
59: perror("ptypair: не удается получить размеры окна");
60: exit(1);
61: }
62:
63: if ((pid = fork()) < 0) {
64: perror("ptypair");
65: exit(1);
66: }
67:
68: if (pid == 0) {
69: int slave; /* файловый дескриптор для подчиненного компонента pty*/
70:
71: /* Мы находимся в дочернем процессе */
72: close(master);
73:
74: if ((slave = get_slave_pty(name)) < 0) {
75: perror("ptypair: не удается открыть подчиненный компонент pty");
76: exit(1);
77: }
78: free(name);
79:
80: /* Мы должны сделать этот процесс лидером группы сеансов,
81: * поскольку он выполняется на новом PTY, а функции вроде
82: * управления заданиями просто не будут корректно работать,
83: * если нет лидера группы сеансов и лидера группы процессов
84: * (который автоматически является лидером группы сеансов).
85: * Это также разъединяет со старым управляющим tty.
86: */
87: if (setsid() < 0) {
88: perror("невозможно установить лидер сеанса");
89: }
90:
91: /* Соединиться с новым управляющим tty. */
92: if (ioctl(slave, TIOCSCTTY, NULL)) {
93: perror("невозможно установить новый управляющий tty");
94: }
95:
96: /* сделать подчиненный pty стандартным устройством ввода, вывода и ошибок */
97: dup2(slave, STDIN_FILENO);
98: dup2(slave, STDOUT_FILENO);
99: dup2(slave, STDERR_FILENO);
100:
101: /* в этой точке подчиненный pty должен быть стандартным устройством ввода */
102: if (slave > 2) {
103: close(slave);
104: }
105:
106: /* Попытаться восстановить размеры окна; сбой не является критичным */
107: if (ioctl(STDOUT_FILENO, TIOCSWINSZ, &ws) < 0) {
108: perror("не удается восстановить размеры окна");
109: }
110:
111: /* запустить оболочку */
112: execl("/bin/sh", "/bin/sh", 0);
113:
114: /* сюда управление никогда не попадет */
115: exit(1);
116: }
117:
118: /* родительский процесс */
119: free(name);
120:
121: /* Обратите внимание, что настройки termios устанавливаются только
122: * для стандартного ввода; ведущая сторона pty НЕ является tty.
123: */
124: tcgetattr(STDIN_FILENO, &ot);
125: t = ot;
126: t.c_lflag &= ~(ICANON | ISIG | ECHO | ECHOCTL | ECHOE |
127: ECHOK | ECHOKE | ECHONL | ECHOPRT);
128: t.c_iflag |= IGNBRK;
129: t.c_cc[VMIN] = 1;
130: t.c_cc[VTIME] = 0;
131: tcsetattr(STDIN_FILENO, TCSANOW, &t);
132:
133: /* Этот код взят без изменений из robin.с
134: * Если дочерний процесс завершается, читающая ведущая сторона
135: * должна вернуть -1 и завершиться.
136: */
137: ufds[0].fd = STDIN_FILENO;
138: ufds[0].events = POLLIN;
139: ufds[1].fd = master;
140: ufds[1].events = POLLIN;
141:
142: do {
143: int r;
144:
145: r = poll(ufds, 2, -1);
146: if ((r < 0) && (errno != EINTR)) {
147: done = 1;
148: break;
149: }
150:
151: /* сначала проверить возможность завершения */
152: if ((ufds[0].revents | ufds[1].revents) &
153: (POLLERR | POLLHUP | POLLNVAL)) {
154: done = 1;
155: break;
156: }
157:
158: if (propagate_sigwinch) {
159: /* обработчик сигнала запросил распространение SIGWINCH */
160: if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0) {
161: perror("ptypair: не удается получить размеры окна");
162: }
163: if (ioctl(master, TIOCSWINSZ, &ws) < 0) {
164: perror("не удается восстановить размеры окна");
165: }
166:
167: /* не делать этого снова до поступления следующего SIGWINCH */
168: propagate_sigwinch = 0;
169:
170: /* опрос мог быть прерван SIGWINCH,
171: * потому повторить попытку. */
172: continue;
173: }
174:
175: if (ufds[1].revents & POLLIN) {
176: i = read (master, buf, BUFSIZE);
177: if (i >= 1) {
178: write(STDOUT_FILENO, buf, i);
179: } else {
180: done = 1;
181: }
182: }
183:
184: if (ufds[0].revents & POLLIN) {
185: i = read (STDIN_FILENO, buf, BUFSIZE);
186: if (i >= 1) {
187: write(master, buf, i);
188: } else {
189: done = 1;
190: }
191: }
192: } while (!done);
193:
194: tcsetattr(STDIN_FILENO, TCSANOW, &ot);
195: exit(0);
196: }
Вся добавленная сложность ptytest.с по сравнению с forkptytest.с связана с обработкой старого интерфейса. Все это было описано в данной главе, кроме запуска дочернего процесса, который рассматривался в главе 10.