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