
=head1 НАЗВА

miniexpect — дуже проста бібліотека expect для C.

=head1 КОРОТКИЙ ОПИС

 #include <errno.h>
 #include <sys/wait.h>
 #include <pcre.h>
 #include <miniexpect.h>
 
 mexp_h *h;
 h = mexp_spawnl ("ssh", "ssh", "host", NULL);
 switch (mexp_expect (h, regexps, ovector, ovecsize)) {
   ...
 }
 mexp_close (h);

 cc prog.c -o програма -lminiexpect -lpcre

=head1 ОПИС

Miniexpect — дуже проста expect-подібна бібліотека мовою C. Expect — це
спосіб керування зовнішньою програмою, яка працює у інтерактивному режимі.

Miniexpect має простіший за libexpect інтерфейс і не залежить від Tcl. Крім
того, вона захищена для роботи з потоками, правильно працює зі сталими та
використовує сучасні стандарти C.

Miniexpect — самодостатня бібліотека, окрім єдиної залежності: для її роботи
потрібна бібліотека PCRE (Perl Compatible Regular Expressions) з
L<http://www.pcre.org/>. Залежність від PCRE є базовою, оскільки ми хотіли
надати користувачам максимально потужні можливості синтаксису формальних
виразів. Ще важливішим у цьому аспекті є те, що у PCRE передбачено зручний
спосіб виявлення часткової відповідності, що робить цю бібліотеку дуже
простою у реалізації.

Цю сторінку підручника присвячено програмному інтерфейсу. Приклади
використання програмного інтерфейсу можна знайти у каталозі з початковим
кодом бібліотеки.

=head1 ПРИНЦИПИ

Miniexpect надає вам змогу запустити зовнішню програму, керувати нею
(надсилаючи їй команди), а потім правильно завершити її роботу. Від інших
програмних інтерфейсів, зокрема L<popen(3)> і L<system(3)>, це відрізняється
у двох аспектах: по-перше, miniexpect створює псевдотермінал (pty);
по-друге, miniexpect надає вам змогу шукати у виведених програмою даних за
формальними виразами. Обидві ці особливості є зручними для керування
інтерактивними програмами, які (наприклад) можуть надсилати запити щодо
паролів. Втім, ви можете використовувати miniexpect майже для будь-якої
зовнішньої програми.

Одночасно можна керувати декількома програмами.

=head1 ПОРОДЖЕННЯ ПІДПРОЦЕСІВ

Для створення підпроцесів передбачено чотири виклики:

B<mexp_h *mexp_spawnl (const char *file, const char *arg, ...);>

Створює підпроцес із запущеною зовнішньою програмою C<файл> (пошук цієї
програми відбуватиметься у каталогах C<$PATH>, якщо ви не вкажете абсолютний
шлях до програми). C<аргумент, ...> — аргументи для програми. Список
аргументів слід завершувати символом C<NULL>. Зазвичай, першим аргументом
має бути назва програми.

Поверненим значенням є дескриптор (див. наступний розділ).

Якщо у підпроцесі станеться помилка, буде повернуто C<NULL>, а номер помилки
буде доступним у C<errno>.

Наприклад, щоб запустити підпроцес ssh, ви можете скористатися такою
командою:

 h = mexp_spawnl ("ssh", "ssh", "-l", "root", "host", NULL);

а для запуску певного виконуваного файла ssh такою командою:

 h = mexp_spawnl ("/usr/local/bin/ssh", "ssh", "-l", "root", "host", NULL);

Альтернативою C<mexp_spawnl> є:

B<mexp_h *mexp_spawnv (const char *file, char **argv);>

Те саме, що і C<mexp_spawnl>, але аргументи передаються у форматі масиву,
який завершується символом NULL.

Також передбачено дві версії вказаних вище викликів, які приймають прапорці:

B<mexp_h *mexp_spawnlf (unsigned flags, const char *file, const char *arg,
...);>

B<mexp_h *mexp_spawnvf (unsigned flags, const char *file, char **argv);>

Прапорці можуть містити такі значення, які сполучаються між собою логічним
АБО:

=over 4

=item B<MEXP_SPAWN_KEEP_SIGNALS>

Не скидати у підпроцесах обробники сигналів у значення C<SIG_DFL>.

=item B<MEXP_SPAWN_KEEP_FDS>

Не закривати дескриптори файлів E<ge> 3 у підпроцесі.

=item B<MEXP_SPAWN_COOKED_MODE> або B<MEXP_SPAWN_RAW_MODE>

Налаштувати pty на роботу у приготованому (cooked) або простому (raw)
режимі. Типовим є простий режим.

=back

=head1 ОБРОБНИКИ

Після створення підпроцесу, вам повертається дескриптор у форматі вказівник
на структуру:

 struct mexp_h;
 typedef struct mexp_h mexp_h;

До дескриптора може бути застосовано різні методи:

B<int mexp_get_fd (mexp_h *h);>

Повернути дескриптор файла pty підпроцесу. Ви можете читати дані з цього
дескриптора або записувати їх до нього, хоча для цього є і зручніші функції
(див. нижче).

B<pid_t mexp_get_pid (mexp_h *h);>

Повертає ідентифікатор підпроцесу. Якщо хочете, можете надсилати йому
сигнали.

B<int mexp_get_timeout_ms (mexp_h *h);>

B<void mexp_set_timeout_ms (mexp_h *h, int millisecs);>

B<void mexp_set_timeout (mexp_h *h, int secs);>

Отримати або встановити значення часу очікування, яке використовується
C<mexp_expect> [див. нижче]. Одиницею виміру є мілісекунда (1/1000
секунди). Встановлювати це значення слід до виклику C<mexp_expect>. Якщо
якомусь із методів C<set_>  передано -1, часу очікування просто не
буде. Типовим є значення у 60000 мілісекунд (60 секунд).

B<size_t mexp_get_read_size (mexp *h);>

B<void mexp_set_read_size (mexp *h, size_t read_size);>

Отримати або встановити природний розмір (у байтах) для блоків читання з
підпроцесу. Типовим значенням є 1024. Для більшості функцій виклику немає
ніякої потреби у зміні типового значення.

B<int mexp_get_pcre_error (mexp *h);>

Коли C<mexp_expect> [див. нижче] викликає функцію PCRE L<pcre_exec(3)>, вона
накопичує повернуті значення у полі C<pcre_error> у дескрипторі і це поле
повертається цим методом.

Існує два використання цього:

=over 4

=item 1.

Якщо C<mexp_expect> повертає C<MEXP_PCRE_ERROR>, справжній код помилки PCRE,
який повернуто L<pcre_exec(3)>, доступний за викликом цього методу. Список
кодів помилок PCRE наведено у підручнику з L<pcreapi(3)>.

=item 2.

Трохи незвичнішим використанням є отримання захоплених підрядків з вашого
формального виразу (виклику L<pcre_get_substring(3)>). Третім параметром
функції (C<stringcount>) є значення, яке повертається L<pcre_exec(3)>, отже
ви можете викликати функцію ось так:

 pcre_get_substring (h->buffer, ovector,
                     mexp_get_pcre_error (h), 1, &matched);

=back

B<void mexp_set_debug_file (mexp *h, FILE *fp);>

B<FILE *mexp_get_debug_file (mexp *h);>

Встановити або отримати діагностичний файл дескриптора. Щоб увімкнути
діагностику, передайте дескриптор файла, відмінний від C<NULL>, наприклад
C<stderr>. Щоб вимкнути діагностику, передайте C<NULL>. Діагностичні
повідомлення виводитимуться на дескриптор файла.

Зауважте, що виводитимуться усі виведені і введені дані, зокрема паролі. Щоб
заборонити виведення паролів, змініть ваш код так, щоб викликалася функція
C<mexp_printf_password>, а не C<mexp_printf>.

У вказаних нижче полів у дескрипторі немає методів, але доступ до них можна
здійснювати безпосередньо:

 char *buffer;
 size_t len;
 size_t alloc;

Якщо C<mexp_expect> повертає відповідник, ці змінні містять буфер
читання. Зауважте, що цей буфер не містить усіх вхідних даних від процесу,
але лише містить принаймні ту частину, яка відповідає формальному виразу (і,
можливо, ще дещо). C<buffer> — буфер читання, а C<len> — кількість байтів
даних у буфері.

 ssize_t next_match;

Якщо C<mexp_expect> повертає відповідник, C<next_match> вказує на перший
байт у буфері I<після> виразу, який повністю відповідає критерію
пошуку. (Значенням може бути C<-1>, що означає, що критерій пошуку є
некоректним). Під час наступного виклику C<mexp_expect> функція розпочне
обробку даних C<buffer[next_match...len-1]>. У функцій виклику також може
виникнути потреба у читанні з цієї позиції до буфера до виклику L<read(2)>
щодо дескриптора файла. Функції виклику також можуть встановлювати значення
для цього поля. Наприклад, можна встановити значення C<-1> з метою
ігнорування решти вмісту буфера. Здебільшого, у функціях виклику можна
ігнорувати це поле, і C<mexp_expect> просто зробить все очікуваним чином під
час наступного виклику.

 void *user1;
 void *user2;
 void *user3;

Непрозорі вказівники, які використовуватиме функція виклику. Бібліотека не
працюватиме з ними.

=head1 ЗАКРИТТЯ ДЕСКРИПТОРА

Для закриття дескриптора і очищення підпроцесу виконайте такий виклик:

B<int mexp_close (mexp_h *h);>

Повертає код стану від підпроцесу. Код стану повертається у формі стану
L<waitpid(2)>/L<system(3)>, отже ви можете використовувати макроси
C<WIFEXITED>, C<WEXITSTATUS>, C<WIFSIGNALED>, C<WTERMSIG> тощо, які
визначено у C<E<lt>sys/wait.hE<gt>>, для обробки цього стану.

Якщо сталася помилка системного виклику, буде повернуто C<-1>. Номер помилки
зберігатиметься у C<errno>.

Нотатки:

=over 4

=item *

Навіть у випадках помилок цим викликом дескриптор завжди закривається, а
його пам'ять звільняється.

=item *

Надсилання ядром SIGHUP підпроцесу є звичною справою.

Якщо підпроцес не перехоплює SIGHUP, його роботу буде завершено з таким
станом:

 WIFSIGNALED (status) && WTERMSIG (status) == SIGHUP

Цей випадок не обов'язково вважатиметься помилкою.

=back

Ось як код має знаходити і виводити помилки від C<mexp_close>:

  status = mexp_close (h);
  if (status == -1) {
    perror ("mexp_close");
    return -1;
  }
  if (WIFSIGNALED (status) && WTERMSIG (status) == SIGHUP)
    goto ignore; /* не помилка */
  if (!WIFEXITED (status) || WEXITSTATUS (status) != 0)
    /* Можна скористатися макросом W* для виведення кращого повідомлення про помилку. */
    fprintf (stderr, "error: subprocess failed, status = %d", status);
    return -1;
  }
 ignore:
  /* не пов'язані з помилкою випадки */

=head1 ФУНКЦІЯ EXPECT

Miniexpect містить потужну функцію для пошуку за формальними виразами,
створену на основі L<pcre(3)>:

B<int mexp_expect (mexp_h *h, const mexp_regexp *regexps, int *ovector, int
ovecsize);>

У даних, виведених підпроцесом, буде виконано пошук за списком формальних
виразів PCRE з аргументу C<regexps>. C<regexps> є списком структур
формальних виразів:

 struct mexp_regexp {
   int r;
   const pcre *re;
   const pcre_extra *extra;
   int options;
 };
 typedef struct mexp_regexp mexp_regexp;

C<r> — цілочисельний код, який повертається C<mexp_expect>, якщо буде
встановлено відповідність формальному виразу. Код B<має бути> значенням
E<gt> 0. C<r == 0> означає кінець списку формальних виразів. C<re> є
компільованим формальним виразом.

Можливі повернуті значення:

=over 4

=item C<MEXP_TIMEOUT>

Відповідників до завершення часу очікування на дані (C<h-E<gt>timeout>) не
знайдено.

=item C<MEXP_EOF>

Підпроцес розірвав з'єднання.

=item C<MEXP_ERROR>

Сталася помилка у системному виклику (наприклад, у виклику функції
читання). Помилку буде повернуто у C<errno>.

=item C<MEXP_PCRE_ERROR>

Сталася помилка C<pcre_exec>. Для поля C<h-E<gt>pcre_error> буде встановлено
значення коду помилки. Номери кодів помилок C<PCRE_*> та їхнє тлумачення
можна знайти у підручнику з L<pcreapi(3)>.

=item C<r> E<gt> 0

Якщо буде встановлено відповідність за будь-яким із формальних виразів, буде
повернуто відповідний цілочисельний код (C<regexps[].r>).

=back

Нотатки:

=over 4

=item *

C<regexps> може мати значення NULL або бути порожнім списком. Такі значення
означають, що встановлення відповідності за формальним виразом не
виконуватиметься. Це корисно, якщо вам просто потрібно зачекати на символ
кінця файла або перевищення часу очікування.

=item *

C<regexps[].re>, C<regexps[].extra>, C<regexps[].options>, C<ovector> та
C<ovecsize> передаються до функції L<pcre_exec(3)>.

=item *

Якщо буде передано декілька формальних виразів, їхня обробка відбуватиметься
за вказаним порядком. Буде повернуто  відповідник I<першого> формального
виразу, I<навіть якщо далі може бути встановлено відповідність вхідних даних
іншому формальному виразу>.

Наприклад, якщо вхідними даними є C<"hello world"> і ви передали два таких
формальних вирази:

 regexps[0].re = world
 regexps[1].re = hello

буде встановлено відповідність першого формального виразу (C<"world">),
частину вхідних даних C<"hello"> може бути проігноровано.

Іноді, така поведінка може призвести до несподіваних результатів. У
наведеному вище прикладі, якщо прочитано лише C<"hello wor">, відповідність
I<буде> встановлено для другого формального виразу (C<"hello">).

У таких випадках поєднуйте ваші формальні вирази у один, наприклад
C<(hello)|(world)>.

=back

=head2 Приклад mexp_expect

Зрозуміти, як працює C<mexp_expect>, можна з простого прикладу.

У цьому прикладі ми чекаємо на те, що ssh або надішле запит щодо введення
пароля, або (якщо пароль не потрібен) запит щодо введення команди. Залежно
від отриманих даних, у відповідь буде надіслано або пароль, або команду.

Незвична синтаксична конструкція C<(mexp_regexp[]){...}> називається
«складений літерал», вона доступна у стандарті C99. Якщо вам доводиться
користуватися застарілим компілятором, можете просто скористатися замість
неї локальною змінною.

 mexp_h *h;
 char *errptr;
 int offset;
 pcre *password_re, *prompt_re;
 const int ovecsize = 12;
 int ovector[ovecsize];
 
 password_re = pcre_compile ("assword", 0, &errptr, &offset, NULL);
 prompt_re = pcre_compile ("[$#] ", 0, &errptr, &offset, NULL);
 
 switch (mexp_expect (h,
                      (mexp_regexp[]) {
                        { 100, .re = password_re },
                        { 101, .re = prompt_re },
                        { 0 },
                      }, ovector, ovecsize)) {
  case 100:
    /* тут ви можете надіслати пароль */
    break;
  case 101:
    /* тут ви можете надіслати команду */
    break;
  case MEXP_EOF:
    fprintf (stderr, "error: ssh closed the connection unexpectedly\n");
    exit (EXIT_FAILURE);
  case MEXP_TIMEOUT:
    fprintf (stderr, "error: timeout before reaching the prompt\n");
    exit (EXIT_FAILURE);
  case MEXP_ERROR:
    perror ("mexp_expect");
    exit (EXIT_FAILURE);
  case MEXP_PCRE_ERROR:
    fprintf (stderr, "error: PCRE error: %d\n", h->pcre_error);
    exit (EXIT_FAILURE);
 }

=head1 НАДСИЛАННЯ КОМАНД ДО ПІДПРОЦЕСУ

Запис даних до підпроцесу можна здійснити простим записуванням даних до
C<h-E<gt>fd>. Втім, нами передбачено і зручніший варіант:

B<int mexp_printf (mexp_h *h, const char *fs, ...);>

B<int mexp_printf_password (mexp_h *h, const char *fs, ...);>

Повертає кількість байтів, якщо усе повідомлення було успішно записано. Якщо
сталася помилка, повертає -1 і записує код помилки до C<errno>.

Нотатки:

=over 4

=item *

C<mexp_printf> не виконуватиме частковий запис. Якщо не вдасться записати
усі дані, буде повернуто повідомлення про помилку.

=item *

Ця функція не записує автоматично символ нового рядка. Якщо ви хочете
надіслати команду із завершальним символом нового рядка, слід зробити ось
так:

 mexp_printf (h, "exit\n");

=item *

C<mexp_printf_password> працює ідентично до C<mexp_printf>, але виведені
дані I<не> надсилаються до діагностичного файла, якщо увімкнено
діагностику. Як можна зрозуміти з назви, цим полем можна скористатися для
того, щоб у виведених діагностичних даних не було паролів.

=back

B<int mexp_send_interrupt (mexp_h *h);>

Надіслати символ переривання (C<^C>, Ctrl-C, C<\003>). Те саме, що натиснути
C<^C> - роботу підпроцесу (або віддаленого процесу, якщо використовується
C<ssh>) буде завершено.

Зауважте, що це працює, лише якщо pty перебуває у приготованому (cooked)
режимі (тобто було передано C<MEXP_SPAWN_COOKED_MODE> до C<mexp_spawnlf> або
C<mexp_spawnvf>). У простому (raw) режимі, передаються усі символи без
будь-якої спеціальної обробки.

=head1 ПОЧАТКОВІ КОДИ

Початкові коди можна знайти тут:
L<http://git.annexia.org/?p=miniexpect.git;a=summary>

=head1 ТАКОЖ ПЕРЕГЛЯНЬТЕ

L<pcre(3)>, L<pcre_exec(3)>, L<pcreapi(3)>, L<waitpid(2)>, L<system(3)>.

=head1 АВТОРИ

Richard W.M. Jones (C<rjones at redhat dot com>)

=head1 ЛІЦЕНЗУВАННЯ

Ця бібліотека розповсюджується за умов дотримання Library GPL (LGPL) версії
2 або, якщо потрібно, будь-якої наступної версії цих умов ліцензування.

=head1 АВТОРСЬКІ ПРАВА

© Red Hat Inc., 2014

