The Battle for Wesnoth  1.15.12+dev
version.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2015 - 2018 by Iris Morelle <shadowm2006@gmail.com>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 #define GETTEXT_DOMAIN "wesnoth-lib"
16 
17 #include "desktop/version.hpp"
18 
19 #include "filesystem.hpp"
20 #include "formatter.hpp"
21 #include "gettext.hpp"
22 #include "log.hpp"
24 
25 #include <cstring>
26 
27 #if defined(__APPLE__) || defined(_X11)
28 #include <sys/utsname.h>
29 #endif
30 
31 #if defined(__APPLE__)
32 
33 #include "apple_version.hpp"
35 
36 #include <map>
37 #include <boost/algorithm/string/trim.hpp>
38 
39 #elif defined(_X11)
40 
42 
43 #include <cerrno>
44 #include <map>
45 #include <boost/algorithm/string/trim.hpp>
46 
47 #endif
48 
49 #ifdef _WIN32
50 
51 #ifndef UNICODE
52 #define UNICODE
53 #endif
54 #define WIN32_LEAN_AND_MEAN
55 
56 #include <windows.h>
57 
58 #endif
59 
60 static lg::log_domain log_desktop("desktop");
61 #define ERR_DU LOG_STREAM(err, log_desktop)
62 #define LOG_DU LOG_STREAM(info, log_desktop)
63 
64 namespace desktop
65 {
66 
67 namespace
68 {
69 
70 #ifdef _WIN32
71 /**
72  * Detects whether we are running on Wine or not.
73  *
74  * This is for informational purposes only and all Windows code should assume
75  * we are running on the real thing instead.
76  */
77 bool on_wine()
78 {
79  HMODULE ntdll = GetModuleHandle(L"ntdll.dll");
80  if(!ntdll) {
81  return false;
82  }
83 
84  return GetProcAddress(ntdll, "wine_get_version") != nullptr;
85 }
86 
87 /**
88  * Tries to find out the Windows 10 release number.
89  *
90  * This depends on the registry having special information in it. This may or
91  * may not break in the future or be faked by the application compatibility
92  * layer. Take with a grain of salt.
93  */
94 std::string windows_release_id()
95 {
96  char buf[256]{""};
97  DWORD size = sizeof(buf);
98 
99  const auto res = RegGetValueA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", "ReleaseId", RRF_RT_REG_SZ, nullptr, buf, &size);
100  return std::string{res == ERROR_SUCCESS ? buf : ""};
101 }
102 
103 std::string windows_runtime_arch()
104 {
105  SYSTEM_INFO si;
106  SecureZeroMemory(&si, sizeof(SYSTEM_INFO));
107  GetNativeSystemInfo(&si);
108 
109  switch(si.wProcessorArchitecture) {
110  case PROCESSOR_ARCHITECTURE_INTEL:
111  return "x86";
112  case PROCESSOR_ARCHITECTURE_AMD64:
113  return "x86_64";
114  case PROCESSOR_ARCHITECTURE_ARM:
115  return "arm";
116  case PROCESSOR_ARCHITECTURE_ARM64:
117  return "arm64";
118  case PROCESSOR_ARCHITECTURE_IA64:
119  return "ia64";
120  default:
121  return _("cpu_architecture^<unknown>");
122  }
123 }
124 
125 #endif
126 
127 #if defined(_X11)
128 /**
129  * Release policy for POSIX pipe streams opened with popen(3).
130  */
131 struct posix_pipe_release_policy
132 {
133  void operator()(std::FILE* f) const { if(f != nullptr) { pclose(f); } }
134 };
135 
136 /**
137  * Scoped POSIX pipe stream.
138  *
139  * The stream object type is the same as a regular file stream, but the release
140  * policy is different, as required by popen(3).
141  */
142 typedef std::unique_ptr<std::FILE, posix_pipe_release_policy> scoped_posix_pipe;
143 
144 /**
145  * Read a single line from the specified pipe.
146  *
147  * @returns An empty string if the pipe is invalid or nothing could be read.
148  */
149 std::string read_pipe_line(scoped_posix_pipe& p)
150 {
151  if(!p.get()) {
152  return "";
153  }
154 
155  std::string ver;
156  int c;
157 
158  ver.reserve(64);
159 
160  // We only want the first line.
161  while((c = std::fgetc(p.get())) && c != EOF && c != '\n' && c != '\r') {
162  ver.push_back(static_cast<char>(c));
163  }
164 
165  return ver;
166 }
167 
168 std::map<std::string, std::string> parse_fdo_osrelease(const std::string& path)
169 {
170  auto in = filesystem::istream_file(path);
171  if(!in->good()) {
172  return {};
173  }
174 
175  std::map<std::string, std::string> res;
176 
177  // NOTE: intentionally basic "parsing" here. We are not supposed to see
178  // more complex shell syntax anyway.
179  // <https://www.freedesktop.org/software/systemd/man/os-release.html>
180  for(std::string s; std::getline(*in, s);) {
181  if(s.empty() || s.front() == '#') {
182  continue;
183  }
184 
185  auto eqsign_pos = s.find('=');
186  if(!eqsign_pos || eqsign_pos == std::string::npos) {
187  continue;
188  }
189 
190  auto lhs = s.substr(0, eqsign_pos),
191  rhs = eqsign_pos + 1 < s.length() ? utils::unescape(s.substr(eqsign_pos + 1)) : "";
192 
195 
196  // Unquote if the quotes match on both sides
197  if(rhs.length() >= 2 && rhs.front() == '"' && rhs.back() == '"') {
198  rhs.pop_back();
199  rhs.erase(0, 1);
200  }
201 
202  res.emplace(std::move(lhs), std::move(rhs));
203  }
204 
205  return res;
206 }
207 
208 #endif
209 
210 } // end anonymous namespace
211 
212 std::string os_version()
213 {
214 #if defined(__APPLE__) || defined(_X11)
215  utsname u;
216 
217  if(uname(&u) != 0) {
218  ERR_DU << "os_version: uname error (" << strerror(errno) << ")\n";
219  }
220 #endif
221 
222 #if defined(__APPLE__)
223 
224  //
225  // Standard Mac OS X version
226  //
227 
228  return desktop::apple::os_version() + " " + u.machine;
229 
230 #elif defined(_X11)
231 
232  //
233  // systemd/freedesktop.org method.
234  //
235 
236  std::map<std::string, std::string> osrel;
237 
238  static const std::string fdo_osrel_etc = "/etc/os-release";
239  static const std::string fdo_osrel_usr = "/usr/lib/os-release";
240 
241  if(filesystem::file_exists(fdo_osrel_etc)) {
242  osrel = parse_fdo_osrelease(fdo_osrel_etc);
243  } else if(filesystem::file_exists(fdo_osrel_usr)) {
244  osrel = parse_fdo_osrelease(fdo_osrel_usr);
245  }
246 
247  // Check both existence and emptiness in case some vendor sets PRETTY_NAME=""
248  auto osrel_distname = osrel["PRETTY_NAME"];
249  if(osrel_distname.empty()) {
250  osrel_distname = osrel["NAME"];
251  }
252 
253  if(!osrel_distname.empty()) {
254  return osrel_distname + " " + u.machine;
255  }
256 
257  //
258  // Linux Standard Base fallback.
259  //
260 
261  static const std::string lsb_release_bin = "/usr/bin/lsb_release";
262 
263  if(filesystem::file_exists(lsb_release_bin)) {
264  static const std::string cmdline = lsb_release_bin + " -s -d";
265 
266  scoped_posix_pipe p(popen(cmdline.c_str(), "r"));
267  std::string ver = read_pipe_line(p);
268 
269  if(ver.length() >= 2 && ver[0] == '"' && ver[ver.length() - 1] == '"') {
270  ver.erase(ver.length() - 1, 1);
271  ver.erase(0, 1);
272  }
273 
274  // Check this again in case we got "" above for some weird reason.
275  if(!ver.empty()) {
276  return ver + " " + u.machine;
277  }
278  }
279 
280  //
281  // POSIX uname version fallback.
282  //
283 
284  return formatter() << u.sysname << ' '
285  << u.release << ' '
286  << u.version << ' '
287  << u.machine;
288 
289 #elif defined(_WIN32)
290 
291  //
292  // Windows version.
293  //
294 
295  static const std::string base
296  = !on_wine() ? "Microsoft Windows" : "Wine/Microsoft Windows";
297 
298  OSVERSIONINFOEX v;
299 
300  SecureZeroMemory(&v, sizeof(OSVERSIONINFOEX));
301  v.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
302 
303 #ifdef _MSC_VER
304 // GetVersionEx is rather problematic, but it works for our usecase.
305 // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724451(v=vs.85).aspx
306 // for more info.
307 #pragma warning(push)
308 #pragma warning(disable:4996)
309 #endif
310  if(!GetVersionEx(reinterpret_cast<OSVERSIONINFO*>(&v))) {
311  ERR_DU << "os_version: GetVersionEx error ("
312  << GetLastError() << ")\n";
313  return base;
314  }
315 #ifdef _MSC_VER
316 #pragma warning(pop)
317 #endif
318 
319  const DWORD vnum = v.dwMajorVersion * 100 + v.dwMinorVersion;
320  std::string version;
321 
322  switch(vnum)
323  {
324  case 500:
325  version = "2000";
326  break;
327  case 501:
328  version = "XP";
329  break;
330  case 502:
331  // This will misidentify XP x64 but who really cares?
332  version = "Server 2003";
333  break;
334  case 600:
335  if(v.wProductType == VER_NT_WORKSTATION) {
336  version = "Vista";
337  } else {
338  version = "Server 2008";
339  }
340  break;
341  case 601:
342  if(v.wProductType == VER_NT_WORKSTATION) {
343  version = "7";
344  } else {
345  version = "Server 2008 R2";
346  }
347  break;
348  case 602:
349  if(v.wProductType == VER_NT_WORKSTATION) {
350  version = "8";
351  } else {
352  version = "Server 2012";
353  }
354  break;
355  case 603:
356  if(v.wProductType == VER_NT_WORKSTATION) {
357  version = "8.1";
358  } else {
359  version = "Server 2012 R2";
360  }
361  break;
362  case 1000:
363  if(v.wProductType == VER_NT_WORKSTATION) {
364  version = "10";
365  const auto& release_id = windows_release_id();
366  if(!release_id.empty()) {
367  version += ' ';
368  version += release_id;
369  }
370  break;
371  } // else fallback to default
372  [[fallthrough]];
373  default:
374  if(v.wProductType != VER_NT_WORKSTATION) {
375  version = "Server";
376  }
377  }
378 
379  if(v.szCSDVersion && *v.szCSDVersion) {
380  version += " ";
381  version += unicode_cast<std::string>(std::wstring(v.szCSDVersion));
382  }
383 
384  version += " (";
385  // Add internal version numbers.
386  version += formatter()
387  << v.dwMajorVersion << '.'
388  << v.dwMinorVersion << '.'
389  << v.dwBuildNumber;
390  version += ")";
391 
392  return base + " " + version + " " + windows_runtime_arch();
393 
394 #else
395 
396  //
397  // "I don't know where I am" version.
398  //
399 
400  ERR_DU << "os_version(): unsupported platform\n";
401  return _("operating_system^<unknown>");
402 
403 #endif
404 }
405 
406 } // end namespace desktop
#define ERR_DU
Definition: version.cpp:61
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:263
ucs4_convert_impl::enableif< TD, typename TS::value_type >::type unicode_cast(const TS &source)
filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error)
std::string unescape(const std::string &str)
Remove all escape characters (backslash)
static std::string _(const char *str)
Definition: gettext.hpp:92
unsigned in
If equal to search_counter, the node is off the list.
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:86
std::ostringstream wrapper.
Definition: formatter.hpp:38
std::string path
Definition: game_config.cpp:38
Platform identification and version information functions.
std::string os_version()
Returns a string with the running OS name and version information.
Definition: version.cpp:212
mock_party p
static map_location::DIRECTION s
std::string os_version()
Returns a string with the running OS name and version information.
Definition: version.cpp:212
Declarations for File-IO.
static lg::log_domain log_desktop("desktop")
#define f
Standard logging facilities (interface).
void trim(std::string_view &s)
mock_char c