The Battle for Wesnoth  1.17.4+dev
version.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2015 - 2022
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  // Some systems, e.g. SunOS, need "struct" here
221  struct utsname u;
222 
223  if(uname(&u) != 0) {
224  ERR_DU << "os_version: uname error (" << strerror(errno) << ")\n";
225  }
226 #endif
227 
228 #if defined(__APPLE__)
229 
230  //
231  // Standard Mac OS X version
232  //
233 
234  return desktop::apple::os_version() + " " + u.machine;
235 
236 #elif defined(_X11)
237 
238  //
239  // systemd/freedesktop.org method.
240  //
241 
242  std::map<std::string, std::string> osrel;
243 
244  static const std::string fdo_osrel_etc = "/etc/os-release";
245  static const std::string fdo_osrel_usr = "/usr/lib/os-release";
246 
247  if(filesystem::file_exists(fdo_osrel_etc)) {
248  osrel = parse_fdo_osrelease(fdo_osrel_etc);
249  } else if(filesystem::file_exists(fdo_osrel_usr)) {
250  osrel = parse_fdo_osrelease(fdo_osrel_usr);
251  }
252 
253  // Check both existence and emptiness in case some vendor sets PRETTY_NAME=""
254  auto osrel_distname = osrel["PRETTY_NAME"];
255  if(osrel_distname.empty()) {
256  osrel_distname = osrel["NAME"];
257  }
258 
259  if(!osrel_distname.empty()) {
260  return osrel_distname + " " + u.machine;
261  }
262 
263  //
264  // Linux Standard Base fallback.
265  //
266 
267  static const std::string lsb_release_bin = "/usr/bin/lsb_release";
268 
269  if(filesystem::file_exists(lsb_release_bin)) {
270  static const std::string cmdline = lsb_release_bin + " -s -d";
271 
272  scoped_posix_pipe p(popen(cmdline.c_str(), "r"));
273  std::string ver = read_pipe_line(p);
274 
275  if(ver.length() >= 2 && ver[0] == '"' && ver[ver.length() - 1] == '"') {
276  ver.erase(ver.length() - 1, 1);
277  ver.erase(0, 1);
278  }
279 
280  // Check this again in case we got "" above for some weird reason.
281  if(!ver.empty()) {
282  return ver + " " + u.machine;
283  }
284  }
285 
286  //
287  // POSIX uname version fallback.
288  //
289 
290  return formatter() << u.sysname << ' '
291  << u.release << ' '
292  << u.version << ' '
293  << u.machine;
294 
295 #elif defined(_WIN32)
296 
297  //
298  // Windows version.
299  //
300 
301  static const std::string base
302  = !on_wine() ? "Microsoft Windows" : "Wine/Microsoft Windows";
303 
304  OSVERSIONINFOEX v;
305 
306  SecureZeroMemory(&v, sizeof(OSVERSIONINFOEX));
307  v.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
308 
309 #ifdef _MSC_VER
310 // GetVersionEx is rather problematic, but it works for our usecase.
311 // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724451(v=vs.85).aspx
312 // for more info.
313 #pragma warning(push)
314 #pragma warning(disable:4996)
315 #endif
316  if(!GetVersionEx(reinterpret_cast<OSVERSIONINFO*>(&v))) {
317  ERR_DU << "os_version: GetVersionEx error ("
318  << GetLastError() << ")\n";
319  return base;
320  }
321 #ifdef _MSC_VER
322 #pragma warning(pop)
323 #endif
324 
325  const DWORD vnum = v.dwMajorVersion * 100 + v.dwMinorVersion;
326  std::string version;
327 
328  switch(vnum)
329  {
330  case 500:
331  version = "2000";
332  break;
333  case 501:
334  version = "XP";
335  break;
336  case 502:
337  // This will misidentify XP x64 but who really cares?
338  version = "Server 2003";
339  break;
340  case 600:
341  if(v.wProductType == VER_NT_WORKSTATION) {
342  version = "Vista";
343  } else {
344  version = "Server 2008";
345  }
346  break;
347  case 601:
348  if(v.wProductType == VER_NT_WORKSTATION) {
349  version = "7";
350  } else {
351  version = "Server 2008 R2";
352  }
353  break;
354  case 602:
355  if(v.wProductType == VER_NT_WORKSTATION) {
356  version = "8";
357  } else {
358  version = "Server 2012";
359  }
360  break;
361  case 603:
362  if(v.wProductType == VER_NT_WORKSTATION) {
363  version = "8.1";
364  } else {
365  version = "Server 2012 R2";
366  }
367  break;
368  case 1000:
369  if(v.wProductType == VER_NT_WORKSTATION) {
370  version = v.dwBuildNumber < 22000 ? "10" : "11";
371  const auto& release_id = windows_release_id();
372  if(!release_id.empty()) {
373  version += ' ';
374  version += release_id;
375  }
376  break;
377  } // else fallback to default
378  [[fallthrough]];
379  default:
380  if(v.wProductType != VER_NT_WORKSTATION) {
381  version = "Server";
382  }
383  }
384 
385  if(*v.szCSDVersion) {
386  version += " ";
387  version += unicode_cast<std::string>(std::wstring(v.szCSDVersion));
388  }
389 
390  version += " (";
391  // Add internal version numbers.
392  version += formatter()
393  << v.dwMajorVersion << '.'
394  << v.dwMinorVersion << '.'
395  << v.dwBuildNumber;
396  version += ")";
397 
398  return base + " " + version + " " + windows_runtime_arch();
399 
400 #else
401 
402  //
403  // "I don't know where I am" version.
404  //
405 
406  ERR_DU << "os_version(): unsupported platform\n";
407  return _("operating_system^<unknown>");
408 
409 #endif
410 }
411 
412 } // 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