2 * versioninfo.c: Version information support
5 * Dick Porter (dick@ximian.com)
7 * (C) 2007 Novell, Inc.
15 #include <sys/types.h>
21 #include <mono/io-layer/wapi.h>
22 #include <mono/io-layer/wapi-private.h>
23 #include <mono/io-layer/versioninfo.h>
24 #include <mono/io-layer/io-portability.h>
25 #include <mono/io-layer/error.h>
26 #include <mono/utils/strenc.h>
30 static WapiImageSectionHeader *get_enclosing_section_header (guint32 rva, WapiImageNTHeaders *nt_headers)
32 WapiImageSectionHeader *section = IMAGE_FIRST_SECTION (nt_headers);
35 for (i = 0; i < nt_headers->FileHeader.NumberOfSections; i++, section++) {
36 guint32 size = section->Misc.VirtualSize;
38 size = section->SizeOfRawData;
41 if ((rva >= section->VirtualAddress) &&
42 (rva < (section->VirtualAddress + size))) {
50 static gpointer get_ptr_from_rva (guint32 rva, WapiImageNTHeaders *ntheaders,
53 WapiImageSectionHeader *section_header;
56 section_header = get_enclosing_section_header (rva, ntheaders);
57 if (section_header == NULL) {
61 delta = (guint32)(section_header->VirtualAddress -
62 section_header->PointerToRawData);
64 return((guint8 *)file_map + rva - delta);
67 static gpointer scan_resource_dir (WapiImageResourceDirectory *root,
68 WapiImageNTHeaders *nt_headers,
70 WapiImageResourceDirectoryEntry *entry,
71 int level, guint32 res_id, guint32 lang_id,
74 gboolean is_string = entry->NameIsString;
75 gboolean is_dir = entry->DataIsDirectory;
76 guint32 name_offset = GUINT32_FROM_LE (entry->NameOffset);
77 guint32 dir_offset = GUINT32_FROM_LE (entry->OffsetToDirectory);
78 guint32 data_offset = GUINT32_FROM_LE (entry->OffsetToData);
81 /* Normally holds a directory entry for each type of
84 if ((is_string == FALSE &&
85 name_offset != res_id) ||
86 (is_string == TRUE)) {
89 } else if (level == 1) {
90 /* Normally holds a directory entry for each resource
93 } else if (level == 2) {
94 /* Normally holds a directory entry for each language
96 if ((is_string == FALSE &&
97 name_offset != lang_id &&
99 (is_string == TRUE)) {
103 g_assert_not_reached ();
106 if (is_dir == TRUE) {
107 WapiImageResourceDirectory *res_dir = (WapiImageResourceDirectory *)((guint8 *)root + dir_offset);
108 WapiImageResourceDirectoryEntry *sub_entries = (WapiImageResourceDirectoryEntry *)(res_dir + 1);
111 entries = GUINT16_FROM_LE (res_dir->NumberOfNamedEntries) + GUINT16_FROM_LE (res_dir->NumberOfIdEntries);
113 for (i = 0; i < entries; i++) {
114 WapiImageResourceDirectoryEntry *sub_entry = &sub_entries[i];
117 ret = scan_resource_dir (root, nt_headers, file_map,
118 sub_entry, level + 1, res_id,
127 WapiImageResourceDataEntry *data_entry = (WapiImageResourceDataEntry *)((guint8 *)root + data_offset);
128 *size = GUINT32_FROM_LE (data_entry->Size);
130 return(get_ptr_from_rva (data_entry->OffsetToData, nt_headers, file_map));
134 static gpointer find_pe_file_resources (gpointer file_map, guint32 map_size,
135 guint32 res_id, guint32 lang_id,
138 WapiImageDosHeader *dos_header;
139 WapiImageNTHeaders *nt_headers;
140 WapiImageResourceDirectory *resource_dir;
141 WapiImageResourceDirectoryEntry *resource_dir_entry;
142 guint32 resource_rva, entries, i;
145 dos_header = (WapiImageDosHeader *)file_map;
146 if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) {
148 g_message ("%s: Bad dos signature 0x%x", __func__,
149 dos_header->e_magic);
152 SetLastError (ERROR_INVALID_DATA);
156 if (map_size < sizeof(WapiImageNTHeaders) + GUINT32_FROM_LE (dos_header->e_lfanew)) {
158 g_message ("%s: File is too small: %d", __func__, map_size);
161 SetLastError (ERROR_BAD_LENGTH);
165 nt_headers = (WapiImageNTHeaders *)((guint8 *)file_map + GUINT32_FROM_LE (dos_header->e_lfanew));
166 if (nt_headers->Signature != IMAGE_NT_SIGNATURE) {
168 g_message ("%s: Bad NT signature 0x%x", __func__,
169 nt_headers->Signature);
172 SetLastError (ERROR_INVALID_DATA);
176 if (nt_headers->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
177 /* Do 64-bit stuff */
178 resource_rva = GUINT32_FROM_LE (((WapiImageNTHeaders64 *)nt_headers)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress);
180 resource_rva = GUINT32_FROM_LE (nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress);
183 if (resource_rva == 0) {
185 g_message ("%s: No resources in file!", __func__);
187 SetLastError (ERROR_INVALID_DATA);
191 resource_dir = (WapiImageResourceDirectory *)get_ptr_from_rva (resource_rva, nt_headers, file_map);
192 if (resource_dir == NULL) {
194 g_message ("%s: Can't find resource directory", __func__);
196 SetLastError (ERROR_INVALID_DATA);
200 entries = GUINT16_FROM_LE (resource_dir->NumberOfNamedEntries) + GUINT16_FROM_LE (resource_dir->NumberOfIdEntries);
201 resource_dir_entry = (WapiImageResourceDirectoryEntry *)(resource_dir + 1);
203 for (i = 0; i < entries; i++) {
204 WapiImageResourceDirectoryEntry *direntry = &resource_dir_entry[i];
205 ret = scan_resource_dir (resource_dir, nt_headers, file_map,
206 direntry, 0, res_id, lang_id, size);
215 static gpointer map_pe_file (gunichar2 *filename, guint32 *map_size)
222 /* According to the MSDN docs, a search path is applied to
223 * filename. FIXME: implement this, for now just pass it
227 filename_ext = mono_unicode_to_external (filename);
228 if (filename_ext == NULL) {
230 g_message ("%s: unicode conversion returned NULL", __func__);
233 SetLastError (ERROR_INVALID_NAME);
237 fd = _wapi_open (filename_ext, O_RDONLY, 0);
240 g_message ("%s: Error opening file %s: %s", __func__,
241 filename_ext, strerror (errno));
244 SetLastError (_wapi_get_win32_file_error (errno));
245 g_free (filename_ext);
250 if (fstat (fd, &statbuf) == -1) {
252 g_message ("%s: Error stat()ing file %s: %s", __func__,
253 filename_ext, strerror (errno));
256 SetLastError (_wapi_get_win32_file_error (errno));
257 g_free (filename_ext);
261 *map_size = statbuf.st_size;
263 /* Check basic file size */
264 if (statbuf.st_size < sizeof(WapiImageDosHeader)) {
266 g_message ("%s: File %s is too small: %ld", __func__,
267 filename_ext, statbuf.st_size);
270 SetLastError (ERROR_BAD_LENGTH);
271 g_free (filename_ext);
276 file_map = mmap (NULL, statbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
277 if (file_map == MAP_FAILED) {
279 g_message ("%s: Error mmap()int file %s: %s", __func__,
280 filename_ext, strerror (errno));
283 SetLastError (_wapi_get_win32_file_error (errno));
284 g_free (filename_ext);
289 /* Don't need the fd any more */
295 static void unmap_pe_file (gpointer file_map, guint32 map_size)
297 munmap (file_map, map_size);
300 guint32 GetFileVersionInfoSize (gunichar2 *filename, guint32 *handle)
303 gpointer versioninfo;
307 /* This value is unused, but set to zero */
310 file_map = map_pe_file (filename, &map_size);
311 if (file_map == NULL) {
315 versioninfo = find_pe_file_resources (file_map, map_size, RT_VERSION,
317 if (versioninfo == NULL) {
318 /* Didn't find the resource, so set the return value
324 unmap_pe_file (file_map, map_size);
329 gboolean GetFileVersionInfo (gunichar2 *filename, guint32 handle G_GNUC_UNUSED,
330 guint32 len, gpointer data)
333 gpointer versioninfo;
336 gboolean ret = FALSE;
338 file_map = map_pe_file (filename, &map_size);
339 if (file_map == NULL) {
343 versioninfo = find_pe_file_resources (file_map, map_size, RT_VERSION,
345 if (versioninfo != NULL) {
346 /* This could probably process the data so that
347 * VerQueryValue() doesn't have to follow the data
348 * blocks every time. But hey, these functions aren't
349 * likely to appear in many profiles.
351 memcpy (data, versioninfo, len < size?len:size);
355 unmap_pe_file (file_map, map_size);
360 static guint32 unicode_chars (const gunichar2 *str)
365 if (str[len] == '\0') {
372 static gboolean unicode_compare (const gunichar2 *str1, const gunichar2 *str2)
374 while (*str1 && *str2) {
375 if (GUINT16_TO_LE (*str1) != GUINT16_TO_LE (*str2)) {
382 return(*str1 == *str2);
385 /* compare a little-endian null-terminated utf16 string and a normal string.
386 * Can be used only for ascii or latin1 chars.
388 static gboolean unicode_string_equals (const gunichar2 *str1, const gchar *str2)
390 while (*str1 && *str2) {
391 if (GUINT16_TO_LE (*str1) != *str2) {
398 return(*str1 == *str2);
409 /* Returns a pointer to the value data, because there's no way to know
410 * how big that data is (value_len is set to zero for most blocks :-( )
412 static gconstpointer get_versioninfo_block (gconstpointer data,
415 block->data_len = GUINT16_FROM_LE (*((guint16 *)data));
416 data = (char *)data + sizeof(guint16);
417 block->value_len = GUINT16_FROM_LE (*((guint16 *)data));
418 data = (char *)data + sizeof(guint16);
420 /* No idea what the type is supposed to indicate */
421 block->type = GUINT16_FROM_LE (*((guint16 *)data));
422 data = (char *)data + sizeof(guint16);
423 block->key = ((gunichar2 *)data);
425 /* Skip over the key (including the terminator) */
426 data = ((gunichar2 *)data) + (unicode_chars (block->key) + 1);
428 /* align on a 32-bit boundary */
429 data = (gpointer)((char *)data + 3);
430 data = (gpointer)((char *)data - (GPOINTER_TO_INT (data) & 3));
435 static gconstpointer get_fixedfileinfo_block (gconstpointer data,
438 gconstpointer data_ptr;
439 gint32 data_len; /* signed to guard against underflow */
440 WapiFixedFileInfo *ffi;
442 data_ptr = get_versioninfo_block (data, block);
443 data_len = block->data_len;
445 if (block->value_len != sizeof(WapiFixedFileInfo)) {
447 g_message ("%s: FIXEDFILEINFO size mismatch", __func__);
452 if (!unicode_string_equals (block->key, "VS_VERSION_INFO")) {
454 g_message ("%s: VS_VERSION_INFO mismatch", __func__);
459 ffi = ((WapiFixedFileInfo *)data_ptr);
460 if ((ffi->dwSignature != VS_FFI_SIGNATURE) ||
461 (ffi->dwStrucVersion != VS_FFI_STRUCVERSION)) {
463 g_message ("%s: FIXEDFILEINFO bad signature", __func__);
471 static gconstpointer get_varfileinfo_block (gconstpointer data_ptr,
474 /* data is pointing at a Var block
476 data_ptr = get_versioninfo_block (data_ptr, block);
481 static gconstpointer get_string_block (gconstpointer data_ptr,
482 const gunichar2 *string_key,
483 gpointer *string_value,
484 guint32 *string_value_len,
487 guint16 data_len = block->data_len;
488 guint16 string_len = 28; /* Length of the StringTable block */
490 /* data_ptr is pointing at an array of one or more String blocks
491 * with total length (not including alignment padding) of
494 while (string_len < data_len) {
497 /* align on a 32-bit boundary */
498 data_ptr = (gpointer)((char *)data_ptr + 3);
499 data_ptr = (gpointer)((char *)data_ptr - (GPOINTER_TO_INT (data_ptr) & 3));
501 data_ptr = get_versioninfo_block (data_ptr, block);
502 if (block->data_len == 0) {
503 /* We must have hit padding, so give up
507 g_message ("%s: Hit 0-length block, giving up",
513 string_len = string_len + block->data_len;
514 value = (gunichar2 *)data_ptr;
516 if (string_key != NULL &&
517 string_value != NULL &&
518 string_value_len != NULL &&
519 unicode_compare (string_key, block->key) == TRUE) {
520 *string_value = (gpointer)data_ptr;
521 *string_value_len = block->value_len;
524 /* Skip over the value */
525 data_ptr = ((gunichar2 *)data_ptr) + block->value_len;
531 /* Returns a pointer to the byte following the Stringtable block, or
532 * NULL if the data read hits padding. We can't recover from this
533 * because the data length does not include padding bytes, so it's not
534 * possible to just return the start position + length
536 static gconstpointer get_stringtable_block (gconstpointer data_ptr,
538 const gunichar2 *string_key,
539 gpointer *string_value,
540 guint32 *string_value_len,
543 guint16 data_len = block->data_len;
544 guint16 string_len = 36; /* length of the StringFileInfo block */
546 /* data_ptr is pointing at an array of StringTable blocks,
547 * with total length (not including alignment padding) of
551 while(string_len < data_len) {
552 /* align on a 32-bit boundary */
553 data_ptr = (gpointer)((char *)data_ptr + 3);
554 data_ptr = (gpointer)((char *)data_ptr - (GPOINTER_TO_INT (data_ptr) & 3));
556 data_ptr = get_versioninfo_block (data_ptr, block);
557 if (block->data_len == 0) {
558 /* We must have hit padding, so give up
562 g_message ("%s: Hit 0-length block, giving up",
568 string_len = string_len + block->data_len;
570 if (!memcmp (block->key, lang, 8 * sizeof(gunichar2))) {
571 /* Got the one we're interested in */
572 data_ptr = get_string_block (data_ptr, string_key,
574 string_value_len, block);
576 data_ptr = get_string_block (data_ptr, NULL, NULL,
580 if (data_ptr == NULL) {
581 /* Child block hit padding */
583 g_message ("%s: Child block hit 0-length block, giving up", __func__);
592 gboolean VerQueryValue (gconstpointer datablock, const gunichar2 *subblock,
593 gpointer *buffer, guint32 *len)
595 gchar *subblock_utf8;
596 gboolean ret = FALSE;
598 gconstpointer data_ptr;
599 gint32 data_len; /* signed to guard against underflow */
600 gboolean want_var = FALSE;
601 gboolean want_string = FALSE;
603 const gunichar2 *string_key = NULL;
604 gpointer string_value = NULL;
605 guint32 string_value_len = 0;
607 subblock_utf8 = g_utf16_to_utf8 (subblock, -1, NULL, NULL, NULL);
608 if (subblock_utf8 == NULL) {
612 if (!strcmp (subblock_utf8, "\\VarFileInfo\\Translation")) {
614 } else if (!strncmp (subblock_utf8, "\\StringFileInfo\\", 16)) {
616 memcpy (lang, subblock + 16, 8 * sizeof(gunichar2));
617 string_key = subblock + 25;
620 if (!strcmp (subblock_utf8, "\\")) {
621 data_ptr = get_fixedfileinfo_block (datablock, &block);
622 if (data_ptr != NULL) {
623 *buffer = (gpointer)data_ptr;
624 *len = block.value_len;
628 } else if (want_var || want_string) {
629 data_ptr = get_fixedfileinfo_block (datablock, &block);
630 if (data_ptr != NULL) {
631 /* The FFI and header occupies the first 92
634 data_ptr = (char *)data_ptr + sizeof(WapiFixedFileInfo);
635 data_len = block.data_len - 92;
637 /* There now follow zero or one StringFileInfo
638 * blocks and zero or one VarFileInfo blocks
640 while (data_len > 0) {
641 /* align on a 32-bit boundary */
642 data_ptr = (gpointer)((char *)data_ptr + 3);
643 data_ptr = (gpointer)((char *)data_ptr - (GPOINTER_TO_INT (data_ptr) & 3));
645 data_ptr = get_versioninfo_block (data_ptr,
647 if (block.data_len == 0) {
648 /* We must have hit padding,
649 * so give up processing now
652 g_message ("%s: Hit 0-length block, giving up", __func__);
657 data_len = data_len - block.data_len;
659 if (unicode_string_equals (block.key, "VarFileInfo")) {
660 data_ptr = get_varfileinfo_block (data_ptr, &block);
662 *buffer = (gpointer)data_ptr;
663 *len = block.value_len;
667 /* Skip over the Var block */
668 data_ptr = ((guchar *)data_ptr) + block.value_len;
670 } else if (unicode_string_equals (block.key, "StringFileInfo")) {
671 data_ptr = get_stringtable_block (data_ptr, lang, string_key, &string_value, &string_value_len, &block);
673 string_value != NULL &&
674 string_value_len != 0) {
675 *buffer = string_value;
676 *len = string_value_len;
683 g_message ("%s: Not a valid VERSIONINFO child block", __func__);
688 if (data_ptr == NULL) {
689 /* Child block hit padding */
691 g_message ("%s: Child block hit 0-length block, giving up", __func__);
700 g_free (subblock_utf8);
704 guint32 VerLanguageName (guint32 lang, gunichar2 *lang_out, guint32 lang_len)