diff --git a/CHANGELOG.md b/CHANGELOG.md index 166e82600af..72190a3c6ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,8 @@ will be thrown. Note: if the duration specified in 'timeout' is greater than the system level timeout duration, a timeout may occur sooner than specified in 'timeout'. + * Added `Platform.operatingSystemVersion` that gives a platform-specific + String describing the version of the operating system. * `dart:core` * The `Uri` class now correctly handles paths while running on Node.js on diff --git a/pkg/dev_compiler/tool/input_sdk/patch/io_patch.dart b/pkg/dev_compiler/tool/input_sdk/patch/io_patch.dart index 1c6ba822c42..4a61e479889 100644 --- a/pkg/dev_compiler/tool/input_sdk/patch/io_patch.dart +++ b/pkg/dev_compiler/tool/input_sdk/patch/io_patch.dart @@ -233,6 +233,11 @@ class _Platform { throw new UnsupportedError("Platform._operatingSystem"); } + @patch + static _operatingSystemVersion() { + throw new UnsupportedError("Platform._operatingSystemVersion"); + } + @patch static _localHostname() { throw new UnsupportedError("Platform._localHostname"); diff --git a/runtime/bin/io_natives.cc b/runtime/bin/io_natives.cc index 63945b4bef1..9ecb61da120 100644 --- a/runtime/bin/io_natives.cc +++ b/runtime/bin/io_natives.cc @@ -87,6 +87,7 @@ namespace bin { V(NetworkInterface_ListSupported, 0) \ V(Platform_NumberOfProcessors, 0) \ V(Platform_OperatingSystem, 0) \ + V(Platform_OperatingSystemVersion, 0) \ V(Platform_PathSeparator, 0) \ V(Platform_LocalHostname, 0) \ V(Platform_ExecutableName, 0) \ diff --git a/runtime/bin/platform.cc b/runtime/bin/platform.cc index 88a52a26daa..20c7ff01108 100644 --- a/runtime/bin/platform.cc +++ b/runtime/bin/platform.cc @@ -19,6 +19,15 @@ void FUNCTION_NAME(Platform_OperatingSystem)(Dart_NativeArguments args) { Dart_SetReturnValue(args, DartUtils::NewString(Platform::OperatingSystem())); } +void FUNCTION_NAME(Platform_OperatingSystemVersion)(Dart_NativeArguments args) { + const char* version = Platform::OperatingSystemVersion(); + if (version == NULL) { + Dart_SetReturnValue(args, DartUtils::NewDartOSError()); + } else { + Dart_SetReturnValue(args, DartUtils::NewString(version)); + } +} + void FUNCTION_NAME(Platform_PathSeparator)(Dart_NativeArguments args) { Dart_SetReturnValue(args, DartUtils::NewString(File::PathSeparator())); } diff --git a/runtime/bin/platform.h b/runtime/bin/platform.h index dfe9cde9a06..d174902ad4f 100644 --- a/runtime/bin/platform.h +++ b/runtime/bin/platform.h @@ -24,6 +24,11 @@ class Platform { // deallocated by the caller. static const char* OperatingSystem(); + // Returns a string representing the version of the operating system. The + // format of the string is determined by the platform. The returned string + // should not be deallocated by the caller. + static const char* OperatingSystemVersion(); + // Returns the architecture name of the processor the VM is running on // (ia32, x64, arm, or arm64). static const char* HostArchitecture() { diff --git a/runtime/bin/platform_android.cc b/runtime/bin/platform_android.cc index 770c27f05eb..7e3f880e341 100644 --- a/runtime/bin/platform_android.cc +++ b/runtime/bin/platform_android.cc @@ -7,9 +7,10 @@ #include "bin/platform.h" -#include // NOLINT -#include // NOLINT -#include // NOLINT +#include // NOLINT +#include // NOLINT +#include // NOLINT +#include // NOLINT #include "bin/fdutils.h" #include "bin/file.h" @@ -73,6 +74,28 @@ const char* Platform::OperatingSystem() { return "android"; } +const char* Platform::OperatingSystemVersion() { + struct utsname info; + int ret = uname(&info); + if (ret != 0) { + return NULL; + } + const char* kFormat = "%s %s %s"; + int len = + snprintf(NULL, 0, kFormat, info.sysname, info.release, info.version); + if (len <= 0) { + return NULL; + } + char* result = DartUtils::ScopedCString(len + 1); + ASSERT(result != NULL); + len = snprintf(result, len + 1, kFormat, info.sysname, info.release, + info.version); + if (len <= 0) { + return NULL; + } + return result; +} + const char* Platform::LibraryPrefix() { return "lib"; } diff --git a/runtime/bin/platform_fuchsia.cc b/runtime/bin/platform_fuchsia.cc index b322b439acf..320bc9fe6a0 100644 --- a/runtime/bin/platform_fuchsia.cc +++ b/runtime/bin/platform_fuchsia.cc @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "bin/dartutils.h" @@ -37,6 +38,28 @@ const char* Platform::OperatingSystem() { return "fuchsia"; } +const char* Platform::OperatingSystemVersion() { + struct utsname info; + int ret = uname(&info); + if (ret != 0) { + return NULL; + } + const char* kFormat = "%s %s %s"; + int len = + snprintf(NULL, 0, kFormat, info.sysname, info.release, info.version); + if (len <= 0) { + return NULL; + } + char* result = DartUtils::ScopedCString(len + 1); + ASSERT(result != NULL); + len = snprintf(result, len + 1, kFormat, info.sysname, info.release, + info.version); + if (len <= 0) { + return NULL; + } + return result; +} + const char* Platform::LibraryPrefix() { return "lib"; } diff --git a/runtime/bin/platform_linux.cc b/runtime/bin/platform_linux.cc index a085dff1773..e681f034b89 100644 --- a/runtime/bin/platform_linux.cc +++ b/runtime/bin/platform_linux.cc @@ -7,9 +7,10 @@ #include "bin/platform.h" -#include // NOLINT -#include // NOLINT -#include // NOLINT +#include // NOLINT +#include // NOLINT +#include // NOLINT +#include // NOLINT #include "bin/fdutils.h" #include "bin/file.h" @@ -72,6 +73,28 @@ const char* Platform::OperatingSystem() { return "linux"; } +const char* Platform::OperatingSystemVersion() { + struct utsname info; + int ret = uname(&info); + if (ret != 0) { + return NULL; + } + const char* kFormat = "%s %s %s"; + int len = + snprintf(NULL, 0, kFormat, info.sysname, info.release, info.version); + if (len <= 0) { + return NULL; + } + char* result = DartUtils::ScopedCString(len + 1); + ASSERT(result != NULL); + len = snprintf(result, len + 1, kFormat, info.sysname, info.release, + info.version); + if (len <= 0) { + return NULL; + } + return result; +} + const char* Platform::LibraryPrefix() { return "lib"; } diff --git a/runtime/bin/platform_macos.cc b/runtime/bin/platform_macos.cc index b02bca7554f..e563788ceb3 100644 --- a/runtime/bin/platform_macos.cc +++ b/runtime/bin/platform_macos.cc @@ -13,11 +13,12 @@ #include // NOLINT #endif // !HOST_OS_IOS #include -#include // NOLINT -#include // NOLINT -#include // NOLINT -#include // NOLINT -#include // NOLINT +#include // NOLINT +#include // NOLINT +#include // NOLINT +#include // NOLINT +#include // NOLINT +#include // NOLINT #include "bin/fdutils.h" #include "bin/file.h" @@ -90,6 +91,28 @@ const char* Platform::OperatingSystem() { #endif } +const char* Platform::OperatingSystemVersion() { + struct utsname info; + int ret = uname(&info); + if (ret != 0) { + return NULL; + } + const char* kFormat = "%s %s %s"; + int len = + snprintf(NULL, 0, kFormat, info.sysname, info.release, info.version); + if (len <= 0) { + return NULL; + } + char* result = DartUtils::ScopedCString(len + 1); + ASSERT(result != NULL); + len = snprintf(result, len + 1, kFormat, info.sysname, info.release, + info.version); + if (len <= 0) { + return NULL; + } + return result; +} + const char* Platform::LibraryPrefix() { return "lib"; } diff --git a/runtime/bin/platform_patch.dart b/runtime/bin/platform_patch.dart index a634357534f..b3ed2548ae8 100644 --- a/runtime/bin/platform_patch.dart +++ b/runtime/bin/platform_patch.dart @@ -11,6 +11,8 @@ class _Platform { @patch static String _operatingSystem() native "Platform_OperatingSystem"; @patch + static _operatingSystemVersion() native "Platform_OperatingSystemVersion"; + @patch static _localHostname() native "Platform_LocalHostname"; @patch static _executable() native "Platform_ExecutableName"; diff --git a/runtime/bin/platform_win.cc b/runtime/bin/platform_win.cc index d8e0a434542..5568af08081 100644 --- a/runtime/bin/platform_win.cc +++ b/runtime/bin/platform_win.cc @@ -213,6 +213,89 @@ const char* Platform::OperatingSystem() { return "windows"; } +// We pull the version number, and other version information out of the +// registry because GetVersionEx() and friends lie about the OS version after +// Windows 8.1. See: +// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724451(v=vs.85).aspx +static const wchar_t* kCurrentVersion = + L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"; + +static bool GetCurrentVersionDWord(const wchar_t* field, DWORD* value) { + DWORD value_size = sizeof(*value); + LONG err = RegGetValue(HKEY_LOCAL_MACHINE, kCurrentVersion, field, + RRF_RT_REG_DWORD, NULL, value, &value_size); + return err == ERROR_SUCCESS; +} + +static bool GetCurrentVersionString(const wchar_t* field, const char** value) { + wchar_t wversion[256]; + DWORD wversion_size = sizeof(wversion); + LONG err = RegGetValue(HKEY_LOCAL_MACHINE, kCurrentVersion, field, + RRF_RT_REG_SZ, NULL, wversion, &wversion_size); + if (err != ERROR_SUCCESS) { + return false; + } + *value = StringUtilsWin::WideToUtf8(wversion); + return true; +} + +static const char* VersionNumber() { + // Try to get CurrentMajorVersionNumber. If that fails, fall back on + // CurrentVersion. If it succeeds also get CurrentMinorVersionNumber. + DWORD major; + if (!GetCurrentVersionDWord(L"CurrentMajorVersionNumber", &major)) { + const char* version; + if (!GetCurrentVersionString(L"CurrentVersion", &version)) { + return NULL; + } + return version; + } + + DWORD minor; + if (!GetCurrentVersionDWord(L"CurrentMinorVersionNumber", &minor)) { + return NULL; + } + const char* kFormat = "%d.%d"; + int len = snprintf(NULL, 0, kFormat, major, minor); + if (len < 0) { + return NULL; + } + char* result = DartUtils::ScopedCString(len + 1); + ASSERT(result != NULL); + len = snprintf(result, len + 1, kFormat, major, minor); + if (len < 0) { + return NULL; + } + return result; +} + +const char* Platform::OperatingSystemVersion() { + // Get the product name, e.g. "Windows 10 Home". + const char* name; + if (!GetCurrentVersionString(L"ProductName", &name)) { + return NULL; + } + + // Get the version number, e.g. "10.0". + const char* version_number = VersionNumber(); + if (version_number == NULL) { + return NULL; + } + + // Get the build number. + const char* build; + if (!GetCurrentVersionString(L"CurrentBuild", &build)) { + return NULL; + } + + // Put it all together. + const char* kFormat = "\"%s\" %s (Build %s)"; + int len = snprintf(NULL, 0, kFormat, name, version_number, build); + char* result = DartUtils::ScopedCString(len + 1); + len = snprintf(result, len + 1, kFormat, name, version_number, build); + return result; +} + const char* Platform::LibraryPrefix() { return ""; } diff --git a/sdk/lib/_internal/js_runtime/lib/io_patch.dart b/sdk/lib/_internal/js_runtime/lib/io_patch.dart index 773fa5b074c..c6267565811 100644 --- a/sdk/lib/_internal/js_runtime/lib/io_patch.dart +++ b/sdk/lib/_internal/js_runtime/lib/io_patch.dart @@ -233,6 +233,11 @@ class _Platform { throw new UnsupportedError("Platform._operatingSystem"); } + @patch + static _operatingSystemVersion() { + throw new UnsupportedError("Platform._operatingSystemVersion"); + } + @patch static _localHostname() { throw new UnsupportedError("Platform._localHostname"); diff --git a/sdk/lib/io/platform.dart b/sdk/lib/io/platform.dart index 6ec48e36138..01be4ca9102 100644 --- a/sdk/lib/io/platform.dart +++ b/sdk/lib/io/platform.dart @@ -69,6 +69,7 @@ class Platform { static final _numberOfProcessors = _Platform.numberOfProcessors; static final _pathSeparator = _Platform.pathSeparator; static final _operatingSystem = _Platform.operatingSystem; + static final _operatingSystemVersion = _Platform.operatingSystemVersion; static final _localHostname = _Platform.localHostname; static final _version = _Platform.version; static final _localeName = _Platform.localeName; @@ -94,6 +95,11 @@ class Platform { */ static String get operatingSystem => _operatingSystem; + /** + * A string representing the version of the operating system or platform. + */ + static String get operatingSystemVersion => _operatingSystemVersion; + /** * The local hostname for the system. */ diff --git a/sdk/lib/io/platform_impl.dart b/sdk/lib/io/platform_impl.dart index 2eddc6da20c..df25981c9e7 100644 --- a/sdk/lib/io/platform_impl.dart +++ b/sdk/lib/io/platform_impl.dart @@ -8,6 +8,7 @@ class _Platform { external static int _numberOfProcessors(); external static String _pathSeparator(); external static String _operatingSystem(); + external static _operatingSystemVersion(); external static _localHostname(); external static _executable(); external static _resolvedExecutable(); @@ -61,13 +62,24 @@ class _Platform { static String get operatingSystem => _operatingSystem(); static Uri get script => _script(); + static String _cachedOSVersion; + static String get operatingSystemVersion { + if (_cachedOSVersion == null) { + var result = _operatingSystemVersion(); + if (result is OSError) { + throw result; + } + _cachedOSVersion = result; + } + return _cachedOSVersion; + } + static String get localHostname { var result = _localHostname(); if (result is OSError) { throw result; - } else { - return result; } + return result; } static List get executableArguments => _executableArguments(); diff --git a/tests/standalone/io/platform_os_version_test.dart b/tests/standalone/io/platform_os_version_test.dart new file mode 100644 index 00000000000..ee276aabddb --- /dev/null +++ b/tests/standalone/io/platform_os_version_test.dart @@ -0,0 +1,14 @@ +// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import "dart:io"; + +import "package:expect/expect.dart"; + +main() { + var version = Platform.operatingSystemVersion; + Expect.isNotNull(version); + Expect.isTrue(version is String); + print(Platform.operatingSystemVersion); +}