УПРАВЛЕНИЕ ДЕЛОПРОИЗВОДСТВОМ
jargon | генератор технических терминов |
phone | база данных с телефонными номерами |
office | делопроизводитель |
УПРАВЛЕНИЕ ВРЕМЕНЕМ
at | выполнение задач в указанное время |
b | порожденный shell фоновых задач |
greet | своевременное приветствие с терминала |
lastlog | сообщение времени последней регистрации |
timelog | учет и статистика сеансов работы |
today | печать календаря с отмеченной текущей датой |
ИМЯ: at
at - выполнить команду или файл в указанное время
НАЗНАЧЕНИЕ
Переводит любую командную строку в фоновый режим и выполняет ее в заданное время.
ФОРМАТ ВЫЗОВА
at hr:min cmd [;cmd ...]
ПРИМЕР ВЫЗОВА
at 12:00 echo "time for lunch!"
В двенадцать часов дня выводит сообщение на экран терминала.
ТЕКСТ ПРОГРАММЫ at
1 : 2 # @(#) tree v1.0 Execute command line at specific time Author: Russ Sage 2а Выполнить командную строку в указанное время 4 if [ $# -lt 2 ] 5 then echo "at: wrong arg count" >&2 6 echo "usage: at hr:min cmd [;cmd ...]" >&2 7 exit 1 8 fi 10 ITS=$1; shift 12 while : 13 do 14 TIME=`date | cut -c12-16` 16 if [ "$ITS" = "$TIME" ] 17 then eval $@ 18 exit 0 19 else sleep 35 20 fi 21 done &
ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ
ITS | Время, в которое следует выполнить указанные команды |
TIME | Текущее время в системе |
Зачем нам нужен at?
На протяжении рабочего дня мы выполняем много небольших работ, которые нужно делать через различные интервалы времени. Некоторые вещи просто должны быть сделаны один раз в любое время, тогда как другие должны делаться в определенное время каждый день. Например, вам может понадобиться запускать процедуру копирования файлов каждую ночь, входить в другую систему раз в день и проверять почту или сообщения пользователей сети по заданной теме раз в несколько дней.
Командный файл at предоставляет механизм для выполнения задач, связанных со временем. Мы можем сказать системе, что и когда мы хотим сделать. Задача остается "спящей" в фоновом режиме до назначенного времени. Это дает нам возможность превратить компьютер в будильник, секретаря, администратора встреч и т.д.
Данная концепция не нова и уже существует в системе Berkeley UNIX под тем же именем. Она реализована также в последних версиях System V. Почему же тогда мы представляем здесь нашу собственную версию? Одна из причин в том, что многие из вас имеют более ранние версии UNIX, в которых это средство отсутствует. Но важнее, видимо, другое наша цель не в том, чтобы сделать существующие команды at устаревшими, а в показе того, как легко отслеживать время и реализовывать обработку, связанную со временем. Имея нашу собственную команду at, мы можем настроить ее по своему вкусу и изменить ее, когда необходимо. Команда at, представленная здесь, фактически более гибкая, чем at в системе Berkeley, хотя в первой отсутствуют некоторые особенности второй. Она более гибкая потому, что вы можете поместить настоящие команды в фоновую задачу at, в то время как для at в системе Berkeley вы должны использовать имя командного файла интерпретатора shell. Метод Berkeley запрещает вам вызывать исполняемые модули непосредственно в командной строке, а наша at - нет. (Конечно, вы можете с таким же успехом использовать командные файлы интерпретатора shell, если вам это необходимо.)
Что делает at?
Команда at дает нам возможность собирать несколько команд в одно целое и впоследствии запускать их. Когда они выполняются, их вывод может либо идти на экран, либо перенаправляться в определенный файл. Командная строка принимает два параметра: время выполнения и командную строку, которую следует выполнить. Время выражено в формате час:минута. Час должен быть указан строкой из двух цифр (в диапазоне от 0 до 23 часов), так как его использует команда date. Использование того же стандарта, что и в команде date, значительно упрощает команду at. В качестве второго параметра может быть любая команда, которую обычно можно ввести в командной строке интерпретатора shell. Можно также использовать конвейеры, составные команды и переназначения. Нет ограничений на то, какая команда может быть выполнена. Команда at может запустить обычный исполняемый модуль UNIX или ваш собственный командый файл.
Выход at по умолчанию направляется в стандартный вывод. Стандартным выводом в данном случае является экран терминала. Команда at в системе Berkeley не имеет вывода по умолчанию, что несколько затрудняет получение результата и отправку его на экран.
Команда at всегда запускается как фоновая задача. Нецелесообразно запускать ее в приоритетном режиме, где она блокирует терминал на все время своего выполнения. Пребывая в фоновом режиме, at освобождает ресурсы, но все же работает для вас. Между прочим, отметим, что когда процессы ставятся в фоновый режим изнутри командного файла, идентификатор процесса не печатается на экран, как это происходит, когда процессы запускаются в фоновом режиме с клавиатуры.
Порождение большого количества фоновых процессов может иметь отрицательный эффект для системы. Каждая фоновая задача - это цикл while интерпретатора shell, который работает очень медленно. Когда много фоновых процессов, мало времени центрального процессора остается на другие цели. В результате производительность системы ухудшается. В больших системах это, вероятно, не проблема, если только система не загружена множеством пользователей, но вы должны использовать это средство с осторожностью.
Отметим, что формат час:минута годится только для одного полного дня. Данная программа at задумана как ежедневная и не имеет средств запуска в определенный день или месяц, хотя вы можете легко расширить ее, как только поймете, как читается и используется информация о времени.
ПРИМЕРЫ
1. $ at 11:45 echo ^G^G It's almost lunch time
Без пятнадцати минут двенадцать дважды выдается звуковой сигнал (control-G) и выводится сообщение о ленче.
2. $ at 10:45 "if [ -s $MAIL ]; then echo ^G You have mail; fi"
Без пятнадцати одиннадцать проверяется, существует ли мой почтовый файл и есть ли в нем хотя бы один символ ($MAIL есть /usr/spool/mail/russ). Если это так, выдается звуковой сигнал и сообщение о том, что у меня есть почта.
3. $ at 17:00 "c; date; banner ' time to' ' go home'"
В пять часов вечера очищается экран (с помощью команды c, описанной далее в данной книге), печатается дата и выводится крупными буквами на весь экран сообщение "time to go home" ("пора домой"). С помощью апострофов в командной строке banner мы можем добиться вывода символа возврата каретки, чтобы разместить каждый набор слов в отдельной строке. Если какая-либо из этих команд не срабатывает (например, не найдена команда c), то и весь фоновый процесс оканчивается неудачей.
ПОЯСНЕНИЯ
Прежде всего at проверяет, правильно ли она была вызвана. Строки 4-8 делают проверку ошибок. В командной строке должны присутствовать по крайней мере два параметра: время и команда. Если это так, то счетчик позиционных параметров равен 2. Если этот счетчик меньше 2, произошла ошибка. В стандартный файл ошибок посылаются сообщения об ошибке с помощью переадресации в файловый дескриптор 2.
Переменная интерпретатора shell ITS инициализируется в строке 10. В ней устанавливается значение первого позиционного параметра ($1), которым является час:минута. Как только мы занесли это значение в переменную, оно больше не нужно нам в командной строке. Команда shift удаляет $1 из командной строки. Теперь командная строка состоит из вызывающей команды $0 (т.е. самой at) и остатка строки ($@ или $*). Вызывающая команда не вычисляется как часть остатка строки, поэтому вам не нужно заботиться об аргументе $0.
Далее at переходит к вечному циклу while в строках 12-21. Вечным этот цикл делает команда : (двоеточие). Это встроенная команда интерпретатора shell, которая ничего не делает кроме того, что всегда возвращает успешный статус выхода, заставляя тем самым цикл продолжаться. Команда true интерпретатора shell очень похожа и делает программу более наглядной. Мы же используем : вместо true, чтобы сократить издержки на порождение процесса для каждой итерации цикла. Команда : встроена в сам shell. True, напротив, является внешней командой в каталоге bin (так же, как ls), она должна быть найдена по файловому пути, выполниться и вернуть значение. Это занимает гораздо больше процессорного времени.
На каждой итерации цикла текущее время сверяется с назначенным временем, переданным из командной строки. Текущее время извлекается из команды date в строке 14. Обычно date выдает результат в таком формате:
| | Mon Mar 31 06:54:25 PST 1986 | |
Поскольку это строка фиксированного размера, мы можем посчитать номера позиций, в которых размещены час и минута. Данные час:минута находятся в позициях 12-16. Для получения этих символов мы запускаем команду date, пропускаем ее результат по конвейеру через cut и вырезаем нужные позиции. Весь результат присваивается переменной TIME. Заметим, что поле секунд не используется. Наименьшая единица времени в этой программе - минута.
Все волшебство данной команды заключено в строках 16-20. Если время, указанное в командной строке, равно текущему времени (строка 16), вычислить и выполнить остальные аргументы командной строки (строка 17), затем выйти с успешным нулевым значением (строка 18). Если время не совпало, немного поспать (строка 19) и повторить все сначала. Символ & в конце цикла в строке 21 превращает весь цикл while в фоновый процесс. Как мы можем убедиться, что shell выполняет все свои команды в фоновом режиме и никакие из них не выполняет в оперативном режиме? В действительности мы не можем этого сделать. Мы должны полагать, что shell так работает. Поскольку многое при программировании на shell делается исходя из опыта и интуиции, вам приходится испытывать многие вещи, чтобы увидеть, как они работают. Периодически shell преподносит сюрпризы и делает нечто совершенно неожиданное.
ИССЛЕДОВАНИЯ
Что бы случилось, если бы вы поставили задание at в фоновый режим, а затем вышли из системы? Ответ зависит от того, с каким shell вы работаете. Если у вас Bourne shell, то ввод команды control-D при выходе из системы прекращает выполнение всех ваших фоновых задач. Единственный способ оставить в живых фоновые задачи после выхода из системы - использовать команду nohup ("no hang up" - "не казнить").
Nohup обеспечивает, что все сигналы о прекращении процесса не достигают данного процесса. Не получая сигнал о прекращении выполнения, процесс думает, что вы все еще находитесь в системе. Синтаксис выглядит так:
nohup at 13:00 echo "back from lunch yet?"
Если вы запускаете Си-shell, все фоновые процессы продолжаются после вашего выхода из системы. Причина в том, что Си-shell переводит все свои фоновые задачи в состояние защиты от прекращения выполнения. Этот процесс автоматический, и его не нужно указывать явно.
Вы, возможно, удивлены тем, что в строке 17 использована команда "eval $@". Это сформировалось методом проб и ошибок. На начальных этапах разработки at команда "$@" использовалась сама по себе. При самостоятельном применении эта команда означает "выполнить все позиционные параметры". Поскольку это была единственная команда, выполнялась вся строка позиционных параметров, после чего возникали проблемы.
Использование переназначений и переменных интерпретатора shell сильно запутывало at.
Для иллюстрации рассмотрим пару примеров. Если мы запускаем at с командной строкой
at 09:30 echo $HOME
то все вроде бы работает. Сначала раскрывается переменная $HOME, затем echo печатает ее значение - /usr/russ. Но если мы запускаем командную строку
at 09:30 echo \$HOME
то переменная не раскрывается и echo фактически печатает $HOME вместо значения переменной $HOME. Мы избежали этого просто с помощью команды eval для повторного вычисления командной строки перед ее выполнением. Существо проблемы в том, что вызывающий интерпретатор shell не раскрывает значение переменной, поэтому мы заставляем выполняющийся shell повторно анализировать командную строку и вычислять все переменные. На этот раз значения переменных раскрываются, и мы получаем верный конечный результат.
МОДИФИКАЦИИ
Возможно, вы захотите более подробно рассмотреть интерфейс со временем. В нынешнем состоянии at воспринимает только время в пределах от 0 до 23 часов в течение одного дня. Неплохим дополнением было бы заставить его различать время суток, т.е. 8:30 a.m. (до полудня) или 8:30 p.m. (после полудня). Было бы неплохо также иметь возможность сказать "через 10 минут сделать то-то и то-то". В этом случае команда могла бы иметь примерно такой вид:
at -n 10 echo " do in now plus 10 minutes"
где -n было бы текущим временем, а 10 добавлялось бы к нему.
Другой очевидной областью модификации является наделение at возможностью запоминания периодов времени, превышающих сутки. Это может быть завтрашний день, определенный день или даже определенный месяц. Работа с определенным месяцем может быть не совсем реальной, поскольку командный файл, выполняемый в фоновом режиме в течение нескольких месяцев, потребует громадного количества процессорного времени, а также хранения постоянно возрастающего счетчика идентификаторов процессов, что даст, вероятно, пользователям искаженное представление об активности системы. По достижении максимального номера процесса, снова вернутся младшие номера, так что это не приведет к каким -либо серьезным последствиям. Решение вопроса о том, стоит ли такой ценой достигать вашей цели, зависит от того, считаете ли вы, что ваши требования излишне загружают систему.
ИМЯ: b
b Обработчик фоновых задач
ФОРМАТ ВЫЗОВА
b any_command_with_options_and_arguments (любая команда с опциями и аргументами)
ПРИМЕР ВЫЗОВА
b cg f.c
Компилировать исходный файл в фоновом режиме, где cg - командная строка компилятора, описанная в главе 10.
ТЕКСТ ПРОГРАММЫ b
1 : 2 # @(#) b v1.0 Background task handler Author: Russ Sage 2а Обработчик фоновых задач 4 ($@; echo "^G\ndone\n${PS1}\c") &
ОПИСАНИЕ
Зачем нам нужен b?
Как вы видели в последнем разделе, Bourne shell дает возможность запускать задачи в фоновом режиме выполнения. Это делает символ &. Что же на самом деле происходит, когда мы запускаем что-нибудь в фоновом режиме? Порождается еще один shell, который должен выполнить свою собственную командную строку. После того, как все его команды выполнятся, он завершается. Вы можете определить фоновые задачи по результату работы команды ps. Эти задачи выглядят как интерпретаторы shell, запущенные с вашего терминала, однако их владельцем, или родительским процессом в действительности является команда init, а не ваш регистрационный shell (это справедливо только для shell, к которым применена команда nohup). Интерпретаторы shell, к которым не применялась nohup, принадлежат вашему регистрационному shell. Ниже приводится пример распечатки команды ps для фоновых задач. Командой для выполнения в фоновом режиме была:
while :;do date; done &
Команда ps показывает мой регистрационный shell (PID=32), введенную мной командную строку для выполнения в фоновом режиме (PID=419) и shell, который выполняет цикл while (PID=449).
| | UID PID PPID C STIME TTY TIME COMMAND | | root 0 0 0 Dec 31 ? 0:03 swapper | root 1 0 0 Dec 31 ? 0:02 /etc/init | russ 32 1 0 14:18:36 03 1:26 -shV | russ 419 32 0 15:30:31 03 0:02 -shV | russ 449 419 2 15:30:31 03 0:02 -shV |
Ниже приведен листинг команды ps, который показывает фоновый shell, принадлежащий процессу init. Он был получен командой "b ps -ef", где b - утилита, которая будет рассмотрена далее. Как видите, последний процесс 471 есть фоновый shell, принадлежащий процессу 1, которым является init, а не мой регистрационный shell (PID=32).
| | UID PID PPID C STIME TTY TIME COMMAND | root 0 0 1 Dec 31 ? 0:04 swapper | root 1 0 0 Dec 31 ? 0:02 /etc/init | russ 32 1 1 14:18:36 03 1:30 -shV | russ 472 471 5 15:46:46 03 0:12 ps -ef | russ 471 1 0 15:46:46 03 0:00 -shV |
К чему все это приводит? Когда мы используем фоновые задачи, мы должны мириться с "неразборчивостью" при управлении асинхронными процессами. Каковы эти недостатки?
Во-первых, мы никогда не знаем момента завершения фоновых задач.
Единственный способ определить момент завершения таких задач - проверка результатов в каком-либо файле или некоторой работы, выполненной задачей, или использование команды ps и постоянное слежение за тем, когда процесс завершится. Такое слежение при помощи команды ps - не самый лучший способ, поскольку ps занимает много процессорного времени и очень медленно работает.
Второй неаккуратный момент - это символ приглашения после выдачи на ваш экран результата из фоновой задачи. После того как выдан результат из фоновой задачи, ваш регистрационный shell ожидает ввода команды, но приглашения может и не быть, поскольку оно было удалено с экрана некоторым другим сообщением. Вы можете ожидать приглашения целый день, но оно никогда не появится, поскольку оно уже было выведено на экран. Вы просто должны знать, что shell ждет вашу команду.
Нам необходимо инструментальное средство, которое сообщает нам о завершении фоновой задачи, а также восстанавливает наш экран после выдачи на него каких-либо результатов. Можем ли мы сказать, выполняла ли вывод на экран фоновая задача или нет? Нет, поэтому мы должны жестко запрограммировать восстановление экрана в программе.
Что делает b?
Командный файл b - это механизм, который помогает в выполнении фоновых задач. Он запускает наши фоновые задачи. По завершении он отображает на экран слово "done" и затем повторно выводит символ-приглашение shell.
Данное средство не имеет опций и проверки на наличие ошибок. Обработчик фоновых задач фактически выполняет командную строку, которую мы ему передаем, и последующую обработку. Отметим, что для выдачи на экран вашего символа приглашения, вы должны экспортировать переменную PS1 из вашей текущей среды. Это может соблюдаться не на всех машинах, поскольку каждая система UNIX имеет свои особенности. В системе XENIX переменная PS1 не передается, возможно из-за того, что это shell, который вызывает другой shell в фоновом режиме. Если вы скажете "sh" в интерактивном режиме, он будет передан как приглашение. Система UNIX так прекрасна и удивительна!
ПРИМЕРЫ
1. $ b ls -R ..
Начиная с родительского каталога, рекурсивно составляется список всех файлов и выводится на экран. Обратите внимание, что при использовании фоновых задач вы не можете эффективно передать все это по конвейеру команде more, поскольку обычным входным устройством для фоновых задач является /dev/null. Команда more не работает нормально, когда ее вызывают из фонового режима. Это также имеет смысл, поскольку вы могли бы иметь две задачи - одну в фоновом режиме, а другую в приоритетном производящие беспорядок на экране. Фоновая команда more должна была бы сохранять в неприкосновенности то, что выводит на экран приоритетная команда.
2. $ b echo hello > z
Файл z содержит не только слово "hello", но также и сообщение "done", поскольку переадресация в файл z выполняется во внешнем shell. Переадресация для подзадачи должна быть выполнена в круглых скобках программы b, а мы в данном случае не можем этого сделать.
3. $ b sleep 5; echo hello
Эта командная строка не может быть выполнена, поскольку программа b воспринимает только команду sleep. Команда echo не помещается в фоновый процесс и сразу же выполняется.
4. $ b "sleep 5; echo hello"
Эту командную строку мы тоже не можем выполнить, поскольку эти две команды будут восприняты командным файлом b как одна. Затем команда sleep не выполнится, поскольку "5; echo hello" является недопустимым указанием временного периода для команды sleep.
ПОЯСНЕНИЯ
Обратите внимание, что в строке 4 вся структура команды заключена в круглые скобки, за которыми следует символ &. Круглые скобки передают всю структуру подчиненному shell, который затем помещается в фоновый режим выполнения. Помещая все команды в один shell, мы гарантируем вывод на экран слова "done" после завершения последнего процесса.
Данная командная строка выполняется с помощью символов $@. Это означает: "выполнить всю командную строку, расположенную справа". Поскольку выражение $@ выполняет само себя (т.е. не в операторе echo или в чем-либо подобном), то shell просто выполняет команды исходной командной строки. Это именно то, что мы хотим! Обратите внимание, что здесь нет никакого оператора eval. Поскольку то, что мы делаем, похоже на своего рода "командный интерпретатор строк" для их ввода и исполнения, вы могли бы подумать, что команда eval здесь необходима. По опыту мы знаем, что это не так. Похоже, что применение eval усложнит дело.
Даже наш старый тест, использующий переменные среды выполнения, работает. По команде
b echo $HOME
на экран будет выдано сообщение
/usr/russ
Когда вся команда выполнится, подается звуковой сигнал и выводится сообщение, информирующее пользователя о том, что операция завершилась. Поскольку это сообщение накладывается на то, что было на экране, то переотображается начальный символ приглашения (PS1). Это делает нормальным вид экрана в том смысле, что символ приглашения shell сообщает об ожидании ввода.
ИМЯ: greet
greet Своевременное приветствие с терминала
НАЗНАЧЕНИЕ
Определение времени суток и печать приветствия и какого-либо сообщения на терминал в зависимости от времени дня.
ФОРМАТ ВЫЗОВА
greet
ПРИМЕР ВЫЗОВА
greet Вызывает командный файл greet, который определяет время и печатает соответствующее сообщение.
ТЕКСТ ПРОГРАММЫ greet
1 : 2 # @(#) greet v1.0 Timely greeting from the terminal Author: Russ Sage 2а Своевременное приветствие с терминала 4 if [ `expr \`date +%H\` \< 12` = "1" ] 5 then echo "\nGood morning.\nWhat is the best use of your time right now?" 6 elif [ `expr \`date +%H\` \< 18` ="1" ] 7 then echo "\nGood afternoon.\nRemember, only handle a piece of paper once!" 8 else echo "\nGood evening.\nPlan for tomorrow today." 9 fi
ОПИСАНИЕ
Зачем нам нужен greet?
Одним из замечательных преимуществ многопользовательских операционных систем является то, что они имеют хорошо развитую концепцию времени. Обычно они содержат часы реального времени и некоторое программное обеспечение, которое манипулирует с ними. Однако всегда есть место для дополнительного программного обеспечения, работающего со временем. Такие средства могут быть написаны как на языке Си, так и на shell-языке.
Как мы извлекаем и выделяем время с помощью командного файла интерпретатора shell? Доступно много способов, но стандартная команда UNIX date, видимо, является наилучшим способом. В случае языка Си вы должны программно управлять преобразованием времени и временными зонами. Команда date делает это для вас.
Важна также единица времени. Должны ли мы различать секунды, минуты, часы, дни или недели? Это все зависит от требуемого приложения. В нашем простом примере мы различаем только три части суток: утро, день и вечер. Мы определили эти периоды так: с полуночи до полудня, от полудня до шести часов и от шести часов до полуночи соответственно.
Что делает greet?
Greet - это утилита, которая приветствует пользователя различными сообщениями в зависимости от времени суток. Выводимые сообщения не так важны. Они в основном использованы как примеры, показывающие, как могут быть выполнены какие-то команды. Если вы работаете в одиночестве и хотели бы поболтать, эти сообщения могли бы читаться периодически из соответствующих файлов для создания иллюзии автоматической письменной болтовни в зависимости от времени суток.
Действительной же целью является создание каркаса программы, которая может переключаться в зависимости от параметров времени. Путем расширения концепции времени вы можете создать другие утилиты, которые знают, когда им работать (в какой промежуток времени) и могут вести себя иначе в соответствии со временем.
Greet не требует ничего в командной строке. Не выполняется никакой проверки на наличие ошибок, поэтому и нет в программе синтаксической подсказки. Выход команды greet может быть переадресован в файл или передан по конвейеру другому процессу.
ПРИМЕРЫ
1. $ if greet | fgrep 'morn' > /dev/null > then morning_routine > fi
Выполняется greet. Стандартный вывод greet по конвейеру передается на стандартный ввод fgrep. Производится поиск символьной строки "morn". Весь выход переадресовывается в никуда, так что он не засоряет экран. Если выходной статус команды fgrep равен нулю (она нашла нужную строку), выполняется файл morning_routine.
2. $ at 10:30 greet; at 13:50 greet
Вы могли бы вставить это в ваш .profile. Два процесса at будут выполняться на вашей машине в фоновом режиме до тех пор, пока не наступит время их запуска - тогда они поприветствуют вас на вашем терминале. Правда, это может причинить небольшое неудобство. Сообщение может появиться, когда вы работаете в редакторе, и нарушить содержимое экрана, но на самом деле оно не изменит ваш редактируемый файл.
ПОЯСНЕНИЯ
Вся программа представляет собой один большой оператор if -then-else в строках 4-9. Логика программы выглядит на псевдокоде следующим образом:
if it is morning если утро then echo morning statement то вывести "утреннее" приветствие else if it is noon иначе если день then echo noon statement то вывести "дневное" приветствие else echo evening statement иначе вывести "вечернее" приветствие
В действительности программа гораздо сложнее, поэтому переведем дыхание и приступим к делу.
В строке 4 проверяется текущее время на то, меньше ли оно 12 часов. Если да, то фраза команды expr выводит на стандартное устройство вывода единицу ("1"). Поскольку символы ударения (`), которые обрамляют эту фразу, перехватывают стандартный вывод, символ 1 становится частью оператора проверки, что указано квадратными скобками ([]). Затем оператор test проверяет, равен ли выход команды expr литеральной единице. Если они одинаковы, то в строке 5 выводится "утреннее" сообщение.
Рассмотрим более подробно, как раскрывается оператор expr. Во-первых, он заключен в символы ударения. Это означает, что он выполняется перед оператором проверки. Затем его выход помещается для обработки в оператор test. Однако внутри оператора expr имеется еще одно выражение между знаками ударения, которое выполняется до оператора expr. Такое старшинство выполнения управляется интерпретатором кода внутри shell.
Внутренние знаки ударения сохраняются при начальном синтаксическом разборе строки, поскольку они экранированы символами обратной косой черты. Первой запускается команда date, имеющая в качестве выхода только текущее значение часа в соответствии с форматом %H. Затем expr использует данное значение часа для проверки, меньше ли оно 12.
Если да, expr печатает единицу. Если значение часа больше или равно 12, то возвращаемое значение равно 0. Такое понимание, что 1=истина и 0=ложь, соответствует синтаксису, используемому в языке Си.
Однако ранее мы замечали, что в среде программирования на языке shell 1 означает ложь, а 0 - истину. Это происходит потому, что проверяемое значение оператора if является в действительности статусом выхода из предварительно выполненной команды. Нуль соответствует нормальному завершению, поэтому 0 использован для переключения проверки в состояние "истина" и выполнения оператора then. Для того, чтобы преобразовать возвращаемый статус 1 (при условии, что значение часа меньше 12) в нуль (для переключения оператора then), мы используем команду test. Возвращаемый статус единицы равен константе 1, поэтому команда test возвращает 0, что представляет истину. Вот так!
Если бы не были использованы вложенные знаки ударения, то единственным способом передачи данного типа информации другому процессу было бы применение переменных shell. Использование вложенной командной подстановки дает нам большую гибкость и простоту программирования. Чем больше глубина вложенности, тем глубже экранирование знаков ударения. Порядок экранирования символами обратной косой черты такой: не нужно для внешней команды, один раз для второй внутренней команды, пять раз для третьей внутренней команды. На четвертом уровне их должно быть семь или девять (я еще не пробовал), но вероятно нет большой нужды во вложенности такой глубины.
Если проверка в строке 4 дает "ложь", выполняется строка 6. Это оператор else от первого if и одновременно следующий if. В таких особых случаях синтаксис shell меняется. Ключевое слово "else" становится ключевым словом "elif".
Второй if использует команду test точно так же, как и первый. Проверяемое время здесь 18, что представляет собой 6 часов вечера. Если вторая проверка также дает "ложь", выполняется последний оператор в строке 8. Этот else не использует команду test, поскольку после выполнения первых двух проверок мы можем сделать вывод, что остался последний период времени, а именно период после 18:00.
ИМЯ: lastlog
lastlog Сообщает время последней регистрации
НАЗНАЧЕНИЕ
Записывает и выводит на экран день и время вашей последней регистрации в системе.
ФОРМАТ ВЫЗОВА
lastlog [-l]
ПРИМЕР ВЫЗОВА
lastlog Печатает дату, когда вы последний раз регистрировались
ТЕКСТ ПРОГРАММЫ lastlog
1 : 2 # @(#) lastlog v1.0 Report last login time Author: Russ Sage 2а Сообщает время последней регистрации 4 if [ $# -gt 1 ] 5 then echo "lastlog: arg error" >&2 6 echo "usage: lastlog [-l]" >&2 7 exit 1 8 fi 10 if [ "$#" -eq "1" ] 11 then if [ "$1" = "-l" ] 12 then date >> $HOME/.lastlog 13 lastlog 14 else echo "lastlog: unrecognized option $1" >&2 15 echo "usage: lastlog [-l]" >&2 16 exit 1 17 fi 18 else echo "Time of last login : `tail -2 $HOME/.lastlog | 19 (read FIRST; echo $FIRST)`" 20 fi
ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ
FIRST | Хранит первую из двух введенных строк |
HOME | Хранит имя вашего регистрационного каталога |
Зачем нам нужен lastlog?
Одним из преимуществ работы в системе UNIX является то, что в ней совершается автоматическая запись вашего начального времени при каждом сеансе работы - вашего времени регистрации. Эта информация может быть полезной по нескольким причинам. Вопервых, вы можете запомнить, когда вы действительно работали в системе последний раз и проверять, не регистрировался ли кто-нибудь под вашим паролем во время вашего отсутствия. Как мы увидим в главе 9, имеется ряд возможностей для того, чтобы кто-нибудь мог "заимствовать" ваш пароль без спроса. По этой причине многие коммерческие системы сообщают вам, когда вы регистрировались последний раз (или когда, по их мнению, вы это делали).
Другой возможной причиной мог бы быть подсчет общего времени в конце сеанса работы. Вы могли бы использовать это как учетную информацию для себя или вычислительного центра. Немного позже мы представим средство, которое помогает при таких подсчетах.
Разрабатываемое нами инструментальное средство должно иметь возможность записывать новые значения времени и выводить на экран время нашей последней регистрации. Важно, что данная программа может быть вызвана так, что она не изменяет файл с данными, но постоянно выводит время последней регистрации.
Что делает lastlog?
Lastlog - это программа, которая записывает время вашей регистрации при каждом входе в систему. Затем это время хранится в файле данных в вашем регистрационном каталоге под именем $HOME/.lastlog. Имя файла lastlog начинается с точки с той целью, чтобы сделать его невидимым для команды ls. Укрытие "служебных" файлов от распечатки по умолчанию несколько предохраняет от любопытных глаз, а также убирает эти файлы с дороги, когда вы просматриваете что-то другое.
При вызове без опций lastlog печатает для нас дату последней регистрации, получая запись из файла .lastlog.
Для выполнения новой записи в файл .lastlog необходимо вызвать lastlog с опцией -l. При этом новое значение времени запишется в файл .lastlog, а затем командный файл lastlog вызовет сам себя для вывода на экран нового значения - небольшая рекурсия.
Для того, чтобы программа lastlog работала автоматически, вы должны выполнять ее из вашего файла .profile во время регистрации. При таком способе она запишет последнее время в файл .lastlog. В качестве примера посмотрите файл .profile в первой главе.
ПОЯСНЕНИЯ
В строках 4- 8 выполняется проверка на наличие ошибок. Если вы вызвали lastlog с числом аргументов больше одного, то это приведет к ошибке. Выводится сообщение на стандартное устройство регистрации ошибок, и lastlog завершается со статусом ошибки 1.
Строки 10-20 представляют собой оператор if-then-else, который показывает, был ли это вызов для записи нового значения времени или для печати старых значений.
Если в строке 10 число позиционных параметров равно одному, то мы знаем, что либо этот параметр должен быть опцией -l, либо это ошибка. Следующий оператор if в строке 11 проверяет, является ли первый позиционный параметр опцией -l. Если да, то в файл $HOME/.lastlog добавляется текущая дата и lastlog вызывается снова без аргументов для печати предыдущей даты регистрации. (Мы только что видели, как это делается.) Если это не был аргумент -l, то строки 14-16 выполняют обработку ошибки.
Если число позиционных параметров равно нулю, выполняется оператор else в строке 18. Отсутствие опций означает, что мы хотим найти время нашей последней регистрации на машине и распечатать его. Это кажется довольно простым, но кто сказал, что машины просты?
Если вы помните последовательность работы, то мы сперва регистрируем новое время, а затем хотим найти время нашей предыдущей регистрации. Для файла .lastlog это означает, что наше текущее время регистрации находится в самом конце файла, а наше предыдущее время регистрации находится в строке непосредственно перед ним. Это значит, что мы должны получить вторую строку от конца файла. Да уж.
Как видно из строки 18, она занимается получением последних двух строк. Команда tail красиво выполняет эту работу. Нам нужен такой способ, чтобы мы могли прочитать именно первую строку, а вторую отбросить, что выполняется в строке 19. Мы передаем по конвейеру выход команды tail подчиненному shell (указанному круглыми скобками), который читает первую строку и затем отображает ее. А что же со второй строкой? Она никогда не берется и пропадает. Другим способом может быть передача выхода команды tail по конвейеру команде "head -1". Поскольку эта команда не имеет других опций, мы не даем никаких примеров. Тем не менее, давайте теперь рассмотрим наше другое средство регистрации времени входа в систему.
УПРАВЛЕНИЕ ВРЕМЕНЕМ И ДЕЛОПРОИЗВОДСТВОМ
ВВЕДЕНИЕ
Мы уже многое знаем о файлах и о том, как управлять файловой структурой. Пора рассмотреть, как мы можем использовать систему UNIX для управления множеством задач, которые составляют наш рабочий день и держат нас в курсе того, что делают другие пользователи. Термин "управление личной информацией" (personal management) подразумевает, что вы хотите создать свою собственную ПЕРСОНАЛЬНУЮ рабочую среду и инструментальные средства. Мы предлагаем вам пакет программ, которые вы можете приспособить к вашим требованиям. Фактически мы в этой и следующей главе представляем четыре отдельных набора программ, каждый из которых посвящен определенному аспекту управления личной информацией.
Средства управления временем помогают нам спланировать выполнение задач компьютером, а также контролировать наше личное время. Управление делопроизводством имеет дело с хранением и извлечением информации, а также с организацией доступа к различным функциям системы UNIX посредством простого в использовании интерфейса в виде меню. Для каждой из этих областей деятельности мы даем ее обзор, а затем представляем соответствующую группу средств.
ОСВЕДОМЛЕННОСТЬ ПОЛЬЗОВАТЕЛЯ
activ | показать активность терминалов |
info | показать информацию о паролях пользователей |
uchk | посмотреть процессы других пользователей |
watch | наблюдать за регистрацией в системе |
whox | команда who с дополнительными возможностями |
| | root tty01 01:23 | sage tty05 . | batch tty12 old | nuucp tty16 . |
Важным вопросом является то, как система узнает, когда кто-либо работает с клавиатурой? Поскольку терминальные устройства являются файлами, мы можем собрать информацию о терминале путем просмотра состояния файла, связанного с ним. В данном случае искомая информация - время последнего использования клавиатуры.
То, что мы ищем, уже есть в системе. Это опция -u команды who. Эта опция, однако, существует только в системе AT&T UNIX System V, поэтому команда activ, по крайней мере в представленном виде, может быть реализована только в этой системе. Activ берет выход команды "who -u" и "отрезает" некоторые данные, не имеющие отношения к нашей цели.
Путем проб и ошибок, а затем проверки исходного кода я обнаружил, что значение, используемое в команде "who -u", является временем "модификации" файла устройства, которое сообщает системный вызов stat(2). На страницах руководства по stat дается полная таблица системных вызовов, которые изменяют временные характеристики файлов. В табл. 6.1 представлены эти характеристики:
Таблица 6.1
Временные характеристики файла
Временные характеристики | Системные вызовы, изменяющие временные характеристики |
Время доступа | creat, mknod, pipe, utime, read |
Время модификации | creat, mknod, pipe, utime, write |
Время создания | creat, mknod, pipe, utime, write, chmod, chown, link |
Однако в системе UNIX имеется третье время - время создания. Система не позволяет вам изменить это время при помощи обычных команд. Системный вызов utime(2) обеспечивает изменение только времени доступа и модификации. Команда touch(1) также может изменить время доступа и модификации, но не время создания.
Команда touch строится только на системном вызове. Она может делать не больше того, что обеспечивает системный вызов. Команда fsdb(1) (отладчик файловой системы) является единственным способом преодоления защиты от изменения времени создания. Но даже fsdb не может управлять временем создания. Вы должны выйти за пределы поддерживаемых fsdb требований безопасности. Как это сделать, показано в последующих главах.
В случае изменения регистрационной записи, рассмотренном выше, вы могли бы посмотреть на время создания и увидеть, что оно очень близко к текущему времени. Если же кто-либо использовал fsdb для подделки времени создания, вы никогда не будете иметь уверенности в том, что файл не был изменен.
Какое же все это имеет отношение к определению того, делал ли пользователь что-либо недавно за своим терминалом? Время модификации изменяется вызовом write(2). Поэтому представляется разумным, что запись на терминал будет иметь место, когда драйвер читает символ, а затем посылает его обратно на экран. По мере того, как кто-то нажимает на клавиши, время модификации постоянно обновляется при эхо-отображении символов. Когда символы перестают поступать с клавиатуры, приостанавливается запись в файл терминала. Формула для определения последнего времени работы за терминалом (last_activity) такова:
last_activity = time(NULL) - mod_time,
где mod_time - время модификации. Эта формула закодирована внутри команды who(1). Отметим, что вызов команды time со значением NULL возвращает истинное текущее время.
Команда activ не имеет опций. Если вы используете опции, напечатается сообщение об ошибке.
ПОЯСНЕНИЯ
В строках 4-8 выполняется проверка на наличие ошибок. Если число аргументов командной строки больше нуля, на стандартное устройство регистрации ошибок выводится сообщение и программа завершается с неудачным статусом.
Строка 10 - это команда, выполняющая вывод. Команда who вызвана с опцией -u для получения основных данных. Затем ее выход по конвейеру передается команде cut, которая отображает колонки 1-17 и 38-42. Тем самым печатается только три поля, как показано в нашем предыдущем примере.
ИМЯ: info
info Вывод на экран информации о пароле пользователя
НАЗНАЧЕНИЕ
Печатает информацию поля комментария из файла /etc/ passwd для указанного пользователя.
ФОРМАТ ВЫЗОВА
info login_name [ login_name ... ]
ПРИМЕР ВЫЗОВА
info russ Печатает информацию, которая хранится о пользователе russ
ТЕКСТ ПРОГРАММЫ
1 : 2 # @(#) info v1.0 Display password info on a user Author: Russ Sage 2а Отобразить парольную информацию пользователя 4 for NAME in $@ 5 do 6 USER=`grep "^${NAME}:" /etc/passwd` 7 echo "$NAME:\t`echo ${USER}|cut -d: -f6`\t` echo ${USER}|cut -d: -f5`" 8 done
ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ
NAME | Каждое имя, указанное в командной строке |
USER | Полная запись в файле /etc/passwd для данного имени |
Зачем нам нужен командный файл info?
Система UNIX использует конфигурационные файлы для хранения основной информации о пользователях и другой системной информации. Одними из наиболее популярных конфигурационных файлов являются /etc/group, /etc/passwd и /etc/inittab. Для получения информации о пользователях нам необходимо заглянуть в эти файлы.
В системе Berkeley имеется команда finger, которая получает информацию о пользователях из их регистрационных каталогов и из файла паролей. В System V не так много программ, выполняющих такого рода работу, поэтому нам необходимо разработать их.
Что делает info?
Info - это командный файл, который получает информацию из регистрационного каталога и комментарий о пользователе из файла /etc/passwd. Результат выглядит так:
| | name: home dir comments | имя: регистрационный каталог комментарии |
Если в вашей системе используется это поле комментариев, то эта информация может быть полезной. "Вручную" выполнить просмотр пользователей в файле паролей можно следующим образом:
grep login_name /etc/passwd
При этом распечатывается вся строка со всеми полями данных. Info берет эти необработанные данные и выделяет из них каталог регистрации пользователя и поле комментария.
В командной строке можно указать несколько регистрационных имен. Каждое имя берется по порядку из командной строки.
ПРИМЕР
$ for NAME in `cat /etc/passwd | cut -d: -f1` > do > NAMELIST="$NAMELIST $NAME" > done; info $NAMELIST
Имя каждого пользователя в файле паролей добавляется к списку имен. Затем этот список передается в командную строку info, которая печатает все данные. Смысл команды: "Дай мне информацию обо всех пользователях системы".
ПОЯСНЕНИЯ
Строки 4-8 - это цикл for, который обрабатывает все имена, переданные в командной строке. Для каждого переданного имени выполняются строки 6 и 7.
В строке 6 в переменную USER заносится результат команды grep, заключенной между символами ударения (`). Начиная с начала строки (обозначено символом ^), grep ищет имя, за которым следует символ двоеточия (:). Такое указание заставляет выполнять поиск образца только в первом поле файла паролей.
В строке 7 мы отображаем нашу выходную строку командой echo, внутри которой вложены другие команды echo. Мы могли бы получать элементы информации отдельно, присваивая их значения отдельным переменным, и затем создавать необходимый формат вывода, используя значения этих переменных. Однако помещение всей информации в командную строку работает быстрее, и текст программы более компактный, хотя и не очень читабельный. Мы можем также использовать символы форматирования в команде echo для форматирования нашей распечатки.
Сначала отображается имя в том же виде, как оно получено из командной строки. Затем выводится табуляция (\t). За первой табуляцией следует поле номер шесть из файла паролей. Поскольку мы еще не имеем этих данных, мы должны выделить их из значения переменной USER, которую мы уже имеем из предыдущей строки. Чтобы сделать это, мы командой echo выводим всю строку и выделяем шестое поле, используя разделяющие двоеточия. После этого поля мы выводим еще одну табуляцию и затем пятое поле файла паролей. Мы получили это поле таким же образом, как и шестое поле - эхо-отображением и выделением.
Такая техника получения данных медленная, поскольку вовлечены все процессы, но это самый быстрый путь сделать это на языке shell. Команда awk была бы понятнее и, возможно, быстрее, но наша реализация демонстрирует гибкость языка shell. Язык shell может выполнять почти все, но не всегда лучшим образом. Вот почему в некоторых случаях мы используем язык Си, в чем вы убедитесь по мере продвижения по нашей книге.
ИМЯ: uchk
uchk Проверка процессов, запущенных другим пользователем
НАЗНАЧЕНИЕ
Отобразить все процессы каждого пользователя, указанного в командной строке
ФОРМАТ ВЫЗОВА
uchk [-a] login_name [...]
ПРИМЕР ВЫЗОВА
uchk -a Вывод всех процессов, запущенных администраторами
ТЕКСТ ПРОГРАММЫ
1 : 2 # @(#) uchk v1.0 Check processes of another user Author: Russ Sage 2а Проверка процессов другого пользователя 4 trap "rm /tmp/ps$$ 2> /dev/null" 0 1 2 3 15 6 if [ $# -eq 0 ] 7 then echo "uchk: argument error" >&2 8 echo "usage: uchk [-a] login_name [ ... ]" >&2 9 exit 1 10 fi 12 if [ "`echo $1 | cut -c1`" = "-" -a "$1" != "-a" ] 13 then echo "uchk: invalid argument $1" >&2 14 echo "usage: uchk [-a] login_name [ ... ]" >&2 15 exit 1 16 fi 18 ADMIN="administrators names go here" 19 if [ "$1" = "-a" ] 20 then shift 21 set $ADMIN $@ 22 fi 24 ps -ef > /tmp/ps$$ 25 for NAME 26 do 27 echo 28 fgrep "$NAME" /tmp/ps$$ 29 done
ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ
ADMIN | Строка с именами пользователей, являющихся администраторами в вашей системе |
NAME | Содержит каждое имя, считываемое из командной строки |
Зачем нам нужен командный файл uchk?
Поскольку UNIX является многопользовательской системой, множество задач выполняются одновременно. Единственный способ следить за такими задачами - с помощью команды ps. Ps - это довольно специфичная команда в том смысле, что она должна обращаться к памяти (/dev/mem) и проходить по связанному списку структур процесса. Это тяжелая вещь, поэтому нелегко сделать свою собственную команду для вывода такого рода информации. Вдобавок память защищена, и доступ к ней имеет только пользователь root.
Так что мы должны или удовлетвориться опциями, которые дает команда ps, или модифицировать вывод этой команды. Uchk скорее относится к последней категории. Мне хотелось получить список состояний процессов по имени пользователя, а не смесь информации, относящейся к разным пользователям. Хотя то, что я хотел, не соответствует ни одной из стандартных опций команды ps, я смог модифицировать ее выход для получения того, что мне надо.
Что делает uchk?
Uchk - это средство, которое генерирует, анализирует и сообщает о процессах, запущенных каждым указанным пользователем. За один раз вы можете проверить столько пользователей, сколько вам нужно. Все процессы для каждого пользователя печатаются вместе.
Чтобы делать это, uchk должен работать с временными файлами. Система UNIX весьма элегантно управляет временными файлами. Shell позволяет использовать идентификатор процесса для создания уникальных имен файлов для каждого запуска. После того как утилита запущена, временные файлы очищаются путем использования команды trap в shell'е. Команда trap удаляет файл, если что-либо прервало выполнение командного файла или когда программа завершается. Это прекрасная особенность, которая исключает накопление случайных файлов в системе.
Если uchk была вызвана без аргументов или была использована недопустимая опция, то печатается сообщение об ошибке и выполнение завершается.
Uchk имеет переменную ADMIN, которая определяет всех администраторов вашей системы. Отредактируйте символьную строку, присваиваемую переменной ADMIN, указав имена администраторов, которых вы хотите проверить. Имена должны быть разделены пробелами. Это позволит вам проверять процессы ваших администраторов, указывая опцию -a в командной строке. Все остальные имена должны быть указаны в командной строке. Естественно, вы таким же образом могли бы установить другие группы пользователей. Слежение за администраторами поможет вам оценить, как они управляют сохранностью информации, а также распознать административные задачи, которые имеют склонность загружать процессор.
ПРИМЕР
$ uchk -a russ uucp
Показывает процессы всех администраторов, мои собственные, и процессы uucp по порядку. Все сообщения об ошибках выводятся на стандартное устройство регистрации ошибок, а списки процессов выводятся на стандартное устройство вывода.
ПОЯСНЕНИЯ
Строка 4 - это оператор trap. Символьная строка между двойными кавычками содержит команды, которые должны быть выполнены, когда происходит прерывание. В этом случае мы удаляем временный файл и переадресовываем все выходные данные на нулевое устройство. Когда команда rm пытается удалить несуществующий файл, выводятся сообщения об ошибках. Поскольку мы не знаем, какие файлы могут быть в наличии в тот момент, когда возникнет прерывание, то мы хотим избавиться от сообщений об ошибках. Оператор trap активизируется по выходу из программы (program exit, сигнал 0), разрыву линии (hangup, сигнал 1), прерыванию (interrupt, сигнал 2), выходу (quit, сигнал 3) или программному завершению (software termination, сигнал 15).
В строках 6-10 проверяется, переданы ли какие-либо аргументы. Если вы вызвали uchk без аргументов, выводится сообщение об ошибке и uchk завершается.
В строках 12-16 проверяется, указана ли какая-то опция со знаком минус и является ли она опцией -a - единственно допустимой опцией. Командный файл делает это, применяя команды проверки для сравнения двух различных случаев. Первая проверка вырезает первый символ первого позиционного параметра и смотрит, является ли он символом "-". Следующая проверка делается для того, чтобы увидеть, что первый позиционный параметр не является опцией -a. Поскольку обе проверки соединены операцией AND, то для получения значения "истина" они обе должны быть истинными. Если они обе истинны, выводится сообщение об ошибке и uchk завершается.
Почему мы должны выполнять такую сложную проверку? Проблема в том, что у нас нет иного способа определить, является первый позиционный параметр опцией или нет. Он может быть опцией, а может быть именем, которое нужно искать. Таким образом, мы должны задать вопрос: "Является ли это опцией, и если да, то допустима ли эта опция?"
В строке 18 переменная ADMIN инициализируется символьной строкой, следующей за ней. Это строка, которую вы должны модифицировать в соответствии с вашей системой. Все, что вы должны сделать - это использовать редактор vi и вставить в эту строку имена администраторов, разделенные пробелами. Ниже мы используем переменную ADMIN для обработки аргументов командной строки.
В строках 19-22 проверяется, была ли указана в командной строке опция -a. Если да, эта опция удаляется из командной строки с целью избавления от нее. Строка 21 использует команду set для того, чтобы поместить символьную строку ADMIN в позиционные параметры. Команда set вставляет значение переменной ADMIN, начиная с первого позиционного параметра, и сдвигает все настоящие параметры вправо. Тем самым в цикл for передается множество имен, которые должны быть обработаны.
В строке 24 выполняется команда ps для всех пользователей. Эта команда использует опцию f для вывода большого количества данных. Результат помещается во временный файл, в имени которого применяется идентификационный номер процесса. Этот один большой файл представляет собой источник для остальных распечаток. Возможно, все это немного отстает от реального времени, но программа выполняется гораздо быстрее, чем при многократном вызове команды ps.
Строки 25-29 представляют собой цикл for, который выполняется от $1 до $x, где x - последний позиционный параметр. Для каждого имени выполняется следующее: печатается пустая строка для разделения листинга (строка 27), затем командой fgreps (для ускорения) выбираются из временного файла все процессы, принадлежащие данному пользователю. Благодаря применению команды fgrep для каждого имени пользователя, все процессы данного пользователя печатаются за один раз. Когда закончится проверка всех имен, указанных в командной строке, цикл завершится, завершится командный файл и сработает оператор trap, который удаляет временный файл.
ИМЯ: watch
watch Наблюдение за регистрацией указанных пользователей
НАЗНАЧЕНИЕ
Следит за тем, кто работает в системе, и сообщает о регистрации указанных пользователей.
ФОРМАТ ВЫЗОВА
watch [-k] [login_name ...]
ПРИМЕР ВЫЗОВА
watch Наблюдение за регистрацией всех пользователей, указанных во внутренней переменной LIST
ТЕКСТ ПРОГРАММЫ
1 : 2 # @(#) watch v1.0 Watch for specific logins Author: Russ Sage 2а Наблюдение за регистрацией пользователей 4 if [ "`echo $1 | cut -c1`" = "=" -a "$1" != "-k" ] 5 then echo "watch: invalid argument $1" >&2 6 echo "usage: watch [-k] [login_name ...]" >&2 7 echo " -k kill background process" 8 exit 1 9 fi 11 if [ "$1" = "-k" ] 12 then if [ -s $HOME/.watch ] 13 then echo "killed `cat $HOME/.watch`" 14 kill `cat $HOME/.watch` 15 rm $HOME/.watch 16 exit 0 17 fi 18 fi 20 echo $$ > $HOME/.watch 22 LIST="root sys bin administrator1 administrator2 $*" 24 while : 25 do 26 for NAME in `who | cut -d" " -f1` 27 do 28 for PERSON in $LIST 29 do 30 if [ "$NAME" = $PERSON" ] 31 then echo ONLINE: $NAME 32 fi 33 done 34 done 35 sleep 10 36 done &
ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ
HOME | Полный маршрут к вашему регистрационному каталогу |
LIST | Список имен пользователей системы, разделенных пробелами |
NAME | Содержит имена зарегистрированных в настоящий момент пользователей |
PERSON | Отдельное имя из списка имен в переменной LIST |
Зачем нам нужен командный файл watch?
В течение рабочего дня множество людей входят в систему и выходят из нее. Иногда единственный способ связаться с человеком - через машину. Нам нужно средство, которое автоматически сообщит нам, что нужное лицо на связи.
Что делает watch?
Подразумевается, что watch является фоновой задачей, которая постоянно следит за тем, кто зарегистрировался. Когда лицо или лица, предварительно вами отмеченные, регистрируются в системе, на ваш экран выводится сообщение о том, что они на связи.
Количество имен, за которыми вы можете следить, не ограничено. Общий перечень имен - это объединение имен, указанных в командной строке, и списка системных пользователей, т.е. пользователя root и всех сопровождающих его администраторов. В командном файле watch список системных имен ВСЕГДА включен. Это вызвано тем, что это важные пользователи и вы хотите всегда знать об их входе в систему и выходе из нее. Это отличает watch от uchk, поскольку последний требует указания опции -a для включения списка с системными именами.
Хотя мы устанавливаем watch с точки зрения пользователя, который желает следить за администраторами и другими пользователями, администраторы тоже могут иметь список "критических пользователей" или предполагаемых нарушителей защиты информации и использовать watch для получения сигнала о регистрации таких пользователей.
За кем бы мы ни следили, мы сталкиваемся с одной проблемой. После регистрации в системе указанного лица сообщение об этом поступает на ваш экран, не обращая внимания на то, чем вы в данное время занимались, что не очень приятно. Единственный способ остановить вывод сообщения - аварийно завершить watch командой kill. Это легко сделать путем помещения идентификатора этого процесса в файл $HOME/.watch. Затем этот номер может быть использован в операторе kill для остановки выполнения командного файла. Для того чтобы попроще избавиться от watch, возможность аварийного завершения оформлена в виде опции -k данного командного файла.
ПРИМЕРЫ
1. $ LIST="root bin" watch daemon
Если переменная LIST не была инициализирована внутри самого командного файла watch, то мы можем инициализировать ее на командном уровне shell'а, а затем вызвать watch. Список пользователей для watch будет такой: root, bin и daemon по порядку, поскольку watch добавляет имена, указанные в ее командной строке к именам, имеющимся в переменной LIST. Такой способ указания имен более гибок, чем жесткое программирование части списка в тексте командного файла watch, но он требует помнить больше информации и больше нужно набирать на клавиатуре.
2. echo "Watch (y/n): \c" read ANS if [ "$ANS" = "y" ] then watch fi
Это фрагмент, который вы можете вставить в ваш .profile. Когда вы регистрируетесь, вам автоматически предлагается запустить watch. Если вы ответите "y", watch станет фоновой задачей и запустится по умолчанию (просматривая пользователей согласно списку в переменной LIST). Если будет введен любой символ, отличный от y, watch не запустится.
ПОЯСНЕНИЯ
Строки 4-9 выполняют проверку на наличие ошибок в опциях командной строки. Для первого позиционного параметра проверяется, что он имеет тире и не является единственной допустимой опцией "-k". Если результатом проверки является истина, печатается сообщение об ошибке и командный файл завершается.
Строки 11-16 проверяют, что первый позиционный параметр - это -k. Если это так, значит пользователь хочет уничтожить уже запущенный процесс watch. В этом случае выводится сообщение, указывающее идентификатор процесса, который будет уничтожен, и выполнение продолжается. В строке 12 мы смотрим, существует ли в нашем регистрационном каталоге файл с именем .watch. Если нет, то это означает, что предыдущий экземпляр watch предположительно уже уничтожен и нет необходимости пытаться сделать это снова, поэтому происходит переход к оставшейся части программы и выполнение watch происходит так, как будто опция "-k" не была указана.
Если же файл .watch имеется, то в строке 14 используется команда kill для уничтожения фонового процесса watch. Напомним, что при использовании опции -k мы подразумеваем, что watch был вызван ранее, поэтому файл $HOME/.watch имеет идентификационный номер самого процесса watch. В строке 15 удаляется временный файл watch и в строке 16 происходит выход из программы. Теперь командный файл watch более не выполняется как фоновый.
Строка 20 выполняется, если опция -k не была указана или если нет файла .watch. (Последнее может произойти, если пользователь пытается уничтожить процесс, забыв, что он уже был уничтожен.) Если опция -k не была указана, мы можем считать, что watch был вызван, чтобы стать фоновым процессом и выполнять свою работу. Для того чтобы сделать это, текущий процесс отображает свой идентификатор процесса в файл .watch. Этот файл остается в вашем регистрационном каталоге до тех пор, пока он не будет удален вручную или же изменен путем повторного запуска watch.
В строке 22 инициализируется переменная LIST. Ее значением является символьная строка с именами, разделенными пробелами. Вам нужно вручную отредактировать переменную LIST перед запуском в вашей системе. Просто уберите ее нынешнее содержимое и вставьте туда имена администраторов вашей системы. Если в командной строке будут указаны дополнительные имена пользователей, они будут добавлены в переменную LIST посредством символов расширения параметров $*. Тем самым переменная LIST станет основным списком всех имен пользователей, за которыми будет вестись наблюдение.
Строки 24-36 выполняют цикл постоянного наблюдения. В начале каждой итерации с помощью команды who создается список имен пользователей, который передается циклу for в строке 26. Цикл for использует командную подстановку для получения списка слов, образованного из первого поля команды who. Каждое зарегистрированное имя сравнивается со списком предварительно определенных имен, за которыми мы наблюдаем. Обратите внимание, что внешний цикл while сам себя помещает на выполнение в фоновом режиме. Это означает, что вам нет необходимости вводить это с клавиатуры.
Строки 29-33 управляют внутренним циклом, который проходит по именам, содержащимся в нашем основном списке, и сравнивает их с именами, полученными от команды who. Когда имя, полученное от команды who (имя зарегистрированного пользователя) совпадает с именем в нашем списке, на экран выводится сообщение о том, что данное лицо зарегистрировалось.
После того как все имена проверены, командный файл watch приостанавливается на 10 секунд (строка 35). Когда он снова пробуждается, выполняется следующая итерация вечного цикла while. Все зарегистрированные имена вновь сравниваются со списком. Это будет продолжаться до тех пор, пока вы не прекратите выполнение watch. Как отмечалось ранее, watch можно легко уничтожить с помощью опции -k или же вручную путем ввода команды "kill `cat $HOME/.watch`".
МОДИФИКАЦИИ
Watch выполняет довольно мало работы и использует какую-то часть времени центрального процессора. Вы можете поэкспериментировать с увеличением интервала паузы (sleep), чтобы watch запускался не так часто. Большинство пользователей находятся в системе по крайней мере минуту, поэтому вы можете попробовать значение sleep(60). Вы по-прежнему можете обнаружить регистрацию всех интересующих вас пользователей?
ИМЯ: whox
whox Команда who с дополнительными возможностями
НАЗНАЧЕНИЕ
Предоставляет много дополнений к выходу команды who и позволяет применять данные who для других приложений.
ФОРМАТ ВЫЗОВА
whox [-f] [-n] [-m] [-p] [-t] [-w] [-x]
где
-f указывает каждого зарегистрированного пользователя
-n сортирует выход команды who по именам
-m передает почту каждому пользователю
-p выводит информацию о паролях пользователей
-t сортирует выход команды who по времени (умолчание)
-w показывает возможность записи на зарегистрированные терминальные устройства
-x дополнительная информация о регистрационном каталоге и паролях
ПРИМЕР ВЫЗОВА
whox -w
Показывает права доступа к файлу (возможность чтения и записи) для каждого зарегистрированного терминального устройства
ТЕКСТ ПРОГРАММЫ
1 : 2 # @(#) whox v1.0 Who with expanded options Author: Russ Sage 2а Команда who с дополнительными опциями 4 XTRA="no" 5 SORT="sort -b +2" 6 DISPLAY="norm" 8 CUT1="cut -d' ' -f1" 9 CUT5="cut -d: -f5" 10 CUT6="cut -d: -f6" 12 for ARG in $@ 13 do 14 case $ARG in 15 -f) DISPLAY="finger" 16 COMMAND="finger \$NAME; echo";; 17 -n) SORT="sort";; 18 -m) DISPLAY="mail";; 19 -p) DISPLAY="pass" 20 COMMAND="grep \"^\$NAME:\" /etc/passwd";; 21 -t) SORT="sort -b +2";; 22 -w) DISPLAY="write";; 23 -x) XTRA="yes";; 24 *) echo "whox: invalid option $ARG" 25 echo "usage: whox [-f] [-n] [-m] [-p] [-t] [-w] [-x]" 26 echo " -f finger users" 27 echo " -n sort by name" 28 echo " -m mail to each user" 29 echo " -p password info on users" 30 echo " -t sort by time (default)" 31 echo " -w show writeability of devices" 32 echo " -x extra home dir and gcos info" 33 exit 1;; 34 esac 35 done 37 if [ "$XTRA" = "yes" ] 38 then EXTRA="| while read LINE; do \ 39 NAME=\`echo \$LINE | cut -d' ' -f1\`;\ 40 ENTRY=\`grep \"^\$NAME:\" /etc/passwd\`;\ 41 echo \"\$LINE\t\`echo \$ENTRY|\$CUT6\`\t\`echo \$ENTRY|\$CUT5\` \";done" 42 else EXTRA="" 43 fi 45 case $DISPLAY in 46 norm) eval "who | $SORT $EXTRA";; 47 finger|pass) for NAME in `who | $SORT | cut -d' ' -f1` 48 do 49 eval $COMMAND 50 done;; 51 mail) who | cut -d' ' -f1 | while read NAME 52 do 53 echo "mail to $NAME (y/n): \c" 54 KB=`line < /dev/tty` 55 if [ "$KB" = "y" ] 56 then mail $NAME < /dev/tty 57 fi 58 done;; 59 write) ls -il `who | sed "s/...........\(.......\).* /\/dev\/\1/"`;; 60 esac
ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ
ARG | Аргументы командной строки |
COMMAND | Команда, которую следует выполнить при использовании команды who со списком имен |
CUT1 | Содержит синтаксис для выделения первого поля строки |
CUT5 | Содержит синтаксис для выделения пятого поля строки |
CUT6 | Содержит синтаксис для выделения шестого поля строки |
DISPLAY | Определяет, какой режим отображения использовать |
ENTRY | Запись в файле паролей для указанного пользователя |
EXTRA | Данная переменная содержит полный цикл shell-команд, хранимых в виде одной строки |
KB | Входные данные от клавиатуры, полученные в цикле |
NAME | Содержит в каждый данный момент времени одно имя из списка всех регистрационных имен |
SORT | Содержит выполняемый тип сортировки |
XTRA | Флаг, определяющий, должны ли быть активизированы дополнительные опции |
Зачем нам нужен командный файл whox?
Как уже ранее обсуждалось в других местах этой книги, система UNIX стремится обеспечить минимум возможностей в любой заданной области интересов. Это не значит, что UNIX плохая система. Наоборот, она делает гораздо больше, чем большинство других операционных систем. Но очень часто мы хотим сделать немного больше того, что нам предоставляет базовая система.
Получение информации о том, кто использует машину, может быть применено для многих целей. Основная информация предоставляется командой who, но она может быть не в том виде, который вам нужен для конкретной цели. В любом случае, все, что делает команда who - дает вам моментальную информацию о том, кто зарегистрировался. Нам необходим некоторый способ автоматического доступа к этому списку зарегистрированных имен и использования его для наблюдения, общения или других целей.
Что делает whox?
Whox - это инструментальное средство, расширяющее возможности команды who. Оно не только может переупорядочить в соответствии с вашими требованиями список, полученный от команды who, но также может указать каждого зарегистрированного пользователя, передать почтовое сообщение любому зарегистрированному пользователю, просмотреть парольную информацию всех зарегистрированных пользователей и показать информацию, взятую из индексного дескриптора файла, соответствующего терминальному устройству каждого зарегистрированного пользователя.
По умолчанию действие whox заключается в печати обычного выхода команды who в порядке времени регистрации от более раннего до более позднего. Опция -x добавляет к этому списку информацию из регистрационного каталога и поле комментария из файла паролей. Если эта опция -x кажется вам знакомой, то так оно и есть, поскольку это то же самое, что и команда info, представленная ранее.
Whox имеет четыре различных режима отображения. Первый это формат обычного выхода команды who. Whox позволяет вам сортировать его двумя разными способами. Опция -n сортирует по именам, а опция -t (которую не нужно указывать, поскольку она используется по умолчанию) сортирует по времени регистрации.
Второй режим отображения состоит из режимов указания и паролей, включаемых опциями -f и -p. Основное отличие от первого режима заключается в том, что выход команды who не печатается, а используется для генерации списка имен пользователей, который применяется для других целей. Мы указываем каждого пользователя или печатаем парольную запись каждого пользователя. Выполняемая команда хранится в переменной, поэтому мы можем иметь общий цикл, использующий особым образом переменные. (Команда finger имеется в системе Berkeley UNIX и в некоторых других, но не во всех реализациях. Посмотрите руководство, чтобы выяснить, что выводится на экран по этой команде.)
Третий режим - это почтовый режим, в котором вы имеете возможность посылки почтового сообщения каждому зарегистрированному пользователю. Вам задается вопрос о том, действительно ли вы хотите сделать это. Больше от вас ничего не требуется. Этот режим выбирает опция -m.
Последний режим - это режим записи на терминал. Режим записи (опция -w) показывает информацию о файле терминала для каждого зарегистрированного терминального устройства. Эта информация полезна, если вы хотите использовать команду UNIX'а write. Посмотрев на права доступа к файлу устройства пользователя, вы можете сказать, имеется ли у вас возможность записать текст на его экран. Некоторые пользователи, которые не хотят, чтобы их прерывали, закрывают право записи на их терминал, выполняя команду "mesg n". Вопрос о праве записи касается любого способа посылки текста в другой файл, а не только с использованием команды write. Право записи также защищает от таких вещей, как "echo hello > /dev/tty00".
Способ обработки аргументов в командной строке приводит к несколько странному обращению с данной утилитой. Каждый аргумент проверяется по порядку и устанавливает внутренние флаги. Если вы поставите в конце списка какую-либо опцию, меняющую флаг, установленный одной из предыдущих опций, то вы получите действие последней опции. (Другими словами, некоторые опции взаимно исключают друг друга. Лучший способ изучить это - внимательно прочитать исходный текст командного файла и выполнить несколько экспериментов с различными наборами опций.)
Например, мы хотим указать каждого пользователя. Мы используем опцию -f. Опция -f устанавливает в качестве режима отображения режим указания. Если мы поместим опцию -w справа от -f, как в команде "whox -f -w", то установится режим записи на терминал. Команда whox будет считать, что вы вообще не указывали опцию -f. На самом деле это не представляет большую проблему, если вы знаете, что делает каждая опция. Случайно смешивая их в одной команде, вы можете получить несколько странные выходные данные.
ПРИМЕРЫ
1. $ sh -x whox -x
Запуск интерпретатора shell в отладочном режиме выполнения, подача ему командного файла whox в качестве данных, передача опции -x для whox. Отладочный режим показывает присвоение значений переменным и вызовы команд. (Мы видели это ранее.) 2. $ whox -n -x
Печать выходных данных команды who, отсортированных по именам и выдача дополнительных данных.
ПОЯСНЕНИЯ
В строках 4-10 выполняется инициализация переменных. Переменная XTRA устанавливается в значение "no". Эта переменная используется для построения командной строки при использовании опции -x. Установка XTRA в значение "no" означает, что действие по умолчанию - не получать дополнительную информацию.
Умолчанием для сортировки является сортировка по времени регистрации. Это указывается сортировкой по колонке, стоящей после второй (+2) и игнорированием ведущих пробелов (-b) в строке 5. Такой же синтаксис применен ниже с опцией -t. Опция -t здесь лишняя, но она делает более понятной командную строку. Опция -n также изменяет синтаксис сортировки, чтобы просматривать первую колонку, которая является списком имен.
Строка 6 инициализирует обычный режим отображения, которым является печать стандартного выхода команды who. Если присутствуют другие опции, соответственно изменяется переменная DISPLAY.
Строки 8,9 и 10 инициализируют некоторые переменные для команд вырезки, что делает более компактными последующие команды. Если некоторые командные строки оказываются слишком длинными, вы можете поместить нужный текст в переменные и подставить их при необходимости. Переменная CUT1 выглядит так, как будто она должна работать, но она не работает в моей системе. Почему она не работает, объясняется ниже. Просто запомните, что эта строка никогда не используется в данной программе. Мы пока оставляем ее, чтобы поговорить о ней позже. Вы можете убрать ее, если хотите.
Строки 12-35 обрабатывают аргументы командной строки. Цикл for подставляет в переменную ARG каждый параметр по порядку и выполняет оператор case по значению ARG.
Если опцией является -f, строка 15 изменяет переменную DISPLAY в режим указания. Она также подставляет в переменную COMMAND команду finger, которая выполнится в следующем цикле. Причина, по которой нам нужно иметь переменную, содержащую команду, заключается в том, что данный цикл является общим циклом, применяемым для двух разных целей. Для того чтобы один и тот же цикл справился с двумя различными задачами, мы помещаем эти задачи в переменную и выполняем эту переменную.
Это значительно сокращает общее количество текста, хотя привносит некоторую дополнительную работу. Обратите внимание в строке 16, что символ $ экранирован в операторе присваивания. Это необходимо, ведь мы хотим, чтобы переменная COMMAND содержала символьную строку $NAME, а не значение, которое имеет переменная NAME после присваивания. Значение NAME расшифровывается в цикле во время его выполнения.
Строка 17 обрабатывает опцию -n. Все, что здесь требуется - изменить способ сортировки выхода команды who, чтобы отразить порядок по именам. Поскольку имя находится в первой колонке выхода команды who, а команда sort сортирует по умолчанию по первой колонке, то используется команда sort без опций.
Строка 18 обрабатывает опцию -m для передачи почтовых сообщений. Здесь мы должны изменить режим отображения на почтовый. Все, что нужно для этого, находится в цикле mail, и не требуется инициализировать никакие переменные.
Строки 19 и 20 справляются с опцией -p. Опция паролей изменяет режим отображения на парольный режим и устанавливает команду, которую мы вызываем, находясь в общем цикле. В данном случае мы используем команду grep для получения парольной записи из файла /etc/passwd. Обратите внимание, что в строке 20 мы используем внутренние двойные кавычки. Для этого мы вынуждены экранировать их символами обратной косой черты. Напомним, что обратная косая черта используется для отмены специального значения особых shell-символов.
Строка 21 управляет опцией -t. Как уже упоминалось ранее, опция -t в действительности не требуется в данной программе. Поскольку она является умолчанием, требуемые для нее действия уже были предприняты в начале программы - была выполнена точно такая же инициализация. Синтаксис команды sort точно такой же, как и в строке 5.
Строка 22 обрабатывает опцию -w для показа возможности записи в файлы терминалов. Единственное, что нужно здесь сделать - изменить режим работы терминала.
Строка 23 управляет опцией -x. Поскольку для получения дополнительной информации требуется довольно сложная инициализация, то мы только устанавливаем в этом месте флаг XTRA, показывающий, что мы хотим выполнить эту инициализацию позже.
Строка 24 - это улавливатель для обработки ошибок. Символ * соответствует любому символу, который не был распознан ранее. Печатается сообщение об ошибке и синтаксическая подсказка, и whox завершается.
Строки 37-43 устанавливают переменные, используемые в опции дополнительной информации. Строка 37 проверяет, установлена ли переменная XTRA в состояние "yes", что имеет место только тогда, когда в командной строке имелась опция -x. Если это так, то в переменную EXTRA заносится много всяких вещей, которые мы рассмотрим позже. В противном случае в переменную EXTRA заносится пустая строка, так что она никак не проявляется на стадии фактического выполнения.
Переменная EXTRA здесь очень важна и на самом деле делает небольшой фокус. Мы собираемся поместить в переменную некоторый код, требуемый для обработки опции -x. Поскольку дополнительная информация, которую мы хотим получить, требует предварительно некоторой обработки, то мы помещаем в переменную некоторый командный текст. Как только эта переменная начинает выполняться, выполняется и этот командный текст. Это похоже на макрокоманду, только ее текст находится фактически в исполняемой программе.
Строки 38-41 вставлены внутрь переменной EXTRA. Это сделано путем взятия в двойные кавычки всех четырех строк. Все специальные символы, которые должны быть частью данных в этой переменной, должны быть экранированы символами обратной косой черты. В строке 38 в переменную EXTRA заносится символ конвейера (|) и начало цикла while. В конце строки 38 имеется символ косой черты, указывающий интерпретатору shell, что присваивание продолжается после символа конца строки (возврата каретки или перевода строки).
Строка 39 присваивает переменной NAME значение поля, вырезанного из данных, читаемых в цикле while. Напомним, что весь данный оператор помещается внутрь переменной EXTRA. Когда я выше упоминал, что в строке с переменной CUT1 есть проблемы, то как одно из таких проблемных мест я имел в виду именно это. Когда я попытался использовать переменную CUT1 в этом операторе вместо указания команды cut, shell не смог правильно распознать этот оператор. Одинарные кавычки, отмечающие символ-разделитель для вырезки, не были распознаны. В результате команда cut считала, что символом-разделителем является символ ' и после этого аварийно завершалась, поскольку второй символ ' представлял собой недопустимое описание списка для опции -f. Строка с опцией -f шла позже, но команда cut этого никогда не узнавала, поскольку аварийно завершалась до этого. Когда я заменил переменную CUT1 просто командой cut, эта проблема исчезла.
Давайте рассмотрим, как я отлаживал эту часть. Я использовал shell с опцией -x, поэтому я мог следить за тем, что происходит. Как вы можете видеть, когда переменная CUT1 была инициализирована, одинарные кавычки находились все еще в операторе, но когда выполнялась настоящая команда cut, одинарные кавычки уходили при синтаксическом расширении. Для генерации такого списка данных я выполнил следующий вызов: sh -x whox -x. Вот что я увидел:
XTRA=no SORT=sort -b +2 DISPLAY=norm CUT1=cut -d' ' -f1 <- Одинарные кавычки все еще здесь. Основная проблема. CUT5=cut -d: -f5 CUT6=cut -d: -f6 XTRA=yes + who + read LINE + sort -b +2 + echo russ console Jun 20 14:11 + cut -d -f1 <- Теперь выполняется правильно. Кавычек нет.
Это сокращенная распечатка. Она показывает, что когда выполнялась команда cut, она не имела одинарных кавычек. Когда же запускалась переменная CUT1, она имела одинарные кавычки. Я не мог представить, как избавиться от кавычек, поэтому я просто вставил вызов самой команды cut обратно на это место. Может быть какой-нибудь молодой растущий мастер сможет себе это представить.
Во всяком случае, вы можете видеть полезность отладки.
Цикл, выполняющий такое же присваивание, имеет такой вид при обычном стиле записи на языке shell:
| while read LINE do NAME=`echo $LINE | cut -d' ' -f1` ENTRY=`grep "^$NAME:" /etc/passwd` echo "$LINE\t\`echo $ENTRY|$CUT6\`\t\`echo $ENTRY|$CUT5\`\" done
Для того чтобы поместить такой же цикл в переменную, мы должны экранировать в этом тексте все специальные символы.
Строки 45-60 - это оператор case, который реализует различные режимы отображения. Строка 46 выполняет обычный режим отображения команды who. Поскольку в обычном режиме имеется возможность использовать переменную EXTRA, нам необходимо произвести повторный разбор командной строки командой eval, чтобы эта переменная приняла свое истинное значение во время исполнения. Обратите внимание, что в команде eval имеются кавычки, заключающие всю командную строку. Это необходимо потому, что вся строка является одним набором входных данных для команды eval. Без кавычек команда eval не работала бы. Переменная EXTRA не подвергается повторному разбору.
Строки 47-50 управляют режимами указания пользователя и выдачи информации из файла паролей. Оба эти режима используют один и тот же цикл. Цикл for использован для установки переменной NAME в значение первого поля каждой строки, полученной от команды who. Для каждого имени, вырезанного из результата работы команды who, выполняется повторный синтаксический разбор командой eval переменной COMMAND (которая была установлена в операторе case, выполнявшем разбор аргументов). Тем самым повторно анализируются и выполняются команды, находящиеся в переменной COMMAND. Для режима указания пользователя переменная COMMAND содержит команду finger, а для режима паролей в COMMAND хранится команда grep.
Строки 51-58 похожи на режим указания пользователя. Этот цикл тоже требует имена от команды who, но вместо использования оператора for мы используем метод прямой пересылки по конвейеру. Результат работы команды who по конвейеру передается команде cut (переменная CUT1 и здесь бы не работала), которая по конвейеру передает данные в цикл чтения while. Обратите внимание, что в этом месте нет никакой сортировки. По умолчанию результат команды who выводится в порядке номеров терминальных устройств. Я не думаю, однако, что порядок вывода этих данных имеет большое значение.
Для каждого имени пользователя выводится запрос о том, хотите ли вы передать ему почтовое сообщение. При чтении ответа в строке 54 должна быть использована команда UNIX'а line. Почему? Потому что весь цикл использует оператор read для чтения имен. Оператор read читает только со стандартного ввода, который в данном случае привязан к конвейеру. Для получения входных данных с клавиатуры мы должны использовать команду line, которая получает их из файла /dev/tty. Это распространенный способ чтения данных с клавиатуры из переадресованного цикла.
Строка 55 проверяет, является ли ответом символ y. Если да, вызывается команда UNIX'а mail, и снова ввод переадресовывается из файла /dev/tty (поскольку строки почтового сообщения мы должны вводить с клавиатуры.) В данном случае мы фактически переадресовываем стандартный ввод для вызова подчиненного shell-процесса, выполняющего команду mail. Без выполнения переадресации команда mail читает из файла /dev/null, что нарушает выполнение всего цикла whox.
Строка 59 управляет режимом показа возможности записи на терминал. Цель здесь такова - использовать одну команду ls и, применяя подчиненный процесс, извлечь файлы терминальных устройств из выходных данных команды who. Эти файлы являются вторым полем результата команды who. Сначала запускается команда who, которая по конвейеру передает свои данные команде sed.
Затем sed использует команду подстановки для отбрасывания всего, кроме того, что ограничено символами \( и \). Последующая часть команды подстановки ссылается на этот ограниченный участок с помощью обозначения \1. Используя символ . как соответствующий любому символу распечатки, мы должны всего лишь посчитать столбцы, которые нам нужно вырезать. Кроме того, имена устройств в команде who не имеют префикса /dev/, который нам необходим. Команда sed вставляет его перед текстом, вырезанным из команды who. В результате команде ls дается список полных маршрутных имен ко всем файлам устройств зарегистрированных пользователей. Затем это выводится на экран.
ОСВЕДОМЛЕННОСТЬ ПОЛЬЗОВАТЕЛЯ И ЗАЩИТА ЛИЧНОЙ ИНФОРМАЦИИ
ВВЕДЕНИЕ
Мы уже разработали целый ряд инструментальных средств, которые помогают нам более эффективно управлять нашей личной информацией. Однако мы работаем не в вакууме. Каждый из нас работает с другими людьми и обычно использует систему вместе с другими пользователями. По нескольким причинам важно иметь представление о том, что делают в системе другие люди. Во-первых, у нас может возникнуть необходимость наметить работу, интенсивно использующую процессор, на то время, когда загрузка системы невелика. Изучение типичных вариантов загрузки системы может помочь нам спланировать такую работу и сотрудничать с другими пользователями со взаимной выгодой.
Нам также может понадобиться знать, находится ли кто-то на своем рабочем месте, чтобы мы могли поговорить с ним, или знать, что кто-то очень занят и был бы недоволен, если его прервут.
Наконец, существуют вопросы защиты информации, которым мы уделим больше внимания в последующих главах. Основой защиты информации является осведомленность о том, что делают другие - и, возможно, о том, что они не должны делать.
Поэтому мы здесь представим две группы инструментальных средств. Средства ОСВЕДОМЛЕННОСТИ ПОЛЬЗОВАТЕЛЯ помогают нам следить за тем, что делают другие, и тем самым упростить связь с ними. Средства ЛИЧНОЙ ЗАЩИТЫ важны для защиты нашей учетной информации и наших данных и для получения информации о наших собственных действиях.
Первая группа рассматриваемых здесь средств, относится к "осведомленности пользователя". Этими средствами являются activ, info, uchk, watch и whox.
Командный файл activ представляет собой модификацию команды who, показывающую имя каждого пользователя, номер терминала и сведения о том, насколько давно данное лицо вводило что= либо с клавиатуры. По этой информации мы можем сказать, кто еще работает в системе, активно ли они работают и, если они используют закрепленные линии, то где они находятся.
Следующее средство - info. Оно предоставляет возможность получить информацию о паролях всех указанных в списке пользователей. С помощью этой утилиты вы можете получить общую информацию об этих пользователях и увидеть, где размещаются их регистрационные каталоги.
Еще одно информативное средство - uchk. Этот командный файл полезен для выяснения того, что делает указанный пользователь в настоящее время. По этой информации вы можете сказать, простаивают ли они или же делают что-то важное и их нельзя прерывать.
Следующее средство - watch. Эта программа похожа на "демон", который выполняется в фоновом режиме и постоянно следит за регистрацией определенных пользователей. Когда нужное вам лицо регистрируется, на ваш терминал посылается сообщение о том, что данный пользователь вошел в систему. Таким образом, вы можете продолжать работу вместо того, чтобы постоянно прерывать ее.
Последняя утилита в данном разделе - это whox. Whox дает вам исчерпывающие сведения обо всех пользователях, зарегистрировавшихся на машине. Вы можете получить информацию, похожую на результат команды who, которая отсортирована различным образом, отобразить информацию о терминалах или даже передать почтовое сообщение любому пользователю.
ЗАЩИТА ЛИЧНОЙ ИНФОРМАЦИИ
acme | показать учетную информацию |
inuse | запретить использование терминала |
lock | блокировать и разблокировать файлы |
ИМЯ: acme
acme Отображение учетной информации обо мне
НАЗНАЧЕНИЕ
Генерирует опции, необходимые для вывода на экран информации обо мне, которая хранится в учетном файле.
ФОРМАТ ВЫЗОВА
acme [-l] [-u]
ПРИМЕР ВЫЗОВА
acme -u Выводит всю учетную информацию о пользователе с именем $LOGNAME
ТЕКСТ ПРОГРАММЫ
1 : 2 # @(#) acme v1.0 Give accounting info on me Author: Russ Sage 2а Дать учетную информацию обо мне 4 if [ "$1" != "-l" -a "$1" != "-u" ] 5 then echo "usage: acme [-l] [-u]" >&2 6 echo " -l for ttyline" >&2 7 echo " -u for user name" >&2 8 exit 0 9 fi 11 OPT="" 12 for ARG in $* 13 do 14 case $ARG in 15 -l) OPT="$OPT -l `basename \`tty\``";; 16 -u) OPT="$OPT -u $LOGNAME";; 17 *) OPT="$OPT $ARG";; 18 esac 19 done 21 echo "acctcom $OPT" 22 acctcom $OPT
ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ
ARG | Каждое значение, указанное в командной строке |
LOGNAME | Переменная среды, содержащая мое регистрационное имя |
OPT | Объединенный список всех опций и их аргументов |
Зачем нам нужен командный файл acme?
Большинство больших систем UNIX запускают стандартное программное обеспечение для сбора учетной информации об использовании системы. Результаты учетных транзакций передаются обычно в файл /usr/adm/pacct. Фактически сбор учетной информации выполняется ядром системы. Каждый раз при завершении процесса программы сбора учетной информации в ядре производят одну запись. Переключателем, который включает и выключает эту операцию, является acct(2). Команды пользовательского уровня также взаимодействуют с системным вызовом (acct on(1M)) и печатают результаты сбора учетной информации (acctcom(1)).
Теперь, когда мы знаем, где находятся учетные записи и как они туда попадают, нам нужно напечатать эту информацию. Acctcom может печатать таблицы с информацией, но вам необходимо знать, какой использовать индекс. Просмотр может производится по номеру терминальной линии (это полезно, если идентификатор процесса был изменен командой setuid), по имени пользователя, по группе, по времени и т.д. Я наиболее часто использую опции поиска информации по номеру линии терминала и по имени пользователя. С их помощью вы можете получить список всех основных данных, имеющих отношение к вам. Когда вы вызываете acctcom с этими опциями, вам необходимо указать дополнительную информацию, такую как имя вашего терминала и ваше пользовательское имя. Было бы хорошо, если бы мы могли уменьшить количество нажатий на клавиши и объем вычислений, требуемых для получения информации. Для этого и предназначен acme.
Что делает acme?
Acme - это интерфейсный процессор для команды acctcom(1). Он служит для выдачи информации, которая требуется согласно указанным опциям. Вы должны только дать командному файлу acme опции в сокращенном виде, а все остальное он сделает сам. Если acme вызывается без аргументов, программа acctcom выведет по умолчанию все записи.
Команда acctcom имеет много опций. В действительности мы используем одну или две, но зато используем их часто. Двумя опциями, которые понимает acme, являются -l и -u. Когда указана опция -l, acme получает имя вашего терминала и помещает его в командную строку. Если указана опция -u, acme получает ваше пользовательское имя и тоже вставляет его в командную строку. Время от времени используются другие опции для бавления специфической информации или небольшого изменения выходного формата. Для того чтобы была возможность использовать другие опции команды acctcom, acme включает в командную строку, формируемую для fcctcom, любые дополнительные корректные опции acctcom, переданные в командной строке для acme. Таким образом, acme поддерживает базовые возможности, а кроме того позволяет вам подгонять команду под ваш вкус.
Перед тем, как начнется выполнение команды acctcom, на экран выводится расширенный вид командной строки, так что вы можете видеть командную строку, сгенерированную acme. Без этого может получиться путаница, поскольку вы не будете знать, что собирается делать программа.
ПРИМЕРЫ
1. $ acme
Выдача ВСЕХ моих учетных данных. Это последовательный список всех команд, которые были запущены начиная с момента загрузки по настоящее время. Счастливого чтения!
2. $ acme -u -b
Печать в обратном порядке всех учетных записей с моим пользовательским именем. Обратный порядок означает - от самой последней из предыдущих команд до моей первой команды.
3. $ acme -l
Вывод всех учетных записей для терминальной линии, которую я сейчас занимаю. Сюда могут быть включены фоновые процессы, оставшиеся в системе от предыдущих пользователей моей терминальной линии или даже процессы, имеющие другие идентификационные номера пользователей (из-за программ типа setuid, которые я мог запустить), которые запущены с этой же терминальной линии.
ПОЯСНЕНИЯ
В строках 4-9 выполняется проверка на наличие ошибок. Если первый позиционный параметр не -l и не -u, то это ошибка. Выводится сообщение об этом и программа завершается.
В строке 11 переменная OPT инициализируется пустой строкой. Эта переменная содержит все дополнительные опции acctcom и их аргументы.
Строки 12-19 представляют собой цикл for, который повторяется по всем позиционным параметрам. Каждый аргумент сверяется в операторе case с допустимыми опциями. Если опцией является -l (строка 15), в переменную OPT заносится то значение, которое она уже имеет, опция -l и добавляется имя терминального устройства, полученное от команды UNIX'а tty. Команда tty выводит и префикс /dev, который не нужен. Для того чтобы избавиться от этого префикса, мы берем из этой символьной строки только основное имя.
Если указана опция -u, в переменную OPT добавляется -u и наше регистрационное имя. Если аргументом являются любые другие данные, то они просто добавляются в переменную OPT. Поступая таким образом, мы можем передать в командной строке acme другие опции команде acctcom. Обратите внимание, что здесь не выполняется проверка на ошибки в аргументах командной строки. Вы можете ввести неверное значение, которое нарушит работу команды acctcom. Однако это та цена, которую мы вынуждены платить за гибкость при передаче аргументов в командной строке, иначе нам придется значительно увеличивать текст командного файла.
После того обработки всех опций строка 21 выводит на экран командную строку, которая должна быть выполнена, так что мы знаем, что мы задавали. В строке 22 выполняется сама команда acctcom. Выходной результат соответствует описанию acctcom(1).
ИМЯ: inuse
inuse Запретить использование терминала
НАЗНАЧЕНИЕ
Блокирует ваш терминал путем перевода в состояние занятости. Если кто-либо попытается вторгнуться, вы это заметите.
ФОРМАТ ВЫЗОВА
inuse
ПРИМЕРЫ ВЫЗОВА
inuse Перевод терминала в состояние занятости
mypasswd Вводится мой пароль, но не отображается на экран
ТЕКСТ ПРОГРАММЫ
1 : 2 # @(#) inuse v1.0 Disable terminal and alert if used Author: Russ Sage 2а Запретить использование терминала и сообщить о попытке использования 4 trap "echo you\'re BUSTED!!; stty echo; kill $$" 2 15 6 PATH=/bin:/usr/bin 7 SECRET="secret" 9 stty -echo 10 echo "Lock string: \c" 11 read BUF1 12 echo 14 while : 15 do 16 BUF2=`line < /dev/tty` 17 if [ "$BUF2" = "$BUF1" ] 18 then break 19 elif [ "$BUF2" = "$SECRET" ] 20 then break 21 fi 22 echo "^G\c" 23 done 24 stty echo
ОПИСАНИЕ
Зачем нам нужен командный файл inuse?
Рабочий день всегда загружен: много людей, еще больше бумаг, всякие поручения и так далее и так далее. Когда вы покидаете свое рабочее место, что вы собираетесь делать со своим зарегистрированным терминалом? Каждый раз входить в систему и выходить из нее слишком долго, но вы не хотите оставлять вашу работу открытой для всех. Вам необходима программа, которую вы можете запустить на время вашего отсутствия и которая не позволит другим людям использовать то, что вы делаете.
Конечно, не достаточно иметь некий процесс, который выполняется в фоновом режиме, создавая впечатление, что терминал используется. Мы должны перехватывать прерывания в случае, если кто-нибудь введет символы "прерывания" или "выхода" с вашей клавиатуры. Единственным способом разблокирования терминала должен быть ввод пароля или заранее определенного слова, которое в любом случае разблокирует его.
Что делает inuse?
Inuse переводит ваш терминал в режим вечной работы. Это означает, что терминал не отвечает на ваши запросы или запросы кого-то другого. Когда вы готовы разблокировать ваш терминал, введите секретный пароль или пароль, который вы придумали.
Когда вы первый раз вызываете inuse, у вас запрашивается пароль. Эхо-отображение на терминал отключено, поэтому пароль не выводится на экран. Для гарантии того, что никто не пытается переадресовать командному файлу inuse какой-либо файл данных, все операции чтения производятся непосредственно из файла терминального устройства /dev/tty, а не через файловый дескриптор стандартного ввода. Это позволяет защититься от попыток других пользователей вторгнуться в работу терминала путем посылки на стандартный ввод файла большого размера, содержащего разные слова.
После чтения вашего пароля inuse попадает в бесконечный цикл, который читает символы с клавиатуры и сравнивает полученные входные данные с двумя паролями. Каждый раз, когда кто-то вводит что-то некорректное, выдается звуковой сигнал. Когда же введен один из допустимых паролей, программа останавливается и терминал разблокируется.
Мы предлагаем две реализации inuse: командный файл интерпретатора shell, приведенный выше, и программу на языке Си. Они выполняют одну и ту же работу довольно похожими способами. Они демонстрируют, насколько похожи эти два подхода, а их небольшие отличия рассматриваются ниже. Сначала обсуждается командный файл, а затем Си-программа.
ПОЯСНЕНИЯ
Строка 4 инициализирует оператор trap. При активизации обработчика trap выполняются три команды.
Целью применения ловушки trap является реагирование на любую попытку прервать работу командного файла и прорваться на ваш терминал. Первая команда выдает предупреждение о том, что вы вторгаетесь. Вторая команда переключает терминал обратно в режим эхо-отображения, так что все, что будет впоследствии введено с клавиатуры, отобразится на экране. и последняя команда заставляет программу совершить самоубийство. Как мы увидим позже, это самоубийство является особым родом прекращения работы программы. Обращение к "самому себе" в операторе kill выполняется с использованием метасимволов $$, которые представляют собой идентификационный номер выполняющегося shell-процесса. Обработчик ловушек включается сигналами 2 и 15, которыми являются прерывание и программное завершение соответственно. Отметим, что сигнал выхода из программы (сигнал 3) здесь не используется. Причину этого мы объясним позже.
Строка 6 устанавливает маршрут, по которому может обращаться inuse. Тем самым inuse никогда не сможет быть "застигнут врасплох" кем-нибудь, кто проник незаметно (в "троянском коне"). Строка 7 инициализирует секретный пароль значением "secret", которое совсем не секретно. Вы можете изменить пароль на любое слово перед установкой командного файла в вашей системе. В правах доступа к файлу, в котором хранится текст данной shell-программы, вы должны запретить возможность чтения. В противном случае другие пользователи смогут увидеть секретное слово.
Строка 9 отключает эхо-отображение, строка 10 печатает запрос пароля, а строка 11 читает пароль, который вы вводите, и заносит его в переменную BUF1.
Строки 14-23 представляют собой вечный цикл while, который можно прервать только вводом правильного пароля. Строка 16 читает ввод с клавиатуры. При нажатии возврата каретки строка 17 проверяет, соответствует ли то, что введено с клавиатуры, паролю пользователя. Если нет, переменная BUF2 сравнивается с секретным паролем. Если какой-то из паролей совпадает, оператор break производит выход из цикла while, тем самым прекращая выполнение программы. Если введенные данные не соответствуют ни одному из паролей, то в строке 22 выдается звуковой сигнал и снова начинает выполняться оператор чтения клавиатуры.
Если пароль введен правильно, в строке 24 включается эхо-отображение на терминал и программа завершается. Если происходит прерывание, активизируется оператор trap. Данная операция подробно рассматривается ниже.
ПОДРОБНЕЕ О ЛОВУШКАХ
Нам нужно рассмотреть смысл клавиши выхода из программы. Она производит прерывание, похожее на все другие прерывания, но кроме того выводит дамп памяти для запущенного процесса. Мы оставляем клавишу выхода нетронутой оператором trap, поскольку она становится нашей последней надеждой на приостановление командного файла inuse. Когда ваш терминал заблокирован, он эхо-отображает вводимые с клавиатуры символы, но не реагирует на них. Тот, кто нажимает на клавиши, видит это и пытается выйти из ситуации, нажимая на клавишу прерывания (обычно это клавиша DEL). Когда он это делает, на экран выводится сообщение "you're busted", эхо-отображение снова включается и программа сама себя уничтожает (сигнал 15). Когда сигнал уничтожения принимается программой, этот сигнал ловится, печатается сообщение и программа снова сама себя уничтожает. Эта последовательность выполняется снова и снова, как в вечном цикле. Каждый раз, когда ловушка уничтожается и снова запускается, используется стек. Если все это будет выполняться достаточно долго, то весь стек заполнится записями об активизации и переполнится, аварийно завершая весь сеанс работы.
Если клавиша выхода будет нажата до активизации оператора trap, то программа завершится чисто. Если же клавиша выхода будет нажата после начала работы оператора trap, то произойдет выдача дампа памяти процесса и программа завершится. Это не совсем честный прием, но программирование на языке shell вынуждено быть именно таким, и это предупреждает вас о том, что что= то не в порядке.
Текущие значения клавиш для сигналов прерывания и выхода отображаются командой stty(1). Эти значения можно переустановить в любые по вашему желанию. У меня текущие установки такие:
speed 9600 baud; intr = DEL; quit = ^|; erase = ^h; kill = ^u; eof = ^d;
Набрав на клавиатуре "stty intr z", вы можете установить символ z в качестве сигнала прерывания ваших процессов, поэтому такое изменение клавиши прерывания и запуск бесконечного цикла представляет собой еще один способ защиты вашего сеанса работы. Поскольку вам потом нужно будет вернуть старое значение, вы должны запомнить то, что вы делали. Такой настройкой сигналов вы можете делать с вашим терминалом почти все, что хотите. Этот подход дает меньшую степень защиты, чем перехват прерываний, но может обеспечить вас минимальной защитой, не приводя к выдаче дампа памяти.
Теперь мы представляем версию на языке Си.
ТЕКСТ ПРОГРАММЫ inuse НА ЯЗЫКЕ СИ
1 char id[] = "@(#) inuse v1. 0 Disable terminal Author: Russ Sage"; 3 #include 4 #include 5 #include 7 #define SSIZ 7 8 #define BSIZ 512 9 #define BELL "\07" 10 #define LF "\n" 12 main() 13 { 14 register int fd, sig, n; 15 char secret[SSIZ]; 16 char buf1[BSIZ], buf2[BSIZ]; 17 struct sgttyb sav_tty, chg_tty; 19 secret[0] = 's'; 20 secret[1] = 'e'; 21 secret[2] = 'c'; 22 secret[3] = 'r'; 23 secret[4] = 'e'; 24 secret[5] = 't'; 25 secret[6] = '\n'; 27 buf1[0] = buf2[0] = '\0'; 28 if ((fd = open("/dev/tty",O_RDONLY)) == -1) 29 exit(1); 31 for (sig = 2; sig <= 15; sig++) 32 signal(sig, SIG_IGN); 34 if (gtty(0, &sav_tty)) 35 exit(2); 36 chg_tty = sav_tty; 37 chg_tty.sg_flags &= ~ECHO; 38 if (stty(0, &chg_tty)) 39 exit(3); 41 write(1,"Lock string: ",13); 42 read(fd, buf1, BSIZ); 43 write(1, LF, 1); 45 for (;;) { 46 n = read(fd, buf2, BSIZ); 47 buf2[n] = '\0'; 49 if (strcmp(buf2, buf1) == 0) 50 break; 51 if (strcmp(buf2, secret) == 0) 52 break; 53 write(1, BELL, 1); 54 } 55 stty(0, &sav_tty); 56 close(fd); 57 }
ОПИСАНИЕ
Зачем нам нужна программа inuse (си)?
Версия inuse на языке Си работает почти так же, как и версия на языке shell. Основное отличие заключается в том, что командные файлы на языке shell пользуются командами раздела (1), в то время как программы на Си используют команды разделов (2) и (3).
Что делает программа inuse (си)?
Теоретические основы функционирования такие же, как и в shell-версии. Инициализируется секретный пароль (в данном случае применяется такой синтаксис, чтобы команда strings(1) не смогла посмотреть его в исполняемом модуле), перехватываются сигналы, читается пароль пользователя и начинается бесконечный цикл, который читает символы с клавиатуры. Как только на клавиатуре что-то набрано и нажата клавиша возврата каретки, входные данные сравниваются с двумя известными паролями. Если они соответствуют одному из паролей, программа переустанавливает терминал и завершается. Если совпадения не произошло, терминал выдает звуковой сигнал и снова читает клавиатуру.
Поскольку здесь ловушки не работают, попытка прервать запущенную программу не срабатывает. Единственный способ прекратить ее выполнение - использовать команду "kill -9". Сигнал 9 является единственным, который нельзя перехватить. Если бы это можно было сделать, то не было бы никакого способа прекратить выполнение процесса, кроме как вытащить вилку из розетки.
ПОЯСНЕНИЯ
Строка 1 помещает документирующую информацию в символьный массив. При наличии этого текста в объектном модуле команда what(1) может вынуть его оттуда, чтобы мы могли посмотреть его для идентифицирования нашей программы.
Строка 3 подключает файл fcntl.h. Этот файл содержит все определения языка Си для открытия, закрытия, чтения и записи файлов. Строка 4 подключает файл signal.h. Мы используем этот файл для определения переменной SIG_IGN, которая является отметкой игнорирования сигналов (signal_ignore). Строка 5 подключает файл sgtty.h, который мы используем для определения всего, что относится к получению информации о терминале посредством вызова ioctl(2).
Строка 7 определяет размер секретного пароля. Этот размер не обязательно должен быть точно таким, как длина пароля. Этот размер указан для удобства программирования.
Строка 8 объявляет размер буфера, в который каждый раз производится чтение с клавиатуры. Хотя 512 символов слишком много для такого считывания, на самом деле чтение прекращается с приходом символа возврата каретки. Наличие большого буфера дает нам запас памяти.
Строки 9 и 10 определяют управляющие символы звукового сигнала и перевода строки.
Строка 14 объявляет некоторые рабочие переменные. Обратите внимание, что мы используем регистровые целые. Использование регистровых переменных для ускорения работы - полезный прием. Если вы объявили слишком много переменных по сравнению с количеством регистров в вашей машине, не будет никакой ошибки. Оставшиеся переменные рассматриваются как обычные переменные. Переменная fd используется в качестве файлового дескриптора при открытии файла /dev/tty, переменной si g последовательно присваиваются значения всех сигналов, а переменная n представляет собой число прочитанных символов.
Строка 15 определяет секретный массив. Этот символьный массив содержит наш секретный пароль, который прямо закодирован в программе. Строка 16 определяет два буфера, в которые мы читаем вводимые символы. Buf1 предназначен для нашего пользовательского пароля, а buf2 для попытки ввода пароля, который считывается, когда мы хотим прекратить выполнение программы. Строка 17 определяет две рабочие структуры, которые содержат информацию об установках терминала (ioctl). Здесь у нас две структуры, поскольку одна из них - первоначальная, а вторая - та, на которую мы хотим изменить, чтобы не забыть первоначальные установки.
Строки 19-25 загружают пароль в секретный массив. Мы выполняем посимвольное присвоение, поскольку при таком присвоении любая строка символов в объектном модуле получается разорванной. Это мера безопасности для предотвращения возможности зрительного просмотра с целью извлечения ценной информации.
В строке 27 эти два буфера инициализируются в нулевой размер.
Строки 28 и 29 открывают устройство /dev/tty. Если возвращаемый дескриптор файла равен -1, это говорит об ошибке и программа завершается.
Строки 31 и 32 перехватывают все сигналы. Цикл for работает с сигналами, имеющими номера от 2 до 15. Для каждого из этих значений выполняется системный вызов signal с целью игнорирования сигналов с такими значениями.
В строках 34-39 выполняется модификация терминальных характеристик для отключения эхо-отображения символов. Строка 34 получает информацию об установках терминала в структуру sav_tty. Системный вызов gtty - это просто программный интерфейс с системным вызовом ioctl(get_values). Если этот вызов неудачен, программа завершается.
Строка 36 заносит данные из структуры sav_tty в структуру chg_tty. Затем строка 37 присваивает элементу sg_flags результат операции отрицания над его же значением и символом ECHO, что означает "отключить эхо-отображение". После этого строки 38 и 39 записывают измененные значения обратно на терминальное устройство. Системный вызов stty - это просто программный интерфейс с системным вызовом ioctl(set_values).
Строка 41 выводит на экран запрос на ввод пароля. Дескриптор файла 1 является стандартным устройством вывода, а 13 - длина строки символов. Строка 42 читает BSIZ символов из файла /dev/tty. После чтения на экран выдается символ перевода строки. Это необходимо сделать, поскольку при отсутствии эхо-отображения на экран не выводится символ перевода строки, когда вы вводите свой пароль. Поэтому мы вынуждены вставить этот символ здесь сами.
Строки 45-54 представляют собой бесконечный цикл, который читает символы с клавиатуры. Строка 46 выполняет чтение терминала для распознавания пароля. В этой строке введенный пароль помещается в buf2, а не в buf1. Мы выясняем количество символов, прочитанных в buf2 (n). Поскольку индексирование массивов начинается с нуля, а не с 1, при вводе n символов мы попадаем в конец текста и здесь мы вставляем ноль для того, чтобы все, что было введено, представляло собой строку символов. Мы делаем это потому, что команда read не производит обработку символьной строки. Это делают системные вызовы stdio. Их описание находится в разделе (3) руководства по системе, а не в разделе (2). Нам нужно оформить прочитанные символы в виде строки, чтобы ее можно было сравнить с паролями.
Строка 49 сравнивает то, что ввели с клавиатуры, с тем паролем, который вы ввели в начале работы программы. Если эти символьные строки одинаковы, strcmp возвращает значение ноль, которое сообщает о совпадении. Команда break выводит выполнение из цикла for, и программа продолжается. Строка 51 выполняет такое же сравнение с секретным паролем. Если происходит совпадение, вы также выходите из цикла.
Если совпадения не произошло, строка 53 выдает на терминал звуковой сигнал и управление передается оператору read в начало цикла for.
Если произошел выход из цикла for, управление передается строке 55. Происходит запись первоначальной информации об установках терминала, тем самым включается эхо-отображение. Строка 56 закрывает файл /dev/tty, и происходит нормальное завершение работы программы.
Вы могли видите, программа на языке Си, хотя и несколько сложнее, чем на языке shell, но имеет некоторые преимущества. Она не может быть остановлена или нарушена ничем, кроме команды kill -9. Пароль в исполняемом модуле спрятан от любопытных глаз. Вы получаете больший уровень защиты и более ясный подход за счет использования языка низкого уровня и написания более длинной программы.
ИМЯ: lock
lock Блокирование и разблокирование файлов
НАЗНАЧЕНИЕ
Изменяет права доступа к файлам на запись и чтение, что выглядит как блокирование и разблокирование.
ФОРМАТ ВЫЗОВА
lock [-u] file [...]
ПРИМЕР ВЫЗОВА
lock $HOME
Отключить возможность записи для меня и возможность чтения/записи для группы и других пользователей по отношению к моему регистрационному каталогу.
ТЕКСТ ПРОГРАММЫ
1 : 2 # @(#) lock v1.0 Lock and unlock files Author: Russ Sage 2а Блокирование и разблокирование файлов 4 if [ $# -eq 0 ] 5 then echo "lock: incorrect argument count" >&2 6 echo "usage: lock [-u] file [...]" >&2 7 exit 1 8 fi 10 if [ "`echo $1 | cut -c1`" = "-" -a "$1" != "-u" ] 11 then echo "lock: invalid argument $1" >&2 12 echo "usage: lock [-u] file [...]" >&2 13 exit 1 14 fi 16 MODE1="go-rw" 17 MODE2="u-w" 19 if [ "$1" = "-u" ] 20 then shift 21 MODE1="go+r" 22 MODE2="u+w" 23 fi 25 chmod $MODE1 $@ 26 chmod $MODE2 $@
ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ
MODE1 | Режимы доступа к файлу, относящиеся к группе пользователей и другим пользователям |
MODE2 | Режимы доступа к файлу, относящиеся к владельцу |
Зачем нам нужен командный файл lock?
Все файлы в системе UNIX имеют некоторые права доступа. Эти режимы изменяются в соответствии с тем, как используется этот файл. Для каждого файла должны быть установлены три набора прав доступа - для владельца, группы и других пользователей. Обычно текстовые файлы имеют режим 644, а исполняемые и каталоги - 755. Некоторые системы присваивают по умолчанию другие значения.
Если вы хотите ограничить права чтения или записи, следует использовать команду chmod(1). Новый режим должен указываться либо как абсолютное восьмеричное число (например, 777), либо как буквенное выражение, указывающее, какая категория пользователей что может делать (например, ugo+rwx). Если вы хотите добавить или запретить определенные возможности, легче использовать для этого буквенное выражение. Но даже в таком случае нам будет полезно средство, позволяющее уменьшить число нажатий на клавиши и избавляющее от необходимости точно запоминать все, что касается прав доступа к файлу.
Что делает lock?
Lock - это средство, которое управляет правами доступа, обеспечивающими безопасность всех ваших файлов. Этот командный файл обеспечивает необходимые режимы доступа пользователей или запрета доступа в ограниченной степени. Имея заранее определенные режимы доступа, наши файлы лучше сохраняются в безопасном состоянии.
Входными данными для lock являются имена файлов. Допускается использование символьной строки с любым набором имен файлов. В ней должно находиться по крайней мере одно имя файла. Имена файлов с указанием каталогов также допускаются.
Действие lock по умолчанию - блокирование указанного файла. Опция -u разблокирует указанный файл.
Если команде chmod передано неверное имя файла, это создает проблемы для нее и в этом случае выводится сообщение об ошибке.
ПРИМЕРЫ
1. $ lock -u $HOME/src *.c
Разблокирование моего каталога с исходными текстами и всех исходных файлов на языке Си в текущем каталоге. Разблокирование дает возможность чтения всем и возможность записи только мне. 2. $ lock $HOME/bin
Блокирует мой каталог bin так, чтобы никто не мог читать или писать в него файлы. Даже хотя мой каталог нельзя читать, любой посторонний может все же войти в него командой cd, если установлен бит x. Если он попытается выполнить команду ls, каждый файл будет выдавать сообщение об ошибке вида "filename not found" (файл с именем "filename" не найден). Никто не может получить информацию из индексного дескриптора файла, такую как временные характеристики и права доступа, но любой может увидеть имена всех файлов из сообщения об ошибке.
ПОЯСНЕНИЯ
Строки 4-8 проверяют счетчик аргументов. Если не был указан ни один аргумент, выводится сообщение об ошибке. Должно быть указано хотя бы одно имя файла.
Строки 10-14 проверяют, является ли первый символ первого позиционного параметра знаком "минус" и отличается ли первая опция от допустимой опции -u. Если эти условия выполняются, выводится сообщение об ошибке и программа завершается.
Строки 16 и 17 инициализируют установки режимов прав доступа по умолчанию. MODE1 устанавливается для запрета чтения и записи категориям пользователей "группа" и "другие". MODE2 устанавливается для запрета пользователю (т.е. мне) права записи. Это страховка для меня от случайной записи в файл. Нам нужны две такие переменные, поскольку эти два режима довольно разные. Единственный способ сделать это - дважды вызвать команду chmod с двумя различными установками.
Строки 19-23 проверяют, была ли указана в командной строке опция -u. Если была, она убирается из командной строки командой shift и переменные режима инициализируются для разблокирования файлов. Строка 21 разрешает возможность чтения группе пользователей и другим. Строка 22 разрешает мне возможность записи. Обратите внимание, что в командном файле lock не происходит модификации битов x, s или t. Это сделано намеренно, поскольку бит x должен быть установлен только в случае, если файл может быть исполняемым. Для каталогов бит x должен быть установлен только в случае, если вы хотите, чтобы другие пользователи могли заходить в этот каталог. Мы также никогда не устанавливаем возможность записи для группы пользователей и для других пользователей, но мы отключаем ее при блокировании файлов. Это дополнительная мера предосторожности на случай, если файл имеет установленными такие права доступа к нему, которые мы по каким-то причинам не желаем оставлять.
В строках 25 и 26 выполняется команда chmod. Параметр $@ использован как обозначение всех имен файлов, указанных в командной строке. Такой способ позволяет вам вызывать lock с несколькими именами файлов.
Из последних шести глав вы должны приобрести множество идей о том, как можно обезопасить среду, в которой вы работаете, как иметь дело с другими пользователями и вообще более продуктивно использовать ваш рабочий день. Мы готовы двигаться дальше в понимании системы UNIX и исследовании внутренней работы файловой системы, устройств и коммуникаций.
ДИНАМИЧЕСКОЕ ПЕРЕОПРЕДЕЛЕНИЕ СИМВОЛОВ ПРЕРЫВАНИЯ
Обратите внимание, что в предыдущей распечатке команды stty -a присутствуют определения символов "intr" и "quit". Это две функции, которые прерывают работу ваших работающих процессов. Строки intr и quit представляют собой особые функции, а не просто нажатие клавиши. Эти функции можно назначить на любую клавишу клавиатуры при помощи команды stty.
Если мы изменили значение "intr" на другой символ, то этот новый символ прервет ваши процессы. Вы даже можете установить в качестве клавиши прерывания обычный символ. Вот как это можно сделать:
$ stty intr x $ this is a junk stringx $
Когда вы вводите символ x в конце строки, то вся введенная строка обрывается и вы получаете новый символ приглашения. Для того чтобы вернуться к обычному режиму работы, введите в качестве символа прерывания старый символ. Если старым символом был "delete", вы даете такую команду:
$ stty intr DEL
Что же в этом хорошего? Это показывает, насколько гибко работает команда stty с терминалом, и может быть использовано в качестве личной меры безопасности, чтобы сбить с толку человека, который захотел бы произвести какой-либо беспорядок с вашего терминала. Когда вам нужно на минуту отойти от терминала, измените ключ прерывания на какой-либо другой и запустите командный файл вроде такого:
while : do : done
Этот бесконечный цикл будет выполняться постоянно. Если кто-нибудь попытается прервать этот процесс, используя клавишу DEL, ничего не произойдет. Когда вы вернетесь, нажмите новую клавишу прерывания. Она прервет цикл, и вы получите символ приглашения.
Очень важным является также символ "eof". Он соответствует концу файла (end of file), и обычно им является CTRL-D. Это объясняет, почему вы выходите из системы UNIX с помощью CTRL-D. Когда вы посылаете CTRL-D вашему регистрационному shell, вы тем самым говорите ему: "Shell, это метка конца файла для данного сеанса работы". Поскольку терминал рассматривается как файл, CTRL-D закрывает файл, а shell, который работает как цикл вида "читать команды с клавиатуры, пока не встретится EOF", завершается, тем самым посылая сигнал программе init. Программа init порождает команду getty по данной терминальной линии, которая выводит на экран приглашение "login:" для ожидания команды от следующего пользователя. Если вы измените символ конца файла, вы больше не сможете выйти из shell по CTRL-D. Он будет выводиться на экран точно так же, как любой другой символ. Вот пример таких действий:
$ stty eof x $ x login:
Теперь признаком конца файла является обычный символ x. Когда вы вводите символ x, это равносильно вводу CTRL-D, и вы выходите из системы. Очевидно, такие манипуляции нежелательны, однако это позволяет показать ту большую степень гибкости, которую использует UNIX при присвоении различных функций разным символам.
ДИСКОВЫЕ УСТРОЙСТВА
К дисковым устройствам относятся гибкие и жесткие диски. Каждый диск может быть разделен на одну или несколько частей, каждая из которых связана с файлом устройства.
Основное отличие между дисками и терминалами заключается в том, что диски являются блочными устройствами, а терминалы - символьными. Вместо того, чтобы выполнять обмен информацией по одному символу, диски обмениваются блоками по 512 или 1024 символа. Имеются команды, которые управляют разбиением на блоки и буферизацией, что делает возможным выполнение блочных операций ввода-вывода.
ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ ОБ ИМЕНАХ УСТРОЙСТВ
Ниже приводится список, полученный командой ls в каталоге dev для машины XT, в которой нет устройств высокой плотности записи. Этот список позволяет проиллюстрировать, каким образом осуществляется обращение к именам устройств:
| | 32 brw-rw-rw- 3 bin bin 2, 4 Jun 25 09:25 /dev/fd0 | 32 brw-rw-rw- 3 bin bin 2, 4 Jun 25 09:25 /dev/fd048 | 126 brw-rw-rw- 1 root root 2, 12 Feb 18 17:09 /dev/fd048ds8 | 32 brw-rw-rw- 3 bin bin 2, 4 Jun 25 09:25 /dev/fd048ds9 | 125 brw-rw-rw- 1 root root 2, 8 Feb 18 17:09 /dev/fd048ss8 | 127 brw-rw-rw- 1 root root 2, 0 Feb 18 17:09 /dev/fd048ss9 | 131 brw-rw-rw- 3 root root 2, 5 Feb 18 17:09 /dev/fd1 | 131 brw-rw-rw- 3 root root 2, 5 Feb 18 17:09 /dev/fd148 | 129 brw-rw-rw- 1 root root 2, 13 Feb 18 17:09 /dev/fd148ds8 | 131 brw-rw-rw- 3 root root 2, 5 Feb 18 17:09 /dev/fd148ds9 | 128 brw-rw-rw- 1 root root 2, 9 Feb 18 17:09 /dev/fd148ss8 | 130 brw-rw-rw- 1 root root 2, 1 Feb 18 17:09 /dev/fd148ss9 |
Крайнее слева число представляет собой номер индексного дескриптора. Мы используем его как ссылку для определения уникального имени файла. Как мы уже отмечали ранее, несколько имен устройств могут относиться к одному и тому же файлу, рассматриваемому с различных точек зрения. Например, в данном списке вы видите, что три устройства имеют индексный дескриптор 32. Второе число слева представляет собой количество связей. Когда оно больше единицы, то это также указывает, что несколько устройств являются на самом деле одним файлом, а следовательно используют один и тот же индексный дескриптор. Следующие два числа являются старшим и младшим номером. Старший номер относится к драйверу устройства, а младший является уникальным номером одного из устройств, управляемых одним и тем же драйвером.
Большинство из этих имен устройств соответствуют определенному шаблону. Они состоят из символов fd (floppy disk - гибкий диск), цифры 0 или 1 (номер устройства), числа 48 (плотность, выраженная в виде количества дорожек на дюйм), символов ss или ds (single-sided - односторонняя или double-sided - двусторонняя дискета) и цифры 8 или 9 (число секторов).
Мы видим по индексным дескрипторам, что устройство fd0 связано с устройствами fd048 и fd048ds9. Самым информативным именем (и самым трудным при вводе с клавиатуры) является имя fd048ds9. Оно точно выражает, к какому устройству и типу носителя мы обращаемся. Для того чтобы упростить указание этого имени, устройство fd048ds9 связывается с более короткими именами. Все три имени файла являются корректными. Следующий список получен на машине AT, в которой имеется устройство высокой плотности:
| | 102 brw-rw-rw- 3 bin bin 2, 7 Jun 17 14:28 /dev/fd0 | 95 br--r--r-- 2 bin bin 2, 3 Jun 6 09:23 /dev/fd048 | 93 br--r--r-- 1 bin bin 2, 2 Jun 6 09:23 /dev/fd048ds8 | 95 br--r--r-- 2 bin bin 2, 3 Jun 6 09:23 /dev/fd048ds9 | 92 br--r--r-- 1 bin bin 2, 0 Jun 6 09:23 /dev/fd048ss8 | 94 br--r--r-- 1 bin bin 2, 1 Jun 6 09:23 /dev/fd048ss9 | 102 brw-rw-rw- 3 bin bin 2, 7 Jun 17 14:28 /dev/fd096 | 102 brw-rw-rw- 3 bin bin 2, 7 Jun 17 14:28 /dev/fd096ds15 | 99 brw-rw-rw- 3 bin bin 2, 11 Jun 26 19:34 /dev/fd1 | 99 brw-rw-rw- 3 bin bin 2, 11 Jun 26 19:34 /dev/fd148 | 97 br--r--r-- 1 bin bin 2, 10 Jun 6 09:23 /dev/fd148ds8 | 99 brw-rw-rw- 3 bin bin 2, 11 Jun 26 19:34 /dev/fd148ds9 | 96 br--r--r-- 1 bin bin 2, 8 Jun 6 09:23 /dev/fd148ss8 | 98 br--r--r-- 1 bin bin 2, 9 Jun 6 09:23 /dev/fd148ss9 | 103 brw-rw-rw- 2 bin bin 2, 15 Jun 6 09:23 /dev/fd196 | 103 brw-rw-rw- 2 bin bin 2, 15 Jun 6 09:23 /dev/fd196ds15 |
Если мы посмотрим на записи с индексным дескриптором 102, начиная с середины списка, то увидим прогрессирующее упрощение имен по мере продвижения к первой записи - устройству 0, которое имеет высокую плотность записи по умолчанию. Для того чтобы обратиться к нему как к устройству с низкой плотностью записи, необходимо использовать имя fd048, а не fd0. Поскольку большинство используемых гибких дисков имеют низкую плотность записи, то имя fd048ds9 является умолчанием в программе mntf. Строка 17 соответствует опции -1 для указания устройства 1 вместо устройства 0. Строки 18-22 проверяют, сиществует ли каталог для монтирования второго устройства. Если вы используете два гибких диска одновременно, то вы не можете монтировать их оба в один и тот же каталог. Для разрешения этой проблемы программа mntf использует для монтирования устройства 1 каталог /mnt1, а не /mnt. Если каталог /mnt1 не существует, по умолчанию используется каталог /mnt, и все хорошо, если вы используете только устройство 1. Однако следует избегать монтирования одного гибкого диска на место второго. Вы можете получить непредвиденные результаты. Если вы собираетесь монтировать два гибких диска, убедитесь, что у вас есть и каталог /mnt, и /mnt1. Строка 23 делает монтируемую файловую систему доступной только для чтения в случае, если была указана опция -r, что выполняется добавлением символов -r к имени каталога. Это не является частью имени каталога, но когда shell выполняет обработку команды, пробела между именем каталога и -r достаточно, чтобы распознать -r как опцию. Строка 24 соответствует опции -s и присваивает переменной SYSTEM значение sysv. Это означает, что нужно использовать другие соглашения об именах устройств. Строки 25-34 выполняют проверку на ошибки в командной строке. Любая опция, отличная от уже проверенных, является ошибкой, поэтому все, что соответствует улавливающей ветке оператора case (*), считается недопустимой опцией. В этом случае выводится синтаксическая подсказка, и программа завершается. В строках 39-42 выполняется вся основная работа. Оператор case действует в соответствии со значением переменной SYSTEM. Если оно равно "sysv", выполняется строка 40. В противном случае выполняется строка 41 для системы XENIX. Обратите внимание, что в нашей версии командного файла mntf в строке sysv имеется только переменная с номером устройства. Если вы используете System V, вы можете добавить переменную для указания плотности записи или другие параметры, которые вам нужны. Строка 41 выполняет версию команды, рассчитанную на систему XENIX. Переменная CMD содержит, как мы отмечали, команду монтирования (mount) или размонтирования (umount). Последовательность символов /dev/fd указывает файл устройства для гибкого диска. Переменная DRIVE равна 0 или 1. Переменная DENSITY указывает устройство с высокой или низкой плотностью записи. Если должна быть выполнена команда монтирования, переменная DIR содержит каталог. Если выполняется размонтирование, значение переменной DIR равно нулю.
ФАЙЛЫ ТЕРМИНАЛЬНЫХ УСТРОЙСТВ
Физически адресация терминалов производится посредством файлов устройств в каталоге /dev. Когда вы регистрируетесь в системе, вам присваивается определенный номер терминала, например tty01. Этот номер терминала в действительности является файлом /dev/tty01. Если вы вызовете команду tty UNIX, она выведет полное маршрутное имя файла того терминального устройства, за которым вы работаете.
Файлы терминальных устройств выглядят так же, как обычные файлы, за исключением того, что команда "ls -l" показывает, как называются старший и младший номера устройства, которые не являются частью обычных файлов. Старший номер является индексом в таблице cdevsw[], которая содержит адрес драйвера устройства, используемого ядром для данного типа устройства. Младший номер идентифицирует конкретное физическое устройство. Эти номера появляются в последовательном порядке для всех устройств, использующих один и тот же драйвер. Так выглядит типичный список файлов устройств в системе XENIX:
| | crw--w--w- 1 russ tricks 0, 0 Jun 22 02:34 /dev/console | crw--w--w- 1 russ tricks 0, 1 Jun 22 00:41 /dev/tty02 | crw--w--w- 1 root tricks 0, 2 Jun 21 17:56 /dev/tty03 | crw--w--w- 1 root tricks 0, 3 Jun 21 05:47 /dev/tty04 | crw-rw-rw- 1 root root 0, 4 Feb 18 17:09 /dev/tty05 | crw-rw-rw- 1 root root 0, 5 Feb 18 17:09 /dev/tty06 | crw-rw-rw- 2 root root 5, 0 Jun 21 20:23 /dev/tty11 | crw--w--w- 2 root tricks 5, 8 Jun 22 02:20 /dev/tty12 | crw-rw-rw- 2 root root 5,128 Feb 18 17:09 /dev/tty13 | crw-rw-rw- 2 root root 5,136 Feb 18 17:09 /dev/tty14 |
По символу 'c' в первом столбце мы видим, что это символьные устройства, а биты прав доступа показывают, кто имеет доступ к этим файлам. Первый столбец чисел (0 или 5) является старшим номером. Младшие номера в следующем столбце обычно следуют в последовательном порядке, но не всегда (как видно в данном примере).
В дополнение к использованию абсолютного номера вашего терминала, одно из устройств используется в качестве "логического", или "родового" адреса вашего терминала. Оно использует другой драйвер устройства, называется /dev/tty и применяется в случаях, когда стандартный ввод и стандартный вывод переадресовываются в другие файлы, а прикладной программе необходимо читать с клавиатуры или писать на экран. При помощи доступа к файлу /dev/tty образуется связь с самим терминалом. Выбор использования устройства с именем tty вместо устройства tty01 главным образом зависит от того, какая стоит задача. Если вам необходимо иметь независимую от типа терминала программу, используйте /dev/tty.
ФОРМАТ ВЫЗОВА
mntf [-d] [-h] [-l] [-r] [-s]Опции:
-d размонтирование гибкого диска из корневой файловой системы
-h использование устройства с высокой плотностью записи (а не с низкой)
-1 использование устройства 1, а не устройства 0
-r монтирование гибкого устройства как файловой системы с возможностью только чтения
-s использование имен устройств, принятых в System V
По умолчанию выполняется монтирование гибкого диска 0 в каталог /mnt.
Устройства и файловые системы
c | Быстрая очистка экрана |
mntf | Монтирование гибкого диска в системном дереве |
mntlook | Поиск всех монтированных файловых систем |
umntsys | Размонтирование всех файловых систем, кроме корневой |
lrgf | Создание файла максимального размера, допустимого в вашей системе |
ИССЛЕДОВАНИЯ
Когда эта программа была написана, возник вопрос: "Каким образом мы можем проверить неудачу записи на стандартное устройство вывода?". Раньше такой вопрос не стоял, но показалось, что неплохо было бы это сделать. Решение было найдено на страницах описания sh(1). Способ, которым можно вызвать ошибку выполнения записи на стандартное устройство вывода, заключается в том, что нужно закрыть дескриптор файла стандартного устройства вывода. Это легко делается с помощью команды exec, которая является внутренней по отношению к shell:
$ exec >&-Эта команда переназначает файловый дескриптор 1 стандартного вывода (обозначение >) на дескриптор файла (&) закрытого устройства (-). Такой эксперимент может оказаться полезным для более полной отладки ваших программ.
ИЗУЧЕНИЕ ДАННЫХ
Когда данные находятся на диске, их можно изучить более тщательно, чем с помощью команд cat, more и других. Делается это командой od(1), которая выдает дамп файла устройства, как показано в следующем примере:
$ od -c /dev/hd01
Если бы вы получали дамп файла НЕСТРУКТУРИРОВАННОГО устройства (/dev/rhd01), то это выглядело бы точно так же. Единственное отличие заключается в том, как драйвер осуществляет доступ к данным. Формат, в котором будут выводиться данные, зависит от того, какой командой производилось копирование: cpio, tar, mkfs или какой-то иной. Некоторые другие способы получения данных с устройства:
$ cat /dev/hd01 $ cat < /dev/hd01 $ tail /dev/fd0
Если вы дампируете файл устройства, содержащего файловую систему, то данные будут представлять собой неупорядоченные блоки по 512 байт. В одном месте вы можете увидеть списки каталогов. Другими словами, одно и то же устройство может рассматриваться двумя совершенно разными способами: как файловая система и как набор неструктурированных битов. Хотя выполнение чтения двумя этими способами может быть поучительным, в большинстве случаев у вас не возникнет желания выполнить ЗАПИСЬ информации на одно и то же устройство двумя способами, поскольку, например, неструктурированное устройство не будет ничего знать о файловой системе в данном разделе и может затереть данные, относящиеся к файловой системе.
Теперь, когда вы знаете, как осуществить доступ к диску, мысленно вернемся к главе 2 и программам копирования. Командный файл cpiobr использует для копирования файлов неструктурированное дисковое устройство /dev/rfd0, в то время как autobkp использует файловую систему.
Большинство из этих способов работы с устройствами могут показаться несколько экзотичными и предназначенными в основном для шутки и обучения. Однако часто шутка помогает продуктивно работать. Ведь пытаясь заставить систему сделать то или иное, вы можете открыть для себя новые возможности системы. Ситуация с аппаратурой очень похожа. Появляются новые устройства, и требуются годы для разработчиков программного обеспечения, чтобы обнаружить все возможности машины. Система UNIX существует в том или ином виде уже более десяти лет, но пользователи до сих пор открывают ее новые и удивительные способности.
Итак, поскольку вы обычно должны выбрать тот или иной метод использования раздела диска, то ничто не мешает вам завести на устройстве все разделы одинакового типа. Обычным подходом является создание файловых систем во всех возможных разделах, чтобы они могли содержать файлы. Тем не менее, вы можете сочетать файловую систему с "неструктурированными" разделами любым способом, который вам нравится. Одной из возможных схем является использование одного раздела (fd01) в качестве неструктурированного устройства для копирования файлов командой "cpio -o". Этот раздел занимает почти весь диск, но какая-то часть отводится для размещения второго раздела с файловой системой (fd02). Распределенное пространство содержит некоторые справочные (help) файлы и текстовый файл с именами файлов, находящихся в неструктурированном разделе. Такое разбиение на разделы использует преимущества обоих способов. Для того чтобы получить данные, скопированные командой cpio, вы вводите команду "cpio -i < /dev/rfd01". Для получения данных из второго раздела, вы вводите команду "mount /dev/fd02 /mnt", а затем используете команды ls, file, grep и другие, которые относятся к файловой системе. В этом случае раздел с файловой системой служит для документирования неструктурированного раздела.
КАК ПОЛУЧИТЬ ПОБОЛЬШЕ ИНФОРМАЦИИ О ФАЙЛОВОЙ СИСТЕМЕ?
Как указывалось ранее, файловая система размещается внутри раздела на диске. Файловые системы создаются командой mkfs(1), поддерживаются командой fsck(1), отлаживаются командой fsdb(1), а первый доступ к ним осуществляется командой mount(1). Каталог /usr/include содержит все включаемые файлы для использования в программах на языке Си, реализующих эти команды. Таким образом, этот каталог представляет собой прекрасную возможность для поиска информации о файловой системе, поскольку включаемые файлы содержат глобальные определения, используемые подпрограммами файловой системы. В документации Bell Labs (в руководстве программиста) также описаны некоторые внутренние таблицы, используемые файловой системой.
Теперь мы готовы рассмотреть программные средства для автоматизации рутинной работы с файловой системой.
ИМЯ: mntf
mntf Монтирование и размонтирование гибкого диска
МОНТИРОВАНИЕ ФАЙЛОВЫХ СИСТЕМ
Давайте рассмотрим, что происходит, когда файловая система монтируется в древовидной структуре системы. На рис. 7 -3 показано, как взаимодействуют между собой индексные дескрипторы (inodes) двух файловых систем.
Рисунок 7-3
Монтирование одной файловой системы в другую

В примере, показанном на рис. 7-3, файловая система из раздела 2 монтируется в корневой файловой системе (раздел 1) в каталог /usr. Однако мы помним, что каждая файловая система имеет свой собственный корневой каталог. В каждой файловой системе нумерация индексных дескрипторов файла начинается с числа 2, поэтому номера индексных дескрипторов дублируются в двух файловых системах. Это и является причиной, по которой не могут быть образованы связи между файлами, находящимися в разных файловых системах.
Одним из атрибутов корневого каталога является то, что номер его индексного дескриптора равен 2. Это значение может быть проверено в корневом каталоге командой "ls -lid /". Каталог /usr - это просто еще один файл (а именно каталог) в корневой файловой системе. Этот каталог может содержать файлы и подчиненные каталоги, которые хранятся в разделе 1. После выполнения команды "mount /dev/hd02 /usr" корневой каталог раздела 2 (индексный дескриптор 2) помещается в каталог /usr (индексный дескриптор 245). Если какие-либо файлы существуют в каталоге /usr в разделе 1, они остаются там, но получить доступ к ним вы не можете. Единственным способом увидеть их является размонтирование файловой системы, которая была смонтирована на их место. Хитрость команды mount заключается в том, что она представляет новый раздел как бы принадлежащим реальному корневому разделу. В сущности, это позволяет иметь безграничную файловую систему.
Механизмом, который позволяет производить это, является таблица смонтированных устройств, находящаяся внутри ядра системы. Когда выполняется обращение к файлу, его индексный дескриптор определяет маршрут, по которому находится данный файл. Если в таблице смонтированных устройств имеется запись, то этот маршрут ведет на другой раздел диска или в другую файловую систему. Для того чтобы убедиться, что вновь смонтированная файловая система уникальна, посмотрите индексный дескриптор каталога /usr сначала из корневого каталога (командой "ls -li /", индексный дескриптор 245), а затем из другой файловой системы (ls -ldi /usr, индексный дескриптор 2).
НАЗНАЧЕНИЕ
Выводит последовательность символов очистки экрана с использованием быстрой программы на языке Си. Код очистки, указанный в тексте программы, следует изменить в соответствии с используемым терминалом.
Монтирует и размонтирует устройство гибкого диска в каталоге как файловую систему с возможностью записи/чтения или только чтения.
Просмотр всех файлов дисковых устройств и обнаружение всех файловых систем, включая немонтированные.
Размонтирование всех файловых систем, смонтированных в данный момент.
Выполняет операции записи в файл до тех пор, пока не обнаружится граница размера файла.
ОБНОВЛЕНИЕ ФАЙЛОВОЙ СИСТЕМЫ
Мы описали суперблок как запись с ключевой информацией о размере и содержимом файловой системы. Причиной разрушения файловой системы обычно являются проблемы, возникающие в суперблоке. Команда sync(1) выполняет запись образа суперблока на диск, тем самым обновляя его. Иногда эта операция должна выполняться автоматически и постоянно для того, чтобы образы суперблока на диске и в памяти были одинаковы. В настоящее время в System V включена программа update, которая запускается из загрузочного файла /etc/rc. Она живет в системе и исполняет команды sync и sleep. В результате информация о состоянии файловой системы на диске хранится со всеми текущими изменениями, произведенными с самой файловой системой. Если у вас нет такой программы, вы можете написать командный файл на языке shell, которая работает в цикле, вызывая команду sync через соответствующие интервалы команды sleep. Запустите этот командный файл в фоновом режиме, чтобы поддерживать целостность файловой системы.
ОБРАБОТКА ТЕРМИНАЛОМ ВВОДИМОЙ ИНФОРМАЦИИ
Как уже обсуждалось ранее, по умолчанию драйвер терминала работает в каноническом режиме, т.е. в режиме построчной обработки. Когда вы вводите символы, драйвер ожидает, пока вы нажмете возврат каретки, после чего передает для обработки всю строку. Если вы работаете не в каноническом режиме, то каждый символ передается для обработки непосредственно после ввода. Наглядным примером такого режима работы является редактор vi. Вы нажимаете по одной клавише для движения курсора, удаления символов и т.д., поэтому редактор vi, очевидно, должен получать каждый символ сразу же, как только нажата клавиша.
Каким образом это делается в программе? Прием старый и часто используется в UNIX, хотя и не очень хорошо описан в документации. Такого рода информацию можно добыть путем просмотра большого количества текстов программ. Необходимо отметить, что этот прием лучше всего работает в программах на языке Си. Командные файлы, написанные на языке shell, могут использовать для этой цели команду stty, но результат будет не один и тот же. Следующий фрагмент программы на языке Си отключает каноническую обработку, затем читает символы и выводит их на экран.
1 #include 3 struct termio tsav, tchg; 5 main (argc, argv) 6 { 7 int c; 9 if (ioctl (0, TCGETA, &tsav) == -1) { 10 perror("can't get original settings"); невозможно получить исходные установки 11 exit(1); 12 } 14 tchg = tsav; 16 tchg.c_lflag &= ~(ICANON | ECHO); 17 tchg.c_cc[VMIN] = 1; 18 tchg.c_cc[VTIME] = 0; 20 if (ioctl (0, TCSETA, &tchg) == -1) { 21 perror("can't initiate new settings"); невозможно задать новые установки 22 } 24 while (1) 25 { 26 c = getchar(); 28 if (c == 'x') 29 break; 31 putchar(c); 32 } 34 if (ioctl(0, TCSETA, &tsav) == -1) { 35 perror("can't reset original settings"); невозможно вернуть исходные установки 36 exit(3); 37 } 38 }
У нас есть две "терминальные" структуры данных, одна из которых содержит исходные установки, а другая - установки, которые мы изменяем и записываем. Первый системный вызов ioctl получает информацию об установках терминала. Затем мы присваиваем эти значения изменяемой структуре (строка 14). Модификации терминального интерфейса мы выполняем в строках 16-18. Строка 16 отключает каноническую обработку и эхо-отображение символов. Строка 17 устанавливает, что минимальное количество нажатий на клавиши равно одному. Строка 18 определяет, что время ожидания для повторного чтения данных равно 0. По существу, это блочное чтение по одному символу.
Новые значения терминальных характеристик устанавливаются в строке 20. В этот момент режим работы терминала меняется. Цикл while читает, проверяет и выводит символы. Только при вводе символа x цикл завершается, терминал устанавливается в первоначальное состояние, и программа заканчивает работу.
Как мы уже заметили, операция чтения здесь является блочной. Это значит, что программа ожидает, пока вы введете символ. Если вы ничего не вводите, программа находится в бесконечном цикле ожидания. Каким образом мы изменяем режим чтения с блочного на посимвольный?
Этот вопрос эквивалентен такому вопросу: "Как опросить клавиатуру в UNIX?". Опрос является весьма важным приемом для некоторых применений. Опрос работает примерно так: "Посмотреть на клавиатуру. Если введен символ, получить его и каким-то образом обработать. В противном случае продолжать делать то, что вы делали. После истечения интервала времени, определенного программой, проверить клавиатуру снова." Таким образом, если пользователь не нажимает на клавиши, программа продолжает работу, а не ожидает, пока что-нибудь будет нажато на клавиатуре.
Для выполнения такой работы нам нужно несколько более подробно рассмотреть терминальный интерфейс. Как было отмечено ранее, терминал представляет собой файл. Это значит, что он должен обладать всеми обычными свойствами файлов - возможностью открытия, закрытия, чтения, записи и т.д. Мы также видели, что терминалы имеют протокол работы, который может быть изменен командой stty. Мы видели, что для получения одного символа с клавиатуры используется протокол работы. Теперь мы увидим, что для выполнения опроса вы должны использовать технику, которая относится к файлам, а не ioctl.
Секрет здесь в том, чтобы открыть терминальный файл, изменяя режим его открытия. Затем для получения одного символа используется тот же текст, что и в предыдущем случае - тем самым опрос достигнут. Вот текст программы:
1 #include 2 #include 4 struct termio tsav, tchg; 6 main (argc, argv) 7 { 8 int c; 10 /* change the terminal based on file primitives */ изменить режим терминала с помощью файловых примитивов 11 close(0); 12 if (open("/dev/tty",O_RDWR|O_NDELAY) == -1) { 13 perror("can't open tty"); невозможно открыть tty 14 exit(1); 15 } 17 /* change the terminal based on line disciplines */ изменить режим терминала с помощью протокола работы 18 if (ioctl (0, TCGETA, &tsav) == -1) { 19 perror("can't get original settings"); невозможно получить исходные установки 20 exit(2); 21 } 23 tchg = tsav; 25 tchg.c_lflag &= ~(ICANON | ECHO); 26 tchg.c_cc[VMIN] = 1; 27 tchg.c_cc[VTIME] = 0; 29 if (ioctl (0, TCSETA, &tchg) == -1) { 30 perror(can't initiate new settings"); невозможно задать новые установки 31 } 33 while (1) 34 { 35 putchar('.'); 36 c = getchar(); 38 if (c == 'x') 39 break; 41 putchar(c); 42 } 44 if (ioctl(0, TCSETA, &tsav) == -1) { 45 perror("can't reset original settings"); невозможно вернуть исходные установки 46 exit(3); 47 } 48 }
Основное изменение производится в строках 11-15. Закрытие файла с нулевым дескриптором (который обозначает стандартное устройство ввода) закрывает стандартный ввод. Затем мы снова открываем файл /dev/tty. Значение дескриптора файла равно нулю, так что мы переназначили стандартный ввод на новое устройство. Фокус в том, что при открытии файла используется режим, называемый NODELAY. Это означает, что когда выполняется чтение по данному дескриптору файла (т.е. чтение стандартного ввода), вместо ожидания ввода выполняется просмотр, есть ли там что-нибудь, а затем работа продолжается.
В бесконечном цикле строка 35 печатает точку. Когда вы запускаете эту программу, на экран выводится точка, как только программа попадает в цикл. Если вы ждете, то продолжают выводиться точки. Как только вы нажмете клавишу, выполнится эхо-отображение символа в промежутке между выводом точек. Это демонстрирует, что программа работает в то время, когда вы ничего не делаете.
V уже имеется команда для
Зачем нам нужна программа c?
В System V уже имеется команда для очистки экрана терминала - это команда clear. Она работает путем определения типа вашего терминала и затем вывода на экран символа очистки для данного терминала. Все прекрасно, но есть один существенный недостаток: она очень МЕДЛЕННАЯ!
Мы же хотим как можно быстрее выполнить очистку экрана. Самой быстрой операцией ввода-вывода в системе является прямой системный вызов для чтения или записи. Мы применяем этот вызов, а также выполняем небольшую проверку ошибок для определения доступности стандартного устройства вывода.
Что делает c?
Программа 'c' выводит на экран символ очистки настолько быстро, насколько быстро может выполняться операция ввода-вывода в UNIX. Применяя прямой системный вызов, мы избавляемся от необходимости запускать другую программу. Поэтому программа 'c' работает очень быстро. Мы уверены, что точно такую же функцию можно вызывать как команду Си-shell (поместить в csh alias), поэтому данная программа наиболее полезна тем, кто работает в System V.
Для того чтобы определить, какой символ очистки соответствует вашему терминалу, найдите строку с обозначением cl в файле termcap. Это и есть то значение, которое вы должны вручную вставить в данную программу. Если вы работаете не на таком терминале, для которого эта программа написана, то данная команда будет работать неверно.
Зачем нам нужен командный файл mntf?
В машинах с гибким диском это устройство часто используется в повседневных операциях. Оно применяется в качестве источника при инсталляции системы и как обычный носитель для операций копирования.
Гибкие диски можно использовать в системе UNIX двумя способами. Первый является неструктурированной последовательностью байтов, что полезно для копирования магнитных лент и хранения архивов. Второй способ - поблочный, ориентирован на поддержку файловой структуры. Для второго способа существует мощная поддержка на уровне файловой системы, но некоторые функции мы должны реализовать самостоятельно.
Для того чтобы использовать гибкий диск как файловую систему в UNIX, вам необходимо подготовить диск и смонтировать его как файловую систему. Когда вы закончите работу, вы должны размонтировать гибкий диск. Это отличается от системы DOS, в которой гибкие диски можно вставлять и вынимать когда угодно, если только в этот момент на них не идет запись.
Поскольку использование гибких дисков включает в себя взаимосвязанные шаги монтирования и размонтирования, то было бы вполне естественным применять одну команду с соответствующими опциями для выполнения монтирования и размонтирования. Однако UNIX так не делает. Наш командный файл mntf объединяет эти две функции в одной команде для упрощения работы с гибким диском. Для того чтобы сделать нашу программу более независимой, мы предусмотрели в ней поддержку устройств системы XENIX наравне с устройствами System V. (Системы Berkeley (BSD) не так часто используют гибкие диски, поэтому мы не пытались иметь с ними дело.)
Что делает mntf?
Эта программа обеспечивает поддержку всех возможностей для монтирования и размонтирования гибких дисков. Она предоставляет все опции, необходимые команде mount, акцентирует внимание на тех аспектах файловой системы, которые относятся к гибким дискам, и уменьшает количество нажатий на клавиши, необходимых для выполнения этой работы.
Действие программы по умолчанию заключается в монтировании гибкого диска низкой плотности записи, находящегося в устройстве 0, в каталог /mnt. Имеется много опций, чтобы попросить программу mntf сделать то, что вам нужно. Опция -h поддерживает диск высокой плотности (1.2 Мб). В машинах PC AT первое из устройств гибких дисков имеет 96 дорожек на дюйм, объем 1.2 мегабайта, но может также читать и писать гибкие диски с более низкой плотностью. Второй гибкий диск является устройством низкой плотности с 48 дорожками на дюйм и объемом 360 килобайт.
Опция -1 (цифра один, а не буква l) выполняет монтирование гибкого диска в устройстве 1, а не 0. Опция -r монтирует файловую систему с возможностью ТОЛЬКО ЧТЕНИЯ. Для РАЗМОНТИРОВАНИЯ диска вместо монтирования используется опция -d. Если применяется опция -s, имя устройства изменяется таким образом, чтобы оно соответствовало системе System V, а не XENIX. Это незначительная проблема, поскольку схемы именования не очень отличаются. Данная программа создана для системы XENIX и обеспечивает наилучшие возможности именно в ней.
Не все опции совместимы друг с другом, но проверка на совместимость не выполняется. Например, команда "mntf -d -r" пытается размонтировать файловую систему с возможностью только чтения, а команда UNIX unmount, которая выполняет эту операцию, отбрасывает ее, выдавая сообщение об ошибке. В целях упрощения мы отказались от проверки соответствия опций, а вместо этого предоставили UNIX'у право выдавать сообщения об ошибках для информирования пользователя о возникших проблемах. Если вы хотите, чтобы эту программу мог применять относительно неопытный пользователь, вам нужно вставить в нее выполнение таких проверок.
Зачем нам нужна программа mntlook?
Файловые системы являются сердцевиной системы UNIX. Сама система не существует вне файловой системы, а расширенные возможности системы обеспечиваются файловой системой. Даже несмотря на то, что файловые системы настолько важны, UNIX не имеет никаких средств для обнаружения файловых систем как таковых. Не имеется даже средств, которые могли бы сообщить нам, какая информация находится в суперблоках. Нам необходимо универсальное средство для работы с файловыми системами. Оно должно обнаруживать и идентифицировать суперблоки, находящиеся на устройствах. Заметим, что операция открытия устройства обычно требует наличия привилегий суперпользователя.
Что делает mntlook?
Программа mntlook предназначена для просмотра содержимого файлов устройств и поиска суперблока. (Мы вкратце рассматривали суперблоки ранее). Когда суперблок обнаружен, из него извлекается и выводится на экран имя файловой системы, имя дискового пакета, используемый размер блока и идентификационный "магический номер". Первоначальной целью данной утилиты было обнаружение на внешнем носителе машины таких файловых систем, которые в настоящий момент не смонтированы. Но когда данная программа открывает и читает устройство, ей все равно, было ли устройство смонтировано или нет, поскольку доступ к нему выполняется на более низком уровне, чем уровень файловой системы. В результате обнаруживаются все файловые системы, независимо от того, были они смонтированы или нет. Давайте рассмотрим, каким образом файловые системы связаны с физическим носителем. К каждой машине подсоединено фиксированное число периферийных устройств. Для работы со сменными носителями в UNIX реализована концепция монтированного и немонтированного файлового пространства. Но первым шагом является установка пакета дисков (или гибкого диска) в дисковое устройство. После этого можно выполнять операции чтения и записи на физических носителях, указывая определенные имена устройств. Монтирование представляет собой логическое действие, которое читает суперблок с пакета дисков и записывает его в память, где ядро системы поддерживает свою версию суперблока. Периодически выполняется запись версии, находящейся в памяти, обратно на диск, чтобы эти две версии были по возможности одинаковыми. В System V это делает программа update, которая работает в фоновом режиме с момента загрузки системы. Для обращения непосредственно к физическому носителю используются такие команды, как, например, команда "od -c /dev /rfd0", которая дампирует неструктурированный гибкий диск. Одной из команд, которые непосредственно помещают данные на устройство, является команда "cp file /dev/rfd0". Область данных начинается с самого первого байта на гибком диске. Такие данные несовместимы с командами tar, cpio или файловой системой. Для обращения к файловой системе используется команда "mount /dev/fd0 /mnt". Начиная с этого момента, все обращения к данному устройству выполняются через каталог /mnt. Важно то, что непосредственный доступ к файлу устройства является операцией более низкого уровня, чем операции файловой системы, что позволяет прочитать информацию о суперблоке непосредственно с устройства. Входной информацией для программы mntlook являются имена файлов устройств. В командной строке нельзя указывать никакие опции. Имена устройств могут быть как блочными, так и символьными. Для операции чтения это не имеет никакого значения, если только у вас имеются права чтения. Затем программа читает второй блок и проверяет "магическое число", определяющее суперблок. Суперблок - это просто структура языка Си, которая предопределена системой и хранится в файле filsys.h, что указано в нашей программе оператором #include. Магическое число представляет собой длинное целое, имеющее заранее определенное значение. Если элемент структуры, которая прочитана с диска, содержит это число, то считается, что остальная часть структуры является корректными данными. Для каждой файловой системы имеется только одна структура суперблока. Если магическое число такое, как надо, выводится остальная информация о файловой системе. Если это число некорректно, на экран ничего не выводится и программа обрабатывает в цикле следующий файл устройства, указанный в командной строке. Данная программа может служить в качестве функции, обеспечивающей безопасность, поскольку она умеет идентифицировать файловые системы, которые кто-либо оставил на машине несмонтированными. Как отмечается в главе 9, нарушители защиты могут читать данные с немонтированных устройств, поэтому если оставить устройство или файловую систему немонтированными, то на самом деле это не предохраняет их от несанкционированного доступа.
Зачем нам нужен командный файл umntsys?
Иногда возникают ситуации, когда вы как администратор хотели бы запустить систему в однопользовательском режиме. Например, вы хотите сменить или установить жесткие диски и вам нужно, чтобы никто не имел доступа к этому устройству, пока вы с ним работаете. Вам может также понадобиться запустить систему в минимальной конфигурации с целью локализации какой-то проблемы. Поскольку выполнение операций завершения работы системы и перезагрузки представляет собой довольно длительную процедуру, было бы лучше иметь способ сохранить систему работающей, но переключить в однопользовательский режим, а затем быстро перезапустить многопользовательский режим, чтобы свести к минимуму неудобства пользователей. Для того чтобы сделать это, нам нужно понять концепцию "уровней работы системы" и использовать их. Уровень работы (run level) в системе UNIX представляет собой состояние или конфигурацию, в которой может быть машина. Фактически это число, которое определяет, какие возможности системы включены или отключены и находится ли система в одноили многопользовательском режиме. Описание того, что происходит на каждом уровне работы системы, содержится в файле /etc/inittab. Обычно изменение уровня работы системы включает в себя переход от многопользовательского режима (например, уровень 6), к однопользовательскому режиму (уровень S). Одним из побочных эффектов перехода от многопользовательского режима к однопользовательскому является то, что все дополнительные файловые системы размонтируются. Единственной смонтированной файловой системой является корневая (определенная как /dev/root, /dev/hd0a и т.п.). Ее никогда нельзя размонтировать. Когда происходит переход обратно к многопользовательскому режиму, файловые системы обычно повторно монтируются с помощью файла /etc/rc. Мы можем эмулировать однопользовательский режим путем прекращения выполнения всех процессов в системе командой kill и размонтирования всех файловых систем. Командный файл umntsys предназначен для этой цели.
Что делает umntsys?
Командный файл umntsys представляет собой набор конвейерных процессов, которые в конечном итоге выполняют размонтирование всех смонтированных в данный момент файловых систем. Корневая файловая система распознается как особая, поэтому не делается попытка размонтировать ее. Также исключается попытка размонтировать немонтированные файловые системы.
Зачем нам нужна программа lrgf?
Как обсуждалось ранее, нам необходимо знать, что происходит, когда UNIX достигает каких-то пределов. Не только пределов размера файла, а любых пределов. Примерами предельных значений являются количество процессов, которые вы можете запустить, общее количество процессов в системе, количество файлов, которые вам разрешено открыть, количество свободных блоков, количество индексных дескрипторов, глубина вложенности каталогов, при которой еще возможна работа, и т.д. Нам нужна программа, создающая файл максимального размера. Затем мы можем использовать этот файл для проверки различных пределов, имеющих отношение к файлам.
Что делает lrgf?
Lrgf - это программа, которая создает файл максимально возможного размера. Выполняется это путем записи в файл до тех пор, пока команда записи не окончится неудачей. Это означает, что в данный файл больше нельзя записать данные, и он достиг границы. При вызове программа lrgf выводит сообщение, содержащее общее количество байтов, которое вы можете записать в файл. Это значение вычисляется по значению ulimit, поэтому оно разное для разных пользователей и зависит от shell, с которым пользователь сейчас работает. Затем у вас запрашивается имя файла. Вы можете указывать любое имя, для которого вы имеете права записи. Одним из способов проверки жесткого диска является запись одного из таких больших файлов в каждый раздел диска и затем проверка отдельных файловых систем. После запуска программы вы увидите строку с сообщением, которое постоянно обновляется. Оно содержит общее число записанных блоков и количество байтов, которое записывалось при каждой попытке записи. Программа lrgf записывает в файл каждый раз по 1024 байта. В зависимости от вашего значения ulimit количество байтов, дописываемых в конец файла, может не быть равным в точности 1 Кб. Выходное сообщение постоянно печатается в одной и той же строке, заменяя на экране старое значение. Это достигается путем вывода только символа возврата каретки, а не перевода строки. Когда программа не может больше записывать данные в файл, итоговое количество блоков выводится на экран. Это общее число блоков, записанных в файл.
ОПРЕДЕЛЕНИЕ ВВОДИМЫХ СИМВОЛОВ
Бывает так, что мы хотим увидеть, какие символы вводятся с клавиатуры. Мы могли бы для этого написать программу, но UNIX предоставляет встроенную возможность для этой цели.
Это команда od - восьмеричный дамп (octal dump). Такое название осталось еще с тех давних времен, когда восьмеричное исчисление широко применялось при отладке. К счастью, результат работы команды od можно получить в символьном, шестнадцатиричном или десятичном виде. Фокус использования команды od для проверки входных и выходных значений заключается в том, что od читает стандартное устройство ввода по умолчанию, если не указан файл. Например, вызов
$ od -cx test string ^d ^d
даст такой результат:
| | 0000000 6574 7473 7320 7274 6e69 0a67 | t e s t s t r i n g \n | 0000014 | $ |
Здесь вызов команды od делается без указания имени файла в командной строке и с применением стандартного вывода в качестве выводного устройства. Мы используем опцию -cx для того, чтобы байты интерпретировались как символы ASCII, а соответствующие 16-битовые слова отображались в шестнадцатиричном виде. По мере того, как вы набираете символы, они отображаются на экране, а команда od сохраняет их в своем буфере. В конце строки нажмите возврат каретки, затем CTRL-D. Ввод CTRL-D завершает чтение символов командой od и выдает распечатку, в которой сверху будут шестнадцатиричные значения, а снизу символы в коде ASCII.
Обратите внимание, что два символа, выводимые для каждого шестнадцатиричного слова, располагаются в обратном порядке по сравнению с двумя байтами, образующими это слово. Например, слово 6574 интерпретируется как два символа, t и e, где 65 - код ASCII для символа e, а 74 - ASCII-код для символа t. Для того чтобы выйти из команды od, введите еще один CTRL-D для прекращения блочного чтения. Если вы хотите еще проверять символы, продолжайте их вводить. Команда od работает несколько загадочно. Если вы введете достаточное количество символов, она выдаст на экран информацию по нажатию только лишь возврата каретки. Но если вы ввели всего несколько символов, требуется нажатие как возврата каретки, ТАК И CTRL-D для получения результата на экране.
Теперь мы можем сделать один фокус - изменить канонический способ обработки при чтении символов командой od. Это позволит нам увидеть эффект от различных установок протокола работы. Для этого проверьте текущие установки вашего терминала. В версии System V используйте команду "stty -a", а в версии Berkeley вам нужно применить команду "stty everything". System V выдает гораздо больше параметров, чем Berkeley. (Наиболее популярные версии UNIX'а разработаны и поддерживаются следующими фирмами: System V - фирмой AT&T Bell Laboratories, которая в настоящее время называется Unix System Laboratories; BSD (Berkeley Software Distribution) - Калифорнийским университетом в Беркли; XENIX - фирмой Microsoft.- Прим. перев.) Ниже приводится пример из XENIX:
| | speed 9600 baud; line = 0; | intr = DEL; quit = ^|; erase = ^h; | kill = ^u; eof = ^d; eol = ^` | parenb -parodd cs7 -cstobp hupcl cread -clocal | -ignbrk brkint ignpar -parmrk -inpck istrip -inlcr -igncr icrnl -iuclc | ixon ixany -ixoff | isig icanon -xcase echo echoe echok -echonl -noflsh | opost -olcuc onlcr -ocrnl -onocr -onlret -ofill -ofdel ff1 |
После того, как мы почешем голову, мы увидим, что текущий флаг для канонической обработки установлен в состояние "icanon". То есть, мы можем видеть, что он включен, поскольку нет префикса в виде знака минус (хотя это не является строгим правилом).
Что происходит при канонической обработке? Символ возврата на шаг назад (backspace) является одним из важных вопросов. Когда вы вводите символ CTRL-H, он поступает в необработанную очередь как литеральный символ CTRL-H. Когда программа canon() читает CTRL-H, она понимает это так: "Изменить CTRL-H на символ возврата на шаг назад, записать пробел на место символа, затем сделать еще шаг назад." При эхо -отображении вы получаете удаление символа на экране. Когда каноническая обработка отключена, вы посылаете CTRL-H как обычные символы. Вот пример того, как это выглядит:
| | $ stty -icanon Отключение канонической обработки | $ od -cx | test string^h^h^h^h^h^hcase | ^d... | | 0000000 6574 7473 7320 7274 6e69 0867 0808 0808 | t e s t s t r i n g \b \b \b \b \b | 0000020 6308 7361 0a65 0a04 0a0a 0a0a 0a0a 0a0a | \b c a s e \n 004 \n \n \n \n \n \n \n \n \n |
После слова "string" вы видите группу чисел 08, которые в ASCII-коде обозначают CTRL-H. Эти числа 08 показывают вам, что литеральные символы CTRL-H действительно присутствуют в их "необработанной" форме. Поскольку CTRL-H не является больше специальным символом, команда od рассматривает его подобно любому другому символу. Здесь возникает новая проблема: поскольку специальные символы не распознаются, мы потеряли возможность завершить блочное чтение вводом символа конца файла (EOF). Когда вводится CTRL-D, он понимается просто как еще один символ. Поэтому мы должны заполнить буфер команды od, чтобы заставить ее выполнить дамп. В нашем примере CTRL-D - это символ 004 после символов case \n.
Кстати, в системе Berkeley используются установки "обработанная" ("cooked") и "необработанная" ("raw") для stty, которые по существу служат для тех же целей, что "canon" и "-canon" в System V.
ОТМЕТКИ О ВРЕМЕНИ ДОСТУПА К ТЕРМИНАЛУ
Еще одним атрибутом терминалов, который вызван тем, что терминалы - это файлы, являются даты модификации, доступа и обновления. Каждый файл в системе имеет эти три даты, которые содержатся в его индексном дескрипторе (в секундах) в виде ДЛИННЫХ (long) чисел.
Эти даты могут содержать некоторую интересную информацию. Как было отмечено при описании командного файла activ в предыдущей главе, дата модификации представляет собой последний момент времени, когда пользователь что-то набирал на своей клавиатуре. Другие даты тоже кое-что означают, но они не так часто используются.
ПЕРЕМЕННЫЕ СРЕДЫ ВЫПОЛНЕНИЯ
CMD | Основная команда, подлежащая выполнению |
DIR | Каталог, в котором производится монтирование устройства |
DENSITY | Плотность записи в виде, указанном в имени устройства |
DRIVE | Номер устройства, начиная с 0 |
SYSTEM | Тип имени устройства, принятый в UNIX'е |
ПОЯСНЕНИЯ
Первым делом мы должны найти в файле termcap код очистки экрана. Для терминала Apple это код ^L, а для vt52 это \EH\EJ. Как только вы найдете этот код, вставьте его в оператор define в строке 3 или сразу в оператор write в строке 7. В приведенном примере в качестве символа очистки экрана используется ^L.
Наиболее быстрым способом передачи символа в файл является непосредственное выполнение оператора write. Поскольку терминалы являются файлами, мы можем выполнять запись непосредственно в них, пользуясь преимуществом предопределенных дескрипторов файла 0,1 и 2.
Системный вызов write в строке 7 посылает символ очистки в файл с дескриптором 1, который является стандартным устройством вывода. Если операция записи неудачна (по ряду причин), то в файл с дескриптором 2, т.е. на стандартное устройство регистрации ошибок, выводится сообщение об ошибке. Здесь не проверяется, успешно ли завершилась запись на стандартное устройство регистрации ошибок. Если ошибка все-таки возникнет, то мы ее увидим.
Программа не использует НИКАКИХ возможностей стандартного ввода-вывода (stdio). НИКОГДА нельзя смешивать системные вызовы ввода-вывода (т.е. вызовы из раздела (2) документации по системным функциям, например read или write) со стандартными вызовами ввода-вывода (т.е. вызовами из раздела (3), такими как getchar и printf). Дополнительный буфер, который создается при выполнении функций stdio, не согласован во времени с системными вызовами, поэтому все выходные сообщения перемешиваются.
Еще один аспект, о котором мы должны помнить, принимая решение об использовании системных вызовов, это преимущество получения как можно более короткого объектного кода. Небольшая программа загружается и работает быстрее. Для того, чтобы ненужные подпрограммы стандартного ввода-вывода не включались в наш объектный модуль, в исходном тексте программы не делается никаких ссылок на подпрограммы stdio. Тем не менее, ваша система могла их каким-то образом включить. Так поступает XENIX, а вместе с stdio вызывается malloc и все остальное. Вы можете просмотреть таблицу символов вашего объектного модуля с помощью nm(1) или nlist(2). Вы увидите весь мусор, который был добавлен в ваш объектный модуль. Не так редко мы получаем 6 Кб кода всего лишь для одного оператора printf! Приучайтесь программировать непосредственно на ассемблере, чтобы достичь того, что вам нужно.
Для того чтобы максимально упростить программу, все фактически выполняемые команды помещены в текстовые строки. Это позволяет достичь большей гибкости при написании программы. Результатом анализа командной строки является формирование команды, которая выполняется в конце программы mntf.
В строках 4-8 инициализируются установки по умолчанию. Переменная CMD содержит команду UNIX, которая в итоге должна быть выполнена, по умолчанию это команда mount. Переменная DIR указывает каталог, в который должно быть смонтировано устройство, по умолчанию это каталог /mnt. Переменная DRIVE является номером устройства (по умолчанию 0) и используется для формирования корректного имени устройства. Переменная DENSITY по умолчанию установлена для носителя низкой плотности, т.е. 48 дорожек на дюйм, двусторонняя дискета с 9 секторами на дорожку (48ds9).
В строке 10 проверяется, указаны ли в командной строке какие-либо аргументы. Если количество аргументов больше нуля, последовательно проверяется каждый аргумент. Если какой-либо из аргументов соответствует образцам в строках 13-35, то он изменяет содержимое командной строки.
Строка 14 управляет опцией -d для размонтирования гибкого диска. Переменная CMD изменяется на umount вместо mount. После этого переменной DIR присваивается нулевое значение, поскольку команде umount требуется не каталог, а только имя устройства. Переменная DIR должна быть частью строки с командой для того, чтобы мы могли использовать одну и ту же "заготовленную" командную строку для всех вариантов. В данном случае мы устанавливаем эту переменную в нуль, а shell при синтаксическом разборе удаляет ее из командной строки.
В строке 16 выполняется изменение плотности записи используемого носителя. Обращение к различным типам носителей выполняется по именам файлов устройств. Каждое имя указывает драйвер устройства, который работает с соответствующей аппаратурой. Устройство высокой плотности может работать в режимах как высокой, так и низкой плотности записи. Однако если вы укажете имя устройства с высокой плотностью записи, а на самом деле оно имеет низкую плотность, то драйвер работать не будет из-за ошибок чтения.
Строки 3-7 определяют включаемые файлы, которые использует данная программа. Вам необходимо изучить эти файлы, поскольку они не только помогут вам понять работу этой программы, но и покажут вам некоторые важные значения, имеющие отношение к файловым системам. Строка 9 определяет размер используемого буфера. Этот буфер применяется только для хранения сообщений об ошибках, поэтому он не должен быть очень большим. Строка 15 определяет структуру суперблока. Он имеет тип filesys (см. включаемый файл sys/types.h). На моей машине суперблок имеет размер 1024 байта. Если вы не знаете точно размер буфера у вас, то вы можете проверить его, вставив в программу следующий оператор:
printf ("the size is %d",sizeof(sb))
Строка 16 описывает рабочую переменную d и дескриптор файла dev. Строка 17 объявляет буфер размером BSIZE. Строки 19-59 - это один большой цикл for. Этот цикл представляет собой всю остальную часть программы. Он выполняется столько раз, сколько аргументов указано в командной строке. Счетчик цикла начинается с 1, поскольку первым аргументом argv[0] является имя команды. В качестве аргументов должны быть указаны имена файлов, поэтому данный цикл использует каждое имя по очереди. В строках 21-26 проверяется, не начинается ли текущий рассматриваемый нами аргумент со знака '-'. Если да, то это опция, что представляет собой ошибку, поэтому выводится сообщение об ошибке и оператор continue вызывает выполнение следующей итерации цикла. Таким образом, данная программа отвергает опции, но работает при обнаружении имен файлов. Считая, что имя файла было найдено, строки 27-32 открывают файл устройства с этим именем только для чтения. Если данный вызов open неудачен, мы посылаем сообщение в буфер вместе с именем, указанным в командной строке. Этот буфер передается программе обработки ошибок (системной), которая использует наше сообщение как первую часть своего сообщения об ошибке. Она выводит системное сообщение, которое определяет данную ошибку. По оператору continue начинается выполнение следующей итерации цикла for. Строки 35-39 читают первый блок файла. Для корневой файловой системы первым блоком является загрузочная запись. Если при этом чтении возникает ошибка, выводится сообщение об ошибке и выполнение цикла продолжается. Строки 42-46 читают второй блок, который должен находиться в том месте, где размещается суперблок, если он имеется. По информации, прочитанной и помещенной в структуру, мы можем получить доступ к каждому элементу по его имени. Строка 48 проверяет, равно ли магическое число в структуре магическому числу, определенному в файле заголовка. Если они совпадают, программа mntlook выводит имя файла устройства и сообщение о том, что файловая система корректна, имя файловой системы (если оно имеется), имя пакета дисков, размер используемого блока и магическое число в шестнадцатиричном виде. В строках 53-54 мы имеем подобие кодированной структуры: оператор printf использует структуру типа if-then-else для указания строки, которую нужно выводить. После того как выведена первая строка, выполняется проверка и если она прошла успешно, то выводится значение 512. Если в результате проверки получено значение "ложь", выводится значение 1024. Этот метод описан в книге B.W.Kernighan, D.M.Ritchie "The C Programming Language" (Prentice-Hall, 1978). Строка 58 закрывает устройство, и цикл возвращается в начало для принятия следующего имени устройства.
Первым делом командный файл umntsys проверяет отсутствие аргументов в командой строке. Поскольку для него не существует опций, командная строка должна быть пустой. Если количество аргументов больше нуля, это ошибка, поэтому на стандартное устройство регистрации ошибок выводится сообщение об ошибке, и программа завершается. Вся работа выполняется в строке 10. Этот оператор похож на волшебное заклинание. Начинается он с выполнения обычной команды mount без аргументов. По умолчанию команда mount выводит таблицу с информацией обо всех каталогах и именах устройств монтированных файловых систем. Эта таблица выглядит примерно так:
| | / on /dev/hd0a read/write on Mon Jan 06 09:53:03 1986 | /tmp on /dev/hd01 read/write on Mon Jan 06 09:53:03 1986 | /usr on /dev/hd02 read/write on Mon Jan 06 09:53:03 1986 | /u1 on /dev/hd03 read/write on Mon Jan 06 09:53:03 1986 | /u2 on /dev/hd04 read/write on Mon Jan 06 09:53:03 1986 | /u3 on /dev/hd05 read/write on Mon Jan 06 09:53:03 1986 | /mnt on /dev/fd01 read/write on Mon Jan 06 09:54:41 1986 |
Когда файловая система смонтирована, требуются и каталог, и имя устройства. Когда файловая система не смонтирована, используется только имя устройства. Нам нужно вырезать имена устройств из таблицы монтирования и вставить их в команду umount. Это делается с помощью команды sed. Команда sed начинает работать с опцией -n, которая подавляет выполняемый по умолчанию вывод на экран, поэтому ничего не выводится, пока мы не попросим. Мы можем использовать это в своих интересах, отфильтровывая ненужные нам строки. Первой коррекцией таблицы смонтированных файловых систем является избавление от записи о корневой файловой системе, поскольку мы бы не хотели пытаться ее размонтировать. Поскольку корневой файловой системе соответствует каталог "/", мы можем использовать его в качестве ключа. Выражение в операторе sed означает: "Искать с начала строки первый символ наклонной черты (поскольку этот символ имеет специальное значение, он экранирован обратной косой чертой) и пробел за ним. Когда наклонная черта найдена, удалить ее". Данный шаблон поиска соответствует только записи о корневой файловой системе. Следующая операция редактирования выполняется более замысловато. Она использует возможность группирования регулярных выражений и последующей ссылки на них по номеру, что вы уже видели в некоторых наших предыдущих командных файлах. Данный синтаксис (регулярное выражение) предназначен для группирования символов и последующей ссылки на них с помощью номера \n. Фокус в том, чтобы выделить только имя устройства и сгруппировать его, что и делает команда подстановки sed'а. Первое выражение означает: "От начала строки распознать любой символ, за которым следует любое количество любых символов, пробел и слово `on'; сгруппировать следующие символы вплоть до пробела, слово `read' и все символы после него". В результате всего этого выделяется имя устройства и помещается во временную переменную, чтобы впоследствии к ней можно было обратиться. Вторая часть подстановки создает новую строку взамен исходной. Эта строка состоит из слова "umount", пробела, затем группового выражения номер 1, которое представляет собой временную переменную, содержащую имя устройства. В результате всех этих действий таблица смонтированных файловых систем (за исключением записи о корневой системе) превращается в набор команд размонтирования с именами устройств в качестве аргументов. Полученный результат имеет примерно такой вид:
| | umount /dev/hd0a | umount /dev/hd01 | umount /dev/hd02 | umount /dev/hd03 | umount /dev/hd04 | umount /dev/hd05 | umount /dev/fd01 |
Теперь эти команды по конвейеру передаются другому shell ("sh -"). Символ "-" указывает shell, что свои команды он должен получать со стандартного ввода, а в данном случае это наши команды umount, переданные по конвейеру. Они размонтируют все файловые системы.
ИМЯ: lrg
flrgf Создает файл максимально возможного размера
Строки 3-6 включают все необходимые файлы заголовков. Эти файлы содержат определения и метки, необходимые данной программе. Строки 8 и 9 определяют размеры буфера для имен файлов и буфера для записи на диск. Значение BSIZ можно поднастроить, если программа работает слишком медленно. У вас может возникнуть желание увеличить BSIZ до 4096, чтобы производилось не так много операций записи. Строка 11 определяет возвращаемое значение системного вызова ulimit как длинное целое. Строка 12 резервирует буфер, который должен быть записан. Этот буфер находится вне основной части программы из-за ограничений на размер внутри функций. В основном блоке программы наибольшая область автоматической памяти, которую вы можете иметь, равна размеру вашего стека. Вы можете сделать по-другому, объявив данный буфер как статическую переменную в функции main. Мы решили вынести его за пределы функции main и не объявлять как статическую переменную. Строка 16 объявляет некоторые рабочие переменные. Заметим, что они помещаются в регистры. Это сделано для ускорения работы программы и получения более компактного объектного кода. Строка 17 резервирует буфер, в который вы вводите имя файла.
Строки 19 и 20 заполняют записываемый буфер символами "x", поэтому после создания файла мы можем их там увидеть. Строка 22 выводит значение ulimit для вашего процесса. Обратите внимание, что вызов ulimit возвращает количество блоков, поэтому мы должны умножить это число на 512. В результате мы получим общее количество байтов, которое может содержать файл. Строки 24-26 запрашивают имя файла, читают его и подготавливают экран для следующего сообщения. Строки 28-32 открывают файл с указанным именем для получения дескриптора файла. Файл открывается для записи и чтения, создается при необходимости и обрезается, если он уже существует. Если операция открытия файла заканчивается неудачей, выводится сообщение об ошибке, и программа завершается. Строки 34-42 выполняют запись. Цикл for бесконечен, поскольку в середине оператора нет проверки значения счетчика. Переменная bcnt постоянно увеличивается, пока выполняется запись. Строка 36 выполняет запись в файл. Если запись неудачна, выводится сообщение об ошибке и по оператору break осуществляется выход из цикла. Строка 41 выводит количество выполненных операций записи и количество записанных байтов. Обратите внимание, что данный оператор print содержит возврат каретки (\r), а не перевод строки. Это позволяет курсору оставаться в одной итемах. экране поверх старых значений. Экран не скроллируется, что удобно для наблюдения. Выполнение цикла продолжается до тех пор, пока системный вызов write не закончится неудачей и оператор break не прекратит цикл. Когда это происходит, выполнение продолжается со строки 43, которая печатает "end of program". Выполнение команды "ls -l" для записанного файла показывает, сколько байтов имеет файл максимального размера. Это количество должно совпадать с числом, которое сообщила вам программа lrgf. В данной главе представлена лишь небольшая часть возможных исследований внутренней работы файловых систем и устройств в UNIX. Некоторые из представленных программ могут быть неприменимы в вашей версии системы UNIX или в вашей конфигурации аппаратных средств или могут выглядеть в вашей системе иначе. Однако общие принципы сохраняются, и вы можете использовать рассмотренные средства в качестве основы для ваших собственных исследований.
ПРАВА ДОСТУПА К ТЕРМИНАЛУ
Поскольку терминальное устройство является файлом, оно имеет режимы прав доступа точно так же, как и все другие файлы. Эти режимы представляют собой защиту доступа к вашему терминалу. Если все пользователи имеют право записи на ваш терминал (это обозначается как rw--w--w-), то они могут записать на ваш экран все, что угодно, и вы никогда не узнаете, кто это сделал. Если вы хотите предупредить это, то выполните команду "chmod 600 `tty`", где символы ударения и обозначение tty соответствуют маршрутному имени вашего терминального файла. Более простой в использовании является команда UNIX'а mesg. Команда "mesg n" запрещает запись извне на ваш терминал. Ваши собственные процессы по-прежнему имеют доступ на запись.
Права доступа к терминалу связаны также с проблемой безопасности, которую мы рассмотрим в главе 9. Пока что отметим, что всякий раз, когда вы открываете файл (то ли для чтения, то ли для записи), вам возвращается дескриптор файла. Затем вы можете использовать этот дескриптор файла в системном вызове ioctl. Получение этого дескриптора файла подобно получению ключа к терминальному интерфейсу определенного пользователя. Любые изменения, производимые с помощью ioctl с данным дескриптором файла, вызывают немедленный эффект, и нарушитель защиты может читать все, что записывается или считывается с вашего терминала или даже заставить ваш терминал выполнять команды, которые присваивают себе неразрешенные права! Пользователь, который работает с этим терминалом, может никогда не узнать, что же произошло или кто это сделал.
Другим примером подобного рода является команда write(1). Она используется для установки связи по линии в реальном режиме времени, или "болтовни". Она общается с терминалом путем выполнения записи в файл устройства. Измените биты прав доступа, выключив их командой "mesg n", и никто не сможет выполнить команду write с вашим терминалом. Таким способом вы можете "снять трубку телефона", когда вы хотите, чтобы вам не мешали. Вместе с тем кто-нибудь мог бы сделать такое:
$ while : > do > clear > /dev/tty00 > done &
При этом создается фоновый бесконечный процесс (пока он не будет прекращен командой kill или выходом из системы), который посылает пользователю терминала tty00 символы очистки экрана. Как только этот пользователь что-нибудь набирает на экране, он тут же очищается. Большинство пользователей не могут даже понять, что происходит. Если это случится с вами, попытайтесь отключить права доступа к вашему терминалу. Если же тот, кто это делает, является суперпользователем (root), то никакие права доступа не смогут остановить его, поскольку суперпользователь не имеет ограничений по правам доступа к файлам. В этом случае возникает проблема для системного администратора!
Еще одна странная вещь наблюдается в случае, когда кто-либо посылает что-то на ваш терминал командой write, а вы отключаете права доступа. Команда write по-прежнему имеет доступ к вашему терминалу, пока она не закроет устройство. После закрытия она больше не имеет прав для открытия устройства. Представляется странным факт, что после получения доступа к терминалу последующее запрещение права доступа не оказывает никакого воздействия до тех пор, пока вы не прекратите работу с терминалом.
Размонтирование гибкого диска на устройстве
mntf -d - 1Размонтирование гибкого диска на устройстве 1.
mntlook /dev/hd* Поиск файловых систем на всех жестких дисках
umntsys Размонтирует все смонтированные файловые системы
Монтирование гибкого диска как файловой
1. $ mntf -s
Монтирование гибкого диска как файловой системы с возможностью записи-чтения и с использованием имен устройств, принятых в System V.
2. $ mntf -h -1 -r
Монтирование гибкого диска высокой плотности записи на устройстве 1 как файловой системы с возможностью только чтения и с использованием формата имен устройств, принятого в XENIX. Эта команда должна закончиться неудачей (устройство 1 имеет низкую плотность).
3. $ mntf -d -h
Размонтирование файловой системы на устройстве 0 с высокой плотностью записи и с использованием имен устройств, принятых в XENIX.
1. $ mntlook /dev/hd13
Поиск суперблока на устройстве с именем hd13. Это имя указывает устройство 1, третий раздел. Для просмотра разделов в среде XENIX нужно запустить программу fdisk. Для System V нужно воспользоваться командой iv.
2. $ mntlook /dev/fd0*
Поиск файловых систем на гибких дисках с любой плотностью записи, находящихся в устройстве 0. Это снова пример для системы XENIX.
3. $ for DEV in /dev/*[fh]d* > do > echo "checking device: $DEV" > mntlook $DEV > done
Данный цикл выполняется по всем именам устройств для гибих и жестких дисков по очереди. Каждое имя выводится на экран. Затем проверяется, содержит ли данное устройство файловую систему.
1. $ lrgf /dev/rfd0
Ввод имени устройства в ответ на запрос имени файла, в который будет производиться запись. При этом программа lrgf выполняет последовательную запись на гибкий диск неструктурированных данных. Тем самым проверяется, распознает ли драйвер устройства переполнение гибкого диска. Это важно знать при работе с командой cpio, которая предполагает, что драйвер устройства сообщит об остановке и запросе следующей дискеты.
2. $ lrgf /usr/tmp/lrg
Создание файла в файловой системе /usr. Большинство систем XENIX используют каталог /usr как отдельную файловую систему, отличную от корневой. Созданием файла в каталоге /usr /tmp мы можем проверить положение дел в этой часто используемой файловой системе.
3. $ lrgf /tmp/lrg
В данном случае создаваемый файл займет место в корневой файловой системе (если вы не имеете каталога /tmp в вашей собственной файловой системе). Потребуется не слишком много таких файлов для заполнения всех свободных блоков в корневой файловой системе.
4. $ lrgf /mnt/lrg
Создание файла на гибком диске в предположении, что на гибком диске имеется файловая система и она смонтирована в каталог /mnt.
5. $ F=0 $ while : > do > echo -r "--> Making file $F <--" > ./lrgf <<-! > $F > ! > echo > F=`expr $F + 1` > done
Данный цикл запускает программу lrgf бесконечное число раз. Счетчиком является переменная F. Она должна быть предварительно установлена в нуль, чтобы shell рассматривал ее как число, а не как символьную строку. Сначала выводится сообщение, содержащее имя создаваемого файла. Первым именем файла является 0. Программа lrgf запускается, используя в качестве входных данных "данный документ" (т.е. сам командный файл). В качестве ответа на вопрос об имени файла используется значение $F. Значение переменной F увеличивается, и программа lrgf вызывается снова. Именами файлов являются 0, 1, 2 и т.д. Это продолжается до тех пор, пока не останется больше свободного места. Вряд ли вы будете пользоваться этим часто, но для тестирования это прекрасное средство заполнить все свободное пространство. Если вы хотите увидеть, что делает ваша система, когда исчерпаны свободные блоки, примените данный командный файл.
ПРОТОКОЛ ОПЕРАЦИЙ ВВОДА/ВЫВОДА ТЕРМИНАЛА
Протокол работы терминала представляет собой согласованный набор сигналов, позволяющих системе правильно интерпретировать вводимые с клавиатуры строки. Протокол необходим по четырем причинам. Первой является поддержка входной обработки специальных символов, таких как символы удаления и прекращения работы программы. Во-вторых, нам необходимо поддерживать обработку выводимой информации, например, вставку символов задержки или изменение последовательности возврат каретки/перевод строки. Третьей причиной является поддержка режимов необработанного и "канонического" ввода. Эти два режима позволяют пользовательским программам получать данные или по одному символу, или по одной строке. Последняя причина введения терминального протокола - желание сделать так, чтобы пользователь мог сам изменять параметры конфигурации терминала.
Содержимое терминальной подсистемы показано на рис. 7-2. Рисунок разбит на три части: слева - область пользователя, посредине - область ядра и справа - область устройства. Этот рисунок показывает, как передаются данные между терминалами и программами пользователя.
Рисунок 7-2.
Управление протоколом терминала

Когда какой-либо процесс читает символы с устройства, данные начинают двигаться от буфера драйвера устройства, который называется dbuf. Из этого буфера данные попадают в приемный буфер, управляемый ядром. Приемный буфер читается подпрограммой ядра с именем ttin(), и данные помещаются в структуру clist, называемую необработанной очередью. (Слово "необработанная" означает, что над символами пока что не производилось никакой обработки.) В то же время символы также помещаются в выходную очередь, что позволяет системе выполнять эхо-отображение символов по мере их ввода.
После этого подпрограмма canon() превращает необработанную очередь в каноническую. ("Каноническая" означает применение правил, которые в данный момент установлены системой для выполнения специальных функций над строкой текста, например для обработки символа удаления.) Такая обработка позволяет преобразовать данные перед тем, как их получит процесс пользователя. Последней подпрограммой является ttread(), которая читает символы из канонического буфера в буфер пользовательского процесса в области данных процесса.
Когда символы записываются из процесса пользователя на терминал, они проделывают почти такой же маршрут в обратном направлении: от области процесса к области драйвера устройства. Основное отличие в подпрограмме записи заключается в том, что эти данные проходят на один буфер меньше. От процесса пользователя символы передаются в выходную очередь ядра подпрограммой ttwrite(), а затем в буфер передачи с помощью подпрограммы ttout(). Из буфера передачи они пересылаются непосредственно в приемный буфер драйвера устройства с помощью подпрограммы dzxint().
РАСЧЕТЫ, СВЯЗАННЫЕ С БЛОКАМИ
Еще одним важным вопросом, имеющим отношение к физическим устройствам и логическим файловым системам, является определение местонахождения определенного блока на жестком диске. Номер этого блока вычисляется по номерам цилиндра, дорожки и сектора. Знание номеров блоков становится важным, когда на диске появляется дефектное место. Это дефектное место отмечается номерами цилиндра и головки. Вам необходимо вычислить, какие блоки попадают в дефектную область и занести их номера в таблицу дефектных блоков. Обратная задача также важна. Если программа fsck начинает сообщать, что где-то появился дефектный блок, то каким образом мы можем узнать номера цилиндра, головки, сектора и т.д. для данного дефектного блока? Такое обратное вычисление сделать очень тяжело, если не невозможно. Во-первых, номер блока представляет собой произведение четырех чисел. Трудно узнать, какие именно эти числа. Кроме того, файловые системы могут использовать информацию вида база/смещение, поэтому блок номер 1 в файловой системе в действительности является блоком номер 1382 на диске. Определить, какого вида информация была использована в данном случае, тоже тяжело. Конкретные данные в следующем примере относятся к вполне определенной машине, но на других машинах используются подобные зависимости. Эти данные относятся к машине с жестким диском объемом 20 Мбайт с системами XENIX/DOS.
Характеристики устройства:
1 диск = 615 цилиндров, или 615 цилиндров/диск 1 цилиндр = 4 головки (дорожки), или 4 головки/цилиндр
Промышленный стандарт:
1 дорожка = 17 секторов, или 17 секторов/дорожку 1 сектор = 512 байт, или 512 байт/сектор 1 Кбайт = 1024 байта = 2^10 1 Мбайт = 1024 килобайта = 2^20 = 1 048 576 байт
Характеристики устройства различны для разных устройств, но промышленный стандарт для числа секторов на дорожку и байтов на сектор остается одинаковым. В табл. 7-4 показаны примеры характеристик различных устройств.
Таблица 7-4
Размеры жестких дисков и их конфигурация
Число цилиндров | Число головок | Мегабайты |
981 |
3 |
25 |
697 |
5 |
30 |
981 |
5 |
42 |
925 |
7 |
55 |
1024 |
8 |
71 |
Вы видите, что число цилиндров и число дорожек различны для устройств с разным объемом. Определить максимальный объем дисковой памяти можно перемножением всех чисел. В следующем примере вычисляется общий размер в байтах для предыдущих данных.
615 цил 4 дор 17 сек 512 байт ------- * ----- * ------ * -------- = 21 411 840 байт/диск 1 диск 1 цил 1 дор 1 сек 21411840 байт 1 мегабайт ------------- * ------------ = 20.4 мегабайта/диск 1 диск 1048576 байт
Отметим, что если вы верно указываете единицы измерения, то они попарно сокращаются, за исключением одной снизу и одной сверху, и в результате получаются нужные единицы измерения в ответе. Таким образом, в первой строке вычислений цилиндры, дорожки и секторы сокращаются, и в качестве единиц измерения остаются байты/диск. Поскольку мы имеем дело с таким большим количеством различных единиц измерения, вычисления такого рода (иногда называемые "размерный анализ") убеждают нас, что мы понимаем, о чем идет речь. Объем доступной дисковой памяти уменьшается после форматирования, локализации дефектных блоков и размещения на диске файловой системы. Однако наш пример показывает, что все размеры согласуются между собой. Важным моментом, на который необходимо обратить внимание, является использование разных терминов. Иногда применяется число головок на цилиндр, а иногда число дорожек на цилиндр. При использовании каждого из этих терминов соответствующим образом изменяются и другие термины. Существует такая взаимосвязь:
цилиндр, дорожка, сектор = физический сектор цилиндр, головка, байт = блок
Эти две записи выражают в точности одно и то же. Когда вы используете запись вида цилиндр/дорожка/сектор, то в результате получаете физический сектор. Используя запись вида цилиндр/головка/байт, вы получаете в результате номер блока. Следует помнить, что ГОЛОВКА - это то же самое, что и ДОРОЖКА. Если вы это запомните, все остальное встанет на свое место. Ниже приводятся некоторые общие вычисления, которые часто всплывают, когда вы работаете с диском на низком уровне. Эти примеры в большей степени относятся к самому устройству, чем к системе UNIX. Однако после того, как вы поймете эту информацию о диске, вам будет легче понять, как работает UNIX на этом уровне. 1. Сколько дорожек имеет диск?
615 цил 4 дор Решение: ------- * ----- = 2460 дор/диск 1 диск 1 цил2. Сколько байт в дорожке? 17 сек 512 байт Решение: ------ * -------- = 8704 байт/дор 1 дор 1 сек3. Сколько дорожек в одном мегабайте? 2460 дор 1 диск Решение: -------- * ------ = 123 дор/Мб 1 диск 20 Мб4. Сколько цилиндров в одном мегабайте? 1 цил 2460 дор Решение: ----- * -------- = 30 цил/Мб, 4 дор 20 Мб 615 цил 1 диск 123 дор или ------- * -------- * ------- = 30 цил/Мб 1 диск 2460 дор 1 Мб5. Дан цилиндр 47, дорожка 2, сектор 4. Какой физический номер сектора? Решение: Сначала мы обращаем внимание на то, что вопрос касается секторов. В качестве единиц измерения даны цилиндр, дорожка и сектор. Как перевести их в другие единицы? Мы знаем, что головки - это то же самое, что и дорожки, поэтому в вычислениях нужно использовать 4 головки вместо 4 дорожек: 4 дор 17 сек 17 сек 47 цил * ----- * ------ + 2 дор * ------ + 4 сек = 1 цил 1 дор 1 дор = 3196 сек + 34 сек + 4 сек = = сектор 3234
РАЗБИЕНИЕ ДИСКОВ НА РАЗДЕЛЫ
Части, или области диска, известны как разделы. Раздел может содержать файловую систему, которая сгенерирована командой mkfs(1), или же может содержать неструктурированные данные, доступ к которым выполняется с помощью команды 'cpio -o'.
В системе XENIX управление разделами осуществляется программой fdisk, которая концептуально подобна своей тезке в системе MS-DOS. В других системах UNIX используются другие имена. Например, в системе AT&T 7300 UNIX PC используется программа iv, что значит "format" (хотите верьте, хотите нет). Как упоминалось ранее, обычно разделы содержат одну файловую систему. В настоящее время в системах XENIX и SCO XENIX у вас есть возможность "разделить раздел" на более мелкие части для получения большего количества файловых систем. Это сделано по той причине, что машины с системами DOS и XENIX ограничены четырьмя дисковыми разделами, а у вас может возникнуть желание иметь больше файловых систем, чем число доступных разделов. В системе AT&T 7300 UNIX PC управление разделами диска осуществляется по списку начальных номеров дорожек. Вы можете создать столько разделов, сколько хотите. Каждый компьютер имеет свои преимущества и недостатки.
В каталоге /dev находятся имена как блочных устройств, так и символьных. По этим именам вызываются различные драйверы устройств. Ниже приводится пример списка интерфейсов жестких дисков.
| | brw------- 1 sysinfo sysinfo 1, 0 Feb 18 17:07 /dev/hd00 | brw------- 1 sysinfo sysinfo 1, 15 Feb 18 16:59 /dev/hd01 | brw------- 1 sysinfo sysinfo 1, 23 Feb 18 16:59 /dev/hd02 | brw------- 1 sysinfo sysinfo 1, 31 Feb 18 16:59 /dev/hd03 | brw------- 1 sysinfo sysinfo 1, 39 Feb 18 16:59 /dev/hd04 | brw------- 1 sysinfo sysinfo 1, 47 Feb 18 17:07 /dev/hd0a | brw------- 1 sysinfo sysinfo 1, 55 Feb 18 17:09 /dev/hd0d | crw------- 1 sysinfo sysinfo 1, 0 Feb 18 16:59 /dev/rhd00 | crw------- 1 sysinfo sysinfo 1, 15 Feb 18 16:59 /dev/rhd01 | crw------- 1 sysinfo sysinfo 1, 23 Feb 18 16:59 /dev/rhd02 | crw------- 1 sysinfo sysinfo 1, 31 Feb 18 16:59 /dev/rhd03 | crw------- 1 sysinfo sysinfo 1, 39 Feb 18 16:59 /dev/rhd04 | crw------- 1 sysinfo sysinfo 1, 47 Feb 18 16:59 /dev/rhd0a | crw------- 1 sysinfo sysinfo 1, 55 Feb 18 17:09 /dev/rhd0d |
Имена файлов с префиксом hd указывают блочные устройства, а с префиксом rhd - "неструктурированные" символьные устройства. Не все символьные устройства являются неструктурированными блочными устройствами. Терминалы являются символьными устройствами, как мы уже видели ранее в данной главе. В табл. 7-2 показаны различные характеристики этих двух типов устройств.
Таблица 7-2
Сравнение блочных и символьных устройств
Блочное устройство | Символьное устройство |
/dev/hd0, /dev/fd0 |
/dev/rhd0, /dev/rfd0 |
буфер управляется ядром системы, медленное устройство | буферизация отсутствует, быстрое устройство |
произвольное размещение блоков данных | последовательное размещение блоков данных |
доступ через файловую систему | доступ непосредственно на диск |
cpio -p |
cpio -o, -i |
mkfs, mount, df, du, fsck, fsdb |
tar |
Давайте рассмотрим устройство /dev/hd01 из приведенного выше списка. Если вы хотите адресоваться к физическому разделу на диске как к блочному устройству, вы можете создать на нем файловую систему. Для этого вам нужно выполнить следующую команду, которая создаст файловую систему размером 5000 Кб (5 Мб) на жестком диске:
# mkfs /dev/hd01 5000
Внутри раздела (размером не менее 5000 Кб) размещается файловая система. Файловая система содержит суперблок, списки свободных блоков и т.п., то есть все, что необходимо для хранения файлов, которые размещаются здесь. Однако, создание файловой системы совсем НЕ означает, что вы сразу же можете получить к ней доступ. Сначала вам необходимо смонтировать файловую систему. Команда для выполнения этой операции может иметь такой вид:
# mount /dev/hd01 /mount_pt
Файлы могут быть помещены в дисковый раздел командами mv или cp, путем переадресации вывода в каталог с этим именем, например, >/mount_pt/file.
Для использования раздела диска в качестве области неструктурированных данных, а не блочного устройства, применяйте файл с именем символьного устройства, которое начинается с буквы r. Например, для использования того же устройства, что и в предыдущем примере, в качестве неструктурированного устройства, укажите имя /dev/rhd01. (Из списка устройств вы видите, что это символьное устройство, так как права доступа в первой колонке начинаются с символов crw, а не brw). Это устройство (и соответствующий раздел) в данный момент не имеет файловой системы и является просто набором байтов. Единственным ограничением является то, что вы можете записать в этот раздел не более 5 Мб данных.
Вот пример команды, использующей неструктурированное устройство:
$ find . -print | cpio -ocBv > /dev/rhd01
РАЗМЕРНЫЕ ПАРАМЕТРЫ
Теперь, когда мы рассмотрели взаимоотношения между устройствами и файловыми системами и некоторые параметры, связанные с форматами дисков, давайте обратимся к гайкам и болтикам этих устройств. Хотя основная часть этой информации может показаться экзотичной, она может оказаться важной при определенных обстоятельствах. Например, для установки системы UNIX на новую машину вам нужно разбить диск на сегменты и понимать, каким образом UNIX фактически располагается на диске. Если вы создаете программы, которые выполняют какую-либо операцию низкого уровня с диском, вам, очевидно, необходимо понимать, что вы делаете. Администраторам, поскольку они должны добавлять новые устройства в систему, необходимо уметь определять количество файловых систем (т.е. сколько можно создать разделов на диске), их размеры и знать, каким образом установить файловые системы в эти разделы. Администраторы должны также уметь писать или модифицировать драйверы устройств. Наконец, при работе с дисками возникают проблемы, такие как плохие блоки, которые необходимо изолировать и с которыми приходится иметь дело.
РАЗМЕРЫ БЛОКОВ
System V является последним достижением ветви фирмы AT&T в фамильном дереве UNIX. Это означает, что System V содержит последние правки, внесенные в исходную систему UNIX. Эти правки предназначены для того, чтобы сделать UNIX жизнеспособным и стойким коммерческим продуктом. Для повышения устойчивости были внесены изменения, касающиеся работы с файлами и размеров их блоков. Обычно обмен данными с дисками осуществляется блоками по 512 байтов. Дисковая аппаратура имеет дело именно с таким размером. Для учета этого факта UNIX первоначально использовал 512-байтные блоки внутри файловой системы, что, возможно, облегчало написание программ и создавало впечатление, что так и нужно. Однако нельзя отрицать, что при этом UNIX может работать медленно! Для ускорения работы внутренние программы в настоящее время используют блоки размером 1024 байта. Сам диск должен выполнить два обращения к 512-байтным блокам, но в системе две эти операции чтения рассматриваются как одна операция чтения блока размером 1024 байта. Единственная проблема заключается в том, что одни утилиты выдают результаты в 512-байтных блоках, а другие - в 1024-байтных, в зависимости от того, когда они были написаны. Когда сильно приближаются пределы свободного пространства на диске, вам действительно нужно знать, с каким размером вы имеете дело. Для лучшего понимания проблемы размеров блоков, в табл. 7-3 показано, какие утилиты какой размер блока используют. Эта информация получена в основном из System V на машине VAX, из другого варианта System V и из XENIX. Эти значения могут отличаться на разных машинах, но идея сохраняется. Вы видите, что большинство утилит выдают результат в блоках размером 512 байтов, но утилиты, относящиеся к файловой системе, выдают результат в 1024-байтных блоках. Поскольку UNIX обращается к дисковому пространству поблочно, важно уметь точно вычислять, сколько свободного пространства в файловой системе. Весьма плоха ситуация, когда имеется какой-то большой файл в редакторе vi (который использует файл /tmp для промежуточного редактирования), а на диске недостаточно места для записи временного файла редактора vi в реальный файл на диске. На самом деле это может случиться на персональных машинах с ограниченным (скажем, 20 Мбайт) объемом жесткого диска.
Таблица 7-3
Размеры блоков для различных команд системы UNIX
512 байтов/блок | 1024 байта/блок |
ls -s | fdisk (размеры разделов) |
sum | mkfs |
cpio, df, du | fsck |
РАЗМЕРЫ ФАЙЛОВ
В основном при работе в системе UNIX мы считаем, что ее ресурсы безграничны. Например, мы не заботимся о том, что созданный файл получится "слишком большим", а это не так уж редко в персональных компьютерах на гибких дисках. Если же мы занимаемся сопровождением и администрированием системы UNIX, то мы должны быть готовы иметь дело с ситуациями, когда превышаются различные предельные значения системы. Всегда лучше исследовать эти вопросы заранее в некритичных ситуациях, поэтому давайте рассмотрим пределы размеров файлов и их смысл. Некоторые параметры "зашиты" в ядро системы при ее генерации. Одним из таких значений является максимальный размер файла. Он определяет наибольшее число блоков, которые может занимать файл. Этот параметр тесно связан с принятым в UNIX методом использования индексных дескрипторов файла (inodes). Это наборы указателей, среди которых первые десять указывают на блоки данных, следующий указывает на другую таблицу, следующий - на таблицу, указывающую на таблицу и т.д. Имеется еще одно ограничение размера файла, которое определено для каждого пользователя во время работы в системе - число ulimit (user limit - пользовательский предел). Это значение устанавливается в момент вашей регистрации в системе и представляет собой число блоков по 512 байт, которые вы можете записать в любой заданный файл. В shell'е имеется команда ulimit, которая при ее вызове без аргументов выводит это число. Эта же команда позволяет вам уменьшить ваше значение ulimit. Только суперпользователь (root) может УВЕЛИЧИТЬ значения ulimit. Побочным эффектом уменьшения значения ulimit является то, что вы не можете снова увеличить его до регистрационного значения. Значение ulimit остается таким же на все время работы вашего shell, поэтому для восстановления регистрационного значения вам необходимо выйти из системы, а затем снова зарегистрироваться. Еще одним интересным моментом является то, что если вы установите ваше значение ulimit равным 0, вы не сможете создать никакие файлы! Максимально допустимым размером файла в данном случае является нулевой, поэтому никакой файл не может быть создан. Это представляется достаточно резонным, однако существуют такие ситуации, когда файл нулевого размера МОЖЕТ существовать. Опять же, для восстановления вашего обычного значения ulimit необходимо выйти из системы, а затем снова зарегистрироваться. Как отмечалось ранее, увеличить значение ulimit может только суперпользователь. Эта процедура довольно проста. Сначала нужно увеличить значение ulimit командой ulimit, а затем запустить shell. Этот новый shell имеет новое значение ulimit. Если мы хотим, чтобы система загружалась с shell, имеющим большее значение ulimit, мы можем установить программу в inittab (таблице инициализации системы), чтобы эта операция выполнялась автоматически. Ниже приводится пример программы, которая изменяет значение ulimit и запускает shell с этим новым значением. Напомним, что эта программа может быть запущена только суперпользователем.
1 #include 2 #include
4 main() 5 { 6 long v1, v2, v3, newlimit = 5120;
8 v1 = (long)ulimit(UL_GFILLIM, 0L); 9 v2 = (long)ulimit(UL_SFILLIM,newlimit); 10 v3 = (long)ulimit(UL_GFILLIM, 0L);
12 printf("v1: %ld v2: %ld ulim: %ld\n",v1,v2,v3); 13 setuid(getuid()); 14 execl("/bin/sh","ulimit sh", 0); 15 }
Значение ulimit является возвращаемым значением системного вызова ulimit. Первый вызов ulimit в строке 8 получает исходное значение по умолчанию. Это значение сохраняется в переменной v1. Вызов в строке 9 устанавливает новое значение ulimit равным значению переменной newlimit. Если этот вызов оканчивается неудачей, переменной v2 присваивается возвращаемое значение -1, и мы видим это по распечатке, которую выдает строка 12. Если вызов был успешным, возвращаемым значением является новое значение ulimit, и это мы тоже видим. Затем вызов в строке 10 получает это значение ulimit. Это или новое значение, или старое, в зависимости от того, была ли успешной попытка изменить ulimit. В строке 13 значение идентификатора текущего процесса устанавливается равным значению идентификатора пользователя, запустившего данный процесс. Это сработает только в том случае, если пользователь, запустивший данный shell, имеет более низкий идентификатор, чем сам процесс. Цель заключается в том, чтобы предоставить возможность обычным пользователям запускать данный процесс, давая им временно права суперпользователя. (Не оставляйте исходный текст этой программы в системе, поскольку кто-то может превратить ее в "лазейку" и перекомпилировать ее - в главе 9 мы увидим такого рода дыры в системе защиты.) Строка 14 запускает shell. Аргументом этого shell является строка "ulimit sh". Эта строка будет выведена на экран, если мы выполним команду "ps -ef". Данный shell имеет новое значение ulimit. Возможность изменить значение ulimit позволяет нам определить наибольший возможный размер файла. Создание одного или нескольких таких файлов максимального размера полезно в целях тестирования. Например, полезно выяснить, сколько данных может содержать гибкий диск без переполнения или что произойдет, когда система выйдет за пределы свободных блоков. Мы хотим понять, как ведет себя система в таких ситуациях.
ИМЯ: umntsys
СИСТЕМА UNIX И АППАРАТУРА
Перед тем, как углубиться в сущность вопроса, давайте обсудим некоторые элементарные факты, которые мы должны помнить при рассмотрении всех составляющих системы UNIX. Сердцем аппаратуры является центральный процессор (CPU), который исполняет инструкции, управляющие машиной, и фактически осуществляет всю работу. Операционная система необходима для руководства работой, выполняемой центральным процессором, и для обеспечения интерфейса между ним и ресурсами, требуемыми для того, чтобы сделать что-то полезное: оперативной памятью, внешней памятью и другими периферийными устройствами, такими как терминалы и принтеры.
Операционная система, особенно такая высокоразвитая, как UNIX, имеет множество утилит и характерных особенностей, но сейчас речь не об этом. Сердцем операционной системы (в данном случае UNIX) является ядро (kernel). Ядро управляет процессами и руководит выполняемой работой. Оно также является своего рода мостом между аппаратурой и внешним миром. В данной главе мы обратим внимание на основные взаимоотношения между ядром, процессами и аппаратурой.
В конечном итоге система должна взаимодействовать с внешними устройствами. Наличие базовых знаний об устройствах весьма важно для полного понимания того, как UNIX общается с внешним миром.
При работе с машиной много времени тратится на передачу данных в машину и из нее, а это значит, что необходимо иметь дело со множеством различных типов устройств, каждое из которых имеет свой "характер" и особенности.
К нашему счастью, UNIX был разработан так, чтобы облегчить управление данными и устройствами настолько, насколько это возможно. К нашему несчастью, имеется, по всей видимости, несократимый объем знаний, которыми мы должны овладеть обязательно. На рис. 7-1 показана общая структура операционной системы UNIX. Мы видим, что со стороны ядра обращение ко всем внешним периферийным устройствам выполняется как к файлам устройств. Каждый тип устройств имеет свой собственный драйвер и специфическую архитектуру, но обращение к каждому устройству выполняется одинаковыми методами. Мы увидим, как использовать различные способы доступа к устройствам и определим, какие способы наиболее эффективны.
Рисунок 7-1
Модель среды системы UNIX

UNIX обращается к периферийным устройствам через "специальные файлы". Имеется два типа специальных файлов: блочные и символьные. Оба типа имеют свое предназначение и особенности. Блочный (например, /dev/hd0) использует буферизацию и позволяет получить доступ к большим объемам данных на жестком диске. Символьный (например, /dev/tty00 или /dev/rfd0) не использует значительную буферизацию, а выполняет обмен с устройством по одному символу за обращение. Даже несмотря на особые свойства таких файлов, для них поддерживается все тот же механизм защиты, что и для всех других файлов в системе.
Первая область, которую мы рассмотрим - терминальные устройства и работа с ними. Представленные программы включают в себя средство под названием 'c' для быстрой очистки экрана, а также пример программы, которая считывает значения нажатых клавиш и выполняет опрос нажатия одной клавиши. Мы также рассмотрим пример файла описания терминала (termcap), который обеспечивает доступные определения характеристик терминала.
Затем мы рассмотрим дисковые устройства - жесткие и гибкие диски.
Мы увидим, что имеются различные способы просмотра разделов диска с использованием файлов устройств.
В дополнение к работе с устройствами мы рассмотрим файловые системы на жестком диске. Всем нам известно, что система UNIX существенно ориентирована на диски, поэтому чем больше мы знаем о файловых системах, тем лучше для нас. Для более полного понимания разделов диска и файловых систем мы представим три программных средства. Средство lrgf проверяет граничные значения параметров файловой системы путем создания файла наибольшего возможного размера в вашей системе. Средство mntf обеспечивает удобный способ монтирования и размонтирования гибких дисков. Наконец, средство mntlook выполняет поиск немонтированных файловых систем, которые представляют собой потенциальную опасность.
ТЕКСТ ПРОГРАММЫ
1 char id[] = "@(#) c v1.0 Fast clear screen Author: Russ Sage"; Быстрая очистка экрана 3 #define FF "\014" 5 main() 6 { 7 if (write(1, FF, 1) == -1) 8 write(2,"c: write error to stdout\n",25); ошибка записи в стандартный вывод 9 }
1 : 2 # @(#) mntf v1.0 Mount floppies Author: Russ Sage Монтирование гибких дисков 4 CMD="/etc/mount" 5 DIR="/mnt" 6 DRIVE="0" 7 DENSITY="48ds9" 8 SYSTEM="xenix" 10 if [ $# -gt 0 ] 11 then for ARG in $* 12 do 13 case $ARG in 14 -d) CMD="/etc/umount" 15 DIR="";; 16 -h) DENSITY="96ds15";; 17 -1) DRIVE="1" 18 if [ -d /mnt1 ] 19 then DIR="/mnt1" 20 else echo "the directory /mnt1 does not exist" >&2 нет каталога /mnt1 21 echo "using the directory /mnt instead" >&2 используется каталог /mnt 22 fi;; 23 -r) DIR="$DIR -r";; 24 -s) SYSTEM="sysv";; 25 *) echo "mntf: invalid argument $ARG" >&2 26 echo "usage: mntf [-d] [-h] [-1] [-r] [-s]" >&2 27 echo " -d dismount" >&2 28 echo " -h high density" >&2 29 echo " -1 use drive 1" >&2 30 echo " -r read only" >&2 31 echo " -s System V device" >&2 32 echo " default: mount XENIX drive 0 48 tpi to " >&2 33 echo " /mnt as a read/write filesystem" >&2 34 exit 1;; 35 esac 36 done 37 fi 39 case $SYSTEM in 40 sysv) $CMD /dev/fp${DRIVE}21 $DIR;; 41 xenix) $CMD /dev/fd${DRIVE}${DENSITY} $DIR;; 42 esac
1 static char id[] = "@(#) mntlook v1.0 Look for mounts Author: Russ Sage"; Поиск файловых систем
3 #include 4 #include 5 #include 6 #include 7 #include
9 #define BSIZ 512
11 main(argc,argv) 12 int argc; 13 char *argv[]; 14 { 15 struct filsys sb; 16 int d, dev; 17 char buf[BSIZ];
19 for (d = 1; d < argc; d++) 20 { 21 if (argv[d][0] == '-') 22 { 23 printf("mntlook: invalid argument %s\n", argv[d]); 24 printf("usage: mntlook device [device ...]\n"); 25 continue; 26 } 27 if ((dev = open(argv[d],O_RDONLY)) < 0) 28 { 29 sprintf(buf, "cannot open %s",argv[d]); невозможно открыть 30 perror(buf); 31 continue; 32 }
34 /* throw away first block */ обойти первый блок 35 if (read(dev, &sb, sizeof(sb)) == -1) 36 { 37 perror("cannot read block 0"); не читается блок 0 38 continue; 39 }
41 /* block 1 is the superblock */ блок 1 является суперблоком 42 if (read(dev, &sb, sizeof(sb)) == -1) 43 { 44 perror("cannot read block 1"); не читается блок 1 45 continue; 46 }
48 if (sb.s_magic == S_S3MAGIC) 49 { 50 printf("\nDEV: %s --> VALID file system\n",argv[d]); 51 printf("filsys: %s\n",sb.s_fname); 52 printf("pack : %s\n",sb.s_fpack); 53 printf("type : %s byte block\n", 54 (sb.s_type == S_B512) ? "512" : "1024"); 55 printf("magic : %lx\n",sb.s_magic); 56 }
58 close(dev); 59 } 60 }
1 : 2 # @(#)umntsys v1.0 Unmount all file systems Author: Russ Sage Размонтирование всех файловых систем
4 if [ "$#" -gt 0 ] 5 then echo "umntsys: too many arguments" >&2 6 echo "usage: umntsys" >&2 7 exit 1 8 fi
10 /etc/mount | sed -n -e '/^\/ /d' -e 's/^.* on \(.*\) read.*/umount \1/p' | sh -
1 char id[] = "@(#) lrgf v1.0 Create the largest file Author: Russ Sage Создать файл максимального размера
3 #include 4 #include 5 #include 6 #include
8 #define FSIZ 512 9 #define BSIZ 1024
11 long ulimit(); 12 char buf[BSIZ];
14 main() 15 { 16 register int n, fd, bcnt; 17 char file[FSIZ];
19 for (bcnt=0; bcnt
ТЕРМИНАЛЬНЫЕ УСТРОЙСТВА
Драйверы терминальных устройств являются одними из самых сложных драйверов устройств. Причина этого заключается в том, что существует множество уровней программного обеспечения, которые поддерживают характеристики интерактивных терминалов. При работе терминала по последовательной линии связи необходима мощная поддержка для того, чтобы облегчить его использование. Различные установки, которые может иметь терминал, программируются командами stty(1) и ioctl(2). Команда termio(7) также описывает различные аспекты протокола работы терминала.
ВОЗМОЖНОСТИ ТЕРМИНАЛОВ
Теперь, когда мы имеем понятие о характеристиках терминальных интерфейсов, давайте перейдем к возможностям терминалов. ВОЗМОЖНОСТИ это те функции, которые выполняет аппаратура терминала. Если мы знаем эту информацию, мы можем создать список возможных функций и использовать его, например, для работы редактора vi. Это осуществляется при помощи специального файла данных termcap (terminal capabilities - возможности терминала), который описывает возможности терминала.
Большинство из существующих типов терминалов уже занесены в файл termcap. Это файл /etc/termcap. Файл termcap и редактор vi происходят из системы Berkeley. Такая комбинация оказалась настолько эффективной, что была перенесена в System V. В более поздней System V Release 3 файл termcap уже не используется, его заменяет файл terminfo фирмы AT&T. Мы применяли файл terminfo совместно с командным файлом today в главе 5, но подробное обсуждение terminfo выходит за пределы нашей книги. В системе Berkeley файл termcap по-прежнему остается стандартом, и он заслуживает более детального рассмотрения.
Имеется документация по termcap, но не думайте, что вы из нее много узнаете. В документации приводятся имена и однострочные описания поддерживаемых функций, но нет информации о том, как формировать из ничего записи этого файла. Самое лучшее, что мы можем посоветовать, это взять имеющуюся запись и изменить ее.
В качестве примера мы приводим запись файла termcap для компьютера Apple II. Это описание распространено в различных формах, но наш пример относится к видеоплате Videx UltraTerm для Apple II+. Заметим, что возможности, предоставляемые файлом termcap, являются обычно подмножеством тех возможностей, которые фактически предоставляет аппаратура. В частности, видеоплата в компьютере Apple выполняет некоторые функции, которые не умеет делать файл termcap, например комбинации настроечных битов для изменения видеоатрибутов. Самое большее, что мы можем сделать с видеоатрибутами посредством файла termcap, это включить или выключить инверсное отображение.
С другой стороны, некоторые типы аппаратуры не обладают всеми возможностями, обеспечиваемыми файлом termcap. Например, одной из функций, которой недостает в Apple, является функция прокрутки ("scroll reverse"). Аппаратура не делает этого, поэтому и в termcap нет необходимости иметь описание этой функции. Вместо скроллинга (прокрутки) вниз, отображаемый на экране текст продолжает выводиться в верхней строке.
Для того, чтобы получить представление о том, как termcap соотносит общие характеристики терминала с конкретными возможностями, сравним терминалы Apple и vt52. Две соответствующие записи в termcap имеют много похожих функций, но совершенно разные коды для их выполнения. Приведем пример содержимого файла termcap:
a2|aii|Apple II with UltraTerm :\ :bl=^G:\ :bs:\ :cd=^K:\ :ce=^]:\ :cl=^L:\ :cm=^^%r%+ %+ :\ :co#80:\ :cr=^M:\ :do=^J:\ :ho=^Y:\ :kb=^H:\ :kd=^J:\ :kl=^H:\ :kr=^\\:\ :ku=^_:\ :le=^H:\ :li#24:\ :nd=^\\:\ :nl=^J:\ :se=^O:\ :so=^N:\ :up=^_:
В табл. 7- 1 представлен список функций файла termcap с сопоставлением терминалов Apple и vt52. Если какая-либо функция отсутствует у одного или другого терминала, это отмечается словом "нет".
Таблица 7-1
Терминальные возможности и их конкретные значения
Функция | Apple II | vt52 |
bl - звуковой сигнал (bell) | ^G | ^G |
bs - возврат на шаг по коду ^H (can backspace with ^H) | да | да |
cd - очистка до конца экрана (clear to end of display) | ^K | \EJ |
ce - очистка до конца строки (clear to end of line) | ^] | \EK |
cl - очистка всего экрана (clear entire screen) | ^L | \EH\EJ |
cm - движение курсора (cursor motion) | ^^%r%+ %+ | \EY%+ %+ |
co - число позиций в строке (number of columns in a line) | #80 | #80 |
cr - возврат каретки (carriage return) | ^M | ^M |
do - сдвиг на строку вниз (down one line) | ^J | ^J |
ho - курсор в начало экрана(без команды cm) (home cursor) | ^Y | \EH |
kb - код клавиши backspace (sent by backspace key) | ^H | ^H |
kd - код клавиши "стрелка вниз" (sent by down arrow key) | ^J | \EB |
kl - код клавиши "стрелка влево" (sent by left arrow key) | ^H | \ED |
kr - код клавиши "стрелка вправо" (sent by right arrow key) | ^\\ | \EC |
ku - код клавиши "стрелка вверх" (sent by up arrow key) | ^_ | \EA |
le - курсор влево (cursor left) | ^H | ^H |
li - число строк экрана (number of lines per screen) | #24 | #24 |
nd - нестирающий пробел (nondestructive space) | ^\\ | \EC |
nl - символ перевода строки (newline character) | ^J | ^J |
pt - наличие аппаратной табуляции (has hardware tabs) | нет | да |
se - обычный экран (end stand out mode (normal)) | ^O | нет |
so - инверсный экран (begin stand out mode (inverse)) | ^N | нет |
sr - прокрутка (scroll reverse) | нет | \EI |
ta - символ табуляции (tab) | ^I | ^I |
up - сдвиг вверх на строку(up a line) | нет | ^_ |
Файл termcap позволяет вам спрятать основную информацию о специфических характеристиках терминала (за исключением характеристик, которые могут отсутствовать у терминала, или специальных возможностей, которые не описаны в termcap). Это значит, что вы можете создавать терминально-независимые программы. При этом вам нет необходимости изменять все специфические обращения к терминалу, такие как ESC-последовательности (символы, указывающие терминалу, что передаваемые после них символы (символ) должны интерпретироваться как управляющие коды). Это символы (\E) для терминала vt52 и (^) для Apple.
Наилучший пример - способ использования файла termcap редактором vi. Он начинает выполнять указанную ему функцию, например движение курсора, после чего ставит вопрос: "Какой код функции, которую мы хотим выполнить?". Затем он ищет соответствующую последовательность в той информации, которую предоставляет termcap.
С другой стороны, иногда вам необходимо оптимизировать какую-либо функцию по скорости, заставив ее посылать коды непосредственно на определенный терминал. В этом случае вам опять-таки полезен файл termcap, поскольку вы можете найти необходимую информацию в соответствующем файле termcap, после чего закодировать эту информацию в вашей программе. Это мы и делаем в первой инструментальной программе данной главы - программе 'c'.
Ниже уровня известной нам области
Ниже уровня известной нам области файловых систем находится мир устройств и их драйверов. В данной главе мы исследуем некоторые методы, необходимые для работы с терминалами, дисками и непосредственно файловыми системами. Программное средство 'c' иллюстрирует доступ к терминалу на примере операции быстрой очистки экрана. Следующие три средства - mntf, mntlook и umntsys - имеют дело с монтированием и размонтированием файловых систем. Наконец, средство lrgf позволит вам проверить пределы емкости вашей файловой системы.
ЗАГРУЖАЕМЫЙ ДИСК И АВТОНОМНЫЙ shell (SASH)
Инсталляция системы UNIX на жесткий диск обычно выполняется с помощью автономного shell (SASH, standalone shell). Иногда эта операция выполняется с магнитной ленты, но легче всего использовать гибкий диск. Возникает вопрос: "Как загрузить UNIX с гибкого диска?"
Картина следующая: гибкий диск имеет один раздел или даже может быть разделен на корневой раздел и раздел пользователей. В любом случае гибкий диск имеет файловую систему, созданную другой системой и помещенную на диск. Первый блок файловой системы является загружаемой записью, которая размещается на носителе с помощью команды dd. Команда dd копирует байты, начиная с самого начала устройства. Загрузочная запись содержит код, необходимый для запуска системы UNIX с диска.
Второй блок - это суперблок, своего рода главный каталог файловой системы. В нем находятся индексные дескриптооы файлов, содержащие информацию о каждом файле, а также список доступных свободных блоков. Корневая файловая система имеет также вариант ядра для гибкого диска, который загружается и запускает shell точно так же, как это делает его старший брат (ядро системы для жесткого диска) для всей системы в целом. Вы даже можете смонтировать инсталляционный диск на жесткий диск с другой системой и выполнять команды копирования. Ограничивающим фактором является размер одного инсталляционного диска. Самый большой объем гибкого диска на машинах PC - 1.2 Мб (используется на PC AT), что вполне достаточно. Можно уместить почти всю программу загрузки, которая необходима для запуска многопользовательской системы с гибкого диска.
Как только ядро системы с гибкого диска загружено, имеется полная файловая система со всеми файлами устройств. Ядро монтирует раздел жесткого диска (предполагается, что жесткий диск был разбит на разделы) и копирует на него файлы в формате файловой системы. Вот как это выглядит:
# mount /dev/hd01 /mnt <-вызов с гибкого диска для монтирования первого раздела жесткого диска # copy /unix /mnt <-копирование ядра жесткого диска в раздел жесткого диска
ЗАМЕЧАНИЕ ПО ВОПРОСУ БЕЗОПАСНОСТИ
Обычно только суперпользователь (root) может монтировать файловую систему. В больших системах это имеет смысл. Однако на небольших настольных машинах это может быть слишком ограничивающим фактором. Для того чтобы обойти это требование, используйте возможность изменения прав доступа. Чтобы позволить любому пользователю выполнять команды монтирования и размонтирования, примените следующие команды:
# chown root /etc/mount <- делает пользователя root владельцем модуля /etc/mount # chmod 4511 /etc/mount и дает возможность выполнять команду mount всем пользователям # chown root /etc/umount <- делает то же самое для команды # chmod 4511 /etc/umount размонтирования
Эти команды облегчают всем пользователям работу с гибким диском, но одновременно открывают огромную дыру в защите системы. Если кто-либо уже проложил тропинку прав доступа на гибкий диск (см. главу 9), то монтирование файловой системы продолжит эту тропинку в главную систему и позволит такому пользователю стать суперпользователем всей системы в целом просто с гибкого диска!