Corrected last error information.
[wine] / dlls / msi / string.c
1 /*
2  * Implementation of the Microsoft Installer (msi.dll)
3  *
4  * Copyright 2002-2004, Mike McCormack for CodeWeavers
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20
21 #include <stdarg.h>
22 #include <assert.h>
23
24 #include "windef.h"
25 #include "winbase.h"
26 #include "winerror.h"
27 #include "wine/debug.h"
28 #include "wine/unicode.h"
29 #include "msi.h"
30 #include "msiquery.h"
31 #include "objbase.h"
32 #include "objidl.h"
33 #include "msipriv.h"
34 #include "winnls.h"
35
36 #include "query.h"
37
38 WINE_DEFAULT_DEBUG_CHANNEL(msidb);
39
40 typedef struct _msistring
41 {
42     UINT hash;
43     UINT refcount;
44     LPWSTR str;
45 } msistring;
46
47 struct string_table
48 {
49     UINT maxcount;         /* the number of strings */
50     UINT freeslot;
51     UINT codepage;
52     msistring *strings; /* an array of strings (in the tree) */
53 };
54
55 static UINT msistring_makehash( const WCHAR *str )
56 {
57     UINT hash = 0;
58
59     if (str==NULL)
60         return hash;
61
62     while( *str )
63     {
64         hash ^= *str++;
65         hash *= 53;
66         hash = (hash<<5) | (hash>>27);
67     }
68     return hash;
69 }
70
71 string_table *msi_init_stringtable( int entries, UINT codepage )
72 {
73     string_table *st;
74
75     st = msi_alloc( sizeof (string_table) );
76     if( !st )
77         return NULL;    
78     if( entries < 1 )
79         entries = 1;
80     st->strings = msi_alloc_zero( sizeof (msistring) * entries );
81     if( !st )
82     {
83         msi_free( st );
84         return NULL;    
85     }
86     st->maxcount = entries;
87     st->freeslot = 1;
88     st->codepage = codepage;
89
90     return st;
91 }
92
93 VOID msi_destroy_stringtable( string_table *st )
94 {
95     UINT i;
96
97     for( i=0; i<st->maxcount; i++ )
98     {
99         if( st->strings[i].refcount )
100             msi_free( st->strings[i].str );
101     }
102     msi_free( st->strings );
103     msi_free( st );
104 }
105
106 static int st_find_free_entry( string_table *st )
107 {
108     UINT i, sz;
109     msistring *p;
110
111     TRACE("%p\n", st);
112
113     if( st->freeslot )
114     {
115         for( i = st->freeslot; i < st->maxcount; i++ )
116             if( !st->strings[i].refcount )
117                 return i;
118     }
119     for( i = 1; i < st->maxcount; i++ )
120         if( !st->strings[i].refcount )
121             return i;
122
123     /* dynamically resize */
124     sz = st->maxcount + 1 + st->maxcount/2;
125     p = msi_realloc_zero( st->strings, sz*sizeof(msistring) );
126     if( !p )
127         return -1;
128     st->strings = p;
129     st->freeslot = st->maxcount;
130     st->maxcount = sz;
131     if( st->strings[st->freeslot].refcount )
132         ERR("oops. expected freeslot to be free...\n");
133     return st->freeslot;
134 }
135
136 static void st_mark_entry_used( string_table *st, UINT n )
137 {
138     if( n >= st->maxcount )
139         return;
140     st->freeslot = n + 1;
141 }
142
143 int msi_addstring( string_table *st, UINT n, const CHAR *data, int len, UINT refcount )
144 {
145     int sz;
146
147     if( !data )
148         return 0;
149     if( !data[0] )
150         return 0;
151     if( n > 0 )
152     {
153         if( st->strings[n].refcount )
154             return -1;
155     }
156     else
157     {
158         if( ERROR_SUCCESS == msi_string2idA( st, data, &n ) )
159         {
160             st->strings[n].refcount++;
161             return n;
162         }
163         n = st_find_free_entry( st );
164         if( n < 0 )
165             return -1;
166     }
167
168     if( n < 1 )
169     {
170         ERR("invalid index adding %s (%d)\n", debugstr_a( data ), n );
171         return -1;
172     }
173
174     /* allocate a new string */
175     if( len < 0 )
176         len = strlen(data);
177     sz = MultiByteToWideChar( st->codepage, 0, data, len, NULL, 0 );
178     st->strings[n].str = msi_alloc( (sz+1)*sizeof(WCHAR) );
179     if( !st->strings[n].str )
180         return -1;
181     MultiByteToWideChar( st->codepage, 0, data, len, st->strings[n].str, sz );
182     st->strings[n].str[sz] = 0;
183     st->strings[n].refcount = 1;
184     st->strings[n].hash = msistring_makehash( st->strings[n].str );
185
186     st_mark_entry_used( st, n );
187
188     return n;
189 }
190
191 int msi_addstringW( string_table *st, UINT n, const WCHAR *data, int len, UINT refcount )
192 {
193     /* TRACE("[%2d] = %s\n", string_no, debugstr_an(data,len) ); */
194
195     if( !data )
196         return 0;
197     if( !data[0] )
198         return 0;
199     if( n > 0 )
200     {
201         if( st->strings[n].refcount )
202             return -1;
203     }
204     else
205     {
206         if( ERROR_SUCCESS == msi_string2idW( st, data, &n ) )
207         {
208             st->strings[n].refcount++;
209             return n;
210         }
211         n = st_find_free_entry( st );
212         if( n < 0 )
213             return -1;
214     }
215
216     if( n < 1 )
217     {
218         ERR("invalid index adding %s (%d)\n", debugstr_w( data ), n );
219         return -1;
220     }
221
222     /* allocate a new string */
223     if(len<0)
224         len = strlenW(data);
225     TRACE("%s, n = %d len = %d\n", debugstr_w(data), n, len );
226
227     st->strings[n].str = msi_alloc( (len+1)*sizeof(WCHAR) );
228     if( !st->strings[n].str )
229         return -1;
230     TRACE("%d\n",__LINE__);
231     memcpy( st->strings[n].str, data, len*sizeof(WCHAR) );
232     st->strings[n].str[len] = 0;
233     st->strings[n].refcount = 1;
234     st->strings[n].hash = msistring_makehash( st->strings[n].str );
235
236     st_mark_entry_used( st, n );
237
238     return n;
239 }
240
241 /* find the string identified by an id - return null if there's none */
242 const WCHAR *msi_string_lookup_id( string_table *st, UINT id )
243 {
244     static const WCHAR zero[] = { 0 };
245     if( id == 0 )
246         return zero;
247
248     if( id >= st->maxcount )
249         return NULL;
250
251     if( id && !st->strings[id].refcount )
252         return NULL;
253
254     return st->strings[id].str;
255 }
256
257 /*
258  *  msi_id2stringW
259  *
260  *  [in] st         - pointer to the string table
261  *  [in] id  - id of the string to retrieve
262  *  [out] buffer    - destination of the string
263  *  [in/out] sz     - number of bytes available in the buffer on input
264  *                    number of bytes used on output
265  *
266  *   The size includes the terminating nul character.  Short buffers
267  *  will be filled, but not nul terminated.
268  */
269 UINT msi_id2stringW( string_table *st, UINT id, LPWSTR buffer, UINT *sz )
270 {
271     UINT len;
272     const WCHAR *str;
273
274     TRACE("Finding string %d of %d\n", id, st->maxcount);
275
276     str = msi_string_lookup_id( st, id );
277     if( !str )
278         return ERROR_FUNCTION_FAILED;
279
280     len = strlenW( str ) + 1;
281
282     if( !buffer )
283     {
284         *sz = len;
285         return ERROR_SUCCESS;
286     }
287
288     if( *sz < len )
289         *sz = len;
290     memcpy( buffer, str, (*sz)*sizeof(WCHAR) ); 
291     *sz = len;
292
293     return ERROR_SUCCESS;
294 }
295
296 /*
297  *  msi_id2stringA
298  *
299  *  [in] st         - pointer to the string table
300  *  [in] id         - id of the string to retrieve
301  *  [out] buffer    - destination of the UTF8 string
302  *  [in/out] sz     - number of bytes available in the buffer on input
303  *                    number of bytes used on output
304  *
305  *   The size includes the terminating nul character.  Short buffers
306  *  will be filled, but not nul terminated.
307  */
308 UINT msi_id2stringA( string_table *st, UINT id, LPSTR buffer, UINT *sz )
309 {
310     UINT len;
311     const WCHAR *str;
312     int n;
313
314     TRACE("Finding string %d of %d\n", id, st->maxcount);
315
316     str = msi_string_lookup_id( st, id );
317     if( !str )
318         return ERROR_FUNCTION_FAILED;
319
320     len = WideCharToMultiByte( st->codepage, 0, str, -1, NULL, 0, NULL, NULL );
321
322     if( !buffer )
323     {
324         *sz = len;
325         return ERROR_SUCCESS;
326     }
327
328     if( len > *sz )
329     {
330         n = strlenW( str ) + 1;
331         while( n && (len > *sz) )
332             len = WideCharToMultiByte( st->codepage, 0, 
333                            str, --n, NULL, 0, NULL, NULL );
334     }
335     else
336         n = -1;
337
338     *sz = WideCharToMultiByte( st->codepage, 0, str, n, buffer, len, NULL, NULL );
339
340     return ERROR_SUCCESS;
341 }
342
343 /*
344  *  msi_string2idW
345  *
346  *  [in] st         - pointer to the string table
347  *  [in] str        - string to find in the string table
348  *  [out] id        - id of the string, if found
349  */
350 UINT msi_string2idW( string_table *st, LPCWSTR str, UINT *id )
351 {
352     UINT hash;
353     UINT i, r = ERROR_INVALID_PARAMETER;
354
355     hash = msistring_makehash( str );
356     for( i=0; i<st->maxcount; i++ )
357     {
358         if ( (str == NULL && st->strings[i].str == NULL) || 
359             ( ( st->strings[i].hash == hash ) &&
360             !strcmpW( st->strings[i].str, str ) ))
361         {
362             r = ERROR_SUCCESS;
363             *id = i;
364             break;
365         }
366     }
367
368     return r;
369 }
370
371 UINT msi_string2idA( string_table *st, LPCSTR buffer, UINT *id )
372 {
373     DWORD sz;
374     UINT r = ERROR_INVALID_PARAMETER;
375     LPWSTR str;
376
377     TRACE("Finding string %s in string table\n", debugstr_a(buffer) );
378
379     if( buffer[0] == 0 )
380     {
381         *id = 0;
382         return ERROR_SUCCESS;
383     }
384
385     sz = MultiByteToWideChar( st->codepage, 0, buffer, -1, NULL, 0 );
386     if( sz <= 0 )
387         return r;
388     str = msi_alloc( sz*sizeof(WCHAR) );
389     if( !str )
390         return ERROR_NOT_ENOUGH_MEMORY;
391     MultiByteToWideChar( st->codepage, 0, buffer, -1, str, sz );
392
393     r = msi_string2idW( st, str, id );
394     msi_free( str );
395
396     return r;
397 }
398
399 UINT msi_strcmp( string_table *st, UINT lval, UINT rval, UINT *res )
400 {
401     const WCHAR *l_str, *r_str;
402
403     l_str = msi_string_lookup_id( st, lval );
404     if( !l_str )
405         return ERROR_INVALID_PARAMETER;
406     
407     r_str = msi_string_lookup_id( st, rval );
408     if( !r_str )
409         return ERROR_INVALID_PARAMETER;
410
411     /* does this do the right thing for all UTF-8 strings? */
412     *res = strcmpW( l_str, r_str );
413
414     return ERROR_SUCCESS;
415 }
416
417 UINT msi_string_count( string_table *st )
418 {
419     return st->maxcount;
420 }
421
422 UINT msi_id_refcount( string_table *st, UINT i )
423 {
424     if( i >= st->maxcount )
425         return 0;
426     return st->strings[i].refcount;
427 }
428
429 UINT msi_string_totalsize( string_table *st, UINT *total )
430 {
431     UINT size = 0, i, len;
432
433     if( st->strings[0].str || st->strings[0].refcount )
434         ERR("oops. element 0 has a string\n");
435     *total = 0;
436     for( i=1; i<st->maxcount; i++ )
437     {
438         if( st->strings[i].str )
439         {
440             TRACE("[%u] = %s\n", i, debugstr_w(st->strings[i].str));
441             len = WideCharToMultiByte( st->codepage, 0,
442                      st->strings[i].str, -1, NULL, 0, NULL, NULL);
443             if( len )
444                 len--;
445             size += len;
446             *total = (i+1);
447         }
448     }
449     TRACE("%u/%u strings %u bytes codepage %x\n", *total, st->maxcount, size, st->codepage );
450     return size;
451 }
452
453 UINT msi_string_get_codepage( string_table *st )
454 {
455     return st->codepage;
456 }