The Battle for Wesnoth  1.19.6+dev
version.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2015 - 2024
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 #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  auto res = RegGetValueA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", "DisplayVersion", RRF_RT_REG_SZ, nullptr, buf, &size);
100  if(res != ERROR_SUCCESS) {
101  res = RegGetValueA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", "ReleaseId", RRF_RT_REG_SZ, nullptr, buf, &size);
102  }
103 
104  return std::string{res == ERROR_SUCCESS ? buf : ""};
105 }
106 
107 std::string windows_runtime_arch()
108 {
109  SYSTEM_INFO si;
110  SecureZeroMemory(&si, sizeof(SYSTEM_INFO));
111  GetNativeSystemInfo(&si);
112 
113  switch(si.wProcessorArchitecture) {
114  case PROCESSOR_ARCHITECTURE_INTEL:
115  return "x86";
116  case PROCESSOR_ARCHITECTURE_AMD64:
117  return "x86_64";
118  case PROCESSOR_ARCHITECTURE_ARM:
119  return "arm";
120  case PROCESSOR_ARCHITECTURE_ARM64:
121  return "arm64";
122  case PROCESSOR_ARCHITECTURE_IA64:
123  return "ia64";
124  default:
125  return _("cpu_architecture^<unknown>");
126  }
127 }
128 
129 #endif
130 
131 #if defined(_X11)
132 /**
133  * Release policy for POSIX pipe streams opened with popen(3).
134  */
135 struct posix_pipe_release_policy
136 {
137  void operator()(std::FILE* f) const { if(f != nullptr) { pclose(f); } }
138 };
139 
140 /**
141  * Scoped POSIX pipe stream.
142  *
143  * The stream object type is the same as a regular file stream, but the release
144  * policy is different, as required by popen(3).
145  */
146 typedef std::unique_ptr<std::FILE, posix_pipe_release_policy> scoped_posix_pipe;
147 
148 /**
149  * Read a single line from the specified pipe.
150  *
151  * @returns An empty string if the pipe is invalid or nothing could be read.
152  */
153 std::string read_pipe_line(scoped_posix_pipe& p)
154 {
155  if(!p.get()) {
156  return "";
157  }
158 
159  std::string ver;
160  int c;
161 
162  ver.reserve(64);
163 
164  // We only want the first line.
165  while((c = std::fgetc(p.get())) && c != EOF && c != '\n' && c != '\r') {
166  ver.push_back(static_cast<char>(c));
167  }
168 
169  return ver;
170 }
171 
172 std::map<std::string, std::string> parse_fdo_osrelease(const std::string& path)
173 {
175  if(!in->good()) {
176  return {};
177  }
178 
179  std::map<std::string, std::string> res;
180 
181  // NOTE: intentionally basic "parsing" here. We are not supposed to see
182  // more complex shell syntax anyway.
183  // <https://www.freedesktop.org/software/systemd/man/os-release.html>
184  for(std::string s; std::getline(*in, s);) {
185  if(s.empty() || s.front() == '#') {
186  continue;
187  }
188 
189  auto eqsign_pos = s.find('=');
190  if(!eqsign_pos || eqsign_pos == std::string::npos) {
191  continue;
192  }
193 
194  auto lhs = s.substr(0, eqsign_pos),
195  rhs = eqsign_pos + 1 < s.length() ? utils::unescape(s.substr(eqsign_pos + 1)) : "";
196 
199 
200  // Unquote if the quotes match on both sides
201  if(rhs.length() >= 2 && rhs.front() == '"' && rhs.back() == '"') {
202  rhs.pop_back();
203  rhs.erase(0, 1);
204  }
205 
206  res.emplace(std::move(lhs), std::move(rhs));
207  }
208 
209  return res;
210 }
211 
212 #endif
213 
214 } // end anonymous namespace
215 
216 std::string os_version()
217 {
218 #if defined(__APPLE__) || defined(_X11)
219  // Some systems, e.g. SunOS, need "struct" here
220  struct utsname u;
221 
222  if(uname(&u) != 0) {
223  ERR_DU << "os_version: uname error (" << strerror(errno) << ")";
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 (" << GetLastError() << ')';
317  return base;
318  }
319 #ifdef _MSC_VER
320 #pragma warning(pop)
321 #endif
322 
323  const DWORD vnum = v.dwMajorVersion * 100 + v.dwMinorVersion;
324  std::string version;
325 
326  switch(vnum)
327  {
328  case 500:
329  version = "2000";
330  break;
331  case 501:
332  version = "XP";
333  break;
334  case 502:
335  // This will misidentify XP x64 but who really cares?
336  version = "Server 2003";
337  break;
338  case 600:
339  if(v.wProductType == VER_NT_WORKSTATION) {
340  version = "Vista";
341  } else {
342  version = "Server 2008";
343  }
344  break;
345  case 601:
346  if(v.wProductType == VER_NT_WORKSTATION) {
347  version = "7";
348  } else {
349  version = "Server 2008 R2";
350  }
351  break;
352  case 602:
353  if(v.wProductType == VER_NT_WORKSTATION) {
354  version = "8";
355  } else {
356  version = "Server 2012";
357  }
358  break;
359  case 603:
360  if(v.wProductType == VER_NT_WORKSTATION) {
361  version = "8.1";
362  } else {
363  version = "Server 2012 R2";
364  }
365  break;
366  case 1000:
367  if(v.wProductType == VER_NT_WORKSTATION) {
368  version = v.dwBuildNumber < 22000 ? "10" : "11";
369  const auto& release_id = windows_release_id();
370  if(!release_id.empty()) {
371  version += ' ';
372  version += release_id;
373  }
374  break;
375  } // else fallback to default
376  [[fallthrough]];
377  default:
378  if(v.wProductType != VER_NT_WORKSTATION) {
379  version = "Server";
380  }
381  }
382 
383  if(*v.szCSDVersion) {
384  version += " ";
385  version += unicode_cast<std::string>(std::wstring(v.szCSDVersion));
386  }
387 
388  version += " (";
389  // Add internal version numbers.
390  version += formatter()
391  << v.dwMajorVersion << '.'
392  << v.dwMinorVersion << '.'
393  << v.dwBuildNumber;
394  version += ")";
395 
396  return base + " " + version + " " + windows_runtime_arch();
397 
398 #else
399 
400  //
401  // "I don't know where I am" version.
402  //
403 
404  ERR_DU << "os_version(): unsupported platform";
405  return _("operating_system^<unknown>");
406 
407 #endif
408 }
409 
410 } // 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:216
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:326
std::string path
Definition: filesystem.cpp:91
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
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:61
#define f