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