&& u_ttyp == NULL // еще нет упр.терм.
&& t_pgrp == 0 ){ // у терминала нет группы
u_ttyp = &t_pgrp;
t_pgrp = p_pgrp;
}

Таким процессом обычно является процесс регистрации пользователя в системе (который
спрашивает у вас имя и пароль). При закрытии терминала всеми процессами (что бывает
при выходе пользователя из системы) терминал теряет группу: t_pgrp=0;
При нажатии на клавиатуре терминала некоторых клавиш:

c_cc[ VINTR ] обычно DEL или CTRL/C
c_cc[ VQUIT ] обычно CTRL/\

драйвер терминала посылает соответственно сигналы SIGINT и SIGQUIT всем процессам
группы терминала, т.е. как бы делает
kill( -t_pgrp, sig );
Именно поэтому мы можем прервать процесс нажатием клавиши DEL. Поэтому, если процесс
сделал setpgrp(), то сигнал с клавиатуры ему послать невозможно (т.к. он имеет свой
уникальный номер группы != группе терминала).
Если процесс еще не имеет управляющего терминала (или уже его не имеет после
setpgrp), то он может сделать любой терминал (который он имеет право открыть) управ-
ляющим для себя. Первый же файл-устройство, являющийся интерфейсом драйвера термина-
лов, который будет открыт этим процессом, станет для него управляющим терминалом. Так
процесс может иметь каналы 0, 1, 2 связанные с одним терминалом, а прерывания полу-
чать с клавиатуры другого (который он сделал управляющим для себя).
Процесс регистрации пользователя в системе - /etc/getty (название происходит от
"get tty" - получить терминал) - запускается процессом номер 1 - /etc/init-ом - на
каждом из терминалов, зарегистрированных в системе, когда
- система только что была запущена;
- либо когда пользователь на каком-то терминале вышел из системы (интерпретатор
команд завершился).
В сильном упрощении getty может быть описан так:

void main(ac, av) char *av[];
{ int f; struct termio tmodes;

for(f=0; f < NOFILE; f++) close(f);

/* Отказ от управляющего терминала,
* основание новой группы процессов.
*/
setpgrp();

/* Первоначальное явное открытие терминала */



А. Богатырев, 1992-95 - 230 - Си в UNIX

/* При этом терминал av[1] станет упр. терминалом */
open( av[1], O_RDONLY ); /* fd = 0 */
open( av[1], O_RDWR ); /* fd = 1 */
f = open( av[1], O_RDWR ); /* fd = 2 */

// ... Считывание параметров терминала из файла
// /etc/gettydefs. Тип требуемых параметров линии
// задается меткой, указываемой в av[2].
// Заполнение структуры tmodes требуемыми
// значениями ... и установка мод терминала.
ioctl (f, TCSETA, &tmodes);

// ... запрос имени и пароля ...

chdir (домашний_каталог_пользователя);

execl ("/bin/csh", "-csh", NULL);
/* Запуск интерпретатора команд. Группа процессов,
* управл. терминал, дескрипторы 0,1,2 наследуются.
*/
}

Здесь последовательные вызовы open занимают последовательные ячейки в таблице откры-
тых процессом файлов (поиск каждой новой незанятой ячейки производится с начала таб-
лицы) - в итоге по дескрипторам 0,1,2 открывается файл-терминал. После этого деск-
рипторы 0,1,2 наследуются всеми потомками интерпретатора команд. Процесс init запус-
кает по одному процессу getty на каждый терминал, как бы делая

/etc/getty /dev/tty01 m &
/etc/getty /dev/tty02 m &
...

и ожидает окончания любого из них. После входа пользователя в систему на каком-то
терминале, соответствующий getty превращается в интерпретатор команд (pid процесса
сохраняется). Как только кто-то из них умрет - init перезапустит getty на соответст-
вующем терминале (все они - его сыновья, поэтому он знает - на каком именно терми-
нале).

    6.6. Трубы и FIFO-файлы.


Процессы могут обмениваться между собой информацией через файлы. Существуют
файлы с необычным поведением - так называемые FIFO-файлы (first in, first out), веду-
щие себя подобно очереди. У них указатели чтения и записи разделены. Работа с таким
файлом напоминает проталкивание шаров через трубу - с одного конца мы вталкиваем дан-
ные, с другого конца - вынимаем их. Операция чтения из пустой "трубы" проиостановит
вызов read (и издавший его процесс) до тех пор, пока кто-нибудь не запишет в FIFO-
файл какие-нибудь данные. Операция позиционирования указателя - lseek() - неприме-
нима к FIFO-файлам. FIFO-файл создается системным вызовом

#include <sys/types.h>
#include <sys/stat.h>
mknod( имяФайла, S_IFIFO | 0666, 0 );

где 0666 - коды доступа к файлу. При помощи FIFO-файла могут общаться даже неродст-
венные процессы.
Разновидностью FIFO-файла является безымянный FIFO-файл, предназначенный для
обмена информацией между процессом-отцом и процессом-сыном. Такой файл - канал связи
как раз и называется термином "труба" или pipe. Он создается вызовом pipe:

int conn[2]; pipe(conn);

Если бы файл-труба имел имя PIPEFILE, то вызов pipe можно было бы описать как



А. Богатырев, 1992-95 - 231 - Си в UNIX

mknod("PIPEFILE", S_IFIFO | 0600, 0);
conn[0] = open("PIPEFILE", O_RDONLY);
conn[1] = open("PIPEFILE", O_WRONLY);
unlink("PIPEFILE");

При вызове fork каждому из двух процессов достанется в наследство пара дескрипторов:

pipe(conn);
fork();

conn[0]----<---- ----<-----conn[1]
FIFO
conn[1]---->---- ---->-----conn[0]
процесс A процесс B

Пусть процесс A будет посылать информацию в процесс B. Тогда процесс A сделает:

close(conn[0]);
// т.к. не собирается ничего читать
write(conn[1], ... );

а процесс B

close(conn[1]);
// т.к. не собирается ничего писать
read (conn[0], ... );

Получаем в итоге:

conn[1]---->----FIFO---->-----conn[0]
процесс A процесс B

Обычно поступают еще более элегантно, перенаправляя стандартный вывод A в канал
conn[1]

dup2 (conn[1], 1); close(conn[1]);
write(1, ... ); /* или printf */

а стандартный ввод B - из канала conn[0]

dup2(conn[0], 0); close(conn[0]);
read(0, ... ); /* или gets */

Это соответствует конструкции

$ A | B

записанной на языке СиШелл.
Файл, выделяемый под pipe, имеет ограниченный размер (и поэтому обычно целиком
оседает в буферах в памяти машины). Как только он заполнен целиком - процесс, пишу-
щий в трубу вызовом write, приостанавливается до появления свободного места в трубе.
Это может привести к возникновению тупиковой ситуации, если писать программу неакку-
ратно. Пусть процесс A является сыном процесса B, и пусть процесс B издает вызов
wait, не закрыв канал conn[0]. Процесс же A очень много пишет в трубу conn[1]. Мы
получаем ситуацию, когда оба процесса спят:
A потому что труба переполнена, а процесс B ничего из нее не читает, так как ждет
окончания A;
B потому что процесс-сын A не окончился, а он не может окончиться пока не допишет
свое сообщение.
Решением служит запрет процессу B делать вызов wait до тех пор, пока он не прочитает
ВСЮ информацию из трубы (не получит EOF). Только сделав после этого close(conn[0]);



А. Богатырев, 1992-95 - 232 - Си в UNIX

процесс B имеет право сделать wait.
Если процесс B закроет свою сторону трубы close(conn[0]) прежде, чем процесс A
закончит запись в нее, то при вызове write в процессе A, система пришлет процессу A
сигнал SIGPIPE - "запись в канал, из которого никто не читает".

6.6.1. Открытие FIFO файла приведет к блокированию процесса ("засыпанию"), если в
буфере FIFO файла пусто. Процесс заснет внутри вызова open до тех пор, пока в буфере
что-нибудь не появится.
Чтобы избежать такой ситуации, а, например, сделать что-нибудь иное полезное в
это время, нам надо было бы опросить файл на предмет того - можно ли его открыть?
Это делается при помощи флага O_NDELAY у вызова open.

int fd = open(filename, O_RDONLY|O_NDELAY);

Если open ведет к блокировке процесса внутри вызова, вместо этого будет возвращено
значение (-1). Если же файл может быть немедленно открыт - возвращается нормальный
дескриптор со значением >=0, и файл открыт.
O_NDELAY является зависимым от семантики того файла, который мы открываем. К
примеру, можно использовать его с файлами устройств, например именами, ведущими к
последовательным портам. Эти файлы устройств (порты) обладают тем свойством, что
одновременно их может открыть только один процесс (так устроена реализация функции
open внутри драйвера этих устройств). Поэтому, если один процесс уже работает с пор-
том, а в это время второй пытается его же открыть, второй "заснет" внутри open, и
будет дожидаться освобождения порта close первым процессом. Чтобы не ждать - следует
открывать порт с флагом O_NDELAY.

#include <stdio.h>
#include <fcntl.h>

/* Убрать больше не нужный O_NDELAY */
void nondelay(int fd){
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~O_NDELAY);
}
int main(int ac, char *av[]){
int fd;
char *port = ac > 1 ? "/dev/term/a" : "/dev/cua/a";


retry: if((fd = open(port, O_RDWR|O_NDELAY)) < 0){
perror(port);
sleep(10);
goto retry;
}
printf("Порт %s открыт.\n", port);
nondelay(fd);

printf("Работа с портом, вызови эту программу еще раз!\n");
sleep(60);
printf("Все.\n");
return 0;
}

Вот протокол:











А. Богатырев, 1992-95 - 233 - Си в UNIX

su# a.out & a.out xxx
[1] 22202
Порт /dev/term/a открыт.
Работа с портом, вызови эту программу еще раз!
/dev/cua/a: Device busy
/dev/cua/a: Device busy
/dev/cua/a: Device busy
/dev/cua/a: Device busy
/dev/cua/a: Device busy
/dev/cua/a: Device busy
Все.
Порт /dev/cua/a открыт.
Работа с портом, вызови эту программу еще раз!
su#


    6.7. Нелокальный переход.


Теперь поговорим про нелокальный переход. Стандартная функция setjmp позволяет
установить в программе "контрольную точку"|-, а функция longjmp осуществляет прыжок в
эту точку, выполняя за один раз выход сразу из нескольких вызванных функций (если
надо)|=. Эти функции не являются системными вызовами, но поскольку они реализуются
машинно-зависимым образом, а используются чаще всего как реакция на некоторый сигнал,
речь о них идет в этом разделе. Вот как, например, выглядит рестарт программы по
прерыванию с клавиатуры:

#include <signal.h>
#include <setjmp.h>
jmp_buf jmp; /* контрольная точка */

/* прыгнуть в контрольную точку */
void onintr(nsig){ longjmp(jmp, nsig); }

main(){
int n;
n = setjmp(jmp); /* установить контрольную точку */
if( n ) printf( "Рестарт после сигнала %d\n", n);
signal (SIGINT, onintr); /* реакция на сигнал */
printf("Начали\n");
...
}

setjmp возвращает 0 при запоминании контрольной точки. При прыжке в контрольную
точку при помощи longjmp, мы оказываемся снова в функции setjmp, и эта функция возв-
ращает нам значение второго аргумента longjmp, в этом примере - nsig.
Прыжок в контрольную точку очень удобно использовать в алгоритмах перебора с
возвратом (backtracking): либо - если ответ найден - прыжок на печать ответа, либо -
если ветвь перебора зашла в тупик - прыжок в точку ветвления и выбор другой альтерна-
тивы. При этом можно делать прыжки и в рекурсивных вызовах одной и той же функции: с
более высокого уровня рекурсии в вызов более низкого уровня (в этом случае jmp_buf
лучше делать автоматической переменной - своей для каждого уровня вызова функции).


____________________
|- В некотором буфере запоминается текущее состояние процесса: положение вершины
стека вызовов функций (stack pointer); состояние всех регистров процессора, включая
регистр адреса текущей машинной команды (instruction pointer).
|= Это достигается восстановлением состояния процесса из буфера. Изменения, проис-
шедшие за время между setjmp и longjmp в статических данных не отменяются (т.к. они
не сохранялись).





А. Богатырев, 1992-95 - 234 - Си в UNIX

6.7.1. Перепишите следующий алгоритм при помощи longjmp.

#define FOUND 1 /* ответ найден */
#define NOTFOUND 0 /* ответ не найден */
int value; /* результат */
main(){ int i;
for(i=2; i < 10; i++){
printf( "пробуем i=%d\n", i);
if( test1(i) == FOUND ){
printf("ответ %d\n", value); break;
}
}
}
test1(i){ int j;
for(j=1; j < 10 ; j++ ){
printf( "пробуем j=%d\n", j);
if( test2(i,j) == FOUND ) return FOUND;
/* "сквозной" return */
}
return NOTFOUND;
}
test2(i, j){
printf( "пробуем(%d,%d)\n", i, j);
if( i * j == 21 ){
printf( " Годятся (%d,%d)\n", i,j);
value = j; return FOUND;
}
return NOTFOUND;
}

Вот ответ, использующий нелокальный переход вместо цепочки return-ов:

#include <setjmp.h>
jmp_buf jmp;
main(){ int i;
if( i = setjmp(jmp)) /* после прыжка */
printf("Ответ %d\n", --i);
else /* установка точки */
for(i=2; i < 10; i++)
printf( "пробуем i=%d\n", i), test1(i);
}
test1(i){ int j;
for(j=1; j < 10 ; j++ )
printf( "пробуем j=%d\n", j), test2(i,j);
}
test2(i, j){
printf( "пробуем(%d,%d)\n", i, j);
if( i * j == 21 ){
printf( " Годятся (%d,%d)\n", i,j);
longjmp(jmp, j + 1);
}
}

Обратите внимание, что при возврате ответа через второй аргумент longjmp мы прибавили
1, а при печати ответа мы эту единицу отняли. Это сделано на случай ответа j==0,
чтобы функция setjmp не вернула бы в этом случае значение 0 (признак установки конт-
рольной точки).

6.7.2. В чем ошибка?

#include <setjmp.h>



А. Богатырев, 1992-95 - 235 - Си в UNIX

jmp_buf jmp;
main(){
g();
longjmp(jmp,1);
}
g(){ printf("Вызвана g\n");
f();
printf("Выхожу из g\n");
}
f(){
static n;
printf( "Вызвана f\n");
setjmp(jmp);
printf( "Выхожу из f %d-ый раз\n", ++n);
}

Ответ: longjmp делает прыжок в функцию f(), из которой уже произошел возврат управле-
ния. При переходе в тело функции в обход ее заголовка не выполняются машинные команды
"пролога" функции - функция остается "неактивированной". При возврате из вызванной
таким "нелегальным" путем функции возникает ошибка, и программа падает. Мораль: в
функцию, которая НИКЕМ НЕ ВЫЗВАНА, нельзя передавать управление. Обратный прыжок -
из f() в main() - был бы законен, поскольку функция main() является активной, когда
управление находится в теле функции f(). Т.е. можно "прыгать" из вызванной функции в
вызывающую: из f() в main() или в g(); и из g() в main();

-- --
| f | стек прыгать
| g | вызовов сверху вниз
| main | функций можно - это соответствует
---------- выкидыванию нескольких
верхних слоев стека

но нельзя наоборот: из main() в g() или f(); а также из g() в f(). Можно также
совершать прыжок в пределах одной и той же функции:

f(){ ...
A: setjmp(jmp);
...
longjmp(jmp, ...); ...
/* это как бы goto A; */
}


    6.8. Хозяин файла, процесса, и проверка привелегий.


UNIX - многопользовательская система. Это значит, что одновременно на разных
терминалах, подключенных к машине, могут работать разные пользователи (а может и один
на нескольких терминалах). На каждом терминале работает свой интерпретатор команд,
являющийся потомком процесса /etc/init.

6.8.1. Теперь - про функции, позволяющие узнать некоторые данные про любого пользо-
вателя системы. Каждый пользователь в UNIX имеет уникальный номер: идентификатор
пользователя (user id), а также уникальное имя: регистрационное имя, которое он наби-
рает для входа в систему. Вся информация о пользователях хранится в файле
/etc/passwd. Существуют функции, позволяющие по номеру пользователя узнать регистра-
ционное имя и наоборот, а заодно получить еще некоторую информацию из passwd:









А. Богатырев, 1992-95 - 236 - Си в UNIX

#include <stdio.h>
#include <pwd.h>
struct passwd *p;
int uid; /* номер */
char *uname; /* рег. имя */

uid = getuid();
p = getpwuid( uid );
...
p = getpwnam( uname );

Эти функции возвращают указатели на статические структуры, скрытые внутри этих функ-
ций. Структуры эти имеют поля:

p->pw_uid идентиф. пользователя (int uid);
p->pw_gid идентиф. группы пользователя;

и ряд полей типа char[]
p->pw_name регистрационное имя пользователя (uname);
p->pw_dir полное имя домашнего каталога
(каталога, становящегося текущим при входе в систему);
p->pw_shell интерпретатор команд
(если "", то имеется в виду /bin/sh);
p->pw_comment произвольная учетная информация (не используется);
p->pw_gecos произвольная учетная информация (обычно ФИО);
p->pw_passwd зашифрованный пароль для входа в
систему. Истинный пароль нигде не хранится вовсе!

Функции возвращают значение p==NULL, если указанный пользователь не существует (нап-
ример, если задан неверный uid). uid хозяина данного процесса можно узнать вызовом
getuid, а uid владельца файла - из поля st_uid структуры, заполняемой системным вызо-
вом stat (а идентификатор группы владельца - из поля st_gid). Задание: модифицируйте
наш аналог программы ls, чтобы он выдавал в текстовом виде имя владельца каждого
файла в каталоге.

6.8.2. Владелец файла может изменить своему файлу идентификаторы владельца и группы
вызовом

chown(char *имяФайла, int uid, int gid);

т.е. "подарить" файл другому пользователю. Забрать чужой файл себе невозможно. При
этой операции биты S_ISUID и S_ISGID в кодах доступа к файлу (см. ниже) сбрасываются,
поэтому создать "Троянского коня" и, сделав его хозяином суперпользователя, получить
неограниченные привелегии - не удастся!

6.8.3. Каждый файл имеет своего владельца (поле di_uid в I-узле на диске или поле
i_uid в копии I-узла в памяти ядра|-). Каждый процесс также имеет своего владельца
(поля u_uid и u_ruid в u-area). Как мы видим, процесс имеет два параметра, обознача-
ющие владельца. Поле ruid называется "реальным идентификатором" пользователя, а uid -
"эффективным идентификатором". При вызове exec() заменяется программа, выполняемая
данным процессом:

____________________
|- При открытии файла и вообще при любой операции с файлом, в таблицах ядра заво-
дится копия I-узла (для ускорения доступа, чтобы постоянно не обращаться к диску).
Если I-узел в памяти будет изменен, то при закрытии файла (а также периодически через
некоторые промежутки времени) эта копия будет записана обратно на диск. Структура
I-узла в памяти - struct inode - описана в файле <sys/inode.h>, а на диске - struct
dinode - в файле <sys/ino.h>.





А. Богатырев, 1992-95 - 237 - Си в UNIX

старая программа exec новая программа
ruid -->----------------->---> ruid
uid -->--------*-------->---> uid (new)
|
выполняемый файл
i_uid (st_uid)

Как видно из этой схемы, реальный идентификатор хозяина процесса наследуется. Эффек-
тивный идентификатор обычно также наследуется, за исключением одного случая: если в
кодах доступа файла (i_mode) выставлен бит S_ISUID (set-uid bit), то значение поля
u_uid в новом процессе станет равно значению i_uid файла с программой:

/* ... во время exec ... */
p_suid = u_uid; /* спасти */
if( i_mode & S_ISUID ) u_uid = i_uid;
if( i_mode & S_ISGID ) u_gid = i_gid;

т.е. эффективным владельцем процесса станет владелец файла. Здесь gid - это иденти-
фикаторы группы владельца (которые тоже есть и у файла и у процесса, причем у про-
цесса - реальный и эффективный).
Зачем все это надо? Во-первых затем, что ПРАВА процесса на доступ к какому-либо
файлу проверяются именно для эффективного владельца процесса. Т.е. например, если
файл имеет коды доступа

mode = i_mode & 0777;
/* rwx rwx rwx */

и владельца i_uid, то процесс, пытающийся открыть этот файл, будет "проэкзаменован" в
таком порядке:

if( u_uid == 0 ) /* super user */
то доступ разрешен;
else if( u_uid == i_uid )
проверить коды (mode & 0700);
else if( u_gid == i_gid )
проверить коды (mode & 0070);
else проверить коды (mode & 0007);

Процесс может узнать свои параметры:

unsigned short uid = geteuid(); /* u_uid */
unsigned short ruid = getuid(); /* u_ruid */
unsigned short gid = getegid(); /* u_gid */
unsigned short rgid = getuid(); /* u_rgid */

а также установить их:

setuid(newuid); setgid(newgid);

Рассмотрим вызов setuid. Он работает так (u_uid - относится к процессу, издавшему
этот вызов):

if( u_uid == 0 /* superuser */ )
u_uid = u_ruid = p_suid = newuid;
else if( u_ruid == newuid || p_suid == newuid )
u_uid = newuid;
else неудача;

Поле p_suid позволяет set-uid-ной программе восстановить эффективного владельца,
который был у нее до exec-а.




А. Богатырев, 1992-95 - 238 - Си в UNIX

Во-вторых, все это надо для следующего случая: пусть у меня есть некоторый файл
BASE с хранящимися в нем секретными сведениями. Я являюсь владельцем этого файла и
устанавливаю ему коды доступа 0600 (чтение и запись разрешены только мне). Тем не
менее, я хочу дать другим пользователям возможность работать с этим файлом, однако
контролируя их деятельность. Для этого я пишу программу, которая выполняет некоторые
действия с файлом BASE, при этом проверяя законность этих действий, т.е. позволяя
делать не все что попало, а лишь то, что я в ней предусмотрел, и под жестким контро-
лем. Владельцем файла PROG, в котором хранится эта программа, также являюсь я, и я
задаю этому файлу коды доступа 0711 (rwx--x--x) - всем можно выполнять эту программу.
Все ли я сделал, чтобы позволить другим пользоваться базой BASE через программу (и
только нее) PROG? Нет!
Если кто-то другой запустит программу PROG, то эффективный идентификатор про-
цесса будет равен идентификатору этого другого пользователя, и программа не сможет
открыть мой файл BASE. Чтобы все работало, процесс, выполняющий программу PROG, дол-
жен работать как бы от моего имени. Для этого я должен вызовом chmod либо командой
chmod u+s PROG
добавить к кодам доступа файла PROG бит S_ISUID.
После этого, при запуске программы PROG, она будет получать эффективный иденти-
фикатор, равный моему идентификатору, и таким образом сможет открыть и работать с
файлом BASE. Вызов getuid позволяет выяснить, кто вызвал мою программу (и занести
это в протокол, если надо).
Программы такого типа - не редкость в UNIX, если владельцем программы (файла ее
содержащего) является суперпользователь. В таком случае программа, имеющая бит дос-
тупа S_ISUID работает от имени суперпользователя и может выполнять некоторые дейст-
вия, запрещенные обычным пользователям. При этом программа внутри себя делает всячес-
кие проверки и периодически спрашивает пароли, то есть при работе защищает систему от
дураков и преднамеренных вредителей. Простейшим примером служит команда ps, которая
считывает таблицу процессов из памяти ядра и распечатывает ее. Доступ к физической
памяти машины производится через файл-псевдоустройство /dev/mem, а к памяти ядра -
/dev/kmem. Чтение и запись в них позволены только суперпользователю, поэтому прог-
раммы "общего пользования", обращающиеся к этим файлам, должны иметь бит set-uid.
Откуда же изначально берутся значения uid и ruid (а также gid и rgid) у про-
цесса? Они берутся из процесса регистрации пользователя в системе: /etc/getty. Этот
процесс запускается на каждом терминале как процесс, принадлежащий суперпользователю
(u_uid==0). Сначала он запрашивает имя и пароль пользователя:

#include <stdio.h> /* cc -lc_s */
#include <pwd.h>
#include <signal.h>
struct passwd *p;
char userName[80], *pass, *crpass;
extern char *getpass(), *crypt();
...
/* Не прерываться по сигналам с клавиатуры */
signal (SIGINT, SIG_IGN);
for(;;){
/* Запросить имя пользователя: */
printf("Login: "); gets(userName);
/* Запросить пароль (без эха): */
pass = getpass("Password: ");
/* Проверить имя: */
if(p = getpwnam(userName)){
/* есть такой пользователь */
crpass = (p->pw_passwd[0]) ? /* если есть пароль */
crypt(pass, p->pw_passwd) : pass;
if( !strcmp( crpass, p->pw_passwd))
break; /* верный пароль */
}
printf("Login incorrect.\a\n");
}
signal (SIGINT, SIG_DFL);



А. Богатырев, 1992-95 - 239 - Си в UNIX

Затем он выполняет:

// ... запись информации о входе пользователя в систему
// в файлы /etc/utmp (кто работает в системе сейчас)
// и /etc/wtmp (список всех входов в систему)
...
setuid( p->pw_uid ); setgid( p->pw_gid );
chdir ( p->pw_dir ); /* GO HOME! */
// эти параметры будут унаследованы
// интерпретатором команд.
...
// настройка некоторых переменных окружения envp:
// HOME = p->pw_dir
// SHELL = p->pw_shell
// PATH = нечто по умолчанию, вроде :/bin:/usr/bin
// LOGNAME (USER) = p->pw_name
// TERM = считывается из файла
// /etc/ttytype по имени устройства av[1]
// Делается это как-то подобно
// char *envp[MAXENV], buffer[512]; int envc = 0;
// ...
// sprintf(buffer, "HOME=%s", p->pw_dir);
// envp[envc++] = strdup(buffer);
// ...
// envp[envc] = NULL;
...
// настройка кодов доступа к терминалу. Имя устройства
// содержится в параметре av[1] функции main.
chown (av[1], p->pw_uid, p->pw_gid);
chmod (av[1], 0600 ); /* -rw------- */
// теперь доступ к данному терминалу имеют только
// вошедший в систему пользователь и суперпользователь.
// В случае смерти интерпретатора команд,
// которым заменится getty, процесс init сойдет
// с системного вызова ожидания wait() и выполнит
// chown ( этот_терминал, 2 /*bin*/, 15 /*terminal*/ );
// chmod ( этот_терминал, 0600 );
// и, если терминал числится в файле описания линий
// связи /etc/inittab как активный (метка respawn), то