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