2 * Copyright 2011 André Hentschel
3 * Copyright 2013 Mislav Blažević
4 * Copyright 2015-2017 Mark Jansen
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
21 #define WIN32_NO_STATUS
27 #include "wine/unicode.h"
29 #define MAX_LAYER_LENGTH 256
31 #define GPLK_MACHINE 2
33 static BOOL WINAPI
SdbpFileExists(LPCWSTR path
)
35 DWORD attr
= GetFileAttributesW(path
);
36 return (attr
!= INVALID_FILE_ATTRIBUTES
&& !(attr
& FILE_ATTRIBUTE_DIRECTORY
));
39 static BOOL WINAPI
SdbpMatchExe(PDB db
, TAGID exe
, WCHAR
* dir
)
41 static const WCHAR fmt
[] = {'%','s','%','s',0};
42 WCHAR buffer
[256]; /* FIXME: rewrite using a buffer that can grow if needed, f.e. RtlInitBuffer stuff! */
45 /* TODO: check size/checksum from the main exe as well as from the extra files */
46 for (matching_file
= SdbFindFirstTag(db
, exe
, TAG_MATCHING_FILE
);
47 matching_file
!= TAGID_NULL
; matching_file
= SdbFindNextTag(db
, exe
, matching_file
))
49 TAGID tagName
= SdbFindFirstTag(db
, matching_file
, TAG_NAME
);
50 LPWSTR name
= SdbGetStringTagPtr(db
, tagName
);
52 if (!wcscmp(name
, L
"*"))
54 // if attributes dont match main file, return FALSE!
58 snprintfW(buffer
, _countof(buffer
), fmt
, dir
, name
);
59 if (!SdbpFileExists(buffer
))
66 static void SdbpAddDatabaseGuid(PDB db
, PSDBQUERYRESULT result
)
70 for (n
= 0; n
< _countof(result
->rgGuidDB
); ++n
)
72 if (!memcmp(&result
->rgGuidDB
[n
], &db
->database_id
, sizeof(db
->database_id
)))
75 if (result
->dwCustomSDBMap
& (1<<n
))
78 memcpy(&result
->rgGuidDB
[n
], &db
->database_id
, sizeof(result
->rgGuidDB
[n
]));
79 result
->dwCustomSDBMap
|= (1<<n
);
84 static BOOL
SdbpAddSingleLayerMatch(TAGREF layer
, PSDBQUERYRESULT result
)
88 for (n
= 0; n
< result
->dwLayerCount
; ++n
)
90 if (result
->atrLayers
[n
] == layer
)
94 if (n
>= _countof(result
->atrLayers
))
97 result
->atrLayers
[n
] = layer
;
98 result
->dwLayerCount
++;
104 static BOOL
SdbpAddNamedLayerMatch(HSDB hsdb
, PCWSTR layerName
, PSDBQUERYRESULT result
)
106 TAGID database
, layer
;
110 database
= SdbFindFirstTag(db
, TAGID_ROOT
, TAG_DATABASE
);
111 if (database
== TAGID_NULL
)
114 layer
= SdbFindFirstNamedTag(db
, database
, TAG_LAYER
, TAG_NAME
, layerName
);
115 if (layer
== TAGID_NULL
)
118 if (!SdbTagIDToTagRef(hsdb
, db
, layer
, &tr
))
121 if (!SdbpAddSingleLayerMatch(tr
, result
))
124 SdbpAddDatabaseGuid(db
, result
);
128 static void SdbpAddExeLayers(HSDB hsdb
, PDB db
, TAGID tagExe
, PSDBQUERYRESULT result
)
130 TAGID layer
= SdbFindFirstTag(db
, tagExe
, TAG_LAYER
);
132 while (layer
!= TAGID_NULL
)
135 TAGID layerIdTag
= SdbFindFirstTag(db
, layer
, TAG_LAYER_TAGID
);
136 DWORD tagId
= SdbReadDWORDTag(db
, layerIdTag
, TAGID_NULL
);
138 if (layerIdTag
!= TAGID_NULL
&&
139 tagId
!= TAGID_NULL
&&
140 SdbTagIDToTagRef(hsdb
, db
, tagId
, &tr
))
142 SdbpAddSingleLayerMatch(tr
, result
);
146 /* Try a name lookup */
147 TAGID layerTag
= SdbFindFirstTag(db
, layer
, TAG_NAME
);
148 if (layerTag
!= TAGID_NULL
)
150 LPCWSTR layerName
= SdbGetStringTagPtr(db
, layerTag
);
153 SdbpAddNamedLayerMatch(hsdb
, layerName
, result
);
158 layer
= SdbFindNextTag(db
, tagExe
, layer
);
162 static void SdbpAddExeMatch(HSDB hsdb
, PDB db
, TAGID tagExe
, PSDBQUERYRESULT result
)
167 if (!SdbTagIDToTagRef(hsdb
, db
, tagExe
, &tr
))
170 for (n
= 0; n
< result
->dwExeCount
; ++n
)
172 if (result
->atrExes
[n
] == tr
)
176 if (n
>= _countof(result
->atrExes
))
179 result
->atrExes
[n
] = tr
;
180 result
->dwExeCount
++;
182 SdbpAddExeLayers(hsdb
, db
, tagExe
, result
);
184 SdbpAddDatabaseGuid(db
, result
);
187 static ULONG
SdbpAddLayerMatches(HSDB hsdb
, PWSTR pwszLayers
, DWORD pdwBytes
, PSDBQUERYRESULT result
)
189 PWSTR start
= pwszLayers
, p
;
192 const PWSTR end
= pwszLayers
+ (pdwBytes
/ sizeof(WCHAR
));
193 while (start
< end
&& (*start
== L
'!' || *start
== L
'#' || *start
== L
' ' || *start
== L
'\t'))
201 while (*start
== L
' ' || *start
== L
'\t')
204 if (*start
== UNICODE_NULL
)
206 p
= wcspbrk(start
, L
" \t");
211 if (SdbpAddNamedLayerMatch(hsdb
, start
, result
))
215 } while (start
< end
&& p
);
220 static BOOL
SdbpPropagateEnvLayers(HSDB hsdb
, LPWSTR Environment
, PSDBQUERYRESULT Result
)
222 static const UNICODE_STRING EnvKey
= RTL_CONSTANT_STRING(L
"__COMPAT_LAYER");
223 UNICODE_STRING EnvValue
;
225 WCHAR Buffer
[MAX_LAYER_LENGTH
];
227 RtlInitEmptyUnicodeString(&EnvValue
, Buffer
, sizeof(Buffer
));
229 Status
= RtlQueryEnvironmentVariable_U(Environment
, &EnvKey
, &EnvValue
);
231 if (!NT_SUCCESS(Status
))
234 return SdbpAddLayerMatches(hsdb
, Buffer
, EnvValue
.Length
, Result
) > 0;
240 * Opens specified shim database file Handle returned by this function may only be used by
241 * functions which take HSDB param thus differing it from SdbOpenDatabase.
243 * @param [in] flags Specifies type of path or predefined database.
244 * @param [in] path Path to the shim database file.
246 * @return Success: Handle to the opened shim database, NULL otherwise.
248 HSDB WINAPI
SdbInitDatabase(DWORD flags
, LPCWSTR path
)
250 static const WCHAR shim
[] = {'\\','s','y','s','m','a','i','n','.','s','d','b',0};
251 static const WCHAR msi
[] = {'\\','m','s','i','m','a','i','n','.','s','d','b',0};
252 static const WCHAR drivers
[] = {'\\','d','r','v','m','a','i','n','.','s','d','b',0};
257 hsdb
= SdbAlloc(sizeof(SDB
));
260 hsdb
->auto_loaded
= 0;
262 /* Check for predefined databases */
263 if ((flags
& HID_DATABASE_TYPE_MASK
) && path
== NULL
)
265 switch (flags
& HID_DATABASE_TYPE_MASK
)
267 case SDB_DATABASE_MAIN_SHIM
: name
= shim
; break;
268 case SDB_DATABASE_MAIN_MSI
: name
= msi
; break;
269 case SDB_DATABASE_MAIN_DRIVERS
: name
= drivers
; break;
271 SdbReleaseDatabase(hsdb
);
274 SdbGetAppPatchDir(NULL
, buffer
, 128);
275 memcpy(buffer
+ lstrlenW(buffer
), name
, SdbpStrsize(name
));
276 flags
= HID_DOS_PATHS
;
279 hsdb
->db
= SdbOpenDatabase(path
? path
: buffer
, (flags
& 0xF) - 1);
281 /* If database could not be loaded, a handle doesn't make sense either */
284 SdbReleaseDatabase(hsdb
);
292 * Closes shim database opened by SdbInitDatabase.
294 * @param [in] hsdb Handle to the shim database.
296 void WINAPI
SdbReleaseDatabase(HSDB hsdb
)
298 SdbCloseDatabase(hsdb
->db
);
303 * Queries database for a specified exe If hsdb is NULL default database shall be loaded and
306 * @param [in] hsdb Handle to the shim database.
307 * @param [in] path Path to executable for which we query database.
308 * @param [in] module_name Unused.
309 * @param [in] env The environment block to use
310 * @param [in] flags 0 or SDBGMEF_IGNORE_ENVIRONMENT.
311 * @param [out] result Pointer to structure in which query result shall be stored.
313 * @return TRUE if it succeeds, FALSE if it fails.
315 BOOL WINAPI
SdbGetMatchingExe(HSDB hsdb
, LPCWSTR path
, LPCWSTR module_name
,
316 LPCWSTR env
, DWORD flags
, PSDBQUERYRESULT result
)
319 TAGID database
, iter
, name
;
320 PATTRINFO attribs
= NULL
;
321 /*DWORD attr_count;*/
322 RTL_UNICODE_STRING_BUFFER DosApplicationName
= { { 0 } };
323 WCHAR DosPathBuffer
[MAX_PATH
];
326 WCHAR wszLayers
[MAX_LAYER_LENGTH
];
330 /* Load default database if one is not specified */
333 /* To reproduce windows behaviour HID_DOS_PATHS needs
334 * to be specified when loading default database */
335 hsdb
= SdbInitDatabase(HID_DOS_PATHS
| SDB_DATABASE_MAIN_SHIM
, NULL
);
337 hsdb
->auto_loaded
= TRUE
;
340 ZeroMemory(result
, sizeof(*result
));
342 /* No database could be loaded */
346 /* We do not support multiple db's yet! */
349 RtlInitUnicodeString(&DosApplicationName
.String
, path
);
350 RtlInitBuffer(&DosApplicationName
.ByteBuffer
, (PUCHAR
)DosPathBuffer
, sizeof(DosPathBuffer
));
351 if (!NT_SUCCESS(RtlEnsureBufferSize(RTL_SKIP_BUFFER_COPY
, &DosApplicationName
.ByteBuffer
, DosApplicationName
.String
.MaximumLength
)))
353 SHIM_ERR("Failed to convert allocate buffer.");
356 /* Update the internal buffer to contain the string */
357 memcpy(DosApplicationName
.ByteBuffer
.Buffer
, path
, DosApplicationName
.String
.MaximumLength
);
358 /* Make sure the string uses our internal buffer (we want to modify the buffer,
359 and RtlNtPathNameToDosPathName does not always modify the String to point to the Buffer)! */
360 DosApplicationName
.String
.Buffer
= (PWSTR
)DosApplicationName
.ByteBuffer
.Buffer
;
362 if (!NT_SUCCESS(RtlNtPathNameToDosPathName(0, &DosApplicationName
, &PathType
, NULL
)))
364 SHIM_ERR("Failed to convert %S to DOS Path.", path
);
369 /* Extract file name */
370 file_name
= strrchrW(DosApplicationName
.String
.Buffer
, '\\');
373 SHIM_ERR("Failed to find Exe name in %wZ.", &DosApplicationName
.String
);
377 /* We will use the buffer for exe name and directory. */
378 *(file_name
++) = UNICODE_NULL
;
380 /* Get information about executable required to match it with database entry */
381 /*if (!SdbGetFileAttributes(path, &attribs, &attr_count))
384 /* DATABASE is list TAG which contains all executables */
385 database
= SdbFindFirstTag(db
, TAGID_ROOT
, TAG_DATABASE
);
386 if (database
== TAGID_NULL
)
391 /* EXE is list TAG which contains data required to match executable */
392 iter
= SdbFindFirstTag(db
, database
, TAG_EXE
);
394 /* Search for entry in database, we should look into indexing tags! */
395 while (iter
!= TAGID_NULL
)
398 /* Check if exe name matches */
399 name
= SdbFindFirstTag(db
, iter
, TAG_NAME
);
400 /* If this is a malformed DB, (no TAG_NAME), we should not crash. */
401 foundName
= SdbGetStringTagPtr(db
, name
);
402 if (foundName
&& !lstrcmpiW(foundName
, file_name
))
404 /* We have a null terminator before the application name, so DosApplicationName only contains the path. */
405 if (SdbpMatchExe(db
, iter
, DosApplicationName
.String
.Buffer
))
408 SdbpAddExeMatch(hsdb
, db
, iter
, result
);
412 /* Continue iterating */
413 iter
= SdbFindNextTag(db
, database
, iter
);
416 /* Restore the full path. */
417 *(--file_name
) = L
'\\';
419 dwSize
= sizeof(wszLayers
);
420 if (SdbGetPermLayerKeys(DosApplicationName
.String
.Buffer
, wszLayers
, &dwSize
, GPLK_MACHINE
| GPLK_USER
))
422 SdbpAddLayerMatches(hsdb
, wszLayers
, dwSize
, result
);
426 if (!(flags
& SDBGMEF_IGNORE_ENVIRONMENT
))
428 if (SdbpPropagateEnvLayers(hsdb
, (LPWSTR
)env
, result
))
431 result
->dwFlags
|= SHIMREG_HAS_ENVIRONMENT
;
436 RtlFreeBuffer(&DosApplicationName
.ByteBuffer
);
438 SdbFreeFileAttributes(attribs
);
439 if (hsdb
->auto_loaded
)
440 SdbReleaseDatabase(hsdb
);
445 * Retrieves AppPatch directory.
447 * @param [in] db Handle to the shim database.
448 * @param [out] path Pointer to memory in which path shall be written.
449 * @param [in] size Size of the buffer in characters.
451 BOOL WINAPI
SdbGetAppPatchDir(HSDB db
, LPWSTR path
, DWORD size
)
453 static WCHAR
* default_dir
= NULL
;
454 static CONST WCHAR szAppPatch
[] = {'\\','A','p','p','P','a','t','c','h',0};
456 /* In case function fails, path holds empty string */
463 UINT len
= GetSystemWindowsDirectoryW(NULL
, 0) + lstrlenW(szAppPatch
);
464 tmp
= SdbAlloc((len
+ 1)* sizeof(WCHAR
));
467 UINT r
= GetSystemWindowsDirectoryW(tmp
, len
+1);
470 if (SUCCEEDED(StringCchCatW(tmp
, len
+1, szAppPatch
)))
472 if (InterlockedCompareExchangePointer((void**)&default_dir
, tmp
, NULL
) == NULL
)
481 SHIM_ERR("Unable to obtain default AppPatch directory\n");
488 return SUCCEEDED(StringCchCopyW(path
, size
, default_dir
));
492 SHIM_ERR("Unimplemented for db != NULL\n");
499 * Translates the given trWhich to a specific database / tagid
501 * @param [in] hsdb Handle to the database.
502 * @param [in] trWhich Tagref to find
503 * @param [out,opt] ppdb The Shim database that trWhich belongs to.
504 * @param [out,opt] ptiWhich The tagid that trWhich corresponds to.
506 * @return TRUE if it succeeds, FALSE if it fails.
508 BOOL WINAPI
SdbTagRefToTagID(HSDB hsdb
, TAGREF trWhich
, PDB
* ppdb
, TAGID
* ptiWhich
)
510 if (trWhich
& 0xf0000000)
512 SHIM_ERR("Multiple shim databases not yet implemented!\n");
516 *ptiWhich
= TAG_NULL
;
520 /* There seems to be no range checking on trWhich.. */
524 *ptiWhich
= trWhich
& 0x0fffffff;
530 * Translates the given trWhich to a specific database / tagid
532 * @param [in] hsdb Handle to the database.
533 * @param [in] pdb The Shim database that tiWhich belongs to.
534 * @param [in] tiWhich Path to executable for which we query database.
535 * @param [out,opt] ptrWhich The tagid that tiWhich corresponds to.
537 * @return TRUE if it succeeds, FALSE if it fails.
539 BOOL WINAPI
SdbTagIDToTagRef(HSDB hsdb
, PDB pdb
, TAGID tiWhich
, TAGREF
* ptrWhich
)
543 SHIM_ERR("Multiple shim databases not yet implemented!\n");
545 *ptrWhich
= TAGREF_NULL
;
550 *ptrWhich
= tiWhich
& 0x0fffffff;