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