/*++ 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 #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); }