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