Книга: Разработка приложений в среде Linux. Второе издание

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.

Оглавление книги


Генерация: 1.340. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз