HTML page

	/*      Пример 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;
	}
}