wix-on-linux/version.c
Simon Tatham 39faf94ea2 Move the char16 functions into their own file.
This completes the removal of the monolithic fake-lib.[ch].
2017-05-18 07:16:21 +01:00

216 lines
7.7 KiB
C

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <err.h>
#include "memory.h"
#include "uchars.h"
uint32_t MsiGetFileVersionW(const char16_t *filename,
char16_t *version, uint32_t *version_size,
char16_t *language, uint32_t *language_size)
{
char *fname = ascii(filename, true);
uint32_t toret = 1006; /* ERROR_FILE_INVALID == 'no version info found' */
int fd = -1;
void *mapv = MAP_FAILED;
if (version && *version_size)
*version = 0;
if (language && *language_size)
*language = 0;
fd = open(fname, O_RDONLY);
if (fd < 0)
err(1, "%s: open", fname);
struct stat st;
if (fstat(fd, &st) < 0)
err(1, "%s: fstat", fname);
size_t fsize = st.st_size;
mapv = mmap(NULL, fsize, PROT_READ, MAP_PRIVATE, fd, 0);
if (mapv == MAP_FAILED)
err(1, "%s: mmap", fname);
unsigned char *map = (unsigned char *)mapv;
#define BYTE(offset) ({ \
size_t _offset = (offset); \
if (_offset >= fsize) goto cleanup; /* outside file bounds */ \
map[_offset]; \
})
#define WORD16(offset) ({ \
size_t _offset = (offset); \
uint16_t toret = BYTE(_offset+1); \
toret = (toret << 8) | BYTE(_offset+0); \
toret; \
})
#define WORD32(offset) ({ \
size_t _offset = (offset); \
uint16_t toret = BYTE(_offset+3); \
toret = (toret << 8) | BYTE(_offset+2); \
toret = (toret << 8) | BYTE(_offset+1); \
toret = (toret << 8) | BYTE(_offset+0); \
toret; \
})
if (WORD16(0) != (('Z'<<8) | 'M')) {
warnx("MsiGetFileInfo(%s) -> no MZ", fname);
goto cleanup;
}
unsigned pe_pos = WORD32(0x3c);
if (WORD32(pe_pos) != (('E'<<8) | 'P')) {
warnx("MsiGetFileInfo(%s) -> no PE", fname);
goto cleanup;
}
pe_pos += 4; /* skip to the main header */
unsigned nsections = WORD16(pe_pos + 2);
unsigned opthdr_size = WORD16(pe_pos + 16);
unsigned opthdr_pos = pe_pos + 20;
/* bool sixtyfourbit = WORD16(opthdr_pos) == 0x020B; */
unsigned secthdr_pos = opthdr_pos + opthdr_size;
while (nsections > 0) {
if (BYTE(secthdr_pos+0) == '.' &&
BYTE(secthdr_pos+1) == 'r' &&
BYTE(secthdr_pos+2) == 's' &&
BYTE(secthdr_pos+3) == 'r' &&
BYTE(secthdr_pos+4) == 'c' &&
BYTE(secthdr_pos+5) == 0)
goto found_resource_section;
secthdr_pos += 0x28;
nsections--;
}
warnx("MsiGetFileInfo(%s) -> no .rsrc", fname);
goto cleanup;
found_resource_section:;
unsigned rsrc_size = WORD32(secthdr_pos+8);
unsigned rsrc_offset = WORD32(secthdr_pos+20);
unsigned rsrc_vaddr = WORD32(secthdr_pos+12);
unsigned res_dir_offset = rsrc_offset;
unsigned nnamed, nid;
nnamed = WORD16(res_dir_offset+12);
nid = WORD16(res_dir_offset+14);
for (unsigned i = nnamed; i < nnamed+nid; i++) {
unsigned id = WORD32(res_dir_offset + 16 + 8*i);
unsigned entry = WORD32(res_dir_offset + 16 + 8*i + 4);
if (id == 16 && (entry & 0x80000000)) {
res_dir_offset = rsrc_offset + (entry & 0x7FFFFFFF);
goto found_versioninfo_toplevel;
}
}
warnx("MsiGetFileInfo(%s) -> no top-level numeric key 16 for versioninfo", fname);
goto cleanup;
found_versioninfo_toplevel:
nnamed = WORD16(res_dir_offset+12);
nid = WORD16(res_dir_offset+14);
for (unsigned i = 0; i < nnamed+nid; i++) {
unsigned entry = WORD32(res_dir_offset + 16 + 8*i + 4);
if (entry & 0x80000000) {
res_dir_offset = rsrc_offset + (entry & 0x7FFFFFFF);
goto found_versioninfo_2ndlevel;
}
}
warnx("MsiGetFileInfo(%s) -> no 2nd-level subdir", fname);
goto cleanup;
found_versioninfo_2ndlevel:
nnamed = WORD16(res_dir_offset+12);
nid = WORD16(res_dir_offset+14);
for (unsigned i = 0; i < nnamed+nid; i++) {
unsigned entry = WORD32(res_dir_offset + 16 + 8*i + 4);
if (!(entry & 0x80000000)) {
res_dir_offset = rsrc_offset + (entry & 0x7FFFFFFF);
goto found_versioninfo_3rdlevel;
}
}
warnx("MsiGetFileInfo(%s) -> no 3rd-level resource data", fname);
goto cleanup;
found_versioninfo_3rdlevel:;
unsigned versioninfo_offset = WORD32(res_dir_offset);
unsigned versioninfo_size = WORD32(res_dir_offset+4);
versioninfo_offset = rsrc_offset + versioninfo_offset - rsrc_vaddr;
unsigned name_offset = versioninfo_offset + 6;
const char *next_name_chr = "VS_VERSION_INFO";
do {
if (WORD16(name_offset) != *next_name_chr)
goto cleanup; /* identifying string didn't match */
name_offset += 2;
} while (*next_name_chr++);
unsigned fixed_offset = (name_offset + 3) & ~3;
if (WORD32(fixed_offset) != 0xFEEF04BDU) {
warnx("MsiGetFileInfo(%s) -> no VS_FIXEDFILEINFO magic number", fname);
goto cleanup;
}
int four_part_version[4];
four_part_version[0] = WORD16(fixed_offset + 10);
four_part_version[1] = WORD16(fixed_offset + 8);
four_part_version[2] = WORD16(fixed_offset + 14);
four_part_version[3] = WORD16(fixed_offset + 12);
unsigned child_offset = fixed_offset +
WORD16(versioninfo_offset+2);
unsigned lcid;
while (child_offset < versioninfo_offset + versioninfo_size) {
unsigned this_child_offset = child_offset;
child_offset += WORD16(child_offset);
child_offset = (child_offset + 3) &~ 3;
if (child_offset <= this_child_offset) {
warnx("MsiGetFileInfo(%s) -> bad length field", fname);
goto cleanup;
}
const char *next_name_chr = "VarFileInfo";
name_offset = this_child_offset + 6;
do {
if (WORD16(name_offset) != *next_name_chr)
goto this_is_not_a_varfileinfo;
name_offset += 2;
} while (*next_name_chr++);
unsigned subchild_offset = (name_offset + 3) & ~3;
next_name_chr = "Translation";
name_offset = subchild_offset + 6;
do {
if (WORD16(name_offset) != *next_name_chr) {
warnx("MsiGetFileInfo(%s) -> child not called 'Translation'", fname);
goto cleanup;
}
name_offset += 2;
} while (*next_name_chr++);
subchild_offset = (name_offset + 3) & ~3;
lcid = WORD16(subchild_offset);
goto success;
this_is_not_a_varfileinfo:
continue;
}
warnx("MsiGetFileInfo(%s) -> no VarFileInfo found", fname);
goto cleanup;
success:;
char verbuf[256], langbuf[256];
snprintf(verbuf, sizeof(verbuf), "%d.%d.%d.%d",
four_part_version[0], four_part_version[1],
four_part_version[2], four_part_version[3]);
snprintf(langbuf, sizeof(langbuf), "%u", lcid);
warnx("MsiGetFileInfo(%s) -> version %s lang %s", fname, verbuf, langbuf);
if (version)
c16cpy(version, version_size, verbuf);
if (language)
c16cpy(language, language_size, langbuf);
toret = 0;
cleanup:
if (mapv != MAP_FAILED)
munmap(mapv, fsize);
if (fd != -1)
close(fd);
sfree(fname);
return toret;
}