27134c6fc5e677c99ee92492b148d46c99a20506
[feisty_meow.git] / nucleus / library / versions / version_checker.cpp
1 /*****************************************************************************\
2 *                                                                             *
3 *  Name   : check_version                                                     *
4 *  Author : Chris Koeritz                                                     *
5 *                                                                             *
6 *******************************************************************************
7 * Copyright (c) 1996-$now By Author.  This program is free software; you can  *
8 * redistribute it and/or modify it under the terms of the GNU General Public  *
9 * License as published by the Free Software Foundation; either version 2 of   *
10 * the License or (at your option) any later version.  This is online at:      *
11 *     http://www.fsf.org/copyleft/gpl.html                                    *
12 * Please send any updates to: fred@gruntose.com                               *
13 \*****************************************************************************/
14
15 #include "version_checker.h"
16
17 #include <application/windoze_helper.h>
18 #include <basis/astring.h>
19 #include <basis/environment.h>
20 #include <basis/functions.h>
21 #include <basis/guards.h>
22 #include <basis/utf_conversion.h>
23 #include <configuration/application_configuration.h>
24 #include <filesystem/filename.h>
25 #include <loggers/critical_events.h>
26
27 #include <stdio.h>
28
29 using namespace basis;
30 using namespace configuration;
31 using namespace loggers;
32 using namespace structures;
33
34 #ifndef BOOT_STRAPPING
35   // pull in the version specified for this build.
36 ///hmmm: on hold!  #include <__build_version.h>
37 #else
38   // plug in a fake version for our bootstrapping process.
39   #define __build_FILE_VERSION "108.420.1024.10008"
40 #endif
41
42 #ifdef _MSC_VER
43   #include <direct.h>
44   #include <winver.h>
45 #endif
46
47 #if defined(_MSC_VER)
48 //#ifdef __WIN32__
49   // ensures that we handle the data properly regardless of unicode settings.
50   #ifdef UNICODE
51     #define render_ptr(ptr) from_unicode_temp( (UTF16 *) ptr)
52   #else
53     #define render_ptr(ptr) ( (char *) ptr)
54   #endif
55 #endif
56
57 //////////////
58
59 namespace versions {
60
61 version_checker::version_checker(const astring &library_file_name,
62     const version &expected_version, const astring &version_complaint)
63 : _library_file_name(new astring(library_file_name)),
64   _expected_version(new version(expected_version)),
65   _version_complaint(new astring(version_complaint))
66 {}
67
68 version_checker::~version_checker()
69 {
70   WHACK(_library_file_name);
71   WHACK(_expected_version);
72   WHACK(_version_complaint);
73 }
74
75 astring version_checker::text_form() const
76 {
77   return astring(class_name()) + ": library_file_name=" + *_library_file_name
78       + ", expected_version=" + _expected_version->text_form()
79       + ", complaint_message=" + *_version_complaint;
80 }
81
82 bool version_checker::good_version() const
83 {
84   astring version_disabler = environment::TMP();
85   version_disabler += "/no_version_check.txt";
86   FILE *always_okay = fopen(version_disabler.s(), "r");
87   if (always_okay) {
88     fclose(always_okay);
89     return true;
90   }
91
92   version version_found = retrieve_version(*_library_file_name);
93   if (version_found.compatible(*_expected_version)) return true;  // success.
94   complain_wrong_version(*_library_file_name, *_expected_version,
95       version_found);
96   return false;
97 }
98
99 bool version_checker::loaded(const astring &library_file_name)
100 {
101 //#ifdef __WIN32__
102 #if defined(_MSC_VER)
103   return bool(get_handle(library_file_name) != 0); 
104 #else
105 //temp code. 
106   return true || library_file_name.t();
107 #endif
108 }
109
110 void *version_checker::get_handle(const astring &library_file_name)
111 {
112 //#ifdef __WIN32__
113 #if defined(_MSC_VER)
114   return GetModuleHandle(to_unicode_temp(library_file_name));
115 #else
116   if (library_file_name.t()) return NULL_POINTER; else return NULL_POINTER;
117 #endif
118 }
119
120 astring version_checker::module_name(const void *module_handle)
121 {
122 #if defined(__UNIX__) || defined(__GNU_WINDOWS__)
123   if (module_handle) {}
124   return application_configuration::application_name();
125 #elif defined(_MSC_VER)
126 //#elif defined(__WIN32__)
127   flexichar low_buff[MAX_ABS_PATH + 1];
128   GetModuleFileName((HMODULE)module_handle, low_buff, MAX_ABS_PATH - 1);
129   astring buff = from_unicode_temp(low_buff);
130   buff.to_lower();
131   return buff;
132 #else
133   #pragma message("module_name unknown for this operating system.")
134   return application_configuration::application_name();
135 #endif
136 }
137
138 astring version_checker::get_name(const void *to_find)
139 { return module_name(to_find); }
140
141 bool version_checker::retrieve_version_info(const astring &filename,
142     byte_array &to_fill)
143 {
144   to_fill.reset();  // clear the buffer.
145
146   // determine the required size of the version info buffer.
147   int required_size;
148 #if defined(_MSC_VER)
149 //#ifdef __WIN32__
150   un_long module_handle;  // filled with the dll or exe handle.
151   required_size = GetFileVersionInfoSize(to_unicode_temp(filename), &module_handle);
152 #else
153   required_size = 0 && filename.t();
154 #endif
155   if (!required_size) return false;
156   to_fill.reset(required_size);  // resize the buffer.
157   
158   // read the version info into our buffer.
159   bool success = false;
160 #if defined(_MSC_VER)
161 //#ifdef __WIN32__
162   success = GetFileVersionInfo(to_unicode_temp(filename), module_handle,
163       required_size, to_fill.access());
164 #else
165   success = false;
166 #endif
167   return success;
168 }
169
170 bool version_checker::get_language(byte_array &version_chunk,
171     basis::un_short &high, basis::un_short &low)
172 {
173   high = 0;
174   low = 0;
175 #if defined(_MSC_VER)
176 //#ifdef __WIN32__
177   // determine the language that the version's written in.
178   basis::un_int data_size;
179   void *pointer_to_language_structure;
180   // query the information from the version blob.
181   if (!VerQueryValue(version_chunk.access(),
182       to_unicode_temp("\\VarFileInfo\\Translation"),
183       &pointer_to_language_structure, &data_size))
184     return false;
185   // get the low & high shorts of the structure.
186   high = LOWORD(*(unsigned int *)pointer_to_language_structure);
187   low = HIWORD(*(unsigned int *)pointer_to_language_structure);
188 #else
189   high = 0 && version_chunk.length();
190   low = 0;
191 #endif
192
193   return true;
194 }
195
196 version version_checker::retrieve_version(const astring &filename)
197 {
198 //#ifdef UNIX
199 #if defined(__UNIX__) || defined(__GNU_WINDOWS__)
200
201   // totally bogus stand-in; this just returns the version we were built with
202   // rather than the version that's actually tagged on the file.
203
204 //hmmm: fix this!  get the version header back.
205 //  return version(__build_FILE_VERSION);
206   return version();
207
208 #endif
209
210   byte_array version_info_found(0, NULL_POINTER);
211   if (!retrieve_version_info(filename, version_info_found))
212     return version(0, 0, 0, 0);
213
214   basis::un_short high, low;  // holds the language of the version data.
215   if (!get_language(version_info_found, high, low))
216     return version(0, 0, 0, 0);
217
218   // retrieve the file version from version info using the appropriate
219   // language.
220   astring root_key(astring::SPRINTF, "\\StringFileInfo\\%04x%04x", high, low);
221   astring file_version_key(root_key + astring("\\FileVersion"));
222
223   astring version_string;
224 #ifdef _MSC_VER
225   abyte *file_version_pointer;
226   basis::un_int data_size;
227   if (!VerQueryValue(version_info_found.access(),
228       to_unicode_temp(file_version_key),
229       (LPVOID *)&file_version_pointer, &data_size))
230     return version(0, 0, 0, 0);
231   version_string = render_ptr(file_version_pointer);
232   // clean any spaces out of the string; people sometimes format these
233   // very badly.
234   for (int i = 0; i < version_string.length(); i++) {
235     if (version_string[i] == ' ') {
236       version_string.zap(i, i);
237       i--;  // skip back a beat.
238     }
239   }
240 #else
241   return version(0, 0, 0, 0);
242 //tmp.
243 #endif
244   return version::from_text(version_string);
245 }
246
247 bool version_checker::get_record(const astring &filename, 
248     version_record &to_fill)
249 {
250   to_fill = version_record();
251   byte_array version_info_found(0, NULL_POINTER);
252   if (!retrieve_version_info(filename, version_info_found))
253     return false;
254
255   basis::un_short high, low;  // holds the language of the version data.
256   if (!get_language(version_info_found, high, low))
257     return false;
258
259   // set the root key for all accesses of the version chunk.
260   astring root_key(astring::SPRINTF, "\\StringFileInfo\\%04x%04x", high, low);
261
262   // reports whether all lookups succeeded or not.
263   bool total_success = true;
264
265   // the various version pieces are retrieved...
266
267 //#ifdef __WIN32__
268 #ifdef _MSC_VER
269   basis::un_int data_size;
270   void *data_pointer;
271
272   // file version.
273   if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key
274       + astring("\\FileVersion")), &data_pointer, &data_size))
275     total_success = false;
276   else
277     to_fill.file_version = version::from_text(render_ptr(data_pointer));
278
279   // company name.
280   if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key
281       + astring("\\CompanyName")), &data_pointer, &data_size))
282     total_success = false;
283   else
284     to_fill.company_name = render_ptr(data_pointer);
285
286   // file description.
287   if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key
288       + astring("\\FileDescription")), &data_pointer, &data_size))
289     total_success = false;
290   else
291     to_fill.description = render_ptr(data_pointer);
292
293   // internal name.
294   if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key
295       + astring("\\InternalName")), &data_pointer, &data_size))
296     total_success = false;
297   else
298     to_fill.internal_name = render_ptr(data_pointer);
299
300   // copyright info.
301   if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key
302       + astring("\\LegalCopyright")), &data_pointer, &data_size))
303     total_success = false;
304   else
305     to_fill.copyright = render_ptr(data_pointer);
306
307   // trademark info.
308   if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key
309       + astring("\\LegalTrademarks")), &data_pointer, &data_size))
310     total_success = false;
311   else
312     to_fill.trademarks = render_ptr(data_pointer);
313
314   // original file name.
315   if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key
316       + astring("\\OriginalFilename")), &data_pointer, &data_size))
317     total_success = false;
318   else
319     to_fill.original_name = render_ptr(data_pointer);
320
321   // product name.
322   if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key
323       + astring("\\ProductName")), &data_pointer, &data_size))
324     total_success = false;
325   else
326     to_fill.product_name = render_ptr(data_pointer);
327
328   // product version.
329   if (!VerQueryValue(version_info_found.access(), to_unicode_temp(root_key
330       + astring("\\ProductVersion")), &data_pointer, &data_size))
331     total_success = false;
332   else
333     to_fill.product_version = version::from_text(render_ptr(data_pointer));
334 #else
335   // hmmm: chunks missing in version check.
336 #endif
337
338   return total_success;
339 }
340
341 void version_checker::complain_wrong_version(const astring &library_file_name,
342     const version &expected_version, const version &version_found) const
343 {
344   astring to_show("There has been a Version Mismatch: The module \"");
345   // use embedded module handle to retrieve name of dll or exe.
346   astring module_name = get_name(version::__global_module_handle());
347   if (!module_name) module_name = "Unknown";
348   to_show += module_name;
349   to_show += astring("\" cannot load.  This is because the file \"");
350   to_show += library_file_name;
351   to_show += astring("\" was expected to have a version of [");
352
353   to_show += expected_version.flex_text_form(version::DOTS);
354
355   to_show += astring("] but it instead had a version of [");
356   to_show += version_found.flex_text_form(version::DOTS);
357
358   to_show += astring("].  ");
359   to_show += *_version_complaint;
360 //#ifdef __UNIX__
361 #if defined(__UNIX__) || defined(__GNU_WINDOWS__)
362   continuable_error("version checking", "failure", to_show.s());
363 #elif defined(_MSC_VER)
364   MessageBox(0, to_unicode_temp(to_show),
365       to_unicode_temp("version_checking::failure"), MB_OK);
366 #endif
367 }
368
369 void version_checker::complain_cannot_load(const astring &lib_file) const
370 {
371   astring to_show("There has been a failure in Version Checking: The file \"");
372   to_show += lib_file;
373   to_show += astring("\" could not be loaded or found.  ");
374   to_show += *_version_complaint;
375   continuable_error("version checking", "loading dll", to_show.s());
376 }
377
378 } //namespace.
379
380