7.3. Файлова система: індексні дескриптори
У цьому розділі будуть розглянуті системні виклики, що мають справу з файловою системою і, зокрема, з інформацією про файли, такий як розмір, дати, права доступу і т. д. Ці системні виклики дають доступ до всієї інформації, яку ми обговорювали в розділ 2.
Давайте тепер звернемося власне до індексних дескрипторів. Частина їх описується структурою stat, яка визначається в sys/stat.h:
struct stat /* структура, що повертається stat */
/* пристрій індексного дескриптора */
/* Номер індексного дескриптора */
/* кількість посилань на файл */
/* ідентифікатор групи власника */
/* для спеціальних файлів */
/* Розмір файлу в символах */
/* час останнього читання з файлу */
7.3. Файлова система: індексні дескриптори
останнього запису або створення файлу */
останньої зміни файлу або індексного
#define S_IFMT 0170000
/* при запуску встановити ефективний
ідентифікатор користувача як у власника */
/* при запуску встановити ефективний
ідентифікатор групи як у власника */
/* зберегти перекачуваний текст навіть після
/* права на читання, власник */
/* права на запис, власник */
/* права на виконання/пошук, власник */
Доступ до індексного дескриптора файлу здійснюється за допомогою двох системних викликів: stat та fstat. Виклик stat приймає ім'я файлу і повертає інформацію індексного дескриптора для цього файлу (або 1 у разі помилки), fstat робить те саме з файлового де скриптора для відкритого файлу (не з покажчика FILE). Таким чином,
char * name; int fd;
struct stat stbuf;
Структура stbuf заповнюється інформацією індексного дескриптора для файлу з ім'ям nameчи дескриптором fd.
Тепер, коли ми маємо всі ці факти, спробуємо написати якийсь корисний код. Почнемо з того, що напишемо на мові Сі програму checkmail, яка спостерігатиме за поштовою скринькою користувача. Якщо файл збільшується, програма пише You have mail і подає звуковий сигнал. (Якщо файл стає менше, це, мабуть, тому, що ви тільки що прочитали і видалили якийсь лист мо, повідомлення не потрібні). Для першого кроку цього цілком достатньо; коли ця програма почне працювати, можна буде її вдосконалити.
/* checkmail: стежить за поштовою скринькою користувача */ #include
Розділ 7. Системні виклики UNIX
char *maildir = "/usr/spool/mail";
/* залежить від системи */
main(argc, argv) int argc; char * argv [];
struct stat buf;
char *name, *getlogin(); int lastsize = 0;
if ((name = getlogin()) == NULL)
error("can't get login name", (char *) 0); if (chdir(maildir) == 1)
error("cand cd to %s", maildir); for(;;)
if (buf.st_size > lastsize)
fprintf(stderr, "\nYou have mail\007\n"); lastsize=buf.st_size;
Функція getlogin(3) повертає ім'я, з яким зареєстрований користувач або NULL, якщо ім'я не отримане. Системний виклик chdir переводить checkmail в поштовий каталог, так що наступним викликом вам stat не доводиться переглядати кожен каталог, починаючи з кореневого і закінчуючи поштовим. У вашій системі, можливо, доведеться змінити значення maildir. Програма checkmail написана так, щоб спроби робилися навіть у тому випадку, коли поштова скринька не існує, оскільки більшість поштових програм видаляють поштову скриньку, якщо вона порожня.
Ілюстрація до обробки помилок: sv
Тепер напишемопрограму під назвою sv (схожу на cp), яка копіює набір файлів у каталог, але при цьому перезаписує файл призначення, тільки якщо він не існує або є більше
7.3. Файлова система: індексні дескриптори
старішим, ніж вихідний файл. Назва sv походить від англійського слова save (зберегти) – ідея в тому, що sv не перезаписує більш нові версії файлів. Команда sv враховує більше інформації з індексного дескриптора, ніж checkmail.
Запускатимемо sv наступним чином:
$ sv file1 file2 . dir
Ця команда повинна копіювати file1 dir/file1, file2 dir/file2 і т. д., за винятком тих випадків, коли файл призначення новіший відповідного вихідного файлу - в цьому випадку копія не робиться і видається попередження. Для того щоб уникнути багаторазового копіювання посилань, програма sv не дозволяє використовувати / в іменах вихідних файлів.
/* sv: зберігає нові файли */ #include
#include #include #include char *progname;
main(argc, argv) int argc; char * argv [];
struct stat stbuf;
char * dir = argv [argc 1];
progname = argv [0]; if (argc 254 Глава 7. Системні виклики UNIX
char target [BUFSIZ], buf [BUFSIZ], * index ();
sprintf(target, %s/%s, dir, file);
if (index(file, '/') != NULL) /* strchr() у деяких системах */ error("won't handle /'s in %s", file);
if (stat(file, &sti) == 1)
error("can't stat %s",
if (stat(target, &sto) ==
1) /* файл призначення не існує */
нехай він буде старішим */
if (sti.st_mtime 0) if (write(fout, buf, n) != n)
error("error writing %s", target);
Замість стандартних функцій введення виводу використана creat для того, щоб sv маламожливість зберегти код доступу вихідного файлу. (Зверніть увагу, що index і strchr – це різні назви однієї й тієї процедури; подивіться у посібнику опис string(3), щоб дізнатися, яке ім'я використовує ваша система.)
Незважаючи на те, що sv – це спеціалізована програма, вона або люструє кілька важливих ідей. Є безліч програм, хоч і не є «системними», проте здатних використовувати інформацію, що зберігається операційною системою, до якої можна отримати доступ за допомогою системних викликів. Для таких програм ключовим моментом є те, що системні типи даних визначені тільки в стандартних заголовних файлах, таких як stat.h і dir.h, і програма включає ці файли, а не використовує власні визначення типів. У такого коду набагато більше шансів виявитися переносимим з однієї системи до іншої.
Не дивуйтеся, що принаймні дві третини коду sv – це перевірка помилок. На початкових етапах написання програми є спокуса заощадити на обробці помилок, так як це не входить до постановки основного завдання. А коли програма «запрацює», вже не вистачає ентузіазму для того, щоб повернутися назад і вставити перевірки, які перетворять приватну програму на стійко працюючий продукт.
Програма sv не захищена від усіх можливих нещасть, наприклад вона не обробляє переривання в незручний час, але вона більш гостра
7.3. Файлова система: індексні дескриптори
рожна, ніж більшість програм. Зупинимося буквально на одному моменті – розглянемо останній оператор write. Рідко трапляється, що write не виконується успішно, тому багато програм ігнорують таку можливість. Але на дисках закінчується вільне місце; користувачі перевищують квоти; відбуваютьсяобриви ліній зв'язку. Всі ці обставини можуть спричинити помилки запису, і буде набагато краще, якщо користувач дізнається про це, замість того, щоб програма мовчки вдавала, що все добре.
Вправа 7.10. Змініть checkmail таким чином, щоб ідентифікувати відправника листа в повідомленні You have mail (Ви отримали лист). Підказка: sscanf, lseek.
Вправа 7.11. Змініть checkmail так, щоб каталог не змінювався на поштовий до входу в цикл. Чи матиме це значення для продуктивності? (Складніше.) Чи можете ви написати версію checkma il, якою був би потрібний лише один процес для оповіщення всіх користувачів?
Вправа 7.12. Напишіть програму watchfile, яка перевіряє файл і друкує його спочатку щоразу, коли він змінюється. Де можна використовувати таку програму?
Вправа 7.13. Програма sv не має гнучкості в обробці помилок. Змініть її так, щоб вона продовжувалась навіть у тому випадку, коли вона не може обробити деякі файли.
Вправа 7.15. Напишіть програму random:
яка виводить один рядок, випадково обраний з файлу. Якщо random обробляє файл з іменами, то вона може бути використана в програмі scapegoat, яка допомагає знайти винного:
echo "It's all 'random people''s fault!" $scapegoat
It's all Ken's fault!
Розділ 7. Системні виклики UNIX
Переконайтеся, що random працює правильно незалежно від розподілу довжин рядків.
Цей розділ визначає виконання однієї програми з іншої. Найпростіший спосіб зробити це – звернутися до стандартної біблі набрякової функції system, описаної, але засудженої в розділі 6. Коман та system отримує один аргумент – командний рядок точно в тому вигляді, як він набранийна терміналі (за винятком символу нового рядка наприкінці), та виконує її в підболочці. Якщо командний рядок повинен бути складений з декількох частин, то можуть стати у нагоді можливості форматування в пам'яті, якими володіє sprintf. Наприкінці цього розділу буде представлена більш надійна версія системи для використання в інтерактивних програмах, але спочатку треба досліджувати частини, з яких вона складається.
Низькорівневе створення процесів – execlp та execvp
Базова операція – це виконання іншої програми без очікування завершення системним викликом execlp. Наприклад, щоб надрукувати дату як останню дію програми, що виконується, використовуйте
execlp("date", "date", (char *) 0);
Перший аргумент execlp – це ім'я файлу команди; execlp отримує шлях пошуку (тобто $PATH) з оточення і здійснює такий самий пошук, як оболонка. Другий та наступні аргументи – це ім'я команди та її параметри; вони стають масивом argv для нової програми. Кінець списку позначений нульовим значенням (щоб зрозуміти конструкцію execlp, зверніться до exec(2)).
Виклик execlp перекриває існуючу програму нової, запускає її та виходить. Первинна програма отримує керування назад лише у разі помилки, наприклад, якщо файл не знайдений або не виконується:
execlp("date", "date", (char *) 0);
fprintf(stderr, "Couldn't execute 'date'\n"), exit(l);
Різновид execlp, що називається execvp, застосовується в тих випадках, коли кількість аргументів заздалегідь не відома. Виклик виглядає так:
де argp - це масив покажчиків на аргументи (як argv); останній покажчик у масиві має бути NULL, щоб execvp міг визначити, де закінчується список. Як і для execlp, filename – це файл, у якомузнаходиться програма, а argp - це масив argv для нової програми; argp[0] – ім'я програми.
Жодна з цих програм не допускає наявності метасимволів, *, лапок і т. д. у списку аргументів. Якщо це необхідно, викликайте за допомогою execlp оболонку /bin/sh, яка виконає всю роботу. Сформуйте командний рядок, який міститиме всю команду, якби вона була надрукована на терміналі, потім скажіть:
execlp("/bin/sh", "sh", "c", commandline, (char *) 0);
Аргумент визначає, що наступний аргумент повинен розглядатися як цілий командний рядок, а не як окремий аргумент.
Розглянемо як ілюстрацію програму waitfile. Команда
$ waitfile ім'я5файлу [ команда ]
періодично перевіряє вказаний файл. Якщо він не змінився з моменту останньої перевірки, то команда виконується. Якщо команда не визначена, файл копіюється на стандартний пристрій виведення. Для моніторингу роботи troff ми використовуємо waitfile:
$ waitfile troff.out echo troff done &
Реалізація waitfile отримує час зміни файлу за допомогою fstat.
/* waitfile: чекає, поки файл не перестане змінюватися */ #include