[WIN32K:NTUSER] Correctly delete menus in failure cases in MENU_GetSystemMenu. CORE...
[reactos.git] / win32ss / gdi / ntgdi / gdipool.c
1 /*
2 * PROJECT: ReactOS win32 kernel mode subsystem
3 * LICENSE: GPL - See COPYING in the top level directory
4 * FILE: win32ss/gdi/ntgdi/gdipool.c
5 * PURPOSE: Static size allocator for user mode object attributes
6 * PROGRAMMERS: Timo Kreuzer
7 */
8
9 /* INCLUDES ******************************************************************/
10
11 #include <win32k.h>
12 #define NDEBUG
13 #include <debug.h>
14
15 typedef struct _GDI_POOL_SECTION
16 {
17 LIST_ENTRY leInUseLink;
18 LIST_ENTRY leReadyLink;
19
20 PVOID pvBaseAddress;
21
22 ULONG ulCommitBitmap;
23 ULONG cAllocCount;
24
25 RTL_BITMAP bitmap;
26 ULONG aulBits[1];
27 } GDI_POOL_SECTION, *PGDI_POOL_SECTION;
28
29 typedef struct _GDI_POOL
30 {
31 ULONG ulTag;
32 ULONG cjAllocSize;
33 ULONG cjSectionSize; // 32 * cjAllocSize, rounded up to pages
34 ULONG cSlotsPerSection;
35 ULONG cEmptySections;
36 EX_PUSH_LOCK pushlock; // For pool growth
37 #if DBG_ENABLE_EVENT_LOGGING
38 SLIST_HEADER slhLog;
39 #endif
40
41 LIST_ENTRY leInUseList;
42 LIST_ENTRY leEmptyList;
43 LIST_ENTRY leReadyList;
44 } GDI_POOL;
45
46 #define GDI_POOL_ALLOCATION_GRANULARITY 64 * 1024
47
48 static
49 PGDI_POOL_SECTION
50 GdiPoolAllocateSection(PGDI_POOL pPool)
51 {
52 PGDI_POOL_SECTION pSection;
53 PVOID pvBaseAddress;
54 SIZE_T cjSize;
55 NTSTATUS status;
56
57 /* Allocate a section object */
58 cjSize = sizeof(GDI_POOL_SECTION) + pPool->cSlotsPerSection / sizeof(ULONG);
59 pSection = EngAllocMem(0, cjSize, pPool->ulTag);
60 if (!pSection)
61 {
62 return NULL;
63 }
64
65 /* Reserve user mode memory */
66 cjSize = GDI_POOL_ALLOCATION_GRANULARITY;
67 pvBaseAddress = NULL;
68 status = ZwAllocateVirtualMemory(NtCurrentProcess(),
69 &pvBaseAddress,
70 0,
71 &cjSize,
72 MEM_RESERVE,
73 PAGE_READWRITE);
74 if (!NT_SUCCESS(status))
75 {
76 EngFreeMem(pSection);
77 return NULL;
78 }
79
80 /* Initialize the section */
81 pSection->pvBaseAddress = pvBaseAddress;
82 pSection->ulCommitBitmap = 0;
83 pSection->cAllocCount = 0;
84 RtlInitializeBitMap(&pSection->bitmap,
85 pSection->aulBits,
86 pPool->cSlotsPerSection);
87 RtlClearAllBits(&pSection->bitmap);
88
89 /* Return the section */
90 return pSection;
91 }
92
93 static
94 VOID
95 GdiPoolDeleteSection(PGDI_POOL pPool, PGDI_POOL_SECTION pSection)
96 {
97 NTSTATUS status;
98 SIZE_T cjSize = 0;
99
100 /* Should not have any allocations */
101 if (pSection->cAllocCount != 0)
102 {
103 DPRINT1("There are %lu allocations left, section=%p, pool=%p\n",
104 pSection->cAllocCount, pSection, pPool);
105 DBG_DUMP_EVENT_LIST(&pPool->slhLog);
106 ASSERT(FALSE);
107 }
108
109 /* Release the virtual memory */
110 status = ZwFreeVirtualMemory(NtCurrentProcess(),
111 &pSection->pvBaseAddress,
112 &cjSize,
113 MEM_RELEASE);
114 ASSERT(NT_SUCCESS(status));
115
116 /* Free the section object */
117 EngFreeMem(pSection);
118 }
119
120 PVOID
121 NTAPI
122 GdiPoolAllocate(
123 PGDI_POOL pPool)
124 {
125 PGDI_POOL_SECTION pSection;
126 ULONG ulIndex, cjOffset, ulPageBit;
127 PLIST_ENTRY ple;
128 PVOID pvAlloc, pvBaseAddress;
129 SIZE_T cjSize;
130 NTSTATUS status;
131
132 /* Disable APCs and acquire the pool lock */
133 KeEnterCriticalRegion();
134 ExAcquirePushLockExclusive(&pPool->pushlock);
135
136 /* Check if we have a ready section */
137 if (!IsListEmpty(&pPool->leReadyList))
138 {
139 /* Get a free section */
140 ple = pPool->leReadyList.Flink;
141 pSection = CONTAINING_RECORD(ple, GDI_POOL_SECTION, leReadyLink);
142 if (pSection->cAllocCount >= pPool->cSlotsPerSection)
143 {
144 DPRINT1("pSection->cAllocCount=%lu, pPool->cSlotsPerSection=%lu\n",
145 pSection->cAllocCount, pPool->cSlotsPerSection);
146 DBG_DUMP_EVENT_LIST(&pPool->slhLog);
147 ASSERT(FALSE);
148 }
149 ASSERT(pSection->cAllocCount < pPool->cSlotsPerSection);
150 }
151 else
152 {
153 /* No, check if we have something on the empty list */
154 if (!IsListEmpty(&pPool->leEmptyList))
155 {
156 /* Yes, remove it from the empty list */
157 ple = RemoveHeadList(&pPool->leEmptyList);
158 pSection = CONTAINING_RECORD(ple, GDI_POOL_SECTION, leInUseLink);
159 pPool->cEmptySections--;
160 ASSERT(pSection->cAllocCount == 0);
161 }
162 else
163 {
164 /* No, allocate a new section */
165 pSection = GdiPoolAllocateSection(pPool);
166 if (!pSection)
167 {
168 DPRINT1("Couldn't allocate a section\n");
169 pvAlloc = NULL;
170 goto done;
171 }
172 }
173
174 /* Insert it into the in-use and ready list */
175 InsertHeadList(&pPool->leInUseList, &pSection->leInUseLink);
176 InsertHeadList(&pPool->leReadyList, &pSection->leReadyLink);
177 }
178
179 /* Find and set a single bit */
180 ulIndex = RtlFindClearBitsAndSet(&pSection->bitmap, 1, 0);
181 ASSERT(ulIndex != MAXULONG);
182
183 /* Calculate the allocation address */
184 cjOffset = ulIndex * pPool->cjAllocSize;
185 pvAlloc = (PVOID)((ULONG_PTR)pSection->pvBaseAddress + cjOffset);
186
187 /* Check if memory is comitted */
188 ulPageBit = 1 << (cjOffset / PAGE_SIZE);
189 ulPageBit |= 1 << ((cjOffset + pPool->cjAllocSize - 1) / PAGE_SIZE);
190 if ((pSection->ulCommitBitmap & ulPageBit) != ulPageBit)
191 {
192 /* Commit the pages */
193 pvBaseAddress = PAGE_ALIGN(pvAlloc);
194 cjSize = ADDRESS_AND_SIZE_TO_SPAN_PAGES(pvAlloc, pPool->cjAllocSize) * PAGE_SIZE;
195 status = ZwAllocateVirtualMemory(NtCurrentProcess(),
196 &pvBaseAddress,
197 0,
198 &cjSize,
199 MEM_COMMIT,
200 PAGE_READWRITE);
201 if (!NT_SUCCESS(status))
202 {
203 pvAlloc = NULL;
204 goto done;
205 }
206
207 pSection->ulCommitBitmap |= ulPageBit;
208 }
209
210 /* Increase alloc count */
211 pSection->cAllocCount++;
212 ASSERT(RtlNumberOfSetBits(&pSection->bitmap) == pSection->cAllocCount);
213 DBG_LOGEVENT(&pPool->slhLog, EVENT_ALLOCATE, pvAlloc);
214
215 /* Check if section is now busy */
216 if (pSection->cAllocCount == pPool->cSlotsPerSection)
217 {
218 /* Remove the section from the ready list */
219 RemoveEntryList(&pSection->leReadyLink);
220 }
221
222 done:
223 /* Release the pool lock and enable APCs */
224 ExReleasePushLockExclusive(&pPool->pushlock);
225 KeLeaveCriticalRegion();
226
227 DPRINT("GdiPoolallocate: %p\n", pvAlloc);
228 return pvAlloc;
229 }
230
231 VOID
232 NTAPI
233 GdiPoolFree(
234 PGDI_POOL pPool,
235 PVOID pvAlloc)
236 {
237 PLIST_ENTRY ple;
238 PGDI_POOL_SECTION pSection = NULL;
239 ULONG_PTR cjOffset;
240 ULONG ulIndex;
241 DPRINT("GdiPoolFree: %p\n", pvAlloc);
242
243 /* Disable APCs and acquire the pool lock */
244 KeEnterCriticalRegion();
245 ExAcquirePushLockExclusive(&pPool->pushlock);
246
247 /* Loop all used sections */
248 for (ple = pPool->leInUseList.Flink;
249 ple != &pPool->leInUseList;
250 ple = ple->Flink)
251 {
252 /* Get the pointer to the section */
253 pSection = CONTAINING_RECORD(ple, GDI_POOL_SECTION, leInUseLink);
254
255 /* Calculate offset */
256 cjOffset = (ULONG_PTR)pvAlloc - (ULONG_PTR)pSection->pvBaseAddress;
257
258 /* Check if the allocation is from this section */
259 if (cjOffset < pPool->cjSectionSize)
260 {
261 /* Calculate the index of the allocation */
262 ulIndex = cjOffset / pPool->cjAllocSize;
263
264 /* Mark it as free */
265 ASSERT(RtlTestBit(&pSection->bitmap, ulIndex) == TRUE);
266 RtlClearBit(&pSection->bitmap, ulIndex);
267
268 /* Decrease allocation count */
269 pSection->cAllocCount--;
270 ASSERT(RtlNumberOfSetBits(&pSection->bitmap) == pSection->cAllocCount);
271 DBG_LOGEVENT(&pPool->slhLog, EVENT_FREE, pvAlloc);
272
273 /* Check if the section got valid now */
274 if (pSection->cAllocCount == pPool->cSlotsPerSection - 1)
275 {
276 /* Insert it into the ready list */
277 InsertTailList(&pPool->leReadyList, &pSection->leReadyLink);
278 }
279 /* Check if it got empty now */
280 else if (pSection->cAllocCount == 0)
281 {
282 /* Remove the section from the lists */
283 RemoveEntryList(&pSection->leInUseLink);
284 RemoveEntryList(&pSection->leReadyLink);
285
286 if (pPool->cEmptySections >= 1)
287 {
288 /* Delete the section */
289 GdiPoolDeleteSection(pPool, pSection);
290 }
291 else
292 {
293 /* Insert it into the empty list */
294 InsertHeadList(&pPool->leEmptyList, &pSection->leInUseLink);
295 pPool->cEmptySections++;
296 }
297 }
298
299 goto done;
300 }
301 }
302
303 DbgPrint("failed to free. pvAlloc=%p, base=%p, size=%lx\n",
304 pvAlloc, pSection ? pSection->pvBaseAddress : NULL, pPool->cjSectionSize);
305 ASSERT(FALSE);
306 // KeBugCheck()
307
308 done:
309 /* Release the pool lock and enable APCs */
310 ExReleasePushLockExclusive(&pPool->pushlock);
311 KeLeaveCriticalRegion();
312 }
313
314 PGDI_POOL
315 NTAPI
316 GdiPoolCreate(
317 ULONG cjAllocSize,
318 ULONG ulTag)
319 {
320 PGDI_POOL pPool;
321
322 /* Allocate a pool object */
323 pPool = EngAllocMem(0, sizeof(GDI_POOL), 'lopG');
324 if (!pPool) return NULL;
325
326 /* Initialize the object */
327 ExInitializePushLock(&pPool->pushlock);
328 InitializeListHead(&pPool->leInUseList);
329 InitializeListHead(&pPool->leReadyList);
330 InitializeListHead(&pPool->leEmptyList);
331 pPool->cEmptySections = 0;
332 pPool->cjAllocSize = cjAllocSize;
333 pPool->ulTag = ulTag;
334 pPool->cjSectionSize = GDI_POOL_ALLOCATION_GRANULARITY;
335 pPool->cSlotsPerSection = pPool->cjSectionSize / cjAllocSize;
336 DBG_INITLOG(&pPool->slhLog);
337
338 return pPool;
339 }
340
341 VOID
342 NTAPI
343 GdiPoolDestroy(PGDI_POOL pPool)
344 {
345 PGDI_POOL_SECTION pSection;
346 PLIST_ENTRY ple;
347
348 /* Loop all empty sections, removing them */
349 while (!IsListEmpty(&pPool->leEmptyList))
350 {
351 /* Delete the section */
352 ple = RemoveHeadList(&pPool->leEmptyList);
353 pSection = CONTAINING_RECORD(ple, GDI_POOL_SECTION, leInUseLink);
354 GdiPoolDeleteSection(pPool, pSection);
355 }
356
357 /* Loop all ready sections, removing them */
358 while (!IsListEmpty(&pPool->leInUseList))
359 {
360 /* Delete the section */
361 ple = RemoveHeadList(&pPool->leInUseList);
362 pSection = CONTAINING_RECORD(ple, GDI_POOL_SECTION, leInUseLink);
363 GdiPoolDeleteSection(pPool, pSection);
364 }
365
366 DBG_CLEANUP_EVENT_LIST(&pPool->slhLog);
367
368 EngFreeMem(pPool);
369 }