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