msi: Publish the UpgradeCode in PublishProduct.
[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
229 done:
230     if (rec) msiobj_release(&rec->hdr);
231     msiobj_release(&row->hdr);
232     return ERROR_SUCCESS;
233 }
234
235 static void ACTION_ConvertRegValue(DWORD regType, const BYTE *value, DWORD sz,
236  LPWSTR *appValue)
237 {
238     static const WCHAR dwordFmt[] = { '#','%','d','\0' };
239     static const WCHAR expandSzFmt[] = { '#','%','%','%','s','\0' };
240     static const WCHAR binFmt[] = { '#','x','%','x','\0' };
241     DWORD i;
242
243     switch (regType)
244     {
245         case REG_SZ:
246             if (*(LPCWSTR)value == '#')
247             {
248                 /* escape leading pound with another */
249                 *appValue = msi_alloc(sz + sizeof(WCHAR));
250                 (*appValue)[0] = '#';
251                 strcpyW(*appValue + 1, (LPCWSTR)value);
252             }
253             else
254             {
255                 *appValue = msi_alloc(sz);
256                 strcpyW(*appValue, (LPCWSTR)value);
257             }
258             break;
259         case REG_DWORD:
260             /* 7 chars for digits, 1 for NULL, 1 for #, and 1 for sign
261              * char if needed
262              */
263             *appValue = msi_alloc(10 * sizeof(WCHAR));
264             sprintfW(*appValue, dwordFmt, *(const DWORD *)value);
265             break;
266         case REG_EXPAND_SZ:
267             /* space for extra #% characters in front */
268             *appValue = msi_alloc(sz + 2 * sizeof(WCHAR));
269             sprintfW(*appValue, expandSzFmt, (LPCWSTR)value);
270             break;
271         case REG_BINARY:
272             /* 3 == length of "#x<nibble>" */
273             *appValue = msi_alloc((sz * 3 + 1) * sizeof(WCHAR));
274             for (i = 0; i < sz; i++)
275                 sprintfW(*appValue + i * 3, binFmt, value[i]);
276             break;
277         default:
278             WARN("unimplemented for values of type %d\n", regType);
279             *appValue = NULL;
280     }
281 }
282
283 static UINT ACTION_SearchDirectory(MSIPACKAGE *package, MSISIGNATURE *sig,
284  LPCWSTR path, int depth, LPWSTR *appValue);
285
286 static UINT ACTION_AppSearchReg(MSIPACKAGE *package, LPWSTR *appValue, MSISIGNATURE *sig)
287 {
288     static const WCHAR query[] =  {
289         's','e','l','e','c','t',' ','*',' ',
290         'f','r','o','m',' ',
291         'R','e','g','L','o','c','a','t','o','r',' ',
292         'w','h','e','r','e',' ',
293         'S','i','g','n','a','t','u','r','e','_',' ','=',' ', '\'','%','s','\'',0};
294     LPWSTR keyPath = NULL, valueName = NULL;
295     LPWSTR deformatted = NULL;
296     int root, type;
297     HKEY rootKey, key = NULL;
298     DWORD sz = 0, regType;
299     LPBYTE value = NULL;
300     MSIRECORD *row;
301     UINT rc;
302
303     TRACE("%s\n", debugstr_w(sig->Name));
304
305     *appValue = NULL;
306
307     row = MSI_QueryGetRecord( package->db, query, sig->Name );
308     if (!row)
309     {
310         TRACE("failed to query RegLocator for %s\n", debugstr_w(sig->Name));
311         return ERROR_SUCCESS;
312     }
313
314     root = MSI_RecordGetInteger(row,2);
315     keyPath = msi_dup_record_field(row,3);
316     valueName = msi_dup_record_field(row,4);
317     type = MSI_RecordGetInteger(row,5);
318
319     deformat_string(package, keyPath, &deformatted);
320
321     switch (root)
322     {
323     case msidbRegistryRootClassesRoot:
324         rootKey = HKEY_CLASSES_ROOT;
325         break;
326     case msidbRegistryRootCurrentUser:
327         rootKey = HKEY_CURRENT_USER;
328         break;
329     case msidbRegistryRootLocalMachine:
330         rootKey = HKEY_LOCAL_MACHINE;
331         break;
332     case msidbRegistryRootUsers:
333         rootKey = HKEY_USERS;
334         break;
335     default:
336         WARN("Unknown root key %d\n", root);
337         goto end;
338     }
339
340     rc = RegOpenKeyW(rootKey, deformatted, &key);
341     if (rc)
342     {
343         TRACE("RegOpenKeyW returned %d\n", rc);
344         goto end;
345     }
346
347     rc = RegQueryValueExW(key, valueName, NULL, NULL, NULL, &sz);
348     if (rc)
349     {
350         TRACE("RegQueryValueExW returned %d\n", rc);
351         goto end;
352     }
353     /* FIXME: sanity-check sz before allocating (is there an upper-limit
354      * on the value of a property?)
355      */
356     value = msi_alloc( sz );
357     rc = RegQueryValueExW(key, valueName, NULL, &regType, value, &sz);
358     if (rc)
359     {
360         TRACE("RegQueryValueExW returned %d\n", rc);
361         goto end;
362     }
363
364     /* bail out if the registry key is empty */
365     if (sz == 0)
366         goto end;
367
368     switch (type & 0x0f)
369     {
370     case msidbLocatorTypeDirectory:
371         rc = ACTION_SearchDirectory(package, sig, (LPWSTR)value, 0, appValue);
372         break;
373     case msidbLocatorTypeFileName:
374         *appValue = strdupW((LPWSTR)value);
375         break;
376     case msidbLocatorTypeRawValue:
377         ACTION_ConvertRegValue(regType, value, sz, appValue);
378         break;
379     default:
380         FIXME("AppSearch unimplemented for type %d (key path %s, value %s)\n",
381               type, debugstr_w(keyPath), debugstr_w(valueName));
382     }
383 end:
384     msi_free( value );
385     RegCloseKey( key );
386
387     msi_free( keyPath );
388     msi_free( valueName );
389     msi_free( deformatted );
390
391     msiobj_release(&row->hdr);
392
393     return ERROR_SUCCESS;
394 }
395
396 static UINT ACTION_AppSearchIni(MSIPACKAGE *package, LPWSTR *appValue,
397  MSISIGNATURE *sig)
398 {
399     static const WCHAR query[] =  {
400         's','e','l','e','c','t',' ','*',' ',
401         'f','r','o','m',' ',
402         'I','n','i','L','o','c','a','t','o','r',' ',
403         'w','h','e','r','e',' ',
404         'S','i','g','n','a','t','u','r','e','_',' ','=',' ','\'','%','s','\'',0};
405     MSIRECORD *row;
406     LPWSTR fileName, section, key;
407     int field, type;
408     WCHAR buf[MAX_PATH];
409
410     TRACE("%s\n", debugstr_w(sig->Name));
411
412     *appValue = NULL;
413
414     row = MSI_QueryGetRecord( package->db, query, sig->Name );
415     if (!row)
416     {
417         TRACE("failed to query IniLocator for %s\n", debugstr_w(sig->Name));
418         return ERROR_SUCCESS;
419     }
420
421     fileName = msi_dup_record_field(row, 2);
422     section = msi_dup_record_field(row, 3);
423     key = msi_dup_record_field(row, 4);
424     field = MSI_RecordGetInteger(row, 5);
425     type = MSI_RecordGetInteger(row, 6);
426     if (field == MSI_NULL_INTEGER)
427         field = 0;
428     if (type == MSI_NULL_INTEGER)
429         type = 0;
430
431     GetPrivateProfileStringW(section, key, NULL, buf, MAX_PATH, fileName);
432     if (buf[0])
433     {
434         switch (type & 0x0f)
435         {
436         case msidbLocatorTypeDirectory:
437             FIXME("unimplemented for Directory (%s)\n", debugstr_w(buf));
438             break;
439         case msidbLocatorTypeFileName:
440             FIXME("unimplemented for File (%s)\n", debugstr_w(buf));
441             break;
442         case msidbLocatorTypeRawValue:
443             *appValue = strdupW(buf);
444             break;
445         }
446     }
447
448     msi_free(fileName);
449     msi_free(section);
450     msi_free(key);
451
452     msiobj_release(&row->hdr);
453
454     return ERROR_SUCCESS;
455 }
456
457 /* Expands the value in src into a path without property names and only
458  * containing long path names into dst.  Replaces at most len characters of dst,
459  * and always NULL-terminates dst if dst is not NULL and len >= 1.
460  * May modify src.
461  * Assumes src and dst are non-overlapping.
462  * FIXME: return code probably needed:
463  * - what does AppSearch return if the table values are invalid?
464  * - what if dst is too small?
465  */
466 static void ACTION_ExpandAnyPath(MSIPACKAGE *package, WCHAR *src, WCHAR *dst,
467  size_t len)
468 {
469     WCHAR *ptr, *deformatted;
470
471     if (!src || !dst || !len)
472     {
473         if (dst) *dst = '\0';
474         return;
475     }
476
477     dst[0] = '\0';
478
479     /* Ignore the short portion of the path */
480     if ((ptr = strchrW(src, '|')))
481         ptr++;
482     else
483         ptr = src;
484
485     deformat_string(package, ptr, &deformatted);
486     if (!deformatted || lstrlenW(deformatted) > len - 1)
487     {
488         msi_free(deformatted);
489         return;
490     }
491
492     lstrcpyW(dst, deformatted);
493     dst[lstrlenW(deformatted)] = '\0';
494     msi_free(deformatted);
495 }
496
497 /* Sets *matches to whether the file (whose path is filePath) matches the
498  * versions set in sig.
499  * Return ERROR_SUCCESS in case of success (whether or not the file matches),
500  * something else if an install-halting error occurs.
501  */
502 static UINT ACTION_FileVersionMatches(const MSISIGNATURE *sig, LPCWSTR filePath,
503  BOOL *matches)
504 {
505     UINT rc = ERROR_SUCCESS;
506
507     *matches = FALSE;
508     if (sig->Languages)
509     {
510         FIXME(": need to check version for languages %s\n",
511          debugstr_w(sig->Languages));
512     }
513     else
514     {
515         DWORD zero, size = GetFileVersionInfoSizeW(filePath, &zero);
516
517         if (size)
518         {
519             LPVOID buf = msi_alloc( size);
520
521             if (buf)
522             {
523                 static const WCHAR rootW[] = { '\\',0 };
524                 UINT versionLen;
525                 LPVOID subBlock = NULL;
526
527                 if (GetFileVersionInfoW(filePath, 0, size, buf))
528                     VerQueryValueW(buf, rootW, &subBlock, &versionLen);
529                 if (subBlock)
530                 {
531                     VS_FIXEDFILEINFO *info =
532                      (VS_FIXEDFILEINFO *)subBlock;
533
534                     TRACE("Comparing file version %d.%d.%d.%d:\n",
535                      HIWORD(info->dwFileVersionMS),
536                      LOWORD(info->dwFileVersionMS),
537                      HIWORD(info->dwFileVersionLS),
538                      LOWORD(info->dwFileVersionLS));
539                     if (info->dwFileVersionMS < sig->MinVersionMS
540                      || (info->dwFileVersionMS == sig->MinVersionMS &&
541                      info->dwFileVersionLS < sig->MinVersionLS))
542                     {
543                         TRACE("Less than minimum version %d.%d.%d.%d\n",
544                          HIWORD(sig->MinVersionMS),
545                          LOWORD(sig->MinVersionMS),
546                          HIWORD(sig->MinVersionLS),
547                          LOWORD(sig->MinVersionLS));
548                     }
549                     else if (info->dwFileVersionMS < sig->MinVersionMS
550                      || (info->dwFileVersionMS == sig->MinVersionMS &&
551                      info->dwFileVersionLS < sig->MinVersionLS))
552                     {
553                         TRACE("Greater than minimum version %d.%d.%d.%d\n",
554                          HIWORD(sig->MaxVersionMS),
555                          LOWORD(sig->MaxVersionMS),
556                          HIWORD(sig->MaxVersionLS),
557                          LOWORD(sig->MaxVersionLS));
558                     }
559                     else
560                         *matches = TRUE;
561                 }
562                 msi_free( buf);
563             }
564             else
565                 rc = ERROR_OUTOFMEMORY;
566         }
567     }
568     return rc;
569 }
570
571 /* Sets *matches to whether the file in findData matches that in sig.
572  * fullFilePath is assumed to be the full path of the file specified in
573  * findData, which may be necessary to compare the version.
574  * Return ERROR_SUCCESS in case of success (whether or not the file matches),
575  * something else if an install-halting error occurs.
576  */
577 static UINT ACTION_FileMatchesSig(const MSISIGNATURE *sig,
578  const WIN32_FIND_DATAW *findData, LPCWSTR fullFilePath, BOOL *matches)
579 {
580     UINT rc = ERROR_SUCCESS;
581
582     *matches = TRUE;
583     /* assumes the caller has already ensured the filenames match, so check
584      * the other fields..
585      */
586     if (sig->MinTime.dwLowDateTime || sig->MinTime.dwHighDateTime)
587     {
588         if (findData->ftCreationTime.dwHighDateTime <
589          sig->MinTime.dwHighDateTime ||
590          (findData->ftCreationTime.dwHighDateTime == sig->MinTime.dwHighDateTime
591          && findData->ftCreationTime.dwLowDateTime <
592          sig->MinTime.dwLowDateTime))
593             *matches = FALSE;
594     }
595     if (*matches && (sig->MaxTime.dwLowDateTime || sig->MaxTime.dwHighDateTime))
596     {
597         if (findData->ftCreationTime.dwHighDateTime >
598          sig->MaxTime.dwHighDateTime ||
599          (findData->ftCreationTime.dwHighDateTime == sig->MaxTime.dwHighDateTime
600          && findData->ftCreationTime.dwLowDateTime >
601          sig->MaxTime.dwLowDateTime))
602             *matches = FALSE;
603     }
604     if (*matches && sig->MinSize && findData->nFileSizeLow < sig->MinSize)
605         *matches = FALSE;
606     if (*matches && sig->MaxSize && findData->nFileSizeLow > sig->MaxSize)
607         *matches = FALSE;
608     if (*matches && (sig->MinVersionMS || sig->MinVersionLS ||
609      sig->MaxVersionMS || sig->MaxVersionLS))
610         rc = ACTION_FileVersionMatches(sig, fullFilePath, matches);
611     return rc;
612 }
613
614 /* Recursively searches the directory dir for files that match the signature
615  * sig, up to (depth + 1) levels deep.  That is, if depth is 0, it searches dir
616  * (and only dir).  If depth is 1, searches dir and its immediate
617  * subdirectories.
618  * Assumes sig->File is not NULL.
619  * Returns ERROR_SUCCESS on success (which may include non-critical errors),
620  * something else on failures which should halt the install.
621  */
622 static UINT ACTION_RecurseSearchDirectory(MSIPACKAGE *package, LPWSTR *appValue,
623  MSISIGNATURE *sig, LPCWSTR dir, int depth)
624 {
625     static const WCHAR starDotStarW[] = { '*','.','*',0 };
626     UINT rc = ERROR_SUCCESS;
627     size_t dirLen = lstrlenW(dir), fileLen = lstrlenW(sig->File);
628     WCHAR *buf;
629
630     TRACE("Searching directory %s for file %s, depth %d\n", debugstr_w(dir),
631      debugstr_w(sig->File), depth);
632
633     if (depth < 0)
634         return ERROR_INVALID_PARAMETER;
635
636     *appValue = NULL;
637     /* We need the buffer in both paths below, so go ahead and allocate it
638      * here.  Add two because we might need to add a backslash if the dir name
639      * isn't backslash-terminated.
640      */
641     buf = msi_alloc( (dirLen + max(fileLen, lstrlenW(starDotStarW)) + 2) * sizeof(WCHAR));
642     if (buf)
643     {
644         /* a depth of 0 implies we should search dir, so go ahead and search */
645         HANDLE hFind;
646         WIN32_FIND_DATAW findData;
647
648         memcpy(buf, dir, dirLen * sizeof(WCHAR));
649         if (buf[dirLen - 1] != '\\')
650             buf[dirLen++ - 1] = '\\';
651         memcpy(buf + dirLen, sig->File, (fileLen + 1) * sizeof(WCHAR));
652         hFind = FindFirstFileW(buf, &findData);
653         if (hFind != INVALID_HANDLE_VALUE)
654         {
655             BOOL matches;
656
657             /* assuming Signature can't contain wildcards for the file name,
658              * so don't bother with FindNextFileW here.
659              */
660             if (!(rc = ACTION_FileMatchesSig(sig, &findData, buf, &matches))
661              && matches)
662             {
663                 TRACE("found file, returning %s\n", debugstr_w(buf));
664                 *appValue = buf;
665             }
666             FindClose(hFind);
667         }
668         if (rc == ERROR_SUCCESS && !*appValue && depth > 0)
669         {
670             HANDLE hFind;
671             WIN32_FIND_DATAW findData;
672
673             memcpy(buf, dir, dirLen * sizeof(WCHAR));
674             if (buf[dirLen - 1] != '\\')
675                 buf[dirLen++ - 1] = '\\';
676             lstrcpyW(buf + dirLen, starDotStarW);
677             hFind = FindFirstFileW(buf, &findData);
678             if (hFind != INVALID_HANDLE_VALUE)
679             {
680                 if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
681                     rc = ACTION_RecurseSearchDirectory(package, appValue, sig,
682                      findData.cFileName, depth - 1);
683                 while (rc == ERROR_SUCCESS && !*appValue &&
684                  FindNextFileW(hFind, &findData) != 0)
685                 {
686                     if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
687                         rc = ACTION_RecurseSearchDirectory(package, appValue,
688                          sig, findData.cFileName, depth - 1);
689                 }
690                 FindClose(hFind);
691             }
692         }
693         if (!*appValue)
694             msi_free(buf);
695     }
696     else
697         rc = ERROR_OUTOFMEMORY;
698
699     return rc;
700 }
701
702 static UINT ACTION_CheckDirectory(MSIPACKAGE *package, LPCWSTR dir,
703  LPWSTR *appValue)
704 {
705     DWORD attr = GetFileAttributesW(dir);
706
707     if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY))
708     {
709         TRACE("directory exists, returning %s\n", debugstr_w(dir));
710         *appValue = strdupW(dir);
711     }
712
713     return ERROR_SUCCESS;
714 }
715
716 static BOOL ACTION_IsFullPath(LPCWSTR path)
717 {
718     WCHAR first = toupperW(path[0]);
719     BOOL ret;
720
721     if (first >= 'A' && first <= 'Z' && path[1] == ':')
722         ret = TRUE;
723     else if (path[0] == '\\' && path[1] == '\\')
724         ret = TRUE;
725     else
726         ret = FALSE;
727     return ret;
728 }
729
730 static UINT ACTION_SearchDirectory(MSIPACKAGE *package, MSISIGNATURE *sig,
731  LPCWSTR path, int depth, LPWSTR *appValue)
732 {
733     UINT rc;
734
735     TRACE("%p, %p, %s, %d, %p\n", package, sig, debugstr_w(path), depth,
736      appValue);
737     if (ACTION_IsFullPath(path))
738     {
739         if (sig->File)
740             rc = ACTION_RecurseSearchDirectory(package, appValue, sig,
741              path, depth);
742         else
743         {
744             /* Recursively searching a directory makes no sense when the
745              * directory to search is the thing you're trying to find.
746              */
747             rc = ACTION_CheckDirectory(package, path, appValue);
748         }
749     }
750     else
751     {
752         WCHAR pathWithDrive[MAX_PATH] = { 'C',':','\\',0 };
753         DWORD drives = GetLogicalDrives();
754         int i;
755
756         rc = ERROR_SUCCESS;
757         *appValue = NULL;
758         for (i = 0; rc == ERROR_SUCCESS && !*appValue && i < 26; i++)
759             if (drives & (1 << i))
760             {
761                 pathWithDrive[0] = 'A' + i;
762                 if (GetDriveTypeW(pathWithDrive) == DRIVE_FIXED)
763                 {
764                     lstrcpynW(pathWithDrive + 3, path,
765                               sizeof(pathWithDrive) / sizeof(pathWithDrive[0]) - 3);
766                     if (sig->File)
767                         rc = ACTION_RecurseSearchDirectory(package, appValue,
768                          sig, pathWithDrive, depth);
769                     else
770                         rc = ACTION_CheckDirectory(package, pathWithDrive,
771                          appValue);
772                 }
773             }
774     }
775     TRACE("returning %d\n", rc);
776     return rc;
777 }
778
779 static UINT ACTION_AppSearchSigName(MSIPACKAGE *package, LPCWSTR sigName,
780  MSISIGNATURE *sig, LPWSTR *appValue);
781
782 static UINT ACTION_AppSearchDr(MSIPACKAGE *package, LPWSTR *appValue, MSISIGNATURE *sig)
783 {
784     static const WCHAR query[] =  {
785         's','e','l','e','c','t',' ','*',' ',
786         'f','r','o','m',' ',
787         'D','r','L','o','c','a','t','o','r',' ',
788         'w','h','e','r','e',' ',
789         'S','i','g','n','a','t','u','r','e','_',' ','=',' ', '\'','%','s','\'',0};
790     LPWSTR parentName = NULL, path = NULL, parent = NULL;
791     WCHAR expanded[MAX_PATH];
792     MSIRECORD *row;
793     int depth;
794     UINT rc;
795
796     TRACE("%s\n", debugstr_w(sig->Name));
797
798     msi_free(sig->File);
799     sig->File = NULL;
800
801     *appValue = NULL;
802
803     row = MSI_QueryGetRecord( package->db, query, sig->Name );
804     if (!row)
805     {
806         TRACE("failed to query DrLocator for %s\n", debugstr_w(sig->Name));
807         return ERROR_SUCCESS;
808     }
809
810     /* check whether parent is set */
811     parentName = msi_dup_record_field(row,2);
812     if (parentName)
813     {
814         MSISIGNATURE parentSig;
815
816         rc = ACTION_AppSearchSigName(package, parentName, &parentSig, &parent);
817         ACTION_FreeSignature(&parentSig);
818         msi_free(parentName);
819     }
820     /* now look for path */
821     path = msi_dup_record_field(row,3);
822     if (MSI_RecordIsNull(row,4))
823         depth = 0;
824     else
825         depth = MSI_RecordGetInteger(row,4);
826     ACTION_ExpandAnyPath(package, path, expanded, MAX_PATH);
827     msi_free(path);
828     if (parent)
829     {
830         path = msi_alloc((strlenW(parent) + strlenW(expanded) + 1) * sizeof(WCHAR));
831         if (!path)
832         {
833             rc = ERROR_OUTOFMEMORY;
834             goto end;
835         }
836         strcpyW(path, parent);
837         strcatW(path, expanded);
838     }
839     else
840         path = expanded;
841
842     rc = ACTION_SearchDirectory(package, sig, path, depth, appValue);
843
844 end:
845     if (path != expanded)
846         msi_free(path);
847     msi_free(parent);
848     msiobj_release(&row->hdr);
849
850     TRACE("returning %d\n", rc);
851     return rc;
852 }
853
854 static UINT ACTION_AppSearchSigName(MSIPACKAGE *package, LPCWSTR sigName,
855  MSISIGNATURE *sig, LPWSTR *appValue)
856 {
857     UINT rc;
858
859     *appValue = NULL;
860     rc = ACTION_AppSearchGetSignature(package, sig, sigName);
861     if (rc == ERROR_SUCCESS)
862     {
863         rc = ACTION_AppSearchComponents(package, appValue, sig);
864         if (rc == ERROR_SUCCESS && !*appValue)
865         {
866             rc = ACTION_AppSearchReg(package, appValue, sig);
867             if (rc == ERROR_SUCCESS && !*appValue)
868             {
869                 rc = ACTION_AppSearchIni(package, appValue, sig);
870                 if (rc == ERROR_SUCCESS && !*appValue)
871                     rc = ACTION_AppSearchDr(package, appValue, sig);
872             }
873         }
874     }
875     return rc;
876 }
877
878 static UINT iterate_appsearch(MSIRECORD *row, LPVOID param)
879 {
880     MSIPACKAGE *package = param;
881     LPWSTR propName, sigName, value = NULL;
882     MSISIGNATURE sig;
883     UINT r;
884
885     /* get property and signature */
886     propName = msi_dup_record_field(row,1);
887     sigName = msi_dup_record_field(row,2);
888
889     TRACE("%s %s\n", debugstr_w(propName), debugstr_w(sigName));
890
891     r = ACTION_AppSearchSigName(package, sigName, &sig, &value);
892     if (value)
893     {
894         MSI_SetPropertyW(package, propName, value);
895         msi_free(value);
896     }
897     ACTION_FreeSignature(&sig);
898     msi_free(propName);
899     msi_free(sigName);
900
901     return r;
902 }
903
904 UINT ACTION_AppSearch(MSIPACKAGE *package)
905 {
906     static const WCHAR query[] =  {
907         's','e','l','e','c','t',' ','*',' ',
908         'f','r','o','m',' ',
909         'A','p','p','S','e','a','r','c','h',0};
910     MSIQUERY *view = NULL;
911     UINT r;
912
913     r = MSI_OpenQuery( package->db, &view, query );
914     if (r != ERROR_SUCCESS)
915         return ERROR_SUCCESS;
916
917     r = MSI_IterateRecords( view, NULL, iterate_appsearch, package );
918     msiobj_release( &view->hdr );
919
920     return r;
921 }
922
923 static UINT ITERATE_CCPSearch(MSIRECORD *row, LPVOID param)
924 {
925     MSIPACKAGE *package = param;
926     LPCWSTR signature;
927     LPWSTR value = NULL;
928     MSISIGNATURE sig;
929     UINT r = ERROR_SUCCESS;
930
931     static const WCHAR success[] = {'C','C','P','_','S','u','c','c','e','s','s',0};
932     static const WCHAR one[] = {'1',0};
933
934     signature = MSI_RecordGetString(row, 1);
935
936     TRACE("%s\n", debugstr_w(signature));
937
938     ACTION_AppSearchSigName(package, signature, &sig, &value);
939     if (value)
940     {
941         TRACE("Found signature %s\n", debugstr_w(signature));
942         MSI_SetPropertyW(package, success, one);
943         msi_free(value);
944         r = ERROR_NO_MORE_ITEMS;
945     }
946
947     ACTION_FreeSignature(&sig);
948
949     return r;
950 }
951
952 UINT ACTION_CCPSearch(MSIPACKAGE *package)
953 {
954     static const WCHAR query[] =  {
955         's','e','l','e','c','t',' ','*',' ',
956         'f','r','o','m',' ',
957         'C','C','P','S','e','a','r','c','h',0};
958     MSIQUERY *view = NULL;
959     UINT r;
960
961     r = MSI_OpenQuery(package->db, &view, query);
962     if (r != ERROR_SUCCESS)
963         return ERROR_SUCCESS;
964
965     r = MSI_IterateRecords(view, NULL, ITERATE_CCPSearch, package);
966     msiobj_release(&view->hdr);
967
968     return r;
969 }