____________________
[*] Заметим, что на самом деле коды доступа у нового файла будут равны
di_mode = (коды_доступа & ~u_cmask) | IFREG;
(для каталога вместо IFREG будет IFDIR), где маска u_cmask задается системным вызовом
umask(u_cmask);
(вызов выдает прежнее значение маски) и в дальнейшем наследуется всеми потомками дан-
ного процесса (она хранится в u-area процесса). Эта маска позволяет запретить доступ
к определенным операциям для всех создаваемых нами файлов, несмотря на явно заданные
коды доступа, например
umask(0077); /* ???------ */
делает значащими только первые 3 бита кодов доступа (для владельца файла). Остальные
биты будут равны нулю.
Все это относится и к созданию каталогов вызовом mkdir.
А. Богатырев, 1992-95 - 140 - Си в UNIX
существовал - срабатывает O_CREAT и файл создается. Это позволяет предохранить
уже существующие файлы от уничтожения.
Файл удаляется при помощи
int unlink(char имя_файла[]);
У каждой программы по умолчанию открыты три первых дескриптора, обычно связанные
0 - с клавиатурой (для чтения)
1 - с дисплеем (выдача результатов)
2 - с дисплеем (выдача сообщений об ошибках)
Если при вызове close(fd) дескриптор fd не соответствует открытому файлу (не был отк-
рыт) - ничего не происходит.
Часто используется такая метафора: если представлять себе файлы как книжки
(только чтение) и блокноты (чтение и запись), стоящие на полке, то открытие файла -
это выбор блокнота по заглавию на его обложке и открытие обложки (на первой стра-
нице). Теперь можно читать записи, дописывать, вычеркивать и править записи в сере-
дине, листать книжку! Страницы можно сопоставить блокам файла (см. ниже), а "полку"
с книжками - каталогу.
4.2. Напишите программу, которая копирует содержимое одного файла в другой (новый)
файл. При этом используйте системные вызовы чтения и записи read и write. Эти сис-
вызовы пересылают массивы байт из памяти в файл и наоборот. Но любую переменную можно
рассматривать как массив байт, если забыть о структуре данных в переменной!
Читайте и записывайте файлы большими кусками, кратными 512 байтам. Это уменьшит
число обращений к диску. Схема:
char buffer[512]; int n; int fd_inp, fd_outp;
...
while((n = read (fd_inp, buffer, sizeof buffer)) > 0)
write(fd_outp, buffer, n);
Приведем несколько примеров использования write:
char c = 'a';
int i = 13, j = 15;
char s[20] = "foobar";
char p[] = "FOOBAR";
struct { int x, y; } a = { 666, 999 };
/* создаем файл с доступом rw-r--r-- */
int fd = creat("aFile", 0644);
write(fd, &c, 1);
write(fd, &i, sizeof i); write(fd, &j, sizeof(int));
write(fd, s, strlen(s)); write(fd, &a, sizeof a);
write(fd, p, sizeof(p) - 1);
close(fd);
Обратите внимание на такие моменты:
- При использовании write() и read() надо передавать АДРЕС данного, которое мы
хотим записать в файл (места, куда мы хотим прочитать данные из файла).
- Операции read и write возвращают число действительно прочитанных/записанных байт
(при записи оно может быть меньше указанного нами, если на диске не хватает
места; при чтении - если от позиции чтения до конца файла содержится меньше
информации, чем мы затребовали).
- Операции read/write продвигают указатель чтения/записи
RWptr += прочитанное_или_записанное_число_байт;
При открытии файла указатель стоит на начале файла: RWptr=0. При записи файл
А. Богатырев, 1992-95 - 141 - Си в UNIX
если надо автоматически увеличивает свой размер. При чтении - если мы достигнем
конца файла, то read будет возвращать "прочитано 0 байт" (т.е. при чтении указа-
тель чтения не может стать больше размера файла).
- Аргумент сколькоБайт имеет тип unsigned, а не просто int:
int n = read (int fd, char *адрес, unsigned сколькоБайт);
int n = write(int fd, char *адрес, unsigned сколькоБайт);
Приведем упрощенные схемы логики этих сисвызовов, когда они работают с обычным диско-
вым файлом (в UNIX устройства тоже выглядят для программ как файлы, но иногда с осо-
быми свойствами):
4.2.1. m = write(fd, addr, n);
если( ФАЙЛ[fd] не открыт на запись) то вернуть (-1);
если(n == 0) то вернуть 0;
если( ФАЙЛ[fd] открыт на запись с флагом O_APPEND ) то
RWptr = длина_файла; /* т.е. встать на конец файла */
если( RWptr > длина_файла ) то
заполнить нулями байты файла в интервале
ФАЙЛ[fd][ длина_файла..RWptr-1 ] = '\0';
скопировать байты из памяти процесса в файл
ФАЙЛ[fd][ RWptr..RWptr+n-1 ] = addr[ 0..n-1 ];
отводя на диске новые блоки, если надо
RWptr += n;
если( RWptr > длина_файла ) то
длина_файла = RWptr;
вернуть n;
4.2.2. m = read(fd, addr, n);
если( ФАЙЛ[fd] не открыт на чтение) то вернуть (-1);
если( RWptr >= длина_файла ) то вернуть 0;
m = MIN( n, длина_файла - RWptr );
скопировать байты из файла в память процесса
addr[ 0..m-1 ] = ФАЙЛ[fd][ RWptr..RWptr+m-1 ];
RWptr += m;
вернуть m;
4.3. Найдите ошибки в фрагменте программы:
#define STDOUT 1 /* дескриптор стандартного вывода */
int i;
static char s[20] = "hi\n";
char c = '\n';
struct a{ int x,y; char ss[5]; } po;
scanf( "%d%d%d%s%s", i, po.x, po.y, s, po.ss);
write( STDOUT, s, strlen(s));
write( STDOUT, c, 1 ); /* записать 1 байт */
Ответ: в функции scanf перед аргументом i должна стоять операция "адрес", то есть &i.
Аналогично про &po.x и &po.y. Заметим, что s - это массив, т.е. s и так есть адрес,
поэтому перед s операция & не нужна; аналогично про po.ss - здесь & не требуется.
В системном вызове write второй аргумент должен быть адресом данного, которое мы
хотим записать в файл. Поэтому мы должны были написать &c (во втором вызове write).
Ошибка в scanf - указание значения переменной вместо ее адреса - является
довольно распространенной и не может быть обнаружена компилятором (даже при использо-
вании прототипа функции scanf(char *fmt, ...), так как scanf - функция с переменным
А. Богатырев, 1992-95 - 142 - Си в UNIX
числом аргументов заранее не определенных типов). Приходится полагаться исключительно
на собственную внимательность!
4.4. Как по дескриптору файла узнать, открыт он на чтение, запись, чтение и запись
одновременно? Вот два варианта решения:
#include
#include
#include /* там определено NOFILE */
#include
char *typeOfOpen(fd){
int flags;
if((flags=fcntl (fd, F_GETFL, NULL)) < 0 )
return NULL; /* fd вероятно не открыт */
flags &= O_RDONLY | O_WRONLY | O_RDWR;
switch(flags){
case O_RDONLY: return "r";
case O_WRONLY: return "w";
case O_RDWR: return "r+w";
default: return NULL;
}
}
char *type2OfOpen(fd){
extern errno; /* см. главу "системные вызовы" */
int r=1, w=1;
errno = 0; read(fd, NULL, 0);
if( errno == EBADF ) r = 0;
errno = 0; write(fd, NULL, 0);
if( errno == EBADF ) w = 0;
return (w && r) ? "r+w" :
w ? "w" :
r ? "r" :
"closed";
}
main(){
int i; char *s, *p;
for(i=0; i < NOFILE; i++ ){
s = typeOfOpen(i); p = type2OfOpen(i);
printf("%d:%s %s\n", i, s? s: "closed", p);
}
}
Константа NOFILE означает максимальное число одновременно открытых файлов для одного
процесса (это размер таблицы открытых процессом файлов, таблицы дескрипторов). Изу-
чите описание системного вызова fcntl (file control).
4.5. Напишите функцию rename() для переименования файла. Указание: используйте сис-
темные вызовы link() и unlink(). Ответ:
А. Богатырев, 1992-95 - 143 - Си в UNIX
rename( from, to )
char *from, /* старое имя */
*to; /* новое имя */
{
unlink( to ); /* удалить файл to */
if( link( from, to ) < 0 ) /* связать */
return (-1);
unlink( from ); /* стереть старое имя */
return 0; /* OK */
}
Вызов
link(существующее_имя, новое_имя);
создает файлу альтернативное имя - в UNIX файл может иметь несколько имен: так каждый
каталог имеет какое-то имя в родительском каталоге, а также имя "." в себе самом.
Каталог же, содержащий подкаталоги, имеет некоторое имя в своем родительском ката-
логе, имя "." в себе самом, и по одному имени ".." в каждом из своих подкаталогов.
Этот вызов будет неудачен, если файл новое_имя уже существует; а также если мы
попытаемся создать альтернативное имя в другой файловой системе. Вызов
unlink(имя_файла)
удаляет имя файла. Если файл больше не имеет имен - он уничтожается. Здесь есть одна
тонкость: рассмотрим фрагмент
int fd;
close(creat("/tmp/xyz", 0644)); /*Создать пустой файл*/
fd = open("/tmp/xyz", O_RDWR);
unlink("/tmp/xyz");
...
close(fd);
Первый оператор создает пустой файл. Затем мы открываем файл и уничтожаем его
единственное имя. Но поскольку есть программа, открывшая этот файл, он не удаляется
немедленно! Программа далее работает с безымянным файлом при помощи дескриптора fd.
Как только файл закрывается - он будет уничтожен системой (как не имеющий имен).
Такой трюк используется для создания временных рабочих файлов.
Файл можно удалить из каталога только в том случае, если данный каталог имеет
для вас код доступа "запись". Коды доступа самого файла при удалении не играют роли.
В современных версиях UNIX есть системный вызов rename, который делает то же
самое, что и написанная нами одноименная функция.
4.6. Существование альтернативных имен у файла позволяет нам решить некоторые проб-
лемы, которые могут возникнуть при использовании чужой программы, от которой нет
исходного текста (которую нельзя поправить). Пусть программа выдает некоторую инфор-
мацию в файл zz.out (и это имя жестко зафиксировано в ней, и не задается через аргу-
менты программы):
/* Эта программа компилируется в a.out */
main(){
int fd = creat("zz.out", 0644);
write(fd, "It's me\n", 8);
}
Мы же хотим получить вывод на терминал, а не в файл. Очевидно, мы должны сделать файл
zz.out синонимом устройства /dev/tty (см. конец этой главы). Это можно сделать коман-
дой ln:
$ rm zz.out ; ln /dev/tty zz.out
$ a.out
$ rm zz.out
или программно:
А. Богатырев, 1992-95 - 144 - Си в UNIX
/* Эта программа компилируется в start */
/* и вызывается вместо a.out */
#include
main(){
unlink("zz.out");
link("/dev/tty", "zz.out");
if( !fork()){ execl("a.out", NULL); }
else wait(NULL);
unlink("zz.out");
}
(про fork, exec, wait смотри в главе про UNIX).
Еще один пример: программа a.out желает запустить программу /usr/bin/vi (смотри
про функцию system() сноску через несколько страниц):
main(){
... system("/usr/bin/vi xx.c"); ...
}
На вашей же машине редактор vi помещен в /usr/local/bin/vi. Тогда вы просто создаете
альтернативное имя этому редактору:
$ ln /usr/local/bin/vi /usr/bin/vi
Помните, что альтернативное имя файлу можно создать лишь в той же файловой системе,
где содержится исходное имя. В семействе BSD [*] это ограничение можно обойти, создав