flock.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736
  1. /*++
  2. Copyright (c) 2014 Minoca Corp. All Rights Reserved
  3. Module Name:
  4. flock.c
  5. Abstract:
  6. This module implements support for user mode file locking in the kernel.
  7. Author:
  8. Evan Green 29-Apr-2014
  9. Environment:
  10. Kernel
  11. --*/
  12. //
  13. // ------------------------------------------------------------------- Includes
  14. //
  15. #include <minoca/kernel/kernel.h>
  16. #include "iop.h"
  17. //
  18. // ---------------------------------------------------------------- Definitions
  19. //
  20. //
  21. // ------------------------------------------------------ Data Type Definitions
  22. //
  23. /*++
  24. Structure Description:
  25. This structure defines an active file lock.
  26. Members:
  27. ListEntry - Stores pointers to the next and previous lock entries in the
  28. file object.
  29. Type - Stores the lock type.
  30. Process - Stores a pointer to the process that owns the file lock.
  31. Offset - Stores the offset into the file where the lock begins.
  32. Size - Stores the size of the lock. If zero, then the lock extends to the
  33. end of the file.
  34. --*/
  35. typedef struct _FILE_LOCK_ENTRY {
  36. LIST_ENTRY ListEntry;
  37. FILE_LOCK_TYPE Type;
  38. PKPROCESS Process;
  39. ULONGLONG Offset;
  40. ULONGLONG Size;
  41. } FILE_LOCK_ENTRY, *PFILE_LOCK_ENTRY;
  42. //
  43. // ----------------------------------------------- Internal Function Prototypes
  44. //
  45. KSTATUS
  46. IopTryToSetFileLock (
  47. PFILE_OBJECT FileObject,
  48. PFILE_LOCK_ENTRY NewEntry,
  49. PLIST_ENTRY FreeList,
  50. BOOL DryRun
  51. );
  52. BOOL
  53. IopDoFileLocksOverlap (
  54. ULONGLONG IncomingOffset,
  55. ULONGLONG IncomingSize,
  56. PFILE_LOCK_ENTRY LockEntry
  57. );
  58. //
  59. // -------------------------------------------------------------------- Globals
  60. //
  61. //
  62. // ------------------------------------------------------------------ Functions
  63. //
  64. KSTATUS
  65. IopGetFileLock (
  66. PIO_HANDLE IoHandle,
  67. PFILE_LOCK Lock
  68. )
  69. /*++
  70. Routine Description:
  71. This routine gets information about a file lock. Existing locks are not
  72. reported if they are compatible with making a new lock in the given region.
  73. So set the lock type to write if both read and write locks should be
  74. reported.
  75. Arguments:
  76. IoHandle - Supplies a pointer to the open I/O handle.
  77. Lock - Supplies a pointer to the lock information.
  78. Return Value:
  79. Status code.
  80. --*/
  81. {
  82. PLIST_ENTRY CurrentEntry;
  83. PFILE_OBJECT FileObject;
  84. PFILE_LOCK_ENTRY FoundEntry;
  85. PFILE_LOCK_ENTRY LockEntry;
  86. BOOL Overlap;
  87. ASSERT(KeGetRunLevel() == RunLevelLow);
  88. if ((Lock->Type != FileLockRead) && (Lock->Type != FileLockReadWrite)) {
  89. return STATUS_INVALID_PARAMETER;
  90. }
  91. FileObject = IoHandle->FileObject;
  92. FoundEntry = NULL;
  93. KeAcquireSharedExclusiveLockExclusive(FileObject->Lock);
  94. CurrentEntry = FileObject->FileLockList.Next;
  95. while (CurrentEntry != &(FileObject->FileLockList)) {
  96. LockEntry = LIST_VALUE(CurrentEntry, FILE_LOCK_ENTRY, ListEntry);
  97. CurrentEntry = CurrentEntry->Next;
  98. //
  99. // If the caller only wants write locks, then skip read locks.
  100. //
  101. if ((Lock->Type == FileLockRead) && (LockEntry->Type == FileLockRead)) {
  102. continue;
  103. }
  104. Overlap = IopDoFileLocksOverlap(Lock->Offset, Lock->Size, LockEntry);
  105. if (Overlap != FALSE) {
  106. FoundEntry = LockEntry;
  107. break;
  108. }
  109. }
  110. if (FoundEntry != NULL) {
  111. Lock->Type = FoundEntry->Type;
  112. Lock->Offset = FoundEntry->Offset;
  113. Lock->Size = FoundEntry->Size;
  114. Lock->ProcessId = FoundEntry->Process->Identifiers.ProcessId;
  115. } else {
  116. Lock->Type = FileLockUnlock;
  117. }
  118. KeReleaseSharedExclusiveLockExclusive(FileObject->Lock);
  119. return STATUS_SUCCESS;
  120. }
  121. KSTATUS
  122. IopSetFileLock (
  123. PIO_HANDLE IoHandle,
  124. PFILE_LOCK Lock,
  125. BOOL Blocking
  126. )
  127. /*++
  128. Routine Description:
  129. This routine locks or unlocks a portion of a file. If the process already
  130. has a lock on any part of the region, the old lock is replaced with this
  131. new region. Remove a lock by specifying a lock type of unlock.
  132. Arguments:
  133. IoHandle - Supplies a pointer to the open I/O handle.
  134. Lock - Supplies a pointer to the lock information.
  135. Blocking - Supplies a boolean indicating if this should block until a
  136. determination is made.
  137. Return Value:
  138. Status code.
  139. --*/
  140. {
  141. PFILE_LOCK_ENTRY Entry;
  142. PFILE_OBJECT FileObject;
  143. LIST_ENTRY FreeList;
  144. BOOL LockHeld;
  145. PFILE_LOCK_ENTRY NewEntry;
  146. PKEVENT NewEvent;
  147. PKEVENT PreviousEvent;
  148. FILE_LOCK_ENTRY RemoveEntry;
  149. PFILE_LOCK_ENTRY SplitEntry;
  150. KSTATUS Status;
  151. ASSERT(KeGetRunLevel() == RunLevelLow);
  152. FileObject = IoHandle->FileObject;
  153. INITIALIZE_LIST_HEAD(&FreeList);
  154. NewEntry = NULL;
  155. SplitEntry = NULL;
  156. LockHeld = FALSE;
  157. if ((Lock->Type == FileLockInvalid) || (Lock->Type >= FileLockTypeCount)) {
  158. Status = STATUS_INVALID_PARAMETER;
  159. goto SetFileLockEnd;
  160. }
  161. //
  162. // Use a stack allocated entry if things are being unlocked.
  163. //
  164. if (Lock->Type == FileLockUnlock) {
  165. NewEntry = &RemoveEntry;
  166. } else {
  167. //
  168. // Check to make sure the handle has the appropriate permissions.
  169. //
  170. if (Lock->Type == FileLockRead) {
  171. if ((IoHandle->Access & IO_ACCESS_READ) == 0) {
  172. Status = STATUS_ACCESS_DENIED;
  173. goto SetFileLockEnd;
  174. }
  175. } else {
  176. ASSERT(Lock->Type == FileLockReadWrite);
  177. if ((IoHandle->Access & IO_ACCESS_WRITE) == 0) {
  178. Status = STATUS_ACCESS_DENIED;
  179. goto SetFileLockEnd;
  180. }
  181. }
  182. NewEntry = MmAllocateNonPagedPool(sizeof(FILE_LOCK_ENTRY),
  183. FILE_LOCK_ALLOCATION_TAG);
  184. if (NewEntry == NULL) {
  185. Status = STATUS_INSUFFICIENT_RESOURCES;
  186. goto SetFileLockEnd;
  187. }
  188. }
  189. NewEntry->Type = Lock->Type;
  190. NewEntry->Offset = Lock->Offset;
  191. NewEntry->Size = Lock->Size;
  192. NewEntry->Process = PsGetCurrentProcess();
  193. SplitEntry = MmAllocateNonPagedPool(sizeof(FILE_LOCK_ENTRY),
  194. FILE_LOCK_ALLOCATION_TAG);
  195. if (NewEntry == NULL) {
  196. Status = STATUS_INSUFFICIENT_RESOURCES;
  197. goto SetFileLockEnd;
  198. }
  199. INSERT_BEFORE(&(SplitEntry->ListEntry), &FreeList);
  200. //
  201. // Race to create the event if not created.
  202. //
  203. if (FileObject->FileLockEvent == NULL) {
  204. NewEvent = KeCreateEvent(NULL);
  205. if (NewEvent == NULL) {
  206. Status = STATUS_INSUFFICIENT_RESOURCES;
  207. goto SetFileLockEnd;
  208. }
  209. PreviousEvent = (PKEVENT)RtlAtomicCompareExchange(
  210. (PUINTN)&(FileObject->FileLockEvent),
  211. (UINTN)NewEvent,
  212. (UINTN)NULL);
  213. if (PreviousEvent != NULL) {
  214. KeDestroyEvent(NewEvent);
  215. }
  216. }
  217. while (TRUE) {
  218. if (LockHeld == FALSE) {
  219. KeAcquireSharedExclusiveLockExclusive(FileObject->Lock);
  220. LockHeld = TRUE;
  221. }
  222. //
  223. // If this really is setting a lock, do a dry run to see if this would
  224. // work.
  225. //
  226. if (Lock->Type != FileLockUnlock) {
  227. Status = IopTryToSetFileLock(FileObject,
  228. NewEntry,
  229. NULL,
  230. TRUE);
  231. if (!KSUCCESS(Status)) {
  232. //
  233. // Wait for something to unlock.
  234. //
  235. if (Blocking != FALSE) {
  236. KeReleaseSharedExclusiveLockExclusive(FileObject->Lock);
  237. LockHeld = FALSE;
  238. Status = KeWaitForEvent(FileObject->FileLockEvent,
  239. TRUE,
  240. WAIT_TIME_INDEFINITE);
  241. //
  242. // The thread was interrupted.
  243. //
  244. if (!KSUCCESS(Status)) {
  245. goto SetFileLockEnd;
  246. }
  247. continue;
  248. //
  249. // Not blocking, the dry run was the only attempt.
  250. //
  251. } else {
  252. break;
  253. }
  254. }
  255. }
  256. //
  257. // Do this for real. This should not fail, as any failures should
  258. // have happened during the try run.
  259. //
  260. Status = IopTryToSetFileLock(FileObject, NewEntry, &FreeList, FALSE);
  261. ASSERT(KSUCCESS(Status));
  262. NewEntry = NULL;
  263. break;
  264. }
  265. SetFileLockEnd:
  266. if (LockHeld != FALSE) {
  267. KeReleaseSharedExclusiveLockExclusive(FileObject->Lock);
  268. }
  269. if ((NewEntry != NULL) && (NewEntry != &RemoveEntry)) {
  270. MmFreeNonPagedPool(NewEntry);
  271. }
  272. while (LIST_EMPTY(&FreeList) == FALSE) {
  273. Entry = LIST_VALUE(FreeList.Next, FILE_LOCK_ENTRY, ListEntry);
  274. LIST_REMOVE(&(Entry->ListEntry));
  275. MmFreeNonPagedPool(Entry);
  276. }
  277. return Status;
  278. }
  279. VOID
  280. IopRemoveFileLocks (
  281. PIO_HANDLE IoHandle,
  282. PKPROCESS Process
  283. )
  284. /*++
  285. Routine Description:
  286. This routine destroys any locks the given process has on the file object
  287. pointed to by the given I/O handle. This routine is called anytime any
  288. file descriptor is closed by a process, even if other file descriptors
  289. to the same file in the process remain open.
  290. Arguments:
  291. IoHandle - Supplies a pointer to the I/O handle being closed.
  292. Process - Supplies the process closing the handle.
  293. Return Value:
  294. None.
  295. --*/
  296. {
  297. PLIST_ENTRY CurrentEntry;
  298. PFILE_OBJECT FileObject;
  299. LIST_ENTRY FreeList;
  300. PFILE_LOCK_ENTRY LockEntry;
  301. ASSERT(KeGetRunLevel() == RunLevelLow);
  302. //
  303. // Exit quickly if there are no file locks.
  304. //
  305. FileObject = IoHandle->FileObject;
  306. if (LIST_EMPTY(&(FileObject->FileLockList)) != FALSE) {
  307. return;
  308. }
  309. INITIALIZE_LIST_HEAD(&FreeList);
  310. KeAcquireSharedExclusiveLockExclusive(FileObject->Lock);
  311. //
  312. // Loop through and remove any active locks belonging to this process.
  313. //
  314. CurrentEntry = FileObject->FileLockList.Next;
  315. while (CurrentEntry != &(FileObject->FileLockList)) {
  316. LockEntry = LIST_VALUE(CurrentEntry, FILE_LOCK_ENTRY, ListEntry);
  317. CurrentEntry = CurrentEntry->Next;
  318. if (LockEntry->Process == Process) {
  319. LIST_REMOVE(&(LockEntry->ListEntry));
  320. INSERT_BEFORE(&(LockEntry->ListEntry), &FreeList);
  321. }
  322. }
  323. //
  324. // If locks were removed, signal anyone blocked on this file.
  325. //
  326. if (LIST_EMPTY(&FreeList) == FALSE) {
  327. KeSignalEvent(FileObject->FileLockEvent, SignalOptionSignalAll);
  328. }
  329. KeReleaseSharedExclusiveLockExclusive(FileObject->Lock);
  330. //
  331. // Free any removed entries now that the lock is released.
  332. //
  333. while (LIST_EMPTY(&FreeList) == FALSE) {
  334. LockEntry = LIST_VALUE(FreeList.Next, FILE_LOCK_ENTRY, ListEntry);
  335. LIST_REMOVE(&(LockEntry->ListEntry));
  336. MmFreeNonPagedPool(LockEntry);
  337. }
  338. return;
  339. }
  340. //
  341. // --------------------------------------------------------- Internal Functions
  342. //
  343. KSTATUS
  344. IopTryToSetFileLock (
  345. PFILE_OBJECT FileObject,
  346. PFILE_LOCK_ENTRY NewEntry,
  347. PLIST_ENTRY FreeList,
  348. BOOL DryRun
  349. )
  350. /*++
  351. Routine Description:
  352. This routine attempts to lock or unlocks a portion of a file. If the
  353. process already has a lock on any part of the region, the old lock is
  354. replaced with this new region. Remove a lock by specifying a lock type of
  355. unlock. This routine assumes the file properties lock is already held.
  356. Arguments:
  357. FileObject - Supplies a pointer to the file object.
  358. NewEntry - Supplies a pointer to the new lock to add.
  359. FreeList - Supplies a pointer to a list head that on input contains one
  360. free entry, needed to potentially split an entry. On output, entries
  361. that need to be freed will be put on this list.
  362. DryRun - Supplies a pointer indicating if the lock should actually be
  363. created/destroyed or just checked.
  364. Return Value:
  365. Status code.
  366. --*/
  367. {
  368. PLIST_ENTRY CurrentEntry;
  369. PFILE_LOCK_ENTRY LockEntry;
  370. BOOL LocksRemoved;
  371. ULONGLONG NewEnd;
  372. BOOL Overlap;
  373. PKPROCESS Process;
  374. PFILE_LOCK_ENTRY SplitEntry;
  375. KSTATUS Status;
  376. LocksRemoved = FALSE;
  377. NewEnd = NewEntry->Offset + NewEntry->Size;
  378. Process = PsGetCurrentProcess();
  379. Status = STATUS_SUCCESS;
  380. CurrentEntry = FileObject->FileLockList.Next;
  381. while (CurrentEntry != &(FileObject->FileLockList)) {
  382. LockEntry = LIST_VALUE(CurrentEntry, FILE_LOCK_ENTRY, ListEntry);
  383. CurrentEntry = CurrentEntry->Next;
  384. //
  385. // If the lock belongs to the current process and overlaps the given
  386. // region, it is to be removed.
  387. //
  388. if (LockEntry->Process == Process) {
  389. Overlap = IopDoFileLocksOverlap(NewEntry->Offset,
  390. NewEntry->Size,
  391. LockEntry);
  392. if ((Overlap != FALSE) && (DryRun == FALSE)) {
  393. LocksRemoved = TRUE;
  394. //
  395. // If the existing entry starts before the new one, it needs to
  396. // be shrunk or split.
  397. //
  398. if (LockEntry->Offset < NewEntry->Offset) {
  399. //
  400. // If it ends after the new one, split it.
  401. //
  402. if ((NewEntry->Size != 0) &&
  403. ((LockEntry->Offset + LockEntry->Size > NewEnd) ||
  404. (LockEntry->Size == 0))) {
  405. ASSERT(LIST_EMPTY(FreeList) == FALSE);
  406. SplitEntry = LIST_VALUE(FreeList->Next,
  407. FILE_LOCK_ENTRY,
  408. ListEntry);
  409. LIST_REMOVE(&(SplitEntry->ListEntry));
  410. SplitEntry->Type = LockEntry->Type;
  411. SplitEntry->Offset = NewEnd;
  412. if (LockEntry->Size == 0) {
  413. SplitEntry->Size = 0;
  414. } else {
  415. SplitEntry->Size = LockEntry->Offset +
  416. LockEntry->Size - NewEnd;
  417. }
  418. INSERT_AFTER(&(SplitEntry->ListEntry),
  419. &(LockEntry->ListEntry));
  420. }
  421. //
  422. // Shrink its length.
  423. //
  424. LockEntry->Size = NewEntry->Offset - LockEntry->Offset;
  425. //
  426. // The current entry starts within the new entry. If it
  427. // ends after the new entry, shrink it.
  428. //
  429. } else if ((NewEntry->Size != 0) &&
  430. ((LockEntry->Offset + LockEntry->Size > NewEnd) ||
  431. (LockEntry->Size == 0))) {
  432. if (LockEntry->Size != 0) {
  433. LockEntry->Size = LockEntry->Offset + LockEntry->Size -
  434. NewEnd;
  435. }
  436. LockEntry->Offset = NewEnd;
  437. //
  438. // The new entry completely swallows the existing one.
  439. //
  440. } else {
  441. LIST_REMOVE(&(LockEntry->ListEntry));
  442. INSERT_BEFORE(&(LockEntry->ListEntry), FreeList);
  443. }
  444. }
  445. //
  446. // Another process owns this lock.
  447. //
  448. } else {
  449. //
  450. // Read locks can coexist.
  451. //
  452. if ((NewEntry->Type == FileLockRead) &&
  453. (LockEntry->Type == FileLockRead)) {
  454. continue;
  455. }
  456. //
  457. // If the file lock overlaps with the incoming one, then fail.
  458. //
  459. Overlap = IopDoFileLocksOverlap(NewEntry->Offset,
  460. NewEntry->Size,
  461. LockEntry);
  462. //
  463. // This routine should not be discovering overlaps on the real
  464. // deal.
  465. //
  466. ASSERT((Overlap == FALSE) || (DryRun != FALSE));
  467. if (Overlap != FALSE) {
  468. Status = STATUS_RESOURCE_IN_USE;
  469. KeSignalEvent(FileObject->FileLockEvent, SignalOptionUnsignal);
  470. break;
  471. }
  472. }
  473. }
  474. //
  475. // Add the new entry if conditions are right.
  476. //
  477. if ((KSUCCESS(Status)) && (DryRun == FALSE) &&
  478. (NewEntry->Type != FileLockUnlock)) {
  479. INSERT_AFTER(&(NewEntry->ListEntry), &(FileObject->FileLockList));
  480. }
  481. if (LocksRemoved != FALSE) {
  482. KeSignalEvent(FileObject->FileLockEvent, SignalOptionSignalAll);
  483. }
  484. return Status;
  485. }
  486. BOOL
  487. IopDoFileLocksOverlap (
  488. ULONGLONG IncomingOffset,
  489. ULONGLONG IncomingSize,
  490. PFILE_LOCK_ENTRY LockEntry
  491. )
  492. /*++
  493. Routine Description:
  494. This routine checks to see if the given lock entry overlaps with the
  495. incoming lock. The lock types are not checked by this routine, only the
  496. regions.
  497. Arguments:
  498. IncomingOffset - Supplies the file offset of the incoming lock.
  499. IncomingSize - Supplies the file size of the incoming lock.
  500. LockEntry - Supplies a pointer to the existing lock entry.
  501. Return Value:
  502. TRUE if the lock entry overlaps the incoming lock.
  503. FALSE if the lock entry does not overlap.
  504. --*/
  505. {
  506. //
  507. // If the incoming lock goes to the end of the file, then this lock
  508. // overlaps if it has an offset greater than the incoming one or its
  509. // size is also zero.
  510. //
  511. if (IncomingSize == 0) {
  512. if ((LockEntry->Size == 0) || (LockEntry->Offset >= IncomingOffset)) {
  513. return TRUE;
  514. }
  515. //
  516. // The incoming lock spans a specific region. This lock overlaps if
  517. // one of these conditions is met:
  518. // 1. Size == 0 and the offset starts before the incoming lock's end.
  519. // 2. Offset <= IncomingOffset and Offset + Size > IncomingOffset.
  520. // 3. Offset > IncomingOffset and Offset < IncomingOffset + IncomingSize.
  521. //
  522. } else {
  523. if (LockEntry->Size == 0) {
  524. if (LockEntry->Offset < IncomingOffset + IncomingSize) {
  525. return TRUE;
  526. }
  527. } else if (LockEntry->Offset <= IncomingOffset) {
  528. if (LockEntry->Offset + LockEntry->Size > IncomingOffset) {
  529. return TRUE;
  530. }
  531. } else {
  532. if (LockEntry->Offset < IncomingOffset + IncomingSize) {
  533. return TRUE;
  534. }
  535. }
  536. }
  537. return FALSE;
  538. }