1// Licensed to Elasticsearch B.V. under one or more contributor
2// license agreements. See the NOTICE file distributed with
3// this work for additional information regarding copyright
4// ownership. Elasticsearch B.V. licenses this file to you under
5// the Apache License, Version 2.0 (the "License"); you may
6// not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9//     http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18// +build windows
19
20package windows
21
22import (
23	"fmt"
24	"unsafe"
25
26	"github.com/pkg/errors"
27)
28
29// Syscalls
30//sys   _GetFileVersionInfo(filename string, reserved uint32, dataLen uint32, data *byte) (success bool, err error) [!success] = version.GetFileVersionInfoW
31//sys   _GetFileVersionInfoSize(filename string, handle uintptr) (size uint32, err error) = version.GetFileVersionInfoSizeW
32//sys   _VerQueryValueW(data *byte, subBlock string, pBuffer *uintptr, len *uint32) (success bool, err error) [!success] = version.VerQueryValueW
33
34// FixedFileInfo contains version information for a file. This information is
35// language and code page independent. This is an equivalent representation of
36// VS_FIXEDFILEINFO.
37// https://msdn.microsoft.com/en-us/library/windows/desktop/ms646997(v=vs.85).aspx
38type FixedFileInfo struct {
39	Signature        uint32
40	StrucVersion     uint32
41	FileVersionMS    uint32
42	FileVersionLS    uint32
43	ProductVersionMS uint32
44	ProductVersionLS uint32
45	FileFlagsMask    uint32
46	FileFlags        uint32
47	FileOS           uint32
48	FileType         uint32
49	FileSubtype      uint32
50	FileDateMS       uint32
51	FileDateLS       uint32
52}
53
54// ProductVersion returns the ProductVersion value in string format.
55func (info FixedFileInfo) ProductVersion() string {
56	return fmt.Sprintf("%d.%d.%d.%d",
57		(info.ProductVersionMS >> 16),
58		(info.ProductVersionMS & 0xFFFF),
59		(info.ProductVersionLS >> 16),
60		(info.ProductVersionLS & 0xFFFF))
61}
62
63// FileVersion returns the FileVersion value in string format.
64func (info FixedFileInfo) FileVersion() string {
65	return fmt.Sprintf("%d.%d.%d.%d",
66		(info.FileVersionMS >> 16),
67		(info.FileVersionMS & 0xFFFF),
68		(info.FileVersionLS >> 16),
69		(info.FileVersionLS & 0xFFFF))
70}
71
72// VersionData is a buffer holding the data returned by GetFileVersionInfo.
73type VersionData []byte
74
75// QueryValue uses VerQueryValue to query version information from the a
76// version-information resource. It returns responses using the first language
77// and code point found in the resource. The accepted keys are listed in
78// the VerQueryValue documentation (e.g. ProductVersion, FileVersion, etc.).
79// https://msdn.microsoft.com/en-us/library/windows/desktop/ms647464(v=vs.85).aspx
80func (d VersionData) QueryValue(key string) (string, error) {
81	type LangAndCodePage struct {
82		Language uint16
83		CodePage uint16
84	}
85
86	var dataPtr uintptr
87	var size uint32
88	if _, err := _VerQueryValueW(&d[0], `\VarFileInfo\Translation`, &dataPtr, &size); err != nil || size == 0 {
89		return "", errors.Wrap(err, "failed to get list of languages")
90	}
91
92	offset := int(dataPtr - (uintptr)(unsafe.Pointer(&d[0])))
93	if offset <= 0 || offset > len(d)-1 {
94		return "", errors.New("invalid address")
95	}
96
97	l := *(*LangAndCodePage)(unsafe.Pointer(&d[offset]))
98
99	subBlock := fmt.Sprintf(`\StringFileInfo\%04x%04x\%v`, l.Language, l.CodePage, key)
100	if _, err := _VerQueryValueW(&d[0], subBlock, &dataPtr, &size); err != nil || size == 0 {
101		return "", errors.Wrapf(err, "failed to query %v", subBlock)
102	}
103
104	offset = int(dataPtr - (uintptr)(unsafe.Pointer(&d[0])))
105	if offset <= 0 || offset > len(d)-1 {
106		return "", errors.New("invalid address")
107	}
108
109	str, _, err := UTF16BytesToString(d[offset : offset+int(size)*2])
110	if err != nil {
111		return "", errors.Wrap(err, "failed to decode UTF16 data")
112	}
113
114	return str, nil
115}
116
117// FixedFileInfo returns the fixed version information from a
118// version-information resource. It queries the root block to get the
119// VS_FIXEDFILEINFO value.
120// https://msdn.microsoft.com/en-us/library/windows/desktop/ms647464(v=vs.85).aspx
121func (d VersionData) FixedFileInfo() (*FixedFileInfo, error) {
122	if len(d) == 0 {
123		return nil, errors.New("use GetFileVersionInfo to initialize VersionData")
124	}
125
126	var dataPtr uintptr
127	var size uint32
128	if _, err := _VerQueryValueW(&d[0], `\`, &dataPtr, &size); err != nil {
129		return nil, errors.Wrap(err, "VerQueryValue failed for \\")
130	}
131
132	offset := int(dataPtr - (uintptr)(unsafe.Pointer(&d[0])))
133	if offset <= 0 || offset > len(d)-1 {
134		return nil, errors.New("invalid address")
135	}
136
137	// Make a copy of the struct.
138	ffi := *(*FixedFileInfo)(unsafe.Pointer(&d[offset]))
139
140	return &ffi, nil
141}
142
143// GetFileVersionInfo retrieves version information for the specified file.
144// https://msdn.microsoft.com/en-us/library/windows/desktop/ms647003(v=vs.85).aspx
145func GetFileVersionInfo(filename string) (VersionData, error) {
146	size, err := _GetFileVersionInfoSize(filename, 0)
147	if err != nil {
148		return nil, errors.Wrap(err, "GetFileVersionInfoSize failed")
149	}
150
151	data := make(VersionData, size)
152	_, err = _GetFileVersionInfo(filename, 0, uint32(len(data)), &data[0])
153	if err != nil {
154		return nil, errors.Wrap(err, "GetFileVersionInfo failed")
155	}
156
157	return data, nil
158}
159