From 0351ceb3ac5047cc7e2112ed41e461f4bee434a4 Mon Sep 17 00:00:00 2001 From: Leszek Kubik <39905449+intxgo@users.noreply.github.com> Date: Mon, 5 Feb 2024 08:45:26 +0100 Subject: [PATCH 1/6] adds Host.Info().NativeArchitecture --- providers/darwin/arch_darwin.go | 42 +++++++++++++++++++++++++- providers/darwin/arch_darwin_test.go | 6 ++++ providers/darwin/host_darwin.go | 9 ++++++ providers/linux/arch_linux.go | 20 ++++++++++++ providers/linux/arch_linux_test.go | 36 ++++++++++++++++++++++ providers/linux/host_linux.go | 9 ++++++ providers/windows/arch_windows.go | 37 +++++++++++++++++++++-- providers/windows/arch_windows_test.go | 36 ++++++++++++++++++++++ providers/windows/host_windows.go | 9 ++++++ types/host.go | 23 +++++++------- 10 files changed, 213 insertions(+), 14 deletions(-) create mode 100644 providers/linux/arch_linux_test.go create mode 100644 providers/windows/arch_windows_test.go diff --git a/providers/darwin/arch_darwin.go b/providers/darwin/arch_darwin.go index 8b3ed911..17b925c5 100644 --- a/providers/darwin/arch_darwin.go +++ b/providers/darwin/arch_darwin.go @@ -21,11 +21,17 @@ package darwin import ( "fmt" + "os" "golang.org/x/sys/unix" ) -const hardwareMIB = "hw.machine" +const ( + hardwareMIB = "hw.machine" + procTranslated = "sysctl.proc_translated" + archIntel = "x86_64" + archApple = "arm64" +) func Architecture() (string, error) { arch, err := unix.Sysctl(hardwareMIB) @@ -35,3 +41,37 @@ func Architecture() (string, error) { return arch, nil } + +func NativeArchitecture() (string, error) { + processArch, err := Architecture() + if err != nil { + return "", err + } + + // https://developer.apple.com/documentation/apple-silicon/about-the-rosetta-translation-environment + + data, err := unix.Sysctl(procTranslated) + if err != nil { + // macos without Rosetta installed doesn't have sysctl.proc_translated + if os.IsNotExist(err) { + return processArch, nil + } + return "", fmt.Errorf("failed to read sysctl.proc_translated: %w", err) + } + + nativeArch := "" + translated := data[0] + + switch translated { + case 0: + nativeArch = processArch + case 1: + // Rosetta 2 is supported only on Apple silicon + if processArch == archIntel { + nativeArch = archApple + } + default: + } + + return nativeArch, nil +} diff --git a/providers/darwin/arch_darwin_test.go b/providers/darwin/arch_darwin_test.go index 01a25a5d..340f7e87 100644 --- a/providers/darwin/arch_darwin_test.go +++ b/providers/darwin/arch_darwin_test.go @@ -28,3 +28,9 @@ func TestArchitecture(t *testing.T) { assert.NoError(t, err) assert.NotEmpty(t, a) } + +func TestNativeArchitecture(t *testing.T) { + a, err := NativeArchitecture() + assert.NoError(t, err) + assert.NotEmpty(t, a) +} diff --git a/providers/darwin/host_darwin.go b/providers/darwin/host_darwin.go index 70862e8a..4a6adc1e 100644 --- a/providers/darwin/host_darwin.go +++ b/providers/darwin/host_darwin.go @@ -167,6 +167,7 @@ func newHost() (*host, error) { h := &host{} r := &reader{} r.architecture(h) + r.nativeArchitecture(h) r.bootTime(h) r.hostname(h) r.network(h) @@ -206,6 +207,14 @@ func (r *reader) architecture(h *host) { h.info.Architecture = v } +func (r *reader) nativeArchitecture(h *host) { + v, err := NativeArchitecture() + if r.addErr(err) { + return + } + h.info.NativeArchitecture = v +} + func (r *reader) bootTime(h *host) { v, err := BootTime() if r.addErr(err) { diff --git a/providers/linux/arch_linux.go b/providers/linux/arch_linux.go index e1d28936..50e943e3 100644 --- a/providers/linux/arch_linux.go +++ b/providers/linux/arch_linux.go @@ -19,9 +19,13 @@ package linux import ( "fmt" + "os" + "strings" "syscall" ) +const procSysKernelArch = "/proc/sys/kernel/arch" + func Architecture() (string, error) { var uname syscall.Utsname if err := syscall.Uname(&uname); err != nil { @@ -38,3 +42,19 @@ func Architecture() (string, error) { return string(data), nil } + +func NativeArchitecture() (string, error) { + data, err := os.ReadFile(procSysKernelArch) + if err != nil { + if os.IsNotExist(err) { + return "", nil + } + + return "", fmt.Errorf("failed to read kernel arch: %w", err) + } + + nativeArch := string(data) + nativeArch = strings.TrimSpace(nativeArch) + + return string(data), nil +} diff --git a/providers/linux/arch_linux_test.go b/providers/linux/arch_linux_test.go new file mode 100644 index 00000000..340f7e87 --- /dev/null +++ b/providers/linux/arch_linux_test.go @@ -0,0 +1,36 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package darwin + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestArchitecture(t *testing.T) { + a, err := Architecture() + assert.NoError(t, err) + assert.NotEmpty(t, a) +} + +func TestNativeArchitecture(t *testing.T) { + a, err := NativeArchitecture() + assert.NoError(t, err) + assert.NotEmpty(t, a) +} diff --git a/providers/linux/host_linux.go b/providers/linux/host_linux.go index c2bdeb44..2e0d604d 100644 --- a/providers/linux/host_linux.go +++ b/providers/linux/host_linux.go @@ -156,6 +156,7 @@ func newHost(fs procFS) (*host, error) { h := &host{stat: stat, procFS: fs} r := &reader{} r.architecture(h) + r.nativeArchitecture(h) r.bootTime(h) r.containerized(h) r.hostname(h) @@ -197,6 +198,14 @@ func (r *reader) architecture(h *host) { h.info.Architecture = v } +func (r *reader) nativeArchitecture(h *host) { + v, err := NativeArchitecture() + if r.addErr(err) { + return + } + h.info.NativeArchitecture = v +} + func (r *reader) bootTime(h *host) { v, err := bootTime(h.procFS.FS) if r.addErr(err) { diff --git a/providers/windows/arch_windows.go b/providers/windows/arch_windows.go index 0edfc4d7..a59fcbc6 100644 --- a/providers/windows/arch_windows.go +++ b/providers/windows/arch_windows.go @@ -18,14 +18,47 @@ package windows import ( - windows "github.com/elastic/go-windows" + "golang.org/x/sys/windows" + + go_windows "github.com/elastic/go-windows" +) + +const ( + IMAGE_FILE_MACHINE_AMD64 = 0x8664 + IMAGE_FILE_MACHINE_ARM64 = 0xAA64 + archIntel = "x86_64" + archArm64 = "arm64" ) func Architecture() (string, error) { - systemInfo, err := windows.GetNativeSystemInfo() + systemInfo, err := go_windows.GetNativeSystemInfo() if err != nil { return "", err } return systemInfo.ProcessorArchitecture.String(), nil } + +func NativeArchitecture() (string, error) { + var processMachine, nativeMachine uint16 + // the pseudo handle doesn't need to be closed + var currentProcessHandle = windows.CurrentProcess() + + err := windows.IsWow64Process2(currentProcessHandle, &processMachine, &nativeMachine) + if err != nil { + return "", err + } + + nativeArch := "" + + switch nativeMachine { + case IMAGE_FILE_MACHINE_AMD64: + // for parity with Architecture() as amd64 and x86_64 are used interchangeably + nativeArch = archIntel + case IMAGE_FILE_MACHINE_ARM64: + nativeArch = archArm64 + default: + } + + return nativeArch, nil +} diff --git a/providers/windows/arch_windows_test.go b/providers/windows/arch_windows_test.go new file mode 100644 index 00000000..340f7e87 --- /dev/null +++ b/providers/windows/arch_windows_test.go @@ -0,0 +1,36 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package darwin + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestArchitecture(t *testing.T) { + a, err := Architecture() + assert.NoError(t, err) + assert.NotEmpty(t, a) +} + +func TestNativeArchitecture(t *testing.T) { + a, err := NativeArchitecture() + assert.NoError(t, err) + assert.NotEmpty(t, a) +} diff --git a/providers/windows/host_windows.go b/providers/windows/host_windows.go index f7b527bf..e476778b 100644 --- a/providers/windows/host_windows.go +++ b/providers/windows/host_windows.go @@ -102,6 +102,7 @@ func newHost() (*host, error) { h := &host{} r := &reader{} r.architecture(h) + r.nativeArchitecture(h) r.bootTime(h) r.hostname(h) r.network(h) @@ -141,6 +142,14 @@ func (r *reader) architecture(h *host) { h.info.Architecture = v } +func (r *reader) nativeArchitecture(h *host) { + v, err := NativeArchitecture() + if r.addErr(err) { + return + } + h.info.NativeArchitecture = v +} + func (r *reader) bootTime(h *host) { v, err := BootTime() if r.addErr(err) { diff --git a/types/host.go b/types/host.go index 27ea488c..9f3bc3b4 100644 --- a/types/host.go +++ b/types/host.go @@ -73,17 +73,18 @@ type VMStat interface { // HostInfo contains basic host information. type HostInfo struct { - Architecture string `json:"architecture"` // Hardware architecture (e.g. x86_64, arm, ppc, mips). - BootTime time.Time `json:"boot_time"` // Host boot time. - Containerized *bool `json:"containerized,omitempty"` // Is the process containerized. - Hostname string `json:"name"` // Hostname, lowercased. - IPs []string `json:"ip,omitempty"` // List of all IPs. - KernelVersion string `json:"kernel_version"` // Kernel version. - MACs []string `json:"mac"` // List of MAC addresses. - OS *OSInfo `json:"os"` // OS information. - Timezone string `json:"timezone"` // System timezone. - TimezoneOffsetSec int `json:"timezone_offset_sec"` // Timezone offset (seconds from UTC). - UniqueID string `json:"id,omitempty"` // Unique ID of the host (optional). + Architecture string `json:"architecture"` // Process hardware architecture (e.g. x86_64, arm, ppc, mips). + NativeArchitecture string `json:"native_architecture"` // Native OS hardware architecture (e.g. x86_64, arm, ppc, mips). + BootTime time.Time `json:"boot_time"` // Host boot time. + Containerized *bool `json:"containerized,omitempty"` // Is the process containerized. + Hostname string `json:"name"` // Hostname, lowercased. + IPs []string `json:"ip,omitempty"` // List of all IPs. + KernelVersion string `json:"kernel_version"` // Kernel version. + MACs []string `json:"mac"` // List of MAC addresses. + OS *OSInfo `json:"os"` // OS information. + Timezone string `json:"timezone"` // System timezone. + TimezoneOffsetSec int `json:"timezone_offset_sec"` // Timezone offset (seconds from UTC). + UniqueID string `json:"id,omitempty"` // Unique ID of the host (optional). } // Uptime returns the system uptime From 771ce2599f1fb4cc2e10ae0792c42a97935aa0d2 Mon Sep 17 00:00:00 2001 From: Leszek Kubik <39905449+intxgo@users.noreply.github.com> Date: Mon, 5 Feb 2024 09:33:44 +0100 Subject: [PATCH 2/6] fix package --- providers/linux/arch_linux_test.go | 2 +- providers/windows/arch_windows_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/providers/linux/arch_linux_test.go b/providers/linux/arch_linux_test.go index 340f7e87..be462031 100644 --- a/providers/linux/arch_linux_test.go +++ b/providers/linux/arch_linux_test.go @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -package darwin +package linux import ( "testing" diff --git a/providers/windows/arch_windows_test.go b/providers/windows/arch_windows_test.go index 340f7e87..1e60ac9c 100644 --- a/providers/windows/arch_windows_test.go +++ b/providers/windows/arch_windows_test.go @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -package darwin +package windows import ( "testing" From e220c6d96dc6969ed301ceff8cf300781304cdeb Mon Sep 17 00:00:00 2001 From: Leszek Kubik <39905449+intxgo@users.noreply.github.com> Date: Mon, 5 Feb 2024 11:42:43 +0100 Subject: [PATCH 3/6] fallback to /proc/version on Linux, add changelog --- .changelog/200.txt | 3 +++ providers/linux/arch_linux.go | 24 ++++++++++++++++++++++-- providers/windows/arch_windows.go | 12 ++++++------ 3 files changed, 31 insertions(+), 8 deletions(-) create mode 100644 .changelog/200.txt diff --git a/.changelog/200.txt b/.changelog/200.txt new file mode 100644 index 00000000..51afa524 --- /dev/null +++ b/.changelog/200.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +Adds NativeArchitecture to HostInfo to allow applications to detect whether they are running in emulation. +``` \ No newline at end of file diff --git a/providers/linux/arch_linux.go b/providers/linux/arch_linux.go index 50e943e3..06f7e502 100644 --- a/providers/linux/arch_linux.go +++ b/providers/linux/arch_linux.go @@ -24,7 +24,13 @@ import ( "syscall" ) -const procSysKernelArch = "/proc/sys/kernel/arch" +const ( + procSysKernelArch = "/proc/sys/kernel/arch" + procVersion = "/proc/version" + archAmd64 = "amd64" + archArm64 = "arm64" + archAarch64 = "aarch64" +) func Architecture() (string, error) { var uname syscall.Utsname @@ -47,6 +53,20 @@ func NativeArchitecture() (string, error) { data, err := os.ReadFile(procSysKernelArch) if err != nil { if os.IsNotExist(err) { + // fallback to checking version string + version, err := os.ReadFile(procVersion) + if err != nil { + return "", nil + } + + versionStr := string(version) + if strings.Contains(versionStr, archAmd64) { + return archAmd64, nil + } else if strings.Contains(versionStr, archArm64) { + // for parity with Architecture() and /proc/sys/kernel/arch + // as aarch64 and arm64 are used interchangeably + return archAarch64, nil + } return "", nil } @@ -54,7 +74,7 @@ func NativeArchitecture() (string, error) { } nativeArch := string(data) - nativeArch = strings.TrimSpace(nativeArch) + nativeArch = strings.TrimRight(nativeArch, "\n") return string(data), nil } diff --git a/providers/windows/arch_windows.go b/providers/windows/arch_windows.go index a59fcbc6..1c78efc5 100644 --- a/providers/windows/arch_windows.go +++ b/providers/windows/arch_windows.go @@ -24,10 +24,10 @@ import ( ) const ( - IMAGE_FILE_MACHINE_AMD64 = 0x8664 - IMAGE_FILE_MACHINE_ARM64 = 0xAA64 - archIntel = "x86_64" - archArm64 = "arm64" + imageFileMachineAmd64 = 0x8664 + imageFileMachineArm64 = 0xAA64 + archIntel = "x86_64" + archArm64 = "arm64" ) func Architecture() (string, error) { @@ -52,10 +52,10 @@ func NativeArchitecture() (string, error) { nativeArch := "" switch nativeMachine { - case IMAGE_FILE_MACHINE_AMD64: + case imageFileMachineAmd64: // for parity with Architecture() as amd64 and x86_64 are used interchangeably nativeArch = archIntel - case IMAGE_FILE_MACHINE_ARM64: + case imageFileMachineArm64: nativeArch = archArm64 default: } From 6a0c5210b6bb6f8a3612d7418fe75e48ce222a5f Mon Sep 17 00:00:00 2001 From: Leszek Kubik <39905449+intxgo@users.noreply.github.com> Date: Tue, 6 Feb 2024 19:01:50 +0100 Subject: [PATCH 4/6] review changes --- providers/darwin/arch_darwin.go | 10 +++------- providers/windows/arch_windows.go | 7 +++---- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/providers/darwin/arch_darwin.go b/providers/darwin/arch_darwin.go index 17b925c5..92251c35 100644 --- a/providers/darwin/arch_darwin.go +++ b/providers/darwin/arch_darwin.go @@ -50,7 +50,7 @@ func NativeArchitecture() (string, error) { // https://developer.apple.com/documentation/apple-silicon/about-the-rosetta-translation-environment - data, err := unix.Sysctl(procTranslated) + translated, err := unix.SysctlUint32(procTranslated) if err != nil { // macos without Rosetta installed doesn't have sysctl.proc_translated if os.IsNotExist(err) { @@ -59,18 +59,14 @@ func NativeArchitecture() (string, error) { return "", fmt.Errorf("failed to read sysctl.proc_translated: %w", err) } - nativeArch := "" - translated := data[0] + var nativeArch string switch translated { case 0: nativeArch = processArch case 1: // Rosetta 2 is supported only on Apple silicon - if processArch == archIntel { - nativeArch = archApple - } - default: + nativeArch = archApple } return nativeArch, nil diff --git a/providers/windows/arch_windows.go b/providers/windows/arch_windows.go index 1c78efc5..fc4b9a9f 100644 --- a/providers/windows/arch_windows.go +++ b/providers/windows/arch_windows.go @@ -20,7 +20,7 @@ package windows import ( "golang.org/x/sys/windows" - go_windows "github.com/elastic/go-windows" + gowindows "github.com/elastic/go-windows" ) const ( @@ -31,7 +31,7 @@ const ( ) func Architecture() (string, error) { - systemInfo, err := go_windows.GetNativeSystemInfo() + systemInfo, err := gowindows.GetNativeSystemInfo() if err != nil { return "", err } @@ -49,7 +49,7 @@ func NativeArchitecture() (string, error) { return "", err } - nativeArch := "" + var nativeArch string switch nativeMachine { case imageFileMachineAmd64: @@ -57,7 +57,6 @@ func NativeArchitecture() (string, error) { nativeArch = archIntel case imageFileMachineArm64: nativeArch = archArm64 - default: } return nativeArch, nil From e54f4cf540c6f3a4ca24e70cd8220b10bd973ca7 Mon Sep 17 00:00:00 2001 From: Leszek Kubik <39905449+intxgo@users.noreply.github.com> Date: Tue, 6 Feb 2024 19:50:57 +0100 Subject: [PATCH 5/6] improve comment --- providers/linux/arch_linux.go | 1 + 1 file changed, 1 insertion(+) diff --git a/providers/linux/arch_linux.go b/providers/linux/arch_linux.go index 06f7e502..b0756bdd 100644 --- a/providers/linux/arch_linux.go +++ b/providers/linux/arch_linux.go @@ -54,6 +54,7 @@ func NativeArchitecture() (string, error) { if err != nil { if os.IsNotExist(err) { // fallback to checking version string + // as /proc/sys/kernel/arch was added in 6.1 version, err := os.ReadFile(procVersion) if err != nil { return "", nil From 542d3ca573ba8252f0d3709ceda2a80f522dd00a Mon Sep 17 00:00:00 2001 From: Leszek Kubik <39905449+intxgo@users.noreply.github.com> Date: Thu, 8 Feb 2024 20:02:53 +0100 Subject: [PATCH 6/6] improve comment --- providers/linux/arch_linux.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/providers/linux/arch_linux.go b/providers/linux/arch_linux.go index b0756bdd..f1d807f0 100644 --- a/providers/linux/arch_linux.go +++ b/providers/linux/arch_linux.go @@ -50,11 +50,15 @@ func Architecture() (string, error) { } func NativeArchitecture() (string, error) { + // /proc/sys/kernel/arch was introduced in Kernel 6.1 + // https://www.kernel.org/doc/html/v6.1/admin-guide/sysctl/kernel.html#arch + // It's the same as uname -m, except that for a process running in emulation + // machine returned from syscall reflects the emulated machine, whilst /proc + // filesystem is read as file so its value is not emulated data, err := os.ReadFile(procSysKernelArch) if err != nil { if os.IsNotExist(err) { - // fallback to checking version string - // as /proc/sys/kernel/arch was added in 6.1 + // fallback to checking version string for older kernels version, err := os.ReadFile(procVersion) if err != nil { return "", nil