The Battle for Wesnoth  1.17.17+dev
version.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2015 - 2023
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 {
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) << ")";
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 (" << GetLastError() << ')';
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) {
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";
406  return _("operating_system^<unknown>");
407 
408 #endif
409 }
410 
411 } // end namespace desktop
std::ostringstream wrapper.
Definition: formatter.hpp:40
Platform identification and version information functions.
Declarations for File-IO.
unsigned in
If equal to search_counter, the node is off the list.
static std::string _(const char *str)
Definition: gettext.hpp:93
Standard logging facilities (interface).
std::string os_version()
std::string os_version()
Returns a string with the running OS name and version information.
Definition: version.cpp:217
filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error)
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:320
std::string path
Definition: filesystem.cpp:83
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:87
void trim(std::string_view &s)
std::string unescape(const std::string &str)
Remove all escape characters (backslash)
mock_char c
mock_party p
static map_location::DIRECTION s
static lg::log_domain log_desktop("desktop")
#define ERR_DU
Definition: version.cpp:62
#define f