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