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