NT4/private/posix/psxss/flocks.c
2020-09-30 17:12:29 +02:00

955 lines
23 KiB
C

/*++
Copyright (c) 1989 Microsoft Corporation
Module Name:
flocks.c
Abstract:
Implementation of Posix advisory file record locking (via fcntl).
Author:
Matthew Bradburn (mattbr) 11-May-1992
Revision History:
--*/
#include <fcntl.h>
#include "psxsrv.h"
#include "psxmsg.h"
void
InsertFlock(
PIONODE IoNode,
PSYSFLOCK l
);
void
WakeAllFlockers(
PIONODE IoNode
);
int
found_region(
PPSX_PROCESS Proc,
PIONODE IoNode,
int command,
PSYSFLOCK list,
struct flock *new,
off_t *new_off,
ULONG FileLength,
OUT int *error
);
VOID
FlockHandler(
IN PPSX_PROCESS p,
IN PINTCB IntControlBlock,
IN PSX_INTERRUPTREASON InterruptReason,
IN int Signal
);
//
// These macros take care of the notion that if the length is
// 0, it means the lock goes until EOF
//
#define LEN_A(a) ((a)->l_len == 0 ? (off_t)(FileLength - abs_off) : (a)->l_len)
#define LEN_B(b) ((b)->Len == 0 ? (off_t)(FileLength - (b)->Start) : (b)->Len)
#define OVERLAP(a, b) \
((abs_off >= (b)->Start && \
abs_off < (b)->Start + LEN_B(b)) \
|| (abs_off + LEN_A(a) >= (b)->Start && \
abs_off + LEN_A(a) <= (b)->Start + LEN_B(b)) \
|| (abs_off <= (b)->Start && \
abs_off + LEN_A(a) >= (b)->Start + LEN_B(b)))
//
// Return values from found_region
//
#define DONE 1
#define CONTINUE 2
#define WAIT 3
#define ERROR 4
//
// DoFlockStuff -- do fcntl commands related to advisory file-record
// locking.
//
// Locking: the caller holds the IoNode locked throughout.
//
// Returns: TRUE if the caller should reply to the api request, FALSE
// if the process has been blocked (F_SETLKW) and no reply
// should be sent.
//
BOOLEAN
DoFlockStuff(
PPSX_PROCESS Proc,
PPSX_API_MSG m,
int command,
IN PFILEDESCRIPTOR Fd,
IN OUT struct flock *new,
OUT int *error)
{
PSYSFLOCK p;
PSYSFLOCK l;
off_t abs_off;
NTSTATUS Status;
IO_STATUS_BLOCK IoStat;
FILE_POSITION_INFORMATION FilePosition;
FILE_STANDARD_INFORMATION FileStandardInfo;
PIONODE IoNode;
int did_find_overlap = 0; // were changes made?
ULONG FileLength;
if (new->l_start < 0) {
*error = EINVAL;
return TRUE;
}
if (new->l_type < F_RDLCK || new->l_type > F_WRLCK) {
*error = EINVAL;
return TRUE;
}
IoNode = Fd->SystemOpenFileDesc->IoNode;
Status = NtQueryInformationFile(
Fd->SystemOpenFileDesc->NtIoHandle, &IoStat,
&FileStandardInfo, sizeof(FileStandardInfo),
FileStandardInformation);
if (!NT_SUCCESS(Status)) {
*error = ENOMEM;
return TRUE;
}
ASSERT(0 == FileStandardInfo.EndOfFile.HighPart);
FileLength = FileStandardInfo.EndOfFile.LowPart;
//
// Convert l_whence and l_start into the absolute position of the
// start of the region described.
//
switch (new->l_whence) {
case SEEK_SET:
abs_off = 0L;
break;
case SEEK_CUR:
Status = NtQueryInformationFile(
Fd->SystemOpenFileDesc->NtIoHandle, &IoStat,
&FilePosition, sizeof(FilePosition),
FilePositionInformation);
ASSERT(NT_SUCCESS(Status));
ASSERT(0 == FilePosition.CurrentByteOffset.HighPart);
abs_off = FilePosition.CurrentByteOffset.LowPart;
break;
case SEEK_END:
abs_off = FileLength;
break;
default:
*error = EINVAL;
return TRUE;
}
abs_off += new->l_start;
//
// If we're actually trying to modify an already-locked region
// or create an additional locked, region, we scan the list of
// locks to see whether we can succeed. If we can, we go through
// the list a second time and perform the action.
//
if ((F_SETLK == command || F_SETLKW == command)
&& (F_UNLCK != new->l_type)) {
//
// If the regions overlap, they must either both
// be of type F_RDLCK, or they must have the same
// owner. Otherwise we do not succeed (may block).
//
for (p = (PSYSFLOCK)IoNode->Flocks.Flink;
p != (PSYSFLOCK)&IoNode->Flocks;
p = (PSYSFLOCK)p->Links.Flink) {
if (!OVERLAP(new, p)) {
continue;
}
if ((F_RDLCK == new->l_type && F_RDLCK == p->Type)
|| (Proc->Pid == p->Pid)) {
continue;
}
if (F_SETLK == command) {
*error = EAGAIN;
return TRUE;
}
ASSERT(F_SETLKW == command);
//
// BLOCK the process.
//
Status = BlockProcess(Proc, Proc, FlockHandler, m,
&IoNode->Waiters, &IoNode->IoNodeLock);
if (!NT_SUCCESS(Status)) {
m->Error = PsxStatusToErrno(Status);
return TRUE;
}
//
// Successfully blocked -- don't reply to api request.
//
return FALSE;
}
}
//
// Traverse the list of flocks, looking for locks describing
// regions that overlap the given lock. Peform the indicated
// operation on each.
//
p = (PVOID)IoNode->Flocks.Flink;
while (p != (PVOID)&IoNode->Flocks) {
//
// Get the "next" pointer here, so that if found_region
// frees the entry pointed to by p we'll still be able to
// traverse the list.
//
l = (PVOID)p->Links.Flink;
//
// Find out whether the regions overlap or not: does the
// new region start in this region, or does it end in this
// region?
//
if (OVERLAP(new, p)) {
if ((F_SETLK == command || F_SETLKW == command) &&
F_UNLCK == new->l_type) {
//
// The process is only allowed to unlock locks
// that he owns.
//
if (p->Pid != Proc->Pid) {
continue;
}
}
did_find_overlap++;
switch (found_region(Proc, IoNode, command, p,
new, &abs_off, FileLength, error)) {
case DONE:
*error = 0;
return TRUE;
case CONTINUE:
p = l;
continue;
case ERROR:
return TRUE;
default:
ASSERT(0);
}
}
if (p->Start > abs_off) {
break;
}
p = l;
}
//
// we have come to the end of the list, or we have come to a
// place that our region should have been before.
//
if (F_GETLK == command) {
// no such region; the lock type is set to
// UNLOCK.
new->l_type = F_UNLCK;
*error = 0;
return TRUE;
}
if ((F_SETLK == command || F_SETLKW == command) &&
F_UNLCK == new->l_type) {
//
// Return an error only if nothing was found to unlock.
// We do this to allow the user to give a single unlock
// command spanning the entire file, this to unlock all his
// locks. 1003.1-90 isn't really clear on how this stuff
// should work.
//
if (!did_find_overlap) {
*error = EINVAL;
return TRUE;
}
WakeAllFlockers(IoNode);
*error = 0;
return TRUE;
}
// We're setting a lock, and the type is not UNLOCK.
// So insert a new region here.
l = RtlAllocateHeap(PsxHeap, 0, sizeof(*l));
if (NULL == l) {
*error = ENOMEM;
return TRUE;
}
l->Start = abs_off;
l->Len = new->l_len;
l->Pid = Proc->Pid;
l->Type = new->l_type;
InsertFlock(IoNode, l);
*error = 0;
return TRUE;
}
//
// found_region -- this gets called when there is a region overlapping the
// one the user is interested in. Actually, the user's request may
// span several regions, in which case this routine will get called
// for each one.
//
int
found_region(
PPSX_PROCESS Proc, // the process making the request
PIONODE IoNode, // our IoNode.
int command, // what to do
PSYSFLOCK list, // region that matches new region
struct flock *new, // new region
IN OUT off_t *new_off, // absolute offset, start of new region
ULONG FileLength, // current length of the file
OUT int *error // returned errno
)
{
off_t abs_off = *new_off;
if (F_GETLK == command) {
//
// If we own the overlapping region, we wouldn't block on
// it. So we must continue searching.
//
if (list->Pid == Proc->Pid) {
return CONTINUE;
}
//
// If both lock types are F_RDLCK, then we have not
// yet found a lock the user would block against; we
// must continue searching.
//
if (new->l_type == F_RDLCK && list->Type == F_RDLCK) {
return CONTINUE;
}
new->l_pid = list->Pid;
new->l_type = list->Type;
new->l_whence = SEEK_SET;
new->l_start = list->Start;
new->l_len = list->Len;
return DONE;
}
if (F_SETLK != command && F_SETLKW != command) {
KdPrint(("PSXSS: found_region: command %d\n", command));
}
ASSERT(F_SETLK == command || F_SETLKW == command);
//
// If this user doesn't own the found region, and they're both
// read locks, we just add a shared lock.
//
if (list->Pid != Proc->Pid &&
F_RDLCK == list->Type && F_RDLCK == new->l_type) {
PSYSFLOCK l;
// set a shared lock.
l = RtlAllocateHeap(PsxHeap, 0, sizeof(*l));
if (NULL == l) {
*error = ENOMEM;
return ERROR;
}
l->Start = *new_off;
l->Len = new->l_len;
l->Type = new->l_type;
l->Pid = Proc->Pid;
InsertFlock(IoNode, l);
return DONE;
}
//
// The only way we should get here is if we're unlocking a region
// or if we are changing the type of a region we own. In each case
// we should own the region.
//
ASSERT(list->Pid == Proc->Pid);
if (*new_off == list->Start && LEN_A(new) == LEN_B(list)) {
//
// The found region exactly matches the region in
// the argument.
//
if (F_UNLCK == new->l_type) {
PSYSFLOCK p;
RemoveEntryList(&list->Links);
RtlFreeHeap(PsxHeap, 0, list);
//
// Wake all procs sleeping on this IoNode.
//
WakeAllFlockers(IoNode);
return DONE;
}
list->Type = new->l_type;
return DONE;
}
if (*new_off == list->Start && LEN_A(new) < LEN_B(list)) {
PSYSFLOCK l;
//
// The new region starts at the same place as the found
// region, but does not extend all the way to its end.
//
if (F_UNLCK == new->l_type) {
list->Start += LEN_A(new);
list->Len = LEN_B(list);
list->Len -= LEN_A(new);
//
// We've changed this entry's starting point, so
// we may have changed it's order in the list.
//
RemoveEntryList(&list->Links);
InsertFlock(IoNode, list);
return DONE;
}
//
// Change the type. The region gets split in
// two.
//
l = RtlAllocateHeap(PsxHeap, 0, sizeof(*l));
if (NULL == l) {
*error = ENOMEM;
return ERROR;
}
l->Start = *new_off;
l->Len = LEN_A(new);
l->Type = new->l_type;
l->Pid = Proc->Pid;
InsertFlock(IoNode, l);
list->Start += new->l_len;
list->Len = LEN_B(list);
list->Len -= LEN_A(new);
// re-sort
RemoveEntryList(&list->Links);
InsertFlock(IoNode, list);
return DONE;
}
if (*new_off == list->Start && LEN_A(new) > LEN_B(list)) {
//
// The new region starts at the same place as the found
// region, but extends beyond it, perhaps into some number
// of additional regions
//
if (F_UNLCK == new->l_type) {
//
// We are unlocking all of this region and maybe
// some other stuff, too.
//
RemoveEntryList(&list->Links);
RtlFreeHeap(PsxHeap, 0, list);
return CONTINUE;
}
// Just change the type.
list->Type = new->l_type;
new->l_start += list->Len;
*new_off = new->l_start;
new->l_len = LEN_A(new);
new->l_len -= LEN_B(list);
return CONTINUE;
}
if (*new_off > list->Start &&
*new_off + LEN_A(new) == list->Start + LEN_B(list)) {
PSYSFLOCK l;
//
// The new region starts inside the found one and extends
// to its end.
//
if (F_UNLCK == new->l_type) {
ASSERT(list->Pid == Proc->Pid);
// unlocking the last portion of a region
list->Len = *new_off - list->Start;
return DONE;
}
//
// need to split the region.
//
list->Len = *new_off - list->Start;
l = RtlAllocateHeap(PsxHeap, 0, sizeof(*l));
if (NULL == l) {
*error = ENOMEM;
return ERROR;
}
l->Start = *new_off;
l->Len = LEN_A(new);
l->Pid = Proc->Pid;
l->Type = new->l_type;
InsertFlock(IoNode, l);
return DONE;
}
if (*new_off > list->Start &&
*new_off + LEN_A(new) < list->Start + LEN_B(list)) {
PSYSFLOCK l;
//
// The new region starts inside the found region and does
// not extend to its end.
//
if (F_UNLCK == new->l_type) {
//
// The list region gets split into two.
// Change the list region to be the left
// side, and add another region for the right side.
//
l = RtlAllocateHeap(PsxHeap, 0, sizeof(*l));
if (NULL == l) {
*error = ENOMEM;
return ERROR;
}
l->Start = *new_off + LEN_A(new);
l->Len = (list->Start + LEN_B(list)) -
(*new_off + LEN_A(new));
l->Type = list->Type;
l->Pid = Proc->Pid;
InsertFlock(IoNode, l);
// list->Start stays the same;
list->Len = *new_off - list->Start;
return DONE;
}
//
// changing the type of a sub-region; we end up
// splitting the region into three.
//
// the right region
l = RtlAllocateHeap(PsxHeap, 0, sizeof(*l));
if (NULL == l) {
*error = ENOMEM;
return ERROR;
}
l->Start = *new_off + LEN_A(new);
l->Len = (list->Start + LEN_B(list)) - (*new_off + LEN_A(new));
l->Pid = Proc->Pid;
l->Type = list->Type;
InsertFlock(IoNode, l);
// the left region
list->Len = *new_off - list->Start;
// list->Start stays the same
// the center region
l = RtlAllocateHeap(PsxHeap, 0, sizeof(*l));
if (NULL == l) {
*error = ENOMEM;
return ERROR;
}
l->Start = *new_off;
l->Len = LEN_A(new);
l->Type = new->l_type;
l->Pid = Proc->Pid;
InsertFlock(IoNode, l);
return DONE;
}
if (*new_off > list->Start &&
*new_off + LEN_A(new) > list->Start + LEN_B(list)) {
PSYSFLOCK l;
//
// The new region starts in the found region and extends
// beyond it.
//
if (F_UNLCK == new->l_type) {
new->l_start = list->Start + LEN_B(list) + 1;
list->Len = *new_off - list->Start;
new->l_whence = SEEK_SET;
new->l_len = LEN_A(new) - (new->l_start - *new_off);
*new_off = new->l_start;
return CONTINUE;
}
//
// changing the lock type of a region starting in this
// region, presumably extending into the next region.
// this involves creating a new region for the change
// and continuing on.
//
l = RtlAllocateHeap(PsxHeap, 0, sizeof(*l));
if (NULL == l) {
*error = ENOMEM;
return ERROR;
}
l->Start = *new_off;
l->Whence = SEEK_SET;
l->Len = (list->Start + LEN_B(list)) - *new_off;
l->Pid = Proc->Pid;
l->Type = new->l_type;
InsertFlock(IoNode, l);
new->l_start = list->Start + LEN_B(list);
new->l_whence = SEEK_SET;
new->l_len = LEN_A(new) - (new->l_start - *new_off);
*new_off = new->l_start;
list->Len = l->Start - list->Start;
return CONTINUE;
}
if (*new_off < list->Start &&
*new_off + LEN_A(new) < list->Start + LEN_B(list)) {
off_t new_end = *new_off + (LEN_A(new) - 1);
PSYSFLOCK l;
//
// The new region starts before the list region and ends
// in the midst of it.
//
if (F_UNLCK == new->l_type) {
list->Len = (list->Start + LEN_B(list) - 1) - new_end;
list->Start = new_end + 1;
return DONE;
}
//
// The list region gets split into two.
//
l = RtlAllocateHeap(PsxHeap, 0, sizeof(*l));
if (NULL == l) {
return ERROR;
}
l->Start = *new_off;
l->Len = LEN_A(new);
l->Type = new->l_type;
l->Whence = SEEK_SET;
l->Pid = Proc->Pid;
InsertFlock(IoNode, l);
list->Len = (list->Start + LEN_B(list) - 1) - new_end;
list->Start = new_end + 1;
return DONE;
}
if (*new_off < list->Start &&
*new_off + LEN_A(new) == list->Start + LEN_B(list)) {
//
// The new region starts before the list region and
// ends at the same place.
//
if (F_UNLCK == new->l_type) {
RemoveEntryList(&list->Links);
RtlFreeHeap(PsxHeap, 0, list);
return DONE;
}
list->Start = *new_off;
list->Type = new->l_type;
list->Len = LEN_A(new);
return DONE;
}
if (*new_off < list->Start &&
*new_off + LEN_A(new) > list->Start + LEN_B(list)) {
//
// The new region starts before the list region and
// ends after it.
//
if (F_UNLCK == new->l_type) {
//
// there may be stuff still to unlock when we're
// done.
//
new->l_start = *new_off + LEN_A(new) + 1;
new->l_len = (*new_off + LEN_A(new)) -
(list->Start + LEN_B(list));
new->l_whence = SEEK_SET;
RemoveEntryList(&list->Links);
RtlFreeHeap(PsxHeap, 0, list);
return CONTINUE;
}
//
// The new lock subsumes the one we've found on
// the list.
//
RemoveEntryList(&list->Links);
RtlFreeHeap(PsxHeap, 0, list);
return CONTINUE;
}
ASSERT(0); // shouldn't get here.
}
void
ReleaseFlocksByPid(
PIONODE IoNode,
pid_t pid
)
{
PSYSFLOCK p;
PINTCB IntCb;
PPSX_PROCESS Waiter;
PLIST_ENTRY next;
//
// Remove all the locks that we hold.
//
for (p = CONTAINING_RECORD(IoNode->Flocks.Flink, SYSFLOCK, Links);
p != (PVOID)&IoNode->Flocks;
/* null */) {
PSYSFLOCK next;
next = CONTAINING_RECORD(p->Links.Flink, SYSFLOCK, Links);
if (pid == p->Pid) {
RemoveEntryList(&p->Links);
RtlFreeHeap(PsxHeap, 0, (PVOID)p);
}
p = next;
}
//
// Wake all procs sleeping on this IoNode; since we've released
// all flocks belonging to the given pid, maybe one of the
// blocked procs can run.
//
WakeAllFlockers(IoNode);
}
#if DBG
void
DumpFlockList(PIONODE IoNode)
{
PSYSFLOCK p;
int i;
KdPrint(("DumpFlockList:\n"));
for (i = 0, p = (PSYSFLOCK)IoNode->Flocks.Flink;
p != (PSYSFLOCK)&IoNode->Flocks;
p = (PSYSFLOCK)p->Links.Flink, ++i) {
KdPrint(("Flock %d:\n", i));
KdPrint(("\t Start %d\n", p->Start));
KdPrint(("\t Length %d\n", p->Len));
KdPrint(("\t Type %d\n", p->Type));
KdPrint(("\t Pid 0x%x\n", p->Pid));
}
}
#endif
//
// InsertFlock -- insert the flock in the list. The list is ordered
// according to the starting offset.
//
//
VOID
InsertFlock(
PIONODE IoNode,
PSYSFLOCK l
)
{
PSYSFLOCK p;
ASSERT(NULL != l);
for (p = CONTAINING_RECORD(IoNode->Flocks.Flink, SYSFLOCK, Links);
p != (PVOID)&IoNode->Flocks;
p = CONTAINING_RECORD(p->Links.Flink, SYSFLOCK, Links)) {
if (p->Start >= l->Start) {
InsertTailList(&p->Links, &l->Links);
return;
}
}
InsertTailList(&IoNode->Flocks, &l->Links);
}
VOID
WakeAllFlockers(
PIONODE IoNode
)
{
PINTCB IntCb;
PPSX_PROCESS Waiter;
PVOID p, prev;
RtlEnterCriticalSection(&BlockLock);
//
// We go through the list of blocked processes and wake them
// all up. Each calls the FlockHandler routine below. This is
// a terrible kludge: we go through the list from end to beginning
// because if any of the processed we've waked are added to the
// list, they get added at the end (BlockProcess() calls Insert-
// TailList()).
//
for (p = (PVOID)IoNode->Waiters.Blink;
p != (PVOID)&IoNode->Waiters;
p = prev) {
ASSERT(NULL != p);
IntCb = CONTAINING_RECORD((PINTCB)p, INTCB, Links);
ASSERT(NULL != IntCb);
Waiter = (PPSX_PROCESS)IntCb->IntContext;
ASSERT(NULL != Waiter);
prev = IntCb->Links.Blink;
UnblockProcess(Waiter, WaitSatisfyInterrupt,
TRUE, 0);
RtlEnterCriticalSection(&BlockLock);
}
RtlLeaveCriticalSection(&BlockLock);
}
VOID
FlockHandler(
IN PPSX_PROCESS p,
IN PINTCB IntControlBlock,
IN PSX_INTERRUPTREASON InterruptReason,
IN int Signal // signal causing wakeup, if any
)
/*++
Routine Description:
This procedure is called when a process releases a lock.
Arguments:
p - Supplies the address of the sleeping process.
IntControlBlock - Supplies the address of the interrupt control
block.
InterruptReason - Why the sleep is being interrupted.
Return value:
None.
--*/
{
PPSX_API_MSG m;
PPSX_FCNTL_MSG args;
RtlLeaveCriticalSection(&BlockLock);
m = IntControlBlock->IntMessage;
if (InterruptReason == SignalInterrupt) {
//
// The sleep is being interrupted by a signal. We return
// EINTR to the Posix process.
//
RtlFreeHeap(PsxHeap, 0, (PVOID)IntControlBlock);
m->Error = EINTR;
m->Signal = Signal;
ApiReply(p, m, NULL);
RtlFreeHeap(PsxHeap, 0, (PVOID)m);
return;
}
args = &m->u.Fcntl;
RtlFreeHeap(PsxHeap, 0, (PVOID)IntControlBlock);
if (PsxFcntl(p, m)) {
ApiReply(p, m, NULL);
}
RtlFreeHeap(PsxHeap, 0, m);
}