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