xref: /reactos/dll/win32/kernel32/wine/profile.c (revision c2c66aff)
1 /*
2  * Profile functions
3  *
4  * Copyright 1993 Miguel de Icaza
5  * Copyright 1996 Alexandre Julliard
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21 
22 #include <k32.h>
23 
24 #define NDEBUG
25 #include <debug.h>
26 DEBUG_CHANNEL(profile);
27 
28 static const char bom_utf8[] = {0xEF,0xBB,0xBF};
29 
30 typedef enum
31 {
32     ENCODING_ANSI = 1,
33     ENCODING_UTF8,
34     ENCODING_UTF16LE,
35     ENCODING_UTF16BE
36 } ENCODING;
37 
38 typedef struct tagPROFILEKEY
39 {
40     WCHAR                 *value;
41     struct tagPROFILEKEY  *next;
42     WCHAR                  name[1];
43 } PROFILEKEY;
44 
45 typedef struct tagPROFILESECTION
46 {
47     struct tagPROFILEKEY       *key;
48     struct tagPROFILESECTION   *next;
49     WCHAR                       name[1];
50 } PROFILESECTION;
51 
52 
53 typedef struct
54 {
55     BOOL             changed;
56     PROFILESECTION  *section;
57     WCHAR           *filename;
58     FILETIME LastWriteTime;
59     ENCODING encoding;
60 } PROFILE;
61 
62 
63 #define N_CACHED_PROFILES 10
64 
65 /* Cached profile files */
66 static PROFILE *MRUProfile[N_CACHED_PROFILES]={NULL};
67 
68 #define CurProfile (MRUProfile[0])
69 
70 /* Check for comments in profile */
71 #define IS_ENTRY_COMMENT(str)  ((str)[0] == ';')
72 
73 static const WCHAR emptystringW[] = {0};
74 static const WCHAR wininiW[] = { 'w','i','n','.','i','n','i',0 };
75 
76 static RTL_CRITICAL_SECTION PROFILE_CritSect;
77 static RTL_CRITICAL_SECTION_DEBUG critsect_debug =
78 {
79     0, 0, &PROFILE_CritSect,
80     { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList },
81       0, 0, 0
82 };
83 static RTL_CRITICAL_SECTION PROFILE_CritSect = { &critsect_debug, -1, 0, 0, 0, 0 };
84 
85 static const char hex[16] = "0123456789ABCDEF";
86 
87 /***********************************************************************
88  *           PROFILE_CopyEntry
89  *
90  * Copy the content of an entry into a buffer, removing quotes, and possibly
91  * translating environment variables.
92  */
93 static void PROFILE_CopyEntry( LPWSTR buffer, LPCWSTR value, int len,
94                                BOOL strip_quote )
95 {
96     WCHAR quote = '\0';
97 
98     if(!buffer) return;
99 
100     if (strip_quote && ((*value == '\'') || (*value == '\"')))
101     {
102         if (value[1] && (value[strlenW(value)-1] == *value)) quote = *value++;
103     }
104 
105     lstrcpynW( buffer, value, len );
106     if (quote && (len >= lstrlenW(value))) buffer[strlenW(buffer)-1] = '\0';
107 }
108 
109 /* byte-swaps shorts in-place in a buffer. len is in WCHARs */
110 static inline void PROFILE_ByteSwapShortBuffer(WCHAR * buffer, int len)
111 {
112     int i;
113     USHORT * shortbuffer = buffer;
114     for (i = 0; i < len; i++)
115         shortbuffer[i] = RtlUshortByteSwap(shortbuffer[i]);
116 }
117 
118 /* writes any necessary encoding marker to the file */
119 static inline void PROFILE_WriteMarker(HANDLE hFile, ENCODING encoding)
120 {
121     DWORD dwBytesWritten;
122     WCHAR bom;
123     switch (encoding)
124     {
125     case ENCODING_ANSI:
126         break;
127     case ENCODING_UTF8:
128         WriteFile(hFile, bom_utf8, sizeof(bom_utf8), &dwBytesWritten, NULL);
129         break;
130     case ENCODING_UTF16LE:
131         bom = 0xFEFF;
132         WriteFile(hFile, &bom, sizeof(bom), &dwBytesWritten, NULL);
133         break;
134     case ENCODING_UTF16BE:
135         bom = 0xFFFE;
136         WriteFile(hFile, &bom, sizeof(bom), &dwBytesWritten, NULL);
137         break;
138     }
139 }
140 
141 static void PROFILE_WriteLine( HANDLE hFile, WCHAR * szLine, int len, ENCODING encoding)
142 {
143     char * write_buffer;
144     int write_buffer_len;
145     DWORD dwBytesWritten;
146 
147     TRACE("writing: %s\n", debugstr_wn(szLine, len));
148 
149     switch (encoding)
150     {
151     case ENCODING_ANSI:
152         write_buffer_len = WideCharToMultiByte(CP_ACP, 0, szLine, len, NULL, 0, NULL, NULL);
153         write_buffer = HeapAlloc(GetProcessHeap(), 0, write_buffer_len);
154         if (!write_buffer) return;
155         len = WideCharToMultiByte(CP_ACP, 0, szLine, len, write_buffer, write_buffer_len, NULL, NULL);
156         WriteFile(hFile, write_buffer, len, &dwBytesWritten, NULL);
157         HeapFree(GetProcessHeap(), 0, write_buffer);
158         break;
159     case ENCODING_UTF8:
160         write_buffer_len = WideCharToMultiByte(CP_UTF8, 0, szLine, len, NULL, 0, NULL, NULL);
161         write_buffer = HeapAlloc(GetProcessHeap(), 0, write_buffer_len);
162         if (!write_buffer) return;
163         len = WideCharToMultiByte(CP_UTF8, 0, szLine, len, write_buffer, write_buffer_len, NULL, NULL);
164         WriteFile(hFile, write_buffer, len, &dwBytesWritten, NULL);
165         HeapFree(GetProcessHeap(), 0, write_buffer);
166         break;
167     case ENCODING_UTF16LE:
168         WriteFile(hFile, szLine, len * sizeof(WCHAR), &dwBytesWritten, NULL);
169         break;
170     case ENCODING_UTF16BE:
171         PROFILE_ByteSwapShortBuffer(szLine, len);
172         WriteFile(hFile, szLine, len * sizeof(WCHAR), &dwBytesWritten, NULL);
173         break;
174     default:
175         FIXME("encoding type %d not implemented\n", encoding);
176     }
177 }
178 
179 /***********************************************************************
180  *           PROFILE_Save
181  *
182  * Save a profile tree to a file.
183  */
184 static void PROFILE_Save( HANDLE hFile, const PROFILESECTION *section, ENCODING encoding )
185 {
186     PROFILEKEY *key;
187     WCHAR *buffer, *p;
188 
189     PROFILE_WriteMarker(hFile, encoding);
190 
191     for ( ; section; section = section->next)
192     {
193         int len = 0;
194 
195         if (section->name[0]) len += strlenW(section->name) + 4;
196 
197         for (key = section->key; key; key = key->next)
198         {
199             len += strlenW(key->name) + 2;
200             if (key->value) len += strlenW(key->value) + 1;
201         }
202 
203         buffer = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
204         if (!buffer) return;
205 
206         p = buffer;
207         if (section->name[0])
208         {
209             *p++ = '[';
210             strcpyW( p, section->name );
211             p += strlenW(p);
212             *p++ = ']';
213             *p++ = '\r';
214             *p++ = '\n';
215         }
216 
217         for (key = section->key; key; key = key->next)
218         {
219             strcpyW( p, key->name );
220             p += strlenW(p);
221             if (key->value)
222             {
223                 *p++ = '=';
224                 strcpyW( p, key->value );
225                 p += strlenW(p);
226             }
227             *p++ = '\r';
228             *p++ = '\n';
229         }
230         PROFILE_WriteLine( hFile, buffer, len, encoding );
231         HeapFree(GetProcessHeap(), 0, buffer);
232     }
233 }
234 
235 
236 /***********************************************************************
237  *           PROFILE_Free
238  *
239  * Free a profile tree.
240  */
241 static void PROFILE_Free( PROFILESECTION *section )
242 {
243     PROFILESECTION *next_section;
244     PROFILEKEY *key, *next_key;
245 
246     for ( ; section; section = next_section)
247     {
248         for (key = section->key; key; key = next_key)
249         {
250             next_key = key->next;
251             HeapFree( GetProcessHeap(), 0, key->value );
252             HeapFree( GetProcessHeap(), 0, key );
253         }
254         next_section = section->next;
255         HeapFree( GetProcessHeap(), 0, section );
256     }
257 }
258 
259 /* returns TRUE if a whitespace character, else FALSE */
260 static inline BOOL PROFILE_isspaceW(WCHAR c)
261 {
262 	/* ^Z (DOS EOF) is a space too  (found on CD-ROMs) */
263 	return isspaceW(c) || c == 0x1a;
264 }
265 
266 static inline ENCODING PROFILE_DetectTextEncoding(void * buffer, int * len)
267 {
268     int flags = IS_TEXT_UNICODE_SIGNATURE |
269                 IS_TEXT_UNICODE_REVERSE_SIGNATURE |
270                 IS_TEXT_UNICODE_ODD_LENGTH;
271     if (*len >= sizeof(bom_utf8) && !memcmp(buffer, bom_utf8, sizeof(bom_utf8)))
272     {
273         *len = sizeof(bom_utf8);
274         return ENCODING_UTF8;
275     }
276     RtlIsTextUnicode(buffer, *len, &flags);
277     if (flags & IS_TEXT_UNICODE_SIGNATURE)
278     {
279         *len = sizeof(WCHAR);
280         return ENCODING_UTF16LE;
281     }
282     if (flags & IS_TEXT_UNICODE_REVERSE_SIGNATURE)
283     {
284         *len = sizeof(WCHAR);
285         return ENCODING_UTF16BE;
286     }
287     *len = 0;
288     return ENCODING_ANSI;
289 }
290 
291 
292 /***********************************************************************
293  *           PROFILE_Load
294  *
295  * Load a profile tree from a file.
296  */
297 static PROFILESECTION *PROFILE_Load(HANDLE hFile, ENCODING * pEncoding)
298 {
299     void *buffer_base, *pBuffer;
300     WCHAR * szFile;
301     const WCHAR *szLineStart, *szLineEnd;
302     const WCHAR *szValueStart, *szEnd, *next_line;
303     int line = 0, len;
304     PROFILESECTION *section, *first_section;
305     PROFILESECTION **next_section;
306     PROFILEKEY *key, *prev_key, **next_key;
307     DWORD dwFileSize;
308 
309     TRACE("%p\n", hFile);
310 
311     dwFileSize = GetFileSize(hFile, NULL);
312     if (dwFileSize == INVALID_FILE_SIZE || dwFileSize == 0)
313         return NULL;
314 
315     buffer_base = HeapAlloc(GetProcessHeap(), 0 , dwFileSize);
316     if (!buffer_base) return NULL;
317 
318     if (!ReadFile(hFile, buffer_base, dwFileSize, &dwFileSize, NULL))
319     {
320         HeapFree(GetProcessHeap(), 0, buffer_base);
321         WARN("Error %d reading file\n", GetLastError());
322         return NULL;
323     }
324     len = dwFileSize;
325     *pEncoding = PROFILE_DetectTextEncoding(buffer_base, &len);
326     /* len is set to the number of bytes in the character marker.
327      * we want to skip these bytes */
328     pBuffer = (char *)buffer_base + len;
329     dwFileSize -= len;
330     switch (*pEncoding)
331     {
332     case ENCODING_ANSI:
333         TRACE("ANSI encoding\n");
334 
335         len = MultiByteToWideChar(CP_ACP, 0, pBuffer, dwFileSize, NULL, 0);
336         szFile = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
337         if (!szFile)
338         {
339             HeapFree(GetProcessHeap(), 0, buffer_base);
340             return NULL;
341         }
342         MultiByteToWideChar(CP_ACP, 0, pBuffer, dwFileSize, szFile, len);
343         szEnd = szFile + len;
344         break;
345     case ENCODING_UTF8:
346         TRACE("UTF8 encoding\n");
347 
348         len = MultiByteToWideChar(CP_UTF8, 0, pBuffer, dwFileSize, NULL, 0);
349         szFile = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
350         if (!szFile)
351         {
352             HeapFree(GetProcessHeap(), 0, buffer_base);
353             return NULL;
354         }
355         MultiByteToWideChar(CP_UTF8, 0, pBuffer, dwFileSize, szFile, len);
356         szEnd = szFile + len;
357         break;
358     case ENCODING_UTF16LE:
359         TRACE("UTF16 Little Endian encoding\n");
360         szFile = pBuffer;
361         szEnd = (WCHAR *)((char *)pBuffer + dwFileSize);
362         break;
363     case ENCODING_UTF16BE:
364         TRACE("UTF16 Big Endian encoding\n");
365         szFile = pBuffer;
366         szEnd = (WCHAR *)((char *)pBuffer + dwFileSize);
367         PROFILE_ByteSwapShortBuffer(szFile, dwFileSize / sizeof(WCHAR));
368         break;
369     default:
370         FIXME("encoding type %d not implemented\n", *pEncoding);
371         HeapFree(GetProcessHeap(), 0, buffer_base);
372         return NULL;
373     }
374 
375     first_section = HeapAlloc( GetProcessHeap(), 0, sizeof(*section) );
376     if(first_section == NULL)
377     {
378         if (szFile != pBuffer)
379             HeapFree(GetProcessHeap(), 0, szFile);
380         HeapFree(GetProcessHeap(), 0, buffer_base);
381         return NULL;
382     }
383     first_section->name[0] = 0;
384     first_section->key  = NULL;
385     first_section->next = NULL;
386     next_section = &first_section->next;
387     next_key     = &first_section->key;
388     prev_key     = NULL;
389     next_line    = szFile;
390 
391     while (next_line < szEnd)
392     {
393         szLineStart = next_line;
394         next_line = memchrW(szLineStart, '\n', szEnd - szLineStart);
395         if (!next_line) next_line = memchrW(szLineStart, '\r', szEnd - szLineStart);
396         if (!next_line) next_line = szEnd;
397         else next_line++;
398         szLineEnd = next_line;
399 
400         line++;
401 
402         /* get rid of white space */
403         while (szLineStart < szLineEnd && PROFILE_isspaceW(*szLineStart)) szLineStart++;
404         while ((szLineEnd > szLineStart) && PROFILE_isspaceW(szLineEnd[-1])) szLineEnd--;
405 
406         if (szLineStart >= szLineEnd) continue;
407 
408         if (*szLineStart == '[')  /* section start */
409         {
410             const WCHAR * szSectionEnd;
411             if (!(szSectionEnd = memrchrW( szLineStart, ']', szLineEnd - szLineStart )))
412             {
413                 WARN("Invalid section header at line %d: %s\n",
414                     line, debugstr_wn(szLineStart, (int)(szLineEnd - szLineStart)) );
415             }
416             else
417             {
418                 szLineStart++;
419                 len = (int)(szSectionEnd - szLineStart);
420                 /* no need to allocate +1 for NULL terminating character as
421                  * already included in structure */
422                 if (!(section = HeapAlloc( GetProcessHeap(), 0, sizeof(*section) + len * sizeof(WCHAR) )))
423                     break;
424                 memcpy(section->name, szLineStart, len * sizeof(WCHAR));
425                 section->name[len] = '\0';
426                 section->key  = NULL;
427                 section->next = NULL;
428                 *next_section = section;
429                 next_section  = &section->next;
430                 next_key      = &section->key;
431                 prev_key      = NULL;
432 
433                 TRACE("New section: %s\n", debugstr_w(section->name));
434 
435                 continue;
436             }
437         }
438 
439         /* get rid of white space after the name and before the start
440          * of the value */
441         len = szLineEnd - szLineStart;
442         if ((szValueStart = memchrW( szLineStart, '=', szLineEnd - szLineStart )) != NULL)
443         {
444             const WCHAR *szNameEnd = szValueStart;
445             while ((szNameEnd > szLineStart) && PROFILE_isspaceW(szNameEnd[-1])) szNameEnd--;
446             len = szNameEnd - szLineStart;
447             szValueStart++;
448             while (szValueStart < szLineEnd && PROFILE_isspaceW(*szValueStart)) szValueStart++;
449         }
450 
451         if (len || !prev_key || *prev_key->name)
452         {
453             /* no need to allocate +1 for NULL terminating character as
454              * already included in structure */
455             if (!(key = HeapAlloc( GetProcessHeap(), 0, sizeof(*key) + len * sizeof(WCHAR) ))) break;
456             memcpy(key->name, szLineStart, len * sizeof(WCHAR));
457             key->name[len] = '\0';
458             if (szValueStart)
459             {
460                 len = (int)(szLineEnd - szValueStart);
461                 key->value = HeapAlloc( GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR) );
462                 memcpy(key->value, szValueStart, len * sizeof(WCHAR));
463                 key->value[len] = '\0';
464             }
465             else key->value = NULL;
466 
467            key->next  = NULL;
468            *next_key  = key;
469            next_key   = &key->next;
470            prev_key   = key;
471 
472            TRACE("New key: name=%s, value=%s\n",
473                debugstr_w(key->name), key->value ? debugstr_w(key->value) : L"(none)");
474         }
475     }
476     if (szFile != pBuffer)
477         HeapFree(GetProcessHeap(), 0, szFile);
478     HeapFree(GetProcessHeap(), 0, buffer_base);
479     return first_section;
480 }
481 
482 
483 /***********************************************************************
484  *           PROFILE_DeleteSection
485  *
486  * Delete a section from a profile tree.
487  */
488 static BOOL PROFILE_DeleteSection( PROFILESECTION **section, LPCWSTR name )
489 {
490     while (*section)
491     {
492         if (!strcmpiW( (*section)->name, name ))
493         {
494             PROFILESECTION *to_del = *section;
495             *section = to_del->next;
496             to_del->next = NULL;
497             PROFILE_Free( to_del );
498             return TRUE;
499         }
500         section = &(*section)->next;
501     }
502     return FALSE;
503 }
504 
505 
506 /***********************************************************************
507  *           PROFILE_DeleteKey
508  *
509  * Delete a key from a profile tree.
510  */
511 static BOOL PROFILE_DeleteKey( PROFILESECTION **section,
512 			       LPCWSTR section_name, LPCWSTR key_name )
513 {
514     while (*section)
515     {
516         if (!strcmpiW( (*section)->name, section_name ))
517         {
518             PROFILEKEY **key = &(*section)->key;
519             while (*key)
520             {
521                 if (!strcmpiW( (*key)->name, key_name ))
522                 {
523                     PROFILEKEY *to_del = *key;
524                     *key = to_del->next;
525                     HeapFree( GetProcessHeap(), 0, to_del->value);
526                     HeapFree( GetProcessHeap(), 0, to_del );
527                     return TRUE;
528                 }
529                 key = &(*key)->next;
530             }
531         }
532         section = &(*section)->next;
533     }
534     return FALSE;
535 }
536 
537 
538 /***********************************************************************
539  *           PROFILE_DeleteAllKeys
540  *
541  * Delete all keys from a profile tree.
542  */
543 static void PROFILE_DeleteAllKeys( LPCWSTR section_name)
544 {
545     PROFILESECTION **section= &CurProfile->section;
546     while (*section)
547     {
548         if (!strcmpiW( (*section)->name, section_name ))
549         {
550             PROFILEKEY **key = &(*section)->key;
551             while (*key)
552             {
553                 PROFILEKEY *to_del = *key;
554 		*key = to_del->next;
555                 HeapFree( GetProcessHeap(), 0, to_del->value);
556 		HeapFree( GetProcessHeap(), 0, to_del );
557 		CurProfile->changed =TRUE;
558             }
559         }
560         section = &(*section)->next;
561     }
562 }
563 
564 
565 /***********************************************************************
566  *           PROFILE_Find
567  *
568  * Find a key in a profile tree, optionally creating it.
569  */
570 static PROFILEKEY *PROFILE_Find( PROFILESECTION **section, LPCWSTR section_name,
571                                  LPCWSTR key_name, BOOL create, BOOL create_always )
572 {
573     LPCWSTR p;
574     int seclen = 0, keylen = 0;
575 
576     while (PROFILE_isspaceW(*section_name)) section_name++;
577     if (*section_name)
578     {
579         p = section_name + strlenW(section_name) - 1;
580         while ((p > section_name) && PROFILE_isspaceW(*p)) p--;
581         seclen = p - section_name + 1;
582     }
583 
584     while (PROFILE_isspaceW(*key_name)) key_name++;
585     if (*key_name)
586     {
587         p = key_name + strlenW(key_name) - 1;
588         while ((p > key_name) && PROFILE_isspaceW(*p)) p--;
589         keylen = p - key_name + 1;
590     }
591 
592     while (*section)
593     {
594         if (!strncmpiW((*section)->name, section_name, seclen) &&
595             ((*section)->name)[seclen] == '\0')
596         {
597             PROFILEKEY **key = &(*section)->key;
598 
599             while (*key)
600             {
601                 /* If create_always is FALSE then we check if the keyname
602                  * already exists. Otherwise we add it regardless of its
603                  * existence, to allow keys to be added more than once in
604                  * some cases.
605                  */
606                 if(!create_always)
607                 {
608                     if ( (!(strncmpiW( (*key)->name, key_name, keylen )))
609                          && (((*key)->name)[keylen] == '\0') )
610                         return *key;
611                 }
612                 key = &(*key)->next;
613             }
614             if (!create) return NULL;
615             if (!(*key = HeapAlloc( GetProcessHeap(), 0, sizeof(PROFILEKEY) + strlenW(key_name) * sizeof(WCHAR) )))
616                 return NULL;
617             strcpyW( (*key)->name, key_name );
618             (*key)->value = NULL;
619             (*key)->next  = NULL;
620             return *key;
621         }
622         section = &(*section)->next;
623     }
624     if (!create) return NULL;
625     *section = HeapAlloc( GetProcessHeap(), 0, sizeof(PROFILESECTION) + strlenW(section_name) * sizeof(WCHAR) );
626     if(*section == NULL) return NULL;
627     strcpyW( (*section)->name, section_name );
628     (*section)->next = NULL;
629     if (!((*section)->key  = HeapAlloc( GetProcessHeap(), 0,
630                                         sizeof(PROFILEKEY) + strlenW(key_name) * sizeof(WCHAR) )))
631     {
632         HeapFree(GetProcessHeap(), 0, *section);
633         return NULL;
634     }
635     strcpyW( (*section)->key->name, key_name );
636     (*section)->key->value = NULL;
637     (*section)->key->next  = NULL;
638     return (*section)->key;
639 }
640 
641 
642 /***********************************************************************
643  *           PROFILE_FlushFile
644  *
645  * Flush the current profile to disk if changed.
646  */
647 static BOOL PROFILE_FlushFile(void)
648 {
649     HANDLE hFile = NULL;
650     FILETIME LastWriteTime;
651 
652     if(!CurProfile)
653     {
654         WARN("No current profile!\n");
655         return FALSE;
656     }
657 
658     if (!CurProfile->changed) return TRUE;
659 
660     hFile = CreateFileW(CurProfile->filename, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
661                         NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
662 
663     if (hFile == INVALID_HANDLE_VALUE)
664     {
665         WARN("could not save profile file %s (error was %d)\n", debugstr_w(CurProfile->filename), GetLastError());
666         return FALSE;
667     }
668 
669     TRACE("Saving %s\n", debugstr_w(CurProfile->filename));
670     PROFILE_Save( hFile, CurProfile->section, CurProfile->encoding );
671     if(GetFileTime(hFile, NULL, NULL, &LastWriteTime))
672        CurProfile->LastWriteTime=LastWriteTime;
673     CloseHandle( hFile );
674     CurProfile->changed = FALSE;
675     return TRUE;
676 }
677 
678 
679 /***********************************************************************
680  *           PROFILE_ReleaseFile
681  *
682  * Flush the current profile to disk and remove it from the cache.
683  */
684 static void PROFILE_ReleaseFile(void)
685 {
686     PROFILE_FlushFile();
687     PROFILE_Free( CurProfile->section );
688     HeapFree( GetProcessHeap(), 0, CurProfile->filename );
689     CurProfile->changed = FALSE;
690     CurProfile->section = NULL;
691     CurProfile->filename  = NULL;
692     CurProfile->encoding = ENCODING_ANSI;
693     ZeroMemory(&CurProfile->LastWriteTime, sizeof(CurProfile->LastWriteTime));
694 }
695 
696 /***********************************************************************
697  *
698  * Compares a file time with the current time. If the file time is
699  * at least 2.1 seconds in the past, return true.
700  *
701  * Intended as cache safety measure: The time resolution on FAT is
702  * two seconds, so files that are not at least two seconds old might
703  * keep their time even on modification, so don't cache them.
704  */
705 static BOOL is_not_current(FILETIME * ft)
706 {
707     FILETIME Now;
708     LONGLONG ftll, nowll;
709     GetSystemTimeAsFileTime(&Now);
710     ftll = ((LONGLONG)ft->dwHighDateTime << 32) + ft->dwLowDateTime;
711     nowll = ((LONGLONG)Now.dwHighDateTime << 32) + Now.dwLowDateTime;
712     TRACE("%08x;%08x\n",(unsigned)ftll+21000000,(unsigned)nowll);
713     return ftll + 21000000 < nowll;
714 }
715 
716 /***********************************************************************
717  *           PROFILE_Open
718  *
719  * Open a profile file, checking the cached file first.
720  */
721 static BOOL PROFILE_Open( LPCWSTR filename, BOOL write_access )
722 {
723     WCHAR buffer[MAX_PATH];
724     HANDLE hFile = INVALID_HANDLE_VALUE;
725     FILETIME LastWriteTime;
726     int i,j;
727     PROFILE *tempProfile;
728 
729     ZeroMemory(&LastWriteTime, sizeof(LastWriteTime));
730 
731     /* First time around */
732 
733     if(!CurProfile)
734        for(i=0;i<N_CACHED_PROFILES;i++)
735        {
736           MRUProfile[i]=HeapAlloc( GetProcessHeap(), 0, sizeof(PROFILE) );
737           if(MRUProfile[i] == NULL) break;
738           MRUProfile[i]->changed=FALSE;
739           MRUProfile[i]->section=NULL;
740           MRUProfile[i]->filename=NULL;
741           MRUProfile[i]->encoding=ENCODING_ANSI;
742           ZeroMemory(&MRUProfile[i]->LastWriteTime, sizeof(FILETIME));
743        }
744 
745     if (!filename)
746 	filename = wininiW;
747 
748     if ((RtlDetermineDosPathNameType_U(filename) == RtlPathTypeRelative) &&
749         !strchrW(filename, '\\') && !strchrW(filename, '/'))
750     {
751         static const WCHAR wszSeparator[] = {'\\', 0};
752         WCHAR windirW[MAX_PATH];
753         GetWindowsDirectoryW( windirW, MAX_PATH );
754         strcpyW(buffer, windirW);
755         strcatW(buffer, wszSeparator);
756         strcatW(buffer, filename);
757     }
758     else
759     {
760         LPWSTR dummy;
761         GetFullPathNameW(filename, sizeof(buffer)/sizeof(buffer[0]), buffer, &dummy);
762     }
763 
764     TRACE("path: %s\n", debugstr_w(buffer));
765 
766     hFile = CreateFileW(buffer, GENERIC_READ | (write_access ? GENERIC_WRITE : 0),
767                         FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
768                         OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
769 
770     if ((hFile == INVALID_HANDLE_VALUE) && (GetLastError() != ERROR_FILE_NOT_FOUND))
771     {
772         WARN("Error %d opening file %s\n", GetLastError(), debugstr_w(buffer));
773         return FALSE;
774     }
775 
776     for(i=0;i<N_CACHED_PROFILES;i++)
777     {
778         if ((MRUProfile[i]->filename && !strcmpiW( buffer, MRUProfile[i]->filename )))
779         {
780             TRACE("MRU Filename: %s, new filename: %s\n", debugstr_w(MRUProfile[i]->filename), debugstr_w(buffer));
781             if(i)
782             {
783                 PROFILE_FlushFile();
784                 tempProfile=MRUProfile[i];
785                 for(j=i;j>0;j--)
786                     MRUProfile[j]=MRUProfile[j-1];
787                 CurProfile=tempProfile;
788             }
789 
790             if (hFile != INVALID_HANDLE_VALUE)
791             {
792                 GetFileTime(hFile, NULL, NULL, &LastWriteTime);
793                 if (!memcmp( &CurProfile->LastWriteTime, &LastWriteTime, sizeof(FILETIME) ) &&
794                     is_not_current(&LastWriteTime))
795                     TRACE("(%s): already opened (mru=%d)\n",
796                           debugstr_w(buffer), i);
797                 else
798                 {
799                     TRACE("(%s): already opened, needs refreshing (mru=%d)\n",
800                           debugstr_w(buffer), i);
801                     PROFILE_Free(CurProfile->section);
802                     CurProfile->section = PROFILE_Load(hFile, &CurProfile->encoding);
803                     CurProfile->LastWriteTime = LastWriteTime;
804                 }
805                 CloseHandle(hFile);
806                 return TRUE;
807             }
808             else TRACE("(%s): already opened, not yet created (mru=%d)\n",
809                        debugstr_w(buffer), i);
810         }
811     }
812 
813     /* Flush the old current profile */
814     PROFILE_FlushFile();
815 
816     /* Make the oldest profile the current one only in order to get rid of it */
817     if(i==N_CACHED_PROFILES)
818       {
819        tempProfile=MRUProfile[N_CACHED_PROFILES-1];
820        for(i=N_CACHED_PROFILES-1;i>0;i--)
821           MRUProfile[i]=MRUProfile[i-1];
822        CurProfile=tempProfile;
823       }
824     if(CurProfile->filename) PROFILE_ReleaseFile();
825 
826     /* OK, now that CurProfile is definitely free we assign it our new file */
827     CurProfile->filename  = HeapAlloc( GetProcessHeap(), 0, (strlenW(buffer)+1) * sizeof(WCHAR) );
828     strcpyW( CurProfile->filename, buffer );
829 
830     if (hFile != INVALID_HANDLE_VALUE)
831     {
832         CurProfile->section = PROFILE_Load(hFile, &CurProfile->encoding);
833         GetFileTime(hFile, NULL, NULL, &CurProfile->LastWriteTime);
834         CloseHandle(hFile);
835     }
836     else
837     {
838         /* Does not exist yet, we will create it in PROFILE_FlushFile */
839         WARN("profile file %s not found\n", debugstr_w(buffer) );
840     }
841     return TRUE;
842 }
843 
844 
845 /***********************************************************************
846  *           PROFILE_GetSection
847  *
848  * Returns all keys of a section.
849  * If return_values is TRUE, also include the corresponding values.
850  */
851 static INT PROFILE_GetSection( PROFILESECTION *section, LPCWSTR section_name,
852 			       LPWSTR buffer, DWORD len, BOOL return_values )
853 {
854     PROFILEKEY *key;
855 
856     if(!buffer) return 0;
857 
858     TRACE("%s,%p,%u\n", debugstr_w(section_name), buffer, len);
859 
860     while (section)
861     {
862         if (!strcmpiW( section->name, section_name ))
863         {
864             UINT oldlen = len;
865             for (key = section->key; key; key = key->next)
866             {
867                 if (len <= 2) break;
868                 if (!*key->name) continue;  /* Skip empty lines */
869                 if (IS_ENTRY_COMMENT(key->name)) continue;  /* Skip comments */
870                 if (!return_values && !key->value) continue;  /* Skip lines w.o. '=' */
871                 PROFILE_CopyEntry( buffer, key->name, len - 1, 0 );
872                 len -= strlenW(buffer) + 1;
873                 buffer += strlenW(buffer) + 1;
874 		if (len < 2)
875 		    break;
876 		if (return_values && key->value) {
877 			buffer[-1] = '=';
878 			PROFILE_CopyEntry ( buffer, key->value, len - 1, 0 );
879 			len -= strlenW(buffer) + 1;
880 			buffer += strlenW(buffer) + 1;
881                 }
882             }
883             *buffer = '\0';
884             if (len <= 1)
885                 /*If either lpszSection or lpszKey is NULL and the supplied
886                   destination buffer is too small to hold all the strings,
887                   the last string is truncated and followed by two null characters.
888                   In this case, the return value is equal to cchReturnBuffer
889                   minus two. */
890             {
891 		buffer[-1] = '\0';
892                 return oldlen - 2;
893             }
894             return oldlen - len;
895         }
896         section = section->next;
897     }
898     buffer[0] = buffer[1] = '\0';
899     return 0;
900 }
901 
902 /* See GetPrivateProfileSectionNamesA for documentation */
903 static INT PROFILE_GetSectionNames( LPWSTR buffer, DWORD len )
904 {
905     LPWSTR buf;
906     UINT buflen,tmplen;
907     PROFILESECTION *section;
908 
909     TRACE("(%p, %d)\n", buffer, len);
910 
911     if (!buffer || !len)
912         return 0;
913     if (len==1) {
914         *buffer='\0';
915         return 0;
916     }
917 
918     buflen=len-1;
919     buf=buffer;
920     section = CurProfile->section;
921     while ((section!=NULL)) {
922         if (section->name[0]) {
923             tmplen = strlenW(section->name)+1;
924             if (tmplen >= buflen) {
925                 if (buflen > 0) {
926                     memcpy(buf, section->name, (buflen-1) * sizeof(WCHAR));
927                     buf += buflen-1;
928                     *buf++='\0';
929                 }
930                 *buf='\0';
931                 return len-2;
932             }
933             memcpy(buf, section->name, tmplen * sizeof(WCHAR));
934             buf += tmplen;
935             buflen -= tmplen;
936         }
937         section = section->next;
938     }
939     *buf='\0';
940     return buf-buffer;
941 }
942 
943 
944 /***********************************************************************
945  *           PROFILE_GetString
946  *
947  * Get a profile string.
948  *
949  * Tests with GetPrivateProfileString16, W95a,
950  * with filled buffer ("****...") and section "set1" and key_name "1" valid:
951  * section	key_name	def_val		res	buffer
952  * "set1"	"1"		"x"		43	[data]
953  * "set1"	"1   "		"x"		43	[data]		(!)
954  * "set1"	"  1  "'	"x"		43	[data]		(!)
955  * "set1"	""		"x"		1	"x"
956  * "set1"	""		"x   "		1	"x"		(!)
957  * "set1"	""		"  x   "	3	"  x"		(!)
958  * "set1"	NULL		"x"		6	"1\02\03\0\0"
959  * "set1"	""		"x"		1	"x"
960  * NULL		"1"		"x"		0	""		(!)
961  * ""		"1"		"x"		1	"x"
962  * NULL		NULL		""		0	""
963  *
964  *
965  */
966 static INT PROFILE_GetString( LPCWSTR section, LPCWSTR key_name,
967                               LPCWSTR def_val, LPWSTR buffer, DWORD len )
968 {
969     PROFILEKEY *key = NULL;
970     static const WCHAR empty_strW[] = { 0 };
971 
972     if(!buffer || !len) return 0;
973 
974     if (!def_val) def_val = empty_strW;
975     if (key_name)
976     {
977         key = PROFILE_Find( &CurProfile->section, section, key_name, FALSE, FALSE);
978         PROFILE_CopyEntry( buffer, (key && key->value) ? key->value : def_val,
979                            len, TRUE );
980         TRACE("(%s,%s,%s): returning %s\n",
981               debugstr_w(section), debugstr_w(key_name),
982               debugstr_w(def_val), debugstr_w(buffer) );
983         return strlenW( buffer );
984     }
985     /* no "else" here ! */
986     if (section)
987     {
988         INT ret = PROFILE_GetSection(CurProfile->section, section, buffer, len, FALSE);
989         if (!buffer[0]) /* no luck -> def_val */
990         {
991             PROFILE_CopyEntry(buffer, def_val, len, TRUE);
992             ret = strlenW(buffer);
993         }
994         return ret;
995     }
996     buffer[0] = '\0';
997     return 0;
998 }
999 
1000 
1001 /***********************************************************************
1002  *           PROFILE_SetString
1003  *
1004  * Set a profile string.
1005  */
1006 static BOOL PROFILE_SetString( LPCWSTR section_name, LPCWSTR key_name,
1007                                LPCWSTR value, BOOL create_always )
1008 {
1009     if (!key_name)  /* Delete a whole section */
1010     {
1011         TRACE("(%s)\n", debugstr_w(section_name));
1012         CurProfile->changed |= PROFILE_DeleteSection( &CurProfile->section,
1013                                                       section_name );
1014         return TRUE;         /* Even if PROFILE_DeleteSection() has failed,
1015                                 this is not an error on application's level.*/
1016     }
1017     else if (!value)  /* Delete a key */
1018     {
1019         TRACE("(%s,%s)\n", debugstr_w(section_name), debugstr_w(key_name) );
1020         CurProfile->changed |= PROFILE_DeleteKey( &CurProfile->section,
1021                                                   section_name, key_name );
1022         return TRUE;          /* same error handling as above */
1023     }
1024     else  /* Set the key value */
1025     {
1026         PROFILEKEY *key = PROFILE_Find(&CurProfile->section, section_name,
1027                                         key_name, TRUE, create_always );
1028         TRACE("(%s,%s,%s):\n",
1029               debugstr_w(section_name), debugstr_w(key_name), debugstr_w(value) );
1030         if (!key) return FALSE;
1031 
1032         /* strip the leading spaces. We can safely strip \n\r and
1033          * friends too, they should not happen here anyway. */
1034         while (PROFILE_isspaceW(*value)) value++;
1035 
1036         if (key->value)
1037         {
1038             if (!strcmpW( key->value, value ))
1039             {
1040                 TRACE("  no change needed\n" );
1041                 return TRUE;  /* No change needed */
1042             }
1043             TRACE("  replacing %s\n", debugstr_w(key->value) );
1044             HeapFree( GetProcessHeap(), 0, key->value );
1045         }
1046         else TRACE("  creating key\n" );
1047         key->value = HeapAlloc( GetProcessHeap(), 0, (strlenW(value)+1) * sizeof(WCHAR) );
1048         strcpyW( key->value, value );
1049         CurProfile->changed = TRUE;
1050     }
1051     return TRUE;
1052 }
1053 
1054 
1055 /********************* API functions **********************************/
1056 
1057 
1058 /***********************************************************************
1059  *           GetProfileIntA   (KERNEL32.@)
1060  */
1061 UINT WINAPI GetProfileIntA( LPCSTR section, LPCSTR entry, INT def_val )
1062 {
1063     return GetPrivateProfileIntA( section, entry, def_val, "win.ini" );
1064 }
1065 
1066 /***********************************************************************
1067  *           GetProfileIntW   (KERNEL32.@)
1068  */
1069 UINT WINAPI GetProfileIntW( LPCWSTR section, LPCWSTR entry, INT def_val )
1070 {
1071     return GetPrivateProfileIntW( section, entry, def_val, wininiW );
1072 }
1073 
1074 /***********************************************************************
1075  *           GetPrivateProfileStringW   (KERNEL32.@)
1076  */
1077 DWORD WINAPI GetPrivateProfileStringW( LPCWSTR section, LPCWSTR entry,
1078 				     LPCWSTR def_val, LPWSTR buffer,
1079 				     DWORD len, LPCWSTR filename )
1080 {
1081     int		ret;
1082     LPWSTR	defval_tmp = NULL;
1083 
1084     TRACE("%s,%s,%s,%p,%u,%s\n", debugstr_w(section), debugstr_w(entry),
1085           debugstr_w(def_val), buffer, len, debugstr_w(filename));
1086 
1087     /* strip any trailing ' ' of def_val. */
1088     if (def_val)
1089     {
1090         LPCWSTR p = def_val + strlenW(def_val) - 1;
1091 
1092         while (p > def_val && *p == ' ')
1093             p--;
1094 
1095         if (p >= def_val)
1096         {
1097             int vlen = (int)(p - def_val) + 1;
1098 
1099             defval_tmp = HeapAlloc(GetProcessHeap(), 0, (vlen + 1) * sizeof(WCHAR));
1100             memcpy(defval_tmp, def_val, vlen * sizeof(WCHAR));
1101             defval_tmp[vlen] = '\0';
1102             def_val = defval_tmp;
1103         }
1104     }
1105 
1106     RtlEnterCriticalSection( &PROFILE_CritSect );
1107 
1108     if (PROFILE_Open( filename, FALSE )) {
1109 	if (section == NULL)
1110             ret = PROFILE_GetSectionNames(buffer, len);
1111 	else
1112 	    /* PROFILE_GetString can handle the 'entry == NULL' case */
1113             ret = PROFILE_GetString( section, entry, def_val, buffer, len );
1114     } else if (buffer && def_val) {
1115        lstrcpynW( buffer, def_val, len );
1116        ret = strlenW( buffer );
1117     }
1118     else
1119        ret = 0;
1120 
1121     RtlLeaveCriticalSection( &PROFILE_CritSect );
1122 
1123     HeapFree(GetProcessHeap(), 0, defval_tmp);
1124 
1125     TRACE("returning %s, %d\n", debugstr_w(buffer), ret);
1126 
1127     return ret;
1128 }
1129 
1130 /***********************************************************************
1131  *           GetPrivateProfileStringA   (KERNEL32.@)
1132  */
1133 DWORD WINAPI GetPrivateProfileStringA( LPCSTR section, LPCSTR entry,
1134 				     LPCSTR def_val, LPSTR buffer,
1135 				     DWORD len, LPCSTR filename )
1136 {
1137     UNICODE_STRING sectionW, entryW, def_valW, filenameW;
1138     LPWSTR bufferW;
1139     INT retW, ret = 0;
1140 
1141     bufferW = buffer ? HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)) : NULL;
1142     if (section) RtlCreateUnicodeStringFromAsciiz(&sectionW, section);
1143     else sectionW.Buffer = NULL;
1144     if (entry) RtlCreateUnicodeStringFromAsciiz(&entryW, entry);
1145     else entryW.Buffer = NULL;
1146     if (def_val) RtlCreateUnicodeStringFromAsciiz(&def_valW, def_val);
1147     else def_valW.Buffer = NULL;
1148     if (filename) RtlCreateUnicodeStringFromAsciiz(&filenameW, filename);
1149     else filenameW.Buffer = NULL;
1150 
1151     retW = GetPrivateProfileStringW( sectionW.Buffer, entryW.Buffer,
1152                                      def_valW.Buffer, bufferW, len,
1153                                      filenameW.Buffer);
1154     if (len && buffer)
1155     {
1156         if (retW)
1157         {
1158             ret = WideCharToMultiByte(CP_ACP, 0, bufferW, retW, buffer, len - 1, NULL, NULL);
1159             if (!ret)
1160                 ret = len - 1;
1161         }
1162         buffer[ret] = 0;
1163     }
1164 
1165     RtlFreeUnicodeString(&sectionW);
1166     RtlFreeUnicodeString(&entryW);
1167     RtlFreeUnicodeString(&def_valW);
1168     RtlFreeUnicodeString(&filenameW);
1169     HeapFree(GetProcessHeap(), 0, bufferW);
1170     return ret;
1171 }
1172 
1173 /***********************************************************************
1174  *           GetProfileStringA   (KERNEL32.@)
1175  */
1176 DWORD WINAPI GetProfileStringA( LPCSTR section, LPCSTR entry, LPCSTR def_val,
1177 			      LPSTR buffer, DWORD len )
1178 {
1179     return GetPrivateProfileStringA( section, entry, def_val,
1180                                      buffer, len, "win.ini" );
1181 }
1182 
1183 /***********************************************************************
1184  *           GetProfileStringW   (KERNEL32.@)
1185  */
1186 DWORD WINAPI GetProfileStringW( LPCWSTR section, LPCWSTR entry,
1187 			      LPCWSTR def_val, LPWSTR buffer, DWORD len )
1188 {
1189     return GetPrivateProfileStringW( section, entry, def_val,
1190 				     buffer, len, wininiW );
1191 }
1192 
1193 /***********************************************************************
1194  *           WriteProfileStringA   (KERNEL32.@)
1195  */
1196 BOOL WINAPI WriteProfileStringA( LPCSTR section, LPCSTR entry,
1197 				 LPCSTR string )
1198 {
1199     return WritePrivateProfileStringA( section, entry, string, "win.ini" );
1200 }
1201 
1202 /***********************************************************************
1203  *           WriteProfileStringW   (KERNEL32.@)
1204  */
1205 BOOL WINAPI WriteProfileStringW( LPCWSTR section, LPCWSTR entry,
1206                                      LPCWSTR string )
1207 {
1208     return WritePrivateProfileStringW( section, entry, string, wininiW );
1209 }
1210 
1211 
1212 /***********************************************************************
1213  *           GetPrivateProfileIntW   (KERNEL32.@)
1214  */
1215 UINT WINAPI GetPrivateProfileIntW( LPCWSTR section, LPCWSTR entry,
1216                                    INT def_val, LPCWSTR filename )
1217 {
1218     WCHAR buffer[30];
1219     UNICODE_STRING bufferW;
1220     ULONG result;
1221 
1222     if (GetPrivateProfileStringW( section, entry, emptystringW,
1223                                    buffer, sizeof(buffer)/sizeof(WCHAR),
1224                                    filename ) == 0)
1225         return def_val;
1226 
1227     /* FIXME: if entry can be found but it's empty, then Win16 is
1228      * supposed to return 0 instead of def_val ! Difficult/problematic
1229      * to implement (every other failure also returns zero buffer),
1230      * thus wait until testing framework avail for making sure nothing
1231      * else gets broken that way. */
1232     if (!buffer[0]) return (UINT)def_val;
1233 
1234     RtlInitUnicodeString( &bufferW, buffer );
1235     RtlUnicodeStringToInteger( &bufferW, 0, &result);
1236     return result;
1237 }
1238 
1239 /***********************************************************************
1240  *           GetPrivateProfileIntA   (KERNEL32.@)
1241  *
1242  * FIXME: rewrite using unicode
1243  */
1244 UINT WINAPI GetPrivateProfileIntA( LPCSTR section, LPCSTR entry,
1245 				   INT def_val, LPCSTR filename )
1246 {
1247     UNICODE_STRING entryW, filenameW, sectionW;
1248     UINT res;
1249     if(entry) RtlCreateUnicodeStringFromAsciiz(&entryW, entry);
1250     else entryW.Buffer = NULL;
1251     if(filename) RtlCreateUnicodeStringFromAsciiz(&filenameW, filename);
1252     else filenameW.Buffer = NULL;
1253     if(section) RtlCreateUnicodeStringFromAsciiz(&sectionW, section);
1254     else sectionW.Buffer = NULL;
1255     res = GetPrivateProfileIntW(sectionW.Buffer, entryW.Buffer, def_val,
1256                                 filenameW.Buffer);
1257     RtlFreeUnicodeString(&sectionW);
1258     RtlFreeUnicodeString(&filenameW);
1259     RtlFreeUnicodeString(&entryW);
1260     return res;
1261 }
1262 
1263 /***********************************************************************
1264  *           GetPrivateProfileSectionW   (KERNEL32.@)
1265  */
1266 DWORD WINAPI GetPrivateProfileSectionW( LPCWSTR section, LPWSTR buffer,
1267 				      DWORD len, LPCWSTR filename )
1268 {
1269     int ret = 0;
1270 
1271     if (!section || !buffer)
1272     {
1273         SetLastError(ERROR_INVALID_PARAMETER);
1274         return 0;
1275     }
1276 
1277     TRACE("(%s, %p, %d, %s)\n", debugstr_w(section), buffer, len, debugstr_w(filename));
1278 
1279     RtlEnterCriticalSection( &PROFILE_CritSect );
1280 
1281     if (PROFILE_Open( filename, FALSE ))
1282         ret = PROFILE_GetSection(CurProfile->section, section, buffer, len, TRUE);
1283 
1284     RtlLeaveCriticalSection( &PROFILE_CritSect );
1285 
1286     return ret;
1287 }
1288 
1289 /***********************************************************************
1290  *           GetPrivateProfileSectionA   (KERNEL32.@)
1291  */
1292 DWORD WINAPI GetPrivateProfileSectionA( LPCSTR section, LPSTR buffer,
1293                                       DWORD len, LPCSTR filename )
1294 {
1295     UNICODE_STRING sectionW, filenameW;
1296     LPWSTR bufferW;
1297     INT retW, ret = 0;
1298 
1299     if (!section || !buffer)
1300     {
1301         SetLastError(ERROR_INVALID_PARAMETER);
1302         return 0;
1303     }
1304 
1305     bufferW = HeapAlloc(GetProcessHeap(), 0, len * 2 * sizeof(WCHAR));
1306     RtlCreateUnicodeStringFromAsciiz(&sectionW, section);
1307     if (filename) RtlCreateUnicodeStringFromAsciiz(&filenameW, filename);
1308     else filenameW.Buffer = NULL;
1309 
1310     retW = GetPrivateProfileSectionW(sectionW.Buffer, bufferW, len * 2, filenameW.Buffer);
1311     if (retW)
1312     {
1313         if (retW == len * 2 - 2) retW++;  /* overflow */
1314         ret = WideCharToMultiByte(CP_ACP, 0, bufferW, retW + 1, buffer, len, NULL, NULL);
1315         if (!ret || ret == len)  /* overflow */
1316         {
1317             ret = len - 2;
1318             buffer[len-2] = 0;
1319             buffer[len-1] = 0;
1320         }
1321         else ret--;
1322     }
1323     else
1324     {
1325         buffer[0] = 0;
1326         buffer[1] = 0;
1327     }
1328 
1329     RtlFreeUnicodeString(&sectionW);
1330     RtlFreeUnicodeString(&filenameW);
1331     HeapFree(GetProcessHeap(), 0, bufferW);
1332     return ret;
1333 }
1334 
1335 /***********************************************************************
1336  *           GetProfileSectionA   (KERNEL32.@)
1337  */
1338 DWORD WINAPI GetProfileSectionA( LPCSTR section, LPSTR buffer, DWORD len )
1339 {
1340     return GetPrivateProfileSectionA( section, buffer, len, "win.ini" );
1341 }
1342 
1343 /***********************************************************************
1344  *           GetProfileSectionW   (KERNEL32.@)
1345  */
1346 DWORD WINAPI GetProfileSectionW( LPCWSTR section, LPWSTR buffer, DWORD len )
1347 {
1348     return GetPrivateProfileSectionW( section, buffer, len, wininiW );
1349 }
1350 
1351 
1352 /***********************************************************************
1353  *           WritePrivateProfileStringW   (KERNEL32.@)
1354  */
1355 BOOL WINAPI WritePrivateProfileStringW( LPCWSTR section, LPCWSTR entry,
1356 					LPCWSTR string, LPCWSTR filename )
1357 {
1358     BOOL ret = FALSE;
1359 
1360     RtlEnterCriticalSection( &PROFILE_CritSect );
1361 
1362     if (!section && !entry && !string) /* documented "file flush" case */
1363     {
1364         if (!filename || PROFILE_Open( filename, TRUE ))
1365         {
1366             if (CurProfile) PROFILE_ReleaseFile();  /* always return FALSE in this case */
1367         }
1368     }
1369     else if (PROFILE_Open( filename, TRUE ))
1370     {
1371         if (!section) {
1372             SetLastError(ERROR_FILE_NOT_FOUND);
1373         } else {
1374             ret = PROFILE_SetString( section, entry, string, FALSE);
1375             PROFILE_FlushFile();
1376         }
1377     }
1378 
1379     RtlLeaveCriticalSection( &PROFILE_CritSect );
1380     return ret;
1381 }
1382 
1383 /***********************************************************************
1384  *           WritePrivateProfileStringA   (KERNEL32.@)
1385  */
1386 BOOL WINAPI DECLSPEC_HOTPATCH WritePrivateProfileStringA( LPCSTR section, LPCSTR entry,
1387                                                           LPCSTR string, LPCSTR filename )
1388 {
1389     UNICODE_STRING sectionW, entryW, stringW, filenameW;
1390     BOOL ret;
1391 
1392     if (section) RtlCreateUnicodeStringFromAsciiz(&sectionW, section);
1393     else sectionW.Buffer = NULL;
1394     if (entry) RtlCreateUnicodeStringFromAsciiz(&entryW, entry);
1395     else entryW.Buffer = NULL;
1396     if (string) RtlCreateUnicodeStringFromAsciiz(&stringW, string);
1397     else stringW.Buffer = NULL;
1398     if (filename) RtlCreateUnicodeStringFromAsciiz(&filenameW, filename);
1399     else filenameW.Buffer = NULL;
1400 
1401     ret = WritePrivateProfileStringW(sectionW.Buffer, entryW.Buffer,
1402                                      stringW.Buffer, filenameW.Buffer);
1403     RtlFreeUnicodeString(&sectionW);
1404     RtlFreeUnicodeString(&entryW);
1405     RtlFreeUnicodeString(&stringW);
1406     RtlFreeUnicodeString(&filenameW);
1407     return ret;
1408 }
1409 
1410 /***********************************************************************
1411  *           WritePrivateProfileSectionW   (KERNEL32.@)
1412  */
1413 BOOL WINAPI WritePrivateProfileSectionW( LPCWSTR section,
1414                                          LPCWSTR string, LPCWSTR filename )
1415 {
1416     BOOL ret = FALSE;
1417     LPWSTR p;
1418 
1419     RtlEnterCriticalSection( &PROFILE_CritSect );
1420 
1421     if (!section && !string)
1422     {
1423         if (!filename || PROFILE_Open( filename, TRUE ))
1424         {
1425             if (CurProfile) PROFILE_ReleaseFile();  /* always return FALSE in this case */
1426         }
1427     }
1428     else if (PROFILE_Open( filename, TRUE )) {
1429         if (!string) {/* delete the named section*/
1430 	    ret = PROFILE_SetString(section,NULL,NULL, FALSE);
1431 	    PROFILE_FlushFile();
1432         } else {
1433 	    PROFILE_DeleteAllKeys(section);
1434 	    ret = TRUE;
1435 	    while(*string) {
1436                 LPWSTR buf = HeapAlloc( GetProcessHeap(), 0, (strlenW(string)+1) * sizeof(WCHAR) );
1437                 strcpyW( buf, string );
1438                 if((p = strchrW( buf, '='))) {
1439                     *p='\0';
1440                     ret = PROFILE_SetString( section, buf, p+1, TRUE);
1441                 }
1442                 HeapFree( GetProcessHeap(), 0, buf );
1443                 string += strlenW(string)+1;
1444             }
1445             PROFILE_FlushFile();
1446         }
1447     }
1448 
1449     RtlLeaveCriticalSection( &PROFILE_CritSect );
1450     return ret;
1451 }
1452 
1453 /***********************************************************************
1454  *           WritePrivateProfileSectionA   (KERNEL32.@)
1455  */
1456 BOOL WINAPI WritePrivateProfileSectionA( LPCSTR section,
1457                                          LPCSTR string, LPCSTR filename)
1458 
1459 {
1460     UNICODE_STRING sectionW, filenameW;
1461     LPWSTR stringW;
1462     BOOL ret;
1463 
1464     if (string)
1465     {
1466         INT lenA, lenW;
1467         LPCSTR p = string;
1468 
1469         while(*p) p += strlen(p) + 1;
1470         lenA = p - string + 1;
1471         lenW = MultiByteToWideChar(CP_ACP, 0, string, lenA, NULL, 0);
1472         if ((stringW = HeapAlloc(GetProcessHeap(), 0, lenW * sizeof(WCHAR))))
1473             MultiByteToWideChar(CP_ACP, 0, string, lenA, stringW, lenW);
1474     }
1475     else stringW = NULL;
1476     if (section) RtlCreateUnicodeStringFromAsciiz(&sectionW, section);
1477     else sectionW.Buffer = NULL;
1478     if (filename) RtlCreateUnicodeStringFromAsciiz(&filenameW, filename);
1479     else filenameW.Buffer = NULL;
1480 
1481     ret = WritePrivateProfileSectionW(sectionW.Buffer, stringW, filenameW.Buffer);
1482 
1483     HeapFree(GetProcessHeap(), 0, stringW);
1484     RtlFreeUnicodeString(&sectionW);
1485     RtlFreeUnicodeString(&filenameW);
1486     return ret;
1487 }
1488 
1489 /***********************************************************************
1490  *           WriteProfileSectionA   (KERNEL32.@)
1491  */
1492 BOOL WINAPI WriteProfileSectionA( LPCSTR section, LPCSTR keys_n_values)
1493 
1494 {
1495     return WritePrivateProfileSectionA( section, keys_n_values, "win.ini");
1496 }
1497 
1498 /***********************************************************************
1499  *           WriteProfileSectionW   (KERNEL32.@)
1500  */
1501 BOOL WINAPI WriteProfileSectionW( LPCWSTR section, LPCWSTR keys_n_values)
1502 {
1503    return WritePrivateProfileSectionW(section, keys_n_values, wininiW);
1504 }
1505 
1506 
1507 /***********************************************************************
1508  *           GetPrivateProfileSectionNamesW  (KERNEL32.@)
1509  *
1510  * Returns the section names contained in the specified file.
1511  * FIXME: Where do we find this file when the path is relative?
1512  * The section names are returned as a list of strings with an extra
1513  * '\0' to mark the end of the list. Except for that the behavior
1514  * depends on the Windows version.
1515  *
1516  * Win95:
1517  * - if the buffer is 0 or 1 character long then it is as if it was of
1518  *   infinite length.
1519  * - otherwise, if the buffer is too small only the section names that fit
1520  *   are returned.
1521  * - note that this means if the buffer was too small to return even just
1522  *   the first section name then a single '\0' will be returned.
1523  * - the return value is the number of characters written in the buffer,
1524  *   except if the buffer was too small in which case len-2 is returned
1525  *
1526  * Win2000:
1527  * - if the buffer is 0, 1 or 2 characters long then it is filled with
1528  *   '\0' and the return value is 0
1529  * - otherwise if the buffer is too small then the first section name that
1530  *   does not fit is truncated so that the string list can be terminated
1531  *   correctly (double '\0')
1532  * - the return value is the number of characters written in the buffer
1533  *   except for the trailing '\0'. If the buffer is too small, then the
1534  *   return value is len-2
1535  * - Win2000 has a bug that triggers when the section names and the
1536  *   trailing '\0' fit exactly in the buffer. In that case the trailing
1537  *   '\0' is missing.
1538  *
1539  * Wine implements the observed Win2000 behavior (except for the bug).
1540  *
1541  * Note that when the buffer is big enough then the return value may be any
1542  * value between 1 and len-1 (or len in Win95), including len-2.
1543  */
1544 DWORD WINAPI GetPrivateProfileSectionNamesW( LPWSTR buffer, DWORD size,
1545 					     LPCWSTR filename)
1546 {
1547     DWORD ret = 0;
1548 
1549     RtlEnterCriticalSection( &PROFILE_CritSect );
1550 
1551     if (PROFILE_Open( filename, FALSE ))
1552         ret = PROFILE_GetSectionNames(buffer, size);
1553 
1554     RtlLeaveCriticalSection( &PROFILE_CritSect );
1555 
1556     return ret;
1557 }
1558 
1559 
1560 /***********************************************************************
1561  *           GetPrivateProfileSectionNamesA  (KERNEL32.@)
1562  */
1563 DWORD WINAPI GetPrivateProfileSectionNamesA( LPSTR buffer, DWORD size,
1564 					     LPCSTR filename)
1565 {
1566     UNICODE_STRING filenameW;
1567     LPWSTR bufferW;
1568     INT retW, ret = 0;
1569 
1570     bufferW = buffer ? HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR)) : NULL;
1571     if (filename) RtlCreateUnicodeStringFromAsciiz(&filenameW, filename);
1572     else filenameW.Buffer = NULL;
1573 
1574     retW = GetPrivateProfileSectionNamesW(bufferW, size, filenameW.Buffer);
1575     if (retW && size)
1576     {
1577         ret = WideCharToMultiByte(CP_ACP, 0, bufferW, retW+1, buffer, size-1, NULL, NULL);
1578         if (!ret)
1579         {
1580             ret = size-2;
1581             buffer[size-1] = 0;
1582         }
1583         else
1584           ret = ret-1;
1585     }
1586     else if(size)
1587         buffer[0] = '\0';
1588 
1589     RtlFreeUnicodeString(&filenameW);
1590     HeapFree(GetProcessHeap(), 0, bufferW);
1591     return ret;
1592 }
1593 
1594 /***********************************************************************
1595  *           GetPrivateProfileStructW (KERNEL32.@)
1596  *
1597  * Should match Win95's behaviour pretty much
1598  */
1599 BOOL WINAPI GetPrivateProfileStructW (LPCWSTR section, LPCWSTR key,
1600                                       LPVOID buf, UINT len, LPCWSTR filename)
1601 {
1602     BOOL	ret = FALSE;
1603 
1604     RtlEnterCriticalSection( &PROFILE_CritSect );
1605 
1606     if (PROFILE_Open( filename, FALSE )) {
1607         PROFILEKEY *k = PROFILE_Find ( &CurProfile->section, section, key, FALSE, FALSE);
1608 	if (k) {
1609 	    TRACE("value (at %p): %s\n", k->value, debugstr_w(k->value));
1610 	    if (((strlenW(k->value) - 2) / 2) == len)
1611 	    {
1612 		LPWSTR end, p;
1613 		BOOL valid = TRUE;
1614 		WCHAR c;
1615 		DWORD chksum = 0;
1616 
1617 	        end  = k->value + strlenW(k->value); /* -> '\0' */
1618 	        /* check for invalid chars in ASCII coded hex string */
1619 	        for (p=k->value; p < end; p++)
1620 		{
1621                     if (!isxdigitW(*p))
1622 		    {
1623 			WARN("invalid char '%x' in file %s->[%s]->%s !\n",
1624                              *p, debugstr_w(filename), debugstr_w(section), debugstr_w(key));
1625 		        valid = FALSE;
1626 		        break;
1627 		    }
1628 		}
1629 		if (valid)
1630 		{
1631 		    BOOL highnibble = TRUE;
1632 		    BYTE b = 0, val;
1633                     LPBYTE binbuf = buf;
1634 
1635 	            end -= 2; /* don't include checksum in output data */
1636 	            /* translate ASCII hex format into binary data */
1637                     for (p=k->value; p < end; p++)
1638             	    {
1639 	        	c = toupperW(*p);
1640 			val = (c > '9') ?
1641 				(c - 'A' + 10) : (c - '0');
1642 
1643 			if (highnibble)
1644 		    	    b = val << 4;
1645 			else
1646 			{
1647 		    	    b += val;
1648 		    	    *binbuf++ = b; /* feed binary data into output */
1649 		    	    chksum += b; /* calculate checksum */
1650 			}
1651 			highnibble ^= 1; /* toggle */
1652             	    }
1653 		    /* retrieve stored checksum value */
1654 		    c = toupperW(*p++);
1655 		    b = ( (c > '9') ? (c - 'A' + 10) : (c - '0') ) << 4;
1656 		    c = toupperW(*p);
1657 		    b +=  (c > '9') ? (c - 'A' + 10) : (c - '0');
1658 	            if (b == (chksum & 0xff)) /* checksums match ? */
1659                         ret = TRUE;
1660                 }
1661             }
1662 	}
1663     }
1664     RtlLeaveCriticalSection( &PROFILE_CritSect );
1665 
1666     return ret;
1667 }
1668 
1669 /***********************************************************************
1670  *           GetPrivateProfileStructA (KERNEL32.@)
1671  */
1672 BOOL WINAPI GetPrivateProfileStructA (LPCSTR section, LPCSTR key,
1673 				      LPVOID buffer, UINT len, LPCSTR filename)
1674 {
1675     UNICODE_STRING sectionW, keyW, filenameW;
1676     INT ret;
1677 
1678     if (section) RtlCreateUnicodeStringFromAsciiz(&sectionW, section);
1679     else sectionW.Buffer = NULL;
1680     if (key) RtlCreateUnicodeStringFromAsciiz(&keyW, key);
1681     else keyW.Buffer = NULL;
1682     if (filename) RtlCreateUnicodeStringFromAsciiz(&filenameW, filename);
1683     else filenameW.Buffer = NULL;
1684 
1685     ret = GetPrivateProfileStructW(sectionW.Buffer, keyW.Buffer, buffer, len,
1686                                    filenameW.Buffer);
1687     /* Do not translate binary data. */
1688 
1689     RtlFreeUnicodeString(&sectionW);
1690     RtlFreeUnicodeString(&keyW);
1691     RtlFreeUnicodeString(&filenameW);
1692     return ret;
1693 }
1694 
1695 
1696 
1697 /***********************************************************************
1698  *           WritePrivateProfileStructW (KERNEL32.@)
1699  */
1700 BOOL WINAPI WritePrivateProfileStructW (LPCWSTR section, LPCWSTR key,
1701                                         LPVOID buf, UINT bufsize, LPCWSTR filename)
1702 {
1703     BOOL ret = FALSE;
1704     LPBYTE binbuf;
1705     LPWSTR outstring, p;
1706     DWORD sum = 0;
1707 
1708     if (!section && !key && !buf)  /* flush the cache */
1709         return WritePrivateProfileStringW( NULL, NULL, NULL, filename );
1710 
1711     /* allocate string buffer for hex chars + checksum hex char + '\0' */
1712     outstring = HeapAlloc( GetProcessHeap(), 0, (bufsize*2 + 2 + 1) * sizeof(WCHAR) );
1713     p = outstring;
1714     for (binbuf = (LPBYTE)buf; binbuf < (LPBYTE)buf+bufsize; binbuf++) {
1715       *p++ = hex[*binbuf >> 4];
1716       *p++ = hex[*binbuf & 0xf];
1717       sum += *binbuf;
1718     }
1719     /* checksum is sum & 0xff */
1720     *p++ = hex[(sum & 0xf0) >> 4];
1721     *p++ = hex[sum & 0xf];
1722     *p++ = '\0';
1723 
1724     RtlEnterCriticalSection( &PROFILE_CritSect );
1725 
1726     if (PROFILE_Open( filename, TRUE )) {
1727         ret = PROFILE_SetString( section, key, outstring, FALSE);
1728         PROFILE_FlushFile();
1729     }
1730 
1731     RtlLeaveCriticalSection( &PROFILE_CritSect );
1732 
1733     HeapFree( GetProcessHeap(), 0, outstring );
1734 
1735     return ret;
1736 }
1737 
1738 /***********************************************************************
1739  *           WritePrivateProfileStructA (KERNEL32.@)
1740  */
1741 BOOL WINAPI WritePrivateProfileStructA (LPCSTR section, LPCSTR key,
1742 					LPVOID buf, UINT bufsize, LPCSTR filename)
1743 {
1744     UNICODE_STRING sectionW, keyW, filenameW;
1745     INT ret;
1746 
1747     if (section) RtlCreateUnicodeStringFromAsciiz(&sectionW, section);
1748     else sectionW.Buffer = NULL;
1749     if (key) RtlCreateUnicodeStringFromAsciiz(&keyW, key);
1750     else keyW.Buffer = NULL;
1751     if (filename) RtlCreateUnicodeStringFromAsciiz(&filenameW, filename);
1752     else filenameW.Buffer = NULL;
1753 
1754     /* Do not translate binary data. */
1755     ret = WritePrivateProfileStructW(sectionW.Buffer, keyW.Buffer, buf, bufsize,
1756                                      filenameW.Buffer);
1757 
1758     RtlFreeUnicodeString(&sectionW);
1759     RtlFreeUnicodeString(&keyW);
1760     RtlFreeUnicodeString(&filenameW);
1761     return ret;
1762 }
1763 
1764 
1765 /***********************************************************************
1766  *           OpenProfileUserMapping   (KERNEL32.@)
1767  */
1768 BOOL WINAPI OpenProfileUserMapping(void) {
1769     FIXME("(), stub!\n");
1770     SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
1771     return FALSE;
1772 }
1773 
1774 /***********************************************************************
1775  *           CloseProfileUserMapping   (KERNEL32.@)
1776  */
1777 BOOL WINAPI CloseProfileUserMapping(void) {
1778     FIXME("(), stub!\n");
1779     SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
1780     return FALSE;
1781 }
1782