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