xref: /reactos/base/setup/lib/utils/inicache.c (revision 7f26a396)
1 /*
2  * PROJECT:     ReactOS Setup Library
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     INI file parser that caches contents of INI file in memory.
5  * COPYRIGHT:   Copyright 2002-2018 Royce Mitchell III
6  */
7 
8 /* INCLUDES *****************************************************************/
9 
10 #include "precomp.h"
11 
12 #include "inicache.h"
13 
14 #define NDEBUG
15 #include <debug.h>
16 
17 /* PRIVATE FUNCTIONS ********************************************************/
18 
19 static VOID
20 IniCacheFreeKey(
21     _In_ PINI_KEYWORD Key)
22 {
23     /* Unlink the key */
24     RemoveEntryList(&Key->ListEntry);
25 
26     /* Free its data */
27     if (Key->Name)
28         RtlFreeHeap(ProcessHeap, 0, Key->Name);
29     if (Key->Data)
30         RtlFreeHeap(ProcessHeap, 0, Key->Data);
31     RtlFreeHeap(ProcessHeap, 0, Key);
32 }
33 
34 static VOID
35 IniCacheFreeSection(
36     _In_ PINI_SECTION Section)
37 {
38     /* Unlink the section */
39     RemoveEntryList(&Section->ListEntry);
40 
41     /* Free its data */
42     while (!IsListEmpty(&Section->KeyList))
43     {
44         PLIST_ENTRY Entry = RemoveHeadList(&Section->KeyList);
45         PINI_KEYWORD Key = CONTAINING_RECORD(Entry, INI_KEYWORD, ListEntry);
46         IniCacheFreeKey(Key);
47     }
48     if (Section->Name)
49         RtlFreeHeap(ProcessHeap, 0, Section->Name);
50     RtlFreeHeap(ProcessHeap, 0, Section);
51 }
52 
53 static
54 PINI_SECTION
55 IniCacheFindSection(
56     _In_ PINICACHE Cache,
57     _In_ PCWSTR Name)
58 {
59     PLIST_ENTRY Entry;
60 
61     for (Entry  =  Cache->SectionList.Flink;
62          Entry != &Cache->SectionList;
63          Entry  =  Entry->Flink)
64     {
65         PINI_SECTION Section = CONTAINING_RECORD(Entry, INI_SECTION, ListEntry);
66         if (_wcsicmp(Section->Name, Name) == 0)
67             return Section;
68     }
69     return NULL;
70 }
71 
72 static
73 PINI_KEYWORD
74 IniCacheFindKey(
75     _In_ PINI_SECTION Section,
76     _In_ PCWSTR Name)
77 {
78     PLIST_ENTRY Entry;
79 
80     for (Entry  =  Section->KeyList.Flink;
81          Entry != &Section->KeyList;
82          Entry  =  Entry->Flink)
83     {
84         PINI_KEYWORD Key = CONTAINING_RECORD(Entry, INI_KEYWORD, ListEntry);
85         if (_wcsicmp(Key->Name, Name) == 0)
86             return Key;
87     }
88     return NULL;
89 }
90 
91 static
92 PINI_KEYWORD
93 IniCacheAddKeyAorW(
94     _In_ PINI_SECTION Section,
95     _In_ PINI_KEYWORD AnchorKey,
96     _In_ INSERTION_TYPE InsertionType,
97     _In_ const VOID* Name,
98     _In_ ULONG NameLength,
99     _In_ const VOID* Data,
100     _In_ ULONG DataLength,
101     _In_ BOOLEAN IsUnicode)
102 {
103     PINI_KEYWORD Key;
104     PWSTR NameU, DataU;
105 
106     if (!Section || !Name || NameLength == 0 || !Data || DataLength == 0)
107     {
108         DPRINT("Invalid parameter\n");
109         return NULL;
110     }
111 
112     /* Allocate the UNICODE key name */
113     NameU = (PWSTR)RtlAllocateHeap(ProcessHeap,
114                                    0,
115                                    (NameLength + 1) * sizeof(WCHAR));
116     if (!NameU)
117     {
118         DPRINT("RtlAllocateHeap() failed\n");
119         return NULL;
120     }
121     /* Copy the value name (ANSI or UNICODE) */
122     if (IsUnicode)
123         wcsncpy(NameU, (PCWCH)Name, NameLength);
124     else
125         _snwprintf(NameU, NameLength, L"%.*S", NameLength, (PCCH)Name);
126     NameU[NameLength] = UNICODE_NULL;
127 
128     /*
129      * Find whether a key with the given name already exists in the section.
130      * If so, modify the data and return it; otherwise create a new one.
131      */
132     Key = IniCacheFindKey(Section, NameU);
133     if (Key)
134     {
135         RtlFreeHeap(ProcessHeap, 0, NameU);
136 
137         /* Modify the existing data */
138 
139         /* Allocate the UNICODE data buffer */
140         DataU = (PWSTR)RtlAllocateHeap(ProcessHeap,
141                                        0,
142                                        (DataLength + 1) * sizeof(WCHAR));
143         if (!DataU)
144         {
145             DPRINT("RtlAllocateHeap() failed\n");
146             return NULL; // We failed, don't modify the original key.
147         }
148         /* Copy the data (ANSI or UNICODE) */
149         if (IsUnicode)
150             wcsncpy(DataU, (PCWCH)Data, DataLength);
151         else
152             _snwprintf(DataU, DataLength, L"%.*S", DataLength, (PCCH)Data);
153         DataU[DataLength] = UNICODE_NULL;
154 
155         /* Swap the old key data with the new one */
156         RtlFreeHeap(ProcessHeap, 0, Key->Data);
157         Key->Data = DataU;
158 
159         /* Return the modified key */
160         return Key;
161     }
162 
163     /* Allocate the key buffer and name */
164     Key = (PINI_KEYWORD)RtlAllocateHeap(ProcessHeap,
165                                         HEAP_ZERO_MEMORY,
166                                         sizeof(INI_KEYWORD));
167     if (!Key)
168     {
169         DPRINT("RtlAllocateHeap() failed\n");
170         RtlFreeHeap(ProcessHeap, 0, NameU);
171         return NULL;
172     }
173     Key->Name = NameU;
174 
175     /* Allocate the UNICODE data buffer */
176     DataU = (PWSTR)RtlAllocateHeap(ProcessHeap,
177                                    0,
178                                    (DataLength + 1) * sizeof(WCHAR));
179     if (!DataU)
180     {
181         DPRINT("RtlAllocateHeap() failed\n");
182         RtlFreeHeap(ProcessHeap, 0, NameU);
183         RtlFreeHeap(ProcessHeap, 0, Key);
184         return NULL;
185     }
186     /* Copy the data (ANSI or UNICODE) */
187     if (IsUnicode)
188         wcsncpy(DataU, (PCWCH)Data, DataLength);
189     else
190         _snwprintf(DataU, DataLength, L"%.*S", DataLength, (PCCH)Data);
191     DataU[DataLength] = UNICODE_NULL;
192     Key->Data = DataU;
193 
194     /* Insert the key into section */
195     if (IsListEmpty(&Section->KeyList))
196     {
197         InsertHeadList(&Section->KeyList, &Key->ListEntry);
198     }
199     else if ((InsertionType == INSERT_FIRST) ||
200              ((InsertionType == INSERT_BEFORE) &&
201                 (!AnchorKey || (&AnchorKey->ListEntry == Section->KeyList.Flink))))
202     {
203         /* Insert at the head of the list */
204         InsertHeadList(&Section->KeyList, &Key->ListEntry);
205     }
206     else if ((InsertionType == INSERT_BEFORE) && AnchorKey)
207     {
208         /* Insert before the anchor key */
209         InsertTailList(&AnchorKey->ListEntry, &Key->ListEntry);
210     }
211     else if ((InsertionType == INSERT_LAST) ||
212              ((InsertionType == INSERT_AFTER) &&
213                 (!AnchorKey || (&AnchorKey->ListEntry == Section->KeyList.Blink))))
214     {
215         /* Insert at the tail of the list */
216         InsertTailList(&Section->KeyList, &Key->ListEntry);
217     }
218     else if ((InsertionType == INSERT_AFTER) && AnchorKey)
219     {
220         /* Insert after the anchor key */
221         InsertHeadList(&AnchorKey->ListEntry, &Key->ListEntry);
222     }
223 
224     return Key;
225 }
226 
227 static
228 PINI_SECTION
229 IniCacheAddSectionAorW(
230     _In_ PINICACHE Cache,
231     _In_ const VOID* Name,
232     _In_ ULONG NameLength,
233     _In_ BOOLEAN IsUnicode)
234 {
235     PINI_SECTION Section;
236     PWSTR NameU;
237 
238     if (!Cache || !Name || NameLength == 0)
239     {
240         DPRINT("Invalid parameter\n");
241         return NULL;
242     }
243 
244     /* Allocate the UNICODE section name */
245     NameU = (PWSTR)RtlAllocateHeap(ProcessHeap,
246                                    0,
247                                    (NameLength + 1) * sizeof(WCHAR));
248     if (!NameU)
249     {
250         DPRINT("RtlAllocateHeap() failed\n");
251         return NULL;
252     }
253     /* Copy the section name (ANSI or UNICODE) */
254     if (IsUnicode)
255         wcsncpy(NameU, (PCWCH)Name, NameLength);
256     else
257         _snwprintf(NameU, NameLength, L"%.*S", NameLength, (PCCH)Name);
258     NameU[NameLength] = UNICODE_NULL;
259 
260     /*
261      * Find whether a section with the given name already exists.
262      * If so, just return it; otherwise create a new one.
263      */
264     Section = IniCacheFindSection(Cache, NameU);
265     if (Section)
266     {
267         RtlFreeHeap(ProcessHeap, 0, NameU);
268         return Section;
269     }
270 
271     /* Allocate the section buffer and name */
272     Section = (PINI_SECTION)RtlAllocateHeap(ProcessHeap,
273                                             HEAP_ZERO_MEMORY,
274                                             sizeof(INI_SECTION));
275     if (!Section)
276     {
277         DPRINT("RtlAllocateHeap() failed\n");
278         RtlFreeHeap(ProcessHeap, 0, NameU);
279         return NULL;
280     }
281     Section->Name = NameU;
282     InitializeListHead(&Section->KeyList);
283 
284     /* Append the section */
285     InsertTailList(&Cache->SectionList, &Section->ListEntry);
286 
287     return Section;
288 }
289 
290 static
291 PCHAR
292 IniCacheSkipWhitespace(
293     PCHAR Ptr)
294 {
295     while (*Ptr != 0 && isspace(*Ptr))
296         Ptr++;
297 
298     return (*Ptr == 0) ? NULL : Ptr;
299 }
300 
301 static
302 PCHAR
303 IniCacheSkipToNextSection(
304     PCHAR Ptr)
305 {
306     while (*Ptr != 0 && *Ptr != '[')
307     {
308         while (*Ptr != 0 && *Ptr != L'\n')
309         {
310             Ptr++;
311         }
312 
313         Ptr++;
314     }
315 
316     return (*Ptr == 0) ? NULL : Ptr;
317 }
318 
319 static
320 PCHAR
321 IniCacheGetSectionName(
322     PCHAR Ptr,
323     PCHAR *NamePtr,
324     PULONG NameSize)
325 {
326     ULONG Size = 0;
327 
328     *NamePtr = NULL;
329     *NameSize = 0;
330 
331     /* Skip whitespace */
332     while (*Ptr != 0 && isspace(*Ptr))
333     {
334         Ptr++;
335     }
336 
337     *NamePtr = Ptr;
338 
339     while (*Ptr != 0 && *Ptr != ']')
340     {
341         Size++;
342         Ptr++;
343     }
344     Ptr++;
345 
346     while (*Ptr != 0 && *Ptr != L'\n')
347     {
348         Ptr++;
349     }
350     Ptr++;
351 
352     *NameSize = Size;
353 
354     DPRINT("SectionName: '%.*s'\n", Size, *NamePtr);
355 
356     return Ptr;
357 }
358 
359 static
360 PCHAR
361 IniCacheGetKeyName(
362     PCHAR Ptr,
363     PCHAR *NamePtr,
364     PULONG NameSize)
365 {
366     ULONG Size = 0;
367 
368     *NamePtr = NULL;
369     *NameSize = 0;
370 
371     while (Ptr && *Ptr)
372     {
373         *NamePtr = NULL;
374         *NameSize = 0;
375         Size = 0;
376 
377         /* Skip whitespace and empty lines */
378         while (isspace(*Ptr) || *Ptr == '\n' || *Ptr == '\r')
379         {
380             Ptr++;
381         }
382         if (*Ptr == 0)
383         {
384             continue;
385         }
386 
387         *NamePtr = Ptr;
388 
389         while (*Ptr != 0 && !isspace(*Ptr) && *Ptr != '=' && *Ptr != ';')
390         {
391             Size++;
392             Ptr++;
393         }
394         if (*Ptr == ';')
395         {
396             while (*Ptr != 0 && *Ptr != '\r' && *Ptr != '\n')
397             {
398                 Ptr++;
399             }
400         }
401         else
402         {
403             *NameSize = Size;
404             break;
405         }
406     }
407 
408   return Ptr;
409 }
410 
411 static
412 PCHAR
413 IniCacheGetKeyValue(
414     PCHAR Ptr,
415     PCHAR *DataPtr,
416     PULONG DataSize,
417     BOOLEAN String)
418 {
419     ULONG Size = 0;
420 
421     *DataPtr = NULL;
422     *DataSize = 0;
423 
424     /* Skip whitespace */
425     while (*Ptr != 0 && isspace(*Ptr))
426     {
427         Ptr++;
428     }
429 
430     /* Check and skip '=' */
431     if (*Ptr != '=')
432     {
433         return NULL;
434     }
435     Ptr++;
436 
437     /* Skip whitespace */
438     while (*Ptr != 0 && isspace(*Ptr))
439     {
440         Ptr++;
441     }
442 
443     if (*Ptr == '"' && String)
444     {
445         Ptr++;
446 
447         /* Get data */
448         *DataPtr = Ptr;
449         while (*Ptr != '"')
450         {
451             Ptr++;
452             Size++;
453         }
454         Ptr++;
455 
456         while (*Ptr && *Ptr != '\r' && *Ptr != '\n')
457         {
458             Ptr++;
459         }
460     }
461     else
462     {
463         /* Get data */
464         *DataPtr = Ptr;
465         while (*Ptr != 0 && *Ptr != '\r' && *Ptr != ';')
466         {
467             Ptr++;
468             Size++;
469         }
470     }
471 
472     /* Skip to next line */
473     if (*Ptr == '\r')
474         Ptr++;
475     if (*Ptr == '\n')
476         Ptr++;
477 
478     *DataSize = Size;
479 
480     return Ptr;
481 }
482 
483 
484 /* PUBLIC FUNCTIONS *********************************************************/
485 
486 NTSTATUS
487 IniCacheLoadFromMemory(
488     PINICACHE *Cache,
489     PCHAR FileBuffer,
490     ULONG FileLength,
491     BOOLEAN String)
492 {
493     PCHAR Ptr;
494 
495     PINI_SECTION Section;
496     PINI_KEYWORD Key;
497 
498     PCHAR SectionName;
499     ULONG SectionNameSize;
500 
501     PCHAR KeyName;
502     ULONG KeyNameSize;
503 
504     PCHAR KeyValue;
505     ULONG KeyValueSize;
506 
507     /* Allocate inicache header */
508     *Cache = IniCacheCreate();
509     if (!*Cache)
510         return STATUS_INSUFFICIENT_RESOURCES;
511 
512     /* Parse ini file */
513     Section = NULL;
514     Ptr = FileBuffer;
515     while (Ptr != NULL && *Ptr != 0)
516     {
517         Ptr = IniCacheSkipWhitespace(Ptr);
518         if (Ptr == NULL)
519             continue;
520 
521         if (*Ptr == '[')
522         {
523             Section = NULL;
524             Ptr++;
525 
526             Ptr = IniCacheGetSectionName(Ptr,
527                                          &SectionName,
528                                          &SectionNameSize);
529 
530             DPRINT("[%.*s]\n", SectionNameSize, SectionName);
531 
532             Section = IniCacheAddSectionAorW(*Cache,
533                                              SectionName,
534                                              SectionNameSize,
535                                              FALSE);
536             if (Section == NULL)
537             {
538                 DPRINT("IniCacheAddSectionAorW() failed\n");
539                 Ptr = IniCacheSkipToNextSection(Ptr);
540                 continue;
541             }
542         }
543         else
544         {
545             if (Section == NULL)
546             {
547                 Ptr = IniCacheSkipToNextSection(Ptr);
548                 continue;
549             }
550 
551             Ptr = IniCacheGetKeyName(Ptr,
552                                      &KeyName,
553                                      &KeyNameSize);
554 
555             Ptr = IniCacheGetKeyValue(Ptr,
556                                       &KeyValue,
557                                       &KeyValueSize,
558                                       String);
559 
560             DPRINT("'%.*s' = '%.*s'\n", KeyNameSize, KeyName, KeyValueSize, KeyValue);
561 
562             Key = IniCacheAddKeyAorW(Section,
563                                      NULL,
564                                      INSERT_LAST,
565                                      KeyName,
566                                      KeyNameSize,
567                                      KeyValue,
568                                      KeyValueSize,
569                                      FALSE);
570             if (Key == NULL)
571             {
572                 DPRINT("IniCacheAddKeyAorW() failed\n");
573             }
574         }
575     }
576 
577     return STATUS_SUCCESS;
578 }
579 
580 NTSTATUS
581 IniCacheLoadByHandle(
582     PINICACHE *Cache,
583     HANDLE FileHandle,
584     BOOLEAN String)
585 {
586     NTSTATUS Status;
587     IO_STATUS_BLOCK IoStatusBlock;
588     FILE_STANDARD_INFORMATION FileInfo;
589     PCHAR FileBuffer;
590     ULONG FileLength;
591     LARGE_INTEGER FileOffset;
592 
593     *Cache = NULL;
594 
595     /* Query file size */
596     Status = NtQueryInformationFile(FileHandle,
597                                     &IoStatusBlock,
598                                     &FileInfo,
599                                     sizeof(FILE_STANDARD_INFORMATION),
600                                     FileStandardInformation);
601     if (!NT_SUCCESS(Status))
602     {
603         DPRINT("NtQueryInformationFile() failed (Status %lx)\n", Status);
604         return Status;
605     }
606 
607     FileLength = FileInfo.EndOfFile.u.LowPart;
608 
609     DPRINT("File size: %lu\n", FileLength);
610 
611     /* Allocate file buffer with NULL-terminator */
612     FileBuffer = (PCHAR)RtlAllocateHeap(ProcessHeap,
613                                         0,
614                                         FileLength + 1);
615     if (FileBuffer == NULL)
616     {
617         DPRINT1("RtlAllocateHeap() failed\n");
618         return STATUS_INSUFFICIENT_RESOURCES;
619     }
620 
621     /* Read file */
622     FileOffset.QuadPart = 0ULL;
623     Status = NtReadFile(FileHandle,
624                         NULL,
625                         NULL,
626                         NULL,
627                         &IoStatusBlock,
628                         FileBuffer,
629                         FileLength,
630                         &FileOffset,
631                         NULL);
632 
633     /* Append NULL-terminator */
634     FileBuffer[FileLength] = 0;
635 
636     if (!NT_SUCCESS(Status))
637     {
638         DPRINT("NtReadFile() failed (Status %lx)\n", Status);
639         goto Quit;
640     }
641 
642     Status = IniCacheLoadFromMemory(Cache, FileBuffer, FileLength, String);
643     if (!NT_SUCCESS(Status))
644     {
645         DPRINT1("IniCacheLoadFromMemory() failed (Status %lx)\n", Status);
646     }
647 
648 Quit:
649     /* Free the file buffer, and return */
650     RtlFreeHeap(ProcessHeap, 0, FileBuffer);
651     return Status;
652 }
653 
654 NTSTATUS
655 IniCacheLoad(
656     PINICACHE *Cache,
657     PWCHAR FileName,
658     BOOLEAN String)
659 {
660     NTSTATUS Status;
661     UNICODE_STRING Name;
662     OBJECT_ATTRIBUTES ObjectAttributes;
663     IO_STATUS_BLOCK IoStatusBlock;
664     HANDLE FileHandle;
665 
666     *Cache = NULL;
667 
668     /* Open the INI file */
669     RtlInitUnicodeString(&Name, FileName);
670 
671     InitializeObjectAttributes(&ObjectAttributes,
672                                &Name,
673                                OBJ_CASE_INSENSITIVE,
674                                NULL,
675                                NULL);
676 
677     Status = NtOpenFile(&FileHandle,
678                         FILE_GENERIC_READ | SYNCHRONIZE,
679                         &ObjectAttributes,
680                         &IoStatusBlock,
681                         FILE_SHARE_READ,
682                         FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE);
683     if (!NT_SUCCESS(Status))
684     {
685         DPRINT("NtOpenFile() failed (Status %lx)\n", Status);
686         return Status;
687     }
688 
689     DPRINT("NtOpenFile() successful\n");
690 
691     Status = IniCacheLoadByHandle(Cache, FileHandle, String);
692 
693     /* Close the INI file */
694     NtClose(FileHandle);
695     return Status;
696 }
697 
698 VOID
699 IniCacheDestroy(
700     _In_ PINICACHE Cache)
701 {
702     if (!Cache)
703         return;
704 
705     while (!IsListEmpty(&Cache->SectionList))
706     {
707         PLIST_ENTRY Entry = RemoveHeadList(&Cache->SectionList);
708         PINI_SECTION Section = CONTAINING_RECORD(Entry, INI_SECTION, ListEntry);
709         IniCacheFreeSection(Section);
710     }
711 
712     RtlFreeHeap(ProcessHeap, 0, Cache);
713 }
714 
715 
716 PINI_SECTION
717 IniGetSection(
718     _In_ PINICACHE Cache,
719     _In_ PCWSTR Name)
720 {
721     if (!Cache || !Name)
722     {
723         DPRINT("Invalid parameter\n");
724         return NULL;
725     }
726     return IniCacheFindSection(Cache, Name);
727 }
728 
729 PINI_KEYWORD
730 IniGetKey(
731     _In_ PINI_SECTION Section,
732     _In_ PCWSTR KeyName,
733     _Out_ PCWSTR* KeyData)
734 {
735     PINI_KEYWORD Key;
736 
737     if (!Section || !KeyName || !KeyData)
738     {
739         DPRINT("Invalid parameter\n");
740         return NULL;
741     }
742 
743     *KeyData = NULL;
744 
745     Key = IniCacheFindKey(Section, KeyName);
746     if (!Key)
747         return NULL;
748 
749     *KeyData = Key->Data;
750 
751     return Key;
752 }
753 
754 
755 PINICACHEITERATOR
756 IniFindFirstValue(
757     _In_ PINI_SECTION Section,
758     _Out_ PCWSTR* KeyName,
759     _Out_ PCWSTR* KeyData)
760 {
761     PINICACHEITERATOR Iterator;
762     PLIST_ENTRY Entry;
763     PINI_KEYWORD Key;
764 
765     if (!Section || !KeyName || !KeyData)
766     {
767         DPRINT("Invalid parameter\n");
768         return NULL;
769     }
770 
771     Entry = Section->KeyList.Flink;
772     if (Entry == &Section->KeyList)
773     {
774         DPRINT("Invalid parameter\n");
775         return NULL;
776     }
777     Key = CONTAINING_RECORD(Entry, INI_KEYWORD, ListEntry);
778 
779     Iterator = (PINICACHEITERATOR)RtlAllocateHeap(ProcessHeap,
780                                                   0,
781                                                   sizeof(INICACHEITERATOR));
782     if (!Iterator)
783     {
784         DPRINT("RtlAllocateHeap() failed\n");
785         return NULL;
786     }
787     Iterator->Section = Section;
788     Iterator->Key = Key;
789 
790     *KeyName = Key->Name;
791     *KeyData = Key->Data;
792 
793     return Iterator;
794 }
795 
796 BOOLEAN
797 IniFindNextValue(
798     _In_ PINICACHEITERATOR Iterator,
799     _Out_ PCWSTR* KeyName,
800     _Out_ PCWSTR* KeyData)
801 {
802     PLIST_ENTRY Entry;
803     PINI_KEYWORD Key;
804 
805     if (!Iterator || !KeyName || !KeyData)
806     {
807         DPRINT("Invalid parameter\n");
808         return FALSE;
809     }
810 
811     Entry = Iterator->Key->ListEntry.Flink;
812     if (Entry == &Iterator->Section->KeyList)
813     {
814         DPRINT("No more entries\n");
815         return FALSE;
816     }
817     Key = CONTAINING_RECORD(Entry, INI_KEYWORD, ListEntry);
818 
819     Iterator->Key = Key;
820 
821     *KeyName = Key->Name;
822     *KeyData = Key->Data;
823 
824     return TRUE;
825 }
826 
827 VOID
828 IniFindClose(
829     _In_ PINICACHEITERATOR Iterator)
830 {
831     if (!Iterator)
832         return;
833     RtlFreeHeap(ProcessHeap, 0, Iterator);
834 }
835 
836 
837 PINI_SECTION
838 IniAddSection(
839     _In_ PINICACHE Cache,
840     _In_ PCWSTR Name)
841 {
842     if (!Cache || !Name || !*Name)
843     {
844         DPRINT("Invalid parameter\n");
845         return NULL;
846     }
847     return IniCacheAddSectionAorW(Cache, Name, wcslen(Name), TRUE);
848 }
849 
850 VOID
851 IniRemoveSection(
852     _In_ PINI_SECTION Section)
853 {
854     if (!Section)
855     {
856         DPRINT("Invalid parameter\n");
857         return;
858     }
859     IniCacheFreeSection(Section);
860 }
861 
862 PINI_KEYWORD
863 IniInsertKey(
864     _In_ PINI_SECTION Section,
865     _In_ PINI_KEYWORD AnchorKey,
866     _In_ INSERTION_TYPE InsertionType,
867     _In_ PCWSTR Name,
868     _In_ PCWSTR Data)
869 {
870     if (!Section || !Name || !*Name || !Data || !*Data)
871     {
872         DPRINT("Invalid parameter\n");
873         return NULL;
874     }
875     return IniCacheAddKeyAorW(Section,
876                               AnchorKey, InsertionType,
877                               Name, wcslen(Name),
878                               Data, wcslen(Data),
879                               TRUE);
880 }
881 
882 PINI_KEYWORD
883 IniAddKey(
884     _In_ PINI_SECTION Section,
885     _In_ PCWSTR Name,
886     _In_ PCWSTR Data)
887 {
888     return IniInsertKey(Section, NULL, INSERT_LAST, Name, Data);
889 }
890 
891 VOID
892 IniRemoveKeyByName(
893     _In_ PINI_SECTION Section,
894     _In_ PCWSTR KeyName)
895 {
896     PINI_KEYWORD Key;
897     UNREFERENCED_PARAMETER(Section);
898 
899     Key = IniCacheFindKey(Section, KeyName);
900     if (Key)
901         IniCacheFreeKey(Key);
902 }
903 
904 VOID
905 IniRemoveKey(
906     _In_ PINI_SECTION Section,
907     _In_ PINI_KEYWORD Key)
908 {
909     UNREFERENCED_PARAMETER(Section);
910     if (!Key)
911     {
912         DPRINT("Invalid parameter\n");
913         return;
914     }
915     IniCacheFreeKey(Key);
916 }
917 
918 PINICACHE
919 IniCacheCreate(VOID)
920 {
921     PINICACHE Cache;
922 
923     /* Allocate inicache header */
924     Cache = (PINICACHE)RtlAllocateHeap(ProcessHeap,
925                                        HEAP_ZERO_MEMORY,
926                                        sizeof(INICACHE));
927     if (!Cache)
928     {
929         DPRINT("RtlAllocateHeap() failed\n");
930         return NULL;
931     }
932     InitializeListHead(&Cache->SectionList);
933 
934     return Cache;
935 }
936 
937 NTSTATUS
938 IniCacheSaveByHandle(
939     PINICACHE Cache,
940     HANDLE FileHandle)
941 {
942     NTSTATUS Status;
943     PLIST_ENTRY Entry1, Entry2;
944     PINI_SECTION Section;
945     PINI_KEYWORD Key;
946     ULONG BufferSize;
947     PCHAR Buffer;
948     PCHAR Ptr;
949     ULONG Len;
950     IO_STATUS_BLOCK IoStatusBlock;
951     LARGE_INTEGER Offset;
952 
953     /* Calculate required buffer size */
954     BufferSize = 0;
955     Entry1 = Cache->SectionList.Flink;
956     while (Entry1 != &Cache->SectionList)
957     {
958         Section = CONTAINING_RECORD(Entry1, INI_SECTION, ListEntry);
959         BufferSize += (Section->Name ? wcslen(Section->Name) : 0)
960                        + 4; /* "[]\r\n" */
961 
962         Entry2 = Section->KeyList.Flink;
963         while (Entry2 != &Section->KeyList)
964         {
965             Key = CONTAINING_RECORD(Entry2, INI_KEYWORD, ListEntry);
966             BufferSize += wcslen(Key->Name)
967                           + (Key->Data ? wcslen(Key->Data) : 0)
968                           + 3; /* "=\r\n" */
969             Entry2 = Entry2->Flink;
970         }
971 
972         Entry1 = Entry1->Flink;
973         if (Entry1 != &Cache->SectionList)
974             BufferSize += 2; /* Extra "\r\n" at end of each section */
975     }
976 
977     DPRINT("BufferSize: %lu\n", BufferSize);
978 
979     /* Allocate file buffer with NULL-terminator */
980     Buffer = (PCHAR)RtlAllocateHeap(ProcessHeap,
981                                     HEAP_ZERO_MEMORY,
982                                     BufferSize + 1);
983     if (Buffer == NULL)
984     {
985         DPRINT1("RtlAllocateHeap() failed\n");
986         return STATUS_INSUFFICIENT_RESOURCES;
987     }
988 
989     /* Fill file buffer */
990     Ptr = Buffer;
991     Entry1 = Cache->SectionList.Flink;
992     while (Entry1 != &Cache->SectionList)
993     {
994         Section = CONTAINING_RECORD(Entry1, INI_SECTION, ListEntry);
995         Len = sprintf(Ptr, "[%S]\r\n", Section->Name);
996         Ptr += Len;
997 
998         Entry2 = Section->KeyList.Flink;
999         while (Entry2 != &Section->KeyList)
1000         {
1001             Key = CONTAINING_RECORD(Entry2, INI_KEYWORD, ListEntry);
1002             Len = sprintf(Ptr, "%S=%S\r\n", Key->Name, Key->Data);
1003             Ptr += Len;
1004             Entry2 = Entry2->Flink;
1005         }
1006 
1007         Entry1 = Entry1->Flink;
1008         if (Entry1 != &Cache->SectionList)
1009         {
1010             Len = sprintf(Ptr, "\r\n");
1011             Ptr += Len;
1012         }
1013     }
1014 
1015     /* Write to the INI file */
1016     Offset.QuadPart = 0LL;
1017     Status = NtWriteFile(FileHandle,
1018                          NULL,
1019                          NULL,
1020                          NULL,
1021                          &IoStatusBlock,
1022                          Buffer,
1023                          BufferSize,
1024                          &Offset,
1025                          NULL);
1026     if (!NT_SUCCESS(Status))
1027     {
1028         DPRINT("NtWriteFile() failed (Status %lx)\n", Status);
1029         RtlFreeHeap(ProcessHeap, 0, Buffer);
1030         return Status;
1031     }
1032 
1033     RtlFreeHeap(ProcessHeap, 0, Buffer);
1034     return STATUS_SUCCESS;
1035 }
1036 
1037 NTSTATUS
1038 IniCacheSave(
1039     PINICACHE Cache,
1040     PWCHAR FileName)
1041 {
1042     NTSTATUS Status;
1043     UNICODE_STRING Name;
1044     OBJECT_ATTRIBUTES ObjectAttributes;
1045     IO_STATUS_BLOCK IoStatusBlock;
1046     HANDLE FileHandle;
1047 
1048     /* Create the INI file */
1049     RtlInitUnicodeString(&Name, FileName);
1050 
1051     InitializeObjectAttributes(&ObjectAttributes,
1052                                &Name,
1053                                OBJ_CASE_INSENSITIVE,
1054                                NULL,
1055                                NULL);
1056 
1057     Status = NtCreateFile(&FileHandle,
1058                           FILE_GENERIC_WRITE | SYNCHRONIZE,
1059                           &ObjectAttributes,
1060                           &IoStatusBlock,
1061                           NULL,
1062                           FILE_ATTRIBUTE_NORMAL,
1063                           0,
1064                           FILE_SUPERSEDE,
1065                           FILE_SYNCHRONOUS_IO_NONALERT | FILE_SEQUENTIAL_ONLY | FILE_NON_DIRECTORY_FILE,
1066                           NULL,
1067                           0);
1068     if (!NT_SUCCESS(Status))
1069     {
1070         DPRINT("NtCreateFile() failed (Status %lx)\n", Status);
1071         return Status;
1072     }
1073 
1074     Status = IniCacheSaveByHandle(Cache, FileHandle);
1075 
1076     /* Close the INI file */
1077     NtClose(FileHandle);
1078     return Status;
1079 }
1080 
1081 /* EOF */
1082