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