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