msi: Fix silly pointer error.
[wine/gsoc_dplay.git] / dlls / msi / appsearch.c
blobbea0c29f1a402ee1167a0f9f5baee020d96deb48
1 /*
2 * Implementation of the AppSearch action of the Microsoft Installer (msi.dll)
4 * Copyright 2005 Juan Lang
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.
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.
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
20 #include <stdarg.h>
22 #define COBJMACROS
24 #include "windef.h"
25 #include "winbase.h"
26 #include "winreg.h"
27 #include "msi.h"
28 #include "msiquery.h"
29 #include "msidefs.h"
30 #include "winver.h"
31 #include "wine/unicode.h"
32 #include "wine/debug.h"
33 #include "msipriv.h"
34 #include "action.h"
36 WINE_DEFAULT_DEBUG_CHANNEL(msi);
38 typedef struct tagMSISIGNATURE
40 LPWSTR 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;
53 static void ACTION_VerStrToInteger(LPCWSTR verStr, PDWORD ms, PDWORD ls)
55 const WCHAR *ptr;
56 int x1 = 0, x2 = 0, x3 = 0, x4 = 0;
58 x1 = atoiW(verStr);
59 ptr = strchrW(verStr, '.');
60 if (ptr)
62 x2 = atoiW(ptr + 1);
63 ptr = strchrW(ptr + 1, '.');
65 if (ptr)
67 x3 = atoiW(ptr + 1);
68 ptr = strchrW(ptr + 1, '.');
70 if (ptr)
71 x4 = atoiW(ptr + 1);
72 /* FIXME: byte-order dependent? */
73 *ms = x1 << 16 | x2;
74 *ls = x3 << 16 | x4;
77 /* Fills in sig with the 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.
84 static UINT ACTION_AppSearchGetSignature(MSIPACKAGE *package, MSISIGNATURE *sig,
85 LPCWSTR name)
87 MSIQUERY *view;
88 UINT rc;
89 static const WCHAR ExecSeqQuery[] = {
90 's','e','l','e','c','t',' ','*',' ',
91 'f','r','o','m',' ',
92 'S','i','g','n','a','t','u','r','e',' ',
93 'w','h','e','r','e',' ','S','i','g','n','a','t','u','r','e',' ','=',' ',
94 '\'','%','s','\'',0};
96 TRACE("(package %p, sig %p)\n", package, sig);
97 memset(sig, 0, sizeof(*sig));
98 rc = MSI_OpenQuery(package->db, &view, ExecSeqQuery, name);
99 if (rc == ERROR_SUCCESS)
101 MSIRECORD *row = 0;
102 DWORD time;
103 WCHAR *minVersion, *maxVersion;
105 rc = MSI_ViewExecute(view, 0);
106 if (rc != ERROR_SUCCESS)
108 TRACE("MSI_ViewExecute returned %d\n", rc);
109 goto end;
111 rc = MSI_ViewFetch(view,&row);
112 if (rc != ERROR_SUCCESS)
114 TRACE("MSI_ViewFetch returned %d\n", rc);
115 rc = ERROR_SUCCESS;
116 goto end;
119 /* get properties */
120 sig->File = msi_dup_record_field(row,2);
121 minVersion = msi_dup_record_field(row,3);
122 if (minVersion)
124 ACTION_VerStrToInteger(minVersion, &sig->MinVersionMS,
125 &sig->MinVersionLS);
126 msi_free( minVersion);
128 maxVersion = msi_dup_record_field(row,4);
129 if (maxVersion)
131 ACTION_VerStrToInteger(maxVersion, &sig->MaxVersionMS,
132 &sig->MaxVersionLS);
133 msi_free( maxVersion);
135 sig->MinSize = MSI_RecordGetInteger(row,5);
136 if (sig->MinSize == MSI_NULL_INTEGER)
137 sig->MinSize = 0;
138 sig->MaxSize = MSI_RecordGetInteger(row,6);
139 if (sig->MaxSize == MSI_NULL_INTEGER)
140 sig->MaxSize = 0;
141 sig->Languages = msi_dup_record_field(row,9);
142 time = MSI_RecordGetInteger(row,7);
143 if (time != MSI_NULL_INTEGER)
144 DosDateTimeToFileTime(HIWORD(time), LOWORD(time), &sig->MinTime);
145 time = MSI_RecordGetInteger(row,8);
146 if (time != MSI_NULL_INTEGER)
147 DosDateTimeToFileTime(HIWORD(time), LOWORD(time), &sig->MaxTime);
148 TRACE("Found file name %s for Signature_ %s;\n",
149 debugstr_w(sig->File), debugstr_w(name));
150 TRACE("MinVersion is %d.%d.%d.%d\n", HIWORD(sig->MinVersionMS),
151 LOWORD(sig->MinVersionMS), HIWORD(sig->MinVersionLS),
152 LOWORD(sig->MinVersionLS));
153 TRACE("MaxVersion is %d.%d.%d.%d\n", HIWORD(sig->MaxVersionMS),
154 LOWORD(sig->MaxVersionMS), HIWORD(sig->MaxVersionLS),
155 LOWORD(sig->MaxVersionLS));
156 TRACE("MinSize is %ld, MaxSize is %ld;\n", sig->MinSize, sig->MaxSize);
157 TRACE("Languages is %s\n", debugstr_w(sig->Languages));
159 end:
160 if (row)
161 msiobj_release(&row->hdr);
162 MSI_ViewClose(view);
163 msiobj_release(&view->hdr);
165 else
167 TRACE("MSI_OpenQuery returned %d\n", rc);
168 rc = ERROR_SUCCESS;
171 TRACE("returning %d\n", rc);
172 return rc;
175 static UINT ACTION_AppSearchComponents(MSIPACKAGE *package, LPWSTR *appValue,
176 MSISIGNATURE *sig)
178 MSIQUERY *view;
179 UINT rc;
180 static const WCHAR ExecSeqQuery[] = {
181 's','e','l','e','c','t',' ','*',' ',
182 'f','r','o','m',' ',
183 'C','o','m','p','L','o','c','a','t','o','r',' ',
184 'w','h','e','r','e',' ','S','i','g','n','a','t','u','r','e','_',' ','=',' ',
185 '\'','%','s','\'',0};
187 TRACE("(package %p, appValue %p, sig %p)\n", package, appValue, sig);
188 *appValue = NULL;
189 rc = MSI_OpenQuery(package->db, &view, ExecSeqQuery, sig->Name);
190 if (rc == ERROR_SUCCESS)
192 MSIRECORD *row = 0;
193 WCHAR guid[50];
194 DWORD sz;
196 rc = MSI_ViewExecute(view, 0);
197 if (rc != ERROR_SUCCESS)
199 TRACE("MSI_ViewExecute returned %d\n", rc);
200 goto end;
202 rc = MSI_ViewFetch(view,&row);
203 if (rc != ERROR_SUCCESS)
205 TRACE("MSI_ViewFetch returned %d\n", rc);
206 rc = ERROR_SUCCESS;
207 goto end;
210 /* get GUID */
211 guid[0] = 0;
212 sz=sizeof(guid)/sizeof(guid[0]);
213 rc = MSI_RecordGetStringW(row,2,guid,&sz);
214 if (rc != ERROR_SUCCESS)
216 ERR("Error is %x\n",rc);
217 goto end;
219 FIXME("AppSearch unimplemented for CompLocator table (GUID %s)\n",
220 debugstr_w(guid));
222 end:
223 if (row)
224 msiobj_release(&row->hdr);
225 MSI_ViewClose(view);
226 msiobj_release(&view->hdr);
228 else
230 TRACE("MSI_OpenQuery returned %d\n", rc);
231 rc = ERROR_SUCCESS;
234 TRACE("returning %d\n", rc);
235 return rc;
238 static void ACTION_ConvertRegValue(DWORD regType, const BYTE *value, DWORD sz,
239 LPWSTR *appValue)
241 static const WCHAR dwordFmt[] = { '#','%','d','\0' };
242 static const WCHAR expandSzFmt[] = { '#','%','%','%','s','\0' };
243 static const WCHAR binFmt[] = { '#','x','%','x','\0' };
244 DWORD i;
246 switch (regType)
248 case REG_SZ:
249 if (*(LPWSTR)value == '#')
251 /* escape leading pound with another */
252 *appValue = msi_alloc(sz + sizeof(WCHAR));
253 (*appValue)[0] = '#';
254 strcpyW(*appValue + 1, (LPCWSTR)value);
256 else
258 *appValue = msi_alloc(sz);
259 strcpyW(*appValue, (LPCWSTR)value);
261 break;
262 case REG_DWORD:
263 /* 7 chars for digits, 1 for NULL, 1 for #, and 1 for sign
264 * char if needed
266 *appValue = msi_alloc(10 * sizeof(WCHAR));
267 sprintfW(*appValue, dwordFmt, *(const DWORD *)value);
268 break;
269 case REG_EXPAND_SZ:
270 /* space for extra #% characters in front */
271 *appValue = msi_alloc(sz + 2 * sizeof(WCHAR));
272 sprintfW(*appValue, expandSzFmt, (LPCWSTR)value);
273 break;
274 case REG_BINARY:
275 /* 3 == length of "#x<nibble>" */
276 *appValue = msi_alloc((sz * 3 + 1) * sizeof(WCHAR));
277 for (i = 0; i < sz; i++)
278 sprintfW(*appValue + i * 3, binFmt, value[i]);
279 break;
280 default:
281 WARN("unimplemented for values of type %ld\n", regType);
282 *appValue = NULL;
286 static UINT ACTION_AppSearchReg(MSIPACKAGE *package, LPWSTR *appValue,
287 MSISIGNATURE *sig)
289 MSIQUERY *view;
290 UINT rc;
291 static const WCHAR ExecSeqQuery[] = {
292 's','e','l','e','c','t',' ','*',' ',
293 'f','r','o','m',' ',
294 'R','e','g','L','o','c','a','t','o','r',' ',
295 'w','h','e','r','e',' ','S','i','g','n','a','t','u','r','e','_',' ','=',' ',
296 '\'','%','s','\'',0};
298 TRACE("(package %p, appValue %p, sig %p)\n", package, appValue, sig);
299 *appValue = NULL;
300 rc = MSI_OpenQuery(package->db, &view, ExecSeqQuery, sig->Name);
301 if (rc == ERROR_SUCCESS)
303 MSIRECORD *row = 0;
304 LPWSTR keyPath = NULL, valueName = NULL;
305 int root, type;
306 HKEY rootKey, key = NULL;
307 DWORD sz = 0, regType;
308 LPBYTE value = NULL;
310 rc = MSI_ViewExecute(view, 0);
311 if (rc != ERROR_SUCCESS)
313 TRACE("MSI_ViewExecute returned %d\n", rc);
314 goto end;
316 rc = MSI_ViewFetch(view,&row);
317 if (rc != ERROR_SUCCESS)
319 TRACE("MSI_ViewFetch returned %d\n", rc);
320 rc = ERROR_SUCCESS;
321 goto end;
324 root = MSI_RecordGetInteger(row,2);
325 keyPath = msi_dup_record_field(row,3);
326 /* FIXME: keyPath needs to be expanded for properties */
327 valueName = msi_dup_record_field(row,4);
328 /* FIXME: valueName probably does too */
329 type = MSI_RecordGetInteger(row,5);
331 if ((type & 0x0f) != msidbLocatorTypeRawValue)
333 FIXME("AppSearch unimplemented for type %d (key path %s, value %s)\n",
334 type, debugstr_w(keyPath), debugstr_w(valueName));
335 goto end;
338 switch (root)
340 case msidbRegistryRootClassesRoot:
341 rootKey = HKEY_CLASSES_ROOT;
342 break;
343 case msidbRegistryRootCurrentUser:
344 rootKey = HKEY_CURRENT_USER;
345 break;
346 case msidbRegistryRootLocalMachine:
347 rootKey = HKEY_LOCAL_MACHINE;
348 break;
349 case msidbRegistryRootUsers:
350 rootKey = HKEY_USERS;
351 break;
352 default:
353 WARN("Unknown root key %d\n", root);
354 goto end;
357 rc = RegOpenKeyW(rootKey, keyPath, &key);
358 if (rc)
360 TRACE("RegCreateKeyW returned %d\n", rc);
361 rc = ERROR_SUCCESS;
362 goto end;
364 rc = RegQueryValueExW(key, valueName, NULL, NULL, NULL, &sz);
365 if (rc)
367 TRACE("RegQueryValueExW returned %d\n", rc);
368 rc = ERROR_SUCCESS;
369 goto end;
371 /* FIXME: sanity-check sz before allocating (is there an upper-limit
372 * on the value of a property?)
374 value = msi_alloc( sz);
375 rc = RegQueryValueExW(key, valueName, NULL, &regType, value, &sz);
376 if (rc)
378 TRACE("RegQueryValueExW returned %d\n", rc);
379 rc = ERROR_SUCCESS;
380 goto end;
383 /* bail out if the registry key is empty */
384 if (sz == 0)
386 rc = ERROR_SUCCESS;
387 goto end;
390 ACTION_ConvertRegValue(regType, value, sz, appValue);
392 end:
393 msi_free( value);
394 RegCloseKey(key);
396 msi_free( keyPath);
397 msi_free( valueName);
399 if (row)
400 msiobj_release(&row->hdr);
401 MSI_ViewClose(view);
402 msiobj_release(&view->hdr);
404 else
406 TRACE("MSI_OpenQuery returned %d\n", rc);
407 rc = ERROR_SUCCESS;
410 TRACE("returning %d\n", rc);
411 return rc;
414 static UINT ACTION_AppSearchIni(MSIPACKAGE *package, LPWSTR *appValue,
415 MSISIGNATURE *sig)
417 MSIQUERY *view;
418 UINT rc;
419 static const WCHAR ExecSeqQuery[] = {
420 's','e','l','e','c','t',' ','*',' ',
421 'f','r','o','m',' ',
422 'I','n','i','L','o','c','a','t','o','r',' ',
423 'w','h','e','r','e',' ','S','i','g','n','a','t','u','r','e','_',' ','=',' ',
424 '\'','%','s','\'',0};
426 TRACE("(package %p, appValue %p, sig %p)\n", package, appValue, sig);
427 *appValue = NULL;
428 rc = MSI_OpenQuery(package->db, &view, ExecSeqQuery, sig->Name);
429 if (rc == ERROR_SUCCESS)
431 MSIRECORD *row = 0;
432 LPWSTR fileName;
434 rc = MSI_ViewExecute(view, 0);
435 if (rc != ERROR_SUCCESS)
437 TRACE("MSI_ViewExecute returned %d\n", rc);
438 goto end;
440 rc = MSI_ViewFetch(view,&row);
441 if (rc != ERROR_SUCCESS)
443 TRACE("MSI_ViewFetch returned %d\n", rc);
444 rc = ERROR_SUCCESS;
445 goto end;
448 /* get file name */
449 fileName = msi_dup_record_field(row,2);
450 FIXME("AppSearch unimplemented for IniLocator (ini file name %s)\n",
451 debugstr_w(fileName));
452 msi_free( fileName);
454 end:
455 if (row)
456 msiobj_release(&row->hdr);
457 MSI_ViewClose(view);
458 msiobj_release(&view->hdr);
460 else
462 TRACE("MSI_OpenQuery returned %d\n", rc);
463 rc = ERROR_SUCCESS;
467 TRACE("returning %d\n", rc);
468 return rc;
471 /* Expands the value in src into a path without property names and only
472 * containing long path names into dst. Replaces at most len characters of dst,
473 * and always NULL-terminates dst if dst is not NULL and len >= 1.
474 * May modify src.
475 * Assumes src and dst are non-overlapping.
476 * FIXME: return code probably needed:
477 * - what does AppSearch return if the table values are invalid?
478 * - what if dst is too small?
480 static void ACTION_ExpandAnyPath(MSIPACKAGE *package, WCHAR *src, WCHAR *dst,
481 size_t len)
483 WCHAR *ptr;
484 size_t copied = 0;
486 if (!src || !dst || !len)
487 return;
489 /* Ignore the short portion of the path, don't think we can use it anyway */
490 if ((ptr = strchrW(src, '|')))
491 ptr++;
492 else
493 ptr = src;
494 while (*ptr && copied < len - 1)
496 WCHAR *prop = strchrW(ptr, '[');
498 if (prop)
500 WCHAR *propEnd = strchrW(prop + 1, ']');
502 if (!propEnd)
504 WARN("Unterminated property name in AnyPath: %s\n",
505 debugstr_w(prop));
506 break;
508 else
510 DWORD propLen;
512 *propEnd = 0;
513 propLen = len - copied - 1;
514 MSI_GetPropertyW(package, prop + 1, dst + copied, &propLen);
515 ptr = propEnd + 1;
516 copied += propLen;
519 else
521 size_t toCopy = min(strlenW(ptr) + 1, len - copied - 1);
523 memcpy(dst + copied, ptr, toCopy * sizeof(WCHAR));
524 ptr += toCopy;
525 copied += toCopy;
528 *(dst + copied) = '\0';
531 /* Sets *matches to whether the file (whose path is filePath) matches the
532 * versions set in sig.
533 * Return ERROR_SUCCESS in case of success (whether or not the file matches),
534 * something else if an install-halting error occurs.
536 static UINT ACTION_FileVersionMatches(MSISIGNATURE *sig, LPCWSTR filePath,
537 BOOL *matches)
539 UINT rc = ERROR_SUCCESS;
541 *matches = FALSE;
542 if (sig->Languages)
544 FIXME(": need to check version for languages %s\n",
545 debugstr_w(sig->Languages));
547 else
549 DWORD zero, size = GetFileVersionInfoSizeW(filePath, &zero);
551 if (size)
553 LPVOID buf = msi_alloc( size);
555 if (buf)
557 static WCHAR rootW[] = { '\\',0 };
558 UINT versionLen;
559 LPVOID subBlock = NULL;
561 if (GetFileVersionInfoW(filePath, 0, size, buf))
562 VerQueryValueW(buf, rootW, &subBlock, &versionLen);
563 if (subBlock)
565 VS_FIXEDFILEINFO *info =
566 (VS_FIXEDFILEINFO *)subBlock;
568 TRACE("Comparing file version %d.%d.%d.%d:\n",
569 HIWORD(info->dwFileVersionMS),
570 LOWORD(info->dwFileVersionMS),
571 HIWORD(info->dwFileVersionLS),
572 LOWORD(info->dwFileVersionLS));
573 if (info->dwFileVersionMS < sig->MinVersionMS
574 || (info->dwFileVersionMS == sig->MinVersionMS &&
575 info->dwFileVersionLS < sig->MinVersionLS))
577 TRACE("Less than minimum version %d.%d.%d.%d\n",
578 HIWORD(sig->MinVersionMS),
579 LOWORD(sig->MinVersionMS),
580 HIWORD(sig->MinVersionLS),
581 LOWORD(sig->MinVersionLS));
583 else if (info->dwFileVersionMS < sig->MinVersionMS
584 || (info->dwFileVersionMS == sig->MinVersionMS &&
585 info->dwFileVersionLS < sig->MinVersionLS))
587 TRACE("Greater than minimum version %d.%d.%d.%d\n",
588 HIWORD(sig->MaxVersionMS),
589 LOWORD(sig->MaxVersionMS),
590 HIWORD(sig->MaxVersionLS),
591 LOWORD(sig->MaxVersionLS));
593 else
594 *matches = TRUE;
596 msi_free( buf);
598 else
599 rc = ERROR_OUTOFMEMORY;
602 return rc;
605 /* Sets *matches to whether the file in findData matches that in sig.
606 * fullFilePath is assumed to be the full path of the file specified in
607 * findData, which may be necessary to compare the version.
608 * Return ERROR_SUCCESS in case of success (whether or not the file matches),
609 * something else if an install-halting error occurs.
611 static UINT ACTION_FileMatchesSig(MSISIGNATURE *sig,
612 LPWIN32_FIND_DATAW findData, LPCWSTR fullFilePath, BOOL *matches)
614 UINT rc = ERROR_SUCCESS;
616 *matches = TRUE;
617 /* assumes the caller has already ensured the filenames match, so check
618 * the other fields..
620 if (sig->MinTime.dwLowDateTime || sig->MinTime.dwHighDateTime)
622 if (findData->ftCreationTime.dwHighDateTime <
623 sig->MinTime.dwHighDateTime ||
624 (findData->ftCreationTime.dwHighDateTime == sig->MinTime.dwHighDateTime
625 && findData->ftCreationTime.dwLowDateTime <
626 sig->MinTime.dwLowDateTime))
627 *matches = FALSE;
629 if (*matches && (sig->MaxTime.dwLowDateTime || sig->MaxTime.dwHighDateTime))
631 if (findData->ftCreationTime.dwHighDateTime >
632 sig->MaxTime.dwHighDateTime ||
633 (findData->ftCreationTime.dwHighDateTime == sig->MaxTime.dwHighDateTime
634 && findData->ftCreationTime.dwLowDateTime >
635 sig->MaxTime.dwLowDateTime))
636 *matches = FALSE;
638 if (*matches && sig->MinSize && findData->nFileSizeLow < sig->MinSize)
639 *matches = FALSE;
640 if (*matches && sig->MaxSize && findData->nFileSizeLow > sig->MaxSize)
641 *matches = FALSE;
642 if (*matches && (sig->MinVersionMS || sig->MinVersionLS ||
643 sig->MaxVersionMS || sig->MaxVersionLS))
644 rc = ACTION_FileVersionMatches(sig, fullFilePath, matches);
645 return rc;
648 /* Recursively searches the directory dir for files that match the signature
649 * sig, up to (depth + 1) levels deep. That is, if depth is 0, it searches dir
650 * (and only dir). If depth is 1, searches dir and its immediate
651 * subdirectories.
652 * Assumes sig->File is not NULL.
653 * Returns ERROR_SUCCESS on success (which may include non-critical errors),
654 * something else on failures which should halt the install.
656 static UINT ACTION_RecurseSearchDirectory(MSIPACKAGE *package, LPWSTR *appValue,
657 MSISIGNATURE *sig, LPCWSTR dir, int depth)
659 static const WCHAR starDotStarW[] = { '*','.','*',0 };
660 UINT rc = ERROR_SUCCESS;
661 size_t dirLen = lstrlenW(dir), fileLen = lstrlenW(sig->File);
662 WCHAR *buf;
664 TRACE("Searching directory %s for file %s, depth %d\n", debugstr_w(dir),
665 debugstr_w(sig->File), depth);
667 if (depth < 0)
668 return ERROR_INVALID_PARAMETER;
670 *appValue = NULL;
671 /* We need the buffer in both paths below, so go ahead and allocate it
672 * here. Add two because we might need to add a backslash if the dir name
673 * isn't backslash-terminated.
675 buf = msi_alloc( (dirLen + max(fileLen, lstrlenW(starDotStarW)) + 2) * sizeof(WCHAR));
676 if (buf)
678 /* a depth of 0 implies we should search dir, so go ahead and search */
679 HANDLE hFind;
680 WIN32_FIND_DATAW findData;
682 memcpy(buf, dir, dirLen * sizeof(WCHAR));
683 if (buf[dirLen - 1] != '\\')
684 buf[dirLen++ - 1] = '\\';
685 memcpy(buf + dirLen, sig->File, (fileLen + 1) * sizeof(WCHAR));
686 hFind = FindFirstFileW(buf, &findData);
687 if (hFind != INVALID_HANDLE_VALUE)
689 BOOL matches;
691 /* assuming Signature can't contain wildcards for the file name,
692 * so don't bother with FindNextFileW here.
694 if (!(rc = ACTION_FileMatchesSig(sig, &findData, buf, &matches))
695 && matches)
697 TRACE("found file, returning %s\n", debugstr_w(buf));
698 *appValue = buf;
700 FindClose(hFind);
702 if (rc == ERROR_SUCCESS && !*appValue && depth > 0)
704 HANDLE hFind;
705 WIN32_FIND_DATAW findData;
707 memcpy(buf, dir, dirLen * sizeof(WCHAR));
708 if (buf[dirLen - 1] != '\\')
709 buf[dirLen++ - 1] = '\\';
710 lstrcpyW(buf + dirLen, starDotStarW);
711 hFind = FindFirstFileW(buf, &findData);
712 if (hFind != INVALID_HANDLE_VALUE)
714 if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
715 rc = ACTION_RecurseSearchDirectory(package, appValue, sig,
716 findData.cFileName, depth - 1);
717 while (rc == ERROR_SUCCESS && !*appValue &&
718 FindNextFileW(hFind, &findData) != 0)
720 if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
721 rc = ACTION_RecurseSearchDirectory(package, appValue,
722 sig, findData.cFileName, depth - 1);
724 FindClose(hFind);
727 if (!*appValue)
728 msi_free(buf);
730 else
731 rc = ERROR_OUTOFMEMORY;
733 return rc;
736 static UINT ACTION_CheckDirectory(MSIPACKAGE *package, LPCWSTR dir,
737 LPWSTR *appValue)
739 UINT rc = ERROR_SUCCESS;
741 if (GetFileAttributesW(dir) & FILE_ATTRIBUTE_DIRECTORY)
743 TRACE("directory exists, returning %s\n", debugstr_w(dir));
744 *appValue = strdupW(dir);
746 else
747 *appValue = NULL;
748 return rc;
751 static BOOL ACTION_IsFullPath(LPCWSTR path)
753 WCHAR first = toupperW(path[0]);
754 BOOL ret;
756 if (first >= 'A' && first <= 'Z' && path[1] == ':')
757 ret = TRUE;
758 else if (path[0] == '\\' && path[1] == '\\')
759 ret = TRUE;
760 else
761 ret = FALSE;
762 return ret;
765 static UINT ACTION_SearchDirectory(MSIPACKAGE *package, MSISIGNATURE *sig,
766 LPCWSTR path, int depth, LPWSTR *appValue)
768 UINT rc;
770 TRACE("%p, %p, %s, %d, %p\n", package, sig, debugstr_w(path), depth,
771 appValue);
772 if (ACTION_IsFullPath(path))
774 if (sig->File)
775 rc = ACTION_RecurseSearchDirectory(package, appValue, sig,
776 path, depth);
777 else
779 /* Recursively searching a directory makes no sense when the
780 * directory to search is the thing you're trying to find.
782 rc = ACTION_CheckDirectory(package, path, appValue);
785 else
787 WCHAR pathWithDrive[MAX_PATH] = { 'C',':','\\',0 };
788 DWORD drives = GetLogicalDrives();
789 int i;
791 rc = ERROR_SUCCESS;
792 *appValue = NULL;
793 for (i = 0; rc == ERROR_SUCCESS && !*appValue && i < 26; i++)
794 if (drives & (1 << drives))
796 pathWithDrive[0] = 'A' + i;
797 if (GetDriveTypeW(pathWithDrive) == DRIVE_FIXED)
799 lstrcpynW(pathWithDrive + 3, path,
800 sizeof(pathWithDrive) / sizeof(pathWithDrive[0]) - 3);
801 if (sig->File)
802 rc = ACTION_RecurseSearchDirectory(package, appValue,
803 sig, pathWithDrive, depth);
804 else
805 rc = ACTION_CheckDirectory(package, pathWithDrive,
806 appValue);
810 TRACE("returning %d\n", rc);
811 return rc;
814 static UINT ACTION_AppSearchDr(MSIPACKAGE *package, LPWSTR *appValue,
815 MSISIGNATURE *sig)
817 MSIQUERY *view;
818 UINT rc;
819 static const WCHAR ExecSeqQuery[] = {
820 's','e','l','e','c','t',' ','*',' ',
821 'f','r','o','m',' ',
822 'D','r','L','o','c','a','t','o','r',' ',
823 'w','h','e','r','e',' ','S','i','g','n','a','t','u','r','e','_',' ','=',' ',
824 '\'','%','s','\'',0};
826 TRACE("(package %p, sig %p)\n", package, sig);
827 rc = MSI_OpenQuery(package->db, &view, ExecSeqQuery, sig->Name);
828 if (rc == ERROR_SUCCESS)
830 MSIRECORD *row = 0;
831 WCHAR buffer[MAX_PATH], expanded[MAX_PATH];
832 DWORD sz;
833 int depth;
835 rc = MSI_ViewExecute(view, 0);
836 if (rc != ERROR_SUCCESS)
838 TRACE("MSI_ViewExecute returned %d\n", rc);
839 goto end;
841 rc = MSI_ViewFetch(view,&row);
842 if (rc != ERROR_SUCCESS)
844 TRACE("MSI_ViewFetch returned %d\n", rc);
845 rc = ERROR_SUCCESS;
846 goto end;
849 /* check whether parent is set */
850 buffer[0] = 0;
851 sz=sizeof(buffer)/sizeof(buffer[0]);
852 rc = MSI_RecordGetStringW(row,2,buffer,&sz);
853 if (rc != ERROR_SUCCESS)
855 ERR("Error is %x\n",rc);
856 goto end;
858 else if (buffer[0])
860 FIXME(": searching parent (%s) unimplemented\n",
861 debugstr_w(buffer));
862 goto end;
864 /* no parent, now look for path */
865 buffer[0] = 0;
866 sz=sizeof(buffer)/sizeof(buffer[0]);
867 rc = MSI_RecordGetStringW(row,3,buffer,&sz);
868 if (rc != ERROR_SUCCESS)
870 ERR("Error is %x\n",rc);
871 goto end;
873 if (MSI_RecordIsNull(row,4))
874 depth = 0;
875 else
876 depth = MSI_RecordGetInteger(row,4);
877 ACTION_ExpandAnyPath(package, buffer, expanded,
878 sizeof(expanded) / sizeof(expanded[0]));
879 rc = ACTION_SearchDirectory(package, sig, expanded, depth, appValue);
881 end:
882 if (row)
883 msiobj_release(&row->hdr);
884 MSI_ViewClose(view);
885 msiobj_release(&view->hdr);
887 else
889 TRACE("MSI_OpenQuery returned %d\n", rc);
890 rc = ERROR_SUCCESS;
894 TRACE("returning %d\n", rc);
895 return rc;
898 /* http://msdn.microsoft.com/library/en-us/msi/setup/appsearch_table.asp
899 * is the best reference for the AppSearch table and how it's used.
901 UINT ACTION_AppSearch(MSIPACKAGE *package)
903 MSIQUERY *view;
904 UINT rc;
905 static const WCHAR ExecSeqQuery[] = {
906 's','e','l','e','c','t',' ','*',' ',
907 'f','r','o','m',' ',
908 'A','p','p','S','e','a','r','c','h',0};
910 rc = MSI_OpenQuery(package->db, &view, ExecSeqQuery);
911 if (rc == ERROR_SUCCESS)
913 MSIRECORD *row = 0;
914 WCHAR propBuf[0x100], sigBuf[0x100];
915 DWORD sz;
917 rc = MSI_ViewExecute(view, 0);
918 if (rc != ERROR_SUCCESS)
919 goto end;
921 while (!rc)
923 MSISIGNATURE sig;
924 LPWSTR value;
926 rc = MSI_ViewFetch(view,&row);
927 if (rc != ERROR_SUCCESS)
929 rc = ERROR_SUCCESS;
930 break;
933 /* get property and signature */
934 propBuf[0] = 0;
935 sz=sizeof(propBuf)/sizeof(propBuf[0]);
936 rc = MSI_RecordGetStringW(row,1,propBuf,&sz);
937 if (rc != ERROR_SUCCESS)
939 ERR("Error is %x\n",rc);
940 msiobj_release(&row->hdr);
941 break;
943 sigBuf[0] = 0;
944 sz=sizeof(sigBuf)/sizeof(sigBuf[0]);
945 rc = MSI_RecordGetStringW(row,2,sigBuf,&sz);
946 if (rc != ERROR_SUCCESS)
948 ERR("Error is %x\n",rc);
949 msiobj_release(&row->hdr);
950 break;
952 TRACE("Searching for Property %s, Signature_ %s\n",
953 debugstr_w(propBuf), debugstr_w(sigBuf));
954 /* This clears all the fields, so set Name and Property afterward */
955 rc = ACTION_AppSearchGetSignature(package, &sig, sigBuf);
956 sig.Name = sigBuf;
957 if (rc == ERROR_SUCCESS)
959 rc = ACTION_AppSearchComponents(package, &value, &sig);
960 if (rc == ERROR_SUCCESS && !value)
962 rc = ACTION_AppSearchReg(package, &value, &sig);
963 if (rc == ERROR_SUCCESS && !value)
965 rc = ACTION_AppSearchIni(package, &value, &sig);
966 if (rc == ERROR_SUCCESS && !value)
967 rc = ACTION_AppSearchDr(package, &value, &sig);
971 if (value)
973 MSI_SetPropertyW(package, propBuf, value);
974 msi_free(value);
976 msi_free( sig.File);
977 msi_free( sig.Languages);
978 msiobj_release(&row->hdr);
981 end:
982 MSI_ViewClose(view);
983 msiobj_release(&view->hdr);
985 else
986 rc = ERROR_SUCCESS;
988 return rc;