1 /*
2    Copyright (C) 2015 - 2018 by Iris Morelle <shadowm2006@gmail.com>
3    Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY.
11 
12    See the COPYING file for more details.
13 */
14 
15 #define GETTEXT_DOMAIN "wesnoth-lib"
16 
17 #include "desktop/version.hpp"
18 
19 #include "filesystem.hpp"
20 #include "formatter.hpp"
21 #include "gettext.hpp"
22 #include "log.hpp"
23 #include "serialization/unicode.hpp"
24 
25 #include <cstring>
26 
27 #if defined(__APPLE__)
28 
29 #include "apple_version.hpp"
30 
31 #elif defined(_X11)
32 
33 #include <cerrno>
34 #include <sys/utsname.h>
35 
36 #endif
37 
38 #ifdef _WIN32
39 
40 #ifndef UNICODE
41 #define UNICODE
42 #endif
43 #define WIN32_LEAN_AND_MEAN
44 
45 #include <windows.h>
46 
47 #endif
48 
49 static lg::log_domain log_desktop("desktop");
50 #define ERR_DU LOG_STREAM(err, log_desktop)
51 #define LOG_DU LOG_STREAM(info, log_desktop)
52 
53 namespace desktop
54 {
55 
56 namespace
57 {
58 
59 #ifdef _WIN32
60 /**
61  * Detects whether we are running on Wine or not.
62  *
63  * This is for informational purposes only and all Windows code should assume
64  * we are running on the real thing instead.
65  */
on_wine()66 bool on_wine()
67 {
68 	HMODULE ntdll = GetModuleHandle(L"ntdll.dll");
69 	if(!ntdll) {
70 		return false;
71 	}
72 
73 	return GetProcAddress(ntdll, "wine_get_version") != nullptr;
74 }
75 #endif
76 
77 #if defined(_X11)
78 /**
79  * Release policy for POSIX pipe streams opened with popen(3).
80  */
81 struct posix_pipe_release_policy
82 {
operator ()desktop::__anon89d778720111::posix_pipe_release_policy83 	void operator()(std::FILE* f) const { if(f != nullptr) { pclose(f); } }
84 };
85 
86 /**
87  * Scoped POSIX pipe stream.
88  *
89  * The stream object type is the same as a regular file stream, but the release
90  * policy is different, as required by popen(3).
91  */
92 typedef std::unique_ptr<std::FILE, posix_pipe_release_policy> scoped_posix_pipe;
93 
94 /**
95  * Read a single line from the specified pipe.
96  *
97  * @returns An empty string if the pipe is invalid or nothing could be read.
98  */
read_pipe_line(scoped_posix_pipe & p)99 std::string read_pipe_line(scoped_posix_pipe& p)
100 {
101 	if(!p.get()) {
102 		return "";
103 	}
104 
105 	std::string ver;
106 	int c;
107 
108 	ver.reserve(64);
109 
110 	// We only want the first line.
111 	while((c = std::fgetc(p.get())) && c != EOF && c != '\n' && c != '\r') {
112 		ver.push_back(static_cast<char>(c));
113 	}
114 
115 	return ver;
116 }
117 #endif
118 
119 } // end anonymous namespace
120 
os_version()121 std::string os_version()
122 {
123 #if defined(__APPLE__)
124 
125 	//
126 	// Standard Mac OS X version
127 	//
128 
129 	return desktop::apple::os_version();
130 
131 #elif defined(_X11)
132 
133 	//
134 	// Linux Standard Base version.
135 	//
136 
137 	static const std::string lsb_release_bin = "/usr/bin/lsb_release";
138 
139 	if(filesystem::file_exists(lsb_release_bin)) {
140 		static const std::string cmdline = lsb_release_bin + " -s -d";
141 
142 		scoped_posix_pipe p(popen(cmdline.c_str(), "r"));
143 		std::string ver = read_pipe_line(p);
144 
145 		if(ver.length() >= 2 && ver[0] == '"' && ver[ver.length() - 1] == '"') {
146 			ver.erase(ver.length() - 1, 1);
147 			ver.erase(0, 1);
148 		}
149 
150 		// Check this again in case we got "" above for some weird reason.
151 		if(!ver.empty()) {
152 			return ver;
153 		}
154 	}
155 
156 	//
157 	// POSIX uname version fallback.
158 	//
159 
160 	utsname u;
161 
162 	if(uname(&u) != 0) {
163 		ERR_DU << "os_version: uname error (" << strerror(errno) << ")\n";
164 	}
165 
166 	return formatter() << u.sysname << ' '
167 						<< u.release << ' '
168 						<< u.version << ' '
169 						<< u.machine;
170 
171 #elif defined(_WIN32)
172 
173 	//
174 	// Windows version.
175 	//
176 
177 	static const std::string base
178 			= !on_wine() ? "Microsoft Windows" : "Wine/Microsoft Windows";
179 
180 	OSVERSIONINFOEX v { sizeof(OSVERSIONINFOEX) };
181 
182 #ifdef _MSC_VER
183 // GetVersionEx is rather problematic, but it works for our usecase.
184 // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724451(v=vs.85).aspx
185 // for more info.
186 #pragma warning(push)
187 #pragma warning(disable:4996)
188 #endif
189 	if(!GetVersionEx(reinterpret_cast<OSVERSIONINFO*>(&v))) {
190 		ERR_DU << "os_version: GetVersionEx error ("
191 			   << GetLastError() << ")\n";
192 		return base;
193 	}
194 #ifdef _MSC_VER
195 #pragma warning(pop)
196 #endif
197 
198 	const DWORD vnum = v.dwMajorVersion * 100 + v.dwMinorVersion;
199 	std::string version;
200 
201 	switch(vnum)
202 	{
203 		case 500:
204 			version = "2000";
205 			break;
206 		case 501:
207 			version = "XP";
208 			break;
209 		case 502:
210 			// This will misidentify XP x64 but who really cares?
211 			version = "Server 2003";
212 			break;
213 		case 600:
214 			if(v.wProductType == VER_NT_WORKSTATION) {
215 				version = "Vista";
216 			} else {
217 				version = "Server 2008";
218 			}
219 			break;
220 		case 601:
221 			if(v.wProductType == VER_NT_WORKSTATION) {
222 				version = "7";
223 			} else {
224 				version = "Server 2008 R2";
225 			}
226 			break;
227 		case 602:
228 			if(v.wProductType == VER_NT_WORKSTATION) {
229 				version = "8";
230 			} else {
231 				version = "Server 2012";
232 			}
233 			break;
234 		case 603:
235 			if(v.wProductType == VER_NT_WORKSTATION) {
236 				version = "8.1";
237 			} else {
238 				version = "Server 2012 R2";
239 			}
240 			break;
241 		case 1000:
242 			if(v.wProductType == VER_NT_WORKSTATION) {
243 				version = "10";
244 				break;
245 			} // else fallback to default
246 		default:
247 			if(v.wProductType != VER_NT_WORKSTATION) {
248 				version = "Server";
249 			}
250 	}
251 
252 	if(v.szCSDVersion && *v.szCSDVersion) {
253 		version += " ";
254 		version += unicode_cast<std::string>(std::wstring(v.szCSDVersion));
255 	}
256 
257 	version += " (";
258 	// Add internal version numbers.
259 	version += formatter()
260 			<< v.dwMajorVersion << '.'
261 			<< v.dwMinorVersion << '.'
262 			<< v.dwBuildNumber;
263 	version += ")";
264 
265 	return base + " " + version;
266 
267 #else
268 
269 	//
270 	// "I don't know where I am" version.
271 	//
272 
273 	ERR_DU << "os_version(): unsupported platform\n";
274 	return _("operating_system^<unknown>");
275 
276 #endif
277 }
278 
279 } // end namespace desktop
280