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