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