Аргумент cmd поддерживает значения g (F_GETLK), s (F_SETLK) или w (F_SETLKW). Остальные аргументы применяются для инициализации структуры flock, передаваемой в вызов fcntl(). Аргумент lock представляет собой значение поля l_type и может быть равен r (F_RDLCK), w (F_WRLCK) или u (F_WRLCK). Аргументы start и length являются целыми числами, представляющими значения полей l_start и l_len. Последний аргумент, whence, является необязательным и представляет значение поля l_whence; он может быть равен s (SEEK_SET — по умолчанию), c (SEEK_CUR) или e (SEEK_END). Причина, по которой мы приводим поля l_start и l_len к типу long long, когда передаем их в вызов printf(), описана в разделе 5.10.
Листинг 51.2. Эксперимент с блокировкой записей
filelock/i_fcntl_locking.c
#include
#include
#include "tlpi_hdr.h"
#define MAX_LINE 100
static void
displayCmdFmt(void)
{
printf("\n Format: cmd lock start length [whence]\n\n");
printf(" 'cmd' is 'g' (GETLK), 's' (SETLK), or 'w' (SETLKW)\n");
printf(" 'lock' is 'r' (READ), 'w' (WRITE), or 'u' (UNLOCK)\n");
printf(" 'start' and 'length' specify byte range to lock\n");
printf(" 'whence' is 's' (SEEK_SET, default), 'c' (SEEK_CUR), "
"or 'e' (SEEK_END)\n\n");
}
int
main(int argc, char *argv[])
{
int fd, numRead, cmd, status;
char lock, cmdCh, whence, line[MAX_LINE];
struct flock fl;
long long len, st;
if (argc!= 2 || strcmp(argv[1], "-help") == 0)
usageErr("%s file\n", argv[0]);
fd = open(argv[1], O_RDWR);
if (fd == -1)
errExit("open (%s)", argv[1]);
printf("Enter? for help\n");
for (;;) { /* Просим ввести команду блокирования и выполняем ее */
printf("PID=%ld> ", (long) getpid());
fflush(stdout);
if (fgets(line, MAX_LINE, stdin) == NULL) /* Конец файла */
exit(EXIT_SUCCESS);
line[strlen(line) — 1] = '\0'; /* Удаляем '\n' в конце */
if (*line == '\0')
continue; /* Пропускаем пустые строки */
if (line[0] == '?') {
displayCmdFmt();
continue;
}
whence = 's'; /* Значение по умолчанию для 'whence' */
numRead = sscanf(line, "%c %c %lld %lld %c", &cmdCh, &lock,
&st, &len, &whence);
fl.l_start = st;
fl.l_len = len;
if (numRead < 4 || strchr("gsw", cmdCh) == NULL ||
strchr("rwu", lock) == NULL || strchr("sce", whence) == NULL) {
printf("Invalid command!\n");
continue;
}
cmd = (cmdCh == 'g')? F_GETLK: (cmdCh == 's')
? F_SETLK: F_SETLKW;
fl.l_type = (lock == 'r')? F_RDLCK: (lock == 'w')
? F_WRLCK: F_UNLCK;
fl.l_whence = (whence == 'c')? SEEK_CUR:
(whence == 'e')? SEEK_END: SEEK_SET;
status = fcntl(fd, cmd, &fl); /* Выполняем запрос… */
if (cmd == F_GETLK) { /*…и смотрим, что получилось */
if (status == -1) {
errMsg("fcntl — F_GETLK");
} else {
if (fl.l_type == F_UNLCK)
printf("[PID=%ld] Lock can be placed\n",
(long) getpid());
else /* Заблокировано кем-то другим */
printf("[PID=%ld] Denied by %s lock on %lld:%lld "
"(held by PID %ld)\n", (long) getpid(),
(fl.l_type == F_RDLCK)? "READ": "WRITE",
(long long) fl.l_start,
(long long) fl.l_len, (long) fl.l_pid);
}
} else { /* F_SETLK, F_SETLKW */
if (status == 0)
printf("[PID=%ld] %s\n", (long) getpid(),
(lock == 'u')? "unlocked": "got lock");
else if (errno == EAGAIN || errno == EACCES) /* F_SETLK */
printf("[PID=%ld] failed (incompatible lock)\n",
(long) getpid());
else if (errno == EDEADLK) /* F_SETLKW */
printf("[PID=%ld] failed (deadlock)\n", (long) getpid());
else
errMsg("fcntl — F_SETLK(W)");
}
}
}
filelock/i_fcntl_locking.c
В сессиях командной строки, представленных ниже, демонстрируется использование двух экземпляров программы из листинга 51.2, с помощью которых устанавливаются блокировки для одного и того же файла размером 100 байт (tfile). На рис. 51.5 показано состояние удовлетворенных и отложенных запросов на получение блокировки на разных этапах сессии (см. комментарии ниже).
Для начала запустим первый экземпляр программы из листинга 51.2 (процесс А), установив блокировку для чтения байтов файла с 0 по 39:
Terminal window 1
$ ls — l tfile
— rw-r-r- 1 mtk users 100 Apr 18 12:19 tfile
$ ./i_fcntl_locking tfile
Enter? for help
PID=790> s r 0 40
[PID=790] got lock
Теперь запустим второй экземпляр программы (процесс Б) и установим блокировку для чтения байтов с 70 и до конца файла:
Terminal window 2
$ ./i_fcntl_locking tfile
Enter? for help
PID=800> s r -30 0 e
[PID=800] got lock
К этому моменту все должно выглядеть так, как на рис. 51.5,
Теперь мы возвращаемся к процессу A, где пытаемся добавить блокировку с возможностью записи для всего файла. Сначала мы используем операцию F_GETLK для проверки того, можно ли убрать блокировку и выдать сообщение, что существует конфликтная блокировка. Затем мы пытаемся установить блокировку с помощью операции F_SETLK, которая также не работает. Наконец, мы пытаемся добавить блокировку с F_SETLKW.
PID=790> g w 0 0
[PID=790] Denied by READ lock on 70:0 (held by PID 800)
PID=790> s w 0 0