Online Documentation Server
 ПОИСК
ods.com.ua Web
 КАТЕГОРИИ
Home
Programming
Net technology
Unixes
Security
RFC, HOWTO
Web technology
Data bases
Other docs

 

 ПОДПИСКА

 О КОПИРАЙТАХ
Вся предоставленная на этом сервере информация собрана нами из разных источников. Если Вам кажется, что публикация каких-то документов нарушает чьи-либо авторские права, сообщите нам об этом.




 /*      Пример 1      */

/* Задача о размене монеты:
 * Поиск всех возможных коэффициентов a0 .. an разложения числа  S
 * в виде
 *      S = a0 * c0 + a1 * c1 + ... + an * cn
 * где веса c0 .. cn заданы заранее и упорядочены.
 * Веса и коэффициенты неотрицательны (ai >= 0, ci >= 0).
 */

#include <stdio.h>

/* Достоинства разменных монет (веса ci) */
int cost[] = {
 1, 2, 3, 5, 10, 15, 20, 50, 100, 300, 500  /* копеек */
};

#define N       (sizeof cost / sizeof(int))
int count[ N ];         /* число монет данного типа (коэффициенты ai) */
long nvar;              /* число вариантов */

main( ac, av ) char *av[];
{
    int coin;

    if( ac == 1 ){
 fprintf( stderr, "Укажите, какую монету разменивать: %s число\n",
  av[0] );
 exit(1);
    }
    coin = atoi( av[1] );
    printf( "          Таблица разменов монеты %d коп.\n", coin );
printf( " Каждый столбец содержит количество монет указанного достоинства.\n" );
printf( "-------------------------------------------------------------------\n" );
printf( "| 5р. | 3р. | 1р. | 50к.| 20к.| 15к.| 10к.|  5к.|  3к.|  2к.|  1к.|\n" );
printf( "-------------------------------------------------------------------\n" );

    change( N-1, coin );

printf( "-------------------------------------------------------------------\n" );
    printf( "Всего %ld вариантов\n", nvar );
}

/* рекурсивный размен */
change( maxcoin, sum )
 int sum;        /* монета, которую меняем */
 int maxcoin;    /* индекс по массиву cost[] монеты максимального
    * достоинства, допустимой в данном размене.
    */
{
 register i;

 if( sum == 0 ){  /* вся сумма разменяна */
  /* распечатать очередной вариант */
  putchar( '|' );
  for( i = N-1 ; i >= 0 ; i-- )
   if( count[i] )
       printf(" %3d |", count[ i ] );
   else
       printf("     |" );
  putchar( '\n' );
  nvar++;
  return;
 }
 if( sum >= cost [ maxcoin ] ){
     /* если можно выдать монету достоинством cost[maxcoin] ,
      * то выдать ее:
      */
     count[ maxcoin ] ++;   /* посчитали выданную монету */

       /* размениваем остаток суммы :
 * Первый аргумент - может быть можно дать еще одну такую монету ?
 * Второй аргумент - общая сумма убавилась на одну монету cost[maxcoin].
 */
     change( maxcoin, sum - cost[maxcoin] );

     count[ maxcoin ] --;   /* ... Теперь попробуем иной вариант ... */
 }

 /* попробовать размен более мелкими монетами */
 if( maxcoin )
  change( maxcoin-1, sum );
}
.
 /*      Пример 2     */

/* Подсчет количества вхождений каждой из букв алфавита в файл.
 * Выдача таблицы.
 * Подсчет частоты использования битов в байтах файла.
 */
#include <stdio.h>
#include <ctype.h>

long bcnt[8];
char masks[8] = {       /* маски битов */
 1, 2, 4, 8, 16, 32, 64, 128 };
long cnt[256];          /* счетчики для каждой из 256 букв */

/* распечатка букв в стиле языка СИ */
char *pr( c ){
 static char buf[ 20 ];

 switch( c ){
 case '\n': return   " \\n "   ;
 case '\r': return   " \\r "   ;
 case '\t': return   " \\t "   ;
 case '\b': return   " \\b "   ;
 case '\f': return   " \\f "   ;
 case '\033': return " ESC"    ;
 case '\0': return   " \\0 "   ;
 case 0177: return   " ^? "    ;
 }
 if( c < ' ' ){
  sprintf( buf, " ^%c ", c + 'A' - 1 );
 }else if( isspace(c)){
  sprintf( buf, " '%c'", c );
 }else if( ! isprint( c ))
  sprintf( buf, "\\%3o", c );
  else   sprintf( buf, "  %c ", c );
  return buf;
}

main( argc, argv ) char **argv;  {
 FILE *fp;

 if( argc == 1 ) process( stdin );
 else{   argv++; argc--;
  while( *argv ){
   printf( "----- FILE %s -----\n", *argv );
   if((fp = fopen( *argv, "r" )) == NULL ){
    printf( "Can not open\n" );
   }else{  process( fp ); fclose( fp );   }
   argv++; argc--;
  }
 }
 exit(0);
}

/* обработать файл с поинтером fp */
process( fp ) FILE *fp;
{       register i; int c; int n;

 /* зачистка счетчиков */
 for( i=0; i < 256; i++ ) cnt[i]  = 0L;
 for( i=0; i < 8  ; i++ ) bcnt[i] = 0;

 while( ( c=getc(fp)) != EOF ){
   c &= 0377;
      /* подсчитать букву */
  cnt[ c ] ++;
      /* подсчет битов */
  for( i=0; i < 8; i++ )
   if( c & masks[i] )
    bcnt[ i ] ++;
 }
 /* выдача результатов в COL колонок */
#define COL 4
 printf( "\tASCII map\n" );
 for( n=i=0; i < 256; i++ ){
      /* if( cnt[i] == 0l ) continue; */
      printf( "%s  %5ld      |", pr(i), cnt[i] );

      if( ++n == COL ){ n = 0; putchar('\n'); }
/* или       if((i % COL) == (COL-1)) putchar('\n');       */
 }
 printf( "\n\tBITS map\n" );
 for( i=7; i >=0 ; i-- ) printf( "%6d ", i );
 putchar( '\n' );
 for( i=7; i >=0 ; i-- )
  printf( "%6ld ", bcnt[i] );
 putchar( '\n' ); putchar( '\n' );
}
.
 /*      Пример 3         */

/* Центрирование строк текста. Пример на работу с указателями. */
/* Входные строки не должны содержать табуляций                */
/* Вызов: a.out < входной_файл                                 */

#include <stdio.h>
extern char *gets();
#define WIDTH 60        /* ширина листа */
main(){
 char rd[81]; register char *s;
 char *head,        /* начало текста */
      *tail;        /* конец текста  */
 register int len, i;
 int shift;         /* отступ */

      /* Читать со стандартного ввода в rd по одной строке,
       * пока файл не кончится. При вводе с клавиатуры конец файла
       * обозначается нажатием клавиш CTRL+D
       */
 while( gets( rd ) != NULL ){
     if( !*rd ){
  /* Строка пуста */
  putchar( '\n' ); continue;
     }
     /* пропуск пробелов в начале строки */
     for( s = rd; *s == ' ' ; s++ );
     if( ! *s ){
  /* Строка состоит только из пробелов */
  putchar( '\n' ); continue;
     }
     head = s;

     /* встать на конец строки */
     while( *s ) s++;

     /* искать последний непробел */
     s--;
     while( *s == ' ' && s != rd ) s--;
     tail = s;

     /* Длина текста */ len = (tail-head) + 1;
     /* разность указателей - целое */
     shift = (WIDTH - len)/2;
     if(shift < 0 ){
  fprintf(stderr, "Строка длиннее чем %d\n", WIDTH );
  shift = 0;
     }
     /* Печать результата */
     for( i=0; i < shift; i++ ) putchar( ' ' );

     while( head <= tail ) putchar( *head++ );
     putchar( '\n' );
 }
}
.
 /*      Пример 4      */
/* Предварительная разметка текста для nroff */
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>  /* прототип strchr() */
#include <locale.h>
FILE *fout = stdout; /* канал вывода      */

/* Состояния вывода */
#define SPACE   0       /* пробелы          */
#define TEXT    1       /* текст            */
#define PUNCT   2       /* знаки препинания */

#define UC(c)   ((unsigned char)(c))

/* Вывод строки текста из буфера */
void putstr (FILE *fp, unsigned char *s) {
/* Punct -  знаки препинания, требующие приклеивания к
 *          концу предыдущего слова.
 * PunctS - знаки, всегда требующие после себя пробела.
 * PunctN - знаки, которые могут следовать за знаком
 *          препинания без пробела.
 */
    static char Punct [] = ",:;!?.)"      ;
    static char PunctS[] = ",:;"          ;
    static char PunctN[] = " \t\"'"       ;
#define is(c, set) (strchr(set, UC(c)) != NULL)
    int c, state = TEXT, cprev = 'X';

    while ((c = *s) != '\0') {
    /*  Пробелы */
 if(isspace(c)) state = SPACE;

    /*  Знаки препинания. Пробелы перед ними игнорируются.
     */ else if(is(c, Punct)){
   switch(state){
   case SPACE: if(is(cprev, Punct ) && cprev==c && c != ')')
    putc(' ', fp);
   /* а просто пробелы - игнорировать */            break;
   case PUNCT: if(is(cprev, PunctS)) putc(' ', fp); break;
   }
   putc(cprev = c, fp); /* выводим сам знак */
   state = PUNCT;
 } else {
    /*  Несколько пробелов сворачиваем в один */
   switch(state){
   case SPACE: putc(' ', fp); break;
   case PUNCT: if(!is(c, PunctN)) putc(' ', fp); break;
   }
   putc(cprev = c, fp); /* сама буква */
   state = TEXT;
   if(c == '\\') putc('e', fp);
 }
 s++;
    } /* пробелы в конце строки просто игнорируются */
    putc ('\n', fp);
}
/* Обработать файл с именем name */
void proceed (char *name) {
    FILE *fp;
    static unsigned char inp[2048];
    /* достаточно большой буфер ввода */

    if      (strcmp(name, "-") == 0 ) fp = stdin;
    else if ((fp = fopen (name, "r")) == NULL) {
 fprintf (stderr, "Cannot read %s\n", name);
 return;
    }
    while (fgets (inp, sizeof inp, fp) != NULL) {
 register unsigned char  *s, *p;
 int len = strlen (inp);
 if (len && inp[len - 1] == '\n')
     inp[--len]   =  '\0';
 if (!*inp) {
 /* .sp N  - пропуск N пустых строк */
space:      fprintf (fout, ".sp 1\n");
     continue;
 }

    /* обрезать концевые пробелы */
 for(p = NULL, s = inp; *s; ++s){
     if (!isspace (*s)) p = s;
 }
 if(p) p[1] = '\0';
 else goto space;
    /* p указывает на последний непробел */

/* Удалить переносы слов в конце строки: перенос - это
   минус, прижатый к концу слова         */
 if (*p == '-' && p != inp /* не в начале строки */
        && isalnum(UC(p[-1])) /* после буквы  */
 ){  int c;  *p = '\0'; /* затереть перенос */
/* Читаем продолжение слова из начала следующей строки */
     while (isspace (c = getc (fp)));
     ungetc (c, fp);
     while ((c = getc (fp)) != '\n' && !isspace (c))
  *p++ = c;
     *p = '\0';
     if (c != '\n' ){ /* прочли пробел */
     /* вычитываем ВСЕ пробелы */
        while (isspace(c = getc (fp)));
        if(c != '\n') ungetc (c, fp);
     }
 }
 /* .pp - директива начала абзаца. */
 if (isspace (*inp)) {
     fprintf (fout, ".pp\n");
     for (s = inp; isspace (*s); s++);
     putstr (fout, s);
 }
 else {
     if (*inp == '.' || *inp == '\'')
  fprintf (fout, "\\&");
     putstr (fout, inp);
 }
    }
    if( fp != stdin ) fclose (fp);
}

int main (int argc, char *argv[]) {
    int  i;
    setlocale(LC_ALL, "");
    for (i = 1; i < argc; i++)
 proceed (argv[i]);
    return 0; /* exit code */
}
.
 /*      Пример 5      */

/* Программа, распечатывающая слова в строках файла в обратном порядке */

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <locale.h>
#define MAXL 255        /* макс. длина строки */

/* Если бы мы не включили ctype.h, то мы должны были бы определить
 * #define isspace(c) ((c) == ' ' || (c) == '\t' || (c) == '\f')
 */
main ( argc, argv )  char **argv;{
 setlocale(LC_ALL, "");
 if( argc == 1 ){
  /* программа вызвана без аргументов */
  munch( "" );
 }else{
      /* аргументы программы - имена файлов */
      while( argv[ 1 ] ){
     munch( argv[1] );
     argv++;
     argc--;
      }
 }
 total(); exit(0);
}

/* обработать файл с именем name */
munch( name ) char *name;
{
 char    l[MAXL];   /* буфер для очередной строки */
 int len;           /* длина этой строки */
 char *words[50];   /* таблица полей строки */
 char **s;          /* служебная */
 int nwords;        /* число слов в строке */

 FILE *fp;

 if( name == NULL || !*name )
        fp = stdin;   /* стандартный ввод */
 else
   if( (fp = fopen( name, "r" )) == NULL ){
  fprintf( stderr, "Не могу открыть файл %s\n",
    name );
  return;
   }

 printf( "----------------------------%s----\n", name );
 while( fgets( l, MAXL, fp ) !=  NULL ){
  len = strlen( l );
  if( len && l[len-1] == '\n' )
      l[--len]  = '\0' ;

  if( nwords = parse( l, words)){
   /* распечатка слов в обратном порядке */
   for( --nwords; nwords >= 0; nwords-- ){
    printf( "%s ", words[ nwords] );
    add( words[ nwords ] );
   }
  }
  putchar ('\n');
 }
 if( fp != stdin ) fclose( fp );
}

/* разобрать строку на слова */
parse( s, tabl )
       register unsigned char *s;
       unsigned char *tabl[];
{
 char eol = 0;
 int nwords = 0;

 while ( !eol ){

  /* пропустить пробелы и табуляции */
  while(isspace(*s)) s++;

  if( !*s ) /* строка кончилась */
   break;

  *tabl++ = s; nwords++;
  /* начало очередного слова */

  /* пока не пробел и не конец строки */
  while( *s && !isspace(*s))s++;

  /* указатель стоит на символе,  следующем за словом */
  if( ! *s ) eol ++;

  *s = '\0';
  /* закрыли Слово, начинаем Дело */
  s++;
 }

 *tabl = NULL;
 return nwords;
}

/* построение таблицы слов, встречающихся в файле */
#define MAXWORDS 1024

struct W{
 int ctr;        /* число вхождений слова */
 char *wrd;      /* слово */
}w [MAXWORDS];          /* таблица */
int busy = 0 ;          /* занято в таблице */

extern char *malloc();

/* Добавить слово в таблицу */
add( word ) char *word;
{
 register i;
 static alert = 1;

 /* нет ли уже слова в таблице ? */
 /* если есть - просто увеличить счетчик */
 for( i = 0; i < busy ; i++ ){
  if( !strcmp( word, w[i].wrd )){
   w[i].ctr++;
   return;
  }
 }

 if( busy >= MAXWORDS ){
  if( alert ){
   fprintf( stderr, "Переполнение таблицы слов\7\n");
   alert = 0;
  }
  return;
 }

 /* нет, слова нет. Заносим: */
 w[busy].wrd = malloc( strlen( word ) + 1 );
         /* 1 байт под символ \0 */

 if( w[busy].wrd == NULL ){
  fprintf( stderr, "Мало памяти\n");

  busy = MAXWORDS+1;  /* якобы переполнение */
  return;
 }
 w[busy].ctr = 1;
 strcpy( w[busy].wrd, word );
 busy++;
}

compare( a, b ) struct W *a, *b;
{
 return strcoll( a-> wrd, b-> wrd );
 /* strcoll сравнивает слова в алфавитном порядке */
}

/* выдача всех слов, встреченных в тексте, и числа их вхождений */
total(){
 register i;

 /* сортируем слова по алфавиту */
 qsort( w, busy, sizeof(struct W), compare );
 printf( "-----|-----------ИТОГ---------------\n");

 for( i=0; i < busy; i++ )
  printf( "%4d | %s\n",
   w[i].ctr,
   w[i].wrd
  );
}
.
 /*      Пример 6         */

/* Сортировка букв в строке методом "пузырька" (bubble sort) */
#define YES 1
#define NO  0

bsort(s) char *s;
{
    register i;          /* индекс сравниваемой буквы */
    register need = YES; /* надо ли продолжать сортировку ? */

    while( need ){
 need = NO;       /* не надо */

 for(i=0; s[i+1]; i++ )
   /* условие цикла: мы сравниваем i-ую и i+1-ую буквы,
    * поэтому и проверяем наличие i+1ой буквы
    */
    if( s[i] > s[i+1] ){ /* в неверном порядке */
  swap( &s[i], &s[i+1] ); /* переставить */
  need = YES; /* что-то изменилось: надо будет
        * повторить просмотр массива букв */
    }
    }
}

/* А вот вариант сортировки, написанный с указателями */
bpsort(s) char *s;
{
 register char *p; register need = YES;

 while( need ){
  need = NO;
  for( p = s; p[1] != '\0' ; p++ )
      if( *p > *(p+1) ){
   swap( p, p+1 ); need = YES;
      }
 }
}

/* обмен двух букв, находящихся по адресам s1 и s2 */
swap( s1, s2 ) register char *s1, *s2;
{
 char tmp;  /* temporary */
 tmp = *s1; *s1 = *s2; *s2 = tmp;
}

char sample1[] = "Homo homini lupus est - ergo bibamus!";
char sample2[ sizeof sample1 ]; /* массив такого же размера */
main(){
 strcpy( sample2, sample1 );  /* скопировать */
 bsort ( sample1 ); printf( "%s\n", sample1 );
 bpsort( sample2 ); printf( "%s\n", sample2 );
}
.
 /*      Пример 7     */
/* Работа с хэш-таблицей. Часть функций написана так, чтобы
 * быть независимой от типов ключа и значения и легко
 * подвергаться модификации.
 */
#include <stdio.h>
#include <string.h>     /* prototype for strchr() */
extern void *malloc(unsigned size);
/* типы ключа и значения: в нашем случае это строки */
typedef unsigned char uchar;
typedef uchar *VAL; typedef uchar *KEY;

/* Для использования следует реализовать операции
int  HASHFUNC(KEY); int  EQKEY(KEY, KEY);
void FREEVAL(VAL);  void SETVAL(VAL, VAL);
void FREEKEY(KEY);  void SETKEY(KEY, KEY);
*/
#define HASHSIZE 21     /* размер таблицы: очень хорошо 2**n */

uchar *strudup(const uchar *s){ /* создание копии строки в "куче" */
  uchar *p =  (uchar *) malloc(strlen(s)+1); strcpy(p, s); return p;
}
/* одна из возможных хэш-функций */
unsigned int hash; /* последнее вычисленное значение хэш-функции */
int HASHFUNC(KEY key){
 unsigned int i = 0; uchar *keysrc = key;
 while(*key){
   i = (i << 1)|(i >> 15); /* ROL */
   i ^= *key++;
 }
 hash = i % HASHSIZE;
 printf( "hash(%s)=%d\n", keysrc, hash);  /* отладка */
 return hash;
}
#define EQKEY(s1, s2)   (strcmp(s1, s2) == 0)
#define FREEKEY(s)      free(s)
#define FREEVAL(s)      free(s)
#define SETVAL(at,s)    at = strudup(s)
#define SETKEY(at,s)    at = strudup(s)
#define KEYFMT          "%s"
#define VALFMT          "%s"

/* ================== типо-независимая часть ================= */
struct cell {
 struct cell *next; /* ссылка на очередной элемент */
 KEY key;           /* ключ     */
 VAL val;           /* значение */
} *hashtable[ HASHSIZE ];  /* хэш-таблица */

/* получение значения по ключу */
struct cell *get(KEY key){
 struct cell *p;
 for(p = hashtable[HASHFUNC(key)]; p; p = p->next)
  if(EQKEY(p->key, key))
   return p;
 return NULL;    /* отсутствует */
}

/* занести пару ключ:значение в таблицу */
void set(KEY key, VAL val){
 struct cell *p;

 /* проверить - не было ли звена с таким ключом */
 if((p = get(key)) == NULL){       /* не было   */
     if(!(p = (struct cell *) malloc(sizeof(*p)))) return;
     SETKEY(p->key, key);
     p->next = hashtable[hash]; /* hash вычислено в get() */
     hashtable[hash] = p;
 } else /* уже было: изменить значение */
     FREEVAL(p->val);
 SETVAL(p->val, val);
}

/* удаление по ключу */
int del(KEY key){
 int indx = HASHFUNC(key);
 struct cell *p, *prev = NULL;

 if((p = hashtable[indx]) == NULL) return 0;
 for( ;p ;prev = p, p=p->next)
  if(EQKEY(p->key, key)){
      FREEVAL(p->val); FREEKEY(p->key);
      if( p == hashtable[indx] ) /* голова списка */
        hashtable[indx] = p->next;
      else     prev->next = p->next;
      free((void *) p ); return 1; /* удален */
  }
 return 0;  /* не было такого */
}

/* распечатать пару ключ:значение */
void printcell(struct cell *ptr){
 putchar('(');
 printf( KEYFMT, ptr->key ); putchar(',');
 printf( VALFMT, ptr->val ); putchar(')');
}

/* распечатка таблицы (для отладки) */
void printtable(){
  register i; struct cell *p;
  printf("----TABLE CONTENTS----\n");
  for(i=0; i < HASHSIZE; i++)
      if((p = hashtable[i]) != NULL){
   printf( "%d: ", i);
   for(; p; p=p->next)
      printcell(p), putchar(' ');
   putchar('\n');
      }
}

/* итератор */
struct celliter {
 int index; struct cell *ptr;
};
/* выдать очередное значение */
struct cell *nextpair(struct celliter *ci){
 struct cell *result;
 while((result = ci->ptr) == NULL){
  if( ++(ci->index) >= HASHSIZE )
   return NULL;    /* больше нет */
  ci->ptr = hashtable[ci->index];
 }
 ci->ptr = result->next; return result;
}
/* инициализация итератора */
struct cell *resetiter(struct celliter *ci){
 ci->index = (-1); ci->ptr = NULL;
 return nextpair(ci);  /* первое значение */
}
/* =========================================================== */

void main(){ /* таблица из имен и размеров файлов текущего каталога */
 struct celliter ci; struct cell *cl;
 char key[40], value[40]; struct cell *val;
 extern FILE *popen();    FILE *fp;     char *s ;

 /* popen() читает вывод команды, заданной в 1-ом аргументе */
 fp = popen( "ls -s", "r" );
 while( fscanf( fp, "%s%s", value, key) == 2 )
 set(key, value);
 pclose(fp);  /* popen() надо закрывать pclose(); */

 for(;;){
 printf( "-> " );  /* приглашение */
 if( !gets( key )) break;   /* EOF */
 if( *key == '-' ){         /* -КЛЮЧ          :удалить     */
  printf( del( key+1 ) ? "OK\n" : "нет такого\n");
  continue;
 }
 if( !*key || !strcmp(key, "=")){ /* = :распечатать таблицу*/
  printtable();    continue;
 }
 if(s = strchr(key, '=')){ /* КЛЮЧ=ЗНАЧЕНИЕ  :добавить     */
  *s++ = '\0';
  set(key, s); continue;
 }
 if((val = get( key )) == NULL) /* КЛЮЧ :найти значение */
      printf( "нет такого ключа\n");
 else{ printf( "значение "); printf(VALFMT, val->val);
       putchar('\n');
 }
 }
 /* распечатка таблицы при помощи итератора */
 for( cl = resetiter(&ci) ; cl ; cl = nextpair(&ci))
 printcell(cl), putchar('\n');
}
.
 /*      Пример 8     */

/* Пример маленькой базы данных.
 * Данные хранятся БЕЗ дубликатов.
 * Надо заметить, что используется плохой (неэффективный)
 * алгоритм доступа - линейный поиск.
 */
#include <stdio.h>

/* Все записи в базе имеют фиксированный размер */
#define VLEN 20
#define KEY_FREE (-13)   /* ключ свободного места. Он выбран
произвольно, но не должен встречаться в качестве входных данных */

struct data{
 short b_key;            /* ключ */
 char  b_val[VLEN];      /* строка-значение */
};

char  BASEF[] = ".base" ;       /* имя файла базы */
FILE *fbase;                    /* pointer на базу */
struct data tmp;                /* вспомогательная переменная */

void
initBase (void){
 /* fopen: r  read  (чтение)
  *        w  write (запись), файл пересоздается.
  * (создается, если не было, если был - опустошается).
  *        r+ чтение и запись (файл уже существует).
  *        w+ чтение и запись (создается пустой файл).
  *        a  append (запись в конец файла), создать если нет:
  *           имеется в виду, что КАЖДАЯ операция записи сначала
  *           ставит указатель записи на конец файла.
  * В MS DOS нетекстовый файл НЕОБХОДИМО открывать как
  *        rb wb rb+ wb+ ab+  иначе ничего не будет работать.
  */
 if(( fbase = fopen( BASEF, "r+" )) == NULL ){
  if(( fbase = fopen( BASEF, "w+" )) == NULL ){
       fprintf( stderr, "Не могу открыть базу данных %s\n",
         BASEF );
       exit(1);
  }
  fprintf( stderr, "База создана\n" );
 }
}

void
closeBase (void){
 fclose( fbase );
}
/* Учтите, что если вы записываете в файл структуры, то в файле
не будет разделения на строки - файл НЕТЕКСТОВЫЙ! Поэтому и
читать такой файл можно только структурами: read(), fread()
(но не scanf-ом и не fgets-ом)
 */
.
/* Поиск по ключу .
   Выдать (-1), если записи с данным ключом нет,
   иначе   - номер слота, где содержится запись с данным ключом.
 */
int
bget (int key)
{
 int n;

 /* последовательно просмотреть весь файл */
 rewind( fbase );
 /* в начало файла. Равно fseek(fbase, 0L, 0); */

 n = 0 ;
 /* int    сколько_элементов_массива_действительно_считано =
  * fread( адрес_массива_куда_считывать,
  *        размер_одного_элемента_массива,
  *        сколько_элементов_считывать_в_массив, канал );
  * Заметьте, что количество данных задается НЕ в байтах,
  * а в 'штуках'
  */
 while( fread( &tmp, sizeof( tmp ), 1, fbase ) == 1 ){
  if( tmp.b_key == key )
   return n;
  n++;
 }
 return (-1);    /* не найдено */
}

/* модифицировать запись с индексом ind */
void
bmod (
    int ind,
    int key,       /* новый ключ */
    char *val      /* новое значение */
)
{
 struct data new;

 fseek( fbase, (long) sizeof( struct data ) * ind, 0 );
 new.b_key = key;
 strncpy( new.b_val, val, VLEN );
 /* int    сколько_элементов_массива_действительно_записано =
  * fwrite( адрес_массива_который_записывать,
  *         размер_одного_элемента_массива,
  *         сколько_элементов_массива_записывать, канал );
  */
 if( fwrite( &new, sizeof new , 1, fbase ) != 1 )
     fprintf( stderr, "Ошибка записи.\n" );
}

/* удаление записи по ключу */
int
bdel (int key){
 int ind = bget( key );
 if( ind == -1 )
  return (-1);        /* записи с таким ключом нет */
 bmod( ind, KEY_FREE, "" );  /* записать признак свободного места */
 return 0;
}

/* Служебная процедура дописи к концу файла */
void
bappend (int key, char *val)
{
  struct data new;

  /* встать на конец файла */
  fseek( fbase, 0L, 2 );

  /* и записать новую структуру в конец */
  new.b_key = key;
  strncpy( new.b_val, val, VLEN );
  fwrite( &new, sizeof( struct data ) , 1, fbase );
}

/* добавление новой записи. Если запись с таким ключом уже есть -
   выдать ошибку
 */
int
bput (int key, char *val)
{
 int i = bget( key );
 if( i != -1 )
  return (-1);    /* запись уже есть */

 /* найти свободное место */
 i = bget( KEY_FREE );
 if( i == -1 ) {         /* нет свободных мест */
  bappend( key, val );
  return 0;
 }
 /* иначе свободное место найдено.
  * Заменяем дырку на полезную информацию */
 bmod( i, key, val );
}

/* распечатать всю базу данных подряд */
void
bprint (void){
 int n;
 int here = 0;

 rewind( fbase );
 n = 0;
 printf( "-номер--ключ-------значение-----------------\n" );
 while( fread( &tmp, sizeof tmp, 1, fbase ) == 1 ){
  if( tmp.b_key == KEY_FREE ){
   n++;
   continue;
  }
  printf( "#%-2d| %6d\t| %s\n", n, tmp.b_key, tmp.b_val );
  here ++; n++;
 }
 printf( "--------------------------------------------\n" );
 printf( "Длина базы:%d Занято:%d\n\n", n, here );
}

/* замена поля val у записи с ключом key */
int
bchange (int key, char *val)
{
 int ind;

 ind = bget( key );
 if( ind == -1 ){
  /* запись с таким ключом не существует */
  /* Добавить как новую запись */
  bput( key, val );
  return 0;
 }
 bmod( ind, key, val );
 return 1;
}

/* Аналогичная функция, но использующая другой способ.
 * Кроме того, если такой ключ отсутствует - ничего не делается
 */
int
bchg (int key, char *val)
{
 struct data d;

 rewind( fbase );        /* в начало файла */
 while( fread( &d, sizeof d, 1, fbase ) == 1 ){
  /* поиск ключа */
  if( d.b_key == key ){
   /* вернуться назад от текущей позиции */
   fseek( fbase, - (long) sizeof d, 1 );
   /* не годится   (long)-sizeof d !!! */

   d.b_key = key;
   strncpy( d.b_val, val, VLEN );
   fwrite( &d, sizeof d, 1, fbase );

   /* между fread и fwrite должен быть
    * хоть один fseek. (магическое заклинание!)
    */
   fseek( fbase, 0L, 1);  /* никуда не сдвигаться */
   return 0;              /* сделано */
  }
 }
 return (-1);    /* такого ключа не было */
}

/* Пример */
void
main (void){
 int i;

 initBase();
 bprint();
 bdel( 8 );

 printf( "Создаем базу данных\n" );
 bput( 1, "строка 1" );
 bput( 2, "строка 2" );
 bput( 3, "строка 3" );
 bput( 4, "строка 4" );
 bprint();

 printf( "Удаляем записи с ключами 1 и 3\n" );
 bdel( 1 );
 bdel( 3 );
 bprint();

 printf( "Добавляем записи 5, 6 и 7\n" );
 bput( 5, "строка 5" );
 bput( 6, "строка 6" );
 bput( 7, "строка 7" );
 bprint();

 printf( "Заменяем строку в записи с ключом 2\n" );
 bchange( 2, "новая строка 2" );
 bprint();

 printf( "Заменяем строку в записи с ключом 4\n" );
 bchg( 4, "новая строка 4" );
 bprint();

 printf( "Заменяем строку в записи с ключом 6 и ключ 6 на 8\n" );
 i = bget( 6 );
 printf( "Сейчас запись с ключом 6 содержит \"%s\"\n",
  tmp.b_val );
 bmod( i, 8, "Новая строка 6/8" );
 bprint();

 closeBase();
}
.
 /*      Пример 9       */
/* Вставка/удаление строк в файл */
#include <stdio.h>

#define INSERT_BEFORE 1  /* Вставить строку перед указанной */
#define INSERT_AFTER  2  /* Вставить строку после указанной */
#define DELETE        3  /* Удалить строку  */
#define REPLACE       4  /* Заменить строку */

/* К каждой строке linenum должно относиться не более 1 операции !!! */
struct lineop {
    char    op;   /* Операция                     */
    long    linenum;  /* Номер строки в файле (с 0)   */
    char   *str;  /* Строка (или NULL для DELETE) */
};

long lineno;                          /* номер текущей строки */
int fileChange (char *name,           /* имя файла */
  struct lineop ops[],  /* задание   */
  int nops              /* число элементов в массиве ops[] */
){
    FILE     *fin, *fout;
    static   char   TMPNAME[] = "  ?  ";
    char     buffer[BUFSIZ];
    register i;
    struct   lineop tmpop;

    if ((fin = fopen (name, "r")) == NULL)
  return (-1);
    if ((fout = fopen (TMPNAME, "w")) == NULL) {
  fclose (fin); return (-1);
    }
    lineno = 0L;
    while (fgets (buffer, BUFSIZ, fin) != NULL) {
 if( nops ) for (i = 0; i < nops; i++)
     if (lineno == ops[i].linenum) {
  switch (ops[i].op) {
      case DELETE: /* удалить */
   break;
      case INSERT_BEFORE: /* вставить перед */
   fprintf (fout, "%s\n", ops[i].str);
   fputs (buffer, fout);
   break;
      case INSERT_AFTER: /* вставить после */
   fputs (buffer, fout);
   fprintf (fout, "%s\n", ops[i].str);
   break;
      case REPLACE: /* заменить */
   fprintf (fout, "%s\n", ops[i].str);
   break;
  }
    /* переставить выполненную операцию в конец массива и забыть */
  tmpop = ops[nops-1]; ops[nops-1] = ops[i]; ops[i] = tmpop;
  nops--; goto next;
     }
    /* иначе строка не числится в массиве ops[] : скопировать */
 fputs (buffer, fout);
next:
 lineno++;
    }
    fclose (fin); fclose (fout); rename (TMPNAME, name);
    return nops;  /* число несделанных операций (0 - все сделано) */
}

struct lineop myops[] = {
 { DELETE,         2L,     NULL                 },
 { INSERT_BEFORE,  0L,     "inserted before 0"  },
 { INSERT_BEFORE,  10L,    "inserted before 10" },
 { INSERT_AFTER,   5L,     "inserted after 5"   },
 { DELETE,         6L,     NULL                 },
 { INSERT_AFTER,   8L,     "inserted after 8"   },
 { INSERT_AFTER,   12L,    "inserted after 12"  },
 { REPLACE,        3L,     "3 replaced"         }
};

void main( void ){
  int n;
  n = fileChange( "aFile", myops, sizeof(myops)/sizeof(struct lineop));
  printf( "Строк в файле: %ld; осталось операций: %d\n", lineno, n);
}
/*
исходный файл            получившийся файл
line 0                   inserted before 0
line 1                   line 0
line 2                   line 1
line 3                   3 replaced
line 4                   line 4
line 5                   line 5
line 6                   inserted after 5
line 7                   line 7
line 8                   line 8
line 9                   inserted after 8
line 10                  line 9
    inserted before 10
    line 10
  Строк в файле: 11; осталось операций: 1
*/
.
 /* Пример 10 */

/* Проблема: позволить делать вызов free(ptr)
 * на данные, не отводившиеся malloc()-ом.
 * Решение: вести список всех данных,
 * отведенных malloc()ом.
 * Возможно также отслеживание диапазона адресов,
 * но последнее является машинно-зависимым решением.
 *
 * При большом количестве файлов эта программа - неплохой тест
 * производительности машины!
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct _cell {
 void *addr;
 struct _cell *next;
} Cell;

typedef struct _entry {
 int length;
 int used;
 Cell *list;
} Entry;

/* Хэшированная таблица */
#define NENTRIES 64
Entry aTab[NENTRIES];

/* Хэш-функция от адреса */
int aHash(void *addr){
 unsigned long x = (unsigned long) addr;
 x >>= 3;        /* деление на 8, так как адреса из malloc()
      обычно четные,
      поскольку выровнены на границу double */
 return(x % NENTRIES);
 /* Тут к месту напомнить, что вычисление остатка от деления на степень двойки
  * можно соптимизировать:
  *   x % (2**N) = x & 0b0001.....1  (N двоичных единиц)
  * К примеру, x % 64 = x & 0x3F;    (6-ая степень двойки)
  */
}

/* Выделить память, записать адрес в таблицу */
void *aCalloc(int n, int m){
 void *ptr = calloc(n, m);
 Entry *ep = &aTab[ aHash(ptr) ];
 Cell *p;

 for(p=ep->list; p; p=p->next)
  if(p->addr == NULL){
  /* Свободная ячейка: переиспользовать */
   p->addr = ptr;
   ep->used++;
   return ptr;
  }
 /* Нет свободных, завести новую */
 p = (Cell *) calloc(1, sizeof(Cell));
 p->addr = ptr;
 p->next = ep->list;
 ep->list = p;
 ep->length++;
 ep->used++;
 return ptr;
}

/* Освободить память */
int aFree(void *ptr){
 Entry *ep = &aTab[ aHash(ptr) ];
 Cell *p;

 for(p=ep->list; p; p=p->next)
  if(p->addr == ptr){
   free(ptr);
   p->addr = NULL;
   /* Ячейка не удаляется, но метится как свободная */
   ep->used--;
   return 1;
  }
 /* Нет, такой указатель не отводился.
  * Не делать free()
  */
 return 0;
}

/* Выдать статистику об использовании хэша */
void aStat(){
 int i;
 int len_all;
 int used_all;

 for(i=len_all=used_all=0; i < NENTRIES; i++){
  len_all  += aTab[i].length;
  used_all += aTab[i].used;

  printf("%d/%d%s", aTab[i].used, aTab[i].length,
         i==NENTRIES-1 ? "\n":" ");
 }
 printf("%d/%d=%g%%\n",
  used_all, len_all,
  (double)used_all * 100 / len_all);
}

/* ТЕСТ =================================================================*/

Cell *text;

/* Прочитать файл в память */
void fileIn(char *name){
 char buf[10000];
 FILE *fp;

 if((fp = fopen(name, "r")) == NULL){
  printf("Cannot read %s\n", name);
  return;
 }
 while(fgets(buf, sizeof buf, fp) != NULL){
  char *s;
  Cell *p;

  s = (char *) aCalloc(1, strlen(buf)+1);
  strcpy(s, buf);

  p = (Cell *) aCalloc(sizeof(Cell), 1);
  p->addr = s;
  p->next = text;
  text = p;
 }
 fclose(fp);
}

/* Уничтожить текст в памяти */
void killAll(){
 Cell *ptr, *nxtp;

 ptr = text;
 while(ptr){
  nxtp = ptr->next;
  if(!aFree(ptr->addr)) printf("No free(1)\n");
  if(!aFree(ptr))       printf("No free(2)\n");
  ptr = nxtp;
 }
}

/* Удалить из текста строки, начинающиеся с определенной буквы */
void randomKill(int *deleted){
 unsigned char c = rand() % 256;
 Cell *ptr, *prevp;
 unsigned char *s;

retry:
 prevp = NULL; ptr = text;
 while(ptr){
  s = (unsigned char *) ptr->addr;
  if(*s == c){    /* нашел */
   if(!aFree(s)) printf("No free(3)\n");

   /* исключить из списка */
   if(prevp) prevp->next = ptr->next;
   else      text        = ptr->next;

   if(!aFree(ptr))    printf("No free(4)\n");

   /* Заведомо неправильный free
   if(!aFree(ptr+1))  printf("No free(5)\n");
   */

   (*deleted)++;

   goto retry;
  }
  prevp = ptr;
  ptr = ptr->next;
 }
}

int main(int ac, char *av[]){
 int i, r, d;
 char buffer[4098];

 srand(time(NULL));
 for(i=1; i < ac; i++){
  printf("File: %s\n", av[i]);
  fileIn(av[i]);
  aStat();

  d = 0;
  for(r=0; r < 128; r++) randomKill(&d);
  printf("%d lines deleted\n", d);
  aStat();
 }
 killAll();
 aStat();

 if(!aFree(buffer))
  printf("buffer[] - не динамическая переменная.\n");

 return 0;
}
.
 /* Пример 11 */

/* Пакет для ловли наездов областей выделенной памяти
 * друг на друга,
 * а также просто повреждений динамически отведенной памяти.
 */
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>      /* O_RDWR */
#include <sys/types.h>
#include <ctype.h>
#include <locale.h>

#define CHECKALL
/*
 ----------------- <--------- ptr
 | red_zone      | головная "пограничная зона"
 -----------------
 | byte[0]       |
 |     ...       |
 | byte[size-1]  |
 | placeholder   |
 ----------------- выровнено на границу RedZoneType
 | red_zone      | хвостовая "пограничная зона"
 -----------------

Основные идеи состоят в следующем:
1) Перед и после области данных строится зона,
   заполненная заранее известным "узором".
   Если ее содержимое изменилось, испорчено -
   значит мы где-то разрушили нашу память.
2) Ведется таблица всех отведенных malloc()-ом сегментов памяти;
   для экономии места эта таблица вынесена в файл (но зато это
   очень медленно).
3) Мы не можем пользоваться библиотекой STDIO для обменов с файлом,
   потому что эта библиотека сама использует malloc() и буфера
   могут быть разрушены.
*/

typedef char *RedZoneType;      /* выравнивание на границу указателя */
/* Можно выравнивать на границу double:
typedef double RedZoneType;
 */

/* Сегмент, выделяемый в оперативной памяти */
typedef struct _allocFrame {
 RedZoneType red_zone;   /* головная "пограничная зона"            */
 RedZoneType stuff[1];   /* место для данных                       */
    /* хвостовая "пограничная зона" безымянна */
} AllocFrame;

const int RedZoneTypeSize = sizeof(RedZoneType);

/* Запись, помещаемая в таблицу всех выделенных malloc()ом
 * областей памяти.
 */
typedef struct _memFileRecord {
 AllocFrame *ptr;        /* адрес                                 */
 size_t size, adjsize;   /* размер выделенной области             */
    /* (0,0) - означает "сегмент освобожден" */
 int serial;
} MemFileRecord;

char red_table[] = {
 0x01, 0x03, 0x02, 0x04,
 0x11, 0x13, 0x12, 0x14,
 0x21, 0x23, 0x22, 0x24,
 0x31, 0x33, 0x32, 0x34
};
char free_table[] = {
 'F', 'r', 'e', 'e', 'p', 't', 'r', '\0',
 'F', 'r', 'e', 'e', 'p', 't', 'r', '\0'
};

/* Файл для хранения таблицы указателей */
static  int mem_fd = (-1);
#define PTABLE "PointerTable.bin"

#define NRECORDS 256
MemFileRecord memrecords[NRECORDS];
/* ============================================================= */
void  MEMputTableRecord(AllocFrame *newptr, AllocFrame *oldptr,
   size_t size, size_t adjsize);
void  MEMputTableRecordKilled(AllocFrame *ptr);
void  MEMerasePreviousRecords(AllocFrame *ptr);
int   MEMcheckRecord(MemFileRecord *rec);
int   MEMcheck_consistency(AllocFrame *ptr);
void  MEMmakeRedZones(char *cptr, size_t size, size_t adjsize);
void  MEMopenFd();
/* ============================================================= */
/* Этим следует пользоваться вместо стандартных функций          */
void *MEMmalloc (size_t size);
void *MEMrealloc(void *ptr, size_t size);
void *MEMcalloc (size_t n,  size_t size);
void  MEMfree   (void *ptr);

void  MEMcheckAll();  /* это можно вызывать в середине программы */
/* ============================================================= */
void MEMopenFd(){
 if(mem_fd < 0){
  close(creat(PTABLE, 0644));     /* создать файл */
  mem_fd = open(PTABLE, O_RDWR);  /* чтение+запись */
  unlink(PTABLE);                 /* только для M_UNIX */

  atexit(MEMcheckAll);
  setlocale(LC_ALL, "");
 }
}

/* Поместить запись в таблицу всех указателей на
 * выделенные области памяти.
 */
void MEMputTableRecord(AllocFrame *newptr, /* для записи */
         AllocFrame *oldptr, /* для стирания */
         size_t size,        /* размер данных */
         size_t adjsize      /* размер всей записи с зонами */
){
 MemFileRecord memrecord;
 static int serial = 0;

 memrecord.ptr     = newptr;
 memrecord.size    = size;
 memrecord.adjsize = adjsize;
 memrecord.serial  = serial++;

 MEMopenFd();
#ifdef CHECKALL
 /* стереть прежние записи про этот адрес */
 MEMerasePreviousRecords(oldptr);
#endif
 lseek(mem_fd, 0L, SEEK_END);                    /* в конец */
 write(mem_fd, &memrecord, sizeof memrecord);    /* добавить */
}

/* Сделать запись об уничтожении области памяти */
void  MEMputTableRecordKilled(AllocFrame *ptr){
 /* Пометить как size=0, adjsize=0 */
 MEMputTableRecord(ptr, ptr, 0, 0);
}

/* Коды ответа функции проверки */
#define OK      0       /* все хорошо                 */
#define DAMAGED 1       /* повреждена "погранзона"    */
#define FREED   2       /* эта память уже освобождена */
#define NOTHERE (-1)    /* нет в таблице              */

/* Проверить сохранность "пограничных зон" */
int MEMcheckRecord(MemFileRecord *rec){
 int code = OK;
 char *cptr;
 register i;
 AllocFrame *ptr        = rec->ptr;
 size_t size            = rec->size;
 size_t adjsize         = rec->adjsize;

 if(size == 0 && adjsize == 0){
  printf("%p [%p] -- сегмент уже освобожден, "
         "record=#%d.\n",
   &ptr->stuff[0], ptr,
   rec->serial
  );
  return FREED;
 }
 cptr    = (char *) ptr;
 for(i=0; i < adjsize; i++){
     if(i <  RedZoneTypeSize || i >= RedZoneTypeSize + size ){
  /* головная погранзона ИЛИ хвостовая погранзона */
  if( cptr[i] != red_table[ i % RedZoneTypeSize ] ){
     printf("%p [%p] -- испорчен байт %4d [%4d]"
     "= 0x%02X '%c' record=#%d size=%lu.\n",
      &ptr->stuff[0], ptr,
      i - RedZoneTypeSize, i,
      cptr[i] & 0xFF,
      isprint(cptr[i] & 0xFF) ? cptr[i] & 0xFF : '?',
      rec->serial, size
     );
     code = DAMAGED;
  }
     }
 }
 for(i=0; i < RedZoneTypeSize; i++)
  if(cptr[i] == free_table[i]){
   printf("%p -- уже освобождено?\n", ptr);
   code = FREED;
  }
 if(code != OK) putchar('\n');
 return code;
}

/* Проверить сохранность памяти по указателю ptr. */
int MEMcheck_consistency(AllocFrame *ptr){
 MemFileRecord mr_found;
 int nrecords, i, found = 0;
 size_t size;

 MEMopenFd();

 /* Ищем запись в таблице указателей */
 lseek(mem_fd, 0L, SEEK_SET);    /* перемотать в начало */
 for(;;){
  size = read(mem_fd, memrecords, sizeof memrecords);
  nrecords = size / sizeof(memrecords[0]);

  if(nrecords <= 0) break;

  for(i=0; i < nrecords; i++)
   if(memrecords[i].ptr == ptr){
   /* Мы ищем последнюю запись про память
    * с таким адресом, поэтому
    * вынуждены прочитать ВЕСЬ файл.
    */
    mr_found = memrecords[i];
    found++;
   }
 }
 if(found) {
  return MEMcheckRecord(&mr_found);
 } else {
  printf("%p -- запись в таблице отсутствует.\n", ptr);
  return NOTHERE;
 }
}

/* Уничтожить все прежние записи про ptr, прописывая их adjsize=0 */
void MEMerasePreviousRecords(AllocFrame *ptr){
 int nrecords, i, found;
 size_t size;

 MEMopenFd();
 lseek(mem_fd, 0L, SEEK_SET);    /* перемотать в начало */
 for(;;){
  found = 0;
  size = read(mem_fd, memrecords, sizeof memrecords);
  nrecords = size / sizeof(memrecords[0]);

  if(nrecords <= 0) break;

  for(i=0; i < nrecords; i++)
   if(memrecords[i].ptr == ptr){
    memrecords[i].adjsize = 0;
    /* memrecords[i].size = 0; */
    found++;
   }
  if(found){
   lseek(mem_fd, -size, SEEK_CUR);    /* шаг назад */
   write(mem_fd, memrecords, size);   /* перезаписать */
  }
 }
}

void MEMcheckAll(){
#ifdef CHECKALL
 int nrecords, i;
 size_t size;

 printf("Проверка всех указателей -------------\n");
 MEMopenFd();
 lseek(mem_fd, 0L, SEEK_SET);    /* перемотать в начало */
 for(;;){
  size = read(mem_fd, memrecords, sizeof memrecords);
  nrecords = size / sizeof(memrecords[0]);

  if(nrecords <= 0) break;

  for(i=0; i < nrecords; i++)
   if(memrecords[i].adjsize != 0)
    MEMcheckRecord(&memrecords[i]);
 }
 printf("Проверка всех указателей завершена ---\n");
#endif
}

/* ============================================================= */
/* Заполнение пограничных зон образцом - "следовой дорожкой" */
void MEMmakeRedZones(char *cptr, size_t size, size_t adjsize){
 register i;

 for(i=0; i < adjsize; i++){
  if(i <  RedZoneTypeSize || i >= RedZoneTypeSize + size ){
     /* головная погранзона ИЛИ
      * хвостовая погранзона + дополнение
      * до целого числа RedZoneType-ов
      */
   cptr[i] = red_table[ i % RedZoneTypeSize ];
  }
 }
}
/* ============================================================= */
/* Функция выделения памяти */
void *MEMmalloc(size_t size){
 AllocFrame *retptr;
 int fullRedZoneTypes =
  (size + RedZoneTypeSize - 1) / RedZoneTypeSize;
 size_t adjustedSize =
  sizeof(retptr->red_zone) * 2 + /* две погранзоны */
  fullRedZoneTypes * RedZoneTypeSize;

 retptr  = (AllocFrame *) malloc(adjustedSize);
 if(retptr == NULL) return NULL;

 MEMmakeRedZones ((char *) retptr, size, adjustedSize);
 MEMputTableRecord(retptr, retptr, size, adjustedSize);
 return &retptr->stuff[0];
 /* вернуть указатель на зону данных */
}

void *MEMrealloc(void *ptr, size_t size){
 AllocFrame *retptr;
 char *cptr = (char *)ptr - RedZoneTypeSize;  /* прежний AllocFrame */
 AllocFrame *oldptr = (AllocFrame *) cptr;
 int fullRedZoneTypes =
  (size + RedZoneTypeSize - 1) / RedZoneTypeSize;
 size_t adjustedSize =
  sizeof(retptr->red_zone) * 2 +
  fullRedZoneTypes * RedZoneTypeSize;

 /* Проверить сохранность того, что мы сейчас будем realloc-ить */
 MEMcheck_consistency(oldptr);

 retptr  = (AllocFrame *) realloc((void *)oldptr, adjustedSize);
 if(retptr == NULL) return NULL;

 MEMmakeRedZones ((char *) retptr, size, adjustedSize);
 MEMputTableRecord(retptr, oldptr, size, adjustedSize);
 return &retptr->stuff[0];
}

void *MEMcalloc(size_t n, size_t size){
 size_t newsize = n * size;
 void *ptr = MEMmalloc(newsize);
 memset(ptr, '\0', newsize);
 return ptr;
}

/* Очистка отведенной памяти.
 * ptr - это указатель не на AllocFrame,
 * а на данные - то есть на stuff[0].
 */
void MEMfree(void *ptr){
 char *cptr = (char *)ptr - RedZoneTypeSize;
 int i, code;

 code = MEMcheck_consistency((AllocFrame *) cptr);
 for(i=0; i < RedZoneTypeSize; i++)
  cptr[i] = free_table[i];

 if(code != FREED) free((void *) cptr);

 MEMputTableRecordKilled((AllocFrame *) cptr);
}

/* ============================================================= */
/* Тестовый пример                                               */
/* ============================================================= */
#define MAXPTRS 512
char *testtable[MAXPTRS];

/* Сгенерировать строку случайной длины со случайным содержимым */
char *wildstring(int c){
#define N 1024
 char teststring[N + 1];
 int len, i;
 char *ptr;

 len = rand() % N;
 for(i=0; i < len; i++)
  teststring[i] = c;
 teststring[len] = '\0';

 ptr = (char *) MEMmalloc(len + 1);
 if(ptr){
  strcpy(ptr, teststring);
 } else printf("NULL wildstring()\n");

 return ptr;
}

int main(int ac, char *av[]){
 int ilen, len, n, i;

 srand(time(NULL));

 for(n=0; n < MAXPTRS; n++)
  testtable[n] = wildstring('A');

#define DAMAGE (MAXPTRS/3*2-1)
#ifdef DAMAGE
 /* Навести порчу */
 len = strlen(testtable[DAMAGE]);
 testtable[DAMAGE][len+1] = 'x';
 testtable[DAMAGE][-2]    = 'y';
 printf("ptr=%p len=%d\n", testtable[DAMAGE], len);
#endif
 for(n=0; n < MAXPTRS/2; n++){
  char *p = wildstring('B');
  int length = strlen(p);
  char *ptr;

  i = rand() % MAXPTRS;
  /* Не забыть присвоить возвращенное realloc() значение
   * обратно в testtable[i] !!!
   */
  testtable[i] = ptr =
   (char *) MEMrealloc(testtable[i], length + 1);

  if(ptr == NULL) printf("Не могу сделать realloc()\n");
  else            strcpy(ptr, p);

#ifdef DAMAGE
  /* Порча */
  if(n == MAXPTRS/3){
   ptr[length+2] = 'z';
  }
#endif
  MEMfree(p);
 }

 for(n=0; n < MAXPTRS; n++){
  if(testtable[n]) MEMfree(testtable[n]);
 }
#ifdef DAMAGE
 MEMfree(testtable[DAMAGE]);
#endif
 return 0;
}
.
 /*      Пример 12     */

/* Программа, совмещающая команды mv и cp. Иллюстрация работы с файлами.
 * Пример того, как программа может выбирать род работы
 * по своему названию.
 * Компиляция:
 *              cc cpmv.c -o copy ; ln copy move
 * По мотивам книги М.Дансмура и Г.Дейвиса.
 */

#include <stdio.h>              /* буферизованный ввод/вывод */
#include <sys/types.h>          /* системные типы данных */
#include <sys/stat.h>           /* struct stat           */
#include <fcntl.h>              /* O_RDONLY              */
#include <errno.h>              /* системные коды ошибок */

/* #define strrchr rindex           /* для версии ДЕМОС (BSD)    */
extern char *strrchr(char *, char); /* из библиотеки libc.a      */
extern int   errno;
char    MV[] = "move"; char CP[] = "copy";
#define OK      1       /* success - успех   */
#define FAILED  0       /* failure - неудача */
#define YES     OK
#define NO      0

/* Выделить базовое имя файла:
 *     ../wawa/xxx  -->   xxx
 *     zzz          -->   zzz
 *     /            -->   /
 */
char *basename( char *name ){
 char *s      = strrchr( name , '/' );
 return (s    == NULL) ? name : /* нет слэшей       */
        (s[1] == '\0') ? name : /* корневой каталог */
    s + 1;
}
#define ECANTSTAT (-1)  /* файл не существует */
struct ftype {
       unsigned type;  /* тип файла */
       dev_t    dev;   /* код устройства, содержащего файл */
       ino_t    ino;   /* индексный узел файла на этом устройстве */
};
/* Получение типа файла */
struct ftype filetype( char *name /* имя файла */   )
{
 struct stat st; struct ftype f;

 if( stat( name, &st ) < 0 ){
   f.type = ECANTSTAT; f.dev = f.ino = 0;
 } else { f.type = st.st_mode & S_IFMT;
   f.dev  = st.st_dev; f.ino = st.st_ino;
 }
 return f;
}
/* Удаляет файлы, кроме устройств */
int unlinkd( char *name, unsigned type )
{
 if( type == S_IFBLK || type == S_IFCHR || type == S_IFDIR)
  return 0;
 return unlink( name );
}
/* Функция нижнего уровня: копирование информации большими порциями */
int copyfile( int from, int to )
 /* from - дескриптор откуда */
 /* to   - дескриптор куда   */
{
 char buffer[ BUFSIZ ];
 int n; /* число прочитанных байт */

 while(( n = read( from, buffer, BUFSIZ )) > 0 )
 /* read возвращает число прочитанных байт,
  * 0 в конце файла
  */
      if( write( to,  buffer, n ) != n ){
   printf( "Write error.\n" );
   return FAILED;
      }
 return OK;
}
/* Копирование файла */
int docopy(char *src, char *dst, unsigned typefrom, unsigned typeto)
{       int retc; int fdin, fdout;
 printf( "copy %s --> %s\n", src, dst );

 if((fdin = open( src, O_RDONLY )) < 0 ){
  printf( "Сan't read %s\n", src );
  return FAILED;
 }
 if((fdout = creat( dst, 0644 )) < 0 ){  /* rw-r--r-- */
  printf( "Can't create %s\n", dst );
  return FAILED;
 }
 retc = copyfile( fdin, fdout );
 close( fdin ); close( fdout );
 return retc;
}
/* Переименование файла. Вернуть OK, если удачно, FAILED - неудачно */
int mlink(char *src, char *dst, unsigned typefrom, unsigned typeto)
{
 switch( typefrom ){
 case S_IFDIR:           /* переименование каталога */
  printf( "rename directory %s --> %s\n", src, dst );

  if( access( dst, 0 ) == 0 ){
  /* 0 - проверить существование файла */
   printf( "%s exists already\n", dst );
   /* файл уже существует */
   return FAILED;
  }
  if( link( src, dst ) < 0 ){
      printf( "Can't link to directory %s\n", dst );
      perror( "link" );
   /* Возможно, что для выполнения link() для каталогов,
    * программа должна обладать правами суперпользователя.
    */
      return FAILED;
  }
  unlink( src );
  return OK;

 default:   /* dst - не существует или обычный файл */
  printf( "move %s --> %s\n", src, dst );
  unlinkd( dst, typeto );
  /* зачищаем место, т.к. link()
   * отказывается выполняться, если
   * файл dst уже существует (errno==EEXIST).
   */
  if( link( src, dst ) < 0 ) return FAILED;
  unlinkd( src, typefrom );  /* удаляем старый файл */
  return OK;
 }
}
/* Если не получилось связать файл при помощи link() - следует
 * скопировать файл в указанное место, а затем уничтожить старый файл.
 */
int mcopy(char *src, char *dst, unsigned typefrom, unsigned typeto)
{
 if( typefrom == S_IFDIR )
  return FAILED;
 /* каталог не копируем, поскольку непосредственная запись
  * в каталог (как целевой файл) разрешена только ядру ОС.
  */
 return docopy( src, dst, typefrom, typeto );
}
/* Переименование файла */
int domove(char *src, char *dst, unsigned typefrom, unsigned typeto)
{
 switch( typefrom ){
 default:
    if( ! mlink( src, dst, typefrom, typeto)){
   if( ! mcopy( src, dst, typefrom, typeto)){
         printf( "Can't move %s\n", src );
         return FAILED;
   } else unlinkd( src, typefrom ); /* стереть старый */
    }
    break;

 case S_IFDIR: /* каталог переименовываем в каталог */
    if( ! strcmp( ".", basename(src))){
   printf( "impossible to move directory \".\"\n" );
   return FAILED;
    }
    if( ! mlink( src, dst, typefrom, typeto )){
   if( errno == EXDEV )
       printf( "No cross device directory links\n" );
   return FAILED;
    }
    break;

 case ECANTSTAT:
    printf( "%s does not exist\n", src );
    return FAILED;
 }
 return OK;    /* okay */
}
int docpmv( char *src,   /* файл-источник   */
     char *dst,   /* файл-получатель */
     struct ftype typeto, /* тип файла-получателя              */
     int cp,      /* 0 - переименование, 1 - копирование       */
     int *confirm /* запрашивать подтверждение на перезапись ? */
){
 struct ftype typefrom;  /* тип источника       */
 char namebuf[BUFSIZ];   /* новое имя получателя (если надо)   */

 typefrom = filetype(src);
 if(typefrom.type == ECANTSTAT){ /* не существует */
    printf("%s does not exist.\n", src);
    return FAILED;
 }
 if( typefrom.type != S_IFDIR && typeto.type == S_IFDIR ){
  /* файл в каталоге dst */
  sprintf(namebuf, "%s/%s", dst, basename(src));
  typeto = filetype(dst = namebuf);
 }
 if(typefrom.dev == typeto.dev && typefrom.ino == typeto.ino){
 /* Нельзя копировать файл сам в себя */
    printf("%s and %s are identical.\n", src, dst);
    return OK;  /* так как файл уже есть - считаем это удачей */
 }
 /* если получатель уже существует, то
  * запросить подтверждение на перезапись */
 if(*confirm && typeto.type == S_IFREG){
    char answer[40];
    printf("%s already exists. Overwrite (y/n/all) ? ", dst);
    fflush(stdout);
    switch( *gets(answer)){
    case 'n': default:  return OK; /* ничего не делать */
    case 'y':           break;
    case 'a': *confirm = NO; /* дальше - без запросов */
       break;
    }
 }
 return cp ? docopy(src, dst, typefrom.type, typeto.type) :
      domove(src, dst, typefrom.type, typeto.type) ;
}
void main(int argc, char *argv[]) {
 char *cmd; int cp, i, err, confirm = YES;
 struct ftype typeto;  /* тип файла-получателя */

 if( argc < 3 ) {
  printf( "Usage: %s source... destination\n", argv[0] );
  exit(1);
  /* ненулевой код возврата сигнализирует об ошибке */
 }
 /* выделяем базовое имя программы. */
 cmd = basename( argv[0] );

 if     ( !strcmp( cmd, CP )) cp = 1;
 else if( !strcmp( cmd, MV )) cp = 0;
 else{
  printf( "%s - wrong program name.\n", cmd );
  exit(2);
 }
 typeto = filetype( argv[argc-1] );
 if(cp && typeto.type != S_IFDIR && typeto.type != S_IFBLK
       && typeto.type != S_IFCHR && argc > 3){
  printf("Group of files can be copied "
         "to the directory or device only.\n"); exit(3);
 }
 if(!cp && typeto.type != S_IFDIR && argc > 3){
  printf("Group of files can be moved "
         "to the directory only.\n");           exit(4);
 }
 for(err=0, i=1; i < argc-1; i++)
  err += ! docpmv(argv[i], argv[argc-1], typeto,
    cp, &confirm);
 exit(err);  /* 0, если не было ошибок */
}
.
 /*      Пример 13          */

/* Обход дерева каталогов в MS DOS при помощи смены текущего каталога.
 * Аналог ls -R в UNIX. По аналогичному алгоритму работает программа
 * find . -print  (напишите команду find, используя match())
 */
#define STYLE2
#include <stdio.h>
#include <stdlib.h>
#include <dir.h>
#include <dos.h>
#include <alloc.h>      /* для malloc() */
#include <string.h>     /* strchr(), strrchr(), strcpy(), ... */

  /* прототипы */
char *strend(char *s); char *strdup(const char *s);
void action(int, char **); void main(int, char **);
int listdir(char *); void printdir(int n);
#ifdef STYLE2
void lookdir(char *s, int ac, char **av, register int level);
#else
void lookdir(char *s, int ac, char **av);
#endif

char root[256]; /* имя стартового каталога */
char cwd[256];  /* полное имя текущего каталога */

char *strend(register char *s){ while(*s)s++; return s; }
char *strdup(const char *s){ /* прототип malloc в <stdlib.h> */
   char *p = (char *) malloc(strlen(s) + 1);
   if(p) strcpy(p, s); return p;
}

stop(){  /* Реакция на control/break */
   chdir( root );
   /* Это необходимо потому, что MS DOS имеет (в отличие от UNIX)
      понятие "текущий каталог" как глобальное для всей системы.
      Если мы прервем программу, то окажемся не в том каталоге,
      откуда начинали. */
   printf( "\nInterrupted by ctrl-break\n");
   return 0;  /* exit */
}

void main(int argc, char **argv){
    /* получить имя текущего каталога */
    (void) getcwd(root, sizeof root);
    ctrlbrk( stop );  /* установить реакцию на ctrl/break */
#ifndef STYLE2
    lookdir( "." /* корень дерева */, argc, argv );
#else
    /* для примера: дерево от "\\" а не от "." */
    lookdir( "\\", argc, argv, 0 /* начальный уровень */ );
#endif /*STYLE2*/
    chdir(root); /* вернуться в исх. каталог */
}

# ifndef STYLE2
  void lookdir(char *s, int ac, char **av){
       static int level = 0;   /* уровень рекурсии */
# else
  void lookdir(char *s, int ac, char **av, register int level){
# endif /*STYLE2*/
   struct ffblk dblk, *psd = &dblk;
   register done;

   if( chdir(s) < 0 ){ /* войти в каталог */
       printf( "Cannot cd %s\n", s ); return;
   } else if (level == 0){ /* верхний уровень */
       (void) getcwd(cwd, sizeof cwd);
       /* получить полное имя корня поддерева */
   }
   action(ac, av);

   /* искать имена каталогов, удовлетворяющие шаблону "*" */
   /* (не в алфавитном порядке !)                         */
   done = findfirst("*.", psd, FA_DIREC);
   while( !done ){
     if((psd->ff_attrib & FA_DIREC) && psd->ff_name[0] != '.' ){
 /* Видим каталог: войти в него! */
 char *tail =  strend(cwd); char *addplace;
 if( tail[-1] == '\\' ){
     addplace = tail;
 }else{
     *tail = '\\'; addplace = tail+1;
 }
 strcpy(addplace, psd->ff_name);
#ifndef STYLE2
 level++; lookdir( psd->ff_name, ac, av ); level--;
#else
   lookdir( psd->ff_name, ac, av,   level+1 );
#endif
 *tail = '\0';
     }
     /* Искать следующее имя. Информация о точке, где был
      * прерван поиск, хранится в dblk */
     done = findnext(psd);
   }
   if( level ) chdir( ".." );  /* выйти вверх */
}

/* Выполнить действия в каталоге */
void action(int ac, char **av){
   extern int busy;
   busy = 0;
   if( ac == 1 ) listdir( "*.*" );
   else{
       av++;
       while( *av ) listdir( *av++ );
   }
   printdir( busy );
}

#define MAXF 400
struct fst{
    char *name; long size; short attr;
} files[MAXF];
int busy;       /* сколько имен собрано */

/* Собрать имена, удовлетворяющие шаблону. */
int listdir( char *picture ){
    int done, n; struct ffblk dentry;

    for(n=0, done=findfirst(picture, &dentry,0xFF /* все типы */);
  busy < MAXF && !done ;
  done = findnext( &dentry )){
     files[busy].name = strdup(dentry.ff_name);
     files[busy].size = dentry.ff_fsize;
     files[busy].attr = dentry.ff_attrib;
     n++; busy++;
    }
    return n;
}

/* int cmp(struct fst *a, struct fst *b)       */
/* новые веяния в Си требуют такого прототипа: */
int cmp(const void *a, const void *b){
    return strcmp(((struct fst *) a) -> name,
    ((struct fst *) b) -> name );
}

/* отсортировать и напечатать */
void printdir(int n){
    register i;
    struct fst *f;

    qsort( files, n, sizeof files[0], cmp );
    printf( "Directory %s\n", cwd );
    for( i=0, f = files; i < n; i++, f++ )
      printf("\t%-16s\t%10ld\t%c%c%c%c%c%c\n",
    f->name, f->size,
    f->attr & FA_DIREC  ? 'd':'-',  /* directory */
    f->attr & FA_RDONLY ? 'r':'-',  /* read only */
    f->attr & FA_HIDDEN ? 'h':'-',  /* hidden */
    f->attr & FA_SYSTEM ? 's':'-',  /* system */
    f->attr & FA_LABEL  ? 'l':'-',  /* volume label */
    f->attr & FA_ARCH   ? 'a':'-'   /* archive */
      ), free(f->name);
    putchar('\n');
}
.
 /*      Пример 14      */
/* Демонстрация работы с longjmp/setjmp и сигналами */
/* По мотивам книги М.Дансмура и Г.Дейвиса.         */
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <setjmp.h>
/*#define IGN*/         /* потом откомментируйте эту строку */

jmp_buf cs_stack;       /* control point */
int in_cs;              /* флаг, что мы в критической секции */
int sig_recd;           /* флаг signal received */

/* активная задержка */
Delay(){
 int i; for( i=0; i < 10000; i++ ){ i += 200; i -= 200; }
}

interrupt( code ){
 fprintf( stderr, "\n\n***\n" );
 fprintf( stderr, "*** Обрабатываем сигнал (%s)\n",
         code == 1 ? "разрешенный" : "отложенный" );
 fprintf( stderr, "***\n\n" );
}

/* аргумент реакции на сигнал - номер сигнала (подставляется системой) */
void mexit( nsig ){
  fprintf( stderr, "\nУбили сигналом #%d...\n\n", nsig ); exit(0);
}

void main(){
    extern void sig_vec(); int code; int killable = 1;

    signal( SIGINT,  mexit );
    signal( SIGQUIT, mexit );
 fprintf( stderr, "Данная программа перезапускается по сигналу INTR\n" );
 fprintf( stderr, "Выход из программы по сигналу QUIT\n\n\n" );
 fprintf( stderr, "Сейчас вы еще можете успеть убить эту программу...\n\n" );
    Delay(); Delay(); Delay();

    for(;;){
 if( code = setjmp( cs_stack )){
  /* Возвращает не 0, если возврат в эту точку произошел
   * по longjmp( cs_stack, code ); где code != 0
   */
  interrupt( code );    /* пришло прерывание */
 } /* else setjmp() возвращает 0,
    * если это УСТАНОВКА контрольной точки (то есть
    * сохранение регистров SP, PC и других в буфер cs_stack),
    * а не прыжок на нее.
    */
 signal( SIGINT, sig_vec ); /* вызывать по прерыванию */
 if( killable ){
   killable = 0;
   fprintf( stderr,
"\7Теперь сигналы INTR обрабатываются особым образом\n\n\n" );
 }
 body();                 /* основная программа */
    }
}

body(){
 static int n = 0; int i;

 fprintf( stderr, "\tВошли в тело %d-ый раз\n", ++n );
 ecs();
 for( i=0; i < 10 ; i++ ){
  fprintf( stderr, "- %d\n",i); Delay();
 }
 lcs();
 for( i=0; i < 10 ; i++ ){
  fprintf( stderr, "+ %d\n",i); Delay();
 }
}

/* запоминание полученных сигналов */
void sig_vec(nsig){
      if( in_cs ){    /* we're in critical section */
#ifdef IGN
 signal( SIGINT, SIG_IGN );      /* игнорировать */
 fprintf( stderr, "Дальнейшие прерывания будут игнорироваться\n" );
#else
 signal( SIGINT, sig_vec );
 fprintf( stderr, "Дальнейшие прерывания будут подсчитываться\n" );
#endif
 fprintf( stderr, "Получен сигнал и отложен\n" );
 sig_recd++  ;  /* signal received */
         /* пометить, что сигнал пришел */
      }else{
 signal( SIGINT, sig_vec );
 fprintf( stderr, "Получен разрешенный сигнал: прыгаем на рестарт\n" );
 longjmp( cs_stack, 1);
      }
}

ecs(){  /* enter critical section */
 fprintf( stderr, "Откладываем прерывания\n" );
 sig_recd = 0;    in_cs = 1;
}

lcs(){  /* leave critical section */
    fprintf( stderr, "Разрешаем прерывания\n" );
    in_cs = 0;
    if( sig_recd ){
 fprintf( stderr,
     "Прыгаем на рестарт, т.к. есть отложенный сигнал (%d раз)\n",
     sig_recd );
 sig_recd = 0;
 signal( SIGINT, sig_vec );
 longjmp( cs_stack, 2);
    }
}
.
 /*      Пример 15       */
/* Команда для изменения скорости обмена в линии (baud).*/
/* Пример вызова в XENIX: baud /dev/tty1a 9600          */
/* /dev/tty1a - это коммуникационный последов. порт #1  */
/* Про управление модами терминала смотри man termio    */
#include <fcntl.h>
#include <termio.h>
struct termio old, new; int fd = 2;  /* stderr */
struct baudrate{ int speed; char *name;} br[] = {
  { B0,    "HANGUP" }, { B1200, "1200" }, { B9600, "9600"   },
  { B600,  "600"    }, { B2400, "2400" }, { EXTA,  "19200"  },
};
#define RATES (sizeof br/sizeof br[0])

main(ac, av) char *av[];
{       register i; char *newbaud;
 if( ac == 3 ){
     if((fd = open(av[1], O_RDWR)) < 0 ){
  printf("Не могу открыть %s\n", av[1]); exit(1);
     }   newbaud = av[2];
 } else  newbaud = av[1];
 if( ioctl(fd, TCGETA, &old) < 0 ){
     printf("Попытка управлять не терминалом и не портом.\n");
     exit(2);
 }
 if(newbaud == (char*)0) newbaud = "<не задано>";
 new=old;
 for(i=0; i < RATES; i++)
     if((old.c_cflag & CBAUD) == br[i].speed) goto ok;
 printf("Неизвестная скорость\n"); exit(3);

ok:     printf("Было %s бод\n", br[i].name);
 for(i=0; i < RATES; i++)
     if( !strcmp(newbaud, br[i].name)){
       new.c_cflag &= ~CBAUD; /* побитное "или" всех масок B... */
       new.c_cflag |= br[i].speed;
       if( ioctl(fd, TCSETA, &new) < 0) perror("ioctl");
   /* Скорость обмена может не измениться, если терминал
    * не открыт ни одним процессом (драйвер не инициализирован).
    */        exit(0);
     }
 printf("Неверная скорость %s\n", newbaud); exit(4);
}
.
 /*      Пример 16     */
/*#!/bin/cc -DUSG wins.c -o wins -lncurses -lx
 Просмотр двух файлов в перекрывающихся окнах.
 Редактирование содержимого окон.
*/
/* _______________________ файл wcur.h __________________________ */
#include "curses.h"

 /* Макросы, зависимые от реализации curses */
/* число колонок и строк в окне: */
#  define wcols(w)  ((w)-> _maxx+1 )
#  define wlines(w) ((w)-> _maxy+1 )
/* верхний левый угол окна: */
#  define wbegx(w)  ((w)-> _begx )
#  define wbegy(w)  ((w)-> _begy )
/* координаты курсора в окне: */
#  define wcurx(w)  ((w)-> _curx )
#  define wcury(w)  ((w)-> _cury )
/* доступ к памяти строк окна: */
#  define wtext(w)  ((w)-> _line)  /* chtype **_line; */
/* в других реализациях: ((w)-> _y) */

/* Псевдографика:    Для  curses   Для IBM PC MS DOS */
#define HOR_LINE        '\200'     /* 196 */
#define VER_LINE        '\201'     /* 179 */
#define UPPER_LEFT      '\210'     /* 218 */
#define LOWER_LEFT      '\202'     /* 192 */
#define UPPER_RIGHT     '\212'     /* 191 */
#define LOWER_RIGHT     '\204'     /* 217 */
#define LEFT_JOIN       '\205'     /* 195 */
#define RIGHT_JOIN      '\207'     /* 180 */
#define TOP_JOIN        '\211'     /* 194 */
#define BOTTOM_JOIN     '\203'     /* 193 */
#define MIDDLE_CROSS    '\206'     /* 197 */
#define BOX             '\272'     /* 219 */
#define BOX_HATCHED     '\273'     /* 177 */
#define LABEL           '\274'     /*   3 */
#define RIGHT_TRIANG    '\234'     /*  16 */
#define LEFT_TRIANG     '\235'     /*  17 */

#define YES                 1
#define NO                  0
#define MIN(a,b)        (((a) < (b)) ? (a):(b))
#define MAX(a,b)        (((a) > (b)) ? (a):(b))
#define A_ITALICS  A_ALTCHARSET  /* в этой версии curses-а - курсив */
#ifndef  ESC
# define ESC '\033'     /* escape */
#endif
#define  ctrl(c)  (c & 037)

/* перерисовка экрана */
#define RedrawScreen() { vidattr(curscr->_attrs = A_NORMAL); \
    wrefresh(curscr); }
/* curscr - служебное окно - копия текущего состояния экрана дисплея
 * для сравнения со сформированным НОВЫМ образом  экрана - newscr.
 * Поле _attrs в структуре окна содержит текущие атрибуты окна,
 * именно это поле изменяется wattrset(), wattron(), wattroff();
 */

/* _______________________ файл wins.c __________________________ */
#include "wcur.h"
#include <signal.h>

WINDOW *wbase1, *wbase2;        /* окна рамки (фоновые окна) */
WINDOW *w1,     *w2;            /* окна для текста */

/* Размеры и расположение окон */
/* COLS - предопределенная переменная: число колонок */
/* LINES     - // -                  : число строк на экране */
#define W1ysize (LINES/2)       /* высота */
#define W1xsize (COLS/3*2)      /* ширина */
#define W1y     5               /* y верхнего левого угла на экране */
#define W1x     20              /* x верхнего левого угла на экране */

#define W2ysize (LINES/2)
#define W2xsize (COLS/3*2)
#define W2y     10
#define W2x     5

FILE *fp1, *fp2;         /* просматриваемые файлы */

/* Завершить работу */
void die(sig){                /* аргумент - номер сигнала */
 /* Восстановление режимов терминала */
 echo();         /* эхо-отображение вводимых букв */
 nocbreak();     /* ввод с системным редактированием строки */

 mvcur( -1, -1, LINES-1, 0 ); /* курсор в нижн. левый угол  */
 endwin();       /* окончание  работы с curses-ом */
 putchar('\n');
 exit(sig);      /* завершение работы с кодом sig. 0 - успешно */
}

int run;
void stop(nsig){ signal(SIGINT, SIG_IGN); run = 0; beep(); }
char label[3][5] = {  /* Демонстрация псевдографики */
 { UPPER_LEFT,  TOP_JOIN,     UPPER_RIGHT,    HOR_LINE, '\0' },
 { LEFT_JOIN,   MIDDLE_CROSS, RIGHT_JOIN,     VER_LINE, '\0' },
 { LOWER_LEFT,  BOTTOM_JOIN,  LOWER_RIGHT,    BOX,      '\0' }
};
/* Нарисовать рамку, название и фон окна */
wborder( w, name ) WINDOW *w;  char *name;
{       register i, j;

 for(i=1; i < wlines(w)-1; i++ ){
  /* поставить курсор и выдать символ */
  mvwaddch(w, i, 0,          VER_LINE );
  /* mvwaddch(w,y,x,c) = wmove(w,y,x); waddch(w,c); */
  /* wmove(w,y,x) - логич. курсор в позицию (y,x)   */
  /* waddch(w,c)  - выдать символ в позиции курсора,
     продвинуть курсор. Аналог putchar              */
  mvwaddch(w, i, wcols(w)-1, VER_LINE );
 }
 for(j=1; j < wcols(w)-1; j++ ){
  mvwaddch(w, 0,           j, HOR_LINE );
  mvwaddch(w, wlines(w)-1, j, HOR_LINE );
 }               /* Углы */
 mvwaddch(w, 0,            0,          UPPER_LEFT);
 mvwaddch(w, wlines(w)-1,  0,          LOWER_LEFT);
 mvwaddch(w, wlines(w)-1,  wcols(w)-1, LOWER_RIGHT);
 mvwaddch(w, 0,            wcols(w)-1, UPPER_RIGHT);

 /* Рисуем заголовки вверху и внизу на рамке.
  * Заголовки выдаем в центре рамки.
  */
 if( (j = (wcols(w) - strlen(name))/2 ) > 0 ){
      /* логический курсор - в 0 строку, позицию j */
      wmove(w, 0, j);
      /* задать режимы выделений */
      wattrset( w, A_BOLD | A_BLINK | A_REVERSE );
      waddstr( w, name );     /* выдать строку в окно */

      wmove( w, wlines(w)-1, j);
      wattrset( w, A_ITALICS | A_STANDOUT );
      waddstr ( w, name );
      wattrset( w, A_NORMAL ); /* нормальные атрибуты */
 }
}

/* режим редактирования текста в окнах     */
int mode = 0;   /* 0 - замена, 1 - вставка */

main( ac, av ) char **av;
{
 char buffer[512];
 int need1, need2;
 int c; void (*save)();
 WINDOW *w;  /* активное окно */

 if( ac < 3 ){
  fprintf( stderr, "Вызов: %s file1 file2\n", av[0] );
  exit( 1 );
 }

 if((fp1 = fopen( av[1], "r" )) == NULL ){
  fprintf( stderr, "Не могу читать %s\n", av[1] );
  exit( 2 );
 }
 if((fp2 = fopen( av[2], "r" )) == NULL ){
  fprintf( stderr, "Не могу читать %s\n", av[2] );
  exit( 2 );
 }
 /* Инициализировать curses */
 initscr();

 signal( SIGINT, die );        /* по ctrl/C - умереть */
 signal( SIGQUIT,die );

 /* Создать окна                                            */
 /*               высота   ширина   Y  и X верх.левого угла */
 wbase1 = newwin( W1ysize, W1xsize, W1y, W1x);
 if( wbase1 == NULL ){
  fprintf( stderr, "Не могу создать wbase1\n" );
  goto bad;
 }
 wbase2 = newwin( W2ysize, W2xsize, W2y, W2x);
 if( wbase2 == NULL ){
  fprintf( stderr, "Не могу создать wbase2\n" );
  goto bad;
 }

 /* Создать подокна для текста                                */
 /*           база    высота       ширина       Y угла X угла */
 w1 = subwin( wbase1, W1ysize - 2, W1xsize - 2, W1y+1, W1x+1);
 w2 = subwin( wbase2, W2ysize - 2, W2xsize - 2, W2y+1, W2x+1);

 scrollok( w1, TRUE );   /* разрешить роллирование окон */
 scrollok( w2, TRUE );

 wattrset( w2, A_REVERSE  ); /*установить атрибуты текста в окнах*/
 wattrset( stdscr, A_STANDOUT );

 wborder( wbase1, av[1] );
 wborder( wbase2, av[2] );   /* рамки */

 werase( w1 ); werase( w2 );        /* очистить окна */

 /* фон экрана */
 werase( stdscr );
 /* функции без буквы w... работают с окном stdscr (весь экран) */
 for(c=0; c < 3; c++)
     mvwaddstr(stdscr, c, COLS-5, &label[c][0]);
 move( 1, 10 ); addstr( "F1 - переключить окна" );
 mvaddstr( 2, 10,       "F5 - переключить режим вставки/замены" );
 move( 3, 10 ); printw( "F%d - удалить строку, F%c - вставить строку",
     7,                    '8'             );
 mvwprintw(stdscr, 4,10, "ESC - выход, CTRL/C - прервать просмотр");
 /* wprintw(w, fmt, ...) - аналог printf для окон */

    /* В нижний правый угол экрана ничего не выводить:
     * на некоторых терминалах это роллирует экран и тем самым
     * портит нам картинку.
     */
    wattrset( stdscr, A_NORMAL );
    wmove(    stdscr, LINES-1, COLS-1 );
    waddch(   stdscr, ' ' );

 wnoutrefresh( stdscr );
 /* виртуальное проявление окна. */

 run = need1 = need2 = 1; /* оба файла не достигли конца */
 /* прерывать просмотр по CTRL/C */
 save = signal(SIGINT, stop);

 while( run && (need1 || need2)){

  if( need1 ){
      /* прочесть строку из первого файла */
      if( fgets( buffer, sizeof buffer, fp1 ) == NULL )
   need1 = 0;      /* конец файла */
      else{
   /* выдать строку в окно */
   waddstr( w1, buffer );
      }
  }
  if( need2 ){
      /* прочесть строку из второго файла */
      if( fgets( buffer, sizeof buffer, fp2 ) == NULL )
   need2 = 0;      /* конец файла */
      else{
   waddstr( w2, buffer );
   /* wnoutrefresh( w2 ); */
      }
  }

  /* Проявить w1 поверх w2 */
  touchwin( wbase2 ); wnoutrefresh( wbase2 );
  touchwin( w2 );     wnoutrefresh( w2 );

  touchwin( wbase1 ); wnoutrefresh( wbase1 );
  touchwin( w1 );     wnoutrefresh( w1 );
  /* touchwin - пометить окно как целиком измененное.
   * wnoutrefresh - переписать изменения в новый образ
   * экрана в памяти. */

  /* Проявить изображение на экране терминала
   * (вывести новый образ экрана). При этом выводятся
   * лишь ОТЛИЧИЯ от текущего содержимого экрана
   * (с целью оптимизации).
   */
  doupdate();
 }
 fclose(fp1); fclose(fp2);
 /* восстановить спасенную реакцию на сигнал */
 signal(SIGINT, save);

 /* Редактирование в окнах                */
 noecho();       /* выкл. эхо-отображение */
 cbreak();       /* немедленный ввод набранных клавиш
    * (без нажатия кнопки \n) */

 keypad( w1, TRUE );     /* распознавать функц. кнопки */
 keypad( w2, TRUE );

 scrollok( w1, FALSE );  /* запретить роллирование окна */

 w = w1;                 /* текущее активное окно */
 for( ;; ){
  int y, x;       /* координаты курсора в окне */

  wrefresh( w ); /* обновить окно. Примерно соответствует
    * wnoutrefresh(w);doupdate(); */
  c = wgetch( w );  /* ввести символ с клавиатуры */
  /* заметим, что в режиме noecho() символ не
   * отобразится в окне без нашей помощи !
   */
  getyx( w, y, x );  /* узнать координаты курсора в окне */
 /* не надо &y &x, т.к. это макрос, превращающийся в пару присваиваний */

  switch( c ){
  case KEY_LEFT:              /* шаг влево */
   waddch( w, '\b' );
   break;
  case KEY_RIGHT:             /* шаг вправо */
   wmove( w, y, x+1 );
   break;
  case KEY_UP:                /* шаг вверх */
   wmove( w, y-1, x );
   break;
  case KEY_DOWN:              /* шаг вниз */
         wmove( w, y+1, x );
   break;
  case KEY_HOME:              /* в начало строки */
  case KEY_LL:   /* KEY_END      в конец строки  */
  {       int xbeg, xend;
   wbegend(w, &xbeg, &xend);
   wmove(w, y, c==KEY_HOME ? xbeg : xend);
   break;
  }
  case '\t':                  /* табуляция */
   x += 8 - (x % 8);
   if( x >= wcols( w ))
    x = wcols(w)-1;
   wmove(w, y, x);
   break;
  case KEY_BACKTAB:           /* обратная табуляция */
   x -= 8 - (x % 8);
   if( x < 0 ) x = 0;
   wmove( w, y, x );
   break;

  case '\b':                  /* забой */
  case KEY_BACKSPACE:
  case '\177':
   if( !x ) break;     /* ничего */
   wmove( w, y, x-1 );
   /* and fall to ... (и провалиться в) */
  case KEY_DC:                /* удаление над курсором */
   wdelch( w );
   break;
  case KEY_IC:         /* вставка пробела над курсором */
   winsch( w, ' ' );
   break;
  case KEY_IL:
  case KEY_F(8):              /* вставка строки */
   winsertln( w );
   break;
  case KEY_DL:                /* удаление строки */
  case KEY_F(7):
   wdeleteln( w );
   break;

  case ESC:                   /* ESC - выход */
   goto out;

  case KEY_F(1):       /* переключение активного окна */
   if( w == w1 ){
    touchwin( wbase2 ); wnoutrefresh( wbase2 );
    touchwin( w2 );     wnoutrefresh( w2 );
    w = w2;
   } else {
    touchwin( wbase1 ); wnoutrefresh( wbase1 );
    touchwin( w1 );     wnoutrefresh( w1 );
    w = w1;
   }
   break;

  case KEY_F(5):    /* переключение режима редактирования */
   mode = ! mode;
   break;

  case ctrl('A'):   /* перерисовка экрана */
   RedrawScreen(); break;

  case '\n': case '\r':
   waddch( w, '\n' );
   break;

  default:          /* добавление символа в окно */
   if( c >= 0400 ){
    beep();     /* гудок */
    break;      /* функц. кнопка - не буква */
   }
   if( mode ){
    winsch( w, ' ' );  /* раздвинь строку */
   }
   waddch( w, c );     /* выдать символ в окно */
   break;
  }
 }
out:
 wrefresh( w ); wsave(w);
bad:
 die(0); /* вызов без возврата */
}

/* Сохранить содержимое окна в файл, обрезая концевые пробелы */
wsave(w) WINDOW *w;
{
 FILE *fp = fopen("win.out", "w");
 register int x,y, lastnospace; int xs, ys;

 getyx(w, ys, xs);
 for( y=0; y < wlines(w); y++ ){
  /* поиск последнего непробела */
  for( lastnospace = (-1), x=0; x < wcols(w); x++ )
   /* читаем символ из координат (x,y) окна */
   if((mvwinch(w,y,x) & A_CHARTEXT) != ' ' )
        lastnospace = x;
  /* запись в файл */
  for( x=0 ; x <= lastnospace; x++ ){
   wmove(w,y,x);
   putc( winch(w) & A_CHARTEXT, fp );
  }
  putc( '\n', fp );
 }
 fclose(fp);
 wmove(w, ys, xs ); /* вернуть курсор на прежнее место */
}

/* На самом деле
 * winch(w) = wtext(w)[ wcury(w) ][ wcurx(w) ];
 * Предложим еще один, более быстрый способ чтения памяти окна
 * (для ЗАПИСИ в окно он непригоден, т.к.  curses еще
 * специальным образом помечает ИЗМЕНЕННЫЕ области окон).
 */
/* Найти начало и конец строки */
int wbegend(w, xbeg, xend) WINDOW *w; int *xbeg, *xend;
{
/* Тип chtype: 0xFF - код символа; 0xFF00 - атрибуты */
 chtype ch, *thisline = wtext(w)[ wcury(w) ];
 register x, notset = TRUE;

 *xbeg = *xend = 0;
 for(x=0; x < wcols(w); x++)
  /* & A_CHARTEXT игнорирует атрибуты символа */
  if(((ch=thisline[x]) & A_CHARTEXT) != ' '){
   if((*xend = x+1) >= wcols(w))
       *xend = wcols(w) - 1;
   if(notset){ notset = FALSE; *xbeg=x; }
  }
 return (*xend - *xbeg);
}
.
 /*      Пример 17       */
/* Window management: "стопка" окон
 *      cc -DTEST -DUSG w.c -lncurses -lx
 *
 *____ Файл w.h для Пример 17, Пример 19, Пример 21, Пример 23 _____ */

#include "wcur.h"      /* Тот же, что в Пример 16 */
extern int botw, topw;
extern struct WindowList {  /* Элемент списка окон */
  WINDOW *w;  /* окно */
  int next;   /* следующее окно в списке */
  char busy;  /* 0:слот свободен, 1:окно видимо, -1:окно спрятано */
} wins[];              /* значения поля busy:   */
#define W_VISIBLE 1    /* окно видимо           */
#define W_FREE    0    /* слот таблицы свободен */
#define W_HIDDEN (-1)  /* окно спрятано         */

#define EOW     (-1)
#define WIN(n)  wins[n].w
  /* если совсем нет видимых окон... */
#define TOPW    (topw != EOW ? WIN(topw) : stdscr)
#define BOTW    (botw == EOW ? stdscr : WIN(botw))
#define MAXW    15
#define iswindow(n) wins[n].busy

int  RaiseWin  (WINDOW *w); void PopWin    ();
void DestroyWin(WINDOW *w,  int destroy);
int  HideWin   (WINDOW *w);
#define KillWin(w) DestroyWin(w, TRUE)
#define DropWin(w) DestroyWin(w, FALSE)
#define PushWin(w) RaiseWin(w)

#define BAR_HOR    01   /* окно имеет горизонтальный scroll bar */
#define BAR_VER    02   /* окно имеет вертикальный   scroll bar */
#define DX              2  /* отступ от краев окна       */
#define BARWIDTH        2  /* ширина scroll bar-а        */
#define BARHEIGHT       1  /* высота                     */
/* Вычисление координат строки выбора в окне             */
#define WY(title, y)     ((y) + (title ? 3 : 1))
#define WX(x)            ((x) + 1 + DX)
#define XEND(w,scrollok) (wcols(w)-((scrollok & BAR_VER) ? BARWIDTH+2 : 1))
void whorline  (WINDOW *w, int y, int x1, int x2);
void wverline  (WINDOW *w, int x, int y1, int y2);
void wbox      (WINDOW *w, int x1, int y1, int x2, int y2);
void wborder   (WINDOW *w);
void wboxerase (WINDOW *w, int x1, int y1, int x2, int y2);
void WinBorder (WINDOW *w, int bgattrib, int titleattrib, char *title,
      int scrollok, int clear);
void WinScrollBar(WINDOW *w, int whichbar, int n, int among,
    char *title, int bgattrib);
/* Спасение/восстановление позиции курсора */
typedef struct { int x, y; } Point;
#define SetPoint(p, yy, xx) { (p).x = (xx); (p).y = (yy);}
#define GetBack(p, w)       wmove((w), (p).y, (p).x)

/* _______________________ файл w.c _____________________________ */
/*            УПРАВЛЕНИЕ ПОРЯДКОМ ОКОН НА ЭКРАНЕ                  */
/* ______________________________________________________________ */
#include "w.h"
int botw = EOW, topw = EOW;   /* нижнее и верхнее окна   */
struct WindowList wins[MAXW]; /* список управляемых окон */

/* Прочесть символ из окна, проявив окно (если оно не спрятано) */
int WinGetch (WINDOW *win) { register n, dorefr = YES;
    if(botw != EOW) for(n=botw; n != EOW; n=wins[n].next)
 if(wins[n].w == win){
    if(wins[n].busy == W_HIDDEN) dorefr = NO;  /* спрятано */
    break;
 }
    if( dorefr ) wrefresh (win);  /* проявка */
    else         doupdate ();
    for(;;){ n = wgetch (win);    /* собственно чтение */
  if( n == ctrl('A')){ RedrawScreen(); continue; }
  return n;
    }
}
/* Вычислить новое верхнее окно */
static void ComputeTopWin(){   register n;
    if(botw == EOW) topw = EOW;  /* список стал пуст */
    else{ /* ищем самое верхнее видимое окно */
   for(topw = EOW, n=botw; n != EOW; n=wins[n].next)
  /* спрятанное окно не может быть верхним */
  if( wins[n].busy == W_VISIBLE) topw = n;
   /* Может совсем не оказаться видимых окон; тогда
    * topw == EOW, хотя botw != EOW. Макрос TOPW предложит
    * в качестве верхнего окна окно stdscr */
    }
}
/* Виртуально перерисовать окна в списке в порядке снизу вверх */
static void WinRefresh(){      register nw;
     /* чистый фон экрана */
     touchwin(stdscr); wnoutrefresh(stdscr);
     if(botw != EOW) for(nw=botw; nw != EOW; nw=wins[nw].next)
 if(wins[nw].busy == W_VISIBLE){
     touchwin(wins[nw].w); wnoutrefresh(wins[nw].w);
 }
}
/* Исключить окно из списка не уничтожая ячейку */
static int WinDelList(WINDOW *w){  register nw, prev;
    if(botw == EOW) return EOW; /* список пуст */
    for(prev=EOW, nw=botw; nw != EOW; prev=nw, nw=wins[nw].next)
 if(wins[nw].w == w){
    if(prev == EOW) botw = wins[nw].next; /* было дно стопки */
    else wins[prev].next = wins[nw].next;
    return nw;   /* номер ячейки в таблице окон */
 }
    return EOW; /* окна не было в списке */
}
/* Сделать окно верхним, если его еще не было в таблице - занести */
int RaiseWin(WINDOW *w){  int nw, n;
    if((nw = WinDelList(w)) == EOW){ /* не было в списке  */
 for(nw=0; nw < MAXW; nw++)   /* занести в таблицу */
   if( !iswindow(nw)){ wins[nw].w = w; break; }
 if(nw == MAXW){ beep(); return EOW; } /* слишком много окон */
    }
    /* поместить окно nw на вершину списка */
    if(botw == EOW) botw = nw;
    else{ for(n = botw; wins[n].next != EOW; n=wins[n].next);
   wins[n].next = nw;
    }
    wins[nw].busy = W_VISIBLE; /* окно видимо, слот занят */
    wins[topw = nw].next = EOW; WinRefresh(); return nw;
}
/* Удалить окно из списка и (возможно) уничтожить */
/* Окно при этом исчезнет с экрана                */
void DestroyWin(WINDOW *w, int destroy){  int nw;
    if((nw = WinDelList(w)) != EOW){ /* окно было в списке */
 ComputeTopWin();
 wins[nw].busy = W_FREE;  /* ячейка свободна */
 wins[nw].w    = NULL;
    }
    if(destroy) delwin(w);       /* уничтожить curses-ное окно */
    WinRefresh();
}
void PopWin(){ KillWin(TOPW); }
/* Спрятать окно, и при этом сделать его самым нижним. */
int HideWin(WINDOW *w){  register nw, prev;
     if(botw == EOW) return EOW; /* список пуст */
     for(prev = EOW, nw = botw; nw != EOW; prev = nw, nw = wins[nw].next )
  if(wins[nw].w == w){
     wnoutrefresh(w); /* вместо untouchwin(w); */
     wins[nw].busy = W_HIDDEN; /* спрятано */
     if( nw != botw ){
  wins[prev].next = wins[nw].next; /* удалить из списка */
  wins[nw].next = botw; botw = nw; /* на дно стопки     */
     }
     WinRefresh();
     ComputeTopWin();
     return nw;
  }
      return EOW;  /* нет в списке */
}
/* _______________ ОФОРМИТЕЛЬСКИЕ РАБОТЫ _____________________ */
/* Нарисовать горизонтальную линию */
void whorline(WINDOW *w, int y, int x1, int x2){
    for( ; x1 <= x2; x1++) mvwaddch(w, y, x1, HOR_LINE);
}
/* Нарисовать вертикальную линию */
void wverline(WINDOW *w, int x, int y1, int y2){
    for( ; y1 <= y2; y1++) mvwaddch(w, y1, x, VER_LINE);
}
/* Нарисовать прямоугольную рамку */
void wbox(WINDOW *w, int x1, int y1, int x2, int y2){
    whorline(w, y1, x1+1, x2-1);
    whorline(w, y2, x1+1, x2-1);
    wverline(w, x1, y1+1, y2-1);
    wverline(w, x2, y1+1, y2-1);
 /* Углы */
    mvwaddch (w, y1, x1, UPPER_LEFT);
    mvwaddch (w, y1, x2, UPPER_RIGHT);
    mvwaddch (w, y2, x1, LOWER_LEFT);
    /* Нижний правый угол нельзя занимать ! */
    if(! (wbegx(w) + x2 == COLS-1 && wbegy(w) + y2 == LINES-1))
     mvwaddch (w, y2, x2, LOWER_RIGHT);
}
/* Нарисовать рамку вокруг окна */
void wborder(WINDOW *w){ wbox(w, 0, 0, wcols(w)-1, wlines(w)-1); }
/* Очистить прямоугольную область в окне */
void wboxerase(WINDOW *w, int x1, int y1, int x2, int y2){
    int x, y; register i, j; getyx(w, y, x);
    for(i=y1; i <= y2; ++i) for(j=x1; j <= x2; j++)
 mvwaddch(w, i, j, ' ');
    wmove(w, y, x);
}
/* Нарисовать рамку и заголовок у окна */
void WinBorder (WINDOW *w, int bgattrib, int titleattrib, char *title,
      int scrollok, int clear){
    register  x, y;

    wattrset (w, bgattrib);     /* задать цвет окна */
    if(clear) werase(w);        /* заполнить окно цветными пробелами */
    wborder  (w);  /* нарисовать рамку вокруг окна      */
    if (title) {   /* если есть заголовок ...        */
 for (x = 1; x < wcols (w) - 1; x++){
      wattrset(w, bgattrib); mvwaddch (w, 2, x, HOR_LINE);
      /* очистка поля заголовка */
      wattrset(w, titleattrib); mvwaddch (w, 1, x, ' ');
 }
 wattrset(w, bgattrib);
 mvwaddch (w, 2, 0,             LEFT_JOIN);
 mvwaddch (w, 2, wcols (w) - 1, RIGHT_JOIN);
 wattrset (w, A_BOLD | titleattrib);
 mvwaddstr(w, 1, (wcols(w)-strlen(title))/2, title);
 wattrset (w, bgattrib);
    }
    if (scrollok & BAR_VER) { /* выделить столбец под scroll bar. */
 int  ystart = WY(title, 0), xend = XEND(w, scrollok);
 for (y = ystart; y < wlines (w) - 1; y++)
     mvwaddch (w, y,        xend, VER_LINE);
 mvwaddch (w, wlines (w)-1, xend, BOTTOM_JOIN);
 mvwaddch (w, ystart-1,     xend, TOP_JOIN);
    }
/*  затычка */
    if(wcols(w)==COLS && wlines(w)==LINES){ wattrset(w, A_NORMAL);
       mvwaddch(w, LINES-1, COLS-1, ' ');
    }
    wattrset (w, bgattrib);
}
/* Нарисовать вертикальный scroll bar (горизонтальный не сделан) */
/* Написано не очень аккуратно                                   */
void WinScrollBar(WINDOW *w, int whichbar, int n, int among,
    char *title, int bgattrib){
    register y, i;
    int     starty = WY(title, 0);
    int     endy   = wlines (w)         - 1;
    int     x      = XEND(w, whichbar)  + 1;
    int     height = endy - starty         ;

    if(whichbar & BAR_VER){     /* вертикальный */
       wattrset (w, A_NORMAL);
       for (y = starty; y < endy; y++)
    for (i = 0;  i < BARWIDTH; i++)
        mvwaddch (w, y, x + i, ' ');
       y = starty;
       if(among > 1) y += ((long) (height - BARHEIGHT) * n / (among - 1));
       wattron(w, A_BOLD);
       for (i = 0; i < BARWIDTH; i++)
    mvwaddch (w, y, x + i, BOX);
       wattrset(w, bgattrib | A_BOLD );
       if( wcols(w) >= 10 )
    mvwprintw(w, 0, wcols(w)-9, "%03d/%03d", n+1, among);
    }
    wattrset (w, bgattrib);
}
#ifdef TEST
main(){ WINDOW *w[5]; register i, y;
     initscr();  /* запустить curses */
     w[0] = newwin(16, 20, 4, 43);  /* создать 5 окон */
     w[1] = newwin(12, 20, 7, 34);
     w[2] = newwin(6, 30, 3, 40);
     w[3] = newwin(7, 35, 12, 38);
     w[4] = newwin(6, 20, 11, 54);
     for(i=0; i < 5; i++){
 keypad  (w[i],   TRUE);
 wattrset(w[i],   A_REVERSE); werase(w[i]);
 wborder (w[i]);  mvwprintw(w[i], 1, 2, "Window %d", i);
 RaiseWin(w[i]);  /* сделать верхним окном */
     }
     noecho(); cbreak(); /* прозрачный ввод */
     for(;botw != EOW;){ int c;
     /* нарисовать порядок окон */
 for(i=botw, y=0; y < 5; y++, i=(i==EOW ? EOW : wins[i].next))
     mvprintw(8 - y, 5, i==EOW ? "~": "%d%c", i,
  wins[i].busy == W_HIDDEN ? 'h':' ');
 mvprintw(9, 5, "topw=%3d botw=%3d", topw, botw);
 wnoutrefresh(stdscr); /* вирт. проявка этих цифр */
 c = WinGetch(TOPW);
 /* здесь происходит doupdate();
  * и только в этот момент картинка проявляется */

 switch(c){
 case KEY_DC: PopWin(); break;
 case KEY_IC: KillWin(BOTW); break;
 case '0': case '1': case '2': case '3': case '4': case '5':
       c -= '0'; if( !iswindow(c)){ beep(); break; }
       RaiseWin(WIN(c)); break;
 case 'D': KillWin(w[2]); break;
 case 'h': HideWin(BOTW); break;
 case 'H': HideWin(TOPW); break;
 case ESC: goto out;
 default:  waddch(TOPW, c & 0377); break;
 }
     }
     mvaddstr(LINES-2, 0, "Больше нет окон"); refresh();
out: echo(); nocbreak(); endwin();
}
#endif
.
        /*   Пример 18    */
/* _______________________ файл glob.h ___________________________*/
/* ПОДДЕРЖКА СПИСКА ИМЕН ФАЙЛОВ ЗАДАННОГО КАТАЛОГА                */
/* ______________________________________________________________ */
#define FILF

#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
# define DIR_SIZE 14
extern char *malloc(unsigned); char *strdup(const char *str);
extern char *getenv();
extern char *strchr(char *, char),  *strrchr(char *, char);
#define ISDIR(mode) ((mode & S_IFMT) == S_IFDIR)
#define ISDEV(mode) ((mode & S_IFMT) & (S_IFCHR|S_IFBLK))
#define ISREG(mode) ((mode & S_IFMT) == S_IFREG)
#define ISEXE(mode) ((mode & S_IFMT) == S_IFREG && (mode & 0111))
#define isdir(st)   ISDIR(st.st_mode)
#define isdev(st)   ISDEV(st.st_mode)
#define isreg(st)   ISREG(st.st_mode)
#define isexe(st)   ISEXE(st.st_mode)
#define YES      1
#define NO       0
#define I_DIR    0x01     /* это имя каталога      */
#define I_EXE    0x02     /* это выполняемый файл  */
#define I_NOSEL  0x04     /* строку нельзя выбрать */
#define I_SYS    (I_DIR | I_EXE | I_NOSEL)
/* Скопировано из treemk.c
 * Лучше просто написать #include "glob.h" в файле treemk.c
 */
#define FAILURE (-1)            /* код неудачи */
#define SUCCESS   1             /* код успеха  */
#define WARNING   0             /* нефатальная ошибка */

typedef struct _info {    /* структура элемента каталога */
 char *s;          /* имя файла                   */
 short fl;         /* флаг                        */
 union _any{
    int (*act)();     /* возможно связанное действие */
    char *note;       /* или комментарий             */
    unsigned i;       /* или еще какой-то параметр   */
    struct _info *inf;
 } any;            /* вспомогательное поле        */
#ifdef FILF
/* дополнительные необязательные параметры, получаемые из stat(); */
 long size;
 int uid, gid;
 unsigned short mode;
#endif
} Info;
typedef union _any Any;

extern Info NullInfo;
#define MAX_ARGV 256      /* Максимальное число имен в каталоге */
typedef struct {          /* Содержимое каталога name */
 time_t lastRead;  /* время последнего чтения каталога  */
 Info *files;      /* содержимое каталога               */
 char *name;       /* имя каталога                      */
 ino_t ino; dev_t dev; /* I-узел и устройство           */
 char valid;       /* существует ли этот каталог вообще */
 short readErrors; /* != 0, если каталог не читается    */
} DirContents;
     /* Виды сортировки имен в каталоге */
typedef enum { SORT_ASC, SORT_DESC, SORT_SUFX,
        SORT_NOSORT, SORT_SIZE }         Sort;
extern Sort sorttype; extern int in_the_root;

int   gcmps (const void *p1, const void *p2);
Info *blkcpy(Info *v); void blkfree(Info *v);
Info *glob(char **patvec,  char *dirname);
Info *glb(char   *pattern, char *dirname);
int ReadDir(char *dirname, DirContents *d);

struct savech{ char *s, c; };
#define SAVE(sv, str) (sv).s = (str); (sv).c = *(str)
#define RESTORE(sv) if((sv).s)   *(sv).s = (sv).c

/* _______________________ файл glob.c __________________________ */
#include "glob.h"
int in_the_root = NO;        /* читаем корневой каталог ?     */
Sort sorttype = SORT_SUFX;   /* сортировка имен по суффиксу   */
Info NullInfo = { NULL, 0 }; /* и прочие поля = 0 (если есть) */

char *strdup(const char *s){
   char *p = malloc(strlen(s)+1); if(p)strcpy(p, s); return p; }

/* Содержится ли любой из символов в строке ? */
int any(register char *s, register char *p){
   while( *s ){ if( strchr(p, *s)) return YES; s++; }
   return NO;
}
/* Найти последнюю точку в имени */
static char *lastpoint (char *s)
{   register char *last; static char no[] = "";
    if((last = strchr(s, '.')) == NULL) return no;
    /* если имя начинается с точки - не считать ее */
    return( last == s ? no : last );
}
/* Сравнение строк с учетом их суффиксов */
int strsfxcmp (register char *s1, register char *s2){
    char *p1, *p2, c1, c2; int code;
    p1 = lastpoint (s1); p2 = lastpoint (s2);
    if (code = strcmp (p1, p2)) return code; /* суффиксы разные */
    /* иначе: суффиксы равны. Сортируем по головам              */
    c1 = *p1; c2 = *p2; *p1 = '\0'; *p2 = '\0'; /* временно     */
    code = strcmp (s1, s2);
    *p1 = c1; *p2 = c2; return code;
}
/* Функция сортировки */
int gcmps(const void *p1, const void *p2){
    Info *s1 = (Info *) p1, *s2 = (Info *) p2;
    switch( sorttype ){
    default:
    case SORT_ASC:    return    strcmp(s1->s, s2->s);
    case SORT_DESC:   return   -strcmp(s1->s, s2->s);
    case SORT_SUFX:   return strsfxcmp(s1->s, s2->s);
    case SORT_NOSORT: return (-1);
#ifdef FILF
    case SORT_SIZE:   return (s1->size <  s2->size ? -1 :
         s1->size == s2->size ? 0 : 1 );
#endif
    }
}
/* Копирование блока */
Info *blkcpy(Info *v){
    register i, len;
    Info *vect = (Info *) malloc(((len=blklen(v)) + 1) * sizeof(Info));
    for(i=0; i < len; i++ ) vect[i] = v[i];
    vect[len] = NullInfo;   return vect;
}
/* Измерение длины блока */
int blklen(Info *v){
    int i = 0;
    while( v->s ) i++, v++;
    return i;
}
/* Очистка блока (уничтожение) */
void blkfree(Info *v){
     Info *all = v;
     while( v->s )
     free((char *) v->s ), v++;
     free((char *) all );
}
/* Сравнение двух блоков */
int blkcmp( register Info *p, register Info *q ){
    while( p->s && q->s && !strcmp(p->s, q->s) &&
   (p->fl & I_SYS) == (q->fl & I_SYS)){ p++; q++; }
    if( p->s == NULL && q->s == NULL )
 return 0;       /* совпадают   */
    return 1;           /* различаются */
}
char   globchars [] = "*?[";
Info gargv[MAX_ARGV]; int gargc;
static short readErrors;
void greset() { gargc = 0; readErrors = 0; }

/* Расширить шаблон имен файлов в сами имена */
static void globone(char *pattern, char dirname[]){
     extern char *strdup(); struct stat st;
     DIR *dirf; struct dirent *d;
     if( any(pattern, globchars) == NO ){  /* no glob */
      gargv[gargc]   = NullInfo;
      gargv[gargc].s = strdup(pattern);
      gargc++;
      gargv[gargc]   = NullInfo;
      return;
     }
     if((dirf = opendir(dirname)) == NULL){ readErrors++; goto out; }
     while(d = readdir(dirf)){
       if(match(d->d_name, pattern)){
   char fullname[512];
   if( sorttype != SORT_NOSORT && !strcmp(d->d_name, "."))
       continue;
   /* В корневом каталоге имя ".." следует пропускать */
   if( in_the_root && !strcmp(d->d_name, "..")) continue;
   /* Проверка на переполнение */
   if( gargc == MAX_ARGV - 1){
       free(gargv[gargc-1].s);
       gargv[gargc-1].s  = strdup(" Слишком много файлов!!!");
       gargv[gargc-1].fl = I_SYS;
       break;
   }
   gargv[gargc]     = NullInfo;
   gargv[gargc].s   = strdup(d->d_name);
   sprintf(fullname, "%s/%s", dirname, d->d_name);
   if(stat(fullname, &st) < 0) gargv[gargc].fl |= I_NOSEL;
   else if(isdir(st))          gargv[gargc].fl |= I_DIR;
   else if(isexe(st))          gargv[gargc].fl |= I_EXE;
#ifdef FILF
   gargv[gargc].size = st.st_size;
   gargv[gargc].uid  = st.st_uid;
   gargv[gargc].gid  = st.st_gid;
   gargv[gargc].mode = st.st_mode;
#endif
   gargc++;
       }
     }
     closedir(dirf);
out: gargv[ gargc ] = NullInfo;
}
/* Расширить несколько шаблонов */
Info *glob(char **patvec, char *dirname){
      greset();
      while(*patvec){ globone(*patvec, dirname); patvec++; }
      qsort(gargv, gargc, sizeof(Info), gcmps);
      return blkcpy(gargv);
}
Info *glb(char *pattern, char *dirname){ char *pv[2];
      pv[0] = pattern; pv[1] = NULL; return glob(pv, dirname);
}
/* Прочесть содержимое каталога, если оно изменилось:
 * Вернуть: 0  - каталог не менялся;
 *          1  - изменился;
 *       1000  - изменился рабочий каталог (chdir);
 *          -1 - каталог не существует;
 */
int ReadDir(char *dirname, DirContents *d){
    struct stat st; Info *newFiles;
    int save = YES; /* сохранять метки у файлов ? */
    int dirchanged = NO; /* сделан chdir() ? */

    /* каталог мог быть удален, а мы об этом не извещены */
    if( stat(dirname, &st) < 0 ){
 d->valid = NO; d->lastRead = 0L;
 if(d->files) blkfree(d->files);
 d->files = blkcpy( &NullInfo );
 return (-1); /* не существует */
    } else d->valid = YES;
    /* не изменился ли адрес каталога, хранимого в *d ? */
    if(d->ino != st.st_ino || d->dev != st.st_dev){ /* изменился */
       d->ino  = st.st_ino;   d->dev  = st.st_dev;
       save = NO; d->lastRead = 0L; dirchanged = YES;
    }
    /* не изменилось ли имя каталога ? */
    if( !d->name || strcmp(d->name, dirname)){
 if(d->name) free(d->name); d->name = strdup(dirname);
 /* save=NO; d->lastRead = 0; */
    }
    /* проверим, был ли модифицирован каталог ? */
    if( save==YES && d->files && st.st_mtime == d->lastRead )
 return 0;       /* содержимое каталога не менялось */
    d->lastRead = st.st_mtime;
    newFiles = glb("*", d->name);  /* прочесть содержимое каталога */
    if(save == YES && d->files){
 register Info *p, *q;
 if( !blkcmp(newFiles, d->files)){
      blkfree(newFiles); return 0;  /* не изменилось */
 } /* иначе сохранить пометки */
 for(p= d->files; p->s; p++)
   for(q= newFiles; q->s; ++q)
     if( !strcmp(p->s, q->s)){
  q->fl |= p->fl & ~I_SYS;   break;
     }
    }
    if(d->files) blkfree(d->files);
    d->files = newFiles; d->readErrors = readErrors;
    return 1 + (dirchanged ? 999:0);
    /* каталог изменился */
}
.
     /*      Пример 19      */
/* ________________________файл menu.h __________________________ */
/*                     РОЛЛИРУЕМОЕ МЕНЮ                           */
/* _______________________________________________________________*/
#include              <ctype.h>
#include              <sys/param.h>
#define M_HOT         '\\'      /* горячий ключ */
#define M_CTRL        '\1'      /* признак горизонтальной черты */
#define MXEND(m)      XEND((m)->win,(m)->scrollok)
#define NOKEY        (-33)      /* горячего ключа нет         */
#define MAXLEN       MAXPATHLEN /* макс. длина имен файлов    */
typedef enum { /* Коды, возвращаемые handler-ом (HandlerReply *reply) */
    HANDLER_OUT      = 0,  /* выйти из функции выбора          */
    HANDLER_CONTINUE = 1,  /* читать очередную букву           */
    HANDLER_NEWCHAR  = 2,  /* пойти на анализ кода handler-ом. */
    HANDLER_SWITCH   = 3,  /* пойти на switch()                */
    HANDLER_AGAIN    = 4   /* перезапустить всю функцию выбора */
} HandlerReply;
typedef struct _Menu {          /* паспорт меню               */
    int     nitems;             /* число элементов меню       */
    Info   *items;              /* сам массив элементов       */
    int    *hotkeys;            /* "горячие" клавиши          */
    int     key;  /* клавиша, завершившая выбор */
    int     current;            /* текущая строка списка      */
    int     shift;              /* сдвиг окна от начала меню  */
    int     scrollok;           /* окно роллируемое ?         */
    WINDOW *win;                /* окно для меню              */
    int     left, top, height, width; /* координаты меню на экране и
      размер окна win       */
    int     textwidth, textheight;    /* размер подокна выбора */
    int     bg_attrib;          /* атрибут фона окна           */
    int     sel_attrib;         /* атрибут выбранной строки    */
    char   *title;              /* заголовок меню              */
    Point   savep;
    void  (*showMe)    (struct _Menu *m);
    void  (*scrollBar) (struct _Menu *m, int n, int among);
    int    *hitkeys;            /* клавиши, обрабатываемые особо */
    int   (*handler)   (struct _Menu *m, int c, HandlerReply *reply);

} Menu;
/* Структура окна с меню:
 *--------------*    +0
 |  ЗАГОЛОВОК   |    +1
 *-----------*--*    +2
 |+ стр1ааа  |  |    +3
 |  стр2ббб  |##| <- scroll bar шириной BARWIDTH
 |  стр3ввв  |  |
 *___________|__*
 |DX| len |DX|BS|
 */
/* Метки у элементов меню */
#define M_BOLD       I_DIR      /* яркая строка */
#define M_HATCH      0x08       /* строка тусклая     */
#define M_LFT        0x10       /* для использования в pulldown menu */
#define M_RGT        0x20       /* для использования в pulldown menu */
#define M_LABEL      0x40       /* строка имеет метку */
#define M_LEFT       (-111)
#define M_RIGHT      (-112)
#define TOTAL_NOSEL  (-I_NOSEL)

#define M_SET(m, i, flg)        (((m)->items)[i]). fl |=  (flg)
#define M_CLR(m, i, flg)        (((m)->items)[i]). fl &= ~(flg)
#define M_TST(m, i, flg)        ((((m)->items)[i]).fl &   (flg))
#define M_ITEM(m, i)            ((((m)->items)[i]).s)
 /* Прототипы */
int  MnuInit (Menu *m); void MnuDeinit (Menu *m);
void MnuDrawItem (Menu * m, int y, int reverse, int selection);
int     MnuNext (Menu *m); int     MnuPrev (Menu *m);
int     MnuFirst(Menu *m); int     MnuLast (Menu *m);
int     MnuPgUp (Menu *m); int     MnuPgDn (Menu *m);
int     MnuThis (Menu *m); int     MnuHot  (Menu *m, unsigned c);
int     MnuName (Menu *m, char *name);
void MnuDraw        (Menu *m);     void MnuHide(Menu *m);
void MnuPointAt     (Menu *m, int y);
void MnuPoint       (Menu *m, int line, int eraseOld);
int  MnuUsualSelect (Menu *m, int block);
int is_in(register int c, register int s[]);
char *MnuConvert    (char *s, int *pos);

#define M_REFUSED(m)    ((m)->key < 0 || (m)->key == ESC )
#define MNU_DY           1

/* _______________________ файл menu.c __________________________ */
#include "w.h"
#include "glob.h"
#include "menu.h"
#include <signal.h>
/* ---------------- implementation module ------------------------- */
/* Не входит ли символ в специальный набор? Массив завершается (-1) */
int is_in(register int c, register int s[]){
    while (*s >= 0) {
 if(*s == c) return YES;
 s++;
    }
    return NO;
}
char STRING_BUFFER[ MAXLEN ]; /* временный буфер */
/* Снять пометку с "горячей" клавиши.            */
char *MnuConvert (char *s, int *pos){
    int i = 0;
    *pos = (-1);
    while (*s) {
 if (*s == M_HOT) { *pos = i; s++; }
 else STRING_BUFFER[i++] = *s++;
    }
    STRING_BUFFER[i] = '\0'; return STRING_BUFFER;
}
/* Рамка вокруг окна с меню */
static void MnuWin (Menu *m) {
    WinBorder(m->win, m->bg_attrib, m->sel_attrib,
        m->title, m->scrollok, YES);
}
/* Нарисовать scroll bar в нужной позиции */
static void MnuWinBar (Menu *m) {
    WINDOW *w = m -> win;  /* окно */
    WinScrollBar(m->win, m->scrollok, m->current, m->nitems,
   m->title, m->bg_attrib);
    if(m->scrollBar)  /* может быть еще какие-то наши действия */
       m->scrollBar(m, m->current, m->nitems);
}
/* Роллирование меню */
/*
 +---+----->+-МАССИВ--+<-----+
 |  n|всего |;;;;;;;;;|      | shift сдвиг до окна
     cur|   |      |;;;;;;;;;|      |
 текущий|   |   =ОКНО============<---------|
 элемент|   |   I   ;;;;;;;;;   I   | y строка окна
 0..n-1 |   |   I   ;;;;;;;;;   I   |      |
 +------>I###:::::::::###I<--+      |h высота окна
     |   I   ;;;;;;;;;   I          |
     |   =================<---------+
     |      |;;;;;;;;;|
     +----->|_________|
*/

static void MnuRoll (Menu *ptr,
    int aid,     /* какой новый элемент выбрать (0..n-1) */
    int *cur, int *shift,
    int h,       /* высота окна    (строк)      */
    int n,       /* высота items[] (элементов)  */
    void (*go)   (Menu *p, int y, int eraseOld),
    void (*draw) (Menu *p),
    int DY
) {
    int     y = *cur - *shift; /* текущая строка окна */
    int     newshift;  /* новый сдвиг */
    int     AID_UP, AID_DN;

    if (aid < 0 || aid >= n) return;  /* incorrect */
    if (y   < 0 || y   >= h) return;  /* incorrect */
    AID_UP = MIN (DY, n);
    AID_DN = MAX (0, MIN (n, h - 1 - DY));

    if (aid < *cur && y <= AID_UP && *shift > 0)
 goto scroll;  /* down */
    if (aid > *cur && y >= AID_DN && *shift + h < n)
 goto scroll;  /* up */

    if (*shift <= aid && aid < *shift + h) {
    /* роллировать не надо, а просто пойти в нужную строку окна */
 (*go) (ptr, aid - *shift, YES);
 *cur = aid;      /* это надо изменять ПОСЛЕ (*go)() !!! */
 return;
    }
scroll:
    if      (aid > *cur)   newshift = aid - AID_DN; /* вверх up   */
    else if (aid < *cur)   newshift = aid - AID_UP; /* вниз  down */
    else                   newshift = *shift;

    if (newshift + h > n)  newshift = n - h;
    if (newshift < 0)      newshift = 0;

    *shift = newshift; *cur = aid;
    (*draw) (ptr); /* перерисовать окно */
    (*go)   (ptr, aid - newshift, NO); /* встать в нужную строку окна */
}
/* Инициализация и разметка меню. На входе:
 m->items       Массив строк.
 m->title       Заголовок  меню.
 m->top         Верхняя строка окна (y).
 m->left        Левый край (x).
 m->handler     Обработчик нажатия клавиш или NULL.
 m->hitkeys     Специальные клавиши [] или NULL.
 m->bg_attrib   Цвет фона окна.
 m->sel_attrib  Цвет селекции.
*/
int MnuInit (Menu *m) {
    int len, pos; char *s; register i;

    m -> current  = m -> shift = 0;
    m -> scrollok = m -> key = 0;
    if (m -> hotkeys) { /* уничтожить старые "горячие" ключи */
 free ((char *) m -> hotkeys); m -> hotkeys = (int *) NULL;
    }
 /* подсчет элементов меню */
    for (i = 0; M_ITEM (m, i) != (char *) NULL; i++);
    m -> nitems = i;

 /* отвести массив для "горячих" клавиш */
    if (m -> hotkeys = (int *) malloc (sizeof (int) * m -> nitems)) {
 for (i = 0; i < m -> nitems; i++)
     m -> hotkeys[i] = NOKEY;
    }
 /* подсчитать ширину текста */
    len = m -> title ? strlen (m -> title) : 0;
    for (i = 0; i < m -> nitems; i++) {
 if (*(s = M_ITEM (m, i)) == M_CTRL) continue;
 s = MnuConvert (s, &pos);
 if (m -> hotkeys && pos >= 0)
     m -> hotkeys[i] =
  isupper (s[pos]) ? tolower (s[pos]) : s[pos];
 if ((pos = strlen (s)) > len)
     len = pos;
    }
 /* сформировать окно */
#define BORDERS_HEIGHT (2 +        (m -> title    ? 2 : 0))
#define BORDERS_WIDTH  (2 + 2*DX + (m -> scrollok ? BARWIDTH + 1 : 0))
    m -> height = m->nitems + BORDERS_HEIGHT;
    if (m -> height > LINES * 2 / 3) { /* слишком высокое меню */
 m -> scrollok = BAR_VER;       /* будет роллироваться  */
 m -> height = LINES * 2 / 3;
    }
    if((m -> width = len + BORDERS_WIDTH) > COLS ) m->width = COLS;
    m -> textheight = m->height - BORDERS_HEIGHT;
    m -> textwidth  = m->width  - BORDERS_WIDTH;
 /* окно должно лежать в пределах экрана */
    if( m->top  + m->height > LINES ) m->top  = LINES - m->height;
    if( m->left + m->width  > COLS  ) m->left = COLS  - m->width;
    if( m->top  < 0 ) m->top  = 0;
    if( m->left < 0 ) m->left = 0;

    if( m->win ){ /* уничтожить старое окно */
 KillWin( m->win ); m->win = NULL; }
    if( m->win == NULL ){ /* создать окно и нарисовать основу */
 if((m->win =  newwin(m->height, m->width, m->top, m->left))
     == NULL) return 0;
 keypad(m->win, TRUE); MnuWin(m); MnuDraw(m);
 /* но окно пока не вставлено в список активных окон */
    }
    return ( m->win != NULL );
}
/* Деинициализировать меню */
void MnuDeinit (Menu *m) {
    if( m->win ){ KillWin (m->win); m->win = NULL; }
    if( m->hotkeys ){
 free ((char *) m -> hotkeys); m -> hotkeys = (int *) NULL;
    }
}
/* Спрятать меню */
void MnuHide (Menu *m){ if( m->win ) HideWin(m->win); }
/* Зачистить место для line-той строки окна меню */
static void MnuBox (Menu *m, int line, int attr) {
    register    WINDOW *w = m -> win;
    register    i, xend   = MXEND(m);

    wattrset (w, attr);
    for (i = 1; i < xend; i++)
 mvwaddch (w, line, i, ' ');
    /* ликвидировать последствия M_CTRL-линии */
    wattrset (w, m->bg_attrib);
    mvwaddch (w, line, 0,    VER_LINE);
    mvwaddch (w, line, xend, VER_LINE);
    wattrset (w, m->bg_attrib);
}
/* Нарисовать строку меню в y-ой строке окна выбора */
void MnuDrawItem (Menu *m, int y, int reverse, int selection) {
    register WINDOW *w = m -> win;
    int     pos, l, attr;
    int     ay = WY (m->title, y), ax = WX (0);
    char   *s, c;
    int     hatch, bold, label, cont = NO, under;

    if (y + m -> shift >= 0 && y + m -> shift < m -> nitems) {
 s =    M_ITEM (m, y + m -> shift);
 hatch = M_TST (m, y + m -> shift, I_NOSEL) ||
  M_TST (m, y + m -> shift, M_HATCH);
 bold  = M_TST (m, y + m -> shift, M_BOLD);
 label = M_TST (m, y + m -> shift, M_LABEL);
 under = M_TST (m, y + m -> shift, I_EXE);
    }
    else {  /* строка вне допустимого диапазона */
 s = "~"; label = hatch = bold = NO;
    }
    if (*s == M_CTRL) { /* нарисовать горизонтальную черту */
 int x, xend = MXEND(m);
 wattrset(w, m->bg_attrib);
 for(x=1; x < xend; x++)
  mvwaddch(w, ay, x, HOR_LINE);
 mvwaddch (w, ay, 0,    LEFT_JOIN);
 mvwaddch (w, ay, xend, RIGHT_JOIN);
 wattrset (w, m->bg_attrib);
 return;
    }
    l = strlen(s = MnuConvert (s, &pos));
    c = '\0';
    if (l > m -> textwidth) { /* слишком длинная строка */
 c = s[m -> textwidth];
 s[m -> textwidth] = '\0'; cont = YES;
 if (pos > m -> textwidth) pos = (-1);
    }
    if (selection)
 MnuBox (m, ay, reverse ? m->sel_attrib   : m->bg_attrib);
    wattrset (w, attr = (bold    ? A_BOLD        : 0) |
   (hatch   ? A_ITALICS     : 0) |
   (under   ? A_UNDERLINE   : 0) |
   (reverse ? m->sel_attrib : m->bg_attrib));
    mvwaddstr (w, ay, ax, s);
    if( cont ) mvwaddch(w, ay, ax+m->textwidth, RIGHT_TRIANG);
 /* Hot key letter */
    if (pos >= 0) {
 wattron (w, bold ? A_ITALICS : A_BOLD);
 mvwaddch (w, ay, WX(pos), s[pos]);
    }
    if (label){  /* строка помечена */
 wattrset (w, attr | A_BOLD);
 mvwaddch (w, ay, 1, LABEL);
    }
    if (under){
 wattrset (w, A_BOLD);
 mvwaddch (w, ay, ax-1, BOX_HATCHED);
    }
    if (c) s[m->textwidth] = c;
    wattrset (w, m->bg_attrib);
    SetPoint (m->savep, ay, ax-1);  /* курсор поставить перед словом */
}
/* Выбор в меню подходящего элемента */
int MnuNext (Menu *m) {
    char *s; register y = m -> current;
    for (++y; y < m -> nitems; y++)
      if ((s = M_ITEM (m, y)) && *s != M_CTRL && !M_TST (m, y, I_NOSEL))
  return y;
    return (-1);
}
int MnuPrev (Menu *m) {
    char *s; register y = m -> current;
    for (--y; y >= 0; --y)
      if ((s = M_ITEM (m, y)) && *s != M_CTRL && !M_TST (m, y, I_NOSEL))
  return y;
    return (-1);
}
int MnuPgUp (Menu *m) {
    char *s; register n, y = m -> current;
    for (--y, n = 0; y >= 0; --y) {
      if ((s = M_ITEM (m, y)) && *s != M_CTRL && !M_TST (m, y, I_NOSEL))
   n++;
      if (n == m -> textheight) return y;
    }
    return MnuFirst (m);
}
int MnuPgDn (Menu *m) {
    char *s; register n, y = m -> current;
    for (++y, n = 0; y < m -> nitems; y++) {
      if ((s = M_ITEM (m, y)) && *s != M_CTRL && !M_TST (m, y, I_NOSEL))
  n++;
      if (n == m -> textheight) return y;
    }
    return MnuLast (m);
}
int MnuFirst (Menu *m) {
    char *s; register y;
    for (y = 0; y < m -> nitems; y++)
      if ((s = M_ITEM (m, y)) && *s != M_CTRL && !M_TST (m, y, I_NOSEL))
  return y;
    return (-1);
}
int MnuLast (Menu *m) {
    char *s; register y;
    for (y = m -> nitems - 1; y >= 0; --y)
      if ((s = M_ITEM (m, y)) && *s != M_CTRL && !M_TST (m, y, I_NOSEL))
  return y;
    return (-1);
}
int MnuThis (Menu *m) {
    char *s;
    if (m -> current < 0 || m -> current >= m -> nitems)
 return (-1);  /* error */
    if ((s = M_ITEM (m, m -> current)) &&
  *s != M_CTRL && !M_TST (m, m -> current, I_NOSEL))
 return m -> current;
    return (-1);
}
int MnuName (Menu *m, char *name) {
    char *s; register y; int pos;
    for(y = 0; y < m -> nitems; ++y)
      if ((s = M_ITEM (m, y)) && *s != M_CTRL && !M_TST (m, y, I_NOSEL) &&
    strcmp(name, MnuConvert(s, &pos)) == 0 ) return y;
    return (-1);
}
int MnuHot (Menu *m, unsigned c) {
    register y; char *s;
    if (m -> hotkeys == (int *) NULL)
 return (-1);
    if (c < 0400 && isupper (c))
 c = tolower (c);
    for (y = 0; y < m -> nitems; y++)
 if (c == m -> hotkeys[y] &&
    (s = M_ITEM (m, y)) && *s != M_CTRL && !M_TST (m, y, I_NOSEL))
     return y;
    return (-1);
}
/* Нарисовать содержимое меню для выбора */
void MnuDraw (Menu *m) {
    register    i, j;
    for (i = 0; i < m -> textheight; i++)
 MnuDrawItem (m, i, NO, m -> scrollok ? YES : NO);
}
/* Поставить курсор в line-тую строку окна. */
void MnuPoint(Menu *m, int line,
       int eraseOld /* стирать старую селекцию? */){
    int curline = m->current - m->shift; /* текущая строка окна */
    if (line < 0 || line >= m -> textheight) return;  /* ошибка */
    if (eraseOld && curline != line) /* стереть старый выбор    */
 MnuDrawItem (m, curline, NO, YES);
    MnuDrawItem (m, line, YES, YES); /* подсветить новую строку */
}
/* Перейти к y-той строке массива элементов, изменить картинку  */
void MnuPointAt (Menu *m, int y) { char *s;
    if (y < 0 || y >= m->nitems) return; /* ошибка! */
    if ((s = M_ITEM (m, y)) == NULL || *s == M_CTRL) return;
    MnuRoll (m, y, &m -> current,    &m -> shift,
      m -> textheight,  m -> nitems,
      MnuPoint, MnuDraw, MNU_DY);
    if (m -> scrollok) MnuWinBar(m); /* сдвинуть scroll bar */
    GetBack(m->savep, m->win); /* вернуть курсор в начало строки селекции,
    * откуда он был сбит MnuWinBar-ом */
}
/* Выбор в меню без участия "мыши". */
int MnuUsualSelect (Menu *m, int block) {
    int sel, snew, c, done = 0;

    m -> key = (-1);
    if( ! m->win ) return TOTAL_NOSEL;
    if((sel = MnuThis  (m)) < 0)
    if((sel = MnuFirst (m)) < 0)
 return TOTAL_NOSEL; /* в меню нельзя ничего выбрать */
    RaiseWin   (m->win);    /* сделать окно верхним         */
    MnuPointAt (m, sel);    /* проявить */
    if(m->showMe) m->showMe(m);  /* может быть изменить позицию ? */

    for (;;) {
 c = WinGetch (m->win);
INP:
 if (m -> hitkeys && m -> handler) {
     HandlerReply reply;
     if (is_in (c, m -> hitkeys)) {
  c = (*m -> handler) (m, c, &reply);
     /* восстановить scroll bar */
  MnuPointAt (m, m -> current);
  switch (reply) {
      case HANDLER_CONTINUE:     continue;
      case HANDLER_NEWCHAR:      goto INP;
      case HANDLER_OUT:          goto out;
      case HANDLER_SWITCH:       default:
   break; /* goto switch(c) */
  }
     }
 }
 switch (c) {
     case KEY_UP:
  if ((snew = MnuPrev (m)) < 0)  break;
  goto mv;
     case KEY_DOWN:
 next:
  if ((snew = MnuNext (m)) < 0)  break;
  goto mv;
     case KEY_HOME:
  if ((snew = MnuFirst (m)) < 0) break;
  goto mv;
     case KEY_END:
  if ((snew = MnuLast (m)) < 0)  break;
  goto mv;
     case KEY_NPAGE:
  if ((snew = MnuPgDn (m)) < 0)  break;
  goto mv;
     case KEY_PPAGE:
  if ((snew = MnuPgUp (m)) < 0)  break;
  goto mv;

     case KEY_IC:   /* поставить/снять пометку */
  if (M_TST (m, sel, M_LABEL)) M_CLR (m, sel, M_LABEL);
  else                         M_SET (m, sel, M_LABEL);
  MnuPointAt (m, sel);
     /* Если вы вычеркнете  goto next;
      * и оставите просто   break;
      * то вставьте в это место
      * MnuPoint( m, m->current - m->shift, NO ); */
  goto next;
     case KEY_DC:
  if (M_TST (m, sel, M_HATCH)) M_CLR (m, sel, M_HATCH);
  else                         M_SET (m, sel, M_HATCH);
  MnuPointAt (m, sel); goto next;

     case KEY_LEFT:
  if (block & M_LFT) {
      sel = M_LEFT;  goto out;
  } break;
     case KEY_RIGHT:
  if (block & M_RGT) {
      sel = M_RIGHT; goto out;
  } break;
     case 0: break;
     default:
  if (c == '\n' || c == '\r' || c == ESC)
      goto out;
  if ((snew = MnuHot (m, c)) < 0) {
      beep(); break;
  }
     /* иначе найден HOT KEY (горячая клавиша) */
  done++; goto mv;
 }
 continue;
mv:
 MnuPointAt (m, sel = snew);
 if(done){ wrefresh(m->win); /* проявить новую позицию */ break; }
    }
out: wnoutrefresh(m->win);
     return((m->key = c) == ESC ? -1 : sel);
     /* Меню автоматически НЕ ИСЧЕЗАЕТ: если надо -
      * явно делайте MnuHide(m); после MnuUsualSelect(); */
}
.
 /*      Пример 20      */
/* ______________________________________________________________ */
/*      PULL_DOWN меню (меню-строка)                              */
/* _______________________ файл pull.h __________________________ */
typedef struct {
 Info info;      /* строка в меню */
 Menu *menu;     /* связанное с ней вертикальное меню */
 char *note;     /* подсказка     */
} PullInfo;
typedef struct _Pull {  /* Паспорт меню */
 int nitems;     /* количество элементов в меню  */
 PullInfo *items;/* элементы меню                */
 int *hotkeys;   /* горячие ключи                */
 int key;        /* клавиша, завершившая выбор   */
 int current;    /* выбранный элемент            */
 int space;      /* интервал между элементами меню */
 int bg_attrib;  /* цвет фона строки             */
 int sel_attrib; /* цвет выбранного элемента     */
 Point  savep;
 void  (*scrollBar) (struct _Pull *m, int n, int among);
} PullMenu;
#define PYBEG   0       /* строка, в которой размещается меню */

#define PM_BOLD       I_DIR
#define PM_NOSEL      I_NOSEL
#define PM_LFT        M_LFT
#define PM_RGT        M_RGT

#define PM_SET(m, i, flg)          (m)->items[i].info.fl  |=  (flg)
#define PM_CLR(m, i, flg)          (m)->items[i].info.fl  &= ~(flg)
#define PM_TST(m, i, flg)         ((m)->items[i].info.fl  &   (flg))
#define PM_ITEM(m, i)             ((m)->items[i].info.s)
#define PM_MENU(m, i)             ((m)->items[i].menu)
#define PM_NOTE(m, i)             ((m)->items[i].note)
#define COORD(m, i)               ((m)->space * (i+1) + PullSum(m, i))

int  PullInit(PullMenu *m);
int  PullSum(PullMenu *m, int n);
void PullDraw(PullMenu *m);
int  PullShow(PullMenu *m);
void PullHide(PullMenu *m);
void PullDrawItem(PullMenu *m, int i, int reverse, int selection);
void PullPointAt(PullMenu *m, int y);

int  PullHot(PullMenu *m, unsigned c);
int  PullPrev(PullMenu *m);
int  PullNext(PullMenu *m);
int  PullFirst(PullMenu *m);
int  PullThis(PullMenu *m);
int  PullUsualSelect(PullMenu *m);

#define PullWin          stdscr
#define PM_REFUSED(m)    ((m)->key < 0 || (m)->key == ESC )

/* _______________________ файл pull.c __________________________ */
#include "glob.h"
#include "w.h"
#include "menu.h"
#include "pull.h"

int PullSum(PullMenu *m, int n){
    register i, total; int pos;
    for(i=0, total = 0;  i < n; i++ )
 total += strlen( MnuConvert(PM_ITEM(m, i), &pos ));
    return total;
}
/* Разметка меню. На входе:
 p->items       массив элементов с M_HOT-метками и связанных меню.
 p->bg_attrib   цвет фона строки.
 p->sel_attrib  цвет выбранного элемента.
   Меню всегда размещается в окне stdscr (PullWin).
*/
int PullInit(PullMenu *m){
/* подменю не должны быть инициализированы,
 * т.к. все равно будут сдвинуты в другое место */
 int total, pos; char *s; register i;
 m->key = m->current = 0;
 if(m->hotkeys){
    free((char *) m->hotkeys); m->hotkeys = (int *) NULL;
 }
 /* подсчитать элементы меню */
 m->nitems = 0;
 for( i=0, total = 0; PM_ITEM(m, i) != NULL; i++ ){
    total += strlen(s = MnuConvert(PM_ITEM(m, i), &pos));
    m->nitems++;
 }
 if( total > wcols(PullWin)){  /* меню слишком широкое */
err:            beep(); return 0;
 }
 m->space = (wcols(PullWin) - total - 2) / (m->nitems + 1);
 if( m->space <= 0 ) goto err;
 /* разметить горячие клавиши */
 if( m-> hotkeys = (int *) malloc( sizeof(int) * m->nitems )){
  for(i=0; i < m->nitems; i++ )
   m->hotkeys[i] = NOKEY;
 }
 for( i=0; i < m->nitems; i++ ){
  if( PM_MENU(m,i)){
      PM_MENU(m,i)->left = COORD(m, i) - 1;
      PM_MENU(m,i)->top  = PYBEG + 1;
      PM_MENU(m,i)->bg_attrib  = m-> bg_attrib;
      PM_MENU(m,i)->sel_attrib = m-> sel_attrib;
      if( PM_MENU(m,i)->win )
   MnuDeinit( PM_MENU(m,i));
      MnuInit( PM_MENU(m,i));
  }
  if( m->hotkeys ){
      s = MnuConvert(PM_ITEM(m, i), &pos);
      if( pos >= 0 )
   m->hotkeys[i] =
     isupper(s[pos]) ? tolower(s[pos]) : s[pos];
  }
 }
 keypad(PullWin, TRUE); return 1;
}
/* Проявить pull-down меню */
int PullShow(PullMenu *m){
 register i; int first, last;
 first = last = (-1);
 for(i=0; i < m->nitems; i++ ){
  PM_SET(m, i, PM_LFT | PM_RGT );
  if( !PM_TST(m, i, PM_NOSEL)){
   if( first < 0 ) first = i;
   last = i;
  }
 }
 if( first < 0 ) return (TOTAL_NOSEL);
 if(first == last ){
  PM_CLR(m, first, PM_LFT | PM_RGT );
 }else{
  PM_CLR(m, first, PM_LFT);
  PM_CLR(m, last,  PM_RGT);
 }
 wmove(PullWin, PYBEG, 0);
 wattrset(PullWin, m->bg_attrib);
 wclrtoeol(PullWin);
 PullDraw(m); return 1;
}
void PullDraw(PullMenu *m){ register i;
 for(i=0; i < m->nitems; i++ )
  PullDrawItem(m, i, NO, NO);
}
/* Спрятать pull-down меню. Сама строка остается, подменю исчезают */
void PullHide(PullMenu *m){
 register i;
 for(i=0; i < m->nitems; i++ )
     if( PM_MENU(m, i)) MnuHide( PM_MENU(m, i));
 PullDraw(m);
}
/* Нарисовать элемент меню */
void PullDrawItem(PullMenu *m, int i, int reverse, int selection){
 int x, pos, hatch = PM_TST(m, i, PM_NOSEL );
 char *s;

 x = COORD(m, i); s = MnuConvert( PM_ITEM(m, i), &pos );
 wattrset(PullWin,
  (reverse ? m->sel_attrib : m->bg_attrib) |
  (hatch   ? A_ITALICS     : 0           ));

 /*mvwaddch(PullWin, PYBEG, x-1, reverse ? LEFT_TRIANG  : ' ');*/
 mvwaddstr(PullWin, PYBEG, x, s);
 /*waddch(PullWin,               reverse ? RIGHT_TRIANG : ' ');*/
 if( pos >= 0 ){  /* Hot key letter */
     wattron(PullWin, A_BOLD);
     mvwaddch(PullWin, PYBEG, x + pos, s[pos]);
 }
 wmove   (PullWin,  PYBEG, x-1); SetPoint(m->savep, PYBEG, x-1);
 wattrset(PullWin, m->bg_attrib);
}
int PullPrev(PullMenu *m){
 register y;
 for( y = m->current - 1; y >= 0; y-- )
  if( !PM_TST(m, y, PM_NOSEL )) return y;
 return (-1);
}
int PullNext(PullMenu *m){
 register y;
 for( y = m->current+1; y < m->nitems; y++ )
  if( !PM_TST(m, y, PM_NOSEL)) return y;
 return (-1);
}
int PullFirst(PullMenu *m){
 register y;
 for( y = 0; y < m->nitems; y++ )
  if( !PM_TST(m, y, PM_NOSEL)) return y;
 return (-1);
}
int PullThis(PullMenu *m){
 register y;
 if( m->current < 0 || m->current >= m->nitems )
  return (-1);
 if( PM_TST(m, m->current, PM_NOSEL))
  return (-1);
 return m->current;
}
int PullHot(PullMenu *m, unsigned c){
 register y;
 if( m-> hotkeys == (int *) NULL )
  return (-1);
 if( c < 0400 && isupper(c))
  c = tolower(c);
 for( y=0; y < m->nitems; y++ )
  if( c == m->hotkeys[y] && !PM_TST(m, y, PM_NOSEL))
   return y;
 return (-1);
}
/* Указать на элемент n */
void PullPointAt( PullMenu *m, int n){
 if( n < 0 || n >= m->nitems ) return ; /* error */
 if( n != m->current ){
  if( PM_MENU(m, m->current))
   MnuHide( PM_MENU(m, m->current));
  PullDrawItem( m, m->current, NO, YES );
 }
 m -> current = n;
 PullDrawItem( m, n, YES, YES );
 if( m->scrollBar ){
     m->scrollBar( m, n, m->nitems );
     GetBack(m->savep, PullWin);
 }
}
/* Выбор в меню */
int PullUsualSelect(PullMenu *m){
 int autogo = NO, c, code, done = 0, snew, sel, reply = (-1);

 m->key = (-1);
 if((sel = PullThis(m))  < 0 )
 if((sel = PullFirst(m)) < 0 ) return TOTAL_NOSEL;
 if( PullShow(m) < 0 )         return TOTAL_NOSEL;
 PullPointAt(m, sel);  /* начальная позиция */
 for(;;){
    if( autogo ){  /* Автоматическая проявка подменю */
        if( PM_MENU(m, m->current) == NULL)
        goto ask;
        code = MnuUsualSelect(PM_MENU(m, m->current),
        PM_TST(m, m->current, PM_LFT) |
        PM_TST(m, m->current, PM_RGT));
        MnuHide(PM_MENU(m, m->current));
        c = PM_MENU(m, m->current)->key;
        if(code == (-1)){
    reply = (-1); goto out;
        }
        /* в подменю ничего нельзя выбрать */
        if( code == TOTAL_NOSEL) goto ask;
        /* MnuUsualSelect выдает специальные коды для
  * сдвигов влево и вправо */
        if( code == M_LEFT )     goto left;
        if( code == M_RIGHT )    goto right;
        reply = code; goto out;
    } else
ask:           c = WinGetch(PullWin);
    switch(c){
    case KEY_LEFT:
    left:   if((snew = PullPrev(m)) < 0 ) goto ask;
     goto mv;
    case KEY_RIGHT:
    right:  if((snew = PullNext(m)) < 0 ) goto ask;
     goto mv;
    case ESC:
      reply = (-1); goto out;
    case '\r': case '\n':
      if( PM_MENU(m, m->current) == NULL){ reply = 0; goto out; }
      autogo = YES; break;
    default:
      if((snew = PullHot(m, c)) < 0 ) break;
      if( PM_MENU(m, snew) == NULL){ reply=0; done++; }
      autogo = YES; goto mv;
    }
    continue;
mv:        PullPointAt(m, sel = snew);
    if( done ) break;
 }
out:    wnoutrefresh(PullWin); PullHide(m); m->key = c;
 wattrset(PullWin, A_NORMAL); /* NOT bg_attrib */
 return reply;   /* номер элемента, выбранного в меню
      PM_MENU(m, m->current) */
}
.
     /*      Пример 21     */
/* РЕДАКТОР СТРОКИ И ИСТОРИЯ РЕДАКТИРУЕМЫХ СТРОК                  */
/* _______________________ файл hist.h __________________________ */
/* ИСТОРИЯ. ЗАПОМИНАНИЕ СТРОК И ВЫДАЧА ИХ НАЗАД ПО ТРЕБОВАНИЮ.    */
/* ______________________________________________________________ */
typedef struct {        /* Паспорт истории        */
 Info *list;     /* запомненные строки     */
 int sz;         /* размер истории (макс.) */
 int len;        /* текущее число строк    */
 Menu mnu;       /* меню для выборки из истории */
} Hist;
void HistInit(Hist *h, int n);
void HistAdd (Hist *h, char *s, int fl);
Info *HistSelect(Hist *h, int x, int y);

/* _______________________ файл hist.c __________________________ */
#include "w.h"
#include "glob.h"
#include "menu.h"
#include "hist.h"
/* Проинициализировать новую "историю" емкостью n строк */
void HistInit(Hist *h, int n){
 register i;
 if( h->list ){ blkfree( h->list ); h->list = NULL; }
 h->len = 0;
 h->mnu.title      = "History";
 h->mnu.bg_attrib  = A_NORMAL;
 h->mnu.sel_attrib = A_REVERSE;
 h->list = (Info *) malloc( (n+1) * sizeof(Info));
 if( ! h->list ){
  h->sz = 0; return;
 }else   h->sz = n;
 for( i=0; i < n+1 ; i++ )
  h->list[i] = NullInfo;
}
/* Добавить строку s с меткой fl в историю */
void HistAdd (Hist *h, char *s, int fl){
 register i, j; Info tmp;

 if( h->sz == 0 ) return;
 /* А нет ли уже такой строки ? */
 for( i=0; i < h->len; i++ )
  if( !strcmp(s, h->list[i].s )){   /* есть ! */
   if( i == 0 ) return;      /* первая */
   /* сделать ее первой строкой */
   tmp = h->list[i];
   for( j=i-1; j >= 0; --j )
    h->list[j+1] = h->list[j];
   h->list[0] = tmp;
   return;
  }
 if( h->len < h->sz ){
  for( i=h->len-1; i>= 0; i-- )
   h->list[i+1] = h->list[i];
  h->len ++ ;
 }else{
 /* выкинуть самую старую строку из истории */
  free( h->list[ h->sz - 1 ].s );
  for( i=h->sz - 2; i >= 0; i-- )
   h->list[i+1] = h->list[i];
 }
 (h->list)[0].s = strdup(s); (h->list)[0].fl = fl;
}
/* Выборка строки из истории */
Info *HistSelect(Hist *h, int x, int y){
 if( h->len == 0 ) return (Info *) NULL;
 h->mnu.top = y;
 h->mnu.left = x;
 h->mnu.items = h->list;
 MnuInit( & h->mnu );
 if( h->mnu.hotkeys ){
  register i;
  for(i=0 ; i < h->mnu.nitems; i++ )
      h->mnu.hotkeys[i] = h->list[i].s[0] & 0377;
 }
 MnuUsualSelect( & h->mnu, 0 );
 MnuDeinit     ( & h->mnu );
 if( M_REFUSED ( & h->mnu ))
  return (Info *) NULL;
 return & h->list[ h->mnu.current ];
}

/* _______________________ файл line.h __________________________ */
/* РЕДАКТОР ДЛИННЫХ СТРОК (ВОЗМОЖНО ШИРЕ ЭКРАНА)                  */
/* ______________________________________________________________ */
typedef struct _LineEdit { /* Паспорт редактора строки        */
    WINDOW *win;         /* окно для редактирования           */
    int     width;       /* ширина поля редактирования        */
    int     left, top;   /* координаты поля редактирования в окне */
    int     pos;         /* позиция в строке                  */
    int     shift;       /* число символов скрытых левее поля */
    char   *line;        /* строка которая редактируется      */
    int     maxlen;      /* максимальная длина строки         */
    int     len;         /* текущая длина строки              */
    int     insert;      /* 1 - режим вставки; 0 - замены */
    int     nc;          /* 1 - стирать строку по первому нажатию */
    int     cursorOn;    /* курсор включен (для графики)  */
    int     bg_attrib;   /* цвет текста                   */
    int     fr_attrib;   /* цвет пустого места в поле     */
    int     wl_attrib;   /* цвет краев строки             */
    int     sel_attrib;  /* цвет символа под курсором     */
    Hist    *histIn;     /* история для выборки строк     */
    Hist    *histOut;    /* история для запоминания строк */
    int      key;        /* кнопка, завершившая редактирование */
    Point    savep;
    /* функции проявки и убирания окна (если надо)        */
    int  (*showMe)(struct _LineEdit *le); /* 1 при успехе */
    void (*hideMe)(struct _LineEdit *le);
    void (*posMe) (struct _LineEdit *le); /* установка позиции */
/* Функция рисования scroll bar-а (если надо)         */
void (*scrollBar)(struct _LineEdit *le, int whichbar, int n, int among);
    /* Специальная обработка клавиш (если надо)           */
    int *hitkeys;
    int (*handler)(struct _LineEdit *le, int c, HandlerReply *reply);
}   LineEdit;

void LePutChar( LineEdit *le, int at);
void LeCursorHide( LineEdit *le );
void LeCursorShow( LineEdit *le );
void LePointAt( LineEdit *le, int at );
void LePoint( LineEdit *le, int x, int eraseOld );
void LeDraw( LineEdit *le );
void LeReport( LineEdit *le );
void LeDelCh ( LineEdit *le );
void LeInsCh ( LineEdit *le, int c );
void LeRepCh ( LineEdit *le, int c );
int  LeInsStr( LineEdit *le, char *s);
int  LeWerase( LineEdit *le, char *to );
int  LeEdit( LineEdit *le );
#define LINE_DX 1
#define LE_REFUSED(m)    ((m)->key < 0 || (m)->key == ESC )

/* _______________________ файл line.c __________________________ */
/* Редактор строки. Эта версия была изначально написана           *
 * для графики, поэтому здесь не совсем CURSES-ные алгоритмы      */
#include "w.h"
#include "glob.h"
#include "menu.h"
#include "hist.h"
#include "line.h"

/* Удалить букву из строки */
static char cdelete(register char *s, int at) { char c;
    s += at; if((c = *s) == '\0') return c;
    while( s[0] = s[1] ) s++;     return c;
}
/* Вставить букву в строку */
static void insert(char *s, int at, int c){
 register char *p;
 s += at; p = s;
 while(*p) p++;  /* найти конец строки */
 p[1] = '\0';    /* закрыть строку     */
 for( ; p != s; p-- )
  p[0] = p[-1];
 *s = c;
}
/* Нарисовать видимую часть строки с позиции from */
static void LeDrawLine( LineEdit *le, int from ){
 LeCursorHide( le );
 for( ; from < le->width; from++ )
  LePutChar(le, from);
 /* курсор остается спрятанным */
}
/* Выдать символ строки в позиции at */
void LePutChar( LineEdit *le, int at){
     int off = le->shift + at;
     int bgcolor = le->bg_attrib, wall;
wall =  /* символ на краю поля и строка выходит за этот край ? */
   ( at == 0 && le->shift    ||
   ( at >= le->width - 1 && le->shift + le->width < le->len ));
bgcolor =
   ( off < le->len ) ?                         le->bg_attrib   :
   ( at >= le->width || off >= le->maxlen ) ? (le->bg_attrib | A_ITALICS):
    /* чистое место в поле */                  le->fr_attrib   ;
 wattrset( le->win, wall? le->wl_attrib|A_BOLD|A_ITALICS: bgcolor);
 mvwaddch( le->win, le->top, le->left + at,
    off < le->len ? le->line[off] : ' ' );
 wattrset( le->win, le->bg_attrib);
}
/* Спрятать курсор. x в интервале 0..le->width */
void LeCursorHide( LineEdit *le ){
 int x = le->pos - le->shift;
 if( x < 0 || x > le->width || le->cursorOn == NO )
  return;
 LePutChar( le, x ); le->cursorOn = NO;
}
/* Проявить курсор */
void LeCursorShow( LineEdit *le ){
     int x = le->pos - le->shift, saveattr = le->bg_attrib;

     if( x < 0 || x >  le->width      || le->cursorOn == YES ) return;
     le->bg_attrib =   le->sel_attrib | (le->insert==NO ? A_BOLD : 0);
     LePutChar(le, x); le->bg_attrib  =  saveattr;
     wmove(le->win, le->top, le->left + x); le->cursorOn = YES;
     SetPoint(le->savep, le->top, le->left+x);
}
/* Функция прокрутки длинной строки через окошко */
static void LeRoll( LineEdit *ptr,
   int aid, int *cur, int *shift,
   int width,    /* ширина окна */
   int len, int maxlen,
   void (*go)  (LineEdit *p, int x, int eraseOld),
   void (*draw)(LineEdit *p), /* перерисовщик поля */
   int LDX
){
 int x = *cur - *shift, oldshift = *shift, newshift = oldshift;
 int AID_LFT, AID_RGT,  drawn = NO;

 if( aid < 0 || aid > len   ) return;       /* incorrect */
 if( x   < 0 || x   > width ) return;       /* incorrect */

 AID_LFT = MIN(LDX, maxlen);
 AID_RGT = MAX(0,  MIN(maxlen, width-1 - LDX));

 if( aid < *cur && x <= AID_LFT && oldshift > 0 )
  goto Scroll;
 else if( aid > *cur && x >= AID_RGT && oldshift + width < maxlen )
  goto Scroll;
 if( oldshift <= aid && aid < oldshift + width )
  /* прокрутка не нужна - символ уже видим */
  goto Position;
Scroll:
 if( aid >= *cur )
  newshift = aid - AID_RGT;
 else    newshift = aid - AID_LFT;
 if( newshift + width > maxlen || (len == maxlen && aid == len))
  newshift = maxlen - width;
 if( newshift < 0 )
  newshift = 0;
 if( newshift != oldshift ){
  *shift = newshift; (*draw)(ptr); drawn = YES;
 }
Position:
 if((x = aid - newshift) >= width && len != maxlen )
  beep();      /* ERROR */
 (*go)(ptr, x, !drawn ); *cur = aid;
}
/* Поставить курсор на at-тый символ строки */
void LePointAt( LineEdit *le, int at ){
 /* at == len допустимо */
 if( at < 0 || at > le->len ) return;
 if( le->pos == at ) return;  /* уже на месте */
 LeCursorHide( le );
 LeRoll( le, at, & le->pos, & le->shift,
      le->width, le->len, le->maxlen,
      LePoint,   LeDraw,
      LINE_DX);
 le->pos = at;
 LeCursorShow( le );
}
/* Нарисовать подходящий scroll bar */
void LePoint( LineEdit *le, int x, int eraseOld ){
     if(le->scrollBar)
      (*le->scrollBar)(le, BAR_HOR, x + le->shift, le->maxlen+1 );
     GetBack( le->savep, le->win);
}
/* Нарисовать подходящий scroll bar            */
/* Вызывай это каждый раз, когда len изменится */
void LeReport( LineEdit *le ){
     if(le->scrollBar)
      le->scrollBar (le, BAR_VER, le->len, le->maxlen+1 );
     GetBack( le->savep, le->win);
}
/* Нарисовать видимую часть строки */
void LeDraw( LineEdit *le ){
     LeDrawLine( le, 0);
}
/* Удаление буквы из строки */
void LeDelCh( LineEdit *le ){
     if( le->len <= 0 || le->pos < 0 || le->pos >= le->len ) return;
     LeCursorHide( le );
     (void) cdelete( le->line, le->pos );
     le->len --;
     LeDrawLine( le, le->pos - le->shift );
     LeReport( le );
}
/* Вставка буквы в строку */
void LeInsCh( LineEdit *le, int c ){
     if( le->len < 0 || le->pos < 0 || le->pos > le->len ) return;
     LeCursorHide( le );
     insert( le->line, le->pos, c );
     le->len++;
     LeDrawLine( le, le->pos - le->shift );
     LeReport( le );
}
/* Замена буквы в строке */
void LeRepCh( LineEdit *le, int c ){
     if( le->len <= 0 || le->pos < 0 || le->pos >= le->len ) return;
     LeCursorHide( le );
     le->line[ le->pos ] = c;
     LePutChar( le, le->pos - le-> shift );
}
/* Вставка подстроки в строку редактирования */
int LeInsStr( LineEdit *le, char *s){
    int len = le->len, slen = strlen(s);
    register i;

    if( len + slen > le->maxlen )
       slen = le->maxlen - len;
    if( ! slen ) return 0;

    for( i=0; i < slen ; i ++ )
  insert( le->line, le->pos+i, s[i] );
    le->len += slen;
    LeCursorHide( le );
    LeDrawLine( le, le->pos - le->shift );
    LePointAt( le, le->pos + slen );
    LeReport( le );
    return slen ;
}
/* Стирание слова */
int LeWerase( LineEdit *le, char *to ){
 register i;
 register char *s = le->line;
 char c;

 if( to ) *to = '\0';
 i = le->pos;
 if( s[i] == ' ' || s[i] == '\0' ){
  /* найти конец слова */
  for( --i; i >= 0 ; i-- )
   if( s[i] != ' ' ) break;
  if( i < 0 || le->len == 0 ){
   beep(); return NO; }
 }
 /* найти начало слова */
 for( ; i >= 0 && s[i] != ' ' ; i-- );
 i++;  /* i < 0 || s[i] == ' ' */
 LeCursorHide( le );  LePointAt( le, i );
 while( s[i] != ' ' && s[i] != '\0' ){
  c = cdelete( s, i );
  if( to ) *to++ = c;
  le->len --;
 }
 /* удалить пробелы после слова */
 while( s[i] == ' ' ){
  c = cdelete( s, i );
  le->len --;
 }
 if( to ) *to = '\0';
 LeDrawLine( le, i - le->shift );
 LeReport( le );
 return YES;
}
/* Редактор строки
 le->line                что редактировать.
 le->maxlen              макс. длина строки.
 le->win                 окно, содержащее поле редактирования.
 le->width               ширина поля редактирования.
 le->top                 коорд-ты поля редактирования
 le->left                  в окне win.
 le->insert = YES        режим вставки.
 le->nc     = YES        стирать строку при первом нажатии.
 le->histIn              входная история  или NULL.
 le->histOut             выходная история или NULL.
 le->showMe              функция проявки окна или NULL.
 le->hideMe              функция спрятывания окна или NULL.
 le->hitkeys             специальные клавиши или NULL.
 le->handler             обработчик специальных клавиш или NULL.
 le->scrollBar           рисовалка scroll bar-ов или NULL.
 le->posMe               установка позиции в строке при входе.
 le->bg_attrib           цвет поля.
 le->fr_attrib           цвет незаполненной части поля.
 le->wl_attrib           цвет краев поля при продолжении.
 le->sel_attrib          цвет символа под курсором.
*/
int LeEdit( LineEdit *le ){
    int    c;
    int nchar = 0;  /* счетчик нажатых клавиш */
    Info *inf;

 /* проявить окно */
    if( le->showMe )
 if( (*le->showMe) (le) <= 0 )
  return (-1);
    if( !le->win ) return (le->key = -1);
Again:
    le -> pos = 0;
    le -> len = strlen( le->line );
    le -> shift = 0;
    le -> cursorOn = NO;
    le->key = (-1);

    LeDraw( le );
    if(le->posMe) (*le->posMe)(le);
    LePointAt(le, le->pos );
    LePoint( le,  le->pos - le->shift, NO );
    LeReport( le );

    for (;;) {
 LeCursorShow( le );

 c = WinGetch(le->win); /* прочесть символ с клавиатуры */
 nchar++;               /* число нажатых клавиш         */
INP:
 if( le->hitkeys && le->handler ){
  HandlerReply reply;
  if( is_in(c, le->hitkeys)){ /* спецсимвол ? */
   c = (*le->handler)(le, c, &reply);
   /* Восстановить scroll bars */
   LePoint( le, le->pos - le->shift, NO );
   LeReport( le );

   switch( reply ){
   case HANDLER_CONTINUE:  continue;
   case HANDLER_NEWCHAR:   goto INP;
   case HANDLER_OUT:       goto out;
   case HANDLER_AGAIN:     /* reset */
        LeCursorHide(le);  goto Again;
   case HANDLER_SWITCH:
   default:        break;  /* goto switch(c) */
   }
  }
 }
sw:
 switch (c) {
     case KEY_RIGHT:     /* курсор вправо */
  if (le->pos != le->len && le->len > 0)
      LePointAt( le, le->pos + 1);
  break;

     case KEY_LEFT:      /* курсор влево */
  if (le->pos > 0)
      LePointAt(le, le->pos - 1);
  break;

     case '\t':          /* табуляция вправо */
  if (le->pos + 8 > le->len)
      LePointAt(le, le->len);
  else
      LePointAt(le, le->pos + 8);
  break;

     case KEY_BACKTAB:   /* табуляция влево */
     case ctrl('X'):
  if( le->pos - 8 < 0 )
   LePointAt(le, 0);
  else    LePointAt(le, le->pos - 8 );
  break;

     case KEY_HOME:      /* в начало строки */
  LePointAt(le, 0); break;

     case KEY_END:       /* в конец строки KEY_LL */
  if( le->len > 0 )
    LePointAt(le, le->len);
  break;

     case 0177:          /* стереть символ перед курсором */
     case KEY_BACKSPACE:
     case '\b':
  if (le->pos == 0) break;
  LePointAt(le, le->pos - 1);   /* налево */
  /* и провалиться в DC ... */

     case KEY_F (6):     /* стереть символ над курсором */
     case KEY_DC:
  if (! le->len || le->pos == le->len)
      break;
  LeDelCh(le);
  break;

     case KEY_UP:        /* вызвать историю */
     case KEY_DOWN:
     case KEY_NPAGE:
     case KEY_PPAGE:
     case KEY_F(4):
  if( ! le->histIn ) break;
  /* иначе позвать историю */
  inf = HistSelect( le->histIn,
   wbegx(le->win) + le->pos - le->shift + 2, le->top + 1);
  if( inf == (Info *) NULL )
   break;
  LeCursorHide( le );
  strncpy( le->line, inf->s, le->maxlen );
  goto Again;

out:        case '\r': case '\n': case ESC:
     /* ввод завершен - выйти */
  LeCursorHide( le );
  if( c != ESC && le->histOut && *le->line )
  /* запомнить строку в историю */
   HistAdd( le->histOut, le->line, 0);
  if( le->hideMe ) /* спрятать окно */
   (*le->hideMe)(le);
  return (le->key = c);

     case KEY_F (8):     /* стереть всю строку */
     case ctrl('U'):
  le->line[0] = '\0';
  le->len = le->pos = le->shift = 0;
  LeCursorHide( le );
  LeReport( le );
  goto REWRITE;

     case KEY_F(0):      /* F10: стереть до конца строки */
  if( le->pos == le->len ) break;
  le->line[ le->pos ] = '\0';
  le->len = strlen( le->line );
  LeCursorHide( le );
  LeDrawLine( le, le->pos - le->shift );
  LeReport( le );
  break;

     case ctrl('W'): /* стереть слово */
  LeWerase( le, NULL );
  break;

     case ctrl('A'): /* перерисовка */
  LeCursorHide(le); /* RedrawScreen(); */
REWRITE:        LeDraw(le);
  break;

     case KEY_F(7):  /* переключить режим вставки/замены */
  le->insert = ! le->insert;
  LeCursorHide( le );
  break;
#ifndef M_UNIX
     case ctrl('V'): /* ввод заэкранированного символа */
  nchar--;
  c = WinGetch(le->win);
  nchar++;
  if( c >= 0400 ) goto sw;
  goto Input;
#endif
     case 0: break;
     default:        /* ввод обычного символа */
  if (c >= 0400 || ! isprint(c)) break;
 Input:  if( le->nc && nchar == 1 && le->insert &&
        /*le->pos == 0 &&*/ le->len != 0 ){
     /* если это первая нажатая кнопка, то
     /* удалить все содержимое строки
     /* и заменить ее нажатой буквой */
         le->shift = 0;
         le->len = le->pos = 1;
         le->line[0] = c;
         le->line[1] = '\0';
         LeCursorHide( le );
         LeReport( le );
         goto REWRITE;
  }
  if (!le->insert) {
  /* REPLACE - режим замены */
      if (le->pos == le->len)
   goto AddChar;  /* временный INSERT */
      LeRepCh( le, c );
      LePointAt( le, le->pos + 1 );
  } else {
  /* INSERT - режим вставки */
AddChar:
      if( le->len >= le->maxlen ){
   beep();      /* строка переполнена */
   break;
      }
      LeInsCh( le, c );
      LePointAt( le, le->pos + 1 );
  }               /* endif */
 }                       /* endswitch */
    }                           /* endfor */
}                               /* endfunc */
.
 /*      Пример 22      */
/* ______________________________________________________________ */
/*      ПРЯМОУГОЛЬНАЯ ТАБЛИЦА-МЕНЮ                                */
/* _______________________ файл table.h _________________________ */
typedef struct _Table { /* Паспорт таблицы                */
 int nitems;     /* количество элементов в таблице */
 Info *items;    /* массив элементов               */
 char *fmt;      /* формат вывода                  */
 int key;        /* кнопка, завершившая выбор в таблице */

 int current;    /* номер выбранного элемента      */
 int shift;      /* число элементов перед окном    */
 WINDOW *win;    /* окно в котором размещена таблица */
 int left, top, height, width; /* размеры и расположение
      таблицы в окне */
 int space;      /* интервал между колонками   */
 int elen;       /* макс. ширина колонки       */
 int tcols;      /* число колонок в таблице    */
 int cols;       /* число колонок в видимой части таблицы       */
 int cutpos;     /* позиция для обрубания слишком длинных строк */
 int scrollok;   /* роллируется ? */
 int exposed;    /* нарисована  ? */
 int elems;      /* текущее число эл-тов в подокне      */
 int maxelems;   /* максимальное число эл-тов в подокне */
 int maxshift;   /* максимальный сдвиг */
 int bg_attrib, sel_attrib;      /* цвет фона и выбранного
        элемента */
 Point   savep;
 /* Функции проявки/спрятывания окна */
 int  (*showMe)(struct _Table *tbl);
 void (*hideMe)(struct _Table *tbl);
void (*scrollBar)(struct _Table *tbl, int whichbar, int n, int among);
 /* Обработчик специальных клавиш */
 int *hitkeys;
 int (*handler)(struct _Table *tbl, int c, HandlerReply *reply);
} Table;

#define T_BOLD       M_BOLD
#define T_NOSEL      I_NOSEL
#define T_HATCH      M_HATCH
#define T_LABEL      M_LABEL

#define T_SET(m, i, flg)        (((m)->items)[i]).fl  |=  (flg)
#define T_CLR(m, i, flg)        (((m)->items)[i]).fl  &= ~(flg)
#define T_TST(m, i, flg)        ((((m)->items)[i]).fl &   (flg))

#define T_ITEM(m, i)            ((((m)->items)[i]).s)
/* Формат 'd' ниже вставлен лишь для текущего состояния использования
 * форматов в нашем проекте:      */
#define T_ITEMF(m, i, cut)        \
  ((m)->fmt && *(m)->fmt != 'd' ? \
 TblConvert(T_ITEM((m), i), (m)->fmt, (cut)) : T_ITEM((m), i))
#define T_VISIBLE(tbl, new)   ((tbl)->exposed == YES && \
  (new) >= (tbl)->shift && (new) < (tbl)->shift + (tbl)->elems)
#define TLABSIZE        2  /* ширина поля меток */
#define T_REFUSED(t) ((t)->key < 0 || (t)->key == ESC )

int TblCount( Table *tbl );
void TblInit( Table *tbl, int forcedOneColumn );

void TblChkCur  ( Table *tbl );
int  TblChkShift( Table *tbl );
void TblChk     ( Table *tbl );
char *TblConvert( char *s, char *fmt, int cutpos );

void TblPointAt ( Table *tbl, int snew );
void TblPoint   ( Table *tbl, int snew, int eraseOld );
void TblDraw    ( Table *tbl );
void TblDrawItem( Table *tbl, int at, int reverse, int selection);
void TblBox     ( Table *tbl, int at, int reverse, int hatched,
    int width, int axl, int axi, int ay);
int  TblClear( Table *tbl );
int  TblPlaceByName( Table *tbl, char *p );
void TblReport( Table *tbl );

void TblTag  ( Table *tbl, int at, int flag);
void TblUntag( Table *tbl, int at, int flag);
void TblRetag( Table *tbl, int at, int flag);
void TblTagAll( Table *tbl, char *pattern, int flag );
void TblUntagAll( Table *tbl, char *pattern, int flag );

int  TblUsualSelect( Table *tbl );

/* _______________________ файл table.c _________________________ */
#include "w.h"
#include "glob.h"
#include "menu.h"
#include "table.h"
extern char STRING_BUFFER[MAXLEN]; /* imported from menu.c */
/* надо указать размер, чтоб работал sizeof(STRING_BUFFER) */

/* Переформатировать строку по формату fmt для выдачи в таблицу.
 * Пока предложена простейшая интерпретация. */
char *TblConvert( char *s, char *fmt, int cutpos ){
    if( fmt && *fmt == 'd'){
 register i, j, len; char *p = strrchr(s, '.');
 if((len = strlen(s)) < DIR_SIZE && *s != '.' && p ){
   int sufxlen = strlen(p);
   for(i=0; i < len - sufxlen  ; ++i) STRING_BUFFER[i] = s[i];
   for(; i < DIR_SIZE - sufxlen; ++i) STRING_BUFFER[i] = ' ';
   for(j=0; i < DIR_SIZE; j++,   ++i) STRING_BUFFER[i] = p[j];
   STRING_BUFFER[i] = '\0';
 } else strcpy(STRING_BUFFER, s);
 if(cutpos > 0 && cutpos < sizeof(STRING_BUFFER))
  STRING_BUFFER[cutpos] = '\0';
    } else {  /* без формата, только обрубание */
       if( cutpos <= 0 ) cutpos = 32000; /* Обрубание выключено */
       strncpy(STRING_BUFFER, s, MIN(sizeof(STRING_BUFFER) - 1, cutpos));
    }
    return STRING_BUFFER;
}
/* Обрубить s до длины cutpos букв */
char *TblCut( char *s, int cutpos ){
    if( cutpos <= 0 ) return s;
    strncpy(STRING_BUFFER, s, MIN(sizeof(STRING_BUFFER) - 1, cutpos));
 return STRING_BUFFER;
}
/* Подсчет элементов таблицы и ширины столбца */
int TblCount( Table *tbl ){
 register i, L, LL; char *s;
 L = i = 0;
 if( tbl->items)
     while((s = T_ITEM(tbl, i)) != NULL ){
  if( tbl->fmt )
   s = TblConvert(s, tbl->fmt, 0);
  LL = strlen(s);
  if( LL > L ) L = LL;
  i++;
     }
 tbl->nitems = i; return L;
}
/* Разметка таблицы. На входе:
 t->items          Массив данных, показываемый в меню.
 t->exposed = NO   Таблица уже нарисована ?
 t->fmt            Формат строк, выводимых в таблицу.
 t->win            Окно для размещения таблицы.
 t->showMe         Функция проявки окна.
 t->hideMe         Функция упрятывания окна.
 t->hitkeys        Специальные клавиши []. Конец -1.
 t->handler        Обработчик или NULL.
 t->width          Ширина поля таблицы.
 t->height         Высота поля таблицы.
 t->left           Левый край таблицы в окне.
 t->top            Верхний край таблицы в окне.
 t->scrollBar      Функция рисования scroll-bar-а или NULL.
 t->bg_attrib      Цвет фона (== цвету фона окна).
 t->sel_attrib     Цвет выбранного элемента.
 forcedOneColumn == YES делает таблицу в 1 колонку.
*/
void TblInit( Table *tbl, int forcedOneColumn ){
 int mlen = TblCount( tbl ); /* самый широкий элемент таблицы */
     /* усечь до ширины таблицы */
 if( mlen > tbl->width || forcedOneColumn )
  mlen = tbl->width; /* слишком широко */
/* ширина столбца таблицы = ширина элемента + поле меток + разделитель */
 tbl->elen = mlen + TLABSIZE + 1;
 /*     #####строка_элемент|     */
 /*     метки   элемент    1     */
     /* число столбцов во всей таблице */
 tbl->tcols = (tbl->nitems + tbl->height - 1) / tbl->height;
     /* число столбцов, видимых через окно (+1 для ошибок округления) */
 tbl->cols  = tbl->width / (tbl->elen + 1);
 if( tbl->cols == 0 ){   /* слишком широкая таблица */
  tbl->cols = 1;  /* таблица в одну колонку  */
  tbl->elen = tbl->width - 2;
  mlen = tbl->elen - (TLABSIZE + 1);
  tbl->cutpos = mlen;  /* и придется обрубать строки */
 } else  tbl->cutpos = 0;     /* без обрубания              */
 tbl->cols  = MIN(tbl->cols, tbl->tcols);
     /* интервал между колонками */
 tbl->space = (tbl->width - tbl->cols * tbl->elen)/(tbl->cols+1);
 if( tbl->space < 0 ){ beep(); tbl->space = 0; }
     /* сколько элементов умещается в окно */
 tbl->maxelems = tbl-> cols *  tbl->height;
 tbl->maxshift = (tbl->tcols * tbl->height) - tbl->maxelems;
 if( tbl->maxshift < 0 ) tbl->maxshift = 0;
     /* требуется ли роллирование таблицы через окно */
 tbl->scrollok = (tbl->nitems > tbl->maxelems);
 tbl->elems    = tbl->shift = tbl->current = 0;  /* пока */
 tbl->exposed  = NO;     /* таблица еще не нарисована */
 tbl->key      = (-1);
}
/* Проверить корректность текущей позиции */
void TblChkCur( Table *tbl ){
 if( tbl->current >= tbl->nitems )
  tbl->current = tbl->nitems - 1;
 if( tbl->current < 0 )
  tbl->current = 0;
}
/* Проверить корректность сдвига (числа элементов ПЕРЕД окном) */
int TblChkShift( Table *tbl ){
 register int oldshift = tbl->shift;
 /* в колонке должно быть видно достаточно много элементов */
 if( tbl->cols == 1 &&   /* таблица в 1 колонку */
     tbl->tcols > 1 &&   /* но всего в ней не одна колонка */
     tbl->nitems - tbl->shift < tbl->height / 2 + 1
 )   tbl->shift = tbl->nitems - (tbl->height/2 + 1);

 if( tbl->shift > tbl->maxshift )
     tbl->shift = tbl->maxshift;
 if( tbl->shift < 0 )
     tbl->shift = 0;
 return tbl->shift != oldshift; /* скорректировано ? */
}
/* Проверить корректность параметров таблицы */
void TblChk( Table *tbl ){
again:
     TblChkCur( tbl ); TblChkShift( tbl );
     if( tbl -> maxelems ){
       if( tbl -> current >= tbl->shift + tbl->maxelems ){
    tbl->shift = tbl->current - (tbl->maxelems - 1);
    goto again;
       }
       if( tbl->current < tbl->shift ){
    tbl->shift = tbl->current; goto again;
       }
     }
}
/* Указать на snew-тый элемент списка, перерисовать картинку */
void TblPointAt( Table *tbl, int snew ){
     int curCol; /* текущий столбец всей таблицы (для current) */
     int newCol; /* нужный столбец таблицы       (для snew)    */
     int colw;   /* нужный столбец ОКНА          (для snew)    */
     int gap;    /* зазор */
     int newshift = tbl->shift; /* новый сдвиг окна от начала массива */
     int drawn = NO;  /* таблица целиком перерисована ? */

     /* ПРоверить корректность номера желаемого элемента */
     if( snew < 0 ) snew = 0;
     if( snew >= tbl->nitems ) snew = tbl->nitems - 1;

     if( tbl->current == snew && tbl->exposed == YES)
  return; /* уже стоим на требуемом элементе */
#define WANTINC 1
#define WANTDEC (tbl->cols-1-WANTINC)
 gap = (tbl->height - (tbl->shift % tbl->height)) % tbl->height;
     /* gap - это смещение, которое превращает строгую
 постолбцовую структуру
   --0--        --3--
   --1--        --4--
   --2--        --5--
 в сдвинутую структуру
      ____                          |------
    gap=2___/    пусто g0     --1-- g3     |    --4-- g6      ....
     \____пусто g1     --2-- g4     |    --5-- g7
   --0-- g2     --3-- g5     |    --6-- g8
        |------ shift=4
 */
/* операция прокрутки данных через таблицу: TblRoll() _________________*/
 /* Элемент уже виден в текущем окне ?     */
 /* Параметр elems вычисляется в TblDraw() */
 if( T_VISIBLE(tbl, snew))
  goto ThisWindow;

 /* smooth scrolling (гладкое роллирование) */
 if( snew == tbl->shift + tbl->elems &&
     /* элемент непосредственно следующий ЗА окном */
     tbl->current == tbl->shift + tbl->elems - 1
     /* курсор стоит в нижнем правом углу окна */
 ){
     newshift++; gap--;
     if ( gap < 0 ) gap = tbl->height - 1 ;
     goto do_this;
 }
 if( snew == tbl->shift - 1  &&
     /* элемент непосредственно стоящий ПЕРЕД окном */
     tbl->current == tbl->shift
     /* и курсор стоит в верхнем левом углу окна таблицы */
 ){
     newshift --; gap = (gap + 1) % tbl->height;
     goto do_this;
 }

 /* jump scrolling (прокрутка скачком) */

 curCol = (tbl->current+gap) / tbl->height;
 newCol = (snew        +gap) / tbl->height;
 if( tbl->cols > 1 ){
  if( newCol > curCol ) colw = WANTINC;
  else                  colw = WANTDEC;
 } else  colw = 0;
 newshift = (newCol - colw) * tbl->height  -  gap ;

do_this:
 if( tbl->shift != newshift || tbl->exposed == NO){
    tbl->shift = newshift;
    TblChkShift( tbl ); /* >= 0  && <= max */
    TblDraw( tbl );     /* перерисовать все окно с нового места */
    drawn = YES;        /* перерисовано целиком */
 }
ThisWindow: /* поставить курсор в текущем окне без перерисовки окна */
 TblPoint( tbl,  snew, !drawn );
 /* tbl->current = snew; сделается в TblPoint() */
}
/* Поставить курсор на элемент в текущем окне */
void TblPoint ( Table *tbl, int snew, int eraseOld ){
 if( ! T_VISIBLE(tbl, snew)){
  beep(); /* ERROR !!! */ return;
 }
 if( eraseOld && tbl->current != snew )
     TblDrawItem( tbl, tbl->current, NO, YES );
 TblDrawItem( tbl, snew, YES, YES );
 tbl->current = snew;
 TblReport( tbl );
}
/* Нарисовать scroll bar в нужной позиции. Кроме того,
 * в эту функцию можно включить и другие действия, например
 * выдачу имени T_ITEM(tbl, tbl->current) на рамке окна. */
void TblReport( Table *tbl ){
     if ( tbl->scrollBar )
 (*tbl->scrollBar)( tbl, BAR_VER|BAR_HOR,
      tbl->current, tbl->nitems);
     GetBack( tbl->savep, tbl->win ); /* курсор на место ! */
}
/* Перерисовать все окно таблицы */
void TblDraw( Table *tbl ){
     register next;
     /* число элементов в таблице (может остаться незанятое
      * место в правой нижней части окна */
     tbl->elems = MIN(tbl->nitems - tbl->shift, tbl->maxelems );
     for( next = 0; next < tbl->maxelems; next++ )
       TblDrawItem(tbl, next + tbl->shift, NO, tbl->scrollok ? YES : NO);
     tbl->exposed = YES; /* окно изображено */
}
/* Нарисовать элемент таблицы */
void TblDrawItem( Table *tbl, int at, int reverse, int selection){
     register WINDOW *w = tbl->win;
     int pos; char *s; int hatch, bold, label, under;
     int ax, axl, ay, column;

     if( at >= 0 && at < tbl->nitems ){
  s =     T_ITEM( tbl, at );
  if( tbl->fmt )
      s = TblConvert(s, tbl->fmt, tbl->cutpos);
  else if( tbl->cutpos > 0 )
      s = TblCut(s, tbl->cutpos);
  /* выделения */
  hatch = T_TST( tbl,  at, T_HATCH );
  bold  = T_TST( tbl,  at, T_BOLD  );
  label = T_TST( tbl,  at, T_LABEL );
  under = T_TST( tbl,  at, I_EXE   );
     } else { s = "~"; label = hatch = bold = under = NO; }

     at -= tbl->shift; /* координату в списке перевести в коорд. окна */
     ay  = tbl->top + at % tbl->height;
     column = at / tbl->height;
     /* начало поля меток */
     axl = tbl->left + tbl->space + column * (tbl->space + tbl->elen);
     /* начало строки-элемента */
     ax = axl + TLABSIZE;
     if(selection)
       TblBox( tbl, at, reverse, reverse && hatch, strlen(s), axl, ax, ay );
     wattrset (w, reverse ? tbl->sel_attrib : tbl->bg_attrib);
     if( hatch ) wattron(w, A_ITALICS);
     if( bold  ) wattron(w, A_BOLD);
     if( under ) wattron(w, A_UNDERLINE);
     mvwaddstr(w, ay, ax, s);
     wattrset(w, tbl->bg_attrib | (bold ? A_BOLD:0));
     if( label )                      mvwaddch(w, ay, axl,   LABEL);
     if( under ){ wattron(w, A_BOLD); mvwaddch(w, ay, axl+1, BOX_HATCHED);}
     wattrset(w, tbl->bg_attrib);
     if( column != tbl->cols-1 ) /* не последний столбец */
 mvwaddch(w, ay, axl+tbl->elen-1 + (tbl->space+1)/2, VER_LINE);
     wmove(w, ay, ax-1);             /* курсор перед началом строки   */
     SetPoint(tbl->savep, ay, ax-1); /* запомнить координаты курсора */
}
/* Зачистить область окна для рисования элемента таблицы */
void TblBox(Table *tbl, int at, int reverse, int hatched,
     int width, int axl, int axi, int ay){
     register WINDOW *w = tbl->win;
     int len = tbl->elen;

     wattrset (w, tbl->bg_attrib);
     wboxerase(w, axl, ay, axl+len-1, ay);
     wattrset (w, reverse ? tbl->sel_attrib : tbl->bg_attrib);
     /* если ниже задать   axl+len+1, то подсвеченный
      * прямоугольник будет фиксированного размера    */
     wboxerase(w, axi, ay, axl+width-1, ay);
     wattrset (w, tbl->bg_attrib);
}
/* Зачистить прямоугольную рабочую область окна tbl->win,
 * в которой будет изображаться таблица.
 * Эта функция нигде не вызывается ЯВНО, поэтому ВЫ должны
 * вызывать ее сами после каждого TblInit() -
 * для этого удобно поместить ее в демон (*showMe)();
 */
int TblClear( Table *tbl ){
 tbl->exposed = NO;
 tbl->elems = 0;   /* Это всегда происходит при exposed:= NO */
 wboxerase( tbl->win,
  tbl->left, tbl->top,
  tbl->left + tbl->width - 1,
  tbl->top  + tbl->height - 1);
 return 1;
}
/* Пометить элемент в таблице */
void TblTag( Table *tbl, int at, int flag){
 if( T_TST(tbl, at, flag)) return;
 T_SET(tbl, at, flag);
 if( T_VISIBLE(tbl, at))
     TblDrawItem(tbl, at, tbl->current == at ? YES:NO, YES );
}
/* Снять пометку с элемента таблицы */
void TblUntag( Table *tbl, int at, int flag){
 if( ! T_TST(tbl, at, flag)) return;
 T_CLR(tbl, at, flag);
 if( T_VISIBLE(tbl, at))
     TblDrawItem(tbl, at, tbl->current == at ? YES:NO, YES );
}
/* Изменить пометку элемента таблицы */
void TblRetag( Table *tbl, int at, int flag){
 if( T_TST(tbl, at, flag)) T_CLR(tbl, at, flag);
 else                      T_SET(tbl, at, flag);
 if( T_VISIBLE(tbl, at))
     TblDrawItem(tbl, at, tbl->current == at ? YES:NO, YES );
}
/* Используется в match() для выдачи сообщения об ошибке */
void TblMatchErr(){}
/* Пометить элементы, чьи имена удовлетворяют шаблону */
void TblTagAll( Table *tbl, char *pattern, int flag ){
     register i;
     for(i=0; i < tbl->nitems; i++)
     if( !T_TST(tbl, i, I_DIR) && match( T_ITEMF(tbl, i, 0), pattern))
   TblTag( tbl, i, flag );
}
/* Снять пометки с элементов по шаблону имени */
void TblUntagAll( Table *tbl, char *pattern, int flag ){
     register i;
     for(i=0; i < tbl->nitems; i++)
  if( match( T_ITEMF(tbl, i, 0), pattern))
      TblUntag( tbl, i, flag );
}
/* Указать на элемент по шаблону его имени */
int TblPlaceByName( Table *tbl, char *p ){
 register i; char *s;

 for( i=0; i < tbl->nitems; i++ ){
      s = T_ITEMF(tbl, i, 0);
      if( match( s, p )){
   if( tbl->exposed == NO ){
       /* Задать некорректный shift,
        * чтобы окно полностью перерисовалось */
       tbl->shift = tbl->nitems+1; tbl->elems = 0;
   }
   TblPointAt( tbl, i );
   return i;
      }
 } return (-1);
}
/* Перемещение по таблице набором первых букв названия элемента */
static int TblTrack( Table *tbl, int c){
 char *s; register i;
 int from;  /* с какого элемента начинать поиск */
 int found   = 0; /* сколько было найдено */
 int plength = 0;
 int more    = 0;
 char pattern[20];

 if( c >= 0400 || iscntrl(c)){ beep(); return 0; }
AddCh:
 from = 0;
 pattern[plength] = c;
 pattern[plength+1] = '*';
 pattern[plength+2] = '\0';
 plength++;
More:
 for(i = from; i < tbl->nitems; i++){
     s = T_ITEMF(tbl, i, 0);
     if( match(s, pattern)){
  ++found; from = i+1;
  TblPointAt( tbl, i );
  c = WinGetch( tbl->win );

  switch(c){
  case '\t':   /* find next matching */
   more++;
   goto More;
  case KEY_BACKSPACE: case '\177': case '\b':
   if( plength > 1 ){
    plength--;
    pattern[plength]   = '*';
    pattern[plength+1] = '\0';
    from = 0; more++;
    goto More;
   } else goto out;
  default:
   if( c >= 0400 || iscntrl(c))        return c;
   if( plength >= sizeof pattern - 2 ) goto out;
   goto AddCh;
  }
     }
 }
 /* не найдено */
 if(more && found){ /* нет БОЛЬШЕ подходящих, но ВООБЩЕ - есть */
        beep(); more = found = from = 0; goto More; }
out:    beep(); return 0;
}
/* Выбор в таблице */
int TblUsualSelect( Table *tbl ){
 int c, want;

 tbl->key = (-1);
 if( tbl->items == NULL || tbl->nitems <= 0 ) return TOTAL_NOSEL;
 TblChk( tbl );
 if( tbl->showMe )
  if((*tbl->showMe)(tbl) <= 0 )
   return (-1);
 if( !tbl->win ) return TOTAL_NOSEL;
 if( tbl->exposed == NO ){
     TblDraw ( tbl );
 }
 /* Указать текущий элемент */
 TblPoint( tbl, tbl->current, NO);
 TblReport( tbl );
 for( ;; ){
  c = WinGetch(tbl->win);
 INP:
  if( tbl->hitkeys && tbl->handler ){
      HandlerReply reply;
      if( is_in(c, tbl->hitkeys)){
   c = (*tbl->handler)(tbl, c, &reply);
   TblReport( tbl ); /* restore scroll bar */
   switch( reply ){
   case HANDLER_CONTINUE:   continue;
   case HANDLER_NEWCHAR:    goto INP;
   case HANDLER_OUT:        goto out;
   case HANDLER_SWITCH:
   default: break;  /* goto switch(c) */
   }
      }
  }
sw:             switch( c ){
  case KEY_LEFT:
   want = tbl->current - tbl->height; goto mv;
  case KEY_RIGHT:
   want = tbl->current + tbl->height; goto mv;
  case KEY_UP:
   want = tbl->current - 1; goto mv;
  case KEY_DOWN:
  next:
   want = tbl->current + 1; goto mv;
  case KEY_HOME:
   want = 0;                goto mv;
  case KEY_END:
   want = tbl->nitems - 1;  goto mv;
  case KEY_NPAGE:
   want = tbl->current + tbl->elems; goto mv;
  case KEY_PPAGE:
   want = tbl->current - tbl->elems; goto mv;
  case KEY_IC:
   if( T_TST(tbl, tbl->current, T_LABEL ))
    T_CLR(tbl, tbl->current, T_LABEL );
   else    T_SET(tbl, tbl->current, T_LABEL);

   if( tbl->current == tbl->nitems - 1 /* LAST */){
    TblPoint(tbl, tbl->current, NO );
    break;
   }
   TblPointAt(tbl, tbl->current );
   /* if not       goto next;
    * but          break;
    * then use
    *      TblPoint(tbl, tbl->current, NO);
    * here
    */
   goto next;

  case KEY_DC:
   if( T_TST(tbl, tbl->current, T_HATCH ))
    T_CLR(tbl, tbl->current, T_HATCH );
   else    T_SET(tbl, tbl->current, T_HATCH);

   if( tbl->current == tbl->nitems - 1 /* LAST */){
    TblPoint(tbl, tbl->current, NO );
    break;
   }
   TblPointAt(tbl, tbl->current );
   goto next;

  case ESC:
  case '\r':
  case '\n':
   goto out;

  case 0: break;
  default:
   c = TblTrack(tbl, c);
   if( c ) goto  INP;
   break;
  }
  continue;
 mv:     TblPointAt( tbl, want );
 }
out:    wnoutrefresh( tbl->win );
 if( tbl->hideMe ) (*tbl->hideMe)(tbl);
 return ((tbl->key = c) == ESC ? -1 : tbl->current );
}
.
#       Пример 23 - simple visual shell.
#       UNIX commander
#########################################################################
# Это файл Makefile для проекта uxcom - простого меню-ориентированного
# экранного интерфейса для переходов по файловой системе.
# Ключ -Iкаталог указывает из какого каталога должны браться
# include-файлы, подключаемые по #include "имяФайла".
# Проект состоит из нескольких файлов:
# Пример 17, Пример 18, Пример 19, Пример 21, Пример 23 и других.
#
#  +  Left    Right   _Commands    Tools    Sorttype      +
#  |           /usr/a+---------------------008/013-+      |
#  +-----------------|        Главное меню         |---+--+
#  |      ..         +--------------------------+--+   |  |
#  |      .BAD       |  Current directory       |  |   |  |
#  |      .contents.m|  Root directory          |  |   |##|
#  |      DUMP       |  Menus                   |  |   |  |
#  |      Makefile   +--------------------------+  |   |  |
#  |      PLAN       |  Help                    |  |   |  |
#  |     _points     |  Unimplemented           |  |   |  |
#  |      table      |  Change sorttype         |##|   |  |
#  |     #unbold     | _Look directory history  |  |   |  |
#  |     #uxcom      +--------------------------+  |   |  |
#  |      x.++       |  Quit                    |  |   |  |
#  |      00         +--------------------------+  |   |  |
#  |      11         |  Redraw screen           |  |   |  |
#  |      LOOP_p     +--------------------------+--+   |  |
#  |      LOOP_q      .c     |       etc               |  |
#  |      LOOP_strt   .c     |       install           |  |
#  +-------------------------+-------------------------+  |
#  | points      165 -r--r-- | .cshrc  2509 -rw-r--r-- |  |
#  +-------------------------+-------------------------+  |
#  |  История путешествий                              |  |
#  +---------------------------------------------------+--+
#
SHELL=/bin/sh
SRCS = glob.c w.c menu.c pull.c match.c pwd.c hist.c line.c table.c \
       main.c treemk.c
OBJS = glob.o w.o menu.o pull.o match.o pwd.o hist.o line.o table.o \
       main.o treemk.o
# INCLUDE = /usr/include
# LIB     = -lncurses
INCLUDE   = -I../../src/curses
LIB       = ../../src/curses/libncurses.a
DEFINES   = -DUSG -DTERMIOS
CC        = cc  -O            # стандартный C-compiler + оптимизация
#CC       = gcc -O            # GNU C-compiler

uxcom: $(OBJS)
 $(CC) $(OBJS) -o $@ $(LIB)
 sync; ls -l $@; size $@
glob.o: glob.c glob.h   # это файл "Пример 18"
 $(CC) -c glob.c
w.o: w.c w.h            # это файл "Пример 17"
 $(CC) -c $(INCLUDE) $(DEFINES) w.c
menu.o: menu.c glob.h w.h menu.h   # это файл "Пример 19"
 $(CC) -c $(INCLUDE) $(DEFINES) menu.c
pull.o: pull.c glob.h w.h menu.h pull.h # это файл "Пример 20"
 $(CC) -c $(INCLUDE) $(DEFINES) pull.c
match.o: match.c
 $(CC) -c -DMATCHONLY \
       -DMATCH_ERR="TblMatchErr()" match.c
pwd.o: pwd.c
 $(CC) -c -DU42 -DCWDONLY pwd.c
treemk.o: treemk.c
 $(CC) -c $(DEFINES) \
       -DERR_CANT_READ=tree_err_cant_read     \
       -DERR_NAME_TOO_LONG=tree_name_too_long \
       -DTREEONLY -DU42 treemk.c
hist.o: hist.c hist.h glob.h menu.h w.h  # это файл "Пример 21"
 $(CC) -c $(INCLUDE) $(DEFINES) hist.c
line.o: line.c w.h glob.h menu.h hist.h line.h  # "Пример 21"
 $(CC) -c $(INCLUDE) $(DEFINES) line.c
table.o: table.c w.h glob.h menu.h table.h      # "Пример 22"
 $(CC) -c $(INCLUDE) $(DEFINES) table.c
main.o: main.c glob.h w.h menu.h hist.h line.h pull.h table.h
 $(CC) -c $(INCLUDE) $(DEFINES) main.c
w.h:    wcur.h
 touch w.h

/* _______________________ файл main.c __________________________ */
/* Ниже предполагается, что вы раскрасили в /etc/termcap          *
 * выделения A_STANDOUT и A_REVERSE в РАЗНЫЕ цвета !              */
#include "w.h"
#include "glob.h"
#include "menu.h"
#include "hist.h"
#include "line.h"
#include "table.h"
#include "pull.h"
#include <signal.h>
#include <ustat.h>
#include <locale.h>

void t_enter(), t_leave();
LineEdit    edit;                     /* редактор строки           */
Hist        hcwd, hedit, hpat;        /* истории:                  */
/* посещенные каталоги, набранные команды, шаблоны имен            */
Menu        mwrk, msort;              /* должны иметь класс static */
PullMenu    pull;

typedef enum { SEL_WRK=0, SEL_PANE1, SEL_PANE2, SEL_PULL, SEL_HELP } Sel;
Sel current_menu;       /* текущее активное меню                   */
Sel previous_menu;      /* предыдущее активное меню                */
#define SEL_PANE (current_menu == SEL_PANE1 || current_menu == SEL_PANE2)
typedef struct {
 Table t;        /* таблица с именами файлов                */
 DirContents d;  /* содержимое каталогов                    */
} FileWidget;
FileWidget tpane1, tpane2;    /* левая и правая панели             */
FileWidget *A_pane = &tpane1; /* активная панель                   */
FileWidget *B_pane = &tpane2; /* противоположная панель            */
#define A_tbl   (&A_pane->t)
#define A_dir   (&A_pane->d)
#define B_tbl   (&B_pane->t)
#define B_dir   (&B_pane->d)
#define TblFW(tbl) ((tbl) == A_tbl ? A_pane : B_pane)
void ExchangePanes(){  /* Обменять указатели на панели */
     FileWidget *tmp = A_pane; A_pane = B_pane; B_pane = tmp;
     current_menu = (current_menu == SEL_PANE1 ? SEL_PANE2 : SEL_PANE1);
}
#define Other_pane(p)  ((p) == A_pane ? B_pane : A_pane)
#define Other_tbl(t)   ((t) == A_tbl  ? B_tbl  : A_tbl )
WINDOW *panewin;        /* окно, содержащее обе панели = stdscr */
typedef enum { NORUN=0, RUNCMD=1, CHDIR=2, TAG=3, FIND=4 } RunType;

#define REPEAT_KEY 666  /* псевдоклавиша "повтори выбор в меню"    */
#define LEAVE_KEY  777  /* псевдоклавиша "покинь это меню"         */
#define NOSELECTED (-1) /* в меню ничего пока не выбрано           */
#define CENTER  (COLS/2-2) /* линия раздела панелей                */
int done;               /* закончена ли программа ?                */
char CWD[MAXLEN];       /* полное имя текущего каталога            */
char SELECTION[MAXLEN]; /* имя выбранного файла                    */
/*-----------------------------------------------------------------*/
/* Выдать подсказку в строке редактора                             */
/*-----------------------------------------------------------------*/
#include <stdarg.h>
void Message(char *s, ... ){
  char msg[80]; va_list args; int field_width;
  va_start(args, s); vsprintf(msg, s, args); va_end(args);
  wattrset    (panewin,     A_tbl->sel_attrib);
  field_width = A_tbl->width + B_tbl->width - 3;
  mvwprintw   (panewin, LINES-2, tpane1.t.left+1, " %*.*s ",
        -field_width, field_width, msg);
  wattrset    (panewin, A_tbl->bg_attrib);
  wnoutrefresh(panewin);
}
/*-----------------------------------------------------------------*
 *      Меню порядка сортировки имен файлов.                       *
 *-----------------------------------------------------------------*/
Info sort_info[] = {
    { "По возрастанию", 0}, { "По убыванию",    0},
    { "По суффиксу",    0}, { "Без сортировки", 0},
    { "По размеру",     M_HATCH},
    { NULL, 0}
};
/* При входе в меню сортировки указать текущий тип сортировки */
void sort_show(Menu *m){
    MnuPointAt(&msort, (int) sorttype);
}
/* Выбрать тип сортировки имен файлов */
static void SelectSortType(int sel){
    if( sel == NOSELECTED )
 sel = MnuUsualSelect(&msort, NO);
    MnuHide(&msort);
    current_menu = previous_menu;
    if(M_REFUSED(&msort)) return;
    sorttype = (Sort) sel;
    A_dir->lastRead = B_dir->lastRead = 0L; /* форсировать перечитку */
    /* но ничего явно не пересортировывать и не перерисовывать       */
}
/*-----------------------------------------------------------------*
 *  Отслеживание содержимого каталогов и переинициализация меню.   *
 *-----------------------------------------------------------------*/
#define NON_VALID(d)  ((d)->readErrors || (d)->valid == NO)
/* Сменить содержимое таблицы и списка файлов */
void InitTblFromDir(FileWidget *wd, int chdired, char *savename){
     char *msg, *name; Table *tbl = &(wd->t); DirContents *d = &wd->d;
     int saveind  = tbl->current, saveshift = tbl->shift;
     char *svname = NULL;
     if(tbl->nitems > 0 ) svname = strdup(T_ITEMF(tbl, saveind, 0));
  /* Несуществующие и нечитаемые каталоги выделить особо */
     if( NON_VALID(d)) wattrset(tbl->win, A_REVERSE);
     TblClear(tbl);
     if(d->valid == NO){
 msg = "Не существует"; name = d->name; goto Report;
     } else if(d->readErrors){ /* тогда d->files->s == NULL */
 msg = "Не читается";   name = d->name;
Report: mvwaddstr(tbl->win, tbl->top + tbl->height/2,
    tbl->left + (tbl->width - strlen(name))/2, name);
 mvwaddstr(tbl->win, tbl->top + tbl->height/2+1,
    tbl->left + (tbl->width - strlen(msg))/2, msg);
     }
     wattrset(tbl->win, tbl->bg_attrib);
     tbl->items = d->files; TblInit(tbl, NO);
     /* Постараться сохранить позицию в таблице */
     if( chdired ) TblPlaceByName(tbl, savename);
     else {
  if( svname == NULL || TblPlaceByName(tbl, svname) < 0 ){
      tbl->shift   = saveshift;
      tbl->current = saveind; TblChk(tbl);
  }
     }
     if(svname) free(svname);
}
/* Перейти в каталог и запомнить его полное имя  */
int mychdir(char *newdir){ int code = chdir(newdir);
    if( code < 0 ) return code;
    getwd(CWD); in_the_root = (strcmp(CWD, "/") == 0);
    HistAdd(&hcwd, CWD, 0); /* запомнить в истории каталогов */
    t_enter(&tpane1.t);     /* на рамке нарисовать имя текущего каталога */
    return code;
}
/* Изменить текущий каталог и перечитать его содержимое */
int cd(char *newdir, FileWidget *wd, char *oldname){
    char oldbase[MAXLEN], *s, *strrchr(char *,char);
 /* Спасти в oldbase базовое имя старого каталога oldname (обычно CWD) */
    if(s = strrchr(oldname, '/')) s++; else s = oldname;
    strcpy(oldbase, s);

    if( mychdir(newdir) < 0){ /* не могу перейти в каталог */
 Message("Не могу перейти в %s", *newdir ? newdir : "???");
 beep(); return (-1); }
    if( ReadDir(CWD, &wd->d)){ /* содержимое изменилось */
 InitTblFromDir (wd, YES, oldbase);
 return 1;
    }
    return 0;
}
/* Проверить содержимое обеих панелей */
void checkBothPanes(){
   /* Случай NON_VALID нужен только для того, чтобы Init...
      восстановил "аварийную" картинку в панели */
      if( ReadDir(tpane1.d.name, &tpane1.d) || NON_VALID(&tpane1.d))
   InitTblFromDir(&tpane1, NO, NULL);
      if( tpane1.t.exposed == NO ) TblDraw(&tpane1.t);
      if( ReadDir(tpane2.d.name, &tpane2.d) || NON_VALID(&tpane2.d))
   InitTblFromDir(&tpane2, NO, NULL);
      if( tpane2.t.exposed == NO ) TblDraw(&tpane2.t);
}
/*-----------------------------------------------------------------*
 *    Ввод команд и выдача подсказки.                              *
 *-----------------------------------------------------------------*/
/* Особая обработка отдельных клавиш в редакторе строки */
char  e_move = NO; /* кнопки со стрелками <- -> двигают
      курсор по строке/по таблице */
int e_hit[] = { KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN,
    KEY_F(0), KEY_IC,
    ctrl('G'), ctrl('E'), ctrl('L'), ctrl('F'), ctrl('X'), ctrl('Y'),
    -1 };
int e_handler (LineEdit *le, int c, HandlerReply *reply){
    *reply = HANDLER_CONTINUE;
    switch(c){
/* Перемещение по таблице без выхода из редактора строки */
    case KEY_LEFT:
  if( !SEL_PANE || !e_move){
       *reply=HANDLER_SWITCH; return c; }
  TblPointAt(A_tbl, A_tbl->current - A_tbl->height); break;
    case KEY_RIGHT:
  if( !SEL_PANE || !e_move){
       *reply=HANDLER_SWITCH; return c; }
  TblPointAt(A_tbl, A_tbl->current + A_tbl->height); break;
    case KEY_DOWN:
  if( !SEL_PANE){ *reply=HANDLER_SWITCH; return c; }
  TblPointAt(A_tbl, A_tbl->current + 1); break;
    case KEY_UP:
  if( !SEL_PANE){ *reply=HANDLER_SWITCH; return c; }
  TblPointAt(A_tbl, A_tbl->current - 1); break;
    case KEY_F(0):      /* F10 */
  e_move = !e_move; break;
    case KEY_IC:
  if( !SEL_PANE){ *reply=HANDLER_SWITCH; return c; }
  TblRetag(A_tbl, A_tbl->current, T_LABEL);
  TblPointAt(A_tbl, A_tbl->current+1);
  break;
/* Подстановки */
    case ctrl('G'): /* подставить полное имя домашнего каталога */
  LeInsStr(le, getenv("HOME")); LeInsStr(le, " "); break;
    case ctrl('E'): /* подставить имя выбранного файла */
  if( A_tbl->nitems )
      LeInsStr(le, T_ITEMF(A_tbl, A_tbl->current, 0));
  LeInsStr(le, " "); break;
    case ctrl('L'): /* подставить имя выбранного файла из другой панели */
  LeInsStr(le, T_ITEMF(B_tbl, B_tbl->current, 0));
  LeInsStr(le, " "); break;
    case ctrl('X'): case ctrl('Y'):
    /* подстановка имен помеченных файлов */
    {    int label = (c == ctrl('X') ? T_LABEL : T_HATCH);
  register i;
  for(i=0; i < A_tbl->nitems && le->len < le->maxlen; ++i )
      if( T_TST(A_tbl, i, label)){
   LeInsStr(le, " "); LeInsStr(le, T_ITEMF(A_tbl, i, 0));
      }
    } break;
    case ctrl('F'): /* подставить имя текущего каталога */
  LeInsStr(le, CWD); LeInsStr(le, " "); break;
   }
   return c;
}
/* При начале редактирования ставь курсор в конец строки */
void e_pos (LineEdit *le){ le->pos = le->len; }
/* Обозначить, что мы покинули редактор строки */
void e_hide(LineEdit *le){
     le->sel_attrib = le->fr_attrib = le->bg_attrib = A_ITALICS;
     LeDraw(le);
}
/* Отредактировать строку в предпоследней строке окна */
char *Edit(WINDOW *w, char *src, RunType dorun){
    static char CMD[MAXLEN];   /* буфер для строки команды */
    int c;
    if(w != TOPW){ beep(); return NULL; }/* это должно быть верхнее окно */
    keypad(w, TRUE);
 /* Проинициализировать редактор строки */
    switch(dorun){
    case NORUN:  edit.histIn = edit.histOut = NULL;   break;
    case RUNCMD: edit.histIn = edit.histOut = &hedit; break;
    case FIND:
    case TAG:    edit.histIn = edit.histOut = &hpat;  break;
    case CHDIR:  edit.histIn = &hcwd; edit.histOut = NULL; break;
    }
    edit.line   = CMD;
    edit.maxlen = sizeof(CMD)-1;
    edit.top    = wlines(w)-2; edit.left = 2;
    edit.width  = wcols (w)-4 - (1+BARWIDTH);
    edit.insert = YES; edit.nc = YES;
    edit.win    = w;
    edit.wl_attrib  = edit.bg_attrib=A_REVERSE;
    edit.fr_attrib=A_STANDOUT; edit.sel_attrib = A_NORMAL|A_BLINK;
    edit.posMe   = e_pos;
    edit.hitkeys = (SEL_PANE ? e_hit : e_hit+5);
    edit.handler = e_handler;
    /* edit.hideMe  = e_hide; вызывается ЯВНО */
    /* остальные поля равны 0, т.к. edit - статическое данное */
    for(;;){
 strcpy(CMD, src); if(*src){ strcat(CMD, " "); }
 c = LeEdit( &edit );
 if( LE_REFUSED(&edit) || dorun != RUNCMD ||
     !*CMD || c != '\n' ) break;
 /* курсор в нижнюю строку экрана */
 attrset(A_NORMAL); move(LINES-1, 0); refresh();
 resetterm();    /* приостановить работу curses-а    */
 putchar('\n');  /* промотать экран на строку        */
 system(CMD);    /* выполнить команду внешним Шеллом */
 fprintf(stderr,"Нажми ENTER чтобы продолжить --- ");gets(CMD);
 fixterm();      /* возобновить работу curses-а      */
 RedrawScreen(); /* перерисовать экран               */
 if(w == panewin){
    checkBothPanes();
    if(A_tbl->nitems) TblPoint(A_tbl, A_tbl->current, NO);
 }
 src = ""; /* во второй раз ничего не подставлять */
    }
    wattrset(w, A_NORMAL); /* ? */
    e_hide ( &edit );
    return ( *CMD && !LE_REFUSED(&edit)) ? CMD : NULL;
}
/* Выдача подсказки а также сообщений об ошибках.         */
/* В этом же окне можно набирать команды (dorun==RUNCMD). */
char *help(char *msg, RunType dorun){ register i; char *s;
    static char *helptext[] = {
 "ESC    - выход в главное меню",
 "F1     - подсказка",
 "INS    - пометить файл",
 "ctrl/E - подставить имя выбранного файла",
 "ctrl/L - подставить имя из другой панели",
 "ctrl/X - подставить помеченные файлы",
 "ctrl/Y - подставить помеченные курсивом",
 "ctrl/G - подставить имя домашнего каталога",
 "ctrl/F - подставить имя текущего каталога",
 "F4     - история",
 "F7     - переключить режим вставки/замены",
 "F10    - переключить перемещения по строке/по панели",
    };
#define HELPLINES (sizeof(helptext)/sizeof helptext[0])
    Sel save_current_menu = current_menu;
    /* "выскакивающее" POP-UP window */
    WINDOW *w = newwin(2+1+HELPLINES+1, 70, 2, (COLS-70)/2);
    if( w == NULL ) return NULL;
    current_menu = SEL_HELP;
    wattrset(w, A_REVERSE);    /* это будет инверсное окно  */
    werase  (w);               /* заполнить инверсным фоном */
    wborder(w);  RaiseWin(w);  /* окно появляется */
    if(*msg){                            wattron (w, A_BOLD);
      mvwaddstr(w, 1+HELPLINES, 2, msg); wattroff(w, A_BOLD);
    }
    for(i=0; i < HELPLINES; i++) mvwaddstr(w, 1+i, 2, helptext[i]);
    s = Edit(w, "", dorun); PopWin(); /* окно исчезает */
    current_menu = save_current_menu;
    return s;
}
/*-----------------------------------------------------------------*
 *   Управляющее меню.                                             *
 *-----------------------------------------------------------------*/
int f_left(), f_right(), f_pull(), f_help(), f_sort(), f_dir(),
    f_bye(),  f_redraw(),f_cdroot();
/* Обратите внимание, что можно указывать не все поля структуры,
 * а только первые. Остальные равны 0 */
#ifndef __GNUC__
Info mwrk_info[] = {    /* строки для главного меню      */
    { "\\Current directory",       0       , f_left  }, /* 0 */
    { "\\Root directory",          M_HATCH , f_right }, /* 1 */
    { "\\Menus",                   0       , f_pull  }, /* 2 */
    { "\1", /* гориз. черта */     0                 }, /* 3 */
    { "\\Help",                    0       , f_help  }, /* 4 */
    { "Un\\implemented",           I_NOSEL           }, /* 5 */
    { "Change \\sorttype",         0       , f_sort  }, /* 6 */
    { "Look directory \\history",  0       , f_dir   }, /* 7 */
    { "\1", /* гориз. черта */     0                 }, /* 8 */
    { "\\Quit",                    M_BOLD  , f_bye   }, /* 9 */
    { "\1", /* гориз. черта */     0                 }, /* 10 */
    { "\\Redraw screen",           M_HATCH , f_redraw}, /* 11 */
    { "Chdir both panels to /",    M_HATCH , f_cdroot}, /* 12 */
    { NULL, 0 }
};
#else /* GNU C-компилятор 1.37 не может инициализировать поля-union-ы */
static char _gnu_[] = "Compiled with GNU C-compiler";
Info mwrk_info[] = {    /* строки для главного меню      */
    { "\\Current directory",            0       },
    { "\\Root directory",               M_HATCH },
    { "\\Menus",                        0       },
    { "\1", /* гориз. черта */          0       },
    { "\\Help",                         0       },
    { "Un\\implemented",                I_NOSEL },
    { "Change \\sorttype",              0       },
    { "Look directory \\history",       0       },
    { "\1", /* гориз. черта */          0       },
    { "\\Quit",                         M_BOLD  },
    { "\1", /* гориз. черта */          0       },
    { "\\Redraw screen",                M_HATCH },
    { "Chdir both panels to /",         M_HATCH },
    { NULL, 0 }
};
void mwrk_init(){
    mwrk_info [0].any.act = f_left;
    mwrk_info [1].any.act = f_right;
    mwrk_info [2].any.act = f_pull;
    mwrk_info [4].any.act = f_help;
    mwrk_info [6].any.act = f_sort;
    mwrk_info [7].any.act = f_dir;
    mwrk_info [9].any.act = f_bye;
    mwrk_info[11].any.act = f_redraw;
    mwrk_info[12].any.act = f_cdroot;
}
#endif
char *mwrk_help[] = {
      "Перейти в левую панель",  "Перейти в правую панель",
      "Перейти в строчное меню", "",
      "Выдать подсказку",        "Не реализовано",
      "Изменить тип сортировки имен", "История путешествий",
      "", "Выход", "", "Перерисовка экрана",
      "Обе панели поставить в корневой каталог", NULL
};
void m_help(Menu *m, int n, int among){
     Message(mwrk_help[n]);    }
/* Выбор в рабочем (командном) меню */
void SelectWorkingMenu(int sel){
    if(sel == NOSELECTED)
       sel = MnuUsualSelect( & mwrk, NO);
    if( M_REFUSED(&mwrk)) help("Выбери Quit", NORUN);
    else if(mwrk.items[sel].any.act)
   (*mwrk.items[sel].any.act)();
    if( !done) MnuHide( & mwrk );
}
f_left ()  { current_menu = SEL_PANE1; return 0; }
f_right()  { current_menu = SEL_PANE2; return 0; }
f_pull ()  { current_menu = SEL_PULL;  return 0; }
f_help ()  { help("Нажми ENTER или набери команду:", RUNCMD);
      return 0; }
f_sort ()  { SelectSortType(NOSELECTED); return 0; }
f_dir  ()  { Info *idir; if(idir = HistSelect(&hcwd, 20, 3))
        cd(idir->s, &tpane2, CWD);
     current_menu = SEL_PANE2;    return 0; }
f_bye   () { done++;                     return 0; }
f_redraw() { RedrawScreen();             return 0; }
f_cdroot() { cd("/", &tpane1, CWD);
      cd("/", &tpane2, CWD); checkBothPanes();
      return 0;                             }
/*-----------------------------------------------------------------*
 *  Выдача информации про файл, редактирование кодов доступа.      *
 *-----------------------------------------------------------------*/
void MYwaddstr(WINDOW *w, int y, int x, int maxwidth, char *s){
     register pos;
     for(pos=0; *s && *s != '\n' && pos < maxwidth; ++s){
  wmove(w, y, x+pos);
       if( *s == '\t')  pos += 8 - (pos & 7);
  else if( *s == '\b'){ if(pos)  --pos; }
  else if( *s == '\r')  pos = 0;
  else { ++pos; waddch(w, isprint(*s) ? *s : '?'); }
     }
}
/* Просмотр начала файла в противоположной панели.            */
void fastView(
     char *name,    /* имя файла                              */
     unsigned mode, /* некоторые типы файлов не просматривать */
     Table *otbl    /* противоположная панель                 */
){   FILE *fp; register int x, y; char buf[512];

     TblClear(otbl);
     Message("Нажми ENTER для окончания. "
      "ПРОБЕЛ - изменяет код доступа. "
      "ESC - откатка.");
     if( !ISREG(mode)) goto out;
     if((fp = fopen(name, "r")) == NULL){
    Message("Не могу читать %s", name); return;
     }
     for(y=0; y < otbl->height && fgets(buf, sizeof buf, fp); y++)
  MYwaddstr(panewin, otbl->top+y, otbl->left+1,
     otbl->width-2, buf);
     fclose(fp);
out: wrefresh(otbl->win);   /* проявить */
}
static struct attrNames{
 unsigned mode; char name; char acc; int off;
} modes[] = {
 { S_IREAD,       'r', 'u',  0    },
 { S_IWRITE,      'w', 'u',  1    },
 { S_IEXEC,       'x', 'u',  2    },
 { S_IREAD  >> 3, 'r', 'g',  3    },
 { S_IWRITE >> 3, 'w', 'g',  4    },
 { S_IEXEC  >> 3, 'x', 'g',  5    },
 { S_IREAD  >> 6, 'r', 'o',  6    },
 { S_IWRITE >> 6, 'w', 'o',  7    },
 { S_IEXEC  >> 6, 'x', 'o',  8    },
};
#define NMODES (sizeof(modes)/sizeof(modes[0]))

/* Позиция в которой изображать i-ый бит кодов доступа */
#define MODE_X_POS(tbl, i) (tbl->left + DIR_SIZE + 12 + modes[i].off)
#define MODE_Y_POS(tbl)    (tbl->top  + tbl->height + 1)

#ifdef FILF
/* Изобразить информацию о текущем выбранном файле */
void showMode(Table *tbl, int attr){
   Info *inf   = & tbl->items[tbl->current];    /* файл   */
   register i; unsigned mode = inf->mode;       /* коды   */
   int     uid = inf->uid, gid = inf->gid;      /* хозяин */
   /* идентификаторы хозяина и группы процесса-коммандера */
   static char first = YES; static int myuid, mygid;
   WINDOW *win = tbl->win;
   int xleft   = tbl->left + 1, y = MODE_Y_POS(tbl);

   if( first ){ first = NO; myuid = getuid(); mygid = getgid(); }
   wattron  (win, attr);
   mvwprintw(win, y, xleft, " %*.*s %8ld ",  /* имя файла */
      -DIR_SIZE, DIR_SIZE,
       inf->s ? (!strcmp(inf->s, "..") ? "<UP-DIR>": inf->s) :
  "(EMPTY)",
       inf->size);
   /* тип файла (обычный|каталог|устройство) */
   wattron (win, A_ITALICS|A_BOLD);
   waddch  (win, ISDIR(mode) ? 'd': ISDEV(mode) ? '@' : '-');
   wattroff(win, A_ITALICS|A_BOLD);
   /* коды доступа */
   for(i=0; i < NMODES; i++){
       if((modes[i].acc == 'u' && myuid == uid) ||
   (modes[i].acc == 'g' && mygid == gid) ||
   (modes[i].acc == 'o' && myuid != uid && mygid != gid)) ;
       else     wattron(win, A_ITALICS);
       mvwaddch(win, y, MODE_X_POS(tbl, i),
  mode & modes[i].mode ? modes[i].name : '-');
       wattroff(win, A_ITALICS);
   }
   waddch(win, ' '); wattroff(win, attr);
}
#define newmode (tbl->items[tbl->current].mode)
/* Редактирование кодов доступа к файлам. */
int editAccessModes(FileWidget *wd){
    Table *tbl  = &wd->t;
    Table *otbl = &(Other_pane(wd)->t); /* или Other_tbl(tbl); */
    unsigned prevmode, oldmode;         /* старый код доступа  */
    char *name;                         /* имя текущего файла  */
    WINDOW *win = tbl->win;
    int position = 0, c;

    for(;;){  /* Цикл выбора файлов в таблице */
 name = T_ITEMF(tbl, tbl->current, 0);
 oldmode = newmode;             /* запомнить */
 fastView(name, newmode, otbl); /* показать первые строки файла */

 for(;;){  /* Цикл обработки выбранного файла */
    wmove(win, MODE_Y_POS(tbl), MODE_X_POS(tbl, position));

    switch(c = WinGetch(win)){
/* Некоторые клавиши вызывают перемещение по таблице */
case KEY_BACKTAB: TblPointAt(tbl, tbl->current - tbl->height); goto mv;
case '\t':        TblPointAt(tbl, tbl->current + tbl->height); goto mv;
case KEY_UP:      TblPointAt(tbl, tbl->current - 1); goto mv;
case KEY_DOWN:    TblPointAt(tbl, tbl->current + 1); goto mv;
case KEY_HOME:    TblPointAt(tbl, 0);                goto mv;
case KEY_END:     TblPointAt(tbl, tbl->nitems-1);    goto mv;
/* Прочие клавиши предназначены для редактирования кодов доступа */
    case KEY_LEFT:  if(position) --position; break;
    case KEY_RIGHT: if(position < NMODES-1)  position++; break;
    default: goto out;
    case ESC:    /* Восстановить старые коды */
  prevmode = newmode = oldmode; goto change;
    case ' ':    /* Инвертировать код доступа */
  prevmode = newmode;              /* запомнить */
  newmode ^= modes[position].mode; /* инвертировать */
change:         if( chmod(name, newmode) < 0){
      beep();
      Message("Не могу изменить доступ к %s", name);
      newmode = prevmode; /* восстановить */
  } else /* доступ изменен, показать это */
      showMode(tbl, A_REVERSE);
  break;
    }
 } /* Конец цикла обработки выбранного файла */
mv:     ;
    } /* Конец цикла выбора файлов в таблице */
out:
    /* Очистить противоположную панель после fastView(); */
    Message(""); TblClear(otbl); return c;
}
#undef newmode
#else
void editAccessModes(FileWidget *wd){}
#endif
long diskFree(){
      struct ustat ust; struct stat st; long freespace;
      if(stat(".", &st) < 0) return 0;
      ustat(st.st_dev, &ust);
      freespace = ust.f_tfree * 512L; freespace /= 1024;
      Message("В %*.*s свободно %ld Кб.",
  -sizeof(ust.f_fname), sizeof(ust.f_fname),
  *ust.f_fname ? ust.f_fname : ".", freespace);
      doupdate();  /* проявить окно для Message() */
      return freespace;
}
/*-----------------------------------------------------------------*
 *   Специальные команды, использующие обход дерева
 *-----------------------------------------------------------------*/
/* Выдача сообщений об ошибках (смотри Makefile) */
int tree_err_cant_read(char *name){
    Message("Не могу читать \"%s\"", name); return WARNING;
}
int tree_name_too_long(){
    Message("Слишком длинное полное имя"); return WARNING;
}
char canRun;  /* продолжать ли поиск */
/* Прерывание обхода по SIGINT */
void onintr_f(nsig){ canRun = NO; Message("Interrupted"); }

/* ==== место, занимаемое поддеревом ==== */
long tu(int *count){
   struct stat st; register i; long sum = 0L;
   *count = 0;
   for(i=0; i < A_tbl->nitems ;++i )
      if( T_TST(A_tbl, i, T_LABEL)){
   stat(T_ITEMF(A_tbl, i, 0), &st);
#define KB(s)   (((s) + 1024L - 1) / 1024L)
   sum += KB(st.st_size); (*count)++;
      }
   return sum;
}
void diskUsage(){ long du(), size, sizetagged; int n;
  char msg[512];
  Message("Измеряем объем файлов..."); doupdate();
  size = du(".");   diskFree();  sizetagged = tu(&n);
  sprintf(msg, "%ld килобайт в %s, %ld кб в %d помеченных файлах",
  size,          CWD, sizetagged,   n);
  help(msg, NORUN);
}
/* ==== поиск файла ===================== */
extern char *find_PATTERN;             /* imported from treemk.c */
extern Info gargv[]; extern int gargc; /* imported from glob.c   */
/* Проверить очередное имя и запомнить его, если подходит */
static int findCheck(char *fullname, int level, struct stat *st){
    char *basename = strrchr(fullname, '/');
    if(basename) basename++;
    else         basename = fullname;
    if( canRun == NO ) return FAILURE;   /* поиск прерван         */
    if( match(basename, find_PATTERN)){  /* imported from match.c */
 gargv[gargc]     = NullInfo;  /* зачистка */
 gargv[gargc].s   = strdup(fullname);
 gargv[gargc++].fl= ISDIR(st->st_mode) ? I_DIR : 0;
 gargv[gargc]     = NullInfo;
 Message("%s", fullname); doupdate();
    }
    /* Страховка от переполнения gargv[] */
    if   ( gargc < MAX_ARGV - 1 ) return SUCCESS;
    else { Message("Найдено слишком много имен."); return FAILURE; }
}
/* Собрать имена файлов, удовлетворяющие шаблону */
static Info *findAndCollect(char *pattern){
     void (*old)() = signal(SIGINT, onintr_f);
     Sort saveSort;

     find_PATTERN = pattern; canRun = YES;
     Message("Ищем %s от %s", pattern, CWD); doupdate();
     greset();  /* смотри glob.c, gargc=0; */
     walktree(CWD, findCheck, NULL, findCheck);
     signal(SIGINT, old);
     saveSort = sorttype; sorttype = SORT_ASC;
     if(gargc) qsort( gargv, gargc, sizeof(Info), gcmps);
     sorttype = saveSort;
     return gargc ? blkcpy(gargv) : NULL;
}
/* Обработать собранные имена при помощи предъявления меню с ними */
void findFile(FileWidget *wd){
     static Info *found; static Menu mfind;
     int c; Table *tbl = & wd->t;
     char *pattern = help("Введи образец для поиска, вроде *.c, "
     "или ENTER для прежнего списка", FIND);
     if( LE_REFUSED( &edit)) return; /* отказались от поиска */
     /* Если набрана пустая строка, help() выдает NULL       */
     if( pattern ){            /* задан новый образец - ищем */
  /* Уничтожить старый список файлов и меню */
  if( found ) blkfree( found );
  MnuDeinit( &mfind );
  found = findAndCollect(pattern); /* поиск */
  HistAdd( &hpat, pattern, 0);
  /* Образуем меню из найденных файлов */
  if( found ){  /* если что-нибудь нашли */
    mfind.items     =  found;
    mfind.title     =  pattern ? pattern : "Найденные файлы";
    mfind.top       =  3; mfind.left = COLS/6;
    mfind.bg_attrib =  A_STANDOUT; mfind.sel_attrib = A_REVERSE;
    MnuInit (&mfind);
  }
     } /* else набрана пустая строка - просто вызываем список
 * найденных ранее файлов.
 */
     if( found == NULL ){
  Message("Ничего не найдено"); beep(); return;
     }
     c = MnuUsualSelect(&mfind, NO);
     /* Выбор файла в этом меню вызовет переход в каталог,
      * в котором содержится этот файл */
     if( !M_REFUSED( &mfind )){
 char *s = M_ITEM(&mfind, mfind.current);

 /* пометить выбранный элемент */
 M_SET(&mfind, mfind.current, M_LABEL);
 /* если это каталог - войти в него */
 if( M_TST(&mfind, mfind.current, I_DIR))
        cd(s, wd, CWD);
 /* иначе войти в каталог, содержащий этот файл */
 else { char *p; struct savech svch; /* смотри glob.h */
      SAVE( svch, strrchr(s, '/'));   *svch.s = '\0';
      p = strdup(s); RESTORE(svch);
      if( !strcmp(CWD, p))               /* мы уже здесь     */
   TblPlaceByName(tbl, svch.s+1); /* указать курсором */
      else /* изменить каталог и указать курсором на файл s  */
   cd(p, wd, s);
      free(p);
 }
     }
     MnuHide(&mfind);  /* спрятать меню, не уничтожая его */
}
/*-----------------------------------------------------------------*
 *   Работа с панелями, содержащими имена файлов двух каталогов.   *
 *-----------------------------------------------------------------*/
/* Восстановить элементы, затертые рамкой WinBorder */
void t_restore_corners(){
    mvwaddch(panewin, LINES-3, 0,               LEFT_JOIN);
    mvwaddch(panewin, LINES-3, COLS-2-BARWIDTH, RIGHT_JOIN);
    mvwaddch(panewin, LINES-5, 0,               LEFT_JOIN);
    mvwaddch(panewin, LINES-5, COLS-2-BARWIDTH, RIGHT_JOIN);
    mvwaddch(panewin, 2,       CENTER, TOP_JOIN);
    wattron (panewin, A_BOLD);
    mvwaddch(panewin, LINES-3, CENTER, BOTTOM_JOIN);
    mvwaddch(panewin, LINES-5, CENTER, MIDDLE_CROSS);
    wattroff(panewin, A_BOLD);
}
/* Нарисовать нечто при входе в панель. Здесь изменяется
 * заголовок окна: он становится равным имени каталога,
 * просматриваемого в панели */
void t_enter(Table *tbl){
     WinBorder(tbl->win, tbl->bg_attrib, tbl->sel_attrib,
        CWD, BAR_VER|BAR_HOR, NO);
     t_restore_corners();
}
/* Стереть подсветку при выходе из панели */
void t_leave(Table *tbl){ TblDrawItem( tbl, tbl->current, NO, YES ); }
/* Рисует недостающую часть рамки, которая не изменяется впоследствии */
void t_border_common(){
    WinBorder(panewin, A_tbl->bg_attrib, A_tbl->sel_attrib,
       A_dir->name, BAR_VER|BAR_HOR, NO);
    wattron (panewin, A_BOLD);
    whorline(panewin, LINES-3, 1, COLS-1-BARWIDTH-1);
    whorline(panewin, LINES-5, 1, COLS-1-BARWIDTH-1);
    wverline(panewin, CENTER, A_tbl->top, A_tbl->top + A_tbl->height+2);
    wattroff(panewin, A_BOLD);
    t_restore_corners();
}
/* Функция, изображающая недостающие части панели при входе в нее */
int t_show(Table *tbl){
#ifdef FILF
     showMode(A_tbl, A_STANDOUT); showMode(B_tbl, A_STANDOUT);
#endif
     return 1;
}
void t_scrollbar(Table *tbl, int whichbar, int n, int among){
     WinScrollBar(tbl->win, BAR_VER|BAR_HOR, n, among,
    "Yes", tbl->bg_attrib);
#ifdef FILF
     showMode(tbl, A_REVERSE);
#endif
}
/* Особая обработка клавиш при выборе в таблице */
int t_hit[] = {
   '\t',        KEY_F(1),       KEY_F(2),       KEY_F(3),
   KEY_F(4),    KEY_F(8),       ' ',            '+',
   '-',         ctrl('R'),      ctrl('L'),      ctrl('F'),
   -1 };
Info t_info[] = {
  { "TAB    Перейти в другую панель",        0},
  { "F1     Выдать подсказку",               0},
  { "F2     Ввести команду",                 0},
  { "F3     Перейти в родительский каталог", 0},
  { "F4     Перейти в каталог по имени",     0},
  { "F8     Удалить помеченные файлы",       0},
  { "ПРОБЕЛ Редактировать коды доступа",     0},
  { "+      Пометить файлы",                 0},
  { "-      Снять пометки",                  0},
  { "ctrl/R Перечитать каталог",             0},
  { "ctrl/L Выдать размер файлов в каталоге",0},
  { "ctrl/F Поиск файла",                    0},
  { NULL, 0}
};
int t_help(){
   static Menu mth; int c = 0;
   if( mth.items == NULL ){
       mth.items     =  t_info;
       mth.title     =  "Команды в панели";
       mth.top       =  3; mth.left = COLS/6;
       mth.bg_attrib =  A_STANDOUT; mth.sel_attrib = A_REVERSE;
       MnuInit (&mth);
       mth.hotkeys   = t_hit;
   }
   c = MnuUsualSelect(&mth, 0);
   /* Спрятать меню, не уничтожая его. Уничтожение выглядело бы так:
    *   mth.hotkeys = NULL; (т.к. они не выделялись malloc()-ом)
    *   MnuDeinit(&mth);
    */
   MnuHide(&mth);
   if( M_REFUSED(&mth)) return 0;             /* ничего не делать */
   return t_hit[c];  /* клавиша, соответствующая выбранной строке */
}
int t_handler (Table *tbl, int c, HandlerReply *reply){
    int i, cnt=0; extern int unlink(), rmdir(); char *answer;
    FileWidget  *wd = TblFW (tbl);
    switch(c){
    case '\t':  /* перейти в соседнюю панель */
 ExchangePanes();
 *reply = HANDLER_OUT; return LEAVE_KEY; /* покинуть эту панель */
    case KEY_F(1): *reply = HANDLER_NEWCHAR; return t_help();
    case KEY_F(2):
    (void) Edit(tbl->win, T_ITEMF(tbl, tbl->current, 0), RUNCMD);
    break;
    case KEY_F(3): cd(".." , wd, CWD); break;
    case KEY_F(4):
      if(answer = help("Введи имя каталога, в который надо перейти",CHDIR))
  cd(answer , wd, CWD);
      break;
    case ctrl('R'): break;
    case KEY_F(8):
      for(i=0; i < tbl->nitems; i++)
  if(T_TST(tbl, i, M_LABEL)){  int code; cnt++;
if((code = (T_TST(tbl, i, I_DIR) ? rmdir : unlink) (T_ITEMF(tbl, i,0))) < 0)
     T_SET(tbl, i, M_HATCH);
      }
      if(cnt==0) help("Нет помеченных файлов", NORUN);
      break;
    case '+':
      if(answer = help("Шаблон для пометки", TAG))
  TblTagAll(tbl, answer, T_LABEL);
      break;
    case '-':
      if(answer = help("Шаблон для снятия пометок", TAG))
  TblUntagAll(tbl, answer, T_LABEL);
      break;
    case ctrl('L'):     /* команда "disk usage" */
      diskUsage(); break;
    case ctrl('F'):     /* поиск файла */
      findFile(wd); break;
    case ' ':           /* редактирование кодов доступа */
      editAccessModes(wd); break;
    }
    *reply = HANDLER_OUT; return REPEAT_KEY;
    /* вернуться в эту же панель */
}
/* Выбор в одной из панелей. */
int SelectPane(FileWidget *wd){
    Table *tbl       = & wd->t;
    DirContents *d   = & wd->d;
    int sel, retcode = 0;

    RaiseWin( tbl->win );
 /* войти в указанный каталог, поправить CWD  */
    if(mychdir( d->name ) < 0) checkBothPanes();
    /* t_enter( tbl );  /* войти в указанную панель, поправить рамку */
    for(;;){
      /* Проверить, не устарело ли содержимое таблиц */
      checkBothPanes();
      if((sel = TblUsualSelect( tbl )) == TOTAL_NOSEL ){
   current_menu = SEL_PULL; goto out;           }
      if( T_REFUSED(tbl)) break; /* нажат ESC */
      if( tbl->key == LEAVE_KEY  ){ retcode=1; break; }
      strcpy(SELECTION, T_ITEMF(tbl, sel, 0));
      if( tbl->key == REPEAT_KEY ) continue;
      if(T_TST(tbl, sel, I_DIR)){ /* это каталог */
      /* попытаться перейти в этот каталог */
  cd(SELECTION, wd, CWD);
      } else if(T_TST(tbl, sel, I_EXE)){ /* выполняемый файл */
  (void) Edit(tbl->win, SELECTION, RUNCMD);
      } else {
  editAccessModes(wd);
      /* На самом деле надо производить подбор команды по
       * типу файла (набор соответствий должен программироваться
       * вами в специальном файле, считываемом при запуске коммандера).
       *        runCommand( classify(SELECTION));
       * где классификация в простейшем случае - по имени и суффиксу,
       * а в более развитом - еще и по кодам доступа (включая тип файла)
       * и по первой строке файла (или "магическому числу").
       */
       }
    }  /* end for */
    t_leave( tbl );
out:
    if( !retcode ) current_menu = SEL_PULL; /* выход по ESC */
    return retcode;
}
/*-----------------------------------------------------------------*
 *   Горизонтальное командное меню (вызывается по ESC).            *
 *-----------------------------------------------------------------*/
PullInfo pm_items [] = {                 /* подсказка */
  {{ " \\Left ",     0 },        NULL,   "Left pane"       }, /* 0 */
  {{ " \\Commands ", 0 },        &mwrk,  "Do some commands"}, /* 1 */
  {{ " \\Tools ",    PM_NOSEL }, NULL,   ""                }, /* 2 */
  {{ " \\Sorttype ", 0 },        &msort, "Change sort type"}, /* 3 */
  {{ " \\Right ",    0 },        NULL,   "Right pane"      }, /* 4 */
  {{ NULL,           0 },        NULL,   NULL }
};
void p_help(PullMenu *p, int n, int among){ Message( PM_NOTE(p, n)); }
/* Выбор в меню-строке */
void SelectPullMenu(){
    int c, sel; Menu *m;
    for(;current_menu == SEL_PULL;){
 c = PullUsualSelect(&pull);
 sel = pull.current;
 if( PM_REFUSED(&pull)){ current_menu = previous_menu; return;}
 switch(sel){
 case 0: current_menu = SEL_PANE1; return;
 case 1: SelectWorkingMenu(c);     return;
 case 2:                           return;  /* не бывает */
 case 3: SelectSortType(c);        return;
 case 4: current_menu = SEL_PANE2; return;
 }
    }
}
/*-----------------------------------------------------------------*
 *   Инициализация и завершение.                                   *
 *-----------------------------------------------------------------*/
void die(int sig){
     echo(); nocbreak(); mvcur(-1,-1,LINES-1,0);
     refresh(); endwin (); putchar('\n');
     if(sig) printf("Signal %d\n", sig);
     if(sig == SIGSEGV) abort(); else exit(sig);
}
void main (void) {
    setlocale(LC_ALL, "");  /* получить информацию о языке диагностик */
    initscr ();          /* включить curses */
    signal(SIGINT, die); /* по сигналу вызывать die(); */
    signal(SIGBUS, die); /* по нарушению защиты памяти */
    signal(SIGSEGV,die);
    refresh();           /* обновить экран: это очистит его */
    noecho(); cbreak();  /* выключить эхо, включить прозрачный ввод */
/* Проинициализировать истории */
    HistInit(&hcwd,  20); hcwd. mnu.title = "История пути";
    HistInit(&hedit, 20); hedit.mnu.title = "История команд";
    HistInit(&hpat,   8); hpat. mnu.title = "Шаблоны имен";
/* Разметить меню сортировки   */
    msort.items     = sort_info;
    msort.title     = "Вид сортировки каталога";
    msort.top       = 1; msort.left = 2;
    msort.showMe    = sort_show;
    msort.bg_attrib = A_NORMAL; msort.sel_attrib = A_STANDOUT;
    /* MnuInit (&msort); инициализируется в pull-menu */
/* Разметить рабочее меню */
    mwrk.items      =  mwrk_info;
    mwrk.title      = "Главное меню";
    mwrk.top        = 1;    mwrk.left = COLS/3;
    mwrk.handler    = NULL; mwrk.hitkeys = NULL;
    mwrk.bg_attrib  = A_STANDOUT; mwrk.sel_attrib = A_REVERSE;
    mwrk.scrollBar  = m_help;
#ifdef __GNUC__
    mwrk_init();
#endif
    /* MnuInit (&mwrk); инициализируется в pull-menu */
/* Разметить левую и правую панели */
    tpane1.t.width      = CENTER - 1;
    tpane2.t.width      = COLS - tpane1.t.width - 2 - (2 + BARWIDTH);
    tpane1.t.height     = tpane2.t.height = (LINES - 8);
    tpane1.t.win        = tpane2.t.win  = panewin = stdscr;
    tpane1.t.left       = 1;
    tpane2.t.left       = CENTER+1;
    tpane1.t.top        = tpane2.t.top    = 3;
    tpane1.t.bg_attrib  = tpane2.t.bg_attrib  = A_NORMAL;
    tpane1.t.sel_attrib = tpane2.t.sel_attrib = A_STANDOUT;
    tpane1.t.scrollBar  = tpane2.t.scrollBar  = t_scrollbar;
    tpane1.t.hitkeys    = tpane2.t.hitkeys    = t_hit;
    tpane1.t.handler    = tpane2.t.handler    = t_handler;
    tpane1.t.showMe     = tpane2.t.showMe     = t_show;
    tpane1.t.hideMe     = tpane2.t.hideMe     = NULL;
/* Разметить имена для файловых объектов */
    tpane1.d.name = strdup("Текущий каталог");
    tpane2.d.name = strdup("Корневой каталог");
/* Изобразить рамки (но пока не проявлять их)
 * Это надо сделать до первого cd(), т.к. иначе при неудаче будет выдано
 * сообщение, которое проявит НЕЗАВЕРШЕННУЮ картинку */
    t_border_common(); t_restore_corners();
/* Доразметить левую панель */
    mychdir(".");  /* узнать полное имя текущего каталога в CWD[] */
    /* прочитать содержимое каталога CWD в tpane1.d */
    cd( CWD , &tpane1, CWD);
    tpane1.t.fmt        = "directory";
    InitTblFromDir(&tpane1, NO, NULL);
/* Доразметить правую панель */
    tpane2.t.fmt = NULL;
    /* прочитать содержимое каталога "/" в tpane2.d */
    cd( "/", &tpane2, CWD); /* теперь стоим в корне */
/* Вернуться в рабочий каталог */
    cd( tpane1.d.name, &tpane1, CWD);
/* Нарисовать обе панели */
    TblDraw(A_tbl); TblDraw(B_tbl);
/* Разметить pulldown меню */
    pull.bg_attrib  = A_REVERSE; pull.sel_attrib = A_NORMAL;
    pull.items      = pm_items;  pull.scrollBar  = p_help;
    PullInit(&pull);
/* Основной цикл */
    for(done=NO, current_menu=SEL_PANE1, A_pane= &tpane1, B_pane= &tpane2;
 done == NO; ){
 Message("");
 if(SEL_PANE) previous_menu = current_menu;
 switch(current_menu){
 case SEL_WRK :  SelectWorkingMenu(NOSELECTED); break;
 case SEL_PULL:  SelectPullMenu();              break;
 case SEL_PANE1: if( SelectPane(&tpane1) < 0)
       M_SET(&mwrk, 0, I_NOSEL);  break;
 case SEL_PANE2: if( SelectPane(&tpane2) < 0)
       M_SET(&mwrk, 0, I_NOSEL);  break;
 }
    }
    die(0);     /* Завершить работу */
}

.
 /*      Пример 24     */

/* Пример коммуникации процессов при помощи программных каналов
 * (трубы, pipes).
 *      Данная программа превращается в две программы,
 *      соединенные трубами в таком порядке:
 *
 *         stdout                  stdin
 *        /------------ PIP1 -----------> cmd2
 *      cmd1 <----------PIP2---------------/
 *         stdin                   stdout
 */
/* файл LOOP_strt.c */
#include <stdio.h>

#define eq(s1,s2) ( strcmp(s1,s2) == 0 ) /* истина, если строки равны */
#define SEP         "---"                /* разделитель команд при наборе */

main( c, v ) char **v;
{
 char **p, **q;
 int pid;
 int PIP1[2];    /* труба cmd1-->cmd2 */
 int PIP2[2];    /* труба cmd2-->cmd1 */

 if( c==1 ){
  printf( "Call: strt cmd1... %s cmd2...\n", SEP );
  exit(1);
 }

   /* разбор аргументов */
 v++;
 /* в p - аргументы первой команды */
 p = v;
 while( *v && !eq( *v, SEP ))
  v++;
 *v = NULL;

 v++;
 /* в q - аргументы второй команды */
 q = v;

 pipe( PIP1 );   /* создаем две трубы */
 pipe( PIP2 );   /* PIP[0] - открыт на чтение, PIP[1] - на запись */

 if( pid = fork()){      /* развилка: порождаем процесс */
 /* ПОРОЖДЕННЫЙ ПРОЦЕСС */
  fprintf( stderr, "сын=%s pid=%d\n", p[0], getpid());

  /* перенаправляем stdout нового процесса в PIP1 */
  dup2( PIP1[1], 1 );
  close( PIP1[1] );
  /* канал чтения мы не будем использовать */
     close( PIP1[0] );

  /* перенаправляем stdin из PIP2 */
  dup2( PIP2[0], 0 );
  close( PIP2[0] );
  /* канал записи мы не будем использовать */
     close( PIP2[1] );

  /* начинаем выполнять программу, содержащуюся в
   * файле p[0] с аргументами p (т.е. cmd1)
   */
  execvp( p[0], p );
  /* возврата из сисвызова exec не бывает */
 }else{
 /* ПРОЦЕСС-РОДИТЕЛЬ */
  fprintf( stderr, "отец=%s pid=%d\n", q[0], getpid());

  /* перенаправляем stdout в PIP2 */
  dup2( PIP2[1], 1 );
  close( PIP2[1] ); close( PIP2[0] );

  /* перенаправляем stdin из PIP1 */
  dup2( PIP1[0], 0 );
  close( PIP1[0] ); close( PIP1[1] );

  /* запускаем cmd2 */
  execvp( q[0], q );
 }
}
/* Ниже приводятся тексты двух программ, которые можно запустить
 * как тест. Сервер компилируется в программу cmd2,
 * клиент - в программу cmd1. Если запускающая программа
 * скомпилирована в strt, то наберите команду
 *              strt cmd1 --- cmd2
 *  либо        strt cmd2 --- cmd1
 */

/* файл LOOP_p.c ---------------------------------------------
 * Процесс-клиент (cmd1)
 */
#include <stdio.h>
int trace = 1;  /* вести трассировку своих действий */

main(c , v) char **v;
{
 FILE *fp;       int pid;
 char buf[128];

 fprintf( stderr, "P: process pid=%d\n", getpid());
 fp = fopen( "LOOP_p.c", "r" );
 /* открываем файл с текстом этой команды */

 /* читаем его построчно */
 while( fgets( buf, sizeof buf, fp ) != NULL ){

  if( trace ) fprintf( stderr, "P посылает: %s", buf );
  /* посылаем его в стандартный вывод: трубу PIP1 */
  printf( "%s", buf );
  fflush( stdout );

  /* ожидать ответа из трубы PIP2 */
  fgets( buf, sizeof buf, stdin );
  if( trace ) fprintf( stderr, "P получил: %s", buf );
 }
 fclose( stdout );
 /* отключиться от трубы PIP1. Если этого не сделать, сервер
  * не прочитает из нее EOF */

 while((pid = wait(NULL)) > 0 )
  fprintf( stderr, "P: %d умер\n", pid );
}

/* файл LOOP_q.c ------------------------------------------------
 * процесс-сервер (cmd2)
 */
#include <stdio.h>
int trace = 1;

main(c , v) char **v;
{
 char buf[128];          int pid;

 fprintf( stderr, "Q: process pid=%d\n", getpid());
 /* читать поступающие из трубы PIP1 строки */
 while( fgets( buf, sizeof(buf), stdin ) != NULL ){

  /* напечатать полученное сообщение */
  if( trace ) fprintf( stderr, "Q прочел: %s", buf );

  if( trace ) fprintf( stderr, "Q отвечает: OK=%s", buf );
  /* ответить в трубу PIP2 */
  printf( "OK=%s", buf ); fflush( stdout );
 }
 fclose( stdout );       /* отключиться от трубы PIP2 */

 while((pid = wait(NULL)) > 0 )
  fprintf( stderr, "Q: %d умер\n", pid );
}
.
 /*      Пример 25      */

/* Пример использования именованных "труб" (pipes) FIFO-файлов
 * для коммуникации независимых процессов
 * (FIFO - first in, first out : первым пришел - первым ушел).
 * По мотивам книги М.Дансмура и Г.Дейвиса.
 */

/* файл P_packet.h --------------------------------------------*/
#include <sys/types.h>
#include <sys/stat.h>   /* S_IFIFO */

/* структура пакета-запроса */
struct packet {
 int pk_pid;     /* идентификатор процесса-отправителя */
 int pk_blk;     /* номер блока, который надо прочитать */
 int pk_code;    /* код запроса */
};

/* request codes (коды запросов) */
#define RQ_READ         0       /* запрос на чтение */
#define CONNECT         1       /* запрос на соединение */
#define SENDPID         2       /* ответ на запрос соединения */
#define DISCONNECT      3       /* разрыв связи */
#define BYE             4       /* завершить сервер */

/* имена FIFO-каналов связи */
#define DNAME           "datapipe"
#define CNAME           "ctrlpipe"

/* размер блока информации */
#define PBUFSIZE 512

/* P_client.c --------------------------------------------------------- */
/*
 *      Процесс-клиент, посылающий запросы к серверу.
 */
#include <stdio.h>
#include <signal.h>
#include <fcntl.h>
#include "P_packet.h"

int datapipe, ctrlpipe;
int got_sig;
int mypid;      /* идентификатор процесса-клиента */
int spid;       /* идентификатор процесса-сервера */

/* waiting for signal */
#define WAITSIG   while( !got_sig )

void handler(nsig){
 signal( SIGUSR1, handler );
 got_sig ++;
}

void init(){
 extern void die();

 /* Ожидать создания каналов связи */
 while( (datapipe = open( DNAME, O_RDONLY | O_NDELAY )) < 0 );
 while( (ctrlpipe = open( CNAME, O_WRONLY | O_NDELAY )) < 0 );
 mypid = getpid();       /* my process identifier */
 printf( "Client pid=%d started\n", mypid );

 signal( SIGINT,  die);
 signal( SIGQUIT, die);
 signal( SIGTERM, die);

 handler(0);
}

int canRun = 1;

void die(nsig){
 canRun = 0;
}

/* подключиться к серверу, запросив его pid */
connect(){
 struct packet pk;

 pk.pk_pid = mypid;
 pk.pk_code = CONNECT;
 pk.pk_blk = (-1);

 got_sig = 0;
 write( ctrlpipe, &pk, sizeof pk ); /* послать запрос */

 /* ожидать сигнала-"толчка" */
 WAITSIG;

 /* прочитать ответ из канала данных */
 read( datapipe, &pk, sizeof pk );

 /* послать сигнал-подтверждение */
 kill( pk.pk_pid, SIGUSR1 );
 return pk.pk_pid;
}

void disconnect(){
 struct packet pk;

 pk.pk_pid  = mypid;
 pk.pk_code = DISCONNECT;
 pk.pk_blk  = (-1);

 got_sig = 0;
 write( ctrlpipe, &pk, sizeof pk );      /* send request */

 /* wait for reply */
 WAITSIG;

 /* receive reply */
 read( datapipe, &pk, sizeof pk );

 /* confirm */
 kill( pk.pk_pid, SIGUSR1 );

 printf( "Disconnected.\n" );
}

request( ptr, blk, spid )
 char *ptr;
 int blk;
 int spid;
{
 struct packet pk;

 pk.pk_pid = mypid;
 pk.pk_blk = blk;
 pk.pk_code = RQ_READ;

 got_sig = 0;
 write( ctrlpipe, &pk, sizeof pk );
 WAITSIG;
 read( datapipe, ptr, PBUFSIZE );
 kill( spid, SIGUSR1 );
}

bye(){
 struct packet pk;

 pk.pk_pid = mypid;
 pk.pk_code = BYE;
 pk.pk_blk = (-1);

 got_sig = 0;
 write( ctrlpipe, &pk, sizeof pk );      /* send request */
 exit(0);
}

/* client [номер_блока] */
main(argc, argv) char *argv[];
{
 int blk;
 char buffer[ PBUFSIZE ];

 setbuf( stdout, NULL ); /* make unbuffered */
 blk = (argv[1] ? atoi( argv[1] ) : 0);
 init();
 spid = connect();
 printf( "Client pid=%d connected to server pid=%d\n",
   mypid, spid );

 /* запрос блока номер -33 соответствует запросу "завершить
  * работу сервера"
  */
 if( blk == -33 )
  bye();

 /* в цикле посылать запросы на чтение блока blk */
 while( canRun ){
  request( buffer, blk, spid );
  printf( "\nBEG-------------------------------------\n" );
  fwrite( buffer, PBUFSIZE, 1, stdout );
  printf( "\nEND-------------------------------------\n" );
 }
 disconnect();   /* отключиться от сервера */
 exit(0);
}

/* P_server.c ---------------------------------------------------------*/
/*
 *      Процесс-сервер, принимающий запросы и выполняющий их.
 */

#include <stdio.h>
#include <signal.h>
#include <fcntl.h>
#include "P_packet.h"

int datapipe, ctrlpipe, datafile, got_sig;
char *dataname = "/etc/passwd";

/* waiting for signal */
#define WAITSIG   while( !got_sig )

void handler(nsig){
 signal( SIGUSR1, handler );     /* reset trap */
 got_sig++;
}

/* завершение работы сервера: уничтожить каналы связи */
void die(nsig){
 unlink( CNAME ); unlink( DNAME ); exit(0);
 /* Если эти файлы были открыты клиентами,
  * то клиенты не умрут, хотя имена файлов и будут удалены!
  */
}

main(){
 struct packet pk;
 struct packet sendpk;

 /* сделать стандартный вывод небуферизованным каналом */
 setbuf( stdout, NULL );         /* make unbuffered */

 /* создать каналы связи */
 mknod( DNAME, S_IFIFO | 0666, 0 ); /* create FIFO */
 mknod( CNAME, S_IFIFO | 0666, 0 ); /* create FIFO */

 /* по этим сигналам будет вызываться функция die() */
 signal( SIGINT, die );
 signal( SIGQUIT, die );
 signal( SIGTERM, die );

 /* Открыть управляющий канал связи. O_NDELAY означает,
  * что файл открывается для "чтения без ожидания",
  * т.е. если канал пуст (нет заявок), то системный вызов
  * read() не будет "спать", дожидаясь появления информации,
  * а просто вернет 0 (прочитано 0 байт).
  * Этот флаг применим также к чтению с терминала.
  */
 ctrlpipe = open( CNAME, O_RDONLY | O_NDELAY );
 if( ctrlpipe < 0 ){
  printf( "Can't open %s\n", CNAME );
  die(0);
 }
 datafile = open( dataname, O_RDONLY );
 if( datafile < 0 ){
  printf( "Can't open %s\n", dataname );
  die(0);
 }

 /* заранее формируем пакет для ответов */
 sendpk.pk_code = SENDPID;
 sendpk.pk_pid = getpid();       /* server's pid */
 sendpk.pk_blk = (-1);

 printf( "Server pid=%d\n", getpid());

 handler(0);
 for(;;){
  int n;
  static long i = 0L;

  /* active spin loop */
  printf( "%20ld\r", i++ );

  /* опрашивать канал насчет поступления запросов */
  while((n = read( ctrlpipe, &pk, sizeof(pk))) > 0 ){
   putchar( '\n' );
   if( n != sizeof pk ){
    printf( "Wrong packet size\n" );
    continue;
   }
   /* обработать прочитанный запрос */
   process( &pk, &sendpk );
  }
 }
 die(0);
}

process( pkp, spkp )
 struct packet *pkp, *spkp;
{
 char pbuf[ PBUFSIZE ];
 /* Запись в FIFO-файл будет произведена только если
  * он уже открыт для чтения
  */
 datapipe = open( DNAME, O_WRONLY | O_NDELAY );

 printf( "REQUEST TYPE_%d from pid=%d blk=%d\n",
  pkp->pk_code, pkp->pk_pid, pkp->pk_blk );

 switch( pkp -> pk_code ){
 case CONNECT:   /* ответить своим идентификатором процесса */
  write( datapipe, spkp, sizeof( struct packet ));
  break;
 case RQ_READ:   /* ответить блоком информации из файла */
  /* read block # pk_blk */
  lseek( datafile, pkp -> pk_blk * (long)PBUFSIZE, 0 );
  read(  datafile, pbuf, PBUFSIZE );
  write( datapipe, pbuf, PBUFSIZE );
  break;
 case DISCONNECT: /* подтвердить отключение */
  printf( "Client pid=%d finished\n", pkp -> pk_pid );
  write ( datapipe, spkp, sizeof( struct packet ));
  break;
 case BYE:       /* завершиться */
  printf( "Server terminated.\n" );
  kill( pkp-> pk_pid, SIGKILL );
  die(0);
 default:
  printf( "Unknown packet type %d\n", pkp -> pk_code );
  break;
 }
 close( datapipe );

 /* "подтолкнуть" отправителя сигналом */
 got_sig = 0;
 kill( pkp -> pk_pid , SIGUSR1 );

 printf( "Waiting for reply...  " );
 /* ждать сигнала-подтверждения от клиента */
 WAITSIG;

 printf( "server continued\n" );
}
.
 /*      Пример 26        */
/* Общение процессов при помощи общей памяти и семафоров.
 * Вызов:       shms &
 *              shmc a & shmc b & shmc c &
 */
/* --------------------------- файл shm.h ----------------------- */
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <signal.h>
#include <errno.h>
extern errno;           /* Системный код ошибки */
struct connect {        /* Структура почтового ящика */
 int pid; int msgnum; int max;
 char message[128];      /* текст сообщения */
};
#define NSEMS   3       /* число семафоров */
 /* Имена семафоров */
#define EMPTY    0       /* 1 - ящик пуст; 0 - содержит письмо */
#define NOTEMPTY 1       /* негатив для EMPTY                  */
#define ACCESS   2       /* 1 - ящик доступен (закрыт);
     * 0 - ящик уже открыт кем-то еще     */
 /* Значения семафоров */
#define YES     1
#define NO      0
 /* Операции */
#define OPEN    1
#define CLOSE  (-1)
#define TEST_NO 0

#ifdef COMMENT
Алгоритм одновременного изменения семафоров: semop
   Дано:
аргумент: число семафоров                         : nsems
аргумент: величины изменения                      : sem_op[i]
в ядре:   текущие значения семафоров группы sem_id: sem[i]
   Алгоритм:

     again:  Сохранить значения всех семафоров (для отмены изменений);
      for(i=0; i<nsems; i++)
     /* OPEN */  if( sem_op[i] > 0 ){
       sem[i] += sem_op[i];
       разбудитьЖдущихСобытие( "sem[i]++" );
     /* CLOSE */ }else if( sem_op[i] < 0 ){
       if((newsm = sem[i] + sem_op[i]) >= 0 ){
    sem[i] = newsm;
    if( sem[i] == 0 )
        разбудитьЖдущихСобытие( "sem[i]==0" );
       }else{
    восстановитьВсеСемафоры;
    ждатьСобытие( "sem[i]++" );
    goto again;
       }
     /* TEST0 */ }else{   /* sem_op[i] == 0 */
    if( sem[i] != 0 ){
        восстановитьВсеСемафоры;
        ждатьСобытие( "sem[i]==0" );
        goto again;
     }
   }

   Алгоритм синхронизации в нашей схеме КЛИЕНТ-СЕРВЕР:
|----------------------------------------------------------------|
|семафоры:           EMPTY               ACCESS                  |
|----------------------------------------------------------------|
|начальное значение:  YES                  YES                   |
|----------------------------------------------------------------|

        СЕРВЕР
|================================================================|
|loop:                                                           |
|----------------------------------------------------------------|
|ждать:               NO                   YES                   |
|сделать:             NO(test0)            NO(close)             |
|----------------------------------------------------------------|
|                     прочесть почту;                            |
|----------------------------------------------------------------|
|из:                  NO                   NO                    |
|сделать:             YES(open)            YES(open)             |
|----------------------------------------------------------------|
|                     goto loop;                                 |
|================================================================|

        КЛИЕНТ
|================================================================|
|loop:                                                           |
|----------------------------------------------------------------|
|ждать:              YES                   YES                   |
|сделать:            YES(test!=0)          NO(close)             |
|----------------------------------------------------------------|
|                    записать почту;                             |
|----------------------------------------------------------------|
|из:                  YES                  NO                    |
|сделать:             NO(close)            YES(open)             |
|----------------------------------------------------------------|
|                     goto loop;                                 |
|================================================================|

 К сожалению, операции test!=0 не существует - приходится вводить
 дополнительный семафор NOTEMPTY, негативный для EMPTY:
|----------------------------------------------------------------|
|семафоры:           EMPTY    NOTEMPTY   ACCESS                  |
|----------------------------------------------------------------|
|начальное значение:  YES       NO         YES                   |
|----------------------------------------------------------------|

        СЕРВЕР
|================================================================|
|loop:                                                           |
|----------------------------------------------------------------|
|ждать:               NO         -         YES                   |
|сделать:             NO(test0)  -         NO(close)             |
|----------------------------------------------------------------|
|                     прочесть почту;                            |
|----------------------------------------------------------------|
|из:                  NO         YES       NO                    |
|сделать:             YES(open)  NO(close) YES(open)             |
|----------------------------------------------------------------|
|                     goto loop;                                 |
|================================================================|

        КЛИЕНТ
|================================================================|
|loop:                                                           |
|----------------------------------------------------------------|
|ждать:              -           NO        YES                   |
|сделать:            -           NO(test0) NO(close)             |
|----------------------------------------------------------------|
|                    записать почту;                             |
|----------------------------------------------------------------|
|из:                  YES        NO        NO                    |
|сделать:             NO(close)  YES(open) YES(open)             |
|----------------------------------------------------------------|
|                     goto loop;                                 |
|================================================================|
#endif /*COMMENT*/

/* Общая часть сервера и клиента ------------------------------- */
key_t key = 1917;       /* Уникальный ключ для доступа           */
int   shm_id;           /* Дескриптор для доступа к общей памяти */
int   sem_id;           /* Дескриптор для доступа к семафорам    */
char  name[40];         /* имя программы                         */

char far *addr;
struct connect far *caddr;
struct sembuf ops[NSEMS];
   /* EMPTY   NOTEMPTY   ACCESS */
short values[NSEMS] = {    YES,    NO,        YES     };

void semtell(msg, name) char *msg, *name; { int i;
 semctl(sem_id, NSEMS, GETALL, values);
 printf( "%s %-10s: значения семафоров:", name, msg);
 for(i=0; i < NSEMS; i++) printf( " %d", values[i]);
 putchar('\n');
}

void inisem(){
 register i;
 for(i=0; i < NSEMS; i++ ) ops[i].sem_flg = 0;
}
/* --------------------------- файл shms.c ----------------------- */
/* Shared memory server */
#include "shm.h"
int npack;              /* номер сообщения */
void cleanup(sig){
 /* Уничтожить сегмент общей памяти (это нужно делать явно) */
 shmctl( shm_id, IPC_RMID, NULL );
 /* Уничтожить семафоры */
 semctl( sem_id, NSEMS, IPC_RMID, NULL );
 if( npack ) printf( "\t** Всего было %d сообщений **\n", npack+1);
 exit(0);
}
void main(){
 register i; int pid = getpid();
 FILE *fout;

 sprintf( name, "Server-%03d", pid );
 for( i = 1; i <= SIGTERM; i++ )
  signal( i, cleanup );

 /* Создать разделяемый сегмент */
 if((shm_id = shmget( key, sizeof(struct connect),
        0644 | IPC_CREAT )) < 0 ){
  perror( "shmget" ) ; exit(1);
 }

 /* Подключить общий сегмент к произвольному адресу */
 if((addr = (char far *) shmat( shm_id, NULL, 0 )) == NULL ){
  perror( "shmat" ); cleanup();
 }
 caddr = (struct connect far *) addr;

 /* Создать группу из NSEMS семафоров */
 if((sem_id = semget( key, NSEMS, 0644 |IPC_CREAT |IPC_EXCL)) < 0){
   if(errno == EEXIST){ printf( "Сервер уже запущен\n");exit(2); }
   else{                perror( "semget" ); cleanup();           }
 }
 /* Загрузить начальные значения семафоров */
 semctl( sem_id, NSEMS, SETALL, values );

 setbuf(stdout, NULL);
 inisem(); printf( "Server is up now. Читай файл MESSAGES.\n");

 fout = fopen( "MESSAGES", "w");
 for(;;npack++){
  printf( "%s: ждет почты\n", name );
  semtell("Вход", name);
  ops[0].sem_num = EMPTY;    ops[0].sem_op = TEST_NO;
  ops[1].sem_num = ACCESS;   ops[1].sem_op = CLOSE;
  semop( sem_id, ops, 2      /* сразу два семафора */);

  printf( "%s: GOT-%02d/%02d от %d \"%s\"\n", name,
    caddr->msgnum, caddr->max, caddr->pid, caddr->message);
  fprintf( fout, "#%03d %02d/%02d от %d \"%s\"\n", npack,
    caddr->msgnum, caddr->max, caddr->pid, caddr->message);
  if( ! strcmp(caddr->message, "-exit" )){
   printf( "%s: завершает работу.\n", name );
   cleanup();
  }

  semtell("Выход", name);
  ops[0].sem_num = EMPTY   ; ops[0].sem_op = OPEN;
  ops[1].sem_num = NOTEMPTY; ops[1].sem_op = CLOSE;
  ops[2].sem_num = ACCESS  ; ops[2].sem_op = OPEN;
  semop( sem_id, ops, 3 /* сразу три семафора */);
 }
 /*NOTREACHED*/
}

/* --------------------------- файл shmc.c ----------------------- */
/* Shared memory client */
#include "shm.h"

void ignsigs(sig){
 register i;
 for( i = 1; i <= SIGTERM; i++ )
  signal( i, ignsigs );
 printf( "Клиент игнорирует сигналы,\n\
чтобы не оставлять закрытых семафоров в случае своей смерти.\n" );
}

void main(argc, argv) char **argv; {
 int pid = getpid();
 int i, ntimes = 60;

 if( argc < 2 ){
    fprintf( stderr, "Вызов: %s сообщение [числоПовторов]\n", argv[0] );
    fprintf( stderr, "сообщение \"-exit\" завершает сервер\n");
    fprintf( stderr, "сообщение \"-info\" выдает значения семафоров\n");
  exit(1);
 }
 if( argc > 2 ) ntimes = atoi(argv[2]);
 sprintf( name, "Client-%03d", pid);
 ignsigs(); srand( pid );

 /* Получить доступ к разделяемому сегменту */
 if((shm_id = shmget( key, sizeof(struct connect), 0644)) < 0 ){
  perror( "shmget" ); exit(2);
 }

 /* Подключить общий сегмент к произвольному адресу */
 if((addr = (char far *) shmat( shm_id, NULL, 0 )) == NULL ){
  perror( "shmat" ); exit(3);
 }
 caddr = (struct connect far *) addr;

 /* Получить доступ к семафорам */
 if((sem_id = semget( key, NSEMS, 0644)) < 0 ){
  perror( "semget" ); exit(4);
 }
 setbuf(stdout, NULL);
 inisem();

 if( !strcmp(argv[1], "-info")){
  semtell("Информация", name); exit(0);
 }

 for( i=0; i < ntimes; i++ ){
  printf( "%s: ждет пустого ящика\n", name);
  semtell("Вход", name);
  ops[0].sem_num = NOTEMPTY; ops[0].sem_op = TEST_NO;
  ops[1].sem_num = ACCESS  ; ops[1].sem_op = CLOSE;
  if( semop( sem_id, ops, 2 /* сразу два семафора */) < 0)
   goto err;

  caddr->pid = pid; caddr->msgnum = i; caddr->max = ntimes;
  strncpy( caddr->message, argv[1],
    sizeof(caddr->message) - 1);
  printf( "%s: PUT-%02d \"%s\"\n", name, i, argv[1]);

  semtell("Выход", name);
  ops[0].sem_num = EMPTY   ; ops[0].sem_op = CLOSE;
  ops[1].sem_num = NOTEMPTY; ops[1].sem_op = OPEN;
  ops[2].sem_num = ACCESS  ; ops[2].sem_op = OPEN;
  if( semop( sem_id, ops, 3 /* сразу три семафора */) < 0)
   goto err;
  if( rand()%2 ) sleep(2);  /* пауза */

 }
 shmdt( addr );  /* Отключиться от общего сегмента */
 exit(0);
err:
 perror("semop");
 exit(5);
}
.
 /*      Пример 27    */

/* Коммуникация процессов при помощи псевдо-терминала.
 *   Данная программа позволяет сохранять полный протокол работы
 *   экранной программы в файл.
 *   Не экранные программы данная версия НЕ трассирует,
 *   поскольку сама работает в "прозрачном" режиме.
 *
 * Вариацией данной программы может служить использование
 * системного вызова select() вместо запуска нескольких процессов.
 *
 * Программа также иллюстрирует "дерево" из 5 процессов.
 *            Данная версия написана для UNIX System V.
 *      TRACE__
 *  \          \           master    slave
 *  |экран<======\(Reader)=======!~!<====(целевая  )
 *  /     <==\      |            ! !====>(программа)
 *             \    |            !P!         |
 *              |   |            !T!         |
 *    . . . .   |   |            !Y!      (Slave)-->Управляет
 *   клавиатура=|===|=>(Writer)=>!_!         | \    семафором
 *              |   |       |                |   \
 *              |  #####starter##################  \
 *              |...................................|
 *                                ftty
 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/signal.h>
#include <termio.h>
#include <sys/stat.h>
#include <fcntl.h>

extern int  exit ();
extern char *ttyname ();
extern  FILE * fopen ();
extern errno;

#define SEMAPHORE "/tmp/+++"            /* семафорный файл */
#define TRACE     "./TRACE"             /* файл с протоколом */

      /* псевдотерминал связи */
/* master - это часть, которая ведет себя как ФАЙЛ и умеет
 * реагировать на некоторые специальные ioctl()-и */
#define PTY       "/dev/ptyp0"          /* master */
/* slave - это часть, которая ведет себя как драйвер терминалов */
#define TTYP      "/dev/ttyp0"          /* slave  */

int     ptyfd;
FILE * ftrace = NULL;

/* при прерывании завершить работу процесса "писателя" */
onintr () {
    closeVisual ();
    fprintf (stderr, "\rwriter finished\r\n");
    exit (0);
}

/* завершение работы процесса-"читателя" */
bye () {
    if (ftrace)
 fclose (ftrace);
    fprintf (stderr, "\rreader finished\r\n");
    exit (0);
}

int     visual = 0;
struct termio   old,
  new;

/* настроить режимы работы терминала на "прозрачный" режим */
initVisual () {
    ioctl (0, TCGETA, &old);
    new = old;
    new.c_iflag &= ~ICRNL;
    new.c_lflag &= ~(ECHO | ICANON);
    new.c_oflag &= ~(TAB3 | ONLCR);
    new.c_cc[VMIN] = 1;
    new.c_cc[VTIME] = 0;

 /* new.c_cc[VINTR] = ctrl('C');   */
    new.c_cc[VQUIT] = 0;
    new.c_cc[VERASE] = 0;
    new.c_cc[VKILL] = 0;
}

/* включить прозрачный режим */
openVisual () {
    if (visual) return;
    visual = 1;
    ioctl (0, TCSETAW, &new);
}

/* выключить прозрачный режим */
closeVisual () {
    if (!visual) return;
    visual = 0;
    ioctl (0, TCSETAW, &old);
}

struct stat st;

main (argc, argv) char **argv; {
    int     r,          /* pid процесса-"читателя" */
     w;          /* pid процесса-"писателя" */

    if (argc == 1) {
 fprintf (stderr, "pty CMD ...\n");
 exit (1);
    }

    initVisual ();

    if((ptyfd = open ( PTY , O_RDWR)) < 0){
 fprintf(stderr, "Cannot open pty\n"); exit(2);
    }

    /* запустить процесс чтения с псевдодисплея */
    r = startReader ();

    /* запустить процесс чтения с клавиатуры */
    w = startWriter ();

    sleep (2);
    /* запустить протоколируемый процесс */
    startSlave (argv + 1, r, w);

    /* дождаться окончания всех потомков */
    while (wait (NULL) > 0);
    exit (0);
}

/* запуск протоколируемого процесса */
startSlave (argv, r, w) char  **argv; {
    FILE * ftty;
    int     pid;
    int     tfd;
    char   *tty = ttyname (1);   /* полное имя нашего терминала */

    if (!(pid = fork ())) {

    /* PTY SLAVE process */
 ftty = fopen (tty, "w"); /* Для выдачи сообщений */
 setpgrp ();       /* образовать новую группу процессов ;
      * лишиться управляющего терминала */

 /* закрыть стандартные ввод, вывод, вывод ошибок */
 close (0);
 close (1);
 close (2);

 /* первый открытый терминал станет управляющим для процесса,
  * не имеющего управляющего терминала.
  * Открываем псевдотерминал (slave) в качестве стандартных
  * ввода, вывода и вывода ошибок
  */
 open ( TTYP, O_RDWR);
 open ( TTYP, O_RDWR);
 tfd = open ( TTYP, O_RDWR);

 if (tfd < 0) {
     fprintf (ftty, "\rSlave: can't read/write pty\r\n");
     kill(r, SIGKILL); kill(w, SIGKILL); exit (1);
 }

 /* запускаем целевую программу */
 if (!(pid = fork ())) {

     fprintf (ftty, "\rCreating %s\r\n", SEMAPHORE);
     fflush (ftty);

     /* создаем семафорный файл */
     close (creat (SEMAPHORE, 0644));

     fprintf (ftty, "\rStart %s\r\n", argv[0]);
     fclose(ftty);

     /* заменить ответвившийся процесс программой,
      * указанной в аргументах
      */
     execvp (argv[0], argv);
     exit (errno);
 }

 /* дожидаться окончания целевой программы */
 while (wait (NULL) != pid);

 /* уничтожить семафор, что является признаком завершения
  * для процессов чтения и записи
  */
 unlink (SEMAPHORE);

 fprintf (ftty, "\rDied.\r\n");
 fflush (ftty);

    /* убить процессы чтения и записи */
    /* terminate reader & writer */
 kill (r, SIGINT); kill (w, SIGINT);

 exit (0);
    }
    return pid;
}


 /* Пара master-процессов чтения и записи */

/* запуск процесса чтения с псевдотерминала (из master-части) */
startReader () {
    char    c[512];
    int     pid;
    int n;

    if (!(pid = fork ())) {
    /* читать данные с ptyp на экран и в файл трассировки */

 signal (SIGINT, bye);

 /* ожидать появления семафора */
 while (stat (SEMAPHORE, &st) < 0);

 fprintf (stderr, "\rReader: Hello\r\n");
 ftrace = fopen (TRACE, "w");

 /* работать, пока существует семафорный файл */
 while (stat (SEMAPHORE, &st) >= 0) {

     /* прочесть очередные данные */
     n = read (ptyfd, c, 512);

     if( n > 0 ) {
        /* записать их на настоящий терминал */
        fwrite( c, sizeof(char), n, stdout );
        /* и в файл протокола */
        fwrite( c, sizeof(char), n, ftrace );

        fflush (stdout);
     }
 }
 bye ();
    }
    return pid;
}

/* запуск процесса чтения данных с клавиатуры и записи
 * их на "псевдоклавиатуру". Эти данные протоколировать не надо,
 * так как их эхо-отобразит сам псевдотерминал
 */
startWriter () {
    char    c;
    int     pid;

    if (!(pid = fork ())) {
    /* читать клавиатуру моего терминала и выдавать это в ptyp */

 openVisual (); /* наш терминал - в прозрачный режим */
 signal (SIGINT, onintr);

 while (stat (SEMAPHORE, &st) < 0);
 fprintf (stderr, "\rWriter: Hello\r\n");

 /* работать, пока существует семафорный файл */
 while (stat (SEMAPHORE, &st) >= 0) {
     read (0, &c, 1);            /* читать букву с клавиатуры */
     write (ptyfd, &c, 1);       /* записать ее на master-pty */
 }
 onintr ();      /* завершиться */
    }
    return pid;
}
.
 /*      Пример 28      */

/* Оценка фрагментированности тома файловой системы
 * (неупорядоченности блоков в файлах).
 * Иллюстрация работы с файловой системой UNIX напрямую,
 * в обход ядра системы. Для этого вы должны иметь права
 * суперпользователя !!! Данная программа относится к классу
 * "системных" (администраторских) программ.
 * Эта программа предполагает каноническую файловую систему V7
 * ("старую"), а не ту, которая используется начиная с BSD/4.2 и
 * в которой все устроено несколько сложнее и эффективнее.
 * Поэтому вы должны будете модифицировать эту программу для
 * использования в современных UNIX-системах.
 * По мотивам книги М.Дансмура и Г.Дейвиса.
 */

#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/ino.h>            /* struct dinode: disk inode */
#include <sys/stat.h>           /* struct stat */
#include <sys/dir.h>            /* struct direct */

char blkflag;   /* печатать ли номера блоков файла */

/* Отведение памяти в куче с выдачей ошибки, если нет памяти */
char *MyAlloc( n ){
 extern char *malloc();
 char *ptr;

 ptr = malloc( n );
 if( ptr == NULL ){
  fprintf( stderr, "Cannot allocate %d bytes\n", n );
  exit(77);
 }
 return ptr;
}
char DEV[] = "/dev" ;   /* каталог, где лежат все файлы устройств */

/* Определить имя устройства по его st_dev номеру.
 * Поиск - по каталогу /dev
 */
char *whichdev( dev ) dev_t dev;
{
 struct stat s;
 struct direct d;
 long i;
 int fd;         /* дескриптор чтения каталога */
 long dsize;     /* число слотов каталога */
 char *devname;

 if( stat( DEV, &s ) < 0 ){
  fprintf( stderr, "Cannot stat %s\n", DEV );
  exit(1);
 }

 if((fd = open( DEV, O_RDONLY )) < 0 ){
  fprintf( stderr, "Cannot read %s\n", DEV );
  exit(2);
 }
 dsize = s.st_size / sizeof( struct direct );

 /* читать каталог */
 for( i = 0 ; i < dsize ; i++ ){
  char leaf[ DIRSIZ + 1 ];

  if( read( fd, &d, sizeof d ) != sizeof d ){
   fprintf( stderr, "Cannot read %s\n", DEV );
   exit(14);
  }

  if( ! d.d_ino ) continue;  /* пустой слот */

  strncpy( leaf, d.d_name, DIRSIZ );
  leaf[ DIRSIZ ] = '\0';

  devname = MyAlloc( strlen( DEV ) + 1 + strlen( leaf ) + 1 );
    /*        /dev     /      xxxx         \0  */
  sprintf( devname, "%s/%s", DEV, leaf );
  if( stat( devname, &s ) < 0 ){
   fprintf( stderr, "Cannot stat %s\n", devname );
   exit(3);
  }
  if( (s.st_mode & S_IFMT ) == S_IFBLK && s.st_rdev == dev ){
   close(fd);
   return devname;
  } else  free( devname );
 }
 close( fd );
 return NULL;
}

/* Файловая система UNIX: константы подстроены под ДЕМОС 2.2 */

/* размер блока файловой системы */
#define BLOCK 1024 /* либо станд. константа BSIZE из <sys/param.h> */

/* число адресов блоков в косвенном блоке */
#define NAPB        (BLOCK/sizeof(daddr_t))
#define LNAPB        ((long) NAPB )

/* число I-узлов в блоке I-файла */
#ifndef INOPB
# define INOPB (BLOCK/sizeof(struct dinode))
#endif

/* I-узлы - "паспорта" файлов. I-узлы расположены в начале диска,
   в области, называемой I-файл. В I-узле файла содержатся:
   размер файла, коды доступа, владелец файла, и.т.п.
   В частности - адреса блоков файла хранятся в массиве di_addr:
   0  :
   ...  сначала   DIR0 адресов первых блоков
   IX1: 1 адрес   косвенного блока, содержащего адреса еще NAPB блоков
   IX2: 1 адрес   косв. блока, содержащего адреса NAPB косв. блоков
   IX3: 1 адрес   косв. блока, содержащего адреса NAPB косв. блоков,
          содержащих адреса еще NAPB косв. блоков
   Сисвызов stat() выдает как раз часть информации из I-узла.
   Поле d_ino в каталоге хранит номер I-узла файла.
*/

/* число адресных полей по 3 байта в I-узле */
#define NADDR 7

/* число прямо адресуемых блоков */
#define DIR0 ((long)(NADDR-3))

/* число прямых и первых косвенных блоков */
#define DIR1 (DIR0 + LNAPB)

/* число прямых, первых и вторых косвенных блоков */
#define DIR2 (DIR0 + LNAPB + LNAPB*LNAPB)

/* число прямых, вторых и третьих косвенных блоков */
#define DIR3 (DIR0 + LNAPB + LNAPB*LNAPB + LNAPB*LNAPB*LNAPB)

/* индекс адреса первичного блока косвенности */
#define IX1 (NADDR-3)

/* индекс адреса вторичного блока косвенности */
#define IX2 (NADDR-2)

/* индекс адреса третичного блока косвенности */
#define IX3 (NADDR-1)

/* Выдать физический номер блока диска,
 * соответствующий логическому блоку файла
 */
daddr_t bmap( fd, ip, lb )
 int fd;                 /* raw диск */
 daddr_t lb;             /* логический блок */
 struct dinode *ip;      /* дисковый I-узел */
{
 long di_map[ NADDR ];
 long dd_map[ NAPB ];

 /* перевести 3х байтовые адреса в daddr_t */
 l3tol( di_map, ip->di_addr, NADDR );

 if( lb < DIR0 )
  return di_map[ lb ];
 if( lb < DIR1 ){
  lb -= DIR0;

  lseek( fd, di_map[ IX1 ] * BLOCK, 0 );
  read( fd, dd_map, BLOCK );

  return dd_map[ lb % LNAPB ];
 }
 if( lb < DIR2 ){
  lb -= DIR1;

  lseek( fd, di_map[ IX2 ] * BLOCK, 0 );
  read(  fd, dd_map, BLOCK );

  lseek( fd, dd_map[ lb / LNAPB ] * BLOCK, 0 );
  read(  fd, dd_map, BLOCK );

  return dd_map[ lb % LNAPB ];
 }
 if( lb < DIR2 ){
  lb -= DIR2;

  lseek( fd, di_map[ IX3 ] * BLOCK, 0 );
  read(  fd, dd_map, BLOCK );

  lseek( fd, dd_map[ lb / (LNAPB*LNAPB) ] * BLOCK, 0 );
  read(  fd, dd_map, BLOCK );

  lseek( fd, dd_map[ lb % (LNAPB*LNAPB) ] * BLOCK, 0 );
  read(  fd, dd_map, BLOCK );

  return dd_map[ lb % LNAPB ];
 }
 fprintf( stderr, "Strange block %ld\n", lb );
 exit(4);
}

/* Рассчитать фрагментацию файла,
   то есть среднее расстояние между блоками файла.
   Норма равна фактору интерливинга для данного устройства.

     N
        SUM          | p(j) - p(j-1) |
     j = 2
        F =  ----------------------------------
    N

   p(j) - номер физ.блока диска, соответствующего
   логич. блоку j
   Замечания:
   1) I-узлы нумеруются с 1 (а не с 0), 0 - признак пустого
      места в каталоге (d_ino == 0).
   2) I-файл начинается со 2-ого блока диска (0-boot, 1-superblock)
   3) если файл пуст - он не содержит блоков, N = 0, F = 0
   4) если блок не отведен ("дырка"), то его адрес равен 0L
*/

double xabs( l ) daddr_t l;
{
 return ( l < (daddr_t) 0 ? -l : l );
}

double getfrag( dev, ino )
 char *dev;      /* имя диска */
 ino_t ino;      /* I-узел файла */
{
 struct dinode db;
 int fd;         /* дескриптор диска */
 daddr_t i;      /* лог. блок */
 daddr_t op;     /* физ.блок */
 daddr_t ip;
 daddr_t nb;     /* длина файла (блоков) */
 long ni = 0L;   /* число интервалов между блоками */
 double ifrag = 0.0;

 if((fd = open( dev, O_RDONLY )) < 0 ){
  fprintf( stderr, "Cannot read %s\n", dev );
  perror( "open" );
  exit(5);
 }

 /* прочитать I-узел с номером ino.
  * Файл I-узлов размещен на диске начиная со 2 блока
  * по INOPB узлов в блоке.
  */
 lseek( fd, (( 2 + ((ino-1)/INOPB)) * (long)BLOCK )  +
     ( sizeof(struct dinode) * ((ino-1) % INOPB)),   0 );
 if( read( fd, &db, sizeof db ) != sizeof db ){
  fprintf( stderr, "Cannot read %s\n", dev );
  perror( "read" );
  exit(6);
 }

 /* вычислить размер файла в блоках */
 nb = ((long) db.di_size + BLOCK - 1) / BLOCK;
 printf( "%4ld blk%s\t" , nb, nb > 1 ? "s" : " " );

 /* игнорировать пустой файл */
 if( nb == 0L ){
  close(fd);
  return 0.0;
 }

 /* вычислить фрагментацию */
 op = bmap( fd, &db, 0L );       /* 0-block */
 if( blkflag ) printf( "%ld ", op );

 for( i = 1 ; i < nb ; i++ ){
  ip = bmap( fd, &db, i );
  if( blkflag ) printf( "%ld ", ip );
  /* адреса, равные 0, следует игнорировать ("дырки") */
  if( ip && op ){
   ni++;
   ifrag += xabs( ip - op );
  }
  if( ip ) op = ip;
 }
 close ( fd );
 if( blkflag ) putchar( '\n' );
 return ni ? (ifrag/ni) : 0.0 ;
}

double process( name ) char *name;
{
 struct stat ss;
 char *dn;
 double f;

 /* определяем имя устройства, на котором расположен
  * файл name */
 if( stat( name, &ss ) < 0 ){
  fprintf( stderr, "Cannot stat %s\n", name );
  exit(8);
 }
 /* printf( "major %d     minor %d", major(ss.st_dev), minor(ss.st_dev)); */
 if((dn = whichdev( ss.st_dev )) == NULL){
  fprintf( stderr, "Cannot determine device\n" );
  exit(9);
 }

 printf( "%-14s on %-12s %12.3f\n",
  name, dn, f = getfrag(dn, ss.st_ino ));
 free( dn );
 return f;
}

usage( name ) char *name; {
 fprintf( stderr, "Usage: %s [-b] file ...\n" , name );
 exit(7);
}

main(ac, av) char *av[];
{
 double fr = 0.0;
 int n = 0;

 if( ac < 2 )
  usage( av[0] );

 if( !strcmp( av[1], "-b" )){
  blkflag = 1;
  av++;
  ac--;
 }
 while( av[1] ){
  fr += process( av[1] );
  n++;
  av++;
 }
 if( n > 1 )
  printf( "\nAverage %12.3f\n", fr / n );
 exit(0);
}
.
 /*      Пример 29       */

/*
 *      Программа восстановления блоков удаленного файла.
 *      Работает на канонической файловой системе UNIX (ДЕМОС).
 *      Просматривает список свободных блоков диска.
 *
 * Эта программа позволяет восстановить блоки ТОЛЬКО ЧТО удаленного файла.
 * Как только вы удалили нужный файл, немедленно прекратите любую
 * работу на машине и даже отмонтируйте диск с удаленным файлом.
 *    Затем, находясь на ДРУГОМ диске, вызовите эту программу.
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/param.h>          /* BSIZE */
#include <sys/filsys.h>         /* struct filsys */
#include <sys/fblk.h>           /* struct fblk */
#include <fcntl.h>
#include <ctype.h>

/*
#define BSIZE 1024     размер блока файловой системы
*/

int fd;             /* raw disk */
int fdout;          /* дескриптор для спасенных блоков на ДРУГОМ диске */
char blk[ BSIZE ],      /* буфер для прочитанного блока */
     sublk[ BSIZE ];    /* буфер для суперблока         */

/* структура суперблока */
struct filsys *super = (struct filsys *) sublk;
/* счетчик */
long n = 0L;

main( ac, av ) char *av[];
{
 daddr_t bno;            /* номер блока из списка свободных */
 extern daddr_t alloc();

 if( ac < 2 ){
  fprintf( stderr, "Usage: %s disk\n", av[0] );
  exit(1);
 }
 if((fd = open( av[1], O_RDONLY )) < 0 ){
  fprintf( stderr, "Can't read %s\n", av[1] );
  exit(2);
 }
 sync();         /* syncronize */

 printf( "Вы должны находиться на ДРУГОМ диске, нежели %s,\n", av[1] );
 printf( "чтобы блоки файлов, в которые будут записаны спасаемые\n");
 printf( "блоки, выделялись на другом устройстве и не портили\n" );
 printf( "список свободных блоков на %s\n\n", av[1] );
 fflush( stdout ); sleep(2);

 /* прочесть суперблок */
 lseek( fd, (long) BSIZE, 0 );
 read(  fd, sublk, BSIZE );

 fprintf( stderr, "%ld free blocks at %s (%6.6s)\n" ,
  super->s_tfree, av[1],
  super->s_fpack );

 /* Просмотр свободных блоков. Список свободных блоков
  * имеет организацию LIFO (стек), поэтому блоки
  * в списке могут идти не в том порядке,
  * в котором они шли в файле. Учтите, что в файле
  * кроме блоков, содержащих текст файла,
  * бывают также косвенные адресные блоки !
  */
 while((bno = alloc()) >= 0L ){
  save( bno );
 }
 printf( "total %ld\n", n );
 exit(0);
}

/* Извлечь очередной блок из списка свободных блоков */
daddr_t alloc(){
 daddr_t bno;

 if( super -> s_nfree <= 0 )     /* число адресов своб. блоков,
      * хранимых в суперблоке */
  goto nospace;
 /* читаем номер блока из списка свободных */
 bno = super -> s_free[ --super -> s_nfree ];
 if( bno == (daddr_t) 0 )
  goto nospace;

 if( super -> s_nfree <= 0 ){
    /* Продолжение списка - не в суперблоке,
     * а в специальном дополнительном блоке файловой системы.
     */
  printf( "Indirect block %ld\n", bno );
  lseek( fd, (long) BSIZE * bno , 0 );
  read ( fd, blk,   BSIZE );

  super -> s_nfree = ((struct fblk *)blk) -> df_nfree ;
  memcpy( (char *) (super -> s_free),
   (char *) (((struct fblk *) blk) -> df_free ),
   sizeof( super->s_free));
 }
 if( super -> s_nfree <= 0 ||
     super -> s_nfree > NICFREE ){
  fprintf( stderr, "Bad free count %d\n", super->s_nfree );
  goto nospace;
 }
 if( super -> s_tfree )  /* кол-во свободных блоков */
     super -> s_tfree --;
 return bno;

nospace:
 super -> s_nfree = 0;
 super -> s_tfree = 0;
 return (-1L);   /* конец списка */
}

/* пересылка участка памяти длиной n байт */
memcpy( to, from, n )
 register char *to, *from;
 register n;
{
 while( n > 0 ){
  *to++ = *from++;
  n--;
 }
}

save( bno ) daddr_t bno;
{
 register i;
 char answer[ 20 ];

 printf( "block %ld-------------------\n", bno );
 lseek( fd, bno * BSIZE , 0 );
 read ( fd,  blk, BSIZE );
 for( i=0; i < BSIZE; i++ )
  putchar(isprint(blk[i]) || isspace(blk[i]) ? blk[i] : '.' );
 printf( "\n\7===> save block %ld ? ", bno );
 fflush( stdout );
 gets( answer );
 if( *answer == 'y' || *answer == 'Y' ){
  sprintf( answer, "#%012ld", n );
  fdout = creat( answer, 0644 );
  if( fdout < 0 ){
   fprintf( stderr, "Can't create %s\n", answer );
   exit(3);
  }
  write( fdout, blk, BSIZE );
  close( fdout );
 }
 n++;
}
.
  /*      Пример 30          */
/* /bin/cc -M2 -Ml -DMATCHONLY -LARGE dosfs.c match.c -o dosfs
 * Копирование файлов с дискеты, записанной в MS DOS, в UNIX.
 * Предполагается, что ваша UNIX-машина имеет соответствующий драйвер
 * для чтения дискет, сформатированных на IBM PC.
 * match.c - файл, содержащий текст функции match().
 */
#include <stdio.h>
#include <fcntl.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>

extern char *malloc();  /* выделитель памяти */
extern char *strrchr(); /* поиск последнего вхождения буквы */
extern long lseek();
void readBoot(), readFAT(), readRootDir(), main(), line(), getFile(),
     doDirectory(), mkname(), enterDir(), countFree(), traceclu();

int fd;         /* дескриптор файла - дисковода */

FILE *mapfp;     /* файл трассировки  */
int trace = 0;   /* трассировка пока выключена */
int ask = 1;     /* спрашивать ли подтверждение на перезапись файлов */
int dironly = 0; /* 1: только показывать имена, файлы не скидывать   */

typedef unsigned char  uchar;
/*typedef unsigned short ushort; Есть в sys/types.h */

/* Формат сектора загрузки */
struct boot {
 char jmp[3];      /* команда jmp */
 char label[8];    /* название системы */
 char bfs[2];      /* размер boot-сектора */
 uchar sectorsPerCluster; /* число секторов в кластере */
 char fatoff[2];   /* смещение до начала FAT */
 uchar copies;     /* число копий FAT  */
 char dirsize[2];  /* число записей в корневом каталоге */
 char sectors[2];  /* размер дискеты в секторах */
 uchar desc;       /* описатель типа дискеты */
 char FATsize[2];  /* размер FAT в секторах */
 char sectorsPerTrack[2]; /* число секторов на трек */
 char sides[2];    /* число сторон (1, 2) */
 char hidden[2];   /* число спрятанных секторов */
} *boot;

#define SECTOR 512      /* Размер сектора в байтах   */
int CLU;        /* Размер кластера в байтах          */
int SPC;        /* Размер кластера в секторах        */
int SECT;       /* Число секторов на дискете         */
long capacity;  /* емкость дискеты в байтах          */
ushort MAXCLU;  /* максимальный номер кластера + 1   */

int NDIR;       /* Число слотов в корневом каталоге  */
int DIRSIZE;    /* Длина корневого каталога в байтах */
int ENTRperCLUSTER;  /* Количество слотов в одном кластере каталога */

int SPF;        /* Размер FAT в секторах             */
int FATSIZE;    /* Размер FAT в байтах               */
int FATSTART;   /* Смещение до FAT в байтах          */
int NFAT;       /* Количество копий FAT              */

uchar DESC;     /* Описатель типа дискеты            */
int DATACLU;    /* Начало области данных (номер физич. кластера) */
int bit16 = 0;  /* 1 если FAT использует 16-битные поля, а не 12 */

/* Преобразование char[] в integer */
#define INT(s)  ( * (short *)s)
#define LONG(s) ( * (long  *)s)

/* Формат одной записи каталога. */
struct dir{
 char name[8];   /* имя файла            */
 char ext[3];    /* расширение (суффикс) */
 uchar attrib;   /* атрибуты файла       */
 char unused[10];
 char creat_time[2];     /* время создания */
 char creat_date[2];     /* дата создания  */
 char firstCluster[2];   /* начальный кластер */
 char size[4];           /* размер в байтах */
};
#define isdir(attr)     (attr & 0x10)     /* Является ли каталогом ? */
#define islabel(attr)   (attr & 0x08)     /* Метка тома ?            */

#define eq(s1, s2)      (!strcmp(s1, s2)) /* сравнение строк на ==   */

struct dir *droot;  /* Содержимое корневого каталога */
char *FAT1;         /* File Allocation Table, копия 1 */
char *FAT2;         /*                        копия 2 */
char cwd[256] = "";     /* Текущий каталог в DOS. "" - корневой  */
char *root = "/tmp";    /* Каталог в UNIX, куда копируются файлы */

char *pattern = NULL;   /* шаблон базового имени */
char *dirpattern;       /* каталог (не шаблон)   */

char newname[256];      /* буфер дла генерации имен */
char cluster[4098];     /* буфер для чтения кластера */

/* Чтение n байт по адресу s */
Read(fd, s, n) char *s;
{
 int nn = read(fd, s, n);
 if(nn != n ){
  fprintf(stderr, "Ошибка чтения: %d вместо %d\n", nn, n);
  perror( "read" ); exit(1);
 }
 return nn;
}

/* Позиционирование головок */
long Lseek(fd, off, how) long off;
{
 long offf;
 if((offf = lseek(fd, off, how)) < 0){
  fprintf(stderr, "Ошибка lseek(%ld,%d)\n", off, how);
 }
 return offf;
}

/* Отведение памяти и ее зачистка */
char *Malloc(n) unsigned n;{
 char *ptr = malloc(n);
 register unsigned i;
 if( !ptr){
  fprintf(stderr, "Не могу malloc(%u)\n", n ); exit(2);
 }
 for(i=0; i < n ; i++ ) ptr[i] = 0;
 /* Можно было бы использовать ptr = calloc(1,n); эта функция
  * как раз отводит и очищает память */
 return ptr;
}

/* Нарисовать горизонтальную черту */
void line(c) char c;{
 register i;
 for(i=0; i < 78; i++) putchar(c);
 putchar('\n');
}

/* Обработка псевдо-имен устройств. Используются имена для XENIX */
char *drive(name) char *name;
{
 if( eq(name, "360")) return "/dev/fd048ds9";
 if( eq(name, "720")) return "/dev/fd096ds9";
 if( eq(name, "1.2")) return "/dev/fd096ds15";
 return name;
}

/* Создать каталог */
char command[512];      /* буфер дла формирования команд */
mkdir(name, mode) char *name;
{
   int retcode;    struct stat st;

   if( stat(name, &st) >= 0 &&
       (st.st_mode & S_IFMT) == S_IFDIR ) return 0; /* уже есть */
   sprintf(command, "mkdir \"%s\"", name );
   retcode = system(command); /* выполнить команду, записанную в command */
   chmod(name, mode & 0777);  /* установить коды доступа */
   return retcode;            /* 0 - успешно */
}

/* Открыть файл, создавая (если надо) недостаюшие каталоги */
FILE *fmdopen(name, mode)
 char *name, *mode;
{
 extern errno;   char *s;    FILE *fp;
 if( fp = fopen(name, mode)) return fp;  /* OK */
 /* иначе файл не смог создаться */
 /* if( errno != ENOENT ) return NULL; /* из-за недостатка прав */
 /* Пробуем создать все каталоги по пути к файлу */
 if((s = strrchr(name, '/' )) == NULL ) return NULL;
 *s = '\0'; md(name); *s = '/';
 return fopen(name, mode);
}

/* Рекурсивный mkdir */
md(path)        char *path;
{       struct stat st; char *s; int code;
 if( !*path) return 0;     /* корневой каталог "/" */
 if( stat(path, &st) >= 0 ){     /* существует */
     if((st.st_mode & S_IFMT) == S_IFDIR) return 0; /* OK */
     printf( "%s - не каталог\n", path ); return 1; /* FAIL */
 }
 if( s = strrchr(path, '/')){
     *s = '\0'; code = md(path); *s = '/';
     if( code ) return code;     /* Облом */
 }
 sprintf(command, "mkdir \"%s\"", path );
 return system(command); /* 0 если OK */
}

/* Сконструировать имя файла в стиле UNIX.
 * В MS DOS все буквы в именах - большие */
void mkname( res, n, e ) char *res, *n, *e;
{ /* res - результат, n - имя, e - суффикс */
 register i; char *start = res;

 if( n[0] == 0x05 ) n[0] = 0xE5;  /* подставной символ */
 for(i=0; i < 8 && n[i] && n[i] != ' ' ; i++)
  *res++ = n[i];
 if( e[0] != ' ')
  *res++ = '.';
 for(i=0; i < 3 && e[i] && e[i] != ' ' ; i++)
  *res++ = e[i];
 *res = '\0';

 while( *start ){
  if( isalpha(*start) && isupper(*start))
   *start = tolower(*start);
  start++;
 }
}
/* ------------------------------------------------------- */
/* Получить запись из FAT для кластера clu */
ushort numCluster(clu) ushort clu;
{       ushort n;

 if( clu >= MAXCLU )
   printf( "Слишком большой номер кластера %03X >= %03X\n",
        clu,    MAXCLU );
 if( bit16 ){    /* 16 бит на номер кластера */
  n = INT( &FAT1[ 2*clu ]);
  n &= 0xFFFF;
  return n;
 } /* иначе 12 бит на номер кластера */
 n = clu + clu/2 ;
 n = INT( &FAT1[n] );
 if( clu % 2 ){  /* нечетный */
  n >>= 4;
 }
 n &= 0xFFF;
 return n;
}

/* Узнать следующий кластер файла. 0 если последний */
ushort nextCluster(clu) ushort clu;
{
 clu = numCluster(clu);
 if( clu >= (bit16 ? 0xFFF8 : 0xFF8 ))
  return 0;       /* EOF */
 return clu;
}

/* Прочесть кластер и сохранить его в файле и буфере */
getCluster(clu, fp, size, buffer)
 ushort clu;     /* логический кластер (2..) */
 FILE *fp;       /* файл для спасения  */
 long size;      /* осталось дописать  */
 char *buffer;   /* буфер для кластера */
{
 long offset;
 int rd, howmuchtoread;

 if( size <= 0L ){
  printf( "CLUSTER %03X лишний\n", clu ); exit(3);
 }
 /* Вычислить смещение. Кластеры нумеруются начиная с #2 */
 offset = (clu - 2 + DATACLU) * (long) CLU;
 Lseek(fd, offset, 0);

 /* Сколько байт прочесть ? */
 howmuchtoread = (size > CLU) ? CLU : size;
 rd = Read(fd, buffer, howmuchtoread);
 if( fp != NULL )
     fwrite(buffer, 1, rd, fp);
 return ( rd < 0 ) ? 0 : rd;
}
/* ------------------------------------------------------------------
 *      dosfs -rPATH    файлы скидываются в каталог PATH, а не в /tmp
 *      dosfs ... "шаблон"    сбрасываются только файлы с подходящими
 *        именами, например:
 *              dosfs 1.2  "/*.c"        *.c из корня дискеты
 *              dosfs 1.2  "/dir1/*.c"   *.c из каталога /dir1
 *              dosfs 1.2  "*.c"         *.c из всех каталогов
 *      dosfs -d        только просмотр каталогов, без сброса файлов
 *      Пример: dosfs -qr. 360
 */
void main(argc, argv) char *argv[];
{
 if( argc < 2 ) goto usage;
 if( *argv[1] == '-' ){  /* разбор ключей */
     char *keys = &argv[1][1];
     while(*keys){
        switch(*keys){
        case 't':  /* включить трассировку */
     trace++;
     if((mapfp = fopen( ".Map", "w" )) == NULL )
         trace = 0;
     break;
        case 'q':  /* без запросов (quiet) */
     ask = 0; break;
        case 'r':  /* переназначить root */
     root = keys+1; goto breakwhile;
        case 'd':  /* dosfs -d == команда dir */
     dironly++; break;
        }
        keys++;
     }
 breakwhile:
     argc--; argv++;
 }
 if( argc < 2 ) goto usage;
 if( pattern = argv[2] ){   /* может быть NULL */
  char *s = strrchr(pattern, '/');
  if(s){  /*      PATH/PATTERN                */
      dirpattern  = pattern;       /* PATH    */
      *s = '\0';    pattern = s+1; /* PATTERN */
  }else{  /*      просто PATTERN              */
      dirpattern = NULL;
  }
 }
 setbuf(stdout, NULL);   /* отменить буферизацию */
 readBoot(drive(argv[1]));
 readFAT();
 countFree();
 readRootDir();
 exit(0);
usage:
 printf( "Вызов:  dosfs  [-dqtrDIR]  устройство [\"шаблон\"]\n" );
 exit(4);
}

/* Прочесть boot-sector, вычислить разные параметры дискеты */
void readBoot(dsk) char *dsk;
{
 char BOOT[SECTOR];
 int skips, sides;

 if((fd = open( dsk, O_RDONLY)) < 0 ){
  fprintf(stderr, "Не могу читать %s\n", dsk); exit(5);
 }
 /* нулевой сектор дискеты - boot */
 Read(fd, BOOT, SECTOR);
 boot = (struct boot *) BOOT;

 line('-');
 printf( "Сформатировано \"%8.8s\"\n", boot->label );
 printf( "Размер boot-сектора %d байт\n", INT(boot->bfs));
 printf( "Кластер содержит %d секторов\n",
  SPC = boot->sectorsPerCluster );
 printf( "Дискета содержит %d секторов ",
  SECT = INT(boot->sectors));
 capacity = SECT * (long) SECTOR;
 printf( "(%ld KB)\n", capacity / 1024L );
 printf( "На треке %d секторов\n", INT(boot->sectorsPerTrack));
 sides = INT(boot->sides);
 printf( "Диск имеет %d сторон%c\n\n", sides, sides==1? 'у':'ы');

 printf( "Смещение до FAT %d сектор\n",
  skips = INT(boot->fatoff));
 printf( "Имеется %d копии FAT\n", NFAT = boot->copies );
 printf( "FAT занимает %d секторов\n\n", SPF = INT(boot->FATsize));

 printf( "Корневой каталог содержит %d записей\n\n",
  NDIR = INT(boot->dirsize));

 printf( "Описатель дискеты = %02X\t(", DESC = boot->desc );
 switch( DESC ){
 case 0xFF: printf( "double sided, 8 sectors per track" ); break;
 case 0xFE: printf( "single sided, 8 sectors per track" ); break;
 case 0xFD: printf( "double sided, 9 sectors per track" ); break;
 case 0xFC: printf( "single sided, 9 sectors per track" ); break;
 case 0xF9: printf( "double sided, 15 sectors per track"); break;
 case 0xF8: printf( "Winchester" ); bit16++; break;
 default:   printf( "неизвестный тип" ); break;
 }
 printf( ")\n");
 printf( "На диске %d спрятанных секторов\n", INT(boot->hidden));

 /* Вычислить характеристики */
 CLU      = SECTOR * SPC;   /* размер кластера в байтах */
 FATSIZE  = SECTOR * SPF;   /* длина FAT в байтах       */
 FATSTART = SECTOR * skips; /* смещение в байтах до FAT */
 /* длина корневого каталога в байтах */
 DIRSIZE  = NDIR   * sizeof(struct dir);
 /* физический номер первого кластера данных */
 DATACLU  = ((long) FATSTART +
      (long) FATSIZE * NFAT +
      (long) DIRSIZE ) / CLU;
 printf( "Первый кластер данных (физ.) = %d\n", DATACLU );
 /* число записей каталога в кластере */
 ENTRperCLUSTER = CLU / sizeof(struct dir);

 /* число секторов для данных */
 MAXCLU = (SECT - DATACLU * SPC);
 /* число кластеров для данных */
 MAXCLU = MAXCLU / SPC;
 /* логические номера кластеров идут с #2 */
 MAXCLU += 2;
}

/* Прочесть File Allocation Table (таблицу размещения файлов) */
void readFAT(){
 register int i;

 FAT1 = Malloc(FATSIZE);

 Lseek(fd, (long) FATSTART, 0);
 Read(fd, FAT1, FATSIZE);
 if(NFAT > 1){
  FAT2 = Malloc(FATSIZE);
  Read(fd, FAT2, FATSIZE);

  /* Сравнить копии FAT */
  for(i=0; i < FATSIZE; i++ )
   if(FAT1[i] != FAT2[i]){
      printf( "копии FAT различаются в %d/%d\n",
        i, FATSIZE );
      break;
   }
  free( FAT2 );
 }
 if( DESC != FAT1[0] )
     printf( "У FAT другой описатель: %02X\n", FAT1[0] & 0xFF );
}

/* Прочесть корневой каталог дискеты.
 * Он расположен сразу же после копий FAT
 */
void readRootDir(){
 if( DIRSIZE % SECTOR )
  printf( "Размер каталога не кратен сектору\n" );
 Lseek(fd, (long)FATSTART + (long)FATSIZE * NFAT, 0);
 droot = (struct dir *) Malloc(DIRSIZE);
 Read(fd, droot, DIRSIZE );
 /* NDIR должно быть 112 для 360K и 720K
  *                  224 для 1.2 Mb
  */
 if( !dironly ) mkdir( root, 0755 );
 line('-');
 doDirectory(0, NDIR, droot);
}

/* Обработать каталог (напечатать, спасти файлы, обойти подкаталоги) */
#define PRINT  \
  for(j=0; j < level; j++ ) printf( "  " ); /* отступ */                \
  printf( "%02d\t%s/%-14s   %12ld   %s\n",                              \
    strt + i,                                                    \
   cwd,                                                   \
      basename,                                           \
       size,                                       \
        isdir(dd[i].attrib) ?    "<DIR>"  : \
        islabel(dd[i].attrib) ?  "<LAB>"  : "" )

void doDirectory(strt, entries, dd)
 struct dir dd[];
{
 register i, j;
 char basename[40];
 static int level = 0;
 int need_to_get;        /* надо ли сбрасывать */

 /* line('-'); */
 for(i=0; i < entries; i++ ){
    uchar c; long size;

    if((c = *dd[i].name) == 0xE5 || !c)
     continue;        /* файл стерт (дыра) */
    mkname(basename, dd[i].name, dd[i].ext);
    size = LONG(dd[i].size); /* размер файла */

    /* проверить шаблон имени, если нужно */
    if( !pattern          || /* pattern задан и */
        (   (!dirpattern  || eq(cwd, dirpattern)) &&
     match(basename, pattern)
        )
    ){  PRINT; need_to_get = !dironly; }
    else       need_to_get = 0;

    if(isdir(dd[i].attrib)){
        /* себя и родителя проигнорировать */
       if( eq(basename, "." ) || eq(basename, ".."))
     continue;
        level++; /* У каталогов почему-то size == 0 */
  enterDir( basename, INT(dd[i].firstCluster), need_to_get);
        level--;
    } else if( islabel(dd[i].attrib)){
        printf( "Volume label:%11.11s\n", dd[i].name );
    } else if( need_to_get )
        getFile ( basename, INT(dd[i].firstCluster), size);
 }
 /* line('#'); */
}

/* Прочесть файл в UNIX-ную файловую систему */
void getFile(name, clu, size)
 char *name;     /* имя файла */
 ushort clu;     /* начальный кластер */
 long size;      /* размер */
{
 FILE *fp;       /* файл куда сохранять */
 struct stat st;
 ushort nclu = 0;/* порядковый номер кластера */

 sprintf(newname, "%s%s/%s", root, cwd, name );

 if( ask && stat(newname, &st) >= 0 ){
  char answer[30];
  fprintf(stderr, "%s уже существует, перезаписать? ",
     newname);
  gets(answer);
  if( *answer != 'y' ) return;
  fprintf( stderr, "\tOK\n" );
 }
 if((fp = fmdopen( newname, "w" )) == NULL){
  printf( "Не могу создать %s\n", newname );
  return;
 }
 if( trace ) fprintf( mapfp, "\n%s/%s:", cwd, name );

 while( clu ){
  if( trace ) traceclu(nclu++, clu);
  size -= getCluster(clu, fp, size, cluster);
  clu = nextCluster(clu);
 }
 fclose(fp);
}

/* Обработать подкаталог */
void enterDir(name, clu, create)
 char *name;     /* имя */
 ushort clu;     /* начальный кластер */
{
 char *tail, *myCluster;
 struct dir *dsub;
 ushort nclu;
 int nentries;   /* число записей в каталоге */

 /* Коррекция cwd */
 tail = cwd + strlen(cwd);
 *tail = '/'; strcpy(tail+1, name);

 if( create ){   /* создать */
     sprintf( newname, "%s%s", root, cwd );
     mkdir  ( newname, 0755);
 }
 if( trace ) fprintf( mapfp, "\nDIR %s:", cwd);

 myCluster = Malloc( sizeof cluster );
 dsub = (struct dir *) myCluster;

 nentries = nclu = 0;
 while( clu ){
  if( trace ) traceclu(nclu++, clu);
  /* Прочесть очередной кластер каталога */
  getCluster(clu, NULL,(long) CLU, myCluster);
  /* Обработать имена в этом кластере */
  doDirectory(nentries, ENTRperCLUSTER, dsub);
  nentries += ENTRperCLUSTER;
  /* Взять следующий кластер */
  clu = nextCluster(clu);
 }
 *tail = '\0';   free(myCluster);
}

/* Подсчет свободных и плохих кластеров. */
void countFree(){
 int isFree = 0;       /* свободные кластеры */
 int isBad  = 0;       /* сбойные кластеры   */
 int isReserved = 0;   /* спрятанные кластеры */

 register ushort n = 0;
 register ushort clu;  /* текущий анализируемый кластер */
 int nline = 300;

 if( trace ) fprintf(mapfp, "\t\tFAT chart\n");
 for(clu=0; clu < MAXCLU; clu++){
  if( clu >= 2 ){
      n = numCluster(clu);
      if( n == 0 ) isFree++;
      if( n == (bit16 ? 0xFFF7 : 0xFF7)) isBad++;
      if( n >= (bit16 ? 0xFFF0 : 0xFF0 ) &&
   n <  (bit16 ? 0xFFF7 : 0xFF7 )) isReserved++;
  }
  if( trace ){
    if( nline >= 8){
   nline = 0; fprintf( mapfp, "\n%03X:\t", clu );
    } else  nline++;
    fprintf( mapfp, "%03X ", n );
  }
 }
 line('=');
 printf( "Свободно %ld, испорчено %ld, резерв %d кластеров\n",
        (long)isFree * CLU,  /* в байтах */
          (long)isBad * CLU,   isReserved );
}

void traceclu(nclu, clu) ushort nclu, clu;
{
 if( nclu % 16 == 0 )
     fprintf( mapfp, "\n\t" );
 fprintf( mapfp, "%03X ", clu );
}

#ifdef LOCAL_MALLOC
/*
Обратите внимание, что в этой программе память отводится malloc()
и освобождается free() по принципу стека (LIFO).
Мы могли бы переопределить стандартные функции malloc() и free(),
заставив их работать со статической памятью! (Если мы напишем
свою функцию с именем, как у стандартной, то будет использоваться
НАША функция).
*/
static char allocArena[32 * 1024];
static char *top = allocArena;
char *malloc(n){        char *ptr;
 /* округлить до целого числа слов */  /* деление с остатком */
 /* число int-ов: */  n = (n + (sizeof(int)-1)) / sizeof(int);
 /* число char-ов:*/  n *= sizeof(int);
 ptr = top; top += n; return ptr;
}
free(ptr) char *ptr; { top = ptr; }
#endif /*LOCAL_MALLOC*/

.
 /*      Пример 31      */
/* Интроспективная программа: печатает сама себя */

#include <stdio.h>
char *text[] = {
 "#include <stdio.h>",
 "char *text[] = {",
 "        NULL};",
 "/* Программа, печатающая свой собственный текст */",
 "main(){ int i;",
 "  puts(text[0]); puts(text[1]);",
 "  for(i=0; text[i]; i++) putq(text[i]);",
 "  for(i=2; text[i]; i++) puts(text[i]);",
 "}",
 "putq(s) char *s; {",
 "  printf(\"\\t\\\"\");",
 "  while(*s){",
 "    if(*s == '\"')       printf(\"\\\\\\\"\");",
 "    else if(*s == '\\\\') printf(\"\\\\\\\\\");",
 "    else putchar(*s);",
 "    s++;",
 "  }",
 "  printf(\"\\\",\\n\");",
 "}",
        NULL};
/* Программа, печатающая свой собственный текст */
main(){ int i;
  puts(text[0]); puts(text[1]);
  for(i=0; text[i]; i++) putq(text[i]);
  for(i=2; text[i]; i++) puts(text[i]);
}
putq(s) char *s; {
  printf("\t\"");
  while(*s){
    if(*s == '"')       printf("\\\"");
    else if(*s == '\\') printf("\\\\");
    else putchar(*s);
    s++;
  }
  printf("\",\n");
}
.
 /*      Пример 32     */
/* C beautify: программа cb.c, форматирующая исходный
 * текст программы на Си. Текст взят из дистрибутива UNIX */
#include <stdio.h>
#include <stdlib.h>

#define gets    getlex
#define puts    putlex

 /* прототипы */
void main(int argc, char *argv[]);
void ptabs( void );
int getch( void );
void puts( void );
int lookup( char *tab[] );
int gets( void );
void gotelse( void );
int getnl( void );
void comment( void );

int     slevel[10];
int     clevel  = 0;
int     spflg[20][10];
int     sind [20][10];
int     siflev[10];
int     sifflg[10];
int     iflev   = 0;
int     ifflg   = -1;
int     level   = 0;
int     ind[10] = { 0,0,0,0,0,0,0,0,0,0 };
int     eflg    = 0;
int     paren   = 0;
int     pflg[10] = { 0,0,0,0,0,0,0,0,0,0 };
char    lchar;
char    pchar;
int     aflg    = 0;
int     ct;
int     stabs[20][10];
int     qflg    = 0;
char    *wif[] = { "if",NULL};
char    *welse[] = { "else", NULL};
char    *wfor[] =  { "for" , NULL};
char    *wds[] =   { "case","default", NULL};
int     j       = 0;
char    string[200];
char    cc;
int     sflg    = 1;
int     peek    = -1;
int     tabs    = 0;
int     lastchar;
int     c;

void main(int argc, char *argv[])
{
 if( argc > 1 ){
  if( freopen( argv[1], "r", stdin ) == NULL ){
   fprintf(stderr, "Can't open %s\n", argv[1] );
   exit(1);
  }
 }
 if( argc > 2 ){
  if( freopen( argv[2], "w", stdout ) == NULL ){
   fprintf(stderr, "Can't create %s\n", argv[2] );
   exit(1);
  }
 }
 while((c = getch()) != EOF){
  switch(c){
  case ' ':
  case '\t':
   if(lookup(welse) == 1){
    gotelse();
    if(sflg == 0 || j > 0) string[j++] = c;
    puts();
    sflg = 0;
    if(getnl() == 1){
     puts();
     printf("\n");
     sflg = 1;
     pflg[level]++;
     tabs++;
    }
    continue;
   }
   if(sflg == 0 || j > 0) string[j++] = c;
   continue;
  case '\n':
   if((eflg = lookup(welse)) == 1) gotelse();
   puts();
   printf("\n");
   sflg = 1;
   if(eflg == 1){
    pflg[level]++;
    tabs++;
   }
   else
    if(pchar == lchar)
     aflg = 1;
   continue;
  case '{':
   if(lookup(welse) == 1) gotelse();
   siflev[clevel] = iflev;
   sifflg[clevel] = ifflg;
   iflev = ifflg = 0;
   clevel++;
   if(sflg == 1 && pflg[level] != 0){
    pflg[level]--;
    tabs--;
   }
   string[j++] = c;
   puts(); getnl(); puts(); printf("\n");
   tabs++;
   sflg = 1;
   if(pflg[level] > 0){
    ind[level] = 1;
    level++;
    slevel[level] = clevel;
   }
   continue;
  case '}':
   clevel--;
   if((iflev = siflev[clevel]-1) < 0) iflev = 0;
   ifflg = sifflg[clevel];
   if(pflg[level] >0 && ind[level] == 0){
    tabs -= pflg[level];
    pflg[level] = 0;
   }
   puts();
   tabs--;
   ptabs();
   if((peek = getch()) == ';'){
    printf("%c;", c);
    peek = -1;
   }
   else printf("%c", c);
   getnl(); puts(); printf("\n");
   sflg = 1;
   if(clevel < slevel[level])if(level > 0) level--;
   if(ind[level] != 0){
    tabs -= pflg[level];
    pflg[level] = 0;
    ind[level] = 0;
   }
   continue;
  case '"':
  case '\'':
   string[j++] = c;
   while((cc = getch()) != c){
    string[j++] = cc;
    if(cc == '\\'){
     string[j++] = getch();
    }
    if(cc == '\n'){
     puts();
     sflg = 1;
    }
   }
   string[j++] = cc;
   if(getnl() == 1){
    lchar = cc;
    peek = '\n';
   }
   continue;
  case ';':
   string[j++] = c;
   puts();
   if(pflg[level] > 0 && ind[level] == 0){
    tabs -= pflg[level];
    pflg[level] = 0;
   }
   getnl(); puts(); printf("\n");
   sflg = 1;
   if(iflev > 0)
    if(ifflg == 1){
     iflev--; ifflg = 0;
    }
    else iflev = 0;
   continue;
  case '\\':
   string[j++] = c;
   string[j++] = getch();
   continue;
  case '?':
   qflg = 1;
   string[j++] = c;
   continue;
  case ':':
   string[j++] = c;
   if(qflg == 1){
    qflg = 0;
    continue;
   }
   if(lookup(wds) == 0){
    sflg = 0;
    puts();
   }
   else{
    tabs--; puts(); tabs++;
   }
   if((peek = getch()) == ';'){
    printf(";");
    peek = -1;
   }
   getnl(); puts(); printf("\n");
   sflg = 1;
   continue;
  case '/':
   string[j++] = c;
   if((peek = getch()) != '*') continue;
   string[j++] = peek;
   peek = -1;
   comment();
   continue;
  case ')':
   paren--;
   string[j++] = c;
   puts();
   if(getnl() == 1){
    peek = '\n';
    if(paren != 0) aflg = 1;
    else if(tabs > 0){
     pflg[level]++;
     tabs++;
     ind[level] = 0;
    }
   }
   continue;
  case '#':
   string[j++] = c;
   while((cc = getch()) != '\n') string[j++] = cc;
   string[j++] = cc;
   sflg = 0;
   puts();
   sflg = 1;
   continue;
  case '(':
   string[j++] = c;
   paren++;
   if(lookup(wfor) == 1){
    while((c = gets()) != ';');
    ct=0;
cont:
    while((c = gets()) != ')'){
     if(c == '(') ct++;
    }
    if(ct != 0){
     ct--; goto cont;
    }
    paren--;
    puts();
    if(getnl() == 1){
     peek = '\n';
     pflg[level]++;
     tabs++;
     ind[level] = 0;
    }
    continue;
   }
   if(lookup(wif) == 1){
    puts();
    stabs[clevel][iflev] = tabs;
    spflg[clevel][iflev] = pflg[level];
    sind[clevel][iflev]  = ind[level];
    iflev++;
    ifflg = 1;
   }
   continue;
  default:
   string[j++] = c;
   if(c != ',') lchar = c;
  }
 }
}

void ptabs( void ){
 int i;
 for(i=0; i < tabs; i++) printf("\t");
}

int getch( void ){
 if(peek < 0 && lastchar != ' ' && lastchar != '\t')
    pchar = lastchar;
 lastchar = (peek<0) ? getc(stdin) : peek;
 peek = -1;
 return(lastchar);
}

void puts( void ){
 if(j > 0){
  if(sflg != 0){
   ptabs();
   sflg = 0;
   if(aflg == 1){
    aflg = 0;
    if(tabs > 0) printf("    ");
   }
  }
  string[j] = '\0';
  printf("%s",string);
  j = 0;
 }
 else{
  if(sflg != 0){
   sflg = 0; aflg = 0;
  }
 }
}

int lookup( char *tab[] )
{
 char r;
 int l,kk,k,i;
 if(j < 1) return(0);
 kk=0;
 while(string[kk] == ' ') kk++;
 for(i=0; tab[i] != 0; i++){
  l=0;
  for(k=kk;(r = tab[i][l++]) == string[k] && r != '\0';k++);
  if(r == '\0' &&
     (string[k] < 'a' || string[k] > 'z' || k >= j))
        return(1);
 }
 return(0);
}

int gets( void ){
 char ch;
beg:
 if((ch = string[j++] = getch()) == '\\'){
  string[j++] = getch();
  goto beg;
 }
 if(ch == '\'' || ch == '"'){
  while((cc = string[j++] = getch()) != ch)
       if(cc == '\\') string[j++] = getch();
  goto beg;
 }
 if(ch == '\n'){
  puts();
  aflg = 1;
  goto beg;
 }
 else return(ch);
}

void gotelse( void ){
 tabs = stabs[clevel][iflev];
 pflg[level] = spflg[clevel][iflev];
 ind[level]  = sind [clevel][iflev];
 ifflg = 1;
}

int getnl( void ){
 while((peek = getch()) == '\t' || peek == ' '){
  string[j++] = peek;
  peek = -1;
 }
 if((peek = getch()) == '/'){
  peek = -1;
  if((peek = getch()) == '*'){
   string[j++] = '/';
   string[j++] = '*';
   peek = -1;
   comment();
  }
  else string[j++] = '/';
 }
 if((peek = getch()) == '\n'){
  peek = -1;
  return(1);
 }
 return(0);
}

void comment( void ){
rep:
 while((c = string[j++] = getch()) != '*')
  if(c == '\n'){
   puts();
   sflg = 1;
  }
gotstar:
 if((c = string[j++] = getch()) != '/'){
  if(c == '*') goto gotstar;
  goto rep;
 }
}


With any suggestions or questions please feel free to contact us