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