Fix a regression in AppSearch.
[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 "msi.h"
27 #include "msiquery.h"
28 #include "winver.h"
29 #include "wine/unicode.h"
30 #include "wine/debug.h"
31 #include "msipriv.h"
32 #include "action.h"
33
34 WINE_DEFAULT_DEBUG_CHANNEL(msi);
35
36 typedef struct tagMSISIGNATURE
37 {
38     LPWSTR   Name;     /* NOT owned by this structure */
39     LPWSTR   Property; /* 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     rc = MSI_OpenQuery(package->db, &view, ExecSeqQuery, name);
98     if (rc == ERROR_SUCCESS)
99     {
100         MSIRECORD *row = 0;
101         DWORD time;
102         WCHAR *minVersion, *maxVersion;
103
104         rc = MSI_ViewExecute(view, 0);
105         if (rc != ERROR_SUCCESS)
106         {
107             TRACE("MSI_ViewExecute returned %d\n", rc);
108             goto end;
109         }
110         rc = MSI_ViewFetch(view,&row);
111         if (rc != ERROR_SUCCESS)
112         {
113             TRACE("MSI_ViewFetch returned %d\n", rc);
114             rc = ERROR_SUCCESS;
115             goto end;
116         }
117
118         /* get properties */
119         sig->File = load_dynamic_stringW(row,2);
120         minVersion = load_dynamic_stringW(row,3);
121         if (minVersion)
122         {
123             ACTION_VerStrToInteger(minVersion, &sig->MinVersionMS,
124              &sig->MinVersionLS);
125             HeapFree(GetProcessHeap(), 0, minVersion);
126         }
127         maxVersion = load_dynamic_stringW(row,4);
128         if (maxVersion)
129         {
130             ACTION_VerStrToInteger(maxVersion, &sig->MaxVersionMS,
131              &sig->MaxVersionLS);
132             HeapFree(GetProcessHeap(), 0, maxVersion);
133         }
134         sig->MinSize = MSI_RecordGetInteger(row,5);
135         if (sig->MinSize == MSI_NULL_INTEGER)
136             sig->MinSize = 0;
137         sig->MaxSize = MSI_RecordGetInteger(row,6);
138         if (sig->MaxSize == MSI_NULL_INTEGER)
139             sig->MaxSize = 0;
140         sig->Languages = load_dynamic_stringW(row,9);
141         time = MSI_RecordGetInteger(row,7);
142         if (time != MSI_NULL_INTEGER)
143             DosDateTimeToFileTime(HIWORD(time), LOWORD(time), &sig->MinTime);
144         time = MSI_RecordGetInteger(row,8);
145         if (time != MSI_NULL_INTEGER)
146             DosDateTimeToFileTime(HIWORD(time), LOWORD(time), &sig->MaxTime);
147         TRACE("Found file name %s for Signature_ %s;\n",
148          debugstr_w(sig->File), debugstr_w(name));
149         TRACE("MinVersion is %d.%d.%d.%d\n", HIWORD(sig->MinVersionMS),
150          LOWORD(sig->MinVersionMS), HIWORD(sig->MinVersionLS),
151          LOWORD(sig->MinVersionLS));
152         TRACE("MaxVersion is %d.%d.%d.%d\n", HIWORD(sig->MaxVersionMS),
153          LOWORD(sig->MaxVersionMS), HIWORD(sig->MaxVersionLS),
154          LOWORD(sig->MaxVersionLS));
155         TRACE("MinSize is %ld, MaxSize is %ld;\n", sig->MinSize, sig->MaxSize);
156         TRACE("Languages is %s\n", debugstr_w(sig->Languages));
157
158 end:
159         msiobj_release(&row->hdr);
160         MSI_ViewClose(view);
161         msiobj_release(&view->hdr);
162     }
163     else
164     {
165         TRACE("MSI_OpenQuery returned %d\n", rc);
166         rc = ERROR_SUCCESS;
167     }
168
169     TRACE("returning %d\n", rc);
170     return rc;
171 }
172
173 static UINT ACTION_AppSearchComponents(MSIPACKAGE *package, BOOL *appFound,
174  MSISIGNATURE *sig)
175 {
176     MSIQUERY *view;
177     UINT rc;
178     static const WCHAR ExecSeqQuery[] =  {
179    's','e','l','e','c','t',' ','*',' ',
180    'f','r','o','m',' ',
181    'C','o','m','p','L','o','c','a','t','o','r',' ',
182    'w','h','e','r','e',' ','S','i','g','n','a','t','u','r','e','_',' ','=',' ',
183    '\'','%','s','\'',0};
184
185     TRACE("(package %p, appFound %p, sig %p)\n", package, appFound, sig);
186     *appFound = FALSE;
187     rc = MSI_OpenQuery(package->db, &view, ExecSeqQuery, sig->Name);
188     if (rc == ERROR_SUCCESS)
189     {
190         MSIRECORD *row = 0;
191         WCHAR guid[50];
192         DWORD sz;
193
194         rc = MSI_ViewExecute(view, 0);
195         if (rc != ERROR_SUCCESS)
196         {
197             TRACE("MSI_ViewExecute returned %d\n", rc);
198             goto end;
199         }
200         rc = MSI_ViewFetch(view,&row);
201         if (rc != ERROR_SUCCESS)
202         {
203             TRACE("MSI_ViewFetch returned %d\n", rc);
204             rc = ERROR_SUCCESS;
205             goto end;
206         }
207
208         /* get GUID */
209         guid[0] = 0;
210         sz=sizeof(guid)/sizeof(guid[0]);
211         rc = MSI_RecordGetStringW(row,2,guid,&sz);
212         if (rc != ERROR_SUCCESS)
213         {
214             ERR("Error is %x\n",rc);
215             goto end;
216         }
217         FIXME("AppSearch unimplemented for CompLocator table (GUID %s)\n",
218          debugstr_w(guid));
219
220 end:
221         msiobj_release(&row->hdr);
222         MSI_ViewClose(view);
223         msiobj_release(&view->hdr);
224     }
225     else
226     {
227         TRACE("MSI_OpenQuery returned %d\n", rc);
228         rc = ERROR_SUCCESS;
229     }
230
231     TRACE("returning %d\n", rc);
232     return rc;
233 }
234
235 static UINT ACTION_AppSearchReg(MSIPACKAGE *package, BOOL *appFound,
236  MSISIGNATURE *sig)
237 {
238     MSIQUERY *view;
239     UINT rc;
240     static const WCHAR ExecSeqQuery[] =  {
241    's','e','l','e','c','t',' ','*',' ',
242    'f','r','o','m',' ',
243    'R','e','g','L','o','c','a','t','o','r',' ',
244    'w','h','e','r','e',' ','S','i','g','n','a','t','u','r','e','_',' ','=',' ',
245    '\'','%','s','\'',0};
246
247     TRACE("(package %p, appFound %p, sig %p)\n", package, appFound, sig);
248     *appFound = FALSE;
249     rc = MSI_OpenQuery(package->db, &view, ExecSeqQuery, sig->Name);
250     if (rc == ERROR_SUCCESS)
251     {
252         MSIRECORD *row = 0;
253         LPWSTR keyPath;
254
255         rc = MSI_ViewExecute(view, 0);
256         if (rc != ERROR_SUCCESS)
257         {
258             TRACE("MSI_ViewExecute returned %d\n", rc);
259             goto end;
260         }
261         rc = MSI_ViewFetch(view,&row);
262         if (rc != ERROR_SUCCESS)
263         {
264             TRACE("MSI_ViewFetch returned %d\n", rc);
265             rc = ERROR_SUCCESS;
266             goto end;
267         }
268
269         /* get key path */
270         keyPath = load_dynamic_stringW(row,3);
271         FIXME("AppSearch unimplemented for RegLocator (key path %s)\n",
272          debugstr_w(keyPath));
273         HeapFree(GetProcessHeap(), 0, keyPath);
274
275 end:
276         msiobj_release(&row->hdr);
277         MSI_ViewClose(view);
278         msiobj_release(&view->hdr);
279     }
280     else
281     {
282         TRACE("MSI_OpenQuery returned %d\n", rc);
283         rc = ERROR_SUCCESS;
284     }
285
286     TRACE("returning %d\n", rc);
287     return rc;
288 }
289
290 static UINT ACTION_AppSearchIni(MSIPACKAGE *package, BOOL *appFound,
291  MSISIGNATURE *sig)
292 {
293     MSIQUERY *view;
294     UINT rc;
295     static const WCHAR ExecSeqQuery[] =  {
296    's','e','l','e','c','t',' ','*',' ',
297    'f','r','o','m',' ',
298    'I','n','i','L','o','c','a','t','o','r',' ',
299    'w','h','e','r','e',' ','S','i','g','n','a','t','u','r','e','_',' ','=',' ',
300    '\'','%','s','\'',0};
301
302     TRACE("(package %p, appFound %p, sig %p)\n", package, appFound, sig);
303     *appFound = FALSE;
304     rc = MSI_OpenQuery(package->db, &view, ExecSeqQuery, sig->Name);
305     if (rc == ERROR_SUCCESS)
306     {
307         MSIRECORD *row = 0;
308         LPWSTR fileName;
309
310         rc = MSI_ViewExecute(view, 0);
311         if (rc != ERROR_SUCCESS)
312         {
313             TRACE("MSI_ViewExecute returned %d\n", rc);
314             goto end;
315         }
316         rc = MSI_ViewFetch(view,&row);
317         if (rc != ERROR_SUCCESS)
318         {
319             TRACE("MSI_ViewFetch returned %d\n", rc);
320             rc = ERROR_SUCCESS;
321             goto end;
322         }
323
324         /* get file name */
325         fileName = load_dynamic_stringW(row,2);
326         FIXME("AppSearch unimplemented for IniLocator (ini file name %s)\n",
327          debugstr_w(fileName));
328         HeapFree(GetProcessHeap(), 0, fileName);
329
330 end:
331         msiobj_release(&row->hdr);
332         MSI_ViewClose(view);
333         msiobj_release(&view->hdr);
334     }
335     else
336     {
337         TRACE("MSI_OpenQuery returned %d\n", rc);
338         rc = ERROR_SUCCESS;
339     }
340
341
342     TRACE("returning %d\n", rc);
343     return rc;
344 }
345
346 /* Expands the value in src into a path without property names and only
347  * containing long path names into dst.  Replaces at most len characters of dst,
348  * and always NULL-terminates dst if dst is not NULL and len >= 1.
349  * May modify src.
350  * Assumes src and dst are non-overlapping.
351  * FIXME: return code probably needed:
352  * - what does AppSearch return if the table values are invalid?
353  * - what if dst is too small?
354  */
355 static void ACTION_ExpandAnyPath(MSIPACKAGE *package, WCHAR *src, WCHAR *dst,
356  size_t len)
357 {
358     WCHAR *ptr;
359     size_t copied = 0;
360
361     if (!src || !dst || !len)
362         return;
363
364     /* Ignore the short portion of the path, don't think we can use it anyway */
365     if ((ptr = strchrW(src, '|')))
366         ptr++;
367     else
368         ptr = src;
369     while (*ptr && copied < len - 1)
370     {
371         WCHAR *prop = strchrW(ptr, '[');
372
373         if (prop)
374         {
375             WCHAR *propEnd = strchrW(prop + 1, ']');
376
377             if (!propEnd)
378             {
379                 WARN("Unterminated property name in AnyPath: %s\n",
380                  debugstr_w(prop));
381                 break;
382             }
383             else
384             {
385                 DWORD propLen;
386
387                 *propEnd = 0;
388                 propLen = len - copied - 1;
389                 MSI_GetPropertyW(package, prop + 1, dst + copied, &propLen);
390                 ptr = propEnd + 1;
391                 copied += propLen;
392             }
393         }
394         else
395         {
396             size_t toCopy = min(strlenW(ptr) + 1, len - copied - 1);
397
398             memcpy(dst + copied, ptr, toCopy * sizeof(WCHAR));
399             ptr += toCopy;
400             copied += toCopy;
401         }
402     }
403     *(dst + copied) = '\0';
404 }
405
406 /* Sets *matches to whether the file (whose path is filePath) matches the
407  * versions set in sig.
408  * Return ERROR_SUCCESS in case of success (whether or not the file matches),
409  * something else if a install-halting error occurs.
410  */
411 static UINT ACTION_FileVersionMatches(MSISIGNATURE *sig, LPCWSTR filePath,
412  BOOL *matches)
413 {
414     UINT rc = ERROR_SUCCESS;
415
416     *matches = FALSE;
417     if (sig->Languages)
418     {
419         FIXME(": need to check version for languages %s\n",
420          debugstr_w(sig->Languages));
421     }
422     else
423     {
424         DWORD zero, size = GetFileVersionInfoSizeW(filePath, &zero);
425
426         if (size)
427         {
428             LPVOID buf = HeapAlloc(GetProcessHeap(), 0, size);
429
430             if (buf)
431             {
432                 static const WCHAR rootW[] = { '\\',0 };
433                 UINT versionLen;
434                 LPVOID subBlock = NULL;
435
436                 if (GetFileVersionInfoW(filePath, 0, size, buf))
437                     VerQueryValueW(buf, rootW, &subBlock, &versionLen);
438                 if (subBlock)
439                 {
440                     VS_FIXEDFILEINFO *info =
441                      (VS_FIXEDFILEINFO *)subBlock;
442
443                     TRACE("Comparing file version %d.%d.%d.%d:\n",
444                      HIWORD(info->dwFileVersionMS),
445                      LOWORD(info->dwFileVersionMS),
446                      HIWORD(info->dwFileVersionLS),
447                      LOWORD(info->dwFileVersionLS));
448                     if (info->dwFileVersionMS < sig->MinVersionMS
449                      || (info->dwFileVersionMS == sig->MinVersionMS &&
450                      info->dwFileVersionLS < sig->MinVersionLS))
451                     {
452                         TRACE("Less than minimum version %d.%d.%d.%d\n",
453                          HIWORD(sig->MinVersionMS),
454                          LOWORD(sig->MinVersionMS),
455                          HIWORD(sig->MinVersionLS),
456                          LOWORD(sig->MinVersionLS));
457                     }
458                     else if (info->dwFileVersionMS < sig->MinVersionMS
459                      || (info->dwFileVersionMS == sig->MinVersionMS &&
460                      info->dwFileVersionLS < sig->MinVersionLS))
461                     {
462                         TRACE("Greater than minimum version %d.%d.%d.%d\n",
463                          HIWORD(sig->MaxVersionMS),
464                          LOWORD(sig->MaxVersionMS),
465                          HIWORD(sig->MaxVersionLS),
466                          LOWORD(sig->MaxVersionLS));
467                     }
468                     else
469                         *matches = TRUE;
470                 }
471                 HeapFree(GetProcessHeap(), 0, buf);
472             }
473             else
474                 rc = ERROR_OUTOFMEMORY;
475         }
476     }
477     return rc;
478 }
479
480 /* Sets *matches to whether the file in findData matches that in sig.
481  * fullFilePath is assumed to be the full path of the file specified in
482  * findData, which may be necessary to compare the version.
483  * Return ERROR_SUCCESS in case of success (whether or not the file matches),
484  * something else if a install-halting error occurs.
485  */
486 static UINT ACTION_FileMatchesSig(MSISIGNATURE *sig,
487  LPWIN32_FIND_DATAW findData, LPCWSTR fullFilePath, BOOL *matches)
488 {
489     UINT rc = ERROR_SUCCESS;
490
491     *matches = TRUE;
492     /* assumes the caller has already ensured the filenames match, so check
493      * the other fields..
494      */
495     if (sig->MinTime.dwLowDateTime || sig->MinTime.dwHighDateTime)
496     {
497         if (findData->ftCreationTime.dwHighDateTime <
498          sig->MinTime.dwHighDateTime ||
499          (findData->ftCreationTime.dwHighDateTime == sig->MinTime.dwHighDateTime
500          && findData->ftCreationTime.dwLowDateTime <
501          sig->MinTime.dwLowDateTime))
502             *matches = FALSE;
503     }
504     if (*matches && (sig->MaxTime.dwLowDateTime || sig->MaxTime.dwHighDateTime))
505     {
506         if (findData->ftCreationTime.dwHighDateTime >
507          sig->MaxTime.dwHighDateTime ||
508          (findData->ftCreationTime.dwHighDateTime == sig->MaxTime.dwHighDateTime
509          && findData->ftCreationTime.dwLowDateTime >
510          sig->MaxTime.dwLowDateTime))
511             *matches = FALSE;
512     }
513     if (*matches && sig->MinSize && findData->nFileSizeLow < sig->MinSize)
514         *matches = FALSE;
515     if (*matches && sig->MaxSize && findData->nFileSizeLow > sig->MaxSize)
516         *matches = FALSE;
517     if (*matches && (sig->MinVersionMS || sig->MinVersionLS ||
518      sig->MaxVersionMS || sig->MaxVersionLS))
519         rc = ACTION_FileVersionMatches(sig, fullFilePath, matches);
520     return rc;
521 }
522
523 /* Recursively searches the directory dir for files that match the signature
524  * sig, up to (depth + 1) levels deep.  That is, if depth is 0, it searches dir
525  * (and only dir).  If depth is 1, searches dir and its immediate
526  * subdirectories.
527  * Assumes sig->File is not NULL.
528  * Returns ERROR_SUCCESS on success (which may include non-critical errors),
529  * something else on failures which should halt the install.
530  */
531 static UINT ACTION_RecurseSearchDirectory(MSIPACKAGE *package, BOOL *appFound,
532  MSISIGNATURE *sig, LPCWSTR dir, int depth)
533 {
534     static const WCHAR starDotStarW[] = { '*','.','*',0 };
535     UINT rc = ERROR_SUCCESS;
536     size_t dirLen = lstrlenW(dir), fileLen = lstrlenW(sig->File);
537     WCHAR *buf;
538
539     TRACE("Searching directory %s for file %s, depth %d\n", debugstr_w(dir),
540      debugstr_w(sig->File), depth);
541
542     if (depth < 0)
543         return ERROR_INVALID_PARAMETER;
544
545     *appFound = FALSE;
546     /* We need the buffer in both paths below, so go ahead and allocate it
547      * here.  Add two because we might need to add a backslash if the dir name
548      * isn't backslash-terminated.
549      */
550     buf = HeapAlloc(GetProcessHeap(), 0,
551      (dirLen + max(fileLen, lstrlenW(starDotStarW)) + 2) * sizeof(WCHAR));
552     if (buf)
553     {
554         /* a depth of 0 implies we should search dir, so go ahead and search */
555         HANDLE hFind;
556         WIN32_FIND_DATAW findData;
557
558         memcpy(buf, dir, dirLen * sizeof(WCHAR));
559         if (buf[dirLen - 1] != '\\')
560             buf[dirLen++ - 1] = '\\';
561         memcpy(buf + dirLen, sig->File, (fileLen + 1) * sizeof(WCHAR));
562         hFind = FindFirstFileW(buf, &findData);
563         if (hFind != INVALID_HANDLE_VALUE)
564         {
565             BOOL matches;
566
567             /* assuming Signature can't contain wildcards for the file name,
568              * so don't bother with FindNextFileW here.
569              */
570             if (!(rc = ACTION_FileMatchesSig(sig, &findData, buf, &matches))
571              && matches)
572             {
573                 TRACE("found file, setting %s to %s\n",
574                  debugstr_w(sig->Property), debugstr_w(buf));
575                 rc = MSI_SetPropertyW(package, sig->Property, buf);
576                 *appFound = TRUE;
577             }
578             FindClose(hFind);
579         }
580         if (rc == ERROR_SUCCESS && !*appFound && depth > 0)
581         {
582             HANDLE hFind;
583             WIN32_FIND_DATAW findData;
584
585             memcpy(buf, dir, dirLen * sizeof(WCHAR));
586             if (buf[dirLen - 1] != '\\')
587                 buf[dirLen++ - 1] = '\\';
588             lstrcpyW(buf + dirLen, starDotStarW);
589             hFind = FindFirstFileW(buf, &findData);
590             if (hFind != INVALID_HANDLE_VALUE)
591             {
592                 if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
593                     rc = ACTION_RecurseSearchDirectory(package, appFound,
594                      sig, findData.cFileName, depth - 1);
595                 while (rc == ERROR_SUCCESS && !*appFound &&
596                  FindNextFileW(hFind, &findData) != 0)
597                 {
598                     if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
599                         rc = ACTION_RecurseSearchDirectory(package, appFound,
600                          sig, findData.cFileName, depth - 1);
601                 }
602                 FindClose(hFind);
603             }
604         }
605         HeapFree(GetProcessHeap(), 0, buf);
606     }
607     else
608         rc = ERROR_OUTOFMEMORY;
609
610     return rc;
611 }
612
613 static UINT ACTION_CheckDirectory(MSIPACKAGE *package, MSISIGNATURE *sig,
614  LPCWSTR dir)
615 {
616     UINT rc = ERROR_SUCCESS;
617
618     if (GetFileAttributesW(dir) & FILE_ATTRIBUTE_DIRECTORY)
619     {
620         TRACE("directory exists, setting %s to %s\n",
621          debugstr_w(sig->Property), debugstr_w(dir));
622         rc = MSI_SetPropertyW(package, sig->Property, dir);
623     }
624     return rc;
625 }
626
627 static BOOL ACTION_IsFullPath(LPCWSTR path)
628 {
629     WCHAR first = toupperW(path[0]);
630     BOOL ret;
631
632     if (first >= 'A' && first <= 'Z' && path[1] == ':')
633         ret = TRUE;
634     else if (path[0] == '\\' && path[1] == '\\')
635         ret = TRUE;
636     else
637         ret = FALSE;
638     return ret;
639 }
640
641 static UINT ACTION_SearchDirectory(MSIPACKAGE *package, MSISIGNATURE *sig,
642  LPCWSTR expanded, int depth)
643 {
644     UINT rc;
645     BOOL found;
646
647     TRACE("%p, %p, %s, %d\n", package, sig, debugstr_w(expanded), depth);
648     if (ACTION_IsFullPath(expanded))
649     {
650         if (sig->File)
651             rc = ACTION_RecurseSearchDirectory(package, &found, sig,
652              expanded, depth);
653         else
654         {
655             /* Recursively searching a directory makes no sense when the
656              * directory to search is the thing you're trying to find.
657              */
658             rc = ACTION_CheckDirectory(package, sig, expanded);
659         }
660     }
661     else
662     {
663         WCHAR pathWithDrive[MAX_PATH] = { 'C',':','\\',0 };
664         DWORD drives = GetLogicalDrives();
665         int i;
666
667         rc = ERROR_SUCCESS;
668         found = FALSE;
669         for (i = 0; rc == ERROR_SUCCESS && !found && i < 26; i++)
670             if (drives & (1 << drives))
671             {
672                 pathWithDrive[0] = 'A' + i;
673                 if (GetDriveTypeW(pathWithDrive) == DRIVE_FIXED)
674                 {
675                     strncpyW(pathWithDrive + 3, expanded,
676                      sizeof(pathWithDrive) / sizeof(pathWithDrive[0]) - 3);
677                     if (sig->File)
678                         rc = ACTION_RecurseSearchDirectory(package, &found, sig,
679                          pathWithDrive, depth);
680                     else
681                         rc = ACTION_CheckDirectory(package, sig, pathWithDrive);
682                 }
683             }
684     }
685     TRACE("returning %d\n", rc);
686     return rc;
687 }
688
689 static UINT ACTION_AppSearchDr(MSIPACKAGE *package, MSISIGNATURE *sig)
690 {
691     MSIQUERY *view;
692     UINT rc;
693     static const WCHAR ExecSeqQuery[] =  {
694    's','e','l','e','c','t',' ','*',' ',
695    'f','r','o','m',' ',
696    'D','r','L','o','c','a','t','o','r',' ',
697    'w','h','e','r','e',' ','S','i','g','n','a','t','u','r','e','_',' ','=',' ',
698    '\'','%','s','\'',0};
699
700     TRACE("(package %p, sig %p)\n", package, sig);
701     rc = MSI_OpenQuery(package->db, &view, ExecSeqQuery, sig->Name);
702     if (rc == ERROR_SUCCESS)
703     {
704         MSIRECORD *row = 0;
705         WCHAR buffer[MAX_PATH], expanded[MAX_PATH];
706         DWORD sz;
707         int depth;
708
709         rc = MSI_ViewExecute(view, 0);
710         if (rc != ERROR_SUCCESS)
711         {
712             TRACE("MSI_ViewExecute returned %d\n", rc);
713             goto end;
714         }
715         rc = MSI_ViewFetch(view,&row);
716         if (rc != ERROR_SUCCESS)
717         {
718             TRACE("MSI_ViewFetch returned %d\n", rc);
719             rc = ERROR_SUCCESS;
720             goto end;
721         }
722
723         /* check whether parent is set */
724         buffer[0] = 0;
725         sz=sizeof(buffer)/sizeof(buffer[0]);
726         rc = MSI_RecordGetStringW(row,2,buffer,&sz);
727         if (rc != ERROR_SUCCESS)
728         {
729             ERR("Error is %x\n",rc);
730             goto end;
731         }
732         else if (buffer[0])
733         {
734             FIXME(": searching parent (%s) unimplemented\n",
735              debugstr_w(buffer));
736             goto end;
737         }
738         /* no parent, now look for path */
739         buffer[0] = 0;
740         sz=sizeof(buffer)/sizeof(buffer[0]);
741         rc = MSI_RecordGetStringW(row,3,buffer,&sz);
742         if (rc != ERROR_SUCCESS)
743         {
744             ERR("Error is %x\n",rc);
745             goto end;
746         }
747         if (MSI_RecordIsNull(row,4))
748             depth = 0;
749         else
750             depth = MSI_RecordGetInteger(row,4);
751         ACTION_ExpandAnyPath(package, buffer, expanded,
752          sizeof(expanded) / sizeof(expanded[0]));
753         rc = ACTION_SearchDirectory(package, sig, expanded, depth);
754
755 end:
756         msiobj_release(&row->hdr);
757         MSI_ViewClose(view);
758         msiobj_release(&view->hdr);
759     }
760     else
761     {
762         TRACE("MSI_OpenQuery returned %d\n", rc);
763         rc = ERROR_SUCCESS;
764     }
765
766
767     TRACE("returning %d\n", rc);
768     return rc;
769 }
770
771 /* http://msdn.microsoft.com/library/en-us/msi/setup/appsearch_table.asp
772  * is the best reference for the AppSearch table and how it's used.
773  */
774 UINT ACTION_AppSearch(MSIPACKAGE *package)
775 {
776     MSIQUERY *view;
777     UINT rc;
778     static const WCHAR ExecSeqQuery[] =  {
779    's','e','l','e','c','t',' ','*',' ',
780    'f','r','o','m',' ',
781    'A','p','p','S','e','a','r','c','h',0};
782
783     rc = MSI_OpenQuery(package->db, &view, ExecSeqQuery);
784     if (rc == ERROR_SUCCESS)
785     {
786         MSIRECORD *row = 0;
787         WCHAR propBuf[0x100], sigBuf[0x100];
788         DWORD sz;
789         MSISIGNATURE sig;
790         BOOL appFound = FALSE;
791
792         rc = MSI_ViewExecute(view, 0);
793         if (rc != ERROR_SUCCESS)
794             goto end;
795
796         while (!rc)
797         {
798             rc = MSI_ViewFetch(view,&row);
799             if (rc != ERROR_SUCCESS)
800             {
801                 rc = ERROR_SUCCESS;
802                 break;
803             }
804
805             /* get property and signature */
806             propBuf[0] = 0;
807             sz=sizeof(propBuf)/sizeof(propBuf[0]);
808             rc = MSI_RecordGetStringW(row,1,propBuf,&sz);
809             if (rc != ERROR_SUCCESS)
810             {
811                 ERR("Error is %x\n",rc);
812                 msiobj_release(&row->hdr);
813                 break;
814             }
815             sigBuf[0] = 0;
816             sz=sizeof(sigBuf)/sizeof(sigBuf[0]);
817             rc = MSI_RecordGetStringW(row,2,sigBuf,&sz);
818             if (rc != ERROR_SUCCESS)
819             {
820                 ERR("Error is %x\n",rc);
821                 msiobj_release(&row->hdr);
822                 break;
823             }
824             TRACE("Searching for Property %s, Signature_ %s\n",
825              debugstr_w(propBuf), debugstr_w(sigBuf));
826             /* This clears all the fields, so set Name and Property afterward */
827             rc = ACTION_AppSearchGetSignature(package, &sig, sigBuf);
828             sig.Name = sigBuf;
829             sig.Property = propBuf;
830             if (rc == ERROR_SUCCESS)
831             {
832                 rc = ACTION_AppSearchComponents(package, &appFound, &sig);
833                 if (rc == ERROR_SUCCESS && !appFound)
834                 {
835                     rc = ACTION_AppSearchReg(package, &appFound, &sig);
836                     if (rc == ERROR_SUCCESS && !appFound)
837                     {
838                         rc = ACTION_AppSearchIni(package, &appFound, &sig);
839                         if (rc == ERROR_SUCCESS && !appFound)
840                             rc = ACTION_AppSearchDr(package, &sig);
841                     }
842                 }
843             }
844             HeapFree(GetProcessHeap(), 0, sig.File);
845             HeapFree(GetProcessHeap(), 0, sig.Languages);
846             msiobj_release(&row->hdr);
847         }
848
849 end:
850         MSI_ViewClose(view);
851         msiobj_release(&view->hdr);
852     }
853     else
854         rc = ERROR_SUCCESS;
855
856     return rc;
857 }