16.6.4. Примеры псевдотерминалов

We use cookies. Read the Privacy and Cookie Policy

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.