rpcrt4: Now that there's a process-wide pool of connections we don't
[wine] / dlls / msi / appsearch.c
1 /*
2  * Implementation of the AppSearch action of the Microsoft Installer (msi.dll)
3  *
4  * Copyright 2005 Juan Lang
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20 #include <stdarg.h>
21
22 #define COBJMACROS
23
24 #include "windef.h"
25 #include "winbase.h"
26 #include "winreg.h"
27 #include "msi.h"
28 #include "msiquery.h"
29 #include "msidefs.h"
30 #include "winver.h"
31 #include "wine/unicode.h"
32 #include "wine/debug.h"
33 #include "msipriv.h"
34 #include "action.h"
35
36 WINE_DEFAULT_DEBUG_CHANNEL(msi);
37
38 typedef struct tagMSISIGNATURE
39 {
40     LPWSTR   Name;     /* NOT owned by this structure */
41     LPWSTR   Property; /* NOT owned by this structure */
42     LPWSTR   File;
43     DWORD    MinVersionMS;
44     DWORD    MinVersionLS;
45     DWORD    MaxVersionMS;
46     DWORD    MaxVersionLS;
47     DWORD    MinSize;
48     DWORD    MaxSize;
49     FILETIME MinTime;
50     FILETIME MaxTime;
51     LPWSTR   Languages;
52 }MSISIGNATURE;
53
54 static void ACTION_VerStrToInteger(LPCWSTR verStr, PDWORD ms, PDWORD ls)
55 {
56     const WCHAR *ptr;
57     int x1 = 0, x2 = 0, x3 = 0, x4 = 0;
58
59     x1 = atoiW(verStr);
60     ptr = strchrW(verStr, '.');
61     if (ptr)
62     {
63         x2 = atoiW(ptr + 1);
64         ptr = strchrW(ptr + 1, '.');
65     }
66     if (ptr)
67     {
68         x3 = atoiW(ptr + 1);
69         ptr = strchrW(ptr + 1, '.');
70     }
71     if (ptr)
72         x4 = atoiW(ptr + 1);
73     /* FIXME: byte-order dependent? */
74     *ms = x1 << 16 | x2;
75     *ls = x3 << 16 | x4;
76 }
77
78 /* Fills in sig with the the values from the Signature table, where name is the
79  * signature to find.  Upon return, sig->File will be NULL if the record is not
80  * found, and not NULL if it is found.
81  * Warning: clears all fields in sig!
82  * Returns ERROR_SUCCESS upon success (where not finding the record counts as
83  * success), something else on error.
84  */
85 static UINT ACTION_AppSearchGetSignature(MSIPACKAGE *package, MSISIGNATURE *sig,
86  LPCWSTR name)
87 {
88     MSIQUERY *view;
89     UINT rc;
90     static const WCHAR ExecSeqQuery[] =  {
91    's','e','l','e','c','t',' ','*',' ',
92    'f','r','o','m',' ',
93    'S','i','g','n','a','t','u','r','e',' ',
94    'w','h','e','r','e',' ','S','i','g','n','a','t','u','r','e',' ','=',' ',
95    '\'','%','s','\'',0};
96
97     TRACE("(package %p, sig %p)\n", package, sig);
98     memset(sig, 0, sizeof(*sig));
99     rc = MSI_OpenQuery(package->db, &view, ExecSeqQuery, name);
100     if (rc == ERROR_SUCCESS)
101     {
102         MSIRECORD *row = 0;
103         DWORD time;
104         WCHAR *minVersion, *maxVersion;
105
106         rc = MSI_ViewExecute(view, 0);
107         if (rc != ERROR_SUCCESS)
108         {
109             TRACE("MSI_ViewExecute returned %d\n", rc);
110             goto end;
111         }
112         rc = MSI_ViewFetch(view,&row);
113         if (rc != ERROR_SUCCESS)
114         {
115             TRACE("MSI_ViewFetch returned %d\n", rc);
116             rc = ERROR_SUCCESS;
117             goto end;
118         }
119
120         /* get properties */
121         sig->File = msi_dup_record_field(row,2);
122         minVersion = msi_dup_record_field(row,3);
123         if (minVersion)
124         {
125             ACTION_VerStrToInteger(minVersion, &sig->MinVersionMS,
126              &sig->MinVersionLS);
127             msi_free( minVersion);
128         }
129         maxVersion = msi_dup_record_field(row,4);
130         if (maxVersion)
131         {
132             ACTION_VerStrToInteger(maxVersion, &sig->MaxVersionMS,
133              &sig->MaxVersionLS);
134             msi_free( maxVersion);
135         }
136         sig->MinSize = MSI_RecordGetInteger(row,5);
137         if (sig->MinSize == MSI_NULL_INTEGER)
138             sig->MinSize = 0;
139         sig->MaxSize = MSI_RecordGetInteger(row,6);
140         if (sig->MaxSize == MSI_NULL_INTEGER)
141             sig->MaxSize = 0;
142         sig->Languages = msi_dup_record_field(row,9);
143         time = MSI_RecordGetInteger(row,7);
144         if (time != MSI_NULL_INTEGER)
145             DosDateTimeToFileTime(HIWORD(time), LOWORD(time), &sig->MinTime);
146         time = MSI_RecordGetInteger(row,8);
147         if (time != MSI_NULL_INTEGER)
148             DosDateTimeToFileTime(HIWORD(time), LOWORD(time), &sig->MaxTime);
149         TRACE("Found file name %s for Signature_ %s;\n",
150          debugstr_w(sig->File), debugstr_w(name));
151         TRACE("MinVersion is %d.%d.%d.%d\n", HIWORD(sig->MinVersionMS),
152          LOWORD(sig->MinVersionMS), HIWORD(sig->MinVersionLS),
153          LOWORD(sig->MinVersionLS));
154         TRACE("MaxVersion is %d.%d.%d.%d\n", HIWORD(sig->MaxVersionMS),
155          LOWORD(sig->MaxVersionMS), HIWORD(sig->MaxVersionLS),
156          LOWORD(sig->MaxVersionLS));
157         TRACE("MinSize is %ld, MaxSize is %ld;\n", sig->MinSize, sig->MaxSize);
158         TRACE("Languages is %s\n", debugstr_w(sig->Languages));
159
160 end:
161         if (row)
162             msiobj_release(&row->hdr);
163         MSI_ViewClose(view);
164         msiobj_release(&view->hdr);
165     }
166     else
167     {
168         TRACE("MSI_OpenQuery returned %d\n", rc);
169         rc = ERROR_SUCCESS;
170     }
171
172     TRACE("returning %d\n", rc);
173     return rc;
174 }
175
176 static UINT ACTION_AppSearchComponents(MSIPACKAGE *package, BOOL *appFound,
177  MSISIGNATURE *sig)
178 {
179     MSIQUERY *view;
180     UINT rc;
181     static const WCHAR ExecSeqQuery[] =  {
182    's','e','l','e','c','t',' ','*',' ',
183    'f','r','o','m',' ',
184    'C','o','m','p','L','o','c','a','t','o','r',' ',
185    'w','h','e','r','e',' ','S','i','g','n','a','t','u','r','e','_',' ','=',' ',
186    '\'','%','s','\'',0};
187
188     TRACE("(package %p, appFound %p, sig %p)\n", package, appFound, sig);
189     *appFound = FALSE;
190     rc = MSI_OpenQuery(package->db, &view, ExecSeqQuery, sig->Name);
191     if (rc == ERROR_SUCCESS)
192     {
193         MSIRECORD *row = 0;
194         WCHAR guid[50];
195         DWORD sz;
196
197         rc = MSI_ViewExecute(view, 0);
198         if (rc != ERROR_SUCCESS)
199         {
200             TRACE("MSI_ViewExecute returned %d\n", rc);
201             goto end;
202         }
203         rc = MSI_ViewFetch(view,&row);
204         if (rc != ERROR_SUCCESS)
205         {
206             TRACE("MSI_ViewFetch returned %d\n", rc);
207             rc = ERROR_SUCCESS;
208             goto end;
209         }
210
211         /* get GUID */
212         guid[0] = 0;
213         sz=sizeof(guid)/sizeof(guid[0]);
214         rc = MSI_RecordGetStringW(row,2,guid,&sz);
215         if (rc != ERROR_SUCCESS)
216         {
217             ERR("Error is %x\n",rc);
218             goto end;
219         }
220         FIXME("AppSearch unimplemented for CompLocator table (GUID %s)\n",
221          debugstr_w(guid));
222
223 end:
224         if (row)
225             msiobj_release(&row->hdr);
226         MSI_ViewClose(view);
227         msiobj_release(&view->hdr);
228     }
229     else
230     {
231         TRACE("MSI_OpenQuery returned %d\n", rc);
232         rc = ERROR_SUCCESS;
233     }
234
235     TRACE("returning %d\n", rc);
236     return rc;
237 }
238
239 static UINT ACTION_AppSearchReg(MSIPACKAGE *package, BOOL *appFound,
240  MSISIGNATURE *sig)
241 {
242     MSIQUERY *view;
243     UINT rc;
244     static const WCHAR ExecSeqQuery[] =  {
245    's','e','l','e','c','t',' ','*',' ',
246    'f','r','o','m',' ',
247    'R','e','g','L','o','c','a','t','o','r',' ',
248    'w','h','e','r','e',' ','S','i','g','n','a','t','u','r','e','_',' ','=',' ',
249    '\'','%','s','\'',0};
250     static const WCHAR dwordFmt[] = { '#','%','d','\0' };
251     static const WCHAR expandSzFmt[] = { '#','%','%','%','s','\0' };
252     static const WCHAR binFmt[] = { '#','x','%','x','\0' };
253
254     TRACE("(package %p, appFound %p, sig %p)\n", package, appFound, sig);
255     *appFound = FALSE;
256     rc = MSI_OpenQuery(package->db, &view, ExecSeqQuery, sig->Name);
257     if (rc == ERROR_SUCCESS)
258     {
259         MSIRECORD *row = 0;
260         LPWSTR keyPath = NULL, valueName = NULL, propertyValue = NULL;
261         int root, type;
262         HKEY rootKey, key = NULL;
263         DWORD sz = 0, regType, i;
264         LPBYTE value = NULL;
265
266         rc = MSI_ViewExecute(view, 0);
267         if (rc != ERROR_SUCCESS)
268         {
269             TRACE("MSI_ViewExecute returned %d\n", rc);
270             goto end;
271         }
272         rc = MSI_ViewFetch(view,&row);
273         if (rc != ERROR_SUCCESS)
274         {
275             TRACE("MSI_ViewFetch returned %d\n", rc);
276             rc = ERROR_SUCCESS;
277             goto end;
278         }
279
280         root = MSI_RecordGetInteger(row,2);
281         keyPath = msi_dup_record_field(row,3);
282         /* FIXME: keyPath needs to be expanded for properties */
283         valueName = msi_dup_record_field(row,4);
284         /* FIXME: valueName probably does too */
285         type = MSI_RecordGetInteger(row,5);
286
287         if ((type & 0x0f) != msidbLocatorTypeRawValue)
288         {
289             FIXME("AppSearch unimplemented for type %d (key path %s, value %s)\n",
290              type, debugstr_w(keyPath), debugstr_w(valueName));
291             goto end;
292         }
293
294         switch (root)
295         {
296             case msidbRegistryRootClassesRoot:
297                 rootKey = HKEY_CLASSES_ROOT;
298                 break;
299             case msidbRegistryRootCurrentUser:
300                 rootKey = HKEY_CURRENT_USER;
301                 break;
302             case msidbRegistryRootLocalMachine:
303                 rootKey = HKEY_LOCAL_MACHINE;
304                 break;
305             case msidbRegistryRootUsers:
306                 rootKey = HKEY_USERS;
307                 break;
308             default:
309                 WARN("Unknown root key %d\n", root);
310                 goto end;
311         }
312
313         rc = RegCreateKeyW(rootKey, keyPath, &key);
314         if (rc)
315         {
316             TRACE("RegCreateKeyW returned %d\n", rc);
317             rc = ERROR_SUCCESS;
318             goto end;
319         }
320         rc = RegQueryValueExW(key, valueName, NULL, NULL, NULL, &sz);
321         if (rc)
322         {
323             TRACE("RegQueryValueExW returned %d\n", rc);
324             rc = ERROR_SUCCESS;
325             goto end;
326         }
327         /* FIXME: sanity-check sz before allocating (is there an upper-limit
328          * on the value of a property?)
329          */
330         value = msi_alloc( sz);
331         rc = RegQueryValueExW(key, valueName, NULL, &regType, value, &sz);
332         if (rc)
333         {
334             TRACE("RegQueryValueExW returned %d\n", rc);
335             rc = ERROR_SUCCESS;
336             goto end;
337         }
338
339         /* bail out if the registry key is empty */
340         if (sz == 0)
341         {
342             rc = ERROR_SUCCESS;
343             goto end;
344         }
345         
346         switch (regType)
347         {
348             case REG_SZ:
349                 if (*(LPWSTR)value == '#')
350                 {
351                     /* escape leading pound with another */
352                     propertyValue = msi_alloc( sz + sizeof(WCHAR));
353                     propertyValue[0] = '#';
354                     strcpyW(propertyValue + 1, (LPWSTR)value);
355                 }
356                 else
357                 {
358                     propertyValue = msi_alloc( sz);
359                     strcpyW(propertyValue, (LPWSTR)value);
360                 }
361                 break;
362             case REG_DWORD:
363                 /* 7 chars for digits, 1 for NULL, 1 for #, and 1 for sign
364                  * char if needed
365                  */
366                 propertyValue = msi_alloc( 10 * sizeof(WCHAR));
367                 sprintfW(propertyValue, dwordFmt, *(DWORD *)value);
368                 break;
369             case REG_EXPAND_SZ:
370                 /* space for extra #% characters in front */
371                 propertyValue = msi_alloc( sz + 2 * sizeof(WCHAR));
372                 sprintfW(propertyValue, expandSzFmt, (LPWSTR)value);
373                 break;
374             case REG_BINARY:
375                 /* 3 == length of "#x<nibble>" */
376                 propertyValue = msi_alloc( (sz * 3 + 1) * sizeof(WCHAR));
377                 for (i = 0; i < sz; i++)
378                     sprintfW(propertyValue + i * 3, binFmt, value[i]);
379                 break;
380             default:
381                 WARN("unimplemented for values of type %ld\n", regType);
382                 goto end;
383         }
384
385         TRACE("found registry value, setting %s to %s\n",
386          debugstr_w(sig->Property), debugstr_w(propertyValue));
387         rc = MSI_SetPropertyW(package, sig->Property, propertyValue);
388         *appFound = TRUE;
389
390 end:
391         msi_free( propertyValue);
392         msi_free( value);
393         RegCloseKey(key);
394
395         msi_free( keyPath);
396         msi_free( valueName);
397
398         if (row)
399             msiobj_release(&row->hdr);
400         MSI_ViewClose(view);
401         msiobj_release(&view->hdr);
402     }
403     else
404     {
405         TRACE("MSI_OpenQuery returned %d\n", rc);
406         rc = ERROR_SUCCESS;
407     }
408
409     TRACE("returning %d\n", rc);
410     return rc;
411 }
412
413 static UINT ACTION_AppSearchIni(MSIPACKAGE *package, BOOL *appFound,
414  MSISIGNATURE *sig)
415 {
416     MSIQUERY *view;
417     UINT rc;
418     static const WCHAR ExecSeqQuery[] =  {
419    's','e','l','e','c','t',' ','*',' ',
420    'f','r','o','m',' ',
421    'I','n','i','L','o','c','a','t','o','r',' ',
422    'w','h','e','r','e',' ','S','i','g','n','a','t','u','r','e','_',' ','=',' ',
423    '\'','%','s','\'',0};
424
425     TRACE("(package %p, appFound %p, sig %p)\n", package, appFound, sig);
426     *appFound = FALSE;
427     rc = MSI_OpenQuery(package->db, &view, ExecSeqQuery, sig->Name);
428     if (rc == ERROR_SUCCESS)
429     {
430         MSIRECORD *row = 0;
431         LPWSTR fileName;
432
433         rc = MSI_ViewExecute(view, 0);
434         if (rc != ERROR_SUCCESS)
435         {
436             TRACE("MSI_ViewExecute returned %d\n", rc);
437             goto end;
438         }
439         rc = MSI_ViewFetch(view,&row);
440         if (rc != ERROR_SUCCESS)
441         {
442             TRACE("MSI_ViewFetch returned %d\n", rc);
443             rc = ERROR_SUCCESS;
444             goto end;
445         }
446
447         /* get file name */
448         fileName = msi_dup_record_field(row,2);
449         FIXME("AppSearch unimplemented for IniLocator (ini file name %s)\n",
450          debugstr_w(fileName));
451         msi_free( fileName);
452
453 end:
454         if (row)
455             msiobj_release(&row->hdr);
456         MSI_ViewClose(view);
457         msiobj_release(&view->hdr);
458     }
459     else
460     {
461         TRACE("MSI_OpenQuery returned %d\n", rc);
462         rc = ERROR_SUCCESS;
463     }
464
465
466     TRACE("returning %d\n", rc);
467     return rc;
468 }
469
470 /* Expands the value in src into a path without property names and only
471  * containing long path names into dst.  Replaces at most len characters of dst,
472  * and always NULL-terminates dst if dst is not NULL and len >= 1.
473  * May modify src.
474  * Assumes src and dst are non-overlapping.
475  * FIXME: return code probably needed:
476  * - what does AppSearch return if the table values are invalid?
477  * - what if dst is too small?
478  */
479 static void ACTION_ExpandAnyPath(MSIPACKAGE *package, WCHAR *src, WCHAR *dst,
480  size_t len)
481 {
482     WCHAR *ptr;
483     size_t copied = 0;
484
485     if (!src || !dst || !len)
486         return;
487
488     /* Ignore the short portion of the path, don't think we can use it anyway */
489     if ((ptr = strchrW(src, '|')))
490         ptr++;
491     else
492         ptr = src;
493     while (*ptr && copied < len - 1)
494     {
495         WCHAR *prop = strchrW(ptr, '[');
496
497         if (prop)
498         {
499             WCHAR *propEnd = strchrW(prop + 1, ']');
500
501             if (!propEnd)
502             {
503                 WARN("Unterminated property name in AnyPath: %s\n",
504                  debugstr_w(prop));
505                 break;
506             }
507             else
508             {
509                 DWORD propLen;
510
511                 *propEnd = 0;
512                 propLen = len - copied - 1;
513                 MSI_GetPropertyW(package, prop + 1, dst + copied, &propLen);
514                 ptr = propEnd + 1;
515                 copied += propLen;
516             }
517         }
518         else
519         {
520             size_t toCopy = min(strlenW(ptr) + 1, len - copied - 1);
521
522             memcpy(dst + copied, ptr, toCopy * sizeof(WCHAR));
523             ptr += toCopy;
524             copied += toCopy;
525         }
526     }
527     *(dst + copied) = '\0';
528 }
529
530 /* Sets *matches to whether the file (whose path is filePath) matches the
531  * versions set in sig.
532  * Return ERROR_SUCCESS in case of success (whether or not the file matches),
533  * something else if an install-halting error occurs.
534  */
535 static UINT ACTION_FileVersionMatches(MSISIGNATURE *sig, LPCWSTR filePath,
536  BOOL *matches)
537 {
538     UINT rc = ERROR_SUCCESS;
539
540     *matches = FALSE;
541     if (sig->Languages)
542     {
543         FIXME(": need to check version for languages %s\n",
544          debugstr_w(sig->Languages));
545     }
546     else
547     {
548         DWORD zero, size = GetFileVersionInfoSizeW(filePath, &zero);
549
550         if (size)
551         {
552             LPVOID buf = msi_alloc( size);
553
554             if (buf)
555             {
556                 static WCHAR rootW[] = { '\\',0 };
557                 UINT versionLen;
558                 LPVOID subBlock = NULL;
559
560                 if (GetFileVersionInfoW(filePath, 0, size, buf))
561                     VerQueryValueW(buf, rootW, &subBlock, &versionLen);
562                 if (subBlock)
563                 {
564                     VS_FIXEDFILEINFO *info =
565                      (VS_FIXEDFILEINFO *)subBlock;
566
567                     TRACE("Comparing file version %d.%d.%d.%d:\n",
568                      HIWORD(info->dwFileVersionMS),
569                      LOWORD(info->dwFileVersionMS),
570                      HIWORD(info->dwFileVersionLS),
571                      LOWORD(info->dwFileVersionLS));
572                     if (info->dwFileVersionMS < sig->MinVersionMS
573                      || (info->dwFileVersionMS == sig->MinVersionMS &&
574                      info->dwFileVersionLS < sig->MinVersionLS))
575                     {
576                         TRACE("Less than minimum version %d.%d.%d.%d\n",
577                          HIWORD(sig->MinVersionMS),
578                          LOWORD(sig->MinVersionMS),
579                          HIWORD(sig->MinVersionLS),
580                          LOWORD(sig->MinVersionLS));
581                     }
582                     else if (info->dwFileVersionMS < sig->MinVersionMS
583                      || (info->dwFileVersionMS == sig->MinVersionMS &&
584                      info->dwFileVersionLS < sig->MinVersionLS))
585                     {
586                         TRACE("Greater than minimum version %d.%d.%d.%d\n",
587                          HIWORD(sig->MaxVersionMS),
588                          LOWORD(sig->MaxVersionMS),
589                          HIWORD(sig->MaxVersionLS),
590                          LOWORD(sig->MaxVersionLS));
591                     }
592                     else
593                         *matches = TRUE;
594                 }
595                 msi_free( buf);
596             }
597             else
598                 rc = ERROR_OUTOFMEMORY;
599         }
600     }
601     return rc;
602 }
603
604 /* Sets *matches to whether the file in findData matches that in sig.
605  * fullFilePath is assumed to be the full path of the file specified in
606  * findData, which may be necessary to compare the version.
607  * Return ERROR_SUCCESS in case of success (whether or not the file matches),
608  * something else if an install-halting error occurs.
609  */
610 static UINT ACTION_FileMatchesSig(MSISIGNATURE *sig,
611  LPWIN32_FIND_DATAW findData, LPCWSTR fullFilePath, BOOL *matches)
612 {
613     UINT rc = ERROR_SUCCESS;
614
615     *matches = TRUE;
616     /* assumes the caller has already ensured the filenames match, so check
617      * the other fields..
618      */
619     if (sig->MinTime.dwLowDateTime || sig->MinTime.dwHighDateTime)
620     {
621         if (findData->ftCreationTime.dwHighDateTime <
622          sig->MinTime.dwHighDateTime ||
623          (findData->ftCreationTime.dwHighDateTime == sig->MinTime.dwHighDateTime
624          && findData->ftCreationTime.dwLowDateTime <
625          sig->MinTime.dwLowDateTime))
626             *matches = FALSE;
627     }
628     if (*matches && (sig->MaxTime.dwLowDateTime || sig->MaxTime.dwHighDateTime))
629     {
630         if (findData->ftCreationTime.dwHighDateTime >
631          sig->MaxTime.dwHighDateTime ||
632          (findData->ftCreationTime.dwHighDateTime == sig->MaxTime.dwHighDateTime
633          && findData->ftCreationTime.dwLowDateTime >
634          sig->MaxTime.dwLowDateTime))
635             *matches = FALSE;
636     }
637     if (*matches && sig->MinSize && findData->nFileSizeLow < sig->MinSize)
638         *matches = FALSE;
639     if (*matches && sig->MaxSize && findData->nFileSizeLow > sig->MaxSize)
640         *matches = FALSE;
641     if (*matches && (sig->MinVersionMS || sig->MinVersionLS ||
642      sig->MaxVersionMS || sig->MaxVersionLS))
643         rc = ACTION_FileVersionMatches(sig, fullFilePath, matches);
644     return rc;
645 }
646
647 /* Recursively searches the directory dir for files that match the signature
648  * sig, up to (depth + 1) levels deep.  That is, if depth is 0, it searches dir
649  * (and only dir).  If depth is 1, searches dir and its immediate
650  * subdirectories.
651  * Assumes sig->File is not NULL.
652  * Returns ERROR_SUCCESS on success (which may include non-critical errors),
653  * something else on failures which should halt the install.
654  */
655 static UINT ACTION_RecurseSearchDirectory(MSIPACKAGE *package, BOOL *appFound,
656  MSISIGNATURE *sig, LPCWSTR dir, int depth)
657 {
658     static const WCHAR starDotStarW[] = { '*','.','*',0 };
659     UINT rc = ERROR_SUCCESS;
660     size_t dirLen = lstrlenW(dir), fileLen = lstrlenW(sig->File);
661     WCHAR *buf;
662
663     TRACE("Searching directory %s for file %s, depth %d\n", debugstr_w(dir),
664      debugstr_w(sig->File), depth);
665
666     if (depth < 0)
667         return ERROR_INVALID_PARAMETER;
668
669     *appFound = FALSE;
670     /* We need the buffer in both paths below, so go ahead and allocate it
671      * here.  Add two because we might need to add a backslash if the dir name
672      * isn't backslash-terminated.
673      */
674     buf = msi_alloc( (dirLen + max(fileLen, lstrlenW(starDotStarW)) + 2) * sizeof(WCHAR));
675     if (buf)
676     {
677         /* a depth of 0 implies we should search dir, so go ahead and search */
678         HANDLE hFind;
679         WIN32_FIND_DATAW findData;
680
681         memcpy(buf, dir, dirLen * sizeof(WCHAR));
682         if (buf[dirLen - 1] != '\\')
683             buf[dirLen++ - 1] = '\\';
684         memcpy(buf + dirLen, sig->File, (fileLen + 1) * sizeof(WCHAR));
685         hFind = FindFirstFileW(buf, &findData);
686         if (hFind != INVALID_HANDLE_VALUE)
687         {
688             BOOL matches;
689
690             /* assuming Signature can't contain wildcards for the file name,
691              * so don't bother with FindNextFileW here.
692              */
693             if (!(rc = ACTION_FileMatchesSig(sig, &findData, buf, &matches))
694              && matches)
695             {
696                 TRACE("found file, setting %s to %s\n",
697                  debugstr_w(sig->Property), debugstr_w(buf));
698                 rc = MSI_SetPropertyW(package, sig->Property, buf);
699                 *appFound = TRUE;
700             }
701             FindClose(hFind);
702         }
703         if (rc == ERROR_SUCCESS && !*appFound && depth > 0)
704         {
705             HANDLE hFind;
706             WIN32_FIND_DATAW findData;
707
708             memcpy(buf, dir, dirLen * sizeof(WCHAR));
709             if (buf[dirLen - 1] != '\\')
710                 buf[dirLen++ - 1] = '\\';
711             lstrcpyW(buf + dirLen, starDotStarW);
712             hFind = FindFirstFileW(buf, &findData);
713             if (hFind != INVALID_HANDLE_VALUE)
714             {
715                 if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
716                     rc = ACTION_RecurseSearchDirectory(package, appFound,
717                      sig, findData.cFileName, depth - 1);
718                 while (rc == ERROR_SUCCESS && !*appFound &&
719                  FindNextFileW(hFind, &findData) != 0)
720                 {
721                     if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
722                         rc = ACTION_RecurseSearchDirectory(package, appFound,
723                          sig, findData.cFileName, depth - 1);
724                 }
725                 FindClose(hFind);
726             }
727         }
728         msi_free(buf);
729     }
730     else
731         rc = ERROR_OUTOFMEMORY;
732
733     return rc;
734 }
735
736 static UINT ACTION_CheckDirectory(MSIPACKAGE *package, MSISIGNATURE *sig,
737  LPCWSTR dir)
738 {
739     UINT rc = ERROR_SUCCESS;
740
741     if (GetFileAttributesW(dir) & FILE_ATTRIBUTE_DIRECTORY)
742     {
743         TRACE("directory exists, setting %s to %s\n",
744          debugstr_w(sig->Property), debugstr_w(dir));
745         rc = MSI_SetPropertyW(package, sig->Property, dir);
746     }
747     return rc;
748 }
749
750 static BOOL ACTION_IsFullPath(LPCWSTR path)
751 {
752     WCHAR first = toupperW(path[0]);
753     BOOL ret;
754
755     if (first >= 'A' && first <= 'Z' && path[1] == ':')
756         ret = TRUE;
757     else if (path[0] == '\\' && path[1] == '\\')
758         ret = TRUE;
759     else
760         ret = FALSE;
761     return ret;
762 }
763
764 static UINT ACTION_SearchDirectory(MSIPACKAGE *package, MSISIGNATURE *sig,
765  LPCWSTR expanded, int depth)
766 {
767     UINT rc;
768     BOOL found;
769
770     TRACE("%p, %p, %s, %d\n", package, sig, debugstr_w(expanded), depth);
771     if (ACTION_IsFullPath(expanded))
772     {
773         if (sig->File)
774             rc = ACTION_RecurseSearchDirectory(package, &found, sig,
775              expanded, depth);
776         else
777         {
778             /* Recursively searching a directory makes no sense when the
779              * directory to search is the thing you're trying to find.
780              */
781             rc = ACTION_CheckDirectory(package, sig, expanded);
782         }
783     }
784     else
785     {
786         WCHAR pathWithDrive[MAX_PATH] = { 'C',':','\\',0 };
787         DWORD drives = GetLogicalDrives();
788         int i;
789
790         rc = ERROR_SUCCESS;
791         found = FALSE;
792         for (i = 0; rc == ERROR_SUCCESS && !found && i < 26; i++)
793             if (drives & (1 << drives))
794             {
795                 pathWithDrive[0] = 'A' + i;
796                 if (GetDriveTypeW(pathWithDrive) == DRIVE_FIXED)
797                 {
798                     lstrcpynW(pathWithDrive + 3, expanded,
799                               sizeof(pathWithDrive) / sizeof(pathWithDrive[0]) - 3);
800                     if (sig->File)
801                         rc = ACTION_RecurseSearchDirectory(package, &found, sig,
802                          pathWithDrive, depth);
803                     else
804                         rc = ACTION_CheckDirectory(package, sig, pathWithDrive);
805                 }
806             }
807     }
808     TRACE("returning %d\n", rc);
809     return rc;
810 }
811
812 static UINT ACTION_AppSearchDr(MSIPACKAGE *package, MSISIGNATURE *sig)
813 {
814     MSIQUERY *view;
815     UINT rc;
816     static const WCHAR ExecSeqQuery[] =  {
817    's','e','l','e','c','t',' ','*',' ',
818    'f','r','o','m',' ',
819    'D','r','L','o','c','a','t','o','r',' ',
820    'w','h','e','r','e',' ','S','i','g','n','a','t','u','r','e','_',' ','=',' ',
821    '\'','%','s','\'',0};
822
823     TRACE("(package %p, sig %p)\n", package, sig);
824     rc = MSI_OpenQuery(package->db, &view, ExecSeqQuery, sig->Name);
825     if (rc == ERROR_SUCCESS)
826     {
827         MSIRECORD *row = 0;
828         WCHAR buffer[MAX_PATH], expanded[MAX_PATH];
829         DWORD sz;
830         int depth;
831
832         rc = MSI_ViewExecute(view, 0);
833         if (rc != ERROR_SUCCESS)
834         {
835             TRACE("MSI_ViewExecute returned %d\n", rc);
836             goto end;
837         }
838         rc = MSI_ViewFetch(view,&row);
839         if (rc != ERROR_SUCCESS)
840         {
841             TRACE("MSI_ViewFetch returned %d\n", rc);
842             rc = ERROR_SUCCESS;
843             goto end;
844         }
845
846         /* check whether parent is set */
847         buffer[0] = 0;
848         sz=sizeof(buffer)/sizeof(buffer[0]);
849         rc = MSI_RecordGetStringW(row,2,buffer,&sz);
850         if (rc != ERROR_SUCCESS)
851         {
852             ERR("Error is %x\n",rc);
853             goto end;
854         }
855         else if (buffer[0])
856         {
857             FIXME(": searching parent (%s) unimplemented\n",
858              debugstr_w(buffer));
859             goto end;
860         }
861         /* no parent, now look for path */
862         buffer[0] = 0;
863         sz=sizeof(buffer)/sizeof(buffer[0]);
864         rc = MSI_RecordGetStringW(row,3,buffer,&sz);
865         if (rc != ERROR_SUCCESS)
866         {
867             ERR("Error is %x\n",rc);
868             goto end;
869         }
870         if (MSI_RecordIsNull(row,4))
871             depth = 0;
872         else
873             depth = MSI_RecordGetInteger(row,4);
874         ACTION_ExpandAnyPath(package, buffer, expanded,
875          sizeof(expanded) / sizeof(expanded[0]));
876         rc = ACTION_SearchDirectory(package, sig, expanded, depth);
877
878 end:
879         if (row)
880             msiobj_release(&row->hdr);
881         MSI_ViewClose(view);
882         msiobj_release(&view->hdr);
883     }
884     else
885     {
886         TRACE("MSI_OpenQuery returned %d\n", rc);
887         rc = ERROR_SUCCESS;
888     }
889
890
891     TRACE("returning %d\n", rc);
892     return rc;
893 }
894
895 /* http://msdn.microsoft.com/library/en-us/msi/setup/appsearch_table.asp
896  * is the best reference for the AppSearch table and how it's used.
897  */
898 UINT ACTION_AppSearch(MSIPACKAGE *package)
899 {
900     MSIQUERY *view;
901     UINT rc;
902     static const WCHAR ExecSeqQuery[] =  {
903    's','e','l','e','c','t',' ','*',' ',
904    'f','r','o','m',' ',
905    'A','p','p','S','e','a','r','c','h',0};
906
907     rc = MSI_OpenQuery(package->db, &view, ExecSeqQuery);
908     if (rc == ERROR_SUCCESS)
909     {
910         MSIRECORD *row = 0;
911         WCHAR propBuf[0x100], sigBuf[0x100];
912         DWORD sz;
913         MSISIGNATURE sig;
914         BOOL appFound = FALSE;
915
916         rc = MSI_ViewExecute(view, 0);
917         if (rc != ERROR_SUCCESS)
918             goto end;
919
920         while (!rc)
921         {
922             rc = MSI_ViewFetch(view,&row);
923             if (rc != ERROR_SUCCESS)
924             {
925                 rc = ERROR_SUCCESS;
926                 break;
927             }
928
929             /* get property and signature */
930             propBuf[0] = 0;
931             sz=sizeof(propBuf)/sizeof(propBuf[0]);
932             rc = MSI_RecordGetStringW(row,1,propBuf,&sz);
933             if (rc != ERROR_SUCCESS)
934             {
935                 ERR("Error is %x\n",rc);
936                 msiobj_release(&row->hdr);
937                 break;
938             }
939             sigBuf[0] = 0;
940             sz=sizeof(sigBuf)/sizeof(sigBuf[0]);
941             rc = MSI_RecordGetStringW(row,2,sigBuf,&sz);
942             if (rc != ERROR_SUCCESS)
943             {
944                 ERR("Error is %x\n",rc);
945                 msiobj_release(&row->hdr);
946                 break;
947             }
948             TRACE("Searching for Property %s, Signature_ %s\n",
949              debugstr_w(propBuf), debugstr_w(sigBuf));
950             /* This clears all the fields, so set Name and Property afterward */
951             rc = ACTION_AppSearchGetSignature(package, &sig, sigBuf);
952             sig.Name = sigBuf;
953             sig.Property = propBuf;
954             if (rc == ERROR_SUCCESS)
955             {
956                 rc = ACTION_AppSearchComponents(package, &appFound, &sig);
957                 if (rc == ERROR_SUCCESS && !appFound)
958                 {
959                     rc = ACTION_AppSearchReg(package, &appFound, &sig);
960                     if (rc == ERROR_SUCCESS && !appFound)
961                     {
962                         rc = ACTION_AppSearchIni(package, &appFound, &sig);
963                         if (rc == ERROR_SUCCESS && !appFound)
964                             rc = ACTION_AppSearchDr(package, &sig);
965                     }
966                 }
967             }
968             msi_free( sig.File);
969             msi_free( sig.Languages);
970             msiobj_release(&row->hdr);
971         }
972
973 end:
974         MSI_ViewClose(view);
975         msiobj_release(&view->hdr);
976     }
977     else
978         rc = ERROR_SUCCESS;
979
980     return rc;
981 }