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