├── .envrc ├── .github ├── settings.yml └── workflows │ └── gh-pages.yml ├── .gitignore ├── .golangci.yml ├── .mergify.yml ├── LICENSE ├── README.md ├── cmd └── root.go ├── docs ├── content │ ├── assets │ │ └── images │ │ │ ├── hero.svg │ │ │ └── logo.png │ ├── contributing │ │ ├── code.md │ │ └── docs.md │ ├── getting-started │ │ └── generate-report.md │ ├── index.md │ ├── reference │ │ └── .gitignore │ └── stylesheets │ │ └── extra.css └── theme │ └── home.html ├── flake.lock ├── flake.nix ├── go.mod ├── go.sum ├── main.go ├── mkdocs.yml ├── nix ├── devshells │ ├── default.nix │ └── docs.nix ├── formatter.nix ├── package.nix └── packages │ └── nixos-facter │ ├── default.nix │ ├── package.nix │ └── tests │ ├── default.nix │ └── disko.nix ├── pkg ├── build │ └── build.go ├── ephem │ ├── ephem.go │ ├── swap.go │ ├── swap_enum_type.go │ └── swap_test.go ├── facter │ ├── facter.go │ ├── hardware.go │ └── smbios.go ├── hwinfo │ ├── detail.go │ ├── detail_bios.go │ ├── detail_cpu.go │ ├── detail_enum_cpu_arch.go │ ├── detail_enum_pci_flag.go │ ├── detail_enum_type.go │ ├── detail_isa_pnp_dev.go │ ├── detail_monitor.go │ ├── detail_pci.go │ ├── detail_sys.go │ ├── detail_usb.go │ ├── detail_usb_enum_usb_class.go │ ├── driver_info.go │ ├── driver_info_display.go │ ├── driver_info_dsl.go │ ├── driver_info_enum_type.go │ ├── driver_info_isdn.go │ ├── driver_info_keyboard.go │ ├── driver_info_module.go │ ├── driver_info_mouse.go │ ├── driver_info_x11.go │ ├── hardware.go │ ├── hardware_enum_base_class.go │ ├── hardware_enum_bus.go │ ├── hardware_enum_hardware_class.go │ ├── hardware_enum_hotplug.go │ ├── hardware_enum_probe_feature.go │ ├── hardware_enum_sub_class_keyboard.go │ ├── hardware_enum_sub_class_mouse.go │ ├── hwinfo.go │ ├── id.go │ ├── id_tag_enum.go │ ├── input.go │ ├── iommu.go │ ├── resource.go │ ├── resource_baud.go │ ├── resource_cache.go │ ├── resource_disk_geo.go │ ├── resource_dma.go │ ├── resource_enum_access_flags.go │ ├── resource_enum_geo_type.go │ ├── resource_enum_size_unit.go │ ├── resource_enum_type.go │ ├── resource_enum_yes_no_flags.go │ ├── resource_fc.go │ ├── resource_frame_buffer.go │ ├── resource_hw_addr.go │ ├── resource_init_strings.go │ ├── resource_io.go │ ├── resource_irq.go │ ├── resource_link.go │ ├── resource_mem.go │ ├── resource_monitor.go │ ├── resource_phys_mem.go │ ├── resource_pppd_option.go │ ├── resource_size.go │ ├── resource_wlan.go │ ├── smbios.go │ ├── smbios_any.go │ ├── smbios_bios.go │ ├── smbios_board.go │ ├── smbios_cache.go │ ├── smbios_chassis.go │ ├── smbios_config.go │ ├── smbios_enum_type.go │ ├── smbios_group_associations.go │ ├── smbios_hardware_security.go │ ├── smbios_language.go │ ├── smbios_memory64_error.go │ ├── smbios_memory_array.go │ ├── smbios_memory_array_mapped_address.go │ ├── smbios_memory_device.go │ ├── smbios_memory_device_mapped_address.go │ ├── smbios_memory_error.go │ ├── smbios_oem_strings.go │ ├── smbios_onboard.go │ ├── smbios_pointing_device.go │ ├── smbios_port_connector.go │ ├── smbios_power_controls.go │ ├── smbios_processor.go │ ├── smbios_slot.go │ ├── smbios_sys_info.go │ ├── utils.go │ └── virtual_network_devices.go ├── linux │ └── input │ │ ├── input.go │ │ ├── input_bus.go │ │ └── input_test.go ├── udev │ ├── udev.go │ └── udev_type.go └── virt │ ├── virt.go │ └── virt_enum_type.go ├── renovate.json └── scripts └── create-release.sh /.envrc: -------------------------------------------------------------------------------- 1 | if ! has nix_direnv_version || ! nix_direnv_version 3.0.5; then 2 | source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.5/direnvrc" "sha256-RuwIS+QKFj/T9M2TFXScjBsLR6V3A17YVoEW/Q6AZ1w=" 3 | fi 4 | 5 | watch_file flake.nix 6 | watch_file nix/devshells/default.nix 7 | 8 | use flake -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: Deploy docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - "v*" 9 | 10 | # Allows you to run this workflow manually from the Actions tab 11 | workflow_dispatch: 12 | 13 | jobs: 14 | deploy: 15 | runs-on: ubuntu-24.04 16 | permissions: 17 | contents: write 18 | concurrency: 19 | group: ${{ github.workflow }} 20 | steps: 21 | - uses: actions/checkout@v4 22 | with: 23 | fetch-depth: 0 24 | - uses: cachix/install-nix-action@V27 25 | with: 26 | extra_nix_config: | 27 | accept-flake-config = true 28 | experimental-features = nix-command flakes 29 | - name: Configure Git user 30 | run: | 31 | git config --local user.email "github-actions[bot]@users.noreply.github.com" 32 | git config --local user.name "github-actions[bot]" 33 | - name: Deploy main 34 | if: ${{ github.ref_name == 'main' }} 35 | run: | 36 | nix develop .#docs --command bash -c "mike deploy -p -u main" 37 | - name: Deploy version 38 | if: startsWith(github.ref, 'refs/tags/v') 39 | run: | 40 | REF_NAME=${{ github.ref_name }} 41 | MAJOR_MINOR=${REF_NAME%.*} 42 | # strip the leading v from the ref_name 43 | # update the latest alias 44 | nix develop .#docs --command bash -c "mike deploy -p -u ${MAJOR_MINOR} latest" 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | result* 3 | *.qcow2 4 | .data 5 | report.json 6 | .cache 7 | site -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | enable-all: true 3 | disable: 4 | - depguard 5 | - exhaustruct 6 | - exportloopref 7 | - funlen 8 | - godox 9 | - mnd 10 | - varnamelen 11 | - forbidigo 12 | - gocognit 13 | - gocyclo 14 | - cyclop 15 | - err113 16 | - maintidx 17 | - tagliatelle # prefer snake_case in json fields 18 | - gochecknoglobals 19 | - gochecknoinits 20 | # would be nice to have but too many tests depend on environment variables, which is not allowed for t.Parallel() 21 | - paralleltest 22 | - nlreturn # find this annoying more than useful 23 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | queue_rules: 2 | - name: default 3 | merge_conditions: 4 | - check-success=buildbot/nix-eval 5 | merge_method: rebase 6 | 7 | pull_request_rules: 8 | - name: refactored queue action rule 9 | conditions: 10 | - base=main 11 | - label~=merge-queue|dependencies 12 | actions: 13 | queue: 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GPL v3 License 2 | 3 | Copyright (C) 2024 Numtide & Contributors 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 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NixOS Facter 2 | 3 | NixOS Facter aims to be an alternative to projects such as [NixOS Hardware] and [nixos-generate-config]. 4 | It solves the problem of bootstrapping [NixOS configurations] by deferring decisions about hardware and other 5 | aspects of the target platform to NixOS modules. 6 | 7 | We do this by first generating a machine-readable report (JSON) which captures detailed information about the machine 8 | or virtual environment it was executed within. 9 | 10 | This report is then passed to a series of [NixOS modules] which can make a variety of decisions, 11 | some simple, some more complex, enabling things like automatic configuration of network controllers or graphics cards, 12 | USB devices, and so on. 13 | 14 | ## Project Structure 15 | 16 | This repository contains the binary for generating the report. 17 | 18 | [NixOS Facter Modules] contains the necessary NixOS modules for making use of the report in a NixOS configuration. 19 | 20 | For more information, please see the [docs]. 21 | 22 | ## Quick Start 23 | 24 | To generate a report using `nixos-facter` from nixpkgs: 25 | 26 | ```console 27 | # you must run this as root 28 | ❯ sudo nix run --option experimental-features "nix-command flakes" nixpkgs#nixos-facter -- -o facter.json 29 | ``` 30 | 31 | To use the latest development version from this flake: 32 | 33 | ```console 34 | # you must run this as root 35 | ❯ sudo nix run \ 36 | --option experimental-features "nix-command flakes" \ 37 | --option extra-substituters https://numtide.cachix.org \ 38 | --option extra-trusted-public-keys numtide.cachix.org-1:2ps1kLBUWjxIneOy1Ik6cQjb41X0iXVXeHigGmycPPE= \ 39 | github:numtide/nixos-facter -- -o facter.json 40 | ``` 41 | 42 | ## Contributing 43 | 44 | Contributions are always welcome! 45 | 46 | ## License 47 | 48 | This software is provided free under [GNU GPL v3]. 49 | 50 | --- 51 | 52 | This project is supported by [Clan] and [Numtide](https://numtide.com/). 53 | 54 | ## Clan 55 | 56 | [Clan] is a peer-to-peer management framework based on [NixOS] that has: 57 | 58 | - a uniform interface 59 | - automated secret management 60 | - automated service setup 61 | - automated backups 62 | - peer-to-peer Mesh VPN 63 | 64 | Get in touch and learn more at: 65 | 66 | - [clan.lol](https://clan.lol) 67 | - [Matrix channel](https://matrix.to/#/#clan:clan.lol) for live discussions. 68 | - IRC bridge on [hackint#clan](https://chat.hackint.org/#/connect?join=clan) for real-time chat support. 69 | 70 | ## Numtide 71 | 72 | ![Numtide Logo](https://codahosted.io/docs/6FCIMTRM0p/blobs/bl-sgSunaXYWX/077f3f9d7d76d6a228a937afa0658292584dedb5b852a8ca370b6c61dabb7872b7f617e603f1793928dc5410c74b3e77af21a89e435fa71a681a868d21fd1f599dd10a647dd855e14043979f1df7956f67c3260c0442e24b34662307204b83ea34de929d) 73 | 74 | We’re a team of independent freelancers that love open source. 75 | We help our customers make their project lifecycles more efficient by: 76 | 77 | - Providing and supporting useful tools such as this one. 78 | - Building and deploying infrastructure, and offering dedicated DevOps support. 79 | - Building their in-house Nix skills, and integrating Nix with their workflows. 80 | - Developing additional features and tools. 81 | - Carrying out custom research and development. 82 | 83 | [Contact us](https://numtide.com/contact) if you have a project in mind, 84 | or if you need help with any of our supported tools, including this one. 85 | 86 | We'd love to hear from you. 87 | 88 | [Clan]: https://clan.lol 89 | [NixOS configurations]: https://nixos.org/manual/nixos/stable/#sec-configuration-syntax 90 | [NixOS Hardware]: https://github.com/NixOS/nixos-hardware 91 | [NixOS Facter Modules]: https://github.com/numtide/nixos-facter-modules 92 | [NixOS modules]: https://github.com/numtide/nixos-facter-modules 93 | [nixos-generate-config]: https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/installer/tools/nixos-generate-config.pl 94 | [Numtide Binary Cache]: https://numtide.cachix.org 95 | [nixos-facter]: https://github.com/numtide/nixos-facter 96 | [nixpkgs]: https://github.com/nixos/nixpkgs 97 | [docs]: https://nix-community.github.io/nixos-facter 98 | [GNU GPL v3]: ./LICENSE 99 | [NixOS]: https://nixos.org 100 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "log/slog" 9 | "os" 10 | "strings" 11 | 12 | "github.com/numtide/nixos-facter/pkg/build" 13 | "github.com/numtide/nixos-facter/pkg/facter" 14 | "github.com/numtide/nixos-facter/pkg/hwinfo" 15 | "github.com/numtide/nixos-facter/pkg/udev" 16 | ) 17 | 18 | var ( 19 | outputPath string 20 | logLevel string 21 | hardwareFeatures []string 22 | version bool 23 | 24 | scanner = facter.Scanner{} 25 | ) 26 | 27 | func init() { 28 | // Define flags 29 | flag.StringVar(&outputPath, "output", "", "path to write the report") 30 | flag.StringVar(&outputPath, "o", "", "path to write the report") 31 | flag.BoolVar(&scanner.Swap, "swap", false, "capture swap entries") 32 | flag.BoolVar( 33 | &scanner.Ephemeral, "ephemeral", false, 34 | "capture all ephemeral properties e.g. swap, filesystems and so on", 35 | ) 36 | flag.BoolVar( 37 | &version, "version", false, 38 | "print version and exit", 39 | ) 40 | flag.StringVar(&logLevel, "log-level", "info", "log level") 41 | 42 | defaultFeatures := []string{ 43 | "memory", "pci", "net", "serial", "cpu", "bios", "monitor", "scsi", "usb", "prom", "sbus", "sys", "sysfs", 44 | "udev", "block", "wlan", 45 | } 46 | 47 | var filteredFeatures []string 48 | 49 | for _, feature := range hwinfo.ProbeFeatureStrings() { 50 | if feature != "default" && feature != "int" { 51 | filteredFeatures = append(filteredFeatures, feature) 52 | } 53 | } 54 | 55 | hardwareFeatures = defaultFeatures 56 | 57 | flag.Func("hardware", "Hardware items to probe (comma separated).", func(flagValue string) error { 58 | hardwareFeatures = strings.Split(flagValue, ",") 59 | return nil 60 | }) 61 | 62 | possibleValues := strings.Join(filteredFeatures, ",") 63 | defaultValues := strings.Join(defaultFeatures, ",") 64 | 65 | const usage = `nixos-facter [flags] 66 | Hardware report generator %s (%s) 67 | 68 | Usage: 69 | nixos-facter [flags] 70 | 71 | Flags: 72 | --ephemeral capture all ephemeral properties e.g. swap, filesystems and so on 73 | -h, --help help for nixos-facter 74 | -o, --output string path to write the report 75 | --swap capture swap entries 76 | --version version for nixos-facter 77 | --log-level string log level, one of (default "info") 78 | --hardware strings Hardware items to probe. 79 | Default: %s 80 | Possible values: %s 81 | 82 | ` 83 | 84 | // Custom usage function 85 | flag.Usage = func() { fmt.Fprintf(os.Stderr, usage, build.Version, build.System, defaultValues, possibleValues) } 86 | } 87 | 88 | func Execute() { 89 | // check udev version 90 | if udevVersion, err := udev.Version(); err != nil { 91 | log.Fatalf("failed to get systemd version: %v", err) 92 | } else if udevVersion < 252 { 93 | log.Fatalf("udev version %d is too old, please upgrade to at least 252", udevVersion) 94 | } 95 | 96 | flag.Parse() 97 | 98 | if version { 99 | fmt.Printf("%s\n", build.Version) 100 | return 101 | } 102 | 103 | // Check if the effective user id is 0 e.g. root 104 | if os.Geteuid() != 0 { 105 | log.Fatalf("you must run this program as root") 106 | } 107 | 108 | // Convert the hardware features into probe features 109 | for _, str := range hardwareFeatures { 110 | probe, err := hwinfo.ProbeFeatureString(str) 111 | if err != nil { 112 | log.Fatalf("invalid hardware feature: %v", err) 113 | } 114 | 115 | scanner.Features = append(scanner.Features, probe) 116 | } 117 | 118 | // Set the log level 119 | 120 | var slogLevel slog.Level 121 | if err := slogLevel.UnmarshalText([]byte(logLevel)); err != nil { 122 | log.Fatalf("invalid log level: %v", err) 123 | } 124 | 125 | switch slogLevel { 126 | case slog.LevelDebug: 127 | log.SetFlags(log.LstdFlags | log.Lshortfile) 128 | case slog.LevelInfo: 129 | log.SetFlags(log.LstdFlags) 130 | case slog.LevelWarn, slog.LevelError: 131 | log.SetFlags(0) 132 | default: 133 | log.Fatalf("invalid log level: %s", logLevel) 134 | } 135 | 136 | slog.SetLogLoggerLevel(slogLevel) 137 | 138 | report, err := scanner.Scan() 139 | if err != nil { 140 | log.Fatalf("failed to scan: %v", err) 141 | } 142 | 143 | bytes, err := json.MarshalIndent(report, "", " ") 144 | if err != nil { 145 | log.Fatalf("failed to marshal report to json: %v", err) 146 | } 147 | 148 | // If a file path is provided write the report to it, otherwise output the report on stdout 149 | if outputPath == "" { 150 | if _, err = os.Stdout.Write(bytes); err != nil { 151 | log.Fatalf("failed to write report to stdout: %v", err) 152 | } 153 | 154 | fmt.Println() 155 | } else if err = os.WriteFile(outputPath, bytes, 0o600); err != nil { 156 | log.Fatalf("failed to write report to output path: %v", err) 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /docs/content/assets/images/hero.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 9 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/content/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nix-community/nixos-facter/1b97f5d94bf3346bf743e188b1b5b14d049baded/docs/content/assets/images/logo.png -------------------------------------------------------------------------------- /docs/content/contributing/code.md: -------------------------------------------------------------------------------- 1 | # Code 2 | 3 | ## Pre-requisites 4 | 5 | You will need to have the following installed: 6 | 7 | - [Nix] 8 | - [Direnv] 9 | 10 | !!! important 11 | 12 | We use a [Flake]-based workflow. You can certainly develop for `nixos-facter` without Flakes and leverage 13 | much of what is listed below, but it is left up to the reader to determine how to make that work. 14 | 15 | ## Go development 16 | 17 | The default [devshell] is a modified version of the underlying [Nix derivation] for `nixos-facter`. As such, it provides 18 | all the necessary tooling and utilities for working on `nixos-facter`. 19 | 20 | ```nix title="nix/devshells/default.nix" 21 | --8<-- "nix/devshells/default.nix" 22 | ``` 23 | 24 | [Direnv] should load this by default when entering the root of the repository. 25 | 26 | For the most part, you _should_ be able to develop normally as you would any other Go program. 27 | 28 | !!! important 29 | 30 | When you have completed making any changes and have tested it as you would any other Go program, it is important 31 | to ensure it works when run as a Nix package. 32 | 33 | This can be done with `nix run .# -- `, which will build the Nix derivation and execute the resultant 34 | `nixos-facter` binary. 35 | 36 | ## Formatting 37 | 38 | We use [treefmt] and [treefmt-nix] to format the repository by running `nix fmt` from the root directory. 39 | 40 | ```nix title="nix/formatter.nix" 41 | --8<-- "nix/formatter.nix" 42 | ``` 43 | 44 | ## Checks 45 | 46 | Running `nix flake check` will build all the devshells and Nix packages, as well as check the formatting with [treefmt] 47 | and any other [Flake checks](https://github.com/NixOS/nix/blob/master/src/nix/flake-check.md) that have been configured. 48 | 49 | ## Documentation 50 | 51 | When making changes, it is **important** to add or update any relevant sections in the documentation within the same 52 | pull request. 53 | 54 | For more information see the [next section](./docs.md). 55 | 56 | [Nix]: https://nixos.org 57 | [Flake]: https://wiki.nixos.org/wiki/Flakes 58 | [Nix derivation]: https://nix.dev/manual/nix/2.18/language/derivations 59 | [Direnv]: https://direnv.net 60 | [devshell]: https://nix.dev/tutorials/first-steps/declarative-shell.html 61 | [treefmt]: https://treefmt.com 62 | [treefmt-nix]: https://github.com/numtide/treefmt-nix 63 | -------------------------------------------------------------------------------- /docs/content/contributing/docs.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | There is a separate [devshell] called `docs` which is provided for working with the docs locally. 4 | 5 | It can be entered by running: `nix develop .#docs` 6 | 7 | ```nix title="nix/devshells/docs.nix" 8 | --8<-- "nix/devshells/docs.nix" 9 | ``` 10 | 11 | The docs are based on [MkDocs] and the [MkDocs Material] theme. 12 | You will find its configuration and content in the following locations: 13 | 14 | - `mkdocs.yaml` 15 | - `./docs` 16 | 17 | ## Serve locally 18 | 19 | To serve the docs locally run `mkdocs serve` from the root of the repository: 20 | 21 | ```console 22 | ❯ mkdocs serve 23 | INFO - Building documentation... 24 | INFO - Cleaning site directory 25 | WARNING - The following pages exist in the docs directory, but are not included in the "nav" configuration: 26 | - index.md 27 | INFO - Documentation built in 0.26 seconds 28 | INFO - [16:22:36] Watching paths for changes: 'docs/content', 'mkdocs.yml' 29 | INFO - [16:22:36] Serving on http://127.0.0.1:8000/nixos-facter/ 30 | ``` 31 | 32 | ## Versioning & Publication 33 | 34 | Versioning of the docs is managed through [mike]. 35 | 36 | It is responsible for managing the structure of the `gh-pages` branch in the repository, which [Github Pages] is 37 | configured to serve from. 38 | 39 | !!! note 40 | 41 | More information about versioning with [MkDocs Material] and [mike] can be found [here](https://squidfunk.github.io/mkdocs-material/setup/setting-up-versioning/). 42 | 43 | There is a github workflow, `.github/workflows/gh-pages.yml` which is responsible for publishing the docs. 44 | It does the following: 45 | 46 | - On merge to `main`, the docs version [main](https://numtide.github.io/nixos-facter/main/) is updated. 47 | - When a new tag is created of the form `v...` a docs version `v.` is created and the 48 | [latest](https://numtide.github.io/nixos-facter/latest) alias is updated to point to this. 49 | 50 | The idea is that users will land on the latest released version of the docs by default, with `main` being available if 51 | they wish to read about unreleased features and changes. 52 | 53 | To preview the versions locally you can use `mike serve` instead of `mkdocs serve`. 54 | 55 | !!! warning 56 | 57 | Be sure to have fetched the latest changes for the `gh-pages` branch first. 58 | This is especially important if you are using `mike` locally to make manual changes to the published site. 59 | 60 | [Nix]: https://nixos.org 61 | [Flake]: https://wiki.nixos.org/wiki/Flakes 62 | [Nix derivation]: https://nix.dev/manual/nix/2.18/language/derivations 63 | [Direnv]: https://direnv.net 64 | [devshell]: https://nix.dev/tutorials/first-steps/declarative-shell.html 65 | [MkDocs]: https://www.mkdocs.org/ 66 | [MkDocs Material]: https://squidfunk.github.io/mkdocs-material/ 67 | [Github Pages]: https://pages.github.com/ 68 | [mike]: https://github.com/jimporter/mike 69 | -------------------------------------------------------------------------------- /docs/content/getting-started/generate-report.md: -------------------------------------------------------------------------------- 1 | # Generate a report 2 | 3 | To generate a report, you will need to have [Nix] installed on the target machine. 4 | 5 | === "Nixpkgs" 6 | 7 | ```shell 8 | sudo nix run nixpkgs#nixos-facter -- -o facter.json 9 | ``` 10 | 11 | === "Flake" 12 | 13 | ```shell 14 | sudo nix run \ 15 | --option experimental-features "nix-command flakes" \ 16 | --option extra-substituters https://numtide.cachix.org \ 17 | --option extra-trusted-public-keys numtide.cachix.org-1:2ps1kLBUWjxIneOy1Ik6cQjb41X0iXVXeHigGmycPPE= \ 18 | github:numtide/nixos-facter -- -o facter.json 19 | ``` 20 | 21 | This will scan your system and produce a JSON-based report in a file named `facter.json`: 22 | 23 | ```json title="facter.json" 24 | { 25 | "version": 2, // (1)! 26 | "system": "x86_64-linux", // (2)! 27 | "virtualisation": "none", // (3)! 28 | "hardware": { // (4)! 29 | "bios": { ... }, 30 | "bluetooth": [ ... ], 31 | "bridge": [ ... ], 32 | "chip_card": [ ... ] , 33 | "cpu": [ ... ], 34 | "disk": [ ... ], 35 | "graphics_card": [ ... ], 36 | "hub": [ ... ], 37 | "keyboard": [ ... ], 38 | "memory": [ ... ], 39 | "monitor": [ ... ], 40 | "mouse": [ ... ], 41 | "network_controller": [ ... ], 42 | "network_interface": [ ... ], 43 | "sound": [ ... ], 44 | "storage_controller": [ ... ], 45 | "system": [ ... ], 46 | "unknown": [ ... ], 47 | "usb_controller": [ ... ] 48 | }, 49 | "smbios": { // (5)! 50 | "bios": { ... }, 51 | "board": { ... }, 52 | "cache": [ ... ], 53 | "chassis": { ... }, 54 | "config": { ... }, 55 | "language": { ... }, 56 | "memory_array": [ ... ], 57 | "memory_array_mapped_address": [ ... ], 58 | "memory_device": [ ... ], 59 | "memory_device_mapped_address": [ ... ], 60 | "memory_error": [ ... ], 61 | "onboard": [ ... ], 62 | "port_connector": [ ... ], 63 | "processor": [ ... ], 64 | "slot": [ ... ], 65 | "system": { ... } 66 | } 67 | } 68 | ``` 69 | 70 | 1. Used to track major breaking changes in the report format. 71 | 2. Architecture of the target machine. 72 | 3. Indicates whether the report was generated inside a virtualised environment, and if so, what type. 73 | 4. All the various bits of hardware that could be detected. 74 | 5. [System Management BIOS] information if available. 75 | 76 | !!! tip 77 | 78 | To use this report in your NixOS configuration, have a look at [NixOS Facter Modules]. 79 | 80 | [Nix]: https://nixos.org 81 | [Numtide]: https://numtide.com 82 | [Numtide Binary Cache]: https://numtide.cachix.org 83 | [nixos-facter]: https://github.com/numtide/nixos-facter 84 | [nixpkgs]: https://github.com/nixos/nixpkgs 85 | [System Management BIOS]: https://wiki.osdev.org/System_Management_BIOS 86 | [NixOS Facter Modules]: https://github.com/numtide/nixos-facter-modules 87 | -------------------------------------------------------------------------------- /docs/content/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | template: home.html 3 | search: 4 | exclude: true 5 | --- 6 | 7 | # Home 8 | -------------------------------------------------------------------------------- /docs/content/reference/.gitignore: -------------------------------------------------------------------------------- 1 | go_doc -------------------------------------------------------------------------------- /docs/content/stylesheets/extra.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nix-community/nixos-facter/1b97f5d94bf3346bf743e188b1b5b14d049baded/docs/content/stylesheets/extra.css -------------------------------------------------------------------------------- /docs/theme/home.html: -------------------------------------------------------------------------------- 1 | {% extends "main.html" %} {% block header %} 2 |
3 | 50 |
51 | {% endblock %} {% block hero %} 52 | 53 | 90 |
91 |
92 |
93 |
94 |

NixOS Facter

95 |

{{ config.site_description }}

96 | 101 | Get started 102 | 103 | 108 | Contribute 109 | 110 |
111 | 112 |
113 | 114 |
115 |
116 |
117 |
118 | {% endblock %} {% block tabs %}{% endblock %} {% block site_nav %}{% endblock %} 119 | {% block content %}{% endblock %} {% block footer %}{% endblock %} 120 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "NixOS Facter"; 3 | 4 | # Add all your dependencies here 5 | inputs = { 6 | blueprint = { 7 | url = "github:numtide/blueprint"; 8 | inputs.nixpkgs.follows = "nixpkgs"; 9 | inputs.systems.follows = "systems"; 10 | }; 11 | systems.url = "github:nix-systems/default"; 12 | nixpkgs.url = "git+https://github.com/NixOS/nixpkgs?shallow=1&ref=nixpkgs-unstable"; 13 | flake-utils.url = "github:numtide/flake-utils"; 14 | flake-utils.inputs.systems.follows = "systems"; 15 | treefmt-nix = { 16 | url = "github:numtide/treefmt-nix"; 17 | inputs.nixpkgs.follows = "nixpkgs"; 18 | }; 19 | disko.url = "github:nix-community/disko"; 20 | disko.inputs.nixpkgs.follows = "nixpkgs"; 21 | 22 | hwinfo.url = "github:numtide/hwinfo"; 23 | hwinfo.inputs.nixpkgs.follows = "nixpkgs"; 24 | hwinfo.inputs.systems.follows = "systems"; 25 | hwinfo.inputs.blueprint.follows = "blueprint"; 26 | 27 | godoc.url = "github:numtide/godoc"; 28 | godoc.inputs.nixpkgs.follows = "nixpkgs"; 29 | godoc.inputs.systems.follows = "systems"; 30 | godoc.inputs.blueprint.follows = "blueprint"; 31 | godoc.inputs.treefmt-nix.follows = "treefmt-nix"; 32 | godoc.inputs.flake-utils.follows = "flake-utils"; 33 | }; 34 | 35 | # Keep the magic invocations to minimum. 36 | outputs = 37 | inputs: 38 | inputs.blueprint { 39 | prefix = "nix/"; 40 | inherit inputs; 41 | systems = [ 42 | "aarch64-linux" 43 | "riscv64-linux" 44 | "x86_64-linux" 45 | ]; 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/numtide/nixos-facter 2 | 3 | go 1.24.1 4 | 5 | require github.com/stretchr/testify v1.10.0 6 | 7 | require ( 8 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 9 | github.com/kr/pretty v0.3.1 // indirect 10 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 11 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect 12 | gopkg.in/yaml.v3 v3.0.1 // indirect 13 | ) 14 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 2 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 3 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 5 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 6 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 7 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 8 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 9 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 10 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 11 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 12 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 13 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 14 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 15 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 16 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 17 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 18 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 19 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 20 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/numtide/nixos-facter/cmd" 5 | ) 6 | 7 | func main() { 8 | cmd.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | # Project info 2 | site_name: NixOS Facter 3 | site_url: https://numtide.github.io/nixos-facter 4 | site_description: >- 5 | Declarative hardware configuration for NixOS 6 | 7 | # Repository 8 | repo_name: numtide/nixos-facter 9 | repo_url: https://github.com/numtide/nixos-facter 10 | 11 | # Copyright 12 | copyright: >- 13 | Content on this site is licensed under a Creative Commons 14 | Attribution-ShareAlike 4.0 International license. 15 | 16 | validation: 17 | omitted_files: warn 18 | absolute_links: warn 19 | unrecognized_links: warn 20 | 21 | # Configuration 22 | 23 | docs_dir: docs/content 24 | 25 | nav: 26 | 27 | theme: 28 | name: material 29 | custom_dir: docs/theme 30 | 31 | logo: assets/images/logo.png 32 | favicon: assets/images/logo.png 33 | 34 | features: 35 | - content.code.annotate 36 | - content.code.copy 37 | - navigation.footer 38 | - navigation.indexes 39 | - navigation.path 40 | - navigation.sections 41 | - navigation.tabs 42 | - navigation.tracking 43 | - search.highlight 44 | - search.share 45 | - search.suggest 46 | 47 | font: 48 | text: Inter 49 | mono: Noto Sans Mono 50 | palette: 51 | # Palette toggle for automatic mode 52 | - media: "(prefers-color-scheme)" 53 | toggle: 54 | icon: material/brightness-auto 55 | name: Switch to light mode 56 | 57 | # Palette toggle for light mode 58 | - media: "(prefers-color-scheme: light)" 59 | scheme: default 60 | toggle: 61 | icon: material/brightness-7 62 | name: Switch to dark mode 63 | 64 | # Palette toggle for dark mode 65 | - media: "(prefers-color-scheme: dark)" 66 | scheme: slate 67 | toggle: 68 | icon: material/brightness-4 69 | name: Switch to system preference 70 | 71 | extra: 72 | version: 73 | provider: mike 74 | social: 75 | - icon: fontawesome/brands/github 76 | link: https://github.com/numtide 77 | - icon: fontawesome/brands/x-twitter 78 | link: https://x.com/numtide 79 | - icon: fontawesome/brands/mastodon 80 | link: https://fosstodon.org/@numtide 81 | 82 | extra_css: 83 | - stylesheets/extra.css 84 | 85 | markdown_extensions: 86 | - tables 87 | - admonition 88 | - attr_list 89 | - footnotes 90 | - md_in_html 91 | - def_list 92 | - meta 93 | - pymdownx.emoji: 94 | emoji_index: !!python/name:material.extensions.emoji.twemoji 95 | emoji_generator: !!python/name:material.extensions.emoji.to_svg 96 | - pymdownx.tasklist: 97 | custom_checkbox: true 98 | - pymdownx.superfences 99 | - pymdownx.tabbed: 100 | alternate_style: true 101 | - pymdownx.details 102 | - pymdownx.highlight: 103 | use_pygments: true 104 | linenums: true 105 | anchor_linenums: true 106 | - pymdownx.inlinehilite 107 | - pymdownx.snippets 108 | - pymdownx.keys 109 | 110 | plugins: 111 | - search 112 | - mike 113 | -------------------------------------------------------------------------------- /nix/devshells/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | perSystem, 3 | pkgs, 4 | ... 5 | }: 6 | perSystem.self.nixos-facter.overrideAttrs (old: { 7 | GOROOT = "${old.passthru.go}/share/go"; 8 | nativeBuildInputs = old.nativeBuildInputs ++ [ 9 | pkgs.enumer 10 | pkgs.delve 11 | pkgs.pprof 12 | pkgs.gotools 13 | pkgs.golangci-lint 14 | pkgs.cobra-cli 15 | pkgs.fx # json tui 16 | perSystem.hwinfo.default 17 | ]; 18 | shellHook = '' 19 | # this is only needed for hermetic builds 20 | unset GO_NO_VENDOR_CHECKS GOSUMDB GOPROXY GOFLAGS 21 | ''; 22 | }) 23 | -------------------------------------------------------------------------------- /nix/devshells/docs.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | perSystem, 4 | ... 5 | }: 6 | pkgs.mkShellNoCC { 7 | packages = 8 | with pkgs; 9 | # Pop an empty shell on systems that aren't supported by godoc 10 | lib.optionals (perSystem.godoc ? default) ( 11 | [ 12 | perSystem.godoc.default 13 | (pkgs.writeScriptBin "gen-reference" '' 14 | out="./docs/content/reference/go_doc" 15 | godoc -c -o $out . 16 | '') 17 | (pkgs.writeScriptBin "mkdocs" '' 18 | # generate reference docs first 19 | gen-reference 20 | # execute the underlying command 21 | ${pkgs.mkdocs}/bin/mkdocs "$@" 22 | '') 23 | ] 24 | ++ (with pkgs.python3Packages; [ 25 | mike 26 | mkdocs-material 27 | ]) 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /nix/formatter.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | flake, 4 | inputs, 5 | ... 6 | }: 7 | let 8 | mod = inputs.treefmt-nix.lib.evalModule pkgs { 9 | projectRootFile = ".git/config"; 10 | 11 | programs = 12 | { 13 | nixfmt.enable = true; 14 | deadnix.enable = true; 15 | gofumpt.enable = true; 16 | prettier.enable = true; 17 | statix.enable = true; 18 | } 19 | // pkgs.lib.optionalAttrs (pkgs.system != "riscv64-linux") { 20 | shellcheck.enable = true; 21 | }; 22 | 23 | settings = { 24 | global.excludes = [ 25 | "LICENSE" 26 | # unsupported extensions 27 | "*.{gif,png,svg,tape,mts,lock,mod,sum,toml,env,envrc,gitignore}" 28 | ]; 29 | 30 | formatter = { 31 | deadnix = { 32 | priority = 1; 33 | }; 34 | 35 | statix = { 36 | priority = 2; 37 | }; 38 | 39 | nixfmt = { 40 | priority = 3; 41 | }; 42 | 43 | prettier = { 44 | options = [ 45 | "--tab-width" 46 | "4" 47 | ]; 48 | includes = [ "*.{css,html,js,json,jsx,md,mdx,scss,ts,yaml}" ]; 49 | }; 50 | }; 51 | }; 52 | }; 53 | 54 | wrapper = mod.config.build.wrapper // { 55 | passthru.tests.check = mod.config.build.check flake; 56 | }; 57 | 58 | unsupported = pkgs.writeShellApplication { 59 | name = "unsupported-platform"; 60 | text = '' 61 | echo "nix fmt is not supported on ${pkgs.hostPlatform.system}"; 62 | ''; 63 | }; 64 | in 65 | # nixfmt-rfc-style is based on Haskell, which is broke on RiscV currently 66 | if pkgs.hostPlatform.isRiscV then unsupported else wrapper 67 | -------------------------------------------------------------------------------- /nix/package.nix: -------------------------------------------------------------------------------- 1 | { perSystem, ... }: perSystem.self.nixos-facter 2 | -------------------------------------------------------------------------------- /nix/packages/nixos-facter/default.nix: -------------------------------------------------------------------------------- 1 | args@{ 2 | # We need the following pragma to ensure deadnix doesn't remove inputs. 3 | # This package is being called with newScope/callPackage, which means it is only being passed args it defines. 4 | # We do not use inputs directly in this file, but need it for passing to the tests. 5 | flake, 6 | # deadnix: skip 7 | inputs, 8 | # deadnix: skip 9 | system, 10 | perSystem, 11 | pkgs, 12 | ... 13 | }: 14 | let 15 | inherit (pkgs) lib; 16 | in 17 | pkgs.callPackage ./package.nix { 18 | hwinfo = perSystem.hwinfo.default; 19 | 20 | # there's no good way of tying in the version to a git tag or branch 21 | # so for simplicity's sake we set the version as the commit revision hash 22 | # we remove the `-dirty` suffix to avoid a lot of unnecessary rebuilds in local dev 23 | versionSuffix = "-${lib.removeSuffix "-dirty" (flake.shortRev or flake.dirtyShortRev)}"; 24 | } 25 | // { 26 | passthru.tests = import ./tests args; 27 | } 28 | -------------------------------------------------------------------------------- /nix/packages/nixos-facter/package.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | versionSuffix ? null, 4 | systemdMinimal, 5 | hwinfo, 6 | gcc, 7 | makeWrapper, 8 | pkg-config, 9 | stdenv, 10 | buildGo124Module, 11 | versionCheckHook, 12 | }: 13 | let 14 | fs = lib.fileset; 15 | in 16 | buildGo124Module (final: { 17 | pname = "nixos-facter"; 18 | version = "0.3.2"; 19 | 20 | src = fs.toSource { 21 | root = ../../..; 22 | fileset = fs.unions [ 23 | ../../../cmd 24 | ../../../go.mod 25 | ../../../go.sum 26 | ../../../main.go 27 | ../../../pkg 28 | ]; 29 | }; 30 | 31 | vendorHash = "sha256-A7ZuY8Gc/a0Y8O6UG2WHWxptHstJOxi4n9F8TY6zqiw="; 32 | 33 | buildInputs = [ 34 | systemdMinimal 35 | hwinfo 36 | ]; 37 | 38 | nativeBuildInputs = [ 39 | gcc 40 | makeWrapper 41 | pkg-config 42 | versionCheckHook 43 | ]; 44 | 45 | ldflags = [ 46 | "-s" 47 | "-w" 48 | "-X github.com/numtide/nixos-facter/pkg/build.Name=${final.pname}" 49 | "-X github.com/numtide/nixos-facter/pkg/build.Version=v${final.version}${toString versionSuffix}" 50 | "-X github.com/numtide/nixos-facter/pkg/build.System=${stdenv.hostPlatform.system}" 51 | ]; 52 | 53 | doInstallCheck = true; 54 | postInstall = 55 | let 56 | binPath = lib.makeBinPath [ 57 | systemdMinimal 58 | ]; 59 | in 60 | '' 61 | wrapProgram "$out/bin/nixos-facter" \ 62 | --prefix PATH : "${binPath}" 63 | ''; 64 | 65 | meta = with lib; { 66 | description = "nixos-facter: declarative nixos-generate-config"; 67 | homepage = "https://github.com/numtide/nixos-facter"; 68 | license = licenses.mit; 69 | mainProgram = "nixos-facter"; 70 | }; 71 | }) 72 | -------------------------------------------------------------------------------- /nix/packages/nixos-facter/tests/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | inputs, 4 | system, 5 | perSystem, 6 | ... 7 | }: 8 | let 9 | # we have to import diskoLib like this because there are some impure default imports e.g. 10 | diskoLib = import "${inputs.disko}/lib" { 11 | inherit (pkgs) lib; 12 | makeTest = import "${inputs.nixpkgs}/nixos/tests/make-test-python.nix"; 13 | eval-config = import "${inputs.nixpkgs}/nixos/lib/eval-config.nix"; 14 | }; 15 | in 16 | # for now we only run the tests in x86_64-linux since we don't have access to a bare-metal ARM box or a VM that supports nested 17 | # virtualisation which makes the test take forever and ultimately fail 18 | pkgs.lib.optionalAttrs pkgs.stdenv.isx86_64 { 19 | golangci-lint = perSystem.self.nixos-facter.overrideAttrs (old: { 20 | nativeBuildInputs = old.nativeBuildInputs ++ [ pkgs.golangci-lint ]; 21 | buildPhase = '' 22 | HOME=$TMPDIR 23 | golangci-lint run 24 | ''; 25 | installPhase = '' 26 | touch $out 27 | ''; 28 | doInstallCheck = false; 29 | doCheck = false; 30 | }); 31 | 32 | basic = diskoLib.testLib.makeDiskoTest { 33 | inherit pkgs; 34 | name = "basic"; 35 | disko-config = ./disko.nix; 36 | extraSystemConfig = { 37 | environment.systemPackages = [ 38 | perSystem.self.nixos-facter 39 | ]; 40 | }; 41 | 42 | extraTestScript = '' 43 | import json 44 | 45 | report = json.loads(machine.succeed("nixos-facter --ephemeral 2>&1")) 46 | 47 | with subtest("Capture system"): 48 | assert report['system'] == '${system}' 49 | 50 | with subtest("Capture virtualisation"): 51 | virt = report['virtualisation'] 52 | # kvm for systems that support it, otherwise the vm test should present itself as qemu 53 | # todo double-check this is the same for intel 54 | assert virt in ("kvm", "qemu"), f"expected virtualisation to be either kvm or qemu, got {virt}" 55 | 56 | with subtest("Capture swap entries"): 57 | assert 'swap' in report, "'swap' not found in the report" 58 | 59 | swap = report['swap'] 60 | 61 | expected = [ 62 | { 'type': 'partition', 'size': 1048572, 'used': 0, 'priority': -2 }, 63 | { 'type': 'partition', 'size': 10236, 'used': 0, 'priority': 100 } 64 | ] 65 | 66 | assert len(swap) == len(expected), f"expected {len(expected)} swap entries, found {len(swap)}" 67 | 68 | for i in range(2): 69 | assert swap[i]['path'].startswith("/dev/disk/by-uuid/"), f"expected a stable device path: {swap[i]['path']}" 70 | 71 | # delete for easier comparison 72 | del swap[i]['path'] 73 | 74 | assert swap[i] == expected[i], "swap[{i}] mismatch" 75 | ''; 76 | }; 77 | } 78 | -------------------------------------------------------------------------------- /nix/packages/nixos-facter/tests/disko.nix: -------------------------------------------------------------------------------- 1 | { 2 | disko = { 3 | devices = { 4 | disk = { 5 | main = { 6 | device = "/dev/vdb"; 7 | type = "disk"; 8 | content = { 9 | type = "gpt"; 10 | partitions = { 11 | ESP = { 12 | size = "500M"; 13 | type = "EF00"; 14 | content = { 15 | type = "filesystem"; 16 | format = "vfat"; 17 | mountpoint = "/boot"; 18 | }; 19 | }; 20 | root = { 21 | end = "-1G"; 22 | content = { 23 | type = "filesystem"; 24 | format = "ext4"; 25 | mountpoint = "/"; 26 | }; 27 | }; 28 | encryptedSwap = { 29 | size = "10M"; 30 | content = { 31 | type = "swap"; 32 | randomEncryption = true; 33 | priority = 100; # prefer to encrypt as long as we have space for it 34 | }; 35 | }; 36 | plainSwap = { 37 | size = "100%"; 38 | content = { 39 | type = "swap"; 40 | discardPolicy = "both"; 41 | resumeDevice = true; # resume from hiberation from this device 42 | }; 43 | }; 44 | }; 45 | }; 46 | }; 47 | }; 48 | }; 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /pkg/build/build.go: -------------------------------------------------------------------------------- 1 | // Package build contains constants and values set at build time via -X flags. 2 | package build 3 | 4 | var ( 5 | // Name is the program name, typically set via Nix to match the derivation's `pname`. 6 | Name = "nixos-facter" 7 | // Version is the program version, typically set via Nix to match the derivation's `version`. 8 | Version = "v0.0.0+dev" 9 | // System is the architecture that this program was built for e.g. x86_64-linux. 10 | // It is set via Nix to match the Nixpkgs system. 11 | System = "" 12 | // ReportVersion is used to indicate significant changes in the report output and is embedded JSON report produced. 13 | ReportVersion uint16 = 1 14 | ) 15 | -------------------------------------------------------------------------------- /pkg/ephem/ephem.go: -------------------------------------------------------------------------------- 1 | // Package ephem contains utilities for capturing ephemeral aspects of a target machine. 2 | // 3 | // Currently, it only supports capturing swap configurations. 4 | // Eventually it will capture more things, such as filesystems. 5 | package ephem 6 | 7 | import ( 8 | "fmt" 9 | "log/slog" 10 | "os" 11 | "path/filepath" 12 | "strings" 13 | ) 14 | 15 | var deviceGlobs = []string{ 16 | "/dev/stratis/*/*", 17 | "/dev/disk/by-uuid/*", 18 | "/dev/mapper/*", 19 | "/dev/disk/by-label/*", 20 | } 21 | 22 | // StableDevicePath takes a device path and converts it into a more stable form. 23 | // For example, /dev/nvme* is assigned on startup by detection order which is not consistent. 24 | // A disk path of the form /dev/disk/by-uuid/* is not startup-dependent. 25 | func StableDevicePath(device string) (string, error) { 26 | l := slog.With("prefix", "stableDevicePath") 27 | 28 | if !strings.HasPrefix(device, "/") { 29 | return device, nil 30 | } 31 | 32 | stat, err := os.Stat(device) 33 | if err != nil { 34 | return "", fmt.Errorf("failed to stat device %s: %w", device, err) 35 | } 36 | 37 | for idx := range deviceGlobs { 38 | glob := deviceGlobs[idx] 39 | l.Debug("searching glob", "glob", glob) 40 | 41 | matches, err := filepath.Glob(glob) 42 | if err != nil { 43 | // the only possible error is ErrBadPattern 44 | return "", fmt.Errorf("failed to glob %s: %w", glob, err) 45 | } 46 | 47 | for _, match := range matches { 48 | matchStat, err := os.Stat(match) 49 | if err != nil { 50 | l.Debug("failed to stat match", "match", match, "error", err) 51 | 52 | continue 53 | } 54 | 55 | if os.SameFile(stat, matchStat) { 56 | l.Debug("match found for device", "match", match, "device", device) 57 | 58 | return match, nil 59 | } 60 | } 61 | } 62 | 63 | l.Debug("no match found for device", "device", device) 64 | // if no match was found, we return the original device path 65 | return device, nil 66 | } 67 | -------------------------------------------------------------------------------- /pkg/ephem/swap.go: -------------------------------------------------------------------------------- 1 | package ephem 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "os" 9 | "regexp" 10 | "strconv" 11 | ) 12 | 13 | var ( 14 | swapHeaderRegex = regexp.MustCompile(`^Filename\s+Type\s+Size\s+Used\s+Priority$`) 15 | swapEntryRegex = regexp.MustCompile(`^(.*?)\s+(partition|file)\s+(.*?)\s+(.*?)\s+(.*?)$`) 16 | ) 17 | 18 | // SwapType represents the type of swap space, either file or partition. 19 | // 20 | //go:generate enumer -type=SwapType -json -transform=snake -trimprefix SwapType -output=./swap_enum_type.go 21 | type SwapType uint //nolint:recvcheck 22 | 23 | const ( 24 | SwapTypeFile SwapType = iota 25 | SwapTypePartition 26 | ) 27 | 28 | // SwapEntry represents a swap entry. 29 | type SwapEntry struct { 30 | // Type is the type of swap e.g. partition or file. 31 | Type SwapType `json:"type"` 32 | // Filename is the path to the swap device or file. 33 | Filename string `json:"path"` 34 | // Size is the total size of the swap in kilobytes. 35 | Size uint64 `json:"size"` 36 | // Used is the amount of swap space currently in use, in kilobytes. 37 | Used uint64 `json:"used"` 38 | // Priority determines the order in which swap spaces are used. 39 | // Higher numbers have higher priority. 40 | Priority int32 `json:"priority"` 41 | } 42 | 43 | // SwapEntries retrieves the list of swap entries from the system and resolves stable device paths for each entry. 44 | func SwapEntries() ([]*SwapEntry, error) { 45 | f, err := os.Open("/proc/swaps") 46 | if err != nil { 47 | return nil, fmt.Errorf("failed to open /proc/swaps: %w", err) 48 | } 49 | 50 | devices, err := ReadSwapFile(f) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | for idx := range devices { 56 | // try to resolve stable device paths for each swap device 57 | stablePath, err := StableDevicePath(devices[idx].Filename) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | devices[idx].Filename = stablePath 63 | } 64 | 65 | return devices, nil 66 | } 67 | 68 | // ReadSwapFile reads swap entries from an io.Reader, validating the format and parsing each entry. 69 | func ReadSwapFile(reader io.Reader) ([]*SwapEntry, error) { 70 | scanner := bufio.NewScanner(reader) 71 | if !scanner.Scan() { 72 | return nil, errors.New("swaps file is empty") 73 | } else if b := scanner.Bytes(); !swapHeaderRegex.Match(b) { 74 | return nil, fmt.Errorf("header in swaps file is malformed: '%s'", string(b)) 75 | } 76 | 77 | var result []*SwapEntry 78 | 79 | for scanner.Scan() { 80 | line := scanner.Text() 81 | 82 | matches := swapEntryRegex.FindAllStringSubmatch(line, 1) 83 | if len(matches) != 1 { 84 | return nil, fmt.Errorf("malformed entry in swaps file: '%s'", line) 85 | } 86 | 87 | fields := matches[0] 88 | 89 | swapType, err := SwapTypeString(fields[2]) 90 | if err != nil { 91 | return nil, fmt.Errorf("malformed swap type: '%s'", fields[2]) 92 | } 93 | 94 | size, err := strconv.ParseUint(fields[3], 10, 64) 95 | if err != nil { 96 | return nil, fmt.Errorf("malformed size value: '%s'", fields[3]) 97 | } 98 | 99 | used, err := strconv.ParseUint(fields[4], 10, 64) 100 | if err != nil { 101 | return nil, fmt.Errorf("malformed used value: '%s'", fields[4]) 102 | } 103 | 104 | priority, err := strconv.ParseInt(fields[5], 10, 32) 105 | if err != nil { 106 | return nil, fmt.Errorf("malformed priority value: '%s'", fields[5]) 107 | } 108 | 109 | result = append(result, &SwapEntry{ 110 | Filename: fields[1], 111 | Type: swapType, 112 | Size: size, 113 | Used: used, 114 | Priority: int32(priority), 115 | }) 116 | } 117 | 118 | return result, nil 119 | } 120 | -------------------------------------------------------------------------------- /pkg/ephem/swap_enum_type.go: -------------------------------------------------------------------------------- 1 | // Code generated by "enumer -type=SwapType -json -transform=snake -trimprefix SwapType -output=./swap_enum_type.go"; DO NOT EDIT. 2 | 3 | package ephem 4 | 5 | import ( 6 | "encoding/json" 7 | "fmt" 8 | "strings" 9 | ) 10 | 11 | const _SwapTypeName = "filepartition" 12 | 13 | var _SwapTypeIndex = [...]uint8{0, 4, 13} 14 | 15 | const _SwapTypeLowerName = "filepartition" 16 | 17 | func (i SwapType) String() string { 18 | if i >= SwapType(len(_SwapTypeIndex)-1) { 19 | return fmt.Sprintf("SwapType(%d)", i) 20 | } 21 | return _SwapTypeName[_SwapTypeIndex[i]:_SwapTypeIndex[i+1]] 22 | } 23 | 24 | // An "invalid array index" compiler error signifies that the constant values have changed. 25 | // Re-run the stringer command to generate them again. 26 | func _SwapTypeNoOp() { 27 | var x [1]struct{} 28 | _ = x[SwapTypeFile-(0)] 29 | _ = x[SwapTypePartition-(1)] 30 | } 31 | 32 | var _SwapTypeValues = []SwapType{SwapTypeFile, SwapTypePartition} 33 | 34 | var _SwapTypeNameToValueMap = map[string]SwapType{ 35 | _SwapTypeName[0:4]: SwapTypeFile, 36 | _SwapTypeLowerName[0:4]: SwapTypeFile, 37 | _SwapTypeName[4:13]: SwapTypePartition, 38 | _SwapTypeLowerName[4:13]: SwapTypePartition, 39 | } 40 | 41 | var _SwapTypeNames = []string{ 42 | _SwapTypeName[0:4], 43 | _SwapTypeName[4:13], 44 | } 45 | 46 | // SwapTypeString retrieves an enum value from the enum constants string name. 47 | // Throws an error if the param is not part of the enum. 48 | func SwapTypeString(s string) (SwapType, error) { 49 | if val, ok := _SwapTypeNameToValueMap[s]; ok { 50 | return val, nil 51 | } 52 | 53 | if val, ok := _SwapTypeNameToValueMap[strings.ToLower(s)]; ok { 54 | return val, nil 55 | } 56 | return 0, fmt.Errorf("%s does not belong to SwapType values", s) 57 | } 58 | 59 | // SwapTypeValues returns all values of the enum 60 | func SwapTypeValues() []SwapType { 61 | return _SwapTypeValues 62 | } 63 | 64 | // SwapTypeStrings returns a slice of all String values of the enum 65 | func SwapTypeStrings() []string { 66 | strs := make([]string, len(_SwapTypeNames)) 67 | copy(strs, _SwapTypeNames) 68 | return strs 69 | } 70 | 71 | // IsASwapType returns "true" if the value is listed in the enum definition. "false" otherwise 72 | func (i SwapType) IsASwapType() bool { 73 | for _, v := range _SwapTypeValues { 74 | if i == v { 75 | return true 76 | } 77 | } 78 | return false 79 | } 80 | 81 | // MarshalJSON implements the json.Marshaler interface for SwapType 82 | func (i SwapType) MarshalJSON() ([]byte, error) { 83 | return json.Marshal(i.String()) 84 | } 85 | 86 | // UnmarshalJSON implements the json.Unmarshaler interface for SwapType 87 | func (i *SwapType) UnmarshalJSON(data []byte) error { 88 | var s string 89 | if err := json.Unmarshal(data, &s); err != nil { 90 | return fmt.Errorf("SwapType should be a string, got %s", data) 91 | } 92 | 93 | var err error 94 | *i, err = SwapTypeString(s) 95 | return err 96 | } 97 | -------------------------------------------------------------------------------- /pkg/ephem/swap_test.go: -------------------------------------------------------------------------------- 1 | package ephem_test 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/numtide/nixos-facter/pkg/ephem" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | var ( 12 | empty = "Filename Type Size Used Priority\n" 13 | corrupt = `Filename Type Size Used Priority 14 | foo bar baz 15 | ` 16 | badPartition = `Filename Type Size Used Priority 17 | /var/lib/swap-1 foo 1048576 123 -3 18 | ` 19 | 20 | sample = `Filename Type Size Used Priority 21 | /dev/sda6 partition 4194300 0 -1 22 | /var/lib/swap-1 file 1048576 123 -3 23 | /var/lib/swap-2 file 2097152 4567 -2` 24 | ) 25 | 26 | func TestReadSwapFile(t *testing.T) { 27 | as := require.New(t) 28 | 29 | _, err := ephem.ReadSwapFile(strings.NewReader("")) 30 | as.Error(err, "swaps file is empty") 31 | 32 | _, err = ephem.ReadSwapFile(strings.NewReader("foo bar baz hello world\n")) 33 | as.Errorf(err, "header in swaps file is malformed: '%s'", "foo bar baz hello world\n") 34 | 35 | swaps, err := ephem.ReadSwapFile(strings.NewReader(empty)) 36 | as.NoError(err) 37 | as.Empty(swaps) 38 | 39 | _, err = ephem.ReadSwapFile(strings.NewReader(corrupt)) 40 | as.Errorf(err, "malformed entry in swaps file: '%s'", "foo bar baz") 41 | 42 | _, err = ephem.ReadSwapFile(strings.NewReader(badPartition)) 43 | as.Errorf(err, "malformed entry in swaps file: '%s'", `/var/lib/swap-1 foo 1048576 123 -3`) 44 | 45 | swaps, err = ephem.ReadSwapFile(strings.NewReader(sample)) 46 | as.NoError(err) 47 | as.Len(swaps, 3) 48 | 49 | as.Equal("/dev/sda6", swaps[0].Filename) 50 | as.Equal(ephem.SwapTypePartition, swaps[0].Type) 51 | as.Equal(uint64(4194300), swaps[0].Size) 52 | as.Equal(uint64(0), swaps[0].Used) 53 | as.Equal(int32(-1), swaps[0].Priority) 54 | 55 | as.Equal("/var/lib/swap-1", swaps[1].Filename) 56 | as.Equal(ephem.SwapTypeFile, swaps[1].Type) 57 | as.Equal(uint64(1048576), swaps[1].Size) 58 | as.Equal(uint64(123), swaps[1].Used) 59 | as.Equal(int32(-3), swaps[1].Priority) 60 | 61 | as.Equal("/var/lib/swap-2", swaps[2].Filename) 62 | as.Equal(ephem.SwapTypeFile, swaps[2].Type) 63 | as.Equal(uint64(2097152), swaps[2].Size) 64 | as.Equal(uint64(4567), swaps[2].Used) 65 | as.Equal(int32(-2), swaps[2].Priority) 66 | } 67 | -------------------------------------------------------------------------------- /pkg/facter/facter.go: -------------------------------------------------------------------------------- 1 | // Package facter contains types and utilities for scanning a system and generating a report, detailing key aspects of 2 | // the system and its connected hardware. 3 | package facter 4 | 5 | import ( 6 | "errors" 7 | "fmt" 8 | "log/slog" 9 | 10 | "github.com/numtide/nixos-facter/pkg/build" 11 | "github.com/numtide/nixos-facter/pkg/ephem" 12 | "github.com/numtide/nixos-facter/pkg/hwinfo" 13 | "github.com/numtide/nixos-facter/pkg/virt" 14 | ) 15 | 16 | // Report represents a detailed report on the system’s hardware, virtualisation, SMBios, and swap entries. 17 | type Report struct { 18 | // Version is a monotonically increasing number, 19 | // used to indicate breaking changes or new features in the report output. 20 | Version uint16 `json:"version"` 21 | 22 | // System indicates the system architecture e.g. x86_64-linux. 23 | System string `json:"system"` 24 | 25 | // Virtualisation indicates the type of virtualisation or container environment present on the system. 26 | Virtualisation virt.Type `json:"virtualisation"` 27 | 28 | // Hardware provides detailed information about the system’s hardware components, such as CPU, memory, and peripherals. 29 | Hardware Hardware `json:"hardware,omitempty"` 30 | 31 | // Smbios provides detailed information about the system's SMBios data, such as BIOS, board, chassis, memory, 32 | // and processors. 33 | Smbios Smbios `json:"smbios,omitempty"` 34 | 35 | // Swap contains a list of swap entries representing the system's swap devices or files and their respective details. 36 | Swap []*ephem.SwapEntry `json:"swap,omitempty"` 37 | } 38 | 39 | // Scanner defines a type responsible for scanning and reporting system hardware information. 40 | type Scanner struct { 41 | // Swap indicates whether the system swap information should be reported. 42 | Swap bool 43 | 44 | // Ephemeral indicates whether the scanner should report ephemeral details, 45 | // such as swap. 46 | Ephemeral bool 47 | 48 | // Features is a list of ProbeFeature types that should be scanned for. 49 | Features []hwinfo.ProbeFeature 50 | } 51 | 52 | // Scan scans the system's hardware and software information and returns a report. 53 | // It also detects IOMMU groups and handles errors gracefully if scanning fails. 54 | func (s *Scanner) Scan() (*Report, error) { 55 | var err error 56 | 57 | report := Report{ 58 | Version: build.ReportVersion, 59 | } 60 | 61 | if build.System == "" { 62 | return nil, errors.New("system is not set") 63 | } 64 | 65 | report.System = build.System 66 | 67 | slog.Debug("building report", "system", report.System, "version", report.Version) 68 | 69 | var ( 70 | smbios []hwinfo.Smbios 71 | devices []hwinfo.HardwareDevice 72 | ) 73 | 74 | slog.Debug("scanning hardware", "features", s.Features) 75 | 76 | smbios, devices, err = hwinfo.Scan(s.Features, s.Ephemeral) 77 | if err != nil { 78 | return nil, fmt.Errorf("failed to scan hardware: %w", err) 79 | } 80 | 81 | slog.Debug("reading IOMMU groups") 82 | 83 | // read iommu groups 84 | iommuGroups, err := hwinfo.IOMMUGroups() 85 | if err != nil { 86 | return nil, fmt.Errorf("failed to read iommu groups: %w", err) 87 | } 88 | 89 | slog.Debug("processing devices", "count", len(devices)) 90 | 91 | for idx := range devices { 92 | // lookup iommu group before adding to the report 93 | device := devices[idx] 94 | 95 | groupID, ok := iommuGroups[device.SysfsID] 96 | if ok { 97 | slog.Debug("IOMMU group found", "device", device.SysfsID, "groupID", groupID) 98 | device.SysfsIOMMUGroupID = &groupID 99 | } 100 | 101 | if err = report.Hardware.add(device); err != nil { 102 | return nil, fmt.Errorf("failed to add to hardware report: %w", err) 103 | } 104 | } 105 | 106 | slog.Debug("processing smbios entries", "count", len(smbios)) 107 | 108 | for idx := range smbios { 109 | if err = report.Smbios.add(smbios[idx]); err != nil { 110 | return nil, fmt.Errorf("failed to add to smbios report: %w", err) 111 | } 112 | } 113 | 114 | slog.Debug("detecting virtualisation") 115 | 116 | if report.Virtualisation, err = virt.Detect(); err != nil { 117 | return nil, fmt.Errorf("failed to detect virtualisation: %w", err) 118 | } 119 | 120 | if s.Ephemeral || s.Swap { 121 | slog.Debug("processing swap devices") 122 | 123 | if report.Swap, err = ephem.SwapEntries(); err != nil { 124 | return nil, fmt.Errorf("failed to detect swap devices: %w", err) 125 | } 126 | } 127 | 128 | slog.Debug("report complete") 129 | 130 | return &report, nil 131 | } 132 | -------------------------------------------------------------------------------- /pkg/hwinfo/detail.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | 7 | // CGO cannot access union type fields, so we do this as a workaround 8 | hd_detail_type_t hd_detail_get_type(hd_detail_t *det) { return det->type; } 9 | hd_detail_pci_t hd_detail_get_pci(hd_detail_t *det) { return det->pci; } 10 | hd_detail_usb_t hd_detail_get_usb(hd_detail_t *det) { return det->usb; } 11 | hd_detail_isapnp_t hd_detail_get_isapnp(hd_detail_t *det) { return det->isapnp; } 12 | hd_detail_cpu_t hd_detail_get_cpu(hd_detail_t *det) { return det->cpu; } 13 | hd_detail_monitor_t hd_detail_get_monitor(hd_detail_t *det) { return det->monitor; } 14 | hd_detail_bios_t hd_detail_get_bios(hd_detail_t *det) { return det->bios; } 15 | hd_detail_sys_t hd_detail_get_sys(hd_detail_t *det) { return det->sys; } 16 | 17 | */ 18 | import "C" 19 | 20 | import ( 21 | "encoding/hex" 22 | "errors" 23 | "fmt" 24 | "unsafe" 25 | ) 26 | 27 | //go:generate enumer -type=DetailType -json -transform=snake -trimprefix DetailType -output=./detail_enum_type.go 28 | type DetailType uint //nolint:recvcheck 29 | 30 | //nolint:revive,stylecheck 31 | const ( 32 | DetailTypePci DetailType = iota 33 | DetailTypeUsb 34 | DetailTypeIsaPnp 35 | DetailTypeCdrom 36 | 37 | DetailTypeFloppy 38 | DetailTypeBios 39 | DetailTypeCpu 40 | DetailTypeProm 41 | 42 | DetailTypeMonitor 43 | DetailTypeSys 44 | DetailTypeScsi 45 | DetailTypeDevtree 46 | 47 | DetailTypeCcw 48 | DetailTypeJoystick 49 | ) 50 | 51 | type Detail interface { 52 | DetailType() DetailType 53 | } 54 | 55 | //nolint:ireturn 56 | func NewDetail(detail *C.hd_detail_t) (Detail, error) { 57 | if detail == nil { 58 | return nil, errors.New("detail is nil") 59 | } 60 | 61 | var ( 62 | err error 63 | result Detail 64 | ) 65 | 66 | switch DetailType(C.hd_detail_get_type(detail)) { 67 | case DetailTypePci: 68 | result, err = NewDetailPci(C.hd_detail_get_pci(detail)) 69 | case DetailTypeUsb: 70 | result, err = NewDetailUsb(C.hd_detail_get_usb(detail)) 71 | case DetailTypeIsaPnp: 72 | result, err = NewDetailIsaPnpDevice(C.hd_detail_get_isapnp(detail)) 73 | case DetailTypeCpu: 74 | result, err = NewDetailCPU(C.hd_detail_get_cpu(detail)) 75 | case DetailTypeMonitor: 76 | result, err = NewDetailMonitor(C.hd_detail_get_monitor(detail)) 77 | case DetailTypeBios: 78 | result, err = NewDetailBios(C.hd_detail_get_bios(detail)) 79 | case DetailTypeSys: 80 | result, err = NewDetailSys(C.hd_detail_get_sys(detail)) 81 | case DetailTypeCdrom, DetailTypeFloppy, DetailTypeProm, DetailTypeScsi, DetailTypeDevtree, DetailTypeCcw, 82 | DetailTypeJoystick: 83 | // do nothing for now 84 | 85 | default: 86 | err = fmt.Errorf("unknown detail type %d", DetailType(C.hd_detail_get_type(detail))) 87 | } 88 | 89 | return result, err 90 | } 91 | 92 | type MemoryRange struct { 93 | Start string `json:"start"` 94 | Size string `json:"size"` 95 | Data string `json:"-"` // hex encoded 96 | } 97 | 98 | func NewMemoryRange(mem C.memory_range_t) MemoryRange { 99 | return MemoryRange{ 100 | Start: fmt.Sprintf("0x%x", uint(mem.start)), 101 | Size: fmt.Sprintf("0x%x", uint(mem.size)), 102 | Data: hex.EncodeToString(C.GoBytes(unsafe.Pointer(&mem.data), C.int(mem.size))), //nolint:gocritic 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /pkg/hwinfo/detail_bios.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | #include 7 | 8 | bool bios_info_is_apm_supported(bios_info_t *info) { return info->apm_supported; } 9 | bool bios_info_is_apm_enabled(bios_info_t *info) { return info->apm_enabled; } 10 | bool bios_info_is_pnp_bios(bios_info_t *info) { return info->is_pnp_bios; } 11 | bool bios_info_has_lba_support(bios_info_t *info) { return info->lba_support; } 12 | */ 13 | import "C" 14 | 15 | type ApmInfo struct { 16 | Supported bool `json:"supported"` 17 | Enabled bool `json:"enabled"` 18 | Version uint8 `json:"version"` 19 | SubVersion uint8 `json:"sub_version"` 20 | BiosFlags uint32 `json:"bios_flags"` 21 | } 22 | 23 | type VbeInfo struct { 24 | Version uint16 `json:"version"` 25 | VideoMemory uint32 `json:"video_memory"` 26 | } 27 | 28 | type DetailBios struct { 29 | Type DetailType `json:"-"` 30 | ApmInfo ApmInfo `json:"apm_info"` 31 | VbeInfo VbeInfo `json:"vbe_info"` 32 | 33 | // todo par and ser ports 34 | PnP bool `json:"pnp"` 35 | PnPId uint `json:"pnp_id"` // it is still in big endian format 36 | LbaSupport bool `json:"lba_support"` 37 | LowMemorySize uint32 `json:"low_memory_size"` 38 | // todo smp info 39 | // todo vbe info 40 | 41 | SmbiosVersion uint32 `json:"smbios_version"` 42 | 43 | // todo lcd 44 | // todo mouse 45 | // todo led 46 | // todo bios32 47 | } 48 | 49 | func (d DetailBios) DetailType() DetailType { 50 | return DetailTypeBios 51 | } 52 | 53 | func NewDetailBios(dev C.hd_detail_bios_t) (*DetailBios, error) { 54 | data := dev.data 55 | 56 | return &DetailBios{ 57 | Type: DetailTypeBios, 58 | ApmInfo: ApmInfo{ 59 | Supported: bool(C.bios_info_is_apm_supported(data)), 60 | Enabled: bool(C.bios_info_is_apm_enabled(data)), 61 | Version: uint8(data.apm_ver), 62 | SubVersion: uint8(data.apm_subver), 63 | BiosFlags: uint32(data.apm_bios_flags), 64 | }, 65 | VbeInfo: VbeInfo{ 66 | Version: uint16(data.vbe_ver), 67 | VideoMemory: uint32(data.vbe_video_mem), 68 | }, 69 | PnP: bool(C.bios_info_is_pnp_bios(data)), 70 | PnPId: uint(data.pnp_id), 71 | LbaSupport: bool(C.bios_info_has_lba_support(data)), 72 | LowMemorySize: uint32(data.low_mem_size), 73 | SmbiosVersion: uint32(data.smbios_ver), 74 | }, nil 75 | } 76 | -------------------------------------------------------------------------------- /pkg/hwinfo/detail_cpu.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | #include 7 | 8 | bool cpu_info_fpu(cpu_info_t *info) { return info->fpu; } 9 | bool cpu_info_fpu_exception(cpu_info_t *info) { return info->fpu_exception; } 10 | bool cpu_info_write_protect(cpu_info_t *info) { return info->write_protect; } 11 | */ 12 | import "C" 13 | 14 | import ( 15 | "fmt" 16 | "regexp" 17 | ) 18 | 19 | //go:generate enumer -type=CPUArch -json -transform=snake -trimprefix CPUArch -output=./detail_enum_cpu_arch.go 20 | type CPUArch uint //nolint:recvcheck 21 | 22 | const ( 23 | CPUArchUnknown CPUArch = iota 24 | CPUArchIntel 25 | CPUArchAlpha 26 | CPUArchSparc 27 | CPUArchSparc64 28 | CPUArchPpc 29 | CPUArchPpc64 30 | CpiArch68k 31 | CPUArchIa64 32 | CPUArchS390 33 | CPUArchS390x 34 | CPUArchArm 35 | CPUArchMips 36 | CPUArchX86_64 37 | CPUArchAarch64 38 | CPUArchLoongarch 39 | CPUArchRiscv 40 | ) 41 | 42 | type AddressSizes struct { 43 | Physical string `json:"physical,omitempty"` 44 | Virtual string `json:"virtual,omitempty"` 45 | } 46 | 47 | type DetailCPU struct { 48 | Type DetailType `json:"-"` 49 | 50 | Architecture CPUArch `json:"architecture"` 51 | 52 | VendorName string `json:"vendor_name,omitempty"` 53 | ModelName string `json:"model_name,omitempty"` 54 | 55 | Family uint16 `json:"family"` 56 | Model uint16 `json:"model"` 57 | Stepping uint32 `json:"stepping"` 58 | 59 | Platform string `json:"platform,omitempty"` 60 | 61 | Features []string `json:"features,omitempty"` 62 | Bugs []string `json:"bugs,omitempty"` 63 | PowerManagement []string `json:"power_management,omitempty"` 64 | 65 | Bogo float64 `json:"bogo"` 66 | Cache uint32 `json:"cache,omitempty"` 67 | Units uint32 `json:"units,omitempty"` 68 | Clock uint `json:"-"` 69 | 70 | // x86 only fields 71 | PhysicalID uint16 `json:"physical_id"` 72 | Siblings uint16 `json:"siblings,omitempty"` 73 | Cores uint16 `json:"cores,omitempty"` 74 | CoreID uint16 `json:"-"` 75 | Fpu bool `json:"fpu"` 76 | FpuException bool `json:"fpu_exception"` 77 | CpuidLevel uint8 `json:"cpuid_level,omitempty"` 78 | WriteProtect bool `json:"write_protect"` 79 | TlbSize uint16 `json:"tlb_size,omitempty"` 80 | ClflushSize uint16 `json:"clflush_size,omitempty"` 81 | CacheAlignment int `json:"cache_alignment,omitempty"` 82 | AddressSizes AddressSizes `json:"address_sizes,omitempty"` 83 | Apicid uint `json:"-"` 84 | ApicidInitial uint `json:"-"` 85 | } 86 | 87 | var matchCPUFreq = regexp.MustCompile(`, \d+ MHz$`) 88 | 89 | func stripCPUFreq(s string) string { 90 | // strip frequency of the model name as it is not stable. 91 | return matchCPUFreq.ReplaceAllString(s, "") 92 | } 93 | 94 | func NewDetailCPU(cpu C.hd_detail_cpu_t) (*DetailCPU, error) { 95 | data := cpu.data 96 | 97 | return &DetailCPU{ 98 | Type: DetailTypeCpu, 99 | 100 | Architecture: CPUArch(data.architecture), 101 | VendorName: C.GoString(data.vend_name), 102 | ModelName: stripCPUFreq(C.GoString(data.model_name)), 103 | 104 | Family: uint16(data.family), 105 | Model: uint16(data.model), 106 | Stepping: uint32(data.stepping), 107 | 108 | Platform: C.GoString(data.platform), 109 | 110 | Features: ReadStringList(data.features), 111 | Bugs: ReadStringList(data.bugs), 112 | PowerManagement: ReadStringList(data.power_management), 113 | 114 | Clock: uint(data.clock), 115 | Bogo: float64(data.bogo), 116 | Cache: uint32(data.cache), 117 | Units: uint32(data.units), 118 | 119 | PhysicalID: uint16(data.physical_id), 120 | Siblings: uint16(data.siblings), 121 | Cores: uint16(data.cores), 122 | CoreID: uint16(data.core_id), 123 | Apicid: uint(data.apicid), 124 | ApicidInitial: uint(data.apicid_initial), 125 | Fpu: bool(C.cpu_info_fpu(data)), 126 | FpuException: bool(C.cpu_info_fpu_exception(data)), 127 | CpuidLevel: uint8(data.cpuid_level), 128 | WriteProtect: bool(C.cpu_info_write_protect(data)), 129 | TlbSize: uint16(data.tlb_size), 130 | ClflushSize: uint16(data.clflush_size), 131 | CacheAlignment: int(data.cache_alignment), 132 | AddressSizes: AddressSizes{ 133 | Physical: fmt.Sprintf("0x%x", uint(data.address_size_physical)), 134 | Virtual: fmt.Sprintf("0x%x", uint(data.address_size_virtual)), 135 | }, 136 | }, nil 137 | } 138 | 139 | func (d DetailCPU) DetailType() DetailType { 140 | return DetailTypeCpu 141 | } 142 | -------------------------------------------------------------------------------- /pkg/hwinfo/detail_enum_pci_flag.go: -------------------------------------------------------------------------------- 1 | // Code generated by "enumer -type=PciFlag -json -transform=snake -trimprefix PciFlag -output=./detail_enum_pci_flag.go"; DO NOT EDIT. 2 | 3 | package hwinfo 4 | 5 | import ( 6 | "encoding/json" 7 | "fmt" 8 | "strings" 9 | ) 10 | 11 | const _PciFlagName = "okpmagp" 12 | 13 | var _PciFlagIndex = [...]uint8{0, 2, 4, 7} 14 | 15 | const _PciFlagLowerName = "okpmagp" 16 | 17 | func (i PciFlag) String() string { 18 | if i >= PciFlag(len(_PciFlagIndex)-1) { 19 | return fmt.Sprintf("PciFlag(%d)", i) 20 | } 21 | return _PciFlagName[_PciFlagIndex[i]:_PciFlagIndex[i+1]] 22 | } 23 | 24 | // An "invalid array index" compiler error signifies that the constant values have changed. 25 | // Re-run the stringer command to generate them again. 26 | func _PciFlagNoOp() { 27 | var x [1]struct{} 28 | _ = x[PciFlagOk-(0)] 29 | _ = x[PciFlagPm-(1)] 30 | _ = x[PciFlagAgp-(2)] 31 | } 32 | 33 | var _PciFlagValues = []PciFlag{PciFlagOk, PciFlagPm, PciFlagAgp} 34 | 35 | var _PciFlagNameToValueMap = map[string]PciFlag{ 36 | _PciFlagName[0:2]: PciFlagOk, 37 | _PciFlagLowerName[0:2]: PciFlagOk, 38 | _PciFlagName[2:4]: PciFlagPm, 39 | _PciFlagLowerName[2:4]: PciFlagPm, 40 | _PciFlagName[4:7]: PciFlagAgp, 41 | _PciFlagLowerName[4:7]: PciFlagAgp, 42 | } 43 | 44 | var _PciFlagNames = []string{ 45 | _PciFlagName[0:2], 46 | _PciFlagName[2:4], 47 | _PciFlagName[4:7], 48 | } 49 | 50 | // PciFlagString retrieves an enum value from the enum constants string name. 51 | // Throws an error if the param is not part of the enum. 52 | func PciFlagString(s string) (PciFlag, error) { 53 | if val, ok := _PciFlagNameToValueMap[s]; ok { 54 | return val, nil 55 | } 56 | 57 | if val, ok := _PciFlagNameToValueMap[strings.ToLower(s)]; ok { 58 | return val, nil 59 | } 60 | return 0, fmt.Errorf("%s does not belong to PciFlag values", s) 61 | } 62 | 63 | // PciFlagValues returns all values of the enum 64 | func PciFlagValues() []PciFlag { 65 | return _PciFlagValues 66 | } 67 | 68 | // PciFlagStrings returns a slice of all String values of the enum 69 | func PciFlagStrings() []string { 70 | strs := make([]string, len(_PciFlagNames)) 71 | copy(strs, _PciFlagNames) 72 | return strs 73 | } 74 | 75 | // IsAPciFlag returns "true" if the value is listed in the enum definition. "false" otherwise 76 | func (i PciFlag) IsAPciFlag() bool { 77 | for _, v := range _PciFlagValues { 78 | if i == v { 79 | return true 80 | } 81 | } 82 | return false 83 | } 84 | 85 | // MarshalJSON implements the json.Marshaler interface for PciFlag 86 | func (i PciFlag) MarshalJSON() ([]byte, error) { 87 | return json.Marshal(i.String()) 88 | } 89 | 90 | // UnmarshalJSON implements the json.Unmarshaler interface for PciFlag 91 | func (i *PciFlag) UnmarshalJSON(data []byte) error { 92 | var s string 93 | if err := json.Unmarshal(data, &s); err != nil { 94 | return fmt.Errorf("PciFlag should be a string, got %s", data) 95 | } 96 | 97 | var err error 98 | *i, err = PciFlagString(s) 99 | return err 100 | } 101 | -------------------------------------------------------------------------------- /pkg/hwinfo/detail_isa_pnp_dev.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | #include 7 | 8 | // custom getters to get around the problem with bitfields https://github.com/golang/go/issues/43261 9 | bool hd_isapnp_card_get_broken(isapnp_card_t *card) { return card->broken; } 10 | */ 11 | import "C" 12 | 13 | import ( 14 | "encoding/hex" 15 | "errors" 16 | "unsafe" 17 | ) 18 | 19 | type IsaPnpResource struct { 20 | Length int `json:"length"` 21 | Type int `json:"type"` 22 | Data string `json:"-"` // hex encoded 23 | } 24 | 25 | func NewIsaPnpResource(res *C.isapnp_res_t) *IsaPnpResource { 26 | if res == nil { 27 | return nil 28 | } 29 | return &IsaPnpResource{ 30 | Length: int(res.len), 31 | Type: int(res._type), 32 | Data: hex.EncodeToString(C.GoBytes(unsafe.Pointer(&res.data), res.len)), //nolint:gocritic 33 | } 34 | } 35 | 36 | type IsaPnpCard struct { 37 | Csn int `json:"csn"` 38 | LogDevs int `json:"log_devs"` // todo full name? 39 | Serial string `json:"-"` 40 | CardRegs string `json:"card_regs"` // todo full name? 41 | LdevRegs string `json:"ldev_regs"` // todo full name? hex encoded 42 | ResLen int `json:"res_len"` // todo full name? 43 | Broken bool `json:"broken"` // mark a broken card 44 | Resource *IsaPnpResource `json:"resource"` 45 | } 46 | 47 | func NewIsaPnpCard(card *C.isapnp_card_t) (*IsaPnpCard, error) { 48 | if card == nil { 49 | return nil, errors.New("card is nil") 50 | } 51 | 52 | return &IsaPnpCard{ 53 | Csn: int(card.csn), 54 | LogDevs: int(card.log_devs), 55 | // Serial: C.GoString(card.serial), todo 56 | // CardRegs: C.GoString(card.card_regs), todo 57 | LdevRegs: hex.EncodeToString(C.GoBytes(unsafe.Pointer(&card.ldev_regs), C.int(0xd0))), //nolint:gocritic 58 | ResLen: int(card.res_len), 59 | Broken: bool(C.hd_isapnp_card_get_broken(card)), 60 | Resource: NewIsaPnpResource(card.res), 61 | }, nil 62 | } 63 | 64 | type DetailIsaPnpDevice struct { 65 | Type DetailType `json:"-"` 66 | Card *IsaPnpCard `json:"card"` 67 | Device uint32 `json:"device"` 68 | Flags uint32 `json:"flags"` 69 | } 70 | 71 | func (d DetailIsaPnpDevice) DetailType() DetailType { 72 | return DetailTypeIsaPnp 73 | } 74 | 75 | func NewDetailIsaPnpDevice(pnp C.hd_detail_isapnp_t) (*DetailIsaPnpDevice, error) { 76 | data := pnp.data 77 | 78 | card, err := NewIsaPnpCard(data.card) 79 | if err != nil { 80 | return nil, err 81 | } 82 | 83 | return &DetailIsaPnpDevice{ 84 | Type: DetailTypeIsaPnp, 85 | Card: card, 86 | Device: uint32(data.dev), 87 | Flags: uint32(data.flags), 88 | }, nil 89 | } 90 | -------------------------------------------------------------------------------- /pkg/hwinfo/detail_monitor.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | 9 | type SyncRange struct { 10 | Min uint16 `json:"min"` 11 | Max uint16 `json:"max"` 12 | } 13 | 14 | type SyncTimings struct { 15 | Disp uint16 `json:"disp"` // todo what's the proper name for this? 16 | SyncStart uint16 `json:"sync_start"` 17 | SyncEnd uint16 `json:"sync_end"` 18 | Total uint32 `json:"total"` // todo what's a better name for this? 19 | } 20 | 21 | type DetailMonitor struct { 22 | Type DetailType `json:"-"` 23 | ManufactureYear uint16 `json:"manufacture_year"` 24 | ManufactureWeek uint8 `json:"manufacture_week"` 25 | VerticalSync SyncRange `json:"vertical_sync"` 26 | HorizontalSync SyncRange `json:"horizontal_sync"` 27 | HorizontalSyncTimings SyncTimings `json:"horizontal_sync_timings"` 28 | VerticalSyncTimings SyncTimings `json:"vertical_sync_timings"` 29 | Clock uint32 `json:"clock"` 30 | Width uint32 `json:"width"` 31 | Height uint32 `json:"height"` 32 | WidthMillimetres uint32 `json:"width_millimetres"` 33 | HeightMillimetres uint32 `json:"height_millimetres"` 34 | HorizontalFlag byte `json:"horizontal_flag"` 35 | VerticalFlag byte `json:"vertical_flag"` 36 | Vendor string `json:"vendor"` 37 | Name string `json:"name"` 38 | 39 | Serial string `json:"-"` 40 | } 41 | 42 | func (d DetailMonitor) DetailType() DetailType { 43 | return DetailTypeMonitor 44 | } 45 | 46 | func NewDetailMonitor(mon C.hd_detail_monitor_t) (*DetailMonitor, error) { 47 | data := mon.data 48 | 49 | return &DetailMonitor{ 50 | Type: DetailTypeMonitor, 51 | ManufactureYear: uint16(data.manu_year), 52 | ManufactureWeek: uint8(data.manu_week), 53 | VerticalSync: SyncRange{ 54 | Min: uint16(data.min_vsync), 55 | Max: uint16(data.max_vsync), 56 | }, 57 | HorizontalSync: SyncRange{ 58 | Min: uint16(data.min_hsync), 59 | Max: uint16(data.max_hsync), 60 | }, 61 | Clock: uint32(data.clock), 62 | Width: uint32(data.width), 63 | Height: uint32(data.height), 64 | WidthMillimetres: uint32(data.width_mm), 65 | HeightMillimetres: uint32(data.height_mm), 66 | HorizontalSyncTimings: SyncTimings{ 67 | Disp: uint16(data.hdisp), 68 | SyncStart: uint16(data.hsyncstart), 69 | SyncEnd: uint16(data.hsyncend), 70 | Total: uint32(data.htotal), 71 | }, 72 | VerticalSyncTimings: SyncTimings{ 73 | Disp: uint16(data.vdisp), 74 | SyncStart: uint16(data.vsyncstart), 75 | SyncEnd: uint16(data.vsyncend), 76 | Total: uint32(data.vtotal), 77 | }, 78 | HorizontalFlag: byte(data.hflag), 79 | VerticalFlag: byte(data.vflag), 80 | Vendor: C.GoString(data.vendor), 81 | Name: C.GoString(data.name), 82 | Serial: C.GoString(data.serial), 83 | }, nil 84 | } 85 | -------------------------------------------------------------------------------- /pkg/hwinfo/detail_pci.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | 9 | import ( 10 | "encoding/hex" 11 | "slices" 12 | "unsafe" 13 | ) 14 | 15 | //go:generate enumer -type=PciFlag -json -transform=snake -trimprefix PciFlag -output=./detail_enum_pci_flag.go 16 | type PciFlag uint //nolint:recvcheck 17 | 18 | const ( 19 | PciFlagOk PciFlag = iota 20 | PciFlagPm 21 | PciFlagAgp 22 | ) 23 | 24 | func ParsePciFlags(flags uint) []PciFlag { 25 | var result []PciFlag 26 | 27 | for _, flag := range PciFlagValues() { 28 | if (flag & (1 << flags)) == 1 { 29 | result = append(result, flag) 30 | } 31 | } 32 | // ensure stable output 33 | slices.Sort(result) 34 | 35 | return result 36 | } 37 | 38 | type DetailPci struct { 39 | Type DetailType `json:"-"` 40 | 41 | Flags []PciFlag `json:"flags,omitempty"` // 42 | Function uint32 `json:"function"` 43 | 44 | // todo map pci constants from pci.h? 45 | Command uint32 `json:"command"` // PCI_COMMAND 46 | HeaderType uint32 `json:"header_type"` // PCI_HEADER_TYPE 47 | SecondaryBus uint32 `json:"secondary_bus"` // > 0 for PCI & CB bridges 48 | 49 | Irq uint16 `json:"irq"` // used irq if any 50 | // Programming Interface Byte: a read-only register that specifies a register-level programming interface for the 51 | // device. 52 | ProgIf uint16 `json:"prog_if"` 53 | 54 | // already included in the parent model, so we omit from JSON output 55 | Bus uint `json:"-"` 56 | Slot uint `json:"-"` 57 | 58 | BaseClass uint `json:"-"` 59 | SubClass uint `json:"-"` 60 | 61 | Device uint `json:"-"` 62 | Vendor uint `json:"-"` 63 | SubDevice uint `json:"-"` 64 | SubVendor uint `json:"-"` 65 | Revision uint `json:"-"` 66 | 67 | BaseAddress [7]uint64 `json:"-"` // I/O or memory base 68 | BaseLength [7]uint64 `json:"-"` // I/O or memory ranges 69 | AddressFlags [7]uint `json:"-"` // I/O or memory address flags 70 | 71 | RomBaseAddress uint64 `json:"-"` // memory base for card ROM 72 | RomBaseLength uint64 `json:"-"` // memory range for card ROM 73 | 74 | SysfsID string `json:"-"` // sysfs path 75 | SysfsBusID string `json:"-"` // sysfs bus id 76 | ModuleAlias string `json:"-"` // module alias 77 | Label string `json:"-"` // Consistent Device Name (CDN), pci firmware 3.1, chapter 4.6.7 78 | 79 | // Omit from JSON output 80 | Data string `json:"-"` // the PCI data, hex encoded 81 | DataLength uint `json:"-"` // holds the actual length of Data 82 | DataExtLength uint `json:"-"` // max. accessed config byte 83 | Log string `json:"-"` // log messages 84 | } 85 | 86 | func (p DetailPci) DetailType() DetailType { 87 | return DetailTypePci 88 | } 89 | 90 | func NewDetailPci(pci C.hd_detail_pci_t) (*DetailPci, error) { 91 | data := pci.data 92 | 93 | return &DetailPci{ 94 | Type: DetailTypePci, 95 | Data: hex.EncodeToString(C.GoBytes(unsafe.Pointer(&data.data), 256)), //nolint:gocritic 96 | DataLength: uint(data.data_len), 97 | DataExtLength: uint(data.data_ext_len), 98 | Log: C.GoString(data.log), 99 | Flags: ParsePciFlags(uint(data.flags)), 100 | Command: uint32(data.cmd), 101 | HeaderType: uint32(data.hdr_type), 102 | SecondaryBus: uint32(data.secondary_bus), 103 | Bus: uint(data.bus), 104 | Slot: uint(data.slot), 105 | Function: uint32(data._func), 106 | BaseClass: uint(data.base_class), 107 | SubClass: uint(data.sub_class), 108 | ProgIf: uint16(data.prog_if), 109 | Device: uint(data.dev), 110 | Vendor: uint(data.vend), 111 | SubDevice: uint(data.sub_dev), 112 | SubVendor: uint(data.sub_vend), 113 | Revision: uint(data.rev), 114 | Irq: uint16(data.irq), 115 | BaseAddress: [7]uint64(ReadUint64Array(unsafe.Pointer(&data.base_addr), 7)), 116 | BaseLength: [7]uint64(ReadUint64Array(unsafe.Pointer(&data.base_len), 7)), 117 | AddressFlags: [7]uint(ReadUintArray(unsafe.Pointer(&data.addr_flags), 7)), 118 | RomBaseAddress: uint64(data.rom_base_addr), 119 | RomBaseLength: uint64(data.rom_base_len), 120 | SysfsID: C.GoString(data.sysfs_id), 121 | SysfsBusID: C.GoString(data.sysfs_bus_id), 122 | ModuleAlias: C.GoString(data.modalias), 123 | Label: C.GoString(data.label), 124 | }, nil 125 | } 126 | -------------------------------------------------------------------------------- /pkg/hwinfo/detail_sys.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | 9 | type DetailSys struct { 10 | Type DetailType `json:"-"` 11 | SystemType string `json:"system_type,omitempty"` 12 | Generation string `json:"generation,omitempty"` 13 | Vendor string `json:"vendor,omitempty"` 14 | Model string `json:"model,omitempty"` 15 | Serial string `json:"-"` 16 | Language string `json:"language,omitempty"` 17 | FormFactor string `json:"form_factor,omitempty"` 18 | } 19 | 20 | func (d DetailSys) DetailType() DetailType { 21 | return DetailTypeSys 22 | } 23 | 24 | func NewDetailSys(sys C.hd_detail_sys_t) (*DetailSys, error) { 25 | data := sys.data 26 | return &DetailSys{ 27 | Type: DetailTypeSys, 28 | SystemType: C.GoString(data.system_type), 29 | Generation: C.GoString(data.generation), 30 | Vendor: C.GoString(data.vendor), 31 | Model: C.GoString(data.model), 32 | Serial: C.GoString(data.serial), 33 | Language: C.GoString(data.lang), 34 | FormFactor: C.GoString(data.formfactor), 35 | }, nil 36 | } 37 | -------------------------------------------------------------------------------- /pkg/hwinfo/detail_usb.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | 9 | //go:generate enumer -type=UsbClass -json -transform=snake -trimprefix UsbClass -output=./detail_usb_enum_usb_class.go 10 | type UsbClass uint16 //nolint:recvcheck 11 | 12 | const ( 13 | UsbClassPerInterface UsbClass = 0x00 14 | UsbClassAudio UsbClass = 0x01 15 | UsbClassComm UsbClass = 0x02 16 | UsbClassHID UsbClass = 0x03 17 | UsbClassPhysical UsbClass = 0x05 18 | UsbClassImage UsbClass = 0x06 19 | UsbClassPTP UsbClass = UsbClassImage // legacy name for image 20 | UsbClassPrinter UsbClass = 0x07 21 | UsbClassMassStorage UsbClass = 0x08 22 | UsbClassHub UsbClass = 0x09 23 | UsbClassData UsbClass = 0x0a 24 | UsbClassSmartCard UsbClass = 0x0b 25 | UsbClassContentSecurity UsbClass = 0x0d 26 | UsbClassVideo UsbClass = 0x0e 27 | UsbClassPersonalHealthcare UsbClass = 0x0f 28 | UsbClassAudioVideo UsbClass = 0x10 29 | UsbClassBillboard UsbClass = 0x11 30 | UsbClassUSBTypeCBridge UsbClass = 0x12 31 | UsbClassDiagnosticDevice UsbClass = 0xdc 32 | UsbClassWireless UsbClass = 0xe0 33 | UsbClassMiscellaneous UsbClass = 0xef 34 | UsbClassApplication UsbClass = 0xfe 35 | UsbClassVendorSpec UsbClass = 0xff 36 | ) 37 | 38 | type DetailUsb struct { 39 | Type DetailType `json:"-"` 40 | 41 | DeviceClass ID `json:"device_class"` 42 | DeviceSubclass ID `json:"device_subclass"` 43 | DeviceProtocol int `json:"device_protocol"` 44 | 45 | InterfaceClass ID `json:"interface_class"` 46 | InterfaceSubclass ID `json:"interface_subclass"` 47 | InterfaceProtocol int `json:"interface_protocol"` 48 | InterfaceNumber int `json:"interface_number"` 49 | InterfaceAlternateSetting int `json:"interface_alternate_setting"` 50 | 51 | InterfaceAssociation *DetailUsbInterfaceAssociation `json:"interface_association,omitempty"` 52 | } 53 | 54 | type DetailUsbInterfaceAssociation struct { 55 | FunctionClass ID `json:"function_class"` 56 | FunctionSubclass ID `json:"function_subclass"` 57 | FunctionProtocol int `json:"function_protocol"` 58 | InterfaceCount int `json:"interface_count"` 59 | FirstInterface int `json:"first_interface"` 60 | } 61 | 62 | func (d DetailUsb) DetailType() DetailType { 63 | return DetailTypeUsb 64 | } 65 | 66 | func NewDetailUsb(usb C.hd_detail_usb_t) (*DetailUsb, error) { 67 | data := usb.data 68 | 69 | if data.next != nil { 70 | println("usb next is not nil") 71 | } 72 | 73 | detail := &DetailUsb{ 74 | Type: DetailTypeUsb, 75 | DeviceClass: ID{ 76 | Type: IDTagUsb, 77 | Value: uint16(data.d_cls), 78 | Name: UsbClass(data.d_cls).String(), 79 | }, 80 | DeviceSubclass: ID{ 81 | Type: IDTagUsb, 82 | Value: uint16(data.d_sub), 83 | Name: UsbClass(data.d_sub).String(), 84 | }, 85 | DeviceProtocol: int(data.d_prot), 86 | InterfaceClass: ID{ 87 | Type: IDTagUsb, 88 | Value: uint16(data.i_cls), 89 | Name: UsbClass(data.i_cls).String(), 90 | }, 91 | InterfaceSubclass: ID{ 92 | Type: IDTagUsb, 93 | Value: uint16(data.i_sub), 94 | Name: UsbClass(data.i_sub).String(), 95 | }, 96 | InterfaceProtocol: int(data.i_prot), 97 | InterfaceNumber: int(data.ifdescr), 98 | InterfaceAlternateSetting: int(data.i_alt), 99 | } 100 | 101 | // The Interface Association Descriptor groups multiple interfaces that are part of a single functional device. 102 | // For instance, a USB webcam with an integrated microphone would use an IAD to group the video input interface and 103 | // the audio input interface together. 104 | if data.iad_i_count > 0 { 105 | detail.InterfaceAssociation = &DetailUsbInterfaceAssociation{ 106 | FunctionClass: ID{ 107 | Type: IDTagUsb, 108 | Value: uint16(data.iad_f_cls), 109 | Name: UsbClass(data.iad_f_cls).String(), 110 | }, 111 | FunctionSubclass: ID{ 112 | Type: IDTagUsb, 113 | Value: uint16(data.iad_f_sub), 114 | Name: UsbClass(data.iad_f_sub).String(), 115 | }, 116 | FunctionProtocol: int(data.iad_f_prot), 117 | FirstInterface: int(data.iad_i_first), 118 | InterfaceCount: int(data.iad_i_count), 119 | } 120 | } 121 | 122 | return detail, nil 123 | } 124 | -------------------------------------------------------------------------------- /pkg/hwinfo/driver_info.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | 7 | enum driver_info_type driver_info_get_type(driver_info_t *info) { return info->any.type; } 8 | driver_info_module_t driver_info_get_module(driver_info_t *info) { return info->module; } 9 | driver_info_mouse_t driver_info_get_mouse(driver_info_t *info) { return info->mouse; } 10 | driver_info_display_t driver_info_get_display(driver_info_t *info) { return info->display; } 11 | driver_info_kbd_t driver_info_get_kbd(driver_info_t *info) { return info->kbd; } 12 | driver_info_dsl_t driver_info_get_dsl(driver_info_t *info) { return info->dsl; } 13 | driver_info_isdn_t driver_info_get_isdn(driver_info_t *info) { return info->isdn; } 14 | driver_info_x11_t driver_info_get_x11(driver_info_t *info) { return info->x11; } 15 | */ 16 | import "C" 17 | 18 | import ( 19 | "errors" 20 | ) 21 | 22 | //go:generate enumer -type=DriverInfoType -json -transform=snake -trimprefix DriverInfoType -output=./driver_info_enum_type.go 23 | type DriverInfoType uint //nolint:recvcheck 24 | 25 | const ( 26 | DriverInfoTypeAny DriverInfoType = iota 27 | DriverInfoTypeDisplay 28 | DriverInfoTypeModule 29 | DriverInfoTypeMouse 30 | DriverInfoTypeX11 31 | DriverInfoTypeIsdn 32 | DriverInfoTypeKeyboard 33 | DriverInfoTypeDsl 34 | ) 35 | 36 | type DriverInfo interface { 37 | DriverInfoType() DriverInfoType 38 | } 39 | 40 | //nolint:ireturn 41 | func NewDriverInfo(info *C.driver_info_t) (DriverInfo, error) { 42 | if info == nil { 43 | return nil, errors.New("info is nil") 44 | } 45 | 46 | var ( 47 | err error 48 | result DriverInfo 49 | ) 50 | 51 | switch DriverInfoType(C.driver_info_get_type(info)) { 52 | case DriverInfoTypeModule: 53 | result, err = NewDriverInfoModule(C.driver_info_get_module(info)), nil 54 | case DriverInfoTypeMouse: 55 | result, err = NewDriverInfoMouse(C.driver_info_get_mouse(info)), nil 56 | case DriverInfoTypeDisplay: 57 | result, err = NewDriverInfoDisplay(C.driver_info_get_display(info)), nil 58 | case DriverInfoTypeKeyboard: 59 | result, err = NewDriverInfoKeyboard(C.driver_info_get_kbd(info)), nil 60 | case DriverInfoTypeDsl: 61 | result, err = NewDriverInfoDsl(C.driver_info_get_dsl(info)), nil 62 | case DriverInfoTypeIsdn: 63 | result, err = NewDriverInfoIsdn(C.driver_info_get_isdn(info)), nil 64 | case DriverInfoTypeX11: 65 | result, err = NewDriverInfoX11(C.driver_info_get_x11(info)), nil 66 | default: 67 | err = errors.New("unknown driver info type") 68 | } 69 | 70 | return result, err 71 | } 72 | -------------------------------------------------------------------------------- /pkg/hwinfo/driver_info_display.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | 9 | type DriverInfoDisplay struct { 10 | Type DriverInfoType `json:"type,omitempty"` 11 | // actual driver database entries 12 | DBEntry0 []string `json:"db_entry_0,omitempty"` 13 | DBEntry1 []string `json:"db_entry_1,omitempty"` 14 | 15 | Width uint32 `json:"width"` 16 | Height uint32 `json:"height"` 17 | VerticalSync SyncRange `json:"vertical_sync"` 18 | HorizontalSync SyncRange `json:"horizontal_sync"` 19 | Bandwidth uint32 `json:"bandwidth"` 20 | HorizontalSyncTimings SyncTimings `json:"horizontal_sync_timings"` 21 | VerticalSyncTimings SyncTimings `json:"vertical_sync_timings"` 22 | HorizontalFlag byte `json:"horizontal_flag"` 23 | VerticalFlag byte `json:"vertical_flag"` 24 | } 25 | 26 | func (d DriverInfoDisplay) DriverInfoType() DriverInfoType { 27 | return DriverInfoTypeDisplay 28 | } 29 | 30 | func NewDriverInfoDisplay(info C.driver_info_display_t) DriverInfoDisplay { 31 | return DriverInfoDisplay{ 32 | Type: DriverInfoTypeDisplay, 33 | DBEntry0: ReadStringList(info.hddb0), 34 | DBEntry1: ReadStringList(info.hddb1), 35 | Width: uint32(info.width), 36 | Height: uint32(info.height), 37 | VerticalSync: SyncRange{ 38 | Min: uint16(info.min_vsync), 39 | Max: uint16(info.max_vsync), 40 | }, 41 | HorizontalSync: SyncRange{ 42 | Min: uint16(info.min_hsync), 43 | Max: uint16(info.max_hsync), 44 | }, 45 | HorizontalSyncTimings: SyncTimings{ 46 | Disp: uint16(info.hdisp), 47 | SyncStart: uint16(info.hsyncstart), 48 | SyncEnd: uint16(info.hsyncend), 49 | Total: uint32(info.htotal), 50 | }, 51 | VerticalSyncTimings: SyncTimings{ 52 | Disp: uint16(info.vdisp), 53 | SyncStart: uint16(info.vsyncstart), 54 | SyncEnd: uint16(info.vsyncend), 55 | Total: uint32(info.vtotal), 56 | }, 57 | HorizontalFlag: byte(info.hflag), 58 | VerticalFlag: byte(info.vflag), 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /pkg/hwinfo/driver_info_dsl.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | 9 | type DriverInfoDsl struct { 10 | Type DriverInfoType `json:"type,omitempty"` 11 | // actual driver database entries 12 | DBEntry0 []string `json:"db_entry_0,omitempty"` 13 | DBEntry1 []string `json:"db_entry_1,omitempty"` 14 | 15 | Mode string `json:"mode,omitempty"` // DSL driver types 16 | Name string `json:"name,omitempty"` // DSL driver name 17 | } 18 | 19 | func (d DriverInfoDsl) DriverInfoType() DriverInfoType { 20 | return DriverInfoTypeDsl 21 | } 22 | 23 | func NewDriverInfoDsl(info C.driver_info_dsl_t) DriverInfoDsl { 24 | return DriverInfoDsl{ 25 | Type: DriverInfoTypeDsl, 26 | DBEntry0: ReadStringList(info.hddb0), 27 | DBEntry1: ReadStringList(info.hddb1), 28 | Mode: C.GoString(info.mode), 29 | Name: C.GoString(info.name), 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /pkg/hwinfo/driver_info_enum_type.go: -------------------------------------------------------------------------------- 1 | // Code generated by "enumer -type=DriverInfoType -json -transform=snake -trimprefix DriverInfoType -output=./driver_info_enum_type.go"; DO NOT EDIT. 2 | 3 | package hwinfo 4 | 5 | import ( 6 | "encoding/json" 7 | "fmt" 8 | "strings" 9 | ) 10 | 11 | const _DriverInfoTypeName = "anydisplaymodulemousex11isdnkeyboarddsl" 12 | 13 | var _DriverInfoTypeIndex = [...]uint8{0, 3, 10, 16, 21, 24, 28, 36, 39} 14 | 15 | const _DriverInfoTypeLowerName = "anydisplaymodulemousex11isdnkeyboarddsl" 16 | 17 | func (i DriverInfoType) String() string { 18 | if i >= DriverInfoType(len(_DriverInfoTypeIndex)-1) { 19 | return fmt.Sprintf("DriverInfoType(%d)", i) 20 | } 21 | return _DriverInfoTypeName[_DriverInfoTypeIndex[i]:_DriverInfoTypeIndex[i+1]] 22 | } 23 | 24 | // An "invalid array index" compiler error signifies that the constant values have changed. 25 | // Re-run the stringer command to generate them again. 26 | func _DriverInfoTypeNoOp() { 27 | var x [1]struct{} 28 | _ = x[DriverInfoTypeAny-(0)] 29 | _ = x[DriverInfoTypeDisplay-(1)] 30 | _ = x[DriverInfoTypeModule-(2)] 31 | _ = x[DriverInfoTypeMouse-(3)] 32 | _ = x[DriverInfoTypeX11-(4)] 33 | _ = x[DriverInfoTypeIsdn-(5)] 34 | _ = x[DriverInfoTypeKeyboard-(6)] 35 | _ = x[DriverInfoTypeDsl-(7)] 36 | } 37 | 38 | var _DriverInfoTypeValues = []DriverInfoType{DriverInfoTypeAny, DriverInfoTypeDisplay, DriverInfoTypeModule, DriverInfoTypeMouse, DriverInfoTypeX11, DriverInfoTypeIsdn, DriverInfoTypeKeyboard, DriverInfoTypeDsl} 39 | 40 | var _DriverInfoTypeNameToValueMap = map[string]DriverInfoType{ 41 | _DriverInfoTypeName[0:3]: DriverInfoTypeAny, 42 | _DriverInfoTypeLowerName[0:3]: DriverInfoTypeAny, 43 | _DriverInfoTypeName[3:10]: DriverInfoTypeDisplay, 44 | _DriverInfoTypeLowerName[3:10]: DriverInfoTypeDisplay, 45 | _DriverInfoTypeName[10:16]: DriverInfoTypeModule, 46 | _DriverInfoTypeLowerName[10:16]: DriverInfoTypeModule, 47 | _DriverInfoTypeName[16:21]: DriverInfoTypeMouse, 48 | _DriverInfoTypeLowerName[16:21]: DriverInfoTypeMouse, 49 | _DriverInfoTypeName[21:24]: DriverInfoTypeX11, 50 | _DriverInfoTypeLowerName[21:24]: DriverInfoTypeX11, 51 | _DriverInfoTypeName[24:28]: DriverInfoTypeIsdn, 52 | _DriverInfoTypeLowerName[24:28]: DriverInfoTypeIsdn, 53 | _DriverInfoTypeName[28:36]: DriverInfoTypeKeyboard, 54 | _DriverInfoTypeLowerName[28:36]: DriverInfoTypeKeyboard, 55 | _DriverInfoTypeName[36:39]: DriverInfoTypeDsl, 56 | _DriverInfoTypeLowerName[36:39]: DriverInfoTypeDsl, 57 | } 58 | 59 | var _DriverInfoTypeNames = []string{ 60 | _DriverInfoTypeName[0:3], 61 | _DriverInfoTypeName[3:10], 62 | _DriverInfoTypeName[10:16], 63 | _DriverInfoTypeName[16:21], 64 | _DriverInfoTypeName[21:24], 65 | _DriverInfoTypeName[24:28], 66 | _DriverInfoTypeName[28:36], 67 | _DriverInfoTypeName[36:39], 68 | } 69 | 70 | // DriverInfoTypeString retrieves an enum value from the enum constants string name. 71 | // Throws an error if the param is not part of the enum. 72 | func DriverInfoTypeString(s string) (DriverInfoType, error) { 73 | if val, ok := _DriverInfoTypeNameToValueMap[s]; ok { 74 | return val, nil 75 | } 76 | 77 | if val, ok := _DriverInfoTypeNameToValueMap[strings.ToLower(s)]; ok { 78 | return val, nil 79 | } 80 | return 0, fmt.Errorf("%s does not belong to DriverInfoType values", s) 81 | } 82 | 83 | // DriverInfoTypeValues returns all values of the enum 84 | func DriverInfoTypeValues() []DriverInfoType { 85 | return _DriverInfoTypeValues 86 | } 87 | 88 | // DriverInfoTypeStrings returns a slice of all String values of the enum 89 | func DriverInfoTypeStrings() []string { 90 | strs := make([]string, len(_DriverInfoTypeNames)) 91 | copy(strs, _DriverInfoTypeNames) 92 | return strs 93 | } 94 | 95 | // IsADriverInfoType returns "true" if the value is listed in the enum definition. "false" otherwise 96 | func (i DriverInfoType) IsADriverInfoType() bool { 97 | for _, v := range _DriverInfoTypeValues { 98 | if i == v { 99 | return true 100 | } 101 | } 102 | return false 103 | } 104 | 105 | // MarshalJSON implements the json.Marshaler interface for DriverInfoType 106 | func (i DriverInfoType) MarshalJSON() ([]byte, error) { 107 | return json.Marshal(i.String()) 108 | } 109 | 110 | // UnmarshalJSON implements the json.Unmarshaler interface for DriverInfoType 111 | func (i *DriverInfoType) UnmarshalJSON(data []byte) error { 112 | var s string 113 | if err := json.Unmarshal(data, &s); err != nil { 114 | return fmt.Errorf("DriverInfoType should be a string, got %s", data) 115 | } 116 | 117 | var err error 118 | *i, err = DriverInfoTypeString(s) 119 | return err 120 | } 121 | -------------------------------------------------------------------------------- /pkg/hwinfo/driver_info_isdn.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | 9 | type DriverInfoIsdn struct { 10 | Type DriverInfoType `json:"type,omitempty"` 11 | // actual driver database entries 12 | DBEntry0 []string `json:"db_entry_0,omitempty"` 13 | DBEntry1 []string `json:"db_entry_1,omitempty"` 14 | 15 | I4lType int `json:"i4l_type"` 16 | I4lSubtype int `json:"i4l_sub_type"` 17 | I4lName string `json:"i4l_name,omitempty"` 18 | // todo isdn params 19 | } 20 | 21 | func (d DriverInfoIsdn) DriverInfoType() DriverInfoType { 22 | return DriverInfoTypeIsdn 23 | } 24 | 25 | func NewDriverInfoIsdn(info C.driver_info_isdn_t) DriverInfoIsdn { 26 | return DriverInfoIsdn{ 27 | Type: DriverInfoTypeIsdn, 28 | DBEntry0: ReadStringList(info.hddb0), 29 | DBEntry1: ReadStringList(info.hddb1), 30 | I4lType: int(info.i4l_type), 31 | I4lSubtype: int(info.i4l_subtype), 32 | I4lName: C.GoString(info.i4l_name), 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /pkg/hwinfo/driver_info_keyboard.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | 9 | type DriverInfoKeyboard struct { 10 | Type DriverInfoType `json:"type,omitempty"` 11 | // actual driver database entries 12 | DBEntry0 []string `json:"db_entry_0,omitempty"` 13 | DBEntry1 []string `json:"db_entry_1,omitempty"` 14 | 15 | XkbRules string `json:"xkb_rules,omitempty"` 16 | XkbModel string `json:"xkb_model,omitempty"` 17 | XkbLayout string `json:"xkb_layout,omitempty"` 18 | Keymap string `json:"keymap,omitempty"` 19 | } 20 | 21 | func (d DriverInfoKeyboard) DriverInfoType() DriverInfoType { 22 | return DriverInfoTypeKeyboard 23 | } 24 | 25 | func NewDriverInfoKeyboard(info C.driver_info_kbd_t) DriverInfoKeyboard { 26 | return DriverInfoKeyboard{ 27 | Type: DriverInfoTypeKeyboard, 28 | DBEntry0: ReadStringList(info.hddb0), 29 | DBEntry1: ReadStringList(info.hddb1), 30 | XkbRules: C.GoString(info.XkbRules), 31 | XkbModel: C.GoString(info.XkbModel), 32 | XkbLayout: C.GoString(info.XkbLayout), 33 | Keymap: C.GoString(info.keymap), 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /pkg/hwinfo/driver_info_module.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | #include 7 | 8 | // custom getters to get around the problem with bitfields https://github.com/golang/go/issues/43261 9 | bool driver_info_module_is_active(driver_info_module_t info) { return info.active; } 10 | bool driver_info_module_is_modprobe(driver_info_module_t info) { return info.modprobe; } 11 | */ 12 | import "C" 13 | 14 | type DriverInfoModule struct { 15 | Type DriverInfoType `json:"type,omitempty"` 16 | // actual driver database entries 17 | DBEntry0 []string `json:"db_entry_0,omitempty"` 18 | DBEntry1 []string `json:"db_entry_1,omitempty"` 19 | 20 | Active bool `json:"active"` // if the module is currently active 21 | Modprobe bool `json:"modprobe"` // modprobe or insmod 22 | Names []string `json:"names"` // (ordered) list of module names 23 | ModuleArgs []string `json:"module_args"` // list of module args (corresponds to the module name list) 24 | Conf string `json:"conf"` // conf.modules entry, if any (e.g., for sb.o) 25 | } 26 | 27 | func (d DriverInfoModule) DriverInfoType() DriverInfoType { 28 | return DriverInfoTypeModule 29 | } 30 | 31 | func NewDriverInfoModule(info C.driver_info_module_t) DriverInfoModule { 32 | return DriverInfoModule{ 33 | Type: DriverInfoTypeModule, 34 | DBEntry0: ReadStringList(info.hddb0), 35 | DBEntry1: ReadStringList(info.hddb1), 36 | Active: bool(C.driver_info_module_is_active(info)), 37 | Modprobe: bool(C.driver_info_module_is_modprobe(info)), 38 | Names: ReadStringList(info.names), 39 | ModuleArgs: ReadStringList(info.mod_args), 40 | Conf: C.GoString(info.conf), 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /pkg/hwinfo/driver_info_mouse.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | 9 | type DriverInfoMouse struct { 10 | Type DriverInfoType `json:"type,omitempty"` 11 | // actual driver database entries 12 | DBEntry0 []string `json:"db_entry_0,omitempty"` 13 | DBEntry1 []string `json:"db_entry_1,omitempty"` 14 | 15 | XF86 string `json:"xf86,omitempty"` // XF86 protocol name 16 | GPM string `json:"gpm,omitempty"` // dto, gpm 17 | Buttons int `json:"buttons,omitempty"` // number of buttons, -1 -> unknown 18 | Wheels int `json:"wheels,omitempty"` // dto, wheels 19 | } 20 | 21 | func (d DriverInfoMouse) DriverInfoType() DriverInfoType { 22 | return DriverInfoTypeMouse 23 | } 24 | 25 | func NewDriverInfoMouse(info C.driver_info_mouse_t) DriverInfoMouse { 26 | return DriverInfoMouse{ 27 | Type: DriverInfoTypeMouse, 28 | DBEntry0: ReadStringList(info.hddb0), 29 | DBEntry1: ReadStringList(info.hddb1), 30 | XF86: C.GoString(info.xf86), 31 | GPM: C.GoString(info.gpm), 32 | Buttons: int(info.buttons), 33 | Wheels: int(info.wheels), 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /pkg/hwinfo/driver_info_x11.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | #include 7 | 8 | // custom getters to get around the problem with bitfields https://github.com/golang/go/issues/43261 9 | bool driver_info_x11_supports_3d(driver_info_x11_t info) { return info.x3d; } 10 | char driver_info_x11_colors_all(driver_info_x11_t info) { return info.colors.all; } 11 | char driver_info_x11_colors_c8(driver_info_x11_t info) { return info.colors.c8; } 12 | char driver_info_x11_colors_c15(driver_info_x11_t info) { return info.colors.c15; } 13 | char driver_info_x11_colors_c16(driver_info_x11_t info) { return info.colors.c16; } 14 | char driver_info_x11_colors_c24(driver_info_x11_t info) { return info.colors.c24; } 15 | char driver_info_x11_colors_c32(driver_info_x11_t info) { return info.colors.c32; } 16 | */ 17 | import "C" 18 | 19 | type DriverInfoX11 struct { 20 | Type DriverInfoType `json:"type,omitempty"` 21 | // actual driver database entries 22 | DBEntry0 []string `json:"db_entry_0,omitempty"` 23 | DBEntry1 []string `json:"db_entry_1,omitempty"` 24 | 25 | Server string `json:"server,omitempty"` // the server/module name 26 | XF86Version string `json:"xf86_version,omitempty"` // XFree86 version (3 or 4) 27 | Supports3D bool `json:"supports_3d"` // has 3D support 28 | Colors struct { 29 | // the next 5 entries combined 30 | All byte `json:"all"` 31 | C8 byte `json:"c8"` 32 | C15 byte `json:"c15"` 33 | C16 byte `json:"c16"` 34 | C24 byte `json:"c24"` 35 | C32 byte `json:"c32"` 36 | } `json:",omitempty"` 37 | DacSpeed uint32 `json:"dac_speed"` // max. ramdac clock 38 | Extensions []string `json:"extensions,omitempty"` // additional X extensions to load ('Module' section) 39 | Options []string `json:"options,omitempty"` // special server options 40 | Raw []string `json:"raw,omitempty"` // extra info to add to XF86Config 41 | Script string `json:"script"` // 3d script to run 42 | } 43 | 44 | func (d DriverInfoX11) DriverInfoType() DriverInfoType { 45 | return DriverInfoTypeX11 46 | } 47 | 48 | func NewDriverInfoX11(info C.driver_info_x11_t) DriverInfoX11 { 49 | result := DriverInfoX11{ 50 | Type: DriverInfoTypeX11, 51 | DBEntry0: ReadStringList(info.hddb0), 52 | DBEntry1: ReadStringList(info.hddb1), 53 | Server: C.GoString(info.server), 54 | XF86Version: C.GoString(info.xf86_ver), 55 | Supports3D: bool(C.driver_info_x11_supports_3d(info)), 56 | DacSpeed: uint32(info.dacspeed), 57 | Extensions: ReadStringList(info.extensions), 58 | Options: ReadStringList(info.options), 59 | Raw: ReadStringList(info.raw), 60 | Script: C.GoString(info.script), 61 | } 62 | 63 | result.Colors.All = byte(C.driver_info_x11_colors_all(info)) 64 | result.Colors.C8 = byte(C.driver_info_x11_colors_c8(info)) 65 | result.Colors.C15 = byte(C.driver_info_x11_colors_c15(info)) 66 | result.Colors.C16 = byte(C.driver_info_x11_colors_c16(info)) 67 | result.Colors.C24 = byte(C.driver_info_x11_colors_c24(info)) 68 | result.Colors.C32 = byte(C.driver_info_x11_colors_c32(info)) 69 | 70 | return result 71 | } 72 | -------------------------------------------------------------------------------- /pkg/hwinfo/hardware_enum_hotplug.go: -------------------------------------------------------------------------------- 1 | // Code generated by "enumer -type=Hotplug -json -transform=snake -trimprefix Hotplug -output=./hardware_enum_hotplug.go"; DO NOT EDIT. 2 | 3 | package hwinfo 4 | 5 | import ( 6 | "encoding/json" 7 | "fmt" 8 | "strings" 9 | ) 10 | 11 | const _HotplugName = "nonepcmciacardbuspciusbfirewire" 12 | 13 | var _HotplugIndex = [...]uint8{0, 4, 10, 17, 20, 23, 31} 14 | 15 | const _HotplugLowerName = "nonepcmciacardbuspciusbfirewire" 16 | 17 | func (i Hotplug) String() string { 18 | if i < 0 || i >= Hotplug(len(_HotplugIndex)-1) { 19 | return fmt.Sprintf("Hotplug(%d)", i) 20 | } 21 | return _HotplugName[_HotplugIndex[i]:_HotplugIndex[i+1]] 22 | } 23 | 24 | // An "invalid array index" compiler error signifies that the constant values have changed. 25 | // Re-run the stringer command to generate them again. 26 | func _HotplugNoOp() { 27 | var x [1]struct{} 28 | _ = x[HotplugNone-(0)] 29 | _ = x[HotplugPcmcia-(1)] 30 | _ = x[HotplugCardbus-(2)] 31 | _ = x[HotplugPci-(3)] 32 | _ = x[HotplugUsb-(4)] 33 | _ = x[HotplugFirewire-(5)] 34 | } 35 | 36 | var _HotplugValues = []Hotplug{HotplugNone, HotplugPcmcia, HotplugCardbus, HotplugPci, HotplugUsb, HotplugFirewire} 37 | 38 | var _HotplugNameToValueMap = map[string]Hotplug{ 39 | _HotplugName[0:4]: HotplugNone, 40 | _HotplugLowerName[0:4]: HotplugNone, 41 | _HotplugName[4:10]: HotplugPcmcia, 42 | _HotplugLowerName[4:10]: HotplugPcmcia, 43 | _HotplugName[10:17]: HotplugCardbus, 44 | _HotplugLowerName[10:17]: HotplugCardbus, 45 | _HotplugName[17:20]: HotplugPci, 46 | _HotplugLowerName[17:20]: HotplugPci, 47 | _HotplugName[20:23]: HotplugUsb, 48 | _HotplugLowerName[20:23]: HotplugUsb, 49 | _HotplugName[23:31]: HotplugFirewire, 50 | _HotplugLowerName[23:31]: HotplugFirewire, 51 | } 52 | 53 | var _HotplugNames = []string{ 54 | _HotplugName[0:4], 55 | _HotplugName[4:10], 56 | _HotplugName[10:17], 57 | _HotplugName[17:20], 58 | _HotplugName[20:23], 59 | _HotplugName[23:31], 60 | } 61 | 62 | // HotplugString retrieves an enum value from the enum constants string name. 63 | // Throws an error if the param is not part of the enum. 64 | func HotplugString(s string) (Hotplug, error) { 65 | if val, ok := _HotplugNameToValueMap[s]; ok { 66 | return val, nil 67 | } 68 | 69 | if val, ok := _HotplugNameToValueMap[strings.ToLower(s)]; ok { 70 | return val, nil 71 | } 72 | return 0, fmt.Errorf("%s does not belong to Hotplug values", s) 73 | } 74 | 75 | // HotplugValues returns all values of the enum 76 | func HotplugValues() []Hotplug { 77 | return _HotplugValues 78 | } 79 | 80 | // HotplugStrings returns a slice of all String values of the enum 81 | func HotplugStrings() []string { 82 | strs := make([]string, len(_HotplugNames)) 83 | copy(strs, _HotplugNames) 84 | return strs 85 | } 86 | 87 | // IsAHotplug returns "true" if the value is listed in the enum definition. "false" otherwise 88 | func (i Hotplug) IsAHotplug() bool { 89 | for _, v := range _HotplugValues { 90 | if i == v { 91 | return true 92 | } 93 | } 94 | return false 95 | } 96 | 97 | // MarshalJSON implements the json.Marshaler interface for Hotplug 98 | func (i Hotplug) MarshalJSON() ([]byte, error) { 99 | return json.Marshal(i.String()) 100 | } 101 | 102 | // UnmarshalJSON implements the json.Unmarshaler interface for Hotplug 103 | func (i *Hotplug) UnmarshalJSON(data []byte) error { 104 | var s string 105 | if err := json.Unmarshal(data, &s); err != nil { 106 | return fmt.Errorf("Hotplug should be a string, got %s", data) 107 | } 108 | 109 | var err error 110 | *i, err = HotplugString(s) 111 | return err 112 | } 113 | -------------------------------------------------------------------------------- /pkg/hwinfo/hardware_enum_sub_class_keyboard.go: -------------------------------------------------------------------------------- 1 | // Code generated by "enumer -type=SubClassKeyboard -json -transform=snake -trimprefix SubClassKeyboard -output=./hardware_enum_sub_class_keyboard.go"; DO NOT EDIT. 2 | 3 | package hwinfo 4 | 5 | import ( 6 | "encoding/json" 7 | "fmt" 8 | "strings" 9 | ) 10 | 11 | const _SubClassKeyboardName = "kbdconsole" 12 | 13 | var _SubClassKeyboardIndex = [...]uint8{0, 3, 10} 14 | 15 | const _SubClassKeyboardLowerName = "kbdconsole" 16 | 17 | func (i SubClassKeyboard) String() string { 18 | if i >= SubClassKeyboard(len(_SubClassKeyboardIndex)-1) { 19 | return fmt.Sprintf("SubClassKeyboard(%d)", i) 20 | } 21 | return _SubClassKeyboardName[_SubClassKeyboardIndex[i]:_SubClassKeyboardIndex[i+1]] 22 | } 23 | 24 | // An "invalid array index" compiler error signifies that the constant values have changed. 25 | // Re-run the stringer command to generate them again. 26 | func _SubClassKeyboardNoOp() { 27 | var x [1]struct{} 28 | _ = x[SubClassKeyboardKbd-(0)] 29 | _ = x[SubClassKeyboardConsole-(1)] 30 | } 31 | 32 | var _SubClassKeyboardValues = []SubClassKeyboard{SubClassKeyboardKbd, SubClassKeyboardConsole} 33 | 34 | var _SubClassKeyboardNameToValueMap = map[string]SubClassKeyboard{ 35 | _SubClassKeyboardName[0:3]: SubClassKeyboardKbd, 36 | _SubClassKeyboardLowerName[0:3]: SubClassKeyboardKbd, 37 | _SubClassKeyboardName[3:10]: SubClassKeyboardConsole, 38 | _SubClassKeyboardLowerName[3:10]: SubClassKeyboardConsole, 39 | } 40 | 41 | var _SubClassKeyboardNames = []string{ 42 | _SubClassKeyboardName[0:3], 43 | _SubClassKeyboardName[3:10], 44 | } 45 | 46 | // SubClassKeyboardString retrieves an enum value from the enum constants string name. 47 | // Throws an error if the param is not part of the enum. 48 | func SubClassKeyboardString(s string) (SubClassKeyboard, error) { 49 | if val, ok := _SubClassKeyboardNameToValueMap[s]; ok { 50 | return val, nil 51 | } 52 | 53 | if val, ok := _SubClassKeyboardNameToValueMap[strings.ToLower(s)]; ok { 54 | return val, nil 55 | } 56 | return 0, fmt.Errorf("%s does not belong to SubClassKeyboard values", s) 57 | } 58 | 59 | // SubClassKeyboardValues returns all values of the enum 60 | func SubClassKeyboardValues() []SubClassKeyboard { 61 | return _SubClassKeyboardValues 62 | } 63 | 64 | // SubClassKeyboardStrings returns a slice of all String values of the enum 65 | func SubClassKeyboardStrings() []string { 66 | strs := make([]string, len(_SubClassKeyboardNames)) 67 | copy(strs, _SubClassKeyboardNames) 68 | return strs 69 | } 70 | 71 | // IsASubClassKeyboard returns "true" if the value is listed in the enum definition. "false" otherwise 72 | func (i SubClassKeyboard) IsASubClassKeyboard() bool { 73 | for _, v := range _SubClassKeyboardValues { 74 | if i == v { 75 | return true 76 | } 77 | } 78 | return false 79 | } 80 | 81 | // MarshalJSON implements the json.Marshaler interface for SubClassKeyboard 82 | func (i SubClassKeyboard) MarshalJSON() ([]byte, error) { 83 | return json.Marshal(i.String()) 84 | } 85 | 86 | // UnmarshalJSON implements the json.Unmarshaler interface for SubClassKeyboard 87 | func (i *SubClassKeyboard) UnmarshalJSON(data []byte) error { 88 | var s string 89 | if err := json.Unmarshal(data, &s); err != nil { 90 | return fmt.Errorf("SubClassKeyboard should be a string, got %s", data) 91 | } 92 | 93 | var err error 94 | *i, err = SubClassKeyboardString(s) 95 | return err 96 | } 97 | -------------------------------------------------------------------------------- /pkg/hwinfo/hardware_enum_sub_class_mouse.go: -------------------------------------------------------------------------------- 1 | // Code generated by "enumer -type=SubClassMouse -json -transform=snake -trimprefix SubClassMouse -output=./hardware_enum_sub_class_mouse.go"; DO NOT EDIT. 2 | 3 | package hwinfo 4 | 5 | import ( 6 | "encoding/json" 7 | "fmt" 8 | "strings" 9 | ) 10 | 11 | const ( 12 | _SubClassMouseName_0 = "ps2serbususbsun" 13 | _SubClassMouseLowerName_0 = "ps2serbususbsun" 14 | _SubClassMouseName_1 = "other" 15 | _SubClassMouseLowerName_1 = "other" 16 | ) 17 | 18 | var ( 19 | _SubClassMouseIndex_0 = [...]uint8{0, 3, 6, 9, 12, 15} 20 | _SubClassMouseIndex_1 = [...]uint8{0, 5} 21 | ) 22 | 23 | func (i SubClassMouse) String() string { 24 | switch { 25 | case 0 <= i && i <= 4: 26 | return _SubClassMouseName_0[_SubClassMouseIndex_0[i]:_SubClassMouseIndex_0[i+1]] 27 | case i == 128: 28 | return _SubClassMouseName_1 29 | default: 30 | return fmt.Sprintf("SubClassMouse(%d)", i) 31 | } 32 | } 33 | 34 | // An "invalid array index" compiler error signifies that the constant values have changed. 35 | // Re-run the stringer command to generate them again. 36 | func _SubClassMouseNoOp() { 37 | var x [1]struct{} 38 | _ = x[SubClassMousePs2-(0)] 39 | _ = x[SubClassMouseSer-(1)] 40 | _ = x[SubClassMouseBus-(2)] 41 | _ = x[SubClassMouseUsb-(3)] 42 | _ = x[SubClassMouseSun-(4)] 43 | _ = x[SubClassMouseOther-(128)] 44 | } 45 | 46 | var _SubClassMouseValues = []SubClassMouse{SubClassMousePs2, SubClassMouseSer, SubClassMouseBus, SubClassMouseUsb, SubClassMouseSun, SubClassMouseOther} 47 | 48 | var _SubClassMouseNameToValueMap = map[string]SubClassMouse{ 49 | _SubClassMouseName_0[0:3]: SubClassMousePs2, 50 | _SubClassMouseLowerName_0[0:3]: SubClassMousePs2, 51 | _SubClassMouseName_0[3:6]: SubClassMouseSer, 52 | _SubClassMouseLowerName_0[3:6]: SubClassMouseSer, 53 | _SubClassMouseName_0[6:9]: SubClassMouseBus, 54 | _SubClassMouseLowerName_0[6:9]: SubClassMouseBus, 55 | _SubClassMouseName_0[9:12]: SubClassMouseUsb, 56 | _SubClassMouseLowerName_0[9:12]: SubClassMouseUsb, 57 | _SubClassMouseName_0[12:15]: SubClassMouseSun, 58 | _SubClassMouseLowerName_0[12:15]: SubClassMouseSun, 59 | _SubClassMouseName_1[0:5]: SubClassMouseOther, 60 | _SubClassMouseLowerName_1[0:5]: SubClassMouseOther, 61 | } 62 | 63 | var _SubClassMouseNames = []string{ 64 | _SubClassMouseName_0[0:3], 65 | _SubClassMouseName_0[3:6], 66 | _SubClassMouseName_0[6:9], 67 | _SubClassMouseName_0[9:12], 68 | _SubClassMouseName_0[12:15], 69 | _SubClassMouseName_1[0:5], 70 | } 71 | 72 | // SubClassMouseString retrieves an enum value from the enum constants string name. 73 | // Throws an error if the param is not part of the enum. 74 | func SubClassMouseString(s string) (SubClassMouse, error) { 75 | if val, ok := _SubClassMouseNameToValueMap[s]; ok { 76 | return val, nil 77 | } 78 | 79 | if val, ok := _SubClassMouseNameToValueMap[strings.ToLower(s)]; ok { 80 | return val, nil 81 | } 82 | return 0, fmt.Errorf("%s does not belong to SubClassMouse values", s) 83 | } 84 | 85 | // SubClassMouseValues returns all values of the enum 86 | func SubClassMouseValues() []SubClassMouse { 87 | return _SubClassMouseValues 88 | } 89 | 90 | // SubClassMouseStrings returns a slice of all String values of the enum 91 | func SubClassMouseStrings() []string { 92 | strs := make([]string, len(_SubClassMouseNames)) 93 | copy(strs, _SubClassMouseNames) 94 | return strs 95 | } 96 | 97 | // IsASubClassMouse returns "true" if the value is listed in the enum definition. "false" otherwise 98 | func (i SubClassMouse) IsASubClassMouse() bool { 99 | for _, v := range _SubClassMouseValues { 100 | if i == v { 101 | return true 102 | } 103 | } 104 | return false 105 | } 106 | 107 | // MarshalJSON implements the json.Marshaler interface for SubClassMouse 108 | func (i SubClassMouse) MarshalJSON() ([]byte, error) { 109 | return json.Marshal(i.String()) 110 | } 111 | 112 | // UnmarshalJSON implements the json.Unmarshaler interface for SubClassMouse 113 | func (i *SubClassMouse) UnmarshalJSON(data []byte) error { 114 | var s string 115 | if err := json.Unmarshal(data, &s); err != nil { 116 | return fmt.Errorf("SubClassMouse should be a string, got %s", data) 117 | } 118 | 119 | var err error 120 | *i, err = SubClassMouseString(s) 121 | return err 122 | } 123 | -------------------------------------------------------------------------------- /pkg/hwinfo/hwinfo.go: -------------------------------------------------------------------------------- 1 | // Package hwinfo provides functionality for scanning hardware devices and reading SMBIOS information. 2 | // It does this using the [hwinfo] C library. 3 | // 4 | // [hwinfo]: https://github.com/numtide/hwinfo 5 | package hwinfo 6 | 7 | /* 8 | #cgo pkg-config: hwinfo 9 | #include 10 | #include 11 | 12 | // CGO cannot access union type fields, so we do this as a workaround 13 | hd_smbios_t* hd_smbios_next(hd_smbios_t *sm) { return sm->next; } 14 | */ 15 | import "C" 16 | 17 | import ( 18 | "unsafe" 19 | ) 20 | 21 | func excludeDevice(item *HardwareDevice) bool { 22 | if item.Class == HardwareClassNetworkInterface { 23 | for _, driver := range item.Drivers { 24 | // devices that are not mapped to hardware should be not included in the hardware report 25 | if virtualNetworkDevices[driver] { 26 | return true 27 | } 28 | } 29 | } 30 | return false 31 | } 32 | 33 | // Scan returns a list of SMBIOS entries and detected hardware devices based on the provided probe features. 34 | func Scan(probes []ProbeFeature, ephemeral bool) ([]Smbios, []HardwareDevice, error) { 35 | // initialise the struct to hold scan data 36 | data := (*C.hd_data_t)(C.calloc(1, C.size_t(unsafe.Sizeof(C.hd_data_t{})))) 37 | 38 | // ProbeFeatureInt needs to always be set, otherwise we don't get pci and usb vendor id lookups. 39 | // https://github.com/openSUSE/hwinfo/blob/c87f449f1d4882c71b0a1e6dc80638224a5baeed/src/hd/hd.c#L597-L605 40 | C.hd_set_probe_feature(data, C.enum_probe_feature(ProbeFeatureInt)) 41 | 42 | // set the hardware probes to run 43 | for _, probe := range probes { 44 | C.hd_set_probe_feature(data, C.enum_probe_feature(probe)) 45 | } 46 | 47 | // scan 48 | C.hd_scan(data) 49 | defer C.hd_free_hd_data(data) 50 | 51 | var smbiosItems []Smbios 52 | for sm := data.smbios; sm != nil; sm = C.hd_smbios_next(sm) { 53 | item, err := NewSmbios(sm) 54 | if err != nil { 55 | return nil, nil, err 56 | } else if item == nil { 57 | continue 58 | } 59 | smbiosItems = append(smbiosItems, item) 60 | } 61 | 62 | var hardwareItems []HardwareDevice 63 | var deviceIdx uint16 64 | for hd := data.hd; hd != nil; hd = hd.next { 65 | item, err := NewHardwareDevice(hd, ephemeral) 66 | if err != nil { 67 | return nil, nil, err 68 | } 69 | 70 | if item.Index > deviceIdx { 71 | deviceIdx = item.Index 72 | } 73 | 74 | if excludeDevice(item) { 75 | continue 76 | } 77 | 78 | hardwareItems = append(hardwareItems, *item) 79 | } 80 | 81 | // probe for additional inputs that hwinfo does not support 82 | touchpads, err := captureTouchpads(deviceIdx + 1) 83 | if err != nil { 84 | return nil, nil, err 85 | } 86 | 87 | hardwareItems = append(hardwareItems, touchpads...) 88 | 89 | return smbiosItems, hardwareItems, nil 90 | } 91 | -------------------------------------------------------------------------------- /pkg/hwinfo/id.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | 9 | import ( 10 | "encoding/json" 11 | "fmt" 12 | "slices" 13 | ) 14 | 15 | //go:generate enumer -type=IDTag -json -transform=snake -trimprefix IDTag -output=./id_tag_enum.go 16 | type IDTag byte //nolint:recvcheck 17 | 18 | const ( 19 | IDTagPci IDTag = iota + 1 20 | IDTagEisa 21 | IDTagUsb 22 | IDTagSpecial 23 | IDTagPcmcia 24 | IDTagSdio 25 | ) 26 | 27 | type ID struct { 28 | Type IDTag 29 | Value uint16 30 | // Name (if any) 31 | Name string 32 | } 33 | 34 | type idJSON struct { 35 | Hex string `json:"hex,omitempty"` 36 | Name string `json:"name,omitempty"` 37 | Value uint16 `json:"value"` 38 | } 39 | 40 | func (i ID) MarshalJSON() ([]byte, error) { 41 | var ( 42 | b []byte 43 | err error 44 | ) 45 | 46 | switch i.Type { 47 | 48 | case IDTagSpecial: 49 | b, err = json.Marshal(i.Name) 50 | 51 | case 0, IDTagPci, IDTagEisa, IDTagUsb, IDTagPcmcia, IDTagSdio: 52 | b, err = json.Marshal(idJSON{ 53 | Hex: fmt.Sprintf("%04x", i.Value), 54 | Name: i.Name, 55 | Value: i.Value, 56 | }) 57 | 58 | default: 59 | err = fmt.Errorf("unknown id type %d", i.Type) 60 | } 61 | 62 | if err != nil { 63 | err = fmt.Errorf("failed to marshal id %s: %w", i, err) 64 | } 65 | 66 | return b, err 67 | } 68 | 69 | func (i ID) IsEmpty() bool { 70 | return i.Type == 0 && i.Value == 0 && (i.Name == "" || i.Name == "None") 71 | } 72 | 73 | func (i ID) String() string { 74 | return fmt.Sprintf("%d:%s", i.Value, i.Name) 75 | } 76 | 77 | func (i ID) Is(ids ...uint16) bool { 78 | return slices.Contains(ids, i.Value) 79 | } 80 | 81 | func NewID(id C.hd_id_t) *ID { 82 | result := ID{ 83 | /* 84 | ID is actually a combination of some tag to differentiate the various id types and the real id. 85 | We do the same thing as the ID_VALUE macro in hd.h to get the true value. 86 | */ 87 | Type: IDTag((id.id >> 16) & 0xf), 88 | Value: uint16(id.id), 89 | Name: C.GoString(id.name), 90 | } 91 | if result.IsEmpty() { 92 | return nil 93 | } 94 | return &result 95 | } 96 | 97 | func NewBusID(bus Bus) *ID { 98 | return &ID{ 99 | Name: bus.String(), 100 | Value: uint16(bus), 101 | } 102 | } 103 | 104 | func NewBaseClassID(bc BaseClass) *ID { 105 | return &ID{ 106 | Name: bc.String(), 107 | Value: uint16(bc), 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /pkg/hwinfo/id_tag_enum.go: -------------------------------------------------------------------------------- 1 | // Code generated by "enumer -type=IDTag -json -transform=snake -trimprefix IDTag -output=./id_tag_enum.go"; DO NOT EDIT. 2 | 3 | package hwinfo 4 | 5 | import ( 6 | "encoding/json" 7 | "fmt" 8 | "strings" 9 | ) 10 | 11 | const _IDTagName = "pcieisausbspecialpcmciasdio" 12 | 13 | var _IDTagIndex = [...]uint8{0, 3, 7, 10, 17, 23, 27} 14 | 15 | const _IDTagLowerName = "pcieisausbspecialpcmciasdio" 16 | 17 | func (i IDTag) String() string { 18 | i -= 1 19 | if i >= IDTag(len(_IDTagIndex)-1) { 20 | return fmt.Sprintf("IDTag(%d)", i+1) 21 | } 22 | return _IDTagName[_IDTagIndex[i]:_IDTagIndex[i+1]] 23 | } 24 | 25 | // An "invalid array index" compiler error signifies that the constant values have changed. 26 | // Re-run the stringer command to generate them again. 27 | func _IDTagNoOp() { 28 | var x [1]struct{} 29 | _ = x[IDTagPci-(1)] 30 | _ = x[IDTagEisa-(2)] 31 | _ = x[IDTagUsb-(3)] 32 | _ = x[IDTagSpecial-(4)] 33 | _ = x[IDTagPcmcia-(5)] 34 | _ = x[IDTagSdio-(6)] 35 | } 36 | 37 | var _IDTagValues = []IDTag{IDTagPci, IDTagEisa, IDTagUsb, IDTagSpecial, IDTagPcmcia, IDTagSdio} 38 | 39 | var _IDTagNameToValueMap = map[string]IDTag{ 40 | _IDTagName[0:3]: IDTagPci, 41 | _IDTagLowerName[0:3]: IDTagPci, 42 | _IDTagName[3:7]: IDTagEisa, 43 | _IDTagLowerName[3:7]: IDTagEisa, 44 | _IDTagName[7:10]: IDTagUsb, 45 | _IDTagLowerName[7:10]: IDTagUsb, 46 | _IDTagName[10:17]: IDTagSpecial, 47 | _IDTagLowerName[10:17]: IDTagSpecial, 48 | _IDTagName[17:23]: IDTagPcmcia, 49 | _IDTagLowerName[17:23]: IDTagPcmcia, 50 | _IDTagName[23:27]: IDTagSdio, 51 | _IDTagLowerName[23:27]: IDTagSdio, 52 | } 53 | 54 | var _IDTagNames = []string{ 55 | _IDTagName[0:3], 56 | _IDTagName[3:7], 57 | _IDTagName[7:10], 58 | _IDTagName[10:17], 59 | _IDTagName[17:23], 60 | _IDTagName[23:27], 61 | } 62 | 63 | // IDTagString retrieves an enum value from the enum constants string name. 64 | // Throws an error if the param is not part of the enum. 65 | func IDTagString(s string) (IDTag, error) { 66 | if val, ok := _IDTagNameToValueMap[s]; ok { 67 | return val, nil 68 | } 69 | 70 | if val, ok := _IDTagNameToValueMap[strings.ToLower(s)]; ok { 71 | return val, nil 72 | } 73 | return 0, fmt.Errorf("%s does not belong to IDTag values", s) 74 | } 75 | 76 | // IDTagValues returns all values of the enum 77 | func IDTagValues() []IDTag { 78 | return _IDTagValues 79 | } 80 | 81 | // IDTagStrings returns a slice of all String values of the enum 82 | func IDTagStrings() []string { 83 | strs := make([]string, len(_IDTagNames)) 84 | copy(strs, _IDTagNames) 85 | return strs 86 | } 87 | 88 | // IsAIDTag returns "true" if the value is listed in the enum definition. "false" otherwise 89 | func (i IDTag) IsAIDTag() bool { 90 | for _, v := range _IDTagValues { 91 | if i == v { 92 | return true 93 | } 94 | } 95 | return false 96 | } 97 | 98 | // MarshalJSON implements the json.Marshaler interface for IDTag 99 | func (i IDTag) MarshalJSON() ([]byte, error) { 100 | return json.Marshal(i.String()) 101 | } 102 | 103 | // UnmarshalJSON implements the json.Unmarshaler interface for IDTag 104 | func (i *IDTag) UnmarshalJSON(data []byte) error { 105 | var s string 106 | if err := json.Unmarshal(data, &s); err != nil { 107 | return fmt.Errorf("IDTag should be a string, got %s", data) 108 | } 109 | 110 | var err error 111 | *i, err = IDTagString(s) 112 | return err 113 | } 114 | -------------------------------------------------------------------------------- /pkg/hwinfo/input.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log/slog" 7 | 8 | "github.com/numtide/nixos-facter/pkg/linux/input" 9 | "github.com/numtide/nixos-facter/pkg/udev" 10 | ) 11 | 12 | // captureTouchpads scans the input devices and identifies touchpads, returning a slice of HardwareDevice structs or an 13 | // error. 14 | // It accepts a deviceIdx to ensure it continues on from the last device index generated by hwinfo. 15 | func captureTouchpads(deviceIdx uint16) ([]HardwareDevice, error) { 16 | inputDevices, err := input.ReadDevices(nil) 17 | if err != nil { 18 | return nil, fmt.Errorf("failed to read input devices: %w", err) 19 | } 20 | 21 | var result []HardwareDevice //nolint:prealloc 22 | 23 | for _, inputDevice := range inputDevices { 24 | path := "/sys" + inputDevice.Sysfs 25 | 26 | udevData, err := udev.Read(path) 27 | if errors.Is(err, udev.ErrNotFound) { 28 | slog.Warn("udev data not found", "name", inputDevice.Name, "sysfs", inputDevice.Sysfs) 29 | continue 30 | } else if err != nil { 31 | return nil, fmt.Errorf("failed to fetch udev data for device %q with udev data: %w", path, err) 32 | } 33 | 34 | if udevData.Input == nil { 35 | slog.Debug("udev data missing input", "name", inputDevice.Name, "sysfs", inputDevice.Sysfs) 36 | continue 37 | } 38 | 39 | if !udevData.Input.IsTouchpad { 40 | // currently, we are only interested in touchpads, as hwinfo does not capture them 41 | // eventually, we may take over more of the input processing that hwinfo performs 42 | continue 43 | } 44 | 45 | if len(inputDevice.Handlers) == 0 { 46 | // I believe this shouldn't be possible, and if it does occur, we should error 47 | return nil, fmt.Errorf("no handlers found for input device %s", inputDevice.Sysfs) 48 | } 49 | 50 | // create a hardware entry for the report 51 | hd := HardwareDevice{ 52 | // todo AttachedTo: it's unclear how to work this out 53 | Class: HardwareClassMouse, 54 | BaseClass: NewBaseClassID(BaseClassTouchpad), 55 | Vendor: &ID{ 56 | Name: udevData.Vendor, 57 | Value: inputDevice.Vendor, 58 | }, 59 | Device: &ID{ 60 | Name: udevData.Model, 61 | Value: inputDevice.Product, 62 | }, 63 | SysfsID: inputDevice.Sysfs, 64 | } 65 | 66 | switch inputDevice.Bus { 67 | case input.BusI2c: 68 | hd.BusType = NewBusID(BusSerial) 69 | hd.SubClass = &ID{ 70 | Name: SubClassMouseBus.String(), 71 | Value: uint16(SubClassMouseSer), 72 | } 73 | 74 | case input.BusUsb: 75 | hd.BusType = NewBusID(BusUsb) 76 | hd.SubClass = &ID{ 77 | Name: SubClassMouseUsb.String(), 78 | Value: uint16(SubClassMouseUsb), 79 | } 80 | case input.BusI8042: 81 | hd.BusType = NewBusID(BusPs2) 82 | hd.SubClass = &ID{ 83 | Name: SubClassMousePs2.String(), 84 | Value: uint16(SubClassMousePs2), 85 | } 86 | 87 | case input.BusRmi: 88 | // RMI is a protocol which runs over other physical buses, typically i2c, but it can also be over others 89 | // such as USB or SPI. 90 | // I'm not sure how to map this into hwinfo's bus classification, so for now we will use other for both the 91 | // bus and mouse subclass. 92 | hd.BusType = NewBusID(BusOther) 93 | hd.SubClass = &ID{ 94 | Name: SubClassMouseOther.String(), 95 | Value: uint16(SubClassMouseOther), 96 | } 97 | 98 | case input.BusPci, 99 | input.BusIsapnp, 100 | input.BusHil, 101 | input.BusBluetooth, 102 | input.BusVirtual, 103 | input.BusIsa, 104 | input.BusXtkbd, 105 | input.BusRs232, 106 | input.BusGameport, 107 | input.BusParport, 108 | input.BusAmiga, 109 | input.BusAdb, 110 | input.BusHost, 111 | input.BusGsc, 112 | input.BusAtari, 113 | input.BusSpi, 114 | input.BusCec, 115 | input.BusIntelIshtp, 116 | input.BusAmdSfh: 117 | // todo unsure if touchpads can be on any other bus type 118 | return nil, fmt.Errorf("unsupported bus type: %s", inputDevice.Bus) 119 | } 120 | 121 | // todo should we error if no event handler is found? 122 | if handler := inputDevice.EventHandler(); handler != "" { 123 | hd.UnixDeviceNames = append(hd.UnixDeviceNames, "/dev/input/"+handler) 124 | } 125 | 126 | if handler := inputDevice.MouseHandler(); handler != "" { 127 | hd.UnixDeviceNames = append(hd.UnixDeviceNames, "/dev/input/ + handler") 128 | } 129 | 130 | hd.Index = deviceIdx 131 | result = append(result, hd) 132 | 133 | deviceIdx++ 134 | } 135 | 136 | return result, nil 137 | } 138 | -------------------------------------------------------------------------------- /pkg/hwinfo/iommu.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "strconv" 8 | ) 9 | 10 | const ( 11 | iommuSysfsPath = "/sys/kernel/iommu_groups" 12 | ) 13 | 14 | type IOMMUGroup int 15 | 16 | func IOMMUGroups() (map[string]IOMMUGroup, error) { 17 | groups, err := os.ReadDir(iommuSysfsPath) 18 | if err != nil { 19 | return nil, fmt.Errorf("failed to read iommu groups from sysfs: %w", err) 20 | } 21 | 22 | result := make(map[string]IOMMUGroup) 23 | 24 | for _, group := range groups { 25 | if !group.IsDir() { 26 | return nil, fmt.Errorf("non-directory entry found in %s: %s", iommuSysfsPath, group.Name()) 27 | } 28 | 29 | groupID, err := strconv.Atoi(group.Name()) 30 | if err != nil { 31 | return nil, fmt.Errorf("failed to parse iommu group id '%s': %w", group.Name(), err) 32 | } 33 | 34 | devicesPath := filepath.Join(iommuSysfsPath, group.Name(), "devices") 35 | 36 | devices, err := os.ReadDir(devicesPath) 37 | if err != nil { 38 | return nil, fmt.Errorf("failed to read devices from iommu group %s: %w", devicesPath, err) 39 | } 40 | 41 | for _, device := range devices { 42 | devicePath := filepath.Join(devicesPath, device.Name()) 43 | 44 | resolvedPath, err := filepath.EvalSymlinks(devicePath) 45 | if err != nil { 46 | return nil, fmt.Errorf("failed to resolve device symlink '%s': %w", devicePath, err) 47 | } 48 | 49 | // sysfs id -> iommu group id 50 | result[resolvedPath[4:]] = IOMMUGroup(groupID) 51 | } 52 | } 53 | 54 | return result, nil 55 | } 56 | -------------------------------------------------------------------------------- /pkg/hwinfo/resource.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | 7 | // CGO cannot access union type fields, so we do this as a workaround 8 | hd_res_t *hd_res_next(hd_res_t *res) { return res->next; } 9 | hd_resource_types_t hd_res_get_type(hd_res_t *res) { return res->any.type; } 10 | res_any_t hd_res_get_any(hd_res_t *res) { return res->any; } 11 | */ 12 | import "C" 13 | 14 | import ( 15 | "bytes" 16 | "encoding/json" 17 | "errors" 18 | "fmt" 19 | "log" 20 | "slices" 21 | ) 22 | 23 | //go:generate enumer -type=ResourceType -json -transform=snake -trimprefix ResourceType -output=./resource_enum_type.go 24 | type ResourceType uint //nolint:recvcheck 25 | 26 | const ( 27 | ResourceTypeAny ResourceType = iota 28 | ResourceTypePhysMem 29 | ResourceTypeMem 30 | ResourceTypeIo 31 | ResourceTypeIrq 32 | ResourceTypeDma 33 | ResourceTypeMonitor 34 | 35 | ResourceTypeSize 36 | ResourceTypeDiskGeo 37 | ResourceTypeCache 38 | ResourceTypeBaud 39 | ResourceTypeInitStrings 40 | ResourceTypePppdOption 41 | 42 | ResourceTypeFramebuffer 43 | ResourceTypeHwaddr 44 | ResourceTypeLink 45 | ResourceTypeWlan 46 | ResourceTypeFc 47 | ResourceTypePhwaddr 48 | ) 49 | 50 | //nolint:ireturn 51 | func NewResource(res *C.hd_res_t) (Resource, error) { 52 | if res == nil { 53 | return nil, errors.New("resource is nil") 54 | } 55 | 56 | var ( 57 | err error 58 | result Resource 59 | ) 60 | 61 | resourceType := ResourceType(C.hd_res_get_type(res)) 62 | 63 | switch resourceType { 64 | case ResourceTypeFc: 65 | result, err = NewResourceFc(res, resourceType) 66 | case ResourceTypePhysMem: 67 | result, err = NewResourcePhysicalMemory(res, resourceType) 68 | case ResourceTypeMem: 69 | result, err = NewResourceMemory(res, resourceType) 70 | case ResourceTypeIo: 71 | result, err = NewResourceIO(res, resourceType) 72 | case ResourceTypeIrq: 73 | result, err = NewResourceIrq(res, resourceType) 74 | case ResourceTypeDma: 75 | result, err = NewResourceDma(res, resourceType) 76 | case ResourceTypeMonitor: 77 | result, err = NewResourceMonitor(res, resourceType) 78 | case ResourceTypeSize: 79 | result, err = NewResourceSize(res, resourceType) 80 | case ResourceTypeDiskGeo: 81 | result, err = NewResourceDiskGeo(res, resourceType) 82 | case ResourceTypeCache: 83 | result, err = NewResourceCache(res, resourceType) 84 | case ResourceTypeBaud: 85 | result, err = NewResourceBaud(res, resourceType) 86 | case ResourceTypeInitStrings: 87 | result, err = NewResourceInitStrings(res, resourceType) 88 | case ResourceTypePppdOption: 89 | result, err = NewResourcePppdOption(res, resourceType) 90 | case ResourceTypeFramebuffer: 91 | result, err = NewResourceFrameBuffer(res, resourceType) 92 | case ResourceTypeHwaddr, ResourceTypePhwaddr: 93 | result, err = NewResourceHardwareAddress(res, resourceType) 94 | case ResourceTypeLink: 95 | // this is the link status of a network interface and can change when we plug/unplug a cable 96 | case ResourceTypeWlan: 97 | result, err = NewResourceWlan(res, resourceType) 98 | default: 99 | err = fmt.Errorf("unexpected resource type: %v", resourceType) 100 | } 101 | 102 | return result, err 103 | } 104 | 105 | func NewResources(hd *C.hd_t) ([]Resource, error) { 106 | var result []Resource 107 | for res := hd.res; res != nil; res = C.hd_res_next(res) { 108 | resource, err := NewResource(res) 109 | if err != nil { 110 | return nil, err 111 | } 112 | if resource == nil { 113 | continue 114 | } 115 | result = append(result, resource) 116 | } 117 | 118 | slices.SortFunc(result, func(a, b Resource) int { 119 | // We don't really care about a proper ordering for resources, just a stable sort that is reasonably quick. 120 | var err error 121 | jsonA, err := json.Marshal(a) 122 | if err != nil { 123 | log.Panicf("failed to marshal resource: %s", err) 124 | } 125 | jsonB, err := json.Marshal(b) 126 | if err != nil { 127 | log.Panicf("failed to marshal resource: %s", err) 128 | } 129 | 130 | return bytes.Compare(jsonA, jsonB) 131 | }) 132 | 133 | return result, nil 134 | } 135 | -------------------------------------------------------------------------------- /pkg/hwinfo/resource_baud.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | 7 | // CGO cannot access union type fields, so we do this as a workaround 8 | res_baud_t hd_res_get_baud(hd_res_t *res) { return res->baud; } 9 | */ 10 | import "C" 11 | 12 | import ( 13 | "errors" 14 | "fmt" 15 | ) 16 | 17 | type ResourceBaud struct { 18 | Type ResourceType `json:"type"` 19 | Speed uint32 `json:"speed"` 20 | Bits uint32 `json:"bits"` 21 | StopBits uint32 `json:"stop_bits"` 22 | Parity byte `json:"parity"` 23 | Handshake byte `json:"handshake"` 24 | } 25 | 26 | func (r ResourceBaud) ResourceType() ResourceType { 27 | return ResourceTypeBaud 28 | } 29 | 30 | func NewResourceBaud(res *C.hd_res_t, resType ResourceType) (*ResourceBaud, error) { 31 | if res == nil { 32 | return nil, errors.New("res is nil") 33 | } 34 | 35 | if resType != ResourceTypeBaud { 36 | return nil, fmt.Errorf("expected resource type '%s', found '%s'", ResourceTypeBaud, resType) 37 | } 38 | 39 | baud := C.hd_res_get_baud(res) 40 | 41 | return &ResourceBaud{ 42 | Type: resType, 43 | Speed: uint32(baud.speed), 44 | Bits: uint32(baud.bits), 45 | StopBits: uint32(baud.stopbits), 46 | Parity: byte(baud.parity), 47 | Handshake: byte(baud.handshake), 48 | }, nil 49 | } 50 | -------------------------------------------------------------------------------- /pkg/hwinfo/resource_cache.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | 7 | // CGO cannot access union type fields, so we do this as a workaround 8 | res_cache_t hd_res_get_cache(hd_res_t *res) { return res->cache; } 9 | */ 10 | import "C" 11 | 12 | import ( 13 | "errors" 14 | "fmt" 15 | ) 16 | 17 | type ResourceCache struct { 18 | Type ResourceType `json:"type"` 19 | Size string `json:"size"` 20 | } 21 | 22 | func (r ResourceCache) ResourceType() ResourceType { 23 | return ResourceTypeCache 24 | } 25 | 26 | func NewResourceCache(res *C.hd_res_t, resType ResourceType) (*ResourceCache, error) { 27 | if res == nil { 28 | return nil, errors.New("res is nil") 29 | } 30 | 31 | if resType != ResourceTypeCache { 32 | return nil, fmt.Errorf("expected resource type '%s', found '%s'", ResourceTypeCache, resType) 33 | } 34 | 35 | cache := C.hd_res_get_cache(res) 36 | 37 | return &ResourceCache{ 38 | Type: resType, 39 | Size: fmt.Sprintf("0x%x", uint(cache.size)), 40 | }, nil 41 | } 42 | -------------------------------------------------------------------------------- /pkg/hwinfo/resource_disk_geo.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | 7 | // CGO cannot access union type fields, so we do this as a workaround 8 | res_disk_geo_t hd_res_get_disk_geo(hd_res_t *res) { return res->disk_geo; } 9 | */ 10 | import "C" 11 | 12 | import ( 13 | "errors" 14 | "fmt" 15 | ) 16 | 17 | //go:generate enumer -type=GeoType -json -transform=snake -trimprefix GeoType -output=./resource_enum_geo_type.go 18 | type GeoType uint //nolint:recvcheck 19 | 20 | const ( 21 | GeoTypePhysical GeoType = iota 22 | GeoTypeLogical 23 | GeoTypeBiosEdd 24 | GeoTypeBiosLegacy 25 | ) 26 | 27 | type ResourceDiskGeo struct { 28 | Type ResourceType `json:"type"` 29 | Cylinders uint32 `json:"cylinders"` 30 | Heads uint8 `json:"heads"` 31 | Sectors uint32 `json:"sectors"` 32 | Size string `json:"size"` 33 | GeoType GeoType `json:"geo_type"` 34 | } 35 | 36 | func (r ResourceDiskGeo) ResourceType() ResourceType { 37 | return ResourceTypeDiskGeo 38 | } 39 | 40 | func NewResourceDiskGeo(res *C.hd_res_t, resType ResourceType) (*ResourceDiskGeo, error) { 41 | if res == nil { 42 | return nil, errors.New("res is nil") 43 | } 44 | 45 | if resType != ResourceTypeDiskGeo { 46 | return nil, fmt.Errorf("expected resource type '%s', found '%s'", ResourceTypeDiskGeo, resType) 47 | } 48 | 49 | disk := C.hd_res_get_disk_geo(res) 50 | 51 | return &ResourceDiskGeo{ 52 | Type: resType, 53 | Cylinders: uint32(disk.cyls), 54 | Heads: uint8(disk.heads), 55 | Sectors: uint32(disk.sectors), 56 | Size: fmt.Sprintf("0x%x", uint64(disk.size)), 57 | GeoType: GeoType(disk.geotype), 58 | }, nil 59 | } 60 | -------------------------------------------------------------------------------- /pkg/hwinfo/resource_dma.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | #include 7 | 8 | // custom getters to get around the problem with bitfields https://github.com/golang/go/issues/43261 9 | bool hd_res_dma_get_enabled(res_dma_t res) { return res.enabled; } 10 | 11 | // CGO cannot access union type fields, so we do this as a workaround 12 | res_dma_t hd_res_get_dma(hd_res_t *res) { return res->dma; } 13 | 14 | */ 15 | import "C" 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | ) 21 | 22 | type ResourceDma struct { 23 | Type ResourceType `json:"type"` 24 | Base string `json:"base"` 25 | Enabled bool `json:"enabled"` 26 | } 27 | 28 | func (r ResourceDma) ResourceType() ResourceType { 29 | return ResourceTypeDma 30 | } 31 | 32 | func NewResourceDma(res *C.hd_res_t, resType ResourceType) (*ResourceDma, error) { 33 | if res == nil { 34 | return nil, errors.New("res is nil") 35 | } 36 | 37 | if resType != ResourceTypeDma { 38 | return nil, fmt.Errorf("expected resource type '%s', found '%s'", ResourceTypeDma, resType) 39 | } 40 | 41 | dma := C.hd_res_get_dma(res) 42 | 43 | return &ResourceDma{ 44 | Type: resType, 45 | Base: fmt.Sprintf("0x%x", uint(dma.base)), 46 | Enabled: bool(C.hd_res_dma_get_enabled(dma)), 47 | }, nil 48 | } 49 | -------------------------------------------------------------------------------- /pkg/hwinfo/resource_enum_access_flags.go: -------------------------------------------------------------------------------- 1 | // Code generated by "enumer -type=AccessFlags -json -transform=snake -trimprefix AccessFlags -output=./resource_enum_access_flags.go"; DO NOT EDIT. 2 | 3 | package hwinfo 4 | 5 | import ( 6 | "encoding/json" 7 | "fmt" 8 | "strings" 9 | ) 10 | 11 | const _AccessFlagsName = "unknownread_onlywrite_onlyread_write" 12 | 13 | var _AccessFlagsIndex = [...]uint8{0, 7, 16, 26, 36} 14 | 15 | const _AccessFlagsLowerName = "unknownread_onlywrite_onlyread_write" 16 | 17 | func (i AccessFlags) String() string { 18 | if i >= AccessFlags(len(_AccessFlagsIndex)-1) { 19 | return fmt.Sprintf("AccessFlags(%d)", i) 20 | } 21 | return _AccessFlagsName[_AccessFlagsIndex[i]:_AccessFlagsIndex[i+1]] 22 | } 23 | 24 | // An "invalid array index" compiler error signifies that the constant values have changed. 25 | // Re-run the stringer command to generate them again. 26 | func _AccessFlagsNoOp() { 27 | var x [1]struct{} 28 | _ = x[AccessFlagsUnknown-(0)] 29 | _ = x[AccessFlagsReadOnly-(1)] 30 | _ = x[AccessFlagsWriteOnly-(2)] 31 | _ = x[AccessFlagsReadWrite-(3)] 32 | } 33 | 34 | var _AccessFlagsValues = []AccessFlags{AccessFlagsUnknown, AccessFlagsReadOnly, AccessFlagsWriteOnly, AccessFlagsReadWrite} 35 | 36 | var _AccessFlagsNameToValueMap = map[string]AccessFlags{ 37 | _AccessFlagsName[0:7]: AccessFlagsUnknown, 38 | _AccessFlagsLowerName[0:7]: AccessFlagsUnknown, 39 | _AccessFlagsName[7:16]: AccessFlagsReadOnly, 40 | _AccessFlagsLowerName[7:16]: AccessFlagsReadOnly, 41 | _AccessFlagsName[16:26]: AccessFlagsWriteOnly, 42 | _AccessFlagsLowerName[16:26]: AccessFlagsWriteOnly, 43 | _AccessFlagsName[26:36]: AccessFlagsReadWrite, 44 | _AccessFlagsLowerName[26:36]: AccessFlagsReadWrite, 45 | } 46 | 47 | var _AccessFlagsNames = []string{ 48 | _AccessFlagsName[0:7], 49 | _AccessFlagsName[7:16], 50 | _AccessFlagsName[16:26], 51 | _AccessFlagsName[26:36], 52 | } 53 | 54 | // AccessFlagsString retrieves an enum value from the enum constants string name. 55 | // Throws an error if the param is not part of the enum. 56 | func AccessFlagsString(s string) (AccessFlags, error) { 57 | if val, ok := _AccessFlagsNameToValueMap[s]; ok { 58 | return val, nil 59 | } 60 | 61 | if val, ok := _AccessFlagsNameToValueMap[strings.ToLower(s)]; ok { 62 | return val, nil 63 | } 64 | return 0, fmt.Errorf("%s does not belong to AccessFlags values", s) 65 | } 66 | 67 | // AccessFlagsValues returns all values of the enum 68 | func AccessFlagsValues() []AccessFlags { 69 | return _AccessFlagsValues 70 | } 71 | 72 | // AccessFlagsStrings returns a slice of all String values of the enum 73 | func AccessFlagsStrings() []string { 74 | strs := make([]string, len(_AccessFlagsNames)) 75 | copy(strs, _AccessFlagsNames) 76 | return strs 77 | } 78 | 79 | // IsAAccessFlags returns "true" if the value is listed in the enum definition. "false" otherwise 80 | func (i AccessFlags) IsAAccessFlags() bool { 81 | for _, v := range _AccessFlagsValues { 82 | if i == v { 83 | return true 84 | } 85 | } 86 | return false 87 | } 88 | 89 | // MarshalJSON implements the json.Marshaler interface for AccessFlags 90 | func (i AccessFlags) MarshalJSON() ([]byte, error) { 91 | return json.Marshal(i.String()) 92 | } 93 | 94 | // UnmarshalJSON implements the json.Unmarshaler interface for AccessFlags 95 | func (i *AccessFlags) UnmarshalJSON(data []byte) error { 96 | var s string 97 | if err := json.Unmarshal(data, &s); err != nil { 98 | return fmt.Errorf("AccessFlags should be a string, got %s", data) 99 | } 100 | 101 | var err error 102 | *i, err = AccessFlagsString(s) 103 | return err 104 | } 105 | -------------------------------------------------------------------------------- /pkg/hwinfo/resource_enum_geo_type.go: -------------------------------------------------------------------------------- 1 | // Code generated by "enumer -type=GeoType -json -transform=snake -trimprefix GeoType -output=./resource_enum_geo_type.go"; DO NOT EDIT. 2 | 3 | package hwinfo 4 | 5 | import ( 6 | "encoding/json" 7 | "fmt" 8 | "strings" 9 | ) 10 | 11 | const _GeoTypeName = "physicallogicalbios_eddbios_legacy" 12 | 13 | var _GeoTypeIndex = [...]uint8{0, 8, 15, 23, 34} 14 | 15 | const _GeoTypeLowerName = "physicallogicalbios_eddbios_legacy" 16 | 17 | func (i GeoType) String() string { 18 | if i >= GeoType(len(_GeoTypeIndex)-1) { 19 | return fmt.Sprintf("GeoType(%d)", i) 20 | } 21 | return _GeoTypeName[_GeoTypeIndex[i]:_GeoTypeIndex[i+1]] 22 | } 23 | 24 | // An "invalid array index" compiler error signifies that the constant values have changed. 25 | // Re-run the stringer command to generate them again. 26 | func _GeoTypeNoOp() { 27 | var x [1]struct{} 28 | _ = x[GeoTypePhysical-(0)] 29 | _ = x[GeoTypeLogical-(1)] 30 | _ = x[GeoTypeBiosEdd-(2)] 31 | _ = x[GeoTypeBiosLegacy-(3)] 32 | } 33 | 34 | var _GeoTypeValues = []GeoType{GeoTypePhysical, GeoTypeLogical, GeoTypeBiosEdd, GeoTypeBiosLegacy} 35 | 36 | var _GeoTypeNameToValueMap = map[string]GeoType{ 37 | _GeoTypeName[0:8]: GeoTypePhysical, 38 | _GeoTypeLowerName[0:8]: GeoTypePhysical, 39 | _GeoTypeName[8:15]: GeoTypeLogical, 40 | _GeoTypeLowerName[8:15]: GeoTypeLogical, 41 | _GeoTypeName[15:23]: GeoTypeBiosEdd, 42 | _GeoTypeLowerName[15:23]: GeoTypeBiosEdd, 43 | _GeoTypeName[23:34]: GeoTypeBiosLegacy, 44 | _GeoTypeLowerName[23:34]: GeoTypeBiosLegacy, 45 | } 46 | 47 | var _GeoTypeNames = []string{ 48 | _GeoTypeName[0:8], 49 | _GeoTypeName[8:15], 50 | _GeoTypeName[15:23], 51 | _GeoTypeName[23:34], 52 | } 53 | 54 | // GeoTypeString retrieves an enum value from the enum constants string name. 55 | // Throws an error if the param is not part of the enum. 56 | func GeoTypeString(s string) (GeoType, error) { 57 | if val, ok := _GeoTypeNameToValueMap[s]; ok { 58 | return val, nil 59 | } 60 | 61 | if val, ok := _GeoTypeNameToValueMap[strings.ToLower(s)]; ok { 62 | return val, nil 63 | } 64 | return 0, fmt.Errorf("%s does not belong to GeoType values", s) 65 | } 66 | 67 | // GeoTypeValues returns all values of the enum 68 | func GeoTypeValues() []GeoType { 69 | return _GeoTypeValues 70 | } 71 | 72 | // GeoTypeStrings returns a slice of all String values of the enum 73 | func GeoTypeStrings() []string { 74 | strs := make([]string, len(_GeoTypeNames)) 75 | copy(strs, _GeoTypeNames) 76 | return strs 77 | } 78 | 79 | // IsAGeoType returns "true" if the value is listed in the enum definition. "false" otherwise 80 | func (i GeoType) IsAGeoType() bool { 81 | for _, v := range _GeoTypeValues { 82 | if i == v { 83 | return true 84 | } 85 | } 86 | return false 87 | } 88 | 89 | // MarshalJSON implements the json.Marshaler interface for GeoType 90 | func (i GeoType) MarshalJSON() ([]byte, error) { 91 | return json.Marshal(i.String()) 92 | } 93 | 94 | // UnmarshalJSON implements the json.Unmarshaler interface for GeoType 95 | func (i *GeoType) UnmarshalJSON(data []byte) error { 96 | var s string 97 | if err := json.Unmarshal(data, &s); err != nil { 98 | return fmt.Errorf("GeoType should be a string, got %s", data) 99 | } 100 | 101 | var err error 102 | *i, err = GeoTypeString(s) 103 | return err 104 | } 105 | -------------------------------------------------------------------------------- /pkg/hwinfo/resource_enum_size_unit.go: -------------------------------------------------------------------------------- 1 | // Code generated by "enumer -type=SizeUnit -json --transform=snake -trimprefix SizeUnit --output=./resource_enum_size_unit.go"; DO NOT EDIT. 2 | 3 | package hwinfo 4 | 5 | import ( 6 | "encoding/json" 7 | "fmt" 8 | "strings" 9 | ) 10 | 11 | const _SizeUnitName = "cmcinchbytesectorskbytembytegbytemm" 12 | 13 | var _SizeUnitIndex = [...]uint8{0, 2, 7, 11, 18, 23, 28, 33, 35} 14 | 15 | const _SizeUnitLowerName = "cmcinchbytesectorskbytembytegbytemm" 16 | 17 | func (i SizeUnit) String() string { 18 | if i >= SizeUnit(len(_SizeUnitIndex)-1) { 19 | return fmt.Sprintf("SizeUnit(%d)", i) 20 | } 21 | return _SizeUnitName[_SizeUnitIndex[i]:_SizeUnitIndex[i+1]] 22 | } 23 | 24 | // An "invalid array index" compiler error signifies that the constant values have changed. 25 | // Re-run the stringer command to generate them again. 26 | func _SizeUnitNoOp() { 27 | var x [1]struct{} 28 | _ = x[SizeUnitCm-(0)] 29 | _ = x[SizeUnitCinch-(1)] 30 | _ = x[SizeUnitByte-(2)] 31 | _ = x[SizeUnitSectors-(3)] 32 | _ = x[SizeUnitKbyte-(4)] 33 | _ = x[SizeUnitMbyte-(5)] 34 | _ = x[SizeUnitGbyte-(6)] 35 | _ = x[SizeUnitMm-(7)] 36 | } 37 | 38 | var _SizeUnitValues = []SizeUnit{SizeUnitCm, SizeUnitCinch, SizeUnitByte, SizeUnitSectors, SizeUnitKbyte, SizeUnitMbyte, SizeUnitGbyte, SizeUnitMm} 39 | 40 | var _SizeUnitNameToValueMap = map[string]SizeUnit{ 41 | _SizeUnitName[0:2]: SizeUnitCm, 42 | _SizeUnitLowerName[0:2]: SizeUnitCm, 43 | _SizeUnitName[2:7]: SizeUnitCinch, 44 | _SizeUnitLowerName[2:7]: SizeUnitCinch, 45 | _SizeUnitName[7:11]: SizeUnitByte, 46 | _SizeUnitLowerName[7:11]: SizeUnitByte, 47 | _SizeUnitName[11:18]: SizeUnitSectors, 48 | _SizeUnitLowerName[11:18]: SizeUnitSectors, 49 | _SizeUnitName[18:23]: SizeUnitKbyte, 50 | _SizeUnitLowerName[18:23]: SizeUnitKbyte, 51 | _SizeUnitName[23:28]: SizeUnitMbyte, 52 | _SizeUnitLowerName[23:28]: SizeUnitMbyte, 53 | _SizeUnitName[28:33]: SizeUnitGbyte, 54 | _SizeUnitLowerName[28:33]: SizeUnitGbyte, 55 | _SizeUnitName[33:35]: SizeUnitMm, 56 | _SizeUnitLowerName[33:35]: SizeUnitMm, 57 | } 58 | 59 | var _SizeUnitNames = []string{ 60 | _SizeUnitName[0:2], 61 | _SizeUnitName[2:7], 62 | _SizeUnitName[7:11], 63 | _SizeUnitName[11:18], 64 | _SizeUnitName[18:23], 65 | _SizeUnitName[23:28], 66 | _SizeUnitName[28:33], 67 | _SizeUnitName[33:35], 68 | } 69 | 70 | // SizeUnitString retrieves an enum value from the enum constants string name. 71 | // Throws an error if the param is not part of the enum. 72 | func SizeUnitString(s string) (SizeUnit, error) { 73 | if val, ok := _SizeUnitNameToValueMap[s]; ok { 74 | return val, nil 75 | } 76 | 77 | if val, ok := _SizeUnitNameToValueMap[strings.ToLower(s)]; ok { 78 | return val, nil 79 | } 80 | return 0, fmt.Errorf("%s does not belong to SizeUnit values", s) 81 | } 82 | 83 | // SizeUnitValues returns all values of the enum 84 | func SizeUnitValues() []SizeUnit { 85 | return _SizeUnitValues 86 | } 87 | 88 | // SizeUnitStrings returns a slice of all String values of the enum 89 | func SizeUnitStrings() []string { 90 | strs := make([]string, len(_SizeUnitNames)) 91 | copy(strs, _SizeUnitNames) 92 | return strs 93 | } 94 | 95 | // IsASizeUnit returns "true" if the value is listed in the enum definition. "false" otherwise 96 | func (i SizeUnit) IsASizeUnit() bool { 97 | for _, v := range _SizeUnitValues { 98 | if i == v { 99 | return true 100 | } 101 | } 102 | return false 103 | } 104 | 105 | // MarshalJSON implements the json.Marshaler interface for SizeUnit 106 | func (i SizeUnit) MarshalJSON() ([]byte, error) { 107 | return json.Marshal(i.String()) 108 | } 109 | 110 | // UnmarshalJSON implements the json.Unmarshaler interface for SizeUnit 111 | func (i *SizeUnit) UnmarshalJSON(data []byte) error { 112 | var s string 113 | if err := json.Unmarshal(data, &s); err != nil { 114 | return fmt.Errorf("SizeUnit should be a string, got %s", data) 115 | } 116 | 117 | var err error 118 | *i, err = SizeUnitString(s) 119 | return err 120 | } 121 | -------------------------------------------------------------------------------- /pkg/hwinfo/resource_enum_yes_no_flags.go: -------------------------------------------------------------------------------- 1 | // Code generated by "enumer -type=YesNoFlags -json -transform=snake -trimprefix YesNoFlags -output=./resource_enum_yes_no_flags.go"; DO NOT EDIT. 2 | 3 | package hwinfo 4 | 5 | import ( 6 | "encoding/json" 7 | "fmt" 8 | "strings" 9 | ) 10 | 11 | const _YesNoFlagsName = "unknownnoyes" 12 | 13 | var _YesNoFlagsIndex = [...]uint8{0, 7, 9, 12} 14 | 15 | const _YesNoFlagsLowerName = "unknownnoyes" 16 | 17 | func (i YesNoFlags) String() string { 18 | if i >= YesNoFlags(len(_YesNoFlagsIndex)-1) { 19 | return fmt.Sprintf("YesNoFlags(%d)", i) 20 | } 21 | return _YesNoFlagsName[_YesNoFlagsIndex[i]:_YesNoFlagsIndex[i+1]] 22 | } 23 | 24 | // An "invalid array index" compiler error signifies that the constant values have changed. 25 | // Re-run the stringer command to generate them again. 26 | func _YesNoFlagsNoOp() { 27 | var x [1]struct{} 28 | _ = x[YesNoFlagsUnknown-(0)] 29 | _ = x[YesNoFlagsNo-(1)] 30 | _ = x[YesNoFlagsYes-(2)] 31 | } 32 | 33 | var _YesNoFlagsValues = []YesNoFlags{YesNoFlagsUnknown, YesNoFlagsNo, YesNoFlagsYes} 34 | 35 | var _YesNoFlagsNameToValueMap = map[string]YesNoFlags{ 36 | _YesNoFlagsName[0:7]: YesNoFlagsUnknown, 37 | _YesNoFlagsLowerName[0:7]: YesNoFlagsUnknown, 38 | _YesNoFlagsName[7:9]: YesNoFlagsNo, 39 | _YesNoFlagsLowerName[7:9]: YesNoFlagsNo, 40 | _YesNoFlagsName[9:12]: YesNoFlagsYes, 41 | _YesNoFlagsLowerName[9:12]: YesNoFlagsYes, 42 | } 43 | 44 | var _YesNoFlagsNames = []string{ 45 | _YesNoFlagsName[0:7], 46 | _YesNoFlagsName[7:9], 47 | _YesNoFlagsName[9:12], 48 | } 49 | 50 | // YesNoFlagsString retrieves an enum value from the enum constants string name. 51 | // Throws an error if the param is not part of the enum. 52 | func YesNoFlagsString(s string) (YesNoFlags, error) { 53 | if val, ok := _YesNoFlagsNameToValueMap[s]; ok { 54 | return val, nil 55 | } 56 | 57 | if val, ok := _YesNoFlagsNameToValueMap[strings.ToLower(s)]; ok { 58 | return val, nil 59 | } 60 | return 0, fmt.Errorf("%s does not belong to YesNoFlags values", s) 61 | } 62 | 63 | // YesNoFlagsValues returns all values of the enum 64 | func YesNoFlagsValues() []YesNoFlags { 65 | return _YesNoFlagsValues 66 | } 67 | 68 | // YesNoFlagsStrings returns a slice of all String values of the enum 69 | func YesNoFlagsStrings() []string { 70 | strs := make([]string, len(_YesNoFlagsNames)) 71 | copy(strs, _YesNoFlagsNames) 72 | return strs 73 | } 74 | 75 | // IsAYesNoFlags returns "true" if the value is listed in the enum definition. "false" otherwise 76 | func (i YesNoFlags) IsAYesNoFlags() bool { 77 | for _, v := range _YesNoFlagsValues { 78 | if i == v { 79 | return true 80 | } 81 | } 82 | return false 83 | } 84 | 85 | // MarshalJSON implements the json.Marshaler interface for YesNoFlags 86 | func (i YesNoFlags) MarshalJSON() ([]byte, error) { 87 | return json.Marshal(i.String()) 88 | } 89 | 90 | // UnmarshalJSON implements the json.Unmarshaler interface for YesNoFlags 91 | func (i *YesNoFlags) UnmarshalJSON(data []byte) error { 92 | var s string 93 | if err := json.Unmarshal(data, &s); err != nil { 94 | return fmt.Errorf("YesNoFlags should be a string, got %s", data) 95 | } 96 | 97 | var err error 98 | *i, err = YesNoFlagsString(s) 99 | return err 100 | } 101 | -------------------------------------------------------------------------------- /pkg/hwinfo/resource_fc.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | #include 7 | 8 | // custom getters to get around the problem with bitfields https://github.com/golang/go/issues/43261 9 | bool hd_res_fc_get_wwpn_ok(res_fc_t res) { return res.wwpn_ok; } 10 | bool hd_res_fc_get_fcp_lun_ok(res_fc_t res) { return res.fcp_lun_ok; } 11 | bool hd_res_fc_get_port_id_ok(res_fc_t res) { return res.port_id_ok; } 12 | 13 | // CGO cannot access union type fields, so we do this as a workaround 14 | res_fc_t hd_res_get_fc(hd_res_t *res) { return res->fc; } 15 | */ 16 | import "C" 17 | 18 | import ( 19 | "errors" 20 | "fmt" 21 | ) 22 | 23 | // todo what is FC? 24 | type ResourceFc struct { 25 | Type ResourceType `json:"type"` 26 | WwpnOk bool `json:"wwpn_ok"` 27 | FcpLunOk bool `json:"fcp_lun_ok"` 28 | PortIDOk bool `json:"port_id_ok"` 29 | Wwpn uint64 `json:"wwpn"` 30 | FcpLun uint64 `json:"fcp_lun"` 31 | PortID uint32 `json:"port_id"` 32 | ControllerID byte `json:"controller_id"` 33 | } 34 | 35 | func (r ResourceFc) ResourceType() ResourceType { 36 | return ResourceTypeFc 37 | } 38 | 39 | func NewResourceFc(res *C.hd_res_t, resType ResourceType) (*ResourceFc, error) { 40 | if res == nil { 41 | return nil, errors.New("res is nil") 42 | } 43 | 44 | if resType != ResourceTypeFc { 45 | return nil, fmt.Errorf("expected resource type '%s', found '%s'", ResourceTypeFc, resType) 46 | } 47 | 48 | fc := C.hd_res_get_fc(res) 49 | 50 | return &ResourceFc{ 51 | WwpnOk: bool(C.hd_res_fc_get_wwpn_ok(fc)), 52 | FcpLunOk: bool(C.hd_res_fc_get_fcp_lun_ok(fc)), 53 | PortIDOk: bool(C.hd_res_fc_get_port_id_ok(fc)), 54 | Wwpn: uint64(fc.wwpn), 55 | FcpLun: uint64(fc.fcp_lun), 56 | PortID: uint32(fc.port_id), 57 | ControllerID: byte(*fc.controller_id), 58 | }, nil 59 | } 60 | -------------------------------------------------------------------------------- /pkg/hwinfo/resource_frame_buffer.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | 7 | // CGO cannot access union type fields, so we do this as a workaround 8 | res_framebuffer_t hd_res_get_framebuffer(hd_res_t *res) { return res->framebuffer; } 9 | */ 10 | import "C" 11 | 12 | import ( 13 | "errors" 14 | "fmt" 15 | ) 16 | 17 | type ResourceFrameBuffer struct { 18 | Type ResourceType `json:"type"` 19 | Width uint32 `json:"width"` 20 | Height uint32 `json:"height"` 21 | BytesPerLine uint16 `json:"bytes_per_line"` 22 | ColorBits uint16 `json:"color_bits"` 23 | Mode uint16 `json:"mode"` 24 | } 25 | 26 | func (r ResourceFrameBuffer) ResourceType() ResourceType { 27 | return ResourceTypeFramebuffer 28 | } 29 | 30 | func NewResourceFrameBuffer(res *C.hd_res_t, resType ResourceType) (*ResourceFrameBuffer, error) { 31 | if res == nil { 32 | return nil, errors.New("res is nil") 33 | } 34 | 35 | if resType != ResourceTypeFramebuffer { 36 | return nil, fmt.Errorf("expected resource type '%s', found '%s'", ResourceTypeFramebuffer, resType) 37 | } 38 | 39 | fb := C.hd_res_get_framebuffer(res) 40 | 41 | return &ResourceFrameBuffer{ 42 | Type: resType, 43 | Width: uint32(fb.width), 44 | Height: uint32(fb.height), 45 | BytesPerLine: uint16(fb.bytes_p_line), 46 | ColorBits: uint16(fb.colorbits), 47 | Mode: uint16(fb.mode), 48 | }, nil 49 | } 50 | -------------------------------------------------------------------------------- /pkg/hwinfo/resource_hw_addr.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | 7 | // CGO cannot access union type fields, so we do this as a workaround 8 | res_hwaddr_t hd_res_get_hwaddr(hd_res_t *res) { return res->hwaddr; } 9 | */ 10 | import "C" 11 | 12 | import ( 13 | "errors" 14 | "fmt" 15 | ) 16 | 17 | type ResourceHardwareAddress struct { 18 | Type ResourceType `json:"type"` 19 | Address byte `json:"address"` 20 | } 21 | 22 | func (r ResourceHardwareAddress) ResourceType() ResourceType { 23 | return r.Type 24 | } 25 | 26 | func NewResourceHardwareAddress(res *C.hd_res_t, resType ResourceType) (*ResourceHardwareAddress, error) { 27 | //nolint:staticcheck 28 | if !(resType == ResourceTypeHwaddr || resType == ResourceTypePhwaddr) { 29 | return nil, fmt.Errorf( 30 | "invalid resource type %s, must be either %s or %s", 31 | resType, ResourceTypeHwaddr, ResourceTypePhwaddr, 32 | ) 33 | } 34 | 35 | if res == nil { 36 | return nil, errors.New("res is nil") 37 | } 38 | 39 | hwaddr := C.hd_res_get_hwaddr(res) 40 | 41 | return &ResourceHardwareAddress{ 42 | Type: resType, 43 | Address: byte(*hwaddr.addr), 44 | }, nil 45 | } 46 | -------------------------------------------------------------------------------- /pkg/hwinfo/resource_init_strings.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | 7 | // CGO cannot access union type fields, so we do this as a workaround 8 | res_init_strings_t hd_res_get_init_strings(hd_res_t *res) { return res->init_strings; } 9 | */ 10 | import "C" 11 | 12 | import ( 13 | "errors" 14 | "fmt" 15 | ) 16 | 17 | type ResourceInitStrings struct { 18 | Type ResourceType 19 | Init1 string `json:"init_1,omitempty"` 20 | Init2 string `json:"init_2,omitempty"` 21 | } 22 | 23 | func (r ResourceInitStrings) ResourceType() ResourceType { 24 | return ResourceTypeInitStrings 25 | } 26 | 27 | func NewResourceInitStrings(res *C.hd_res_t, resType ResourceType) (*ResourceInitStrings, error) { 28 | if res == nil { 29 | return nil, errors.New("res is nil") 30 | } 31 | 32 | if resType != ResourceTypeInitStrings { 33 | return nil, fmt.Errorf("expected resource type '%s', found '%s'", ResourceTypeInitStrings, resType) 34 | } 35 | 36 | init := C.hd_res_get_init_strings(res) 37 | 38 | return &ResourceInitStrings{ 39 | Type: resType, 40 | Init1: C.GoString(init.init1), 41 | Init2: C.GoString(init.init2), 42 | }, nil 43 | } 44 | -------------------------------------------------------------------------------- /pkg/hwinfo/resource_io.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | #include 7 | 8 | // custom getters to get around the problem with bitfields https://github.com/golang/go/issues/43261 9 | bool hd_res_io_get_enabled(res_io_t res) { return res.enabled; } 10 | unsigned hd_res_io_get_access(res_io_t res) { return res.access; } 11 | 12 | // CGO cannot access union type fields, so we do this as a workaround 13 | res_io_t hd_res_get_io(hd_res_t *res) { return res->io; } 14 | */ 15 | import "C" 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | ) 21 | 22 | type ResourceIO struct { 23 | Type ResourceType `json:"type"` 24 | Base uint64 `json:"base"` 25 | Range uint64 `json:"range"` 26 | Enabled bool `json:"enabled"` 27 | Access AccessFlags `json:"access"` 28 | } 29 | 30 | func (r ResourceIO) ResourceType() ResourceType { 31 | return ResourceTypeIo 32 | } 33 | 34 | func NewResourceIO(res *C.hd_res_t, resType ResourceType) (*ResourceIO, error) { 35 | if res == nil { 36 | return nil, errors.New("res is nil") 37 | } 38 | 39 | if resType != ResourceTypeIo { 40 | return nil, fmt.Errorf("expected resource type '%s', found '%s'", ResourceTypeIo, resType) 41 | } 42 | 43 | io := C.hd_res_get_io(res) 44 | 45 | return &ResourceIO{ 46 | Type: resType, 47 | Base: uint64(io.base), 48 | Range: uint64(io._range), 49 | Enabled: bool(C.hd_res_io_get_enabled(io)), 50 | Access: AccessFlags(C.hd_res_io_get_access(io)), 51 | }, nil 52 | } 53 | -------------------------------------------------------------------------------- /pkg/hwinfo/resource_irq.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | #include 7 | 8 | // custom getters to get around the problem with bitfields https://github.com/golang/go/issues/43261 9 | bool hd_res_irq_get_enabled(res_irq_t res) { return res.enabled; } 10 | 11 | // CGO cannot access union type fields, so we do this as a workaround 12 | res_irq_t hd_res_get_irq(hd_res_t *res) { return res->irq; } 13 | */ 14 | import "C" 15 | 16 | import ( 17 | "errors" 18 | "fmt" 19 | ) 20 | 21 | type ResourceIrq struct { 22 | Type ResourceType `json:"type"` 23 | Base uint16 `json:"base"` 24 | Triggered uint16 `json:"triggered"` 25 | Enabled bool `json:"enabled"` 26 | } 27 | 28 | func (r ResourceIrq) ResourceType() ResourceType { 29 | return ResourceTypeIrq 30 | } 31 | 32 | func NewResourceIrq(res *C.hd_res_t, resType ResourceType) (*ResourceIrq, error) { 33 | if res == nil { 34 | return nil, errors.New("res is nil") 35 | } 36 | 37 | if resType != ResourceTypeIrq { 38 | return nil, fmt.Errorf("expected resource type '%s', found '%s'", ResourceTypeIrq, resType) 39 | } 40 | 41 | irq := C.hd_res_get_irq(res) 42 | 43 | return &ResourceIrq{ 44 | Type: resType, 45 | Base: uint16(irq.base), 46 | Triggered: uint16(irq.triggered), 47 | Enabled: bool(C.hd_res_irq_get_enabled(irq)), 48 | }, nil 49 | } 50 | -------------------------------------------------------------------------------- /pkg/hwinfo/resource_link.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | #include 7 | 8 | // custom getters to get around the problem with bitfields https://github.com/golang/go/issues/43261 9 | bool hd_res_link_get_connected(res_link_t res) { return res.state == 1; } 10 | 11 | // CGO cannot access union type fields, so we do this as a workaround 12 | res_link_t hd_res_get_link(hd_res_t *res) { return res->link; } 13 | */ 14 | import "C" 15 | 16 | import ( 17 | "errors" 18 | "fmt" 19 | ) 20 | 21 | type ResourceLink struct { 22 | Type ResourceType `json:"type"` 23 | Connected bool `json:"connected"` 24 | } 25 | 26 | func (r ResourceLink) ResourceType() ResourceType { 27 | return ResourceTypeLink 28 | } 29 | 30 | func NewResourceLink(res *C.hd_res_t, resType ResourceType) (*ResourceLink, error) { 31 | if res == nil { 32 | return nil, errors.New("res is nil") 33 | } 34 | 35 | if resType != ResourceTypeLink { 36 | return nil, fmt.Errorf("expected resource type '%s', found '%s'", ResourceTypeLink, resType) 37 | } 38 | 39 | link := C.hd_res_get_link(res) 40 | 41 | return &ResourceLink{ 42 | Type: resType, 43 | Connected: bool(C.hd_res_link_get_connected(link)), 44 | }, nil 45 | } 46 | -------------------------------------------------------------------------------- /pkg/hwinfo/resource_mem.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | #include 7 | 8 | // custom getters to get around the problem with bitfields https://github.com/golang/go/issues/43261 9 | bool hd_res_mem_get_enabled(res_mem_t res) { return res.enabled; } 10 | unsigned hd_res_mem_get_access(res_mem_t res) { return res.access; } 11 | unsigned hd_res_mem_get_prefetch(res_mem_t res) { return res.prefetch; } 12 | 13 | // CGO cannot access union type fields, so we do this as a workaround 14 | res_mem_t hd_res_get_mem(hd_res_t *res) { return res->mem; } 15 | 16 | */ 17 | import "C" 18 | 19 | import ( 20 | "errors" 21 | "fmt" 22 | ) 23 | 24 | //go:generate enumer -type=AccessFlags -json -transform=snake -trimprefix AccessFlags -output=./resource_enum_access_flags.go 25 | type AccessFlags uint //nolint:recvcheck 26 | 27 | const ( 28 | AccessFlagsUnknown AccessFlags = iota 29 | AccessFlagsReadOnly 30 | AccessFlagsWriteOnly 31 | AccessFlagsReadWrite 32 | ) 33 | 34 | //go:generate enumer -type=YesNoFlags -json -transform=snake -trimprefix YesNoFlags -output=./resource_enum_yes_no_flags.go 35 | type YesNoFlags uint //nolint:recvcheck 36 | 37 | const ( 38 | YesNoFlagsUnknown YesNoFlags = iota 39 | YesNoFlagsNo 40 | YesNoFlagsYes 41 | ) 42 | 43 | type Resource interface { 44 | ResourceType() ResourceType 45 | } 46 | 47 | type ResourceMemory struct { 48 | Type ResourceType `json:"type"` 49 | Base uint64 `json:"base"` 50 | Range uint64 `json:"range"` 51 | Enabled bool `json:"enabled"` 52 | Access AccessFlags `json:"access"` 53 | Prefetch YesNoFlags `json:"prefetch"` 54 | } 55 | 56 | func (r ResourceMemory) ResourceType() ResourceType { 57 | return ResourceTypeMem 58 | } 59 | 60 | func NewResourceMemory(res *C.hd_res_t, resType ResourceType) (*ResourceMemory, error) { 61 | if res == nil { 62 | return nil, errors.New("res is nil") 63 | } 64 | 65 | if resType != ResourceTypeMem { 66 | return nil, fmt.Errorf("expected resource type '%s', found '%s'", ResourceTypeMem, resType) 67 | } 68 | 69 | mem := C.hd_res_get_mem(res) 70 | 71 | return &ResourceMemory{ 72 | Type: resType, 73 | Base: uint64(mem.base), 74 | Range: uint64(mem._range), 75 | Enabled: bool(C.hd_res_mem_get_enabled(mem)), 76 | Access: AccessFlags(C.hd_res_mem_get_access(mem)), 77 | Prefetch: YesNoFlags(C.hd_res_mem_get_prefetch(mem)), 78 | }, nil 79 | } 80 | -------------------------------------------------------------------------------- /pkg/hwinfo/resource_monitor.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | #include 7 | 8 | // custom getters to get around the problem with bitfields https://github.com/golang/go/issues/43261 9 | bool hd_res_monitor_get_interlaced(res_monitor_t res) { return res.interlaced; } 10 | 11 | // CGO cannot access union type fields, so we do this as a workaround 12 | res_monitor_t hd_res_get_monitor(hd_res_t *res) { return res->monitor; } 13 | */ 14 | import "C" 15 | 16 | import ( 17 | "errors" 18 | "fmt" 19 | ) 20 | 21 | type ResourceMonitor struct { 22 | Type ResourceType `json:"type"` 23 | Width uint32 `json:"width"` 24 | Height uint32 `json:"height"` 25 | VerticalFrequency uint16 `json:"vertical_frequency"` 26 | Interlaced bool `json:"interlaced"` 27 | } 28 | 29 | func (r ResourceMonitor) ResourceType() ResourceType { 30 | return ResourceTypeMonitor 31 | } 32 | 33 | func NewResourceMonitor(res *C.hd_res_t, resType ResourceType) (*ResourceMonitor, error) { 34 | if res == nil { 35 | return nil, errors.New("res is nil") 36 | } 37 | 38 | if resType != ResourceTypeMonitor { 39 | return nil, fmt.Errorf("expected resource type '%s', found '%s'", ResourceTypeMonitor, resType) 40 | } 41 | 42 | monitor := C.hd_res_get_monitor(res) 43 | 44 | return &ResourceMonitor{ 45 | Type: resType, 46 | Width: uint32(monitor.width), 47 | Height: uint32(monitor.height), 48 | VerticalFrequency: uint16(monitor.vfreq), 49 | Interlaced: bool(C.hd_res_monitor_get_interlaced(monitor)), 50 | }, nil 51 | } 52 | -------------------------------------------------------------------------------- /pkg/hwinfo/resource_phys_mem.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | 7 | // CGO cannot access union type fields, so we do this as a workaround 8 | res_phys_mem_t hd_res_get_phys_mem(hd_res_t *res) { return res->phys_mem; } 9 | */ 10 | import "C" 11 | 12 | import ( 13 | "errors" 14 | "fmt" 15 | ) 16 | 17 | type ResourcePhysicalMemory struct { 18 | Type ResourceType `json:"type"` 19 | Range uint64 `json:"range"` 20 | } 21 | 22 | func (r ResourcePhysicalMemory) ResourceType() ResourceType { 23 | return ResourceTypePhysMem 24 | } 25 | 26 | func NewResourcePhysicalMemory(res *C.hd_res_t, resType ResourceType) (*ResourcePhysicalMemory, error) { 27 | if res == nil { 28 | return nil, errors.New("res is nil") 29 | } 30 | 31 | if resType != ResourceTypePhysMem { 32 | return nil, fmt.Errorf("expected resource type '%s', found '%s'", ResourceTypePhysMem, resType) 33 | } 34 | 35 | physMem := C.hd_res_get_phys_mem(res) 36 | return &ResourcePhysicalMemory{ 37 | Type: resType, 38 | Range: uint64(physMem._range), 39 | }, nil 40 | } 41 | -------------------------------------------------------------------------------- /pkg/hwinfo/resource_pppd_option.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | 7 | // CGO cannot access union type fields, so we do this as a workaround 8 | res_pppd_option_t hd_res_get_pppd_option(hd_res_t *res) { return res->pppd_option; } 9 | */ 10 | import "C" 11 | 12 | import ( 13 | "errors" 14 | "fmt" 15 | ) 16 | 17 | type ResourcePppdOption struct { 18 | Type ResourceType `json:"type"` 19 | Option byte `json:"option"` 20 | } 21 | 22 | func (r ResourcePppdOption) ResourceType() ResourceType { 23 | return ResourceTypePppdOption 24 | } 25 | 26 | func NewResourcePppdOption(res *C.hd_res_t, resType ResourceType) (*ResourcePppdOption, error) { 27 | if res == nil { 28 | return nil, errors.New("res is nil") 29 | } 30 | 31 | if resType != ResourceTypePppdOption { 32 | return nil, fmt.Errorf("expected resource type '%s', found '%s'", ResourceTypePppdOption, resType) 33 | } 34 | 35 | pppd := C.hd_res_get_pppd_option(res) 36 | 37 | return &ResourcePppdOption{ 38 | Type: resType, 39 | Option: byte(*pppd.option), 40 | }, nil 41 | } 42 | -------------------------------------------------------------------------------- /pkg/hwinfo/resource_size.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | 7 | // CGO cannot access union type fields, so we do this as a workaround 8 | res_size_t hd_res_get_size(hd_res_t *res) { return res->size; } 9 | 10 | */ 11 | import "C" 12 | 13 | import ( 14 | "errors" 15 | "fmt" 16 | ) 17 | 18 | //go:generate enumer -type=SizeUnit -json --transform=snake -trimprefix SizeUnit --output=./resource_enum_size_unit.go 19 | type SizeUnit uint //nolint:recvcheck 20 | 21 | const ( 22 | SizeUnitCm SizeUnit = iota 23 | SizeUnitCinch 24 | SizeUnitByte 25 | SizeUnitSectors 26 | 27 | SizeUnitKbyte 28 | SizeUnitMbyte 29 | SizeUnitGbyte 30 | SizeUnitMm 31 | ) 32 | 33 | type ResourceSize struct { 34 | Type ResourceType `json:"type"` 35 | Unit SizeUnit `json:"unit"` 36 | Value1 uint64 `json:"value_1"` 37 | Value2 uint64 `json:"value_2,omitempty"` 38 | } 39 | 40 | func (r ResourceSize) ResourceType() ResourceType { 41 | return ResourceTypeSize 42 | } 43 | 44 | func NewResourceSize(res *C.hd_res_t, resType ResourceType) (*ResourceSize, error) { 45 | if res == nil { 46 | return nil, errors.New("res is nil") 47 | } 48 | 49 | if resType != ResourceTypeSize { 50 | return nil, fmt.Errorf("expected resource type '%s', found '%s'", ResourceTypeSize, resType) 51 | } 52 | 53 | size := C.hd_res_get_size(res) 54 | 55 | return &ResourceSize{ 56 | Type: resType, 57 | Unit: SizeUnit(size.unit), 58 | Value1: uint64(size.val1), 59 | Value2: uint64(size.val2), 60 | }, nil 61 | } 62 | -------------------------------------------------------------------------------- /pkg/hwinfo/resource_wlan.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | 7 | // CGO cannot access union type fields, so we do this as a workaround 8 | res_wlan_t hd_res_get_wlan(hd_res_t *res) { return res->wlan; } 9 | */ 10 | import "C" 11 | 12 | import ( 13 | "errors" 14 | "fmt" 15 | ) 16 | 17 | type ResourceWlan struct { 18 | Type ResourceType `json:"type"` 19 | Channels []string `json:"channels,omitempty"` 20 | Frequencies []string `json:"frequencies,omitempty"` 21 | BitRates []string `json:"bit_rates,omitempty"` 22 | AuthModes []string `json:"auth_modes,omitempty"` 23 | EncModes []string `json:"enc_modes,omitempty"` 24 | } 25 | 26 | func (r ResourceWlan) ResourceType() ResourceType { 27 | return ResourceTypeWlan 28 | } 29 | 30 | func NewResourceWlan(res *C.hd_res_t, resType ResourceType) (*ResourceWlan, error) { 31 | if res == nil { 32 | return nil, errors.New("res is nil") 33 | } 34 | 35 | if resType != ResourceTypeWlan { 36 | return nil, fmt.Errorf("expected resource type '%s', found '%s'", ResourceTypeWlan, resType) 37 | } 38 | 39 | wlan := C.hd_res_get_wlan(res) 40 | 41 | return &ResourceWlan{ 42 | Type: resType, 43 | Channels: ReadStringList(wlan.channels), 44 | Frequencies: ReadStringList(wlan.frequencies), 45 | BitRates: ReadStringList(wlan.bitrates), 46 | AuthModes: ReadStringList(wlan.auth_modes), 47 | EncModes: ReadStringList(wlan.enc_modes), 48 | }, nil 49 | } 50 | -------------------------------------------------------------------------------- /pkg/hwinfo/smbios_any.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | 9 | import ( 10 | "fmt" 11 | "unsafe" 12 | ) 13 | 14 | // SmbiosAny captures generic smbios data. 15 | type SmbiosAny struct { 16 | Type SmbiosType `json:"-"` 17 | Handle int `json:"handle"` 18 | Data string `json:"data"` 19 | Strings []string `json:"strings,omitempty"` 20 | } 21 | 22 | func (s SmbiosAny) SmbiosType() SmbiosType { 23 | return s.Type 24 | } 25 | 26 | func NewSmbiosAny(smbiosType SmbiosType, info C.smbios_any_t) (*SmbiosAny, error) { 27 | return &SmbiosAny{ 28 | Type: smbiosType, 29 | Handle: int(info.handle), 30 | Data: fmt.Sprintf("0x%x", ReadByteArray(unsafe.Pointer(info.data), int(info.data_len))), 31 | Strings: ReadStringList(info.strings), 32 | }, nil 33 | } 34 | -------------------------------------------------------------------------------- /pkg/hwinfo/smbios_bios.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | 9 | import ( 10 | "fmt" 11 | ) 12 | 13 | // SmbiosBios captures BIOS related information. 14 | type SmbiosBios struct { 15 | Type SmbiosType `json:"-"` 16 | Handle int `json:"handle"` 17 | Vendor string `json:"vendor"` 18 | Version string `json:"version"` 19 | Date string `json:"date"` 20 | Features []string `json:"features"` 21 | StartAddress string `json:"start_address"` 22 | RomSize uint32 `json:"rom_size"` 23 | } 24 | 25 | func (s SmbiosBios) SmbiosType() SmbiosType { 26 | return s.Type 27 | } 28 | 29 | func NewSmbiosBiosInfo(info C.smbios_biosinfo_t) (*SmbiosBios, error) { 30 | return &SmbiosBios{ 31 | Type: SmbiosTypeBios, 32 | Handle: int(info.handle), 33 | Vendor: C.GoString(info.vendor), 34 | Version: C.GoString(info.version), 35 | Date: C.GoString(info.date), 36 | Features: ReadStringList(info.feature.str), 37 | StartAddress: fmt.Sprintf("0x%x", uint(info.start)), 38 | RomSize: uint32(info.rom_size), 39 | }, nil 40 | } 41 | -------------------------------------------------------------------------------- /pkg/hwinfo/smbios_board.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | import "unsafe" 9 | 10 | // SmbiosBoard captures motherboard related information. 11 | type SmbiosBoard struct { 12 | Type SmbiosType `json:"-"` 13 | Handle int `json:"handle"` 14 | Manufacturer string `json:"manufacturer"` 15 | Product string `json:"product"` 16 | Version string `json:"version"` 17 | Serial string `json:"-"` // omit from json output 18 | AssetTag string `json:"-"` 19 | BoardType *ID `json:"board_type"` 20 | Features []string `json:"features"` 21 | Location string `json:"location"` // location in chassis 22 | Chassis int `json:"chassis"` // handle of chassis 23 | Objects []int `json:"objects,omitempty"` // array of object handles 24 | } 25 | 26 | func (s SmbiosBoard) SmbiosType() SmbiosType { 27 | return s.Type 28 | } 29 | 30 | func NewSmbiosBoardInfo(info C.smbios_boardinfo_t) (*SmbiosBoard, error) { 31 | return &SmbiosBoard{ 32 | Type: SmbiosTypeBoard, 33 | Handle: int(info.handle), 34 | Manufacturer: C.GoString(info.manuf), 35 | Product: C.GoString(info.product), 36 | Version: C.GoString(info.version), 37 | Serial: C.GoString(info.serial), 38 | AssetTag: C.GoString(info.asset), 39 | BoardType: NewID(info.board_type), 40 | Features: ReadStringList(info.feature.str), 41 | Location: C.GoString(info.location), 42 | Chassis: int(info.chassis), 43 | Objects: ReadIntArray(unsafe.Pointer(info.objects), int(info.objects_len)), 44 | }, nil 45 | } 46 | -------------------------------------------------------------------------------- /pkg/hwinfo/smbios_cache.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | 9 | // SmbiosCache captures processor information. 10 | type SmbiosCache struct { 11 | Type SmbiosType `json:"-"` 12 | Handle int `json:"handle"` 13 | Socket string `json:"socket"` // socket designation 14 | SizeMax uint32 `json:"size_max"` // max cache size in kbytes 15 | SizeCurrent uint32 `json:"size_current"` // current size in kbytes 16 | Speed uint32 `json:"speed"` // cache speed in nanoseconds 17 | Mode *ID `json:"mode"` // operational mode 18 | Enabled bool `json:"enabled"` 19 | Location *ID `json:"location"` // cache location 20 | Socketed bool `json:"socketed"` 21 | Level uint `json:"level"` // cache level (0 = L1, 1 = L2, ...) 22 | ECC *ID `json:"ecc"` // error correction type 23 | CacheType *ID `json:"cache_type"` // logical cache type 24 | Associativity *ID `json:"associativity"` // cache associativity 25 | SRAMType []string `json:"sram_type_current"` 26 | SRAMTypes []string `json:"sram_type_supported"` 27 | } 28 | 29 | func (s SmbiosCache) SmbiosType() SmbiosType { 30 | return s.Type 31 | } 32 | 33 | func NewSmbiosCache(info C.smbios_cache_t) (*SmbiosCache, error) { 34 | return &SmbiosCache{ 35 | Type: SmbiosTypeCache, 36 | Handle: int(info.handle), 37 | Socket: C.GoString(info.socket), 38 | SizeMax: uint32(info.max_size), 39 | SizeCurrent: uint32(info.current_size), 40 | Speed: uint32(info.speed), 41 | Mode: NewID(info.mode), 42 | Enabled: uint(info.state) == 1, 43 | Location: NewID(info.location), 44 | Socketed: uint(info.socketed) == 1, 45 | Level: uint(info.level), 46 | ECC: NewID(info.ecc), 47 | CacheType: NewID(info.cache_type), 48 | Associativity: NewID(info.assoc), 49 | SRAMType: ReadStringList(info.sram.str), 50 | SRAMTypes: ReadStringList(info.supp_sram.str), 51 | }, nil 52 | } 53 | -------------------------------------------------------------------------------- /pkg/hwinfo/smbios_chassis.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | import "fmt" 9 | 10 | // SmbiosChassis captures motherboard related information. 11 | type SmbiosChassis struct { 12 | Type SmbiosType `json:"-"` 13 | Handle int `json:"handle"` 14 | Manufacturer string `json:"manufacturer"` 15 | Version string `json:"version"` 16 | Serial string `json:"-"` // omit from json output 17 | AssetTag string `json:"-"` // asset tag 18 | ChassisType *ID `json:"chassis_type"` 19 | LockPresent bool `json:"lock_present"` // true: lock present, false: not present or unknown 20 | BootupState *ID `json:"bootup_state"` 21 | PowerState *ID `json:"power_state"` // power supply state (at last boot) 22 | ThermalState *ID `json:"thermal_state"` // thermal state (at last boot) 23 | SecurityState *ID `json:"security_state"` // security state (at last boot) 24 | OEM string `json:"oem"` // oem-specific information" 25 | } 26 | 27 | func (s SmbiosChassis) SmbiosType() SmbiosType { 28 | return s.Type 29 | } 30 | 31 | func NewSmbiosChassis(info C.smbios_chassis_t) (*SmbiosChassis, error) { 32 | return &SmbiosChassis{ 33 | Type: SmbiosTypeChassis, 34 | Handle: int(info.handle), 35 | Manufacturer: C.GoString(info.manuf), 36 | Version: C.GoString(info.version), 37 | Serial: C.GoString(info.serial), 38 | AssetTag: C.GoString(info.asset), 39 | ChassisType: NewID(info.ch_type), 40 | LockPresent: uint(info.lock) == 1, 41 | BootupState: NewID(info.bootup), 42 | PowerState: NewID(info.power), 43 | ThermalState: NewID(info.thermal), 44 | SecurityState: NewID(info.security), 45 | OEM: fmt.Sprintf("0x%x", uint(info.oem)), 46 | }, nil 47 | } 48 | -------------------------------------------------------------------------------- /pkg/hwinfo/smbios_config.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | 9 | // SmbiosConfig captures system config information. 10 | type SmbiosConfig struct { 11 | Type SmbiosType `json:"-"` 12 | Handle int `json:"handle"` 13 | Options []string `json:"options,omitempty"` 14 | } 15 | 16 | func (s SmbiosConfig) SmbiosType() SmbiosType { 17 | return s.Type 18 | } 19 | 20 | func NewSmbiosConfig(info C.smbios_config_t) (*SmbiosConfig, error) { 21 | return &SmbiosConfig{ 22 | Type: SmbiosTypeConfig, 23 | Handle: int(info.handle), 24 | Options: ReadStringList(info.options), 25 | }, nil 26 | } 27 | -------------------------------------------------------------------------------- /pkg/hwinfo/smbios_group_associations.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | import "unsafe" 9 | 10 | // SmbiosGroupAssociations captures group associations. 11 | type SmbiosGroupAssociations struct { 12 | Type SmbiosType `json:"-"` 13 | Handle int `json:"handle"` 14 | Name string `json:"name"` // group name 15 | Handles []int `json:"handles,omitempty"` // array of item handles 16 | } 17 | 18 | func (s SmbiosGroupAssociations) SmbiosType() SmbiosType { 19 | return s.Type 20 | } 21 | 22 | func NewSmbiosGroup(info C.smbios_group_t) (*SmbiosGroupAssociations, error) { 23 | return &SmbiosGroupAssociations{ 24 | Type: SmbiosTypeGroupAssociations, 25 | Handle: int(info.handle), 26 | Name: C.GoString(info.name), 27 | Handles: ReadIntArray(unsafe.Pointer(info.item_handles), int(info.items_len)), 28 | }, nil 29 | } 30 | -------------------------------------------------------------------------------- /pkg/hwinfo/smbios_hardware_security.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | 9 | // SmbiosHardwareSecurity captures hardware security information. 10 | type SmbiosHardwareSecurity struct { 11 | Type SmbiosType `json:"-"` 12 | Handle int `json:"handle"` 13 | Power *ID `json:"power"` // power-on password status 14 | Keyboard *ID `json:"keyboard"` // keyboard password status 15 | Admin *ID `json:"admin"` // admin password status 16 | Reset *ID `json:"reset"` // front panel reset status 17 | } 18 | 19 | func (s SmbiosHardwareSecurity) SmbiosType() SmbiosType { 20 | return s.Type 21 | } 22 | 23 | func NewSmbiosSecure(info C.smbios_secure_t) (*SmbiosHardwareSecurity, error) { 24 | return &SmbiosHardwareSecurity{ 25 | Type: SmbiosTypeHardwareSecurity, 26 | Handle: int(info.handle), 27 | Power: NewID(info.power), 28 | Keyboard: NewID(info.keyboard), 29 | Admin: NewID(info.admin), 30 | Reset: NewID(info.reset), 31 | }, nil 32 | } 33 | -------------------------------------------------------------------------------- /pkg/hwinfo/smbios_language.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | 9 | // SmbiosLanguage captures language information. 10 | type SmbiosLanguage struct { 11 | Type SmbiosType `json:"-"` 12 | Handle int `json:"handle"` 13 | Languages []string `json:"languages,omitempty"` 14 | CurrentLanguage string `json:"-"` 15 | } 16 | 17 | func (s SmbiosLanguage) SmbiosType() SmbiosType { 18 | return s.Type 19 | } 20 | 21 | func NewSmbiosLang(info C.smbios_lang_t) (*SmbiosLanguage, error) { 22 | return &SmbiosLanguage{ 23 | Type: SmbiosTypeLanguage, 24 | Handle: int(info.handle), 25 | Languages: ReadStringList(info.strings), 26 | CurrentLanguage: C.GoString(info.current), 27 | }, nil 28 | } 29 | -------------------------------------------------------------------------------- /pkg/hwinfo/smbios_memory64_error.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | import "fmt" 9 | 10 | // SmbiosMemory64Error captures 32-bit memory error information. 11 | type SmbiosMemory64Error struct { 12 | Type SmbiosType `json:"-"` 13 | Handle int `json:"handle"` 14 | ErrorType *ID `json:"error_type"` // error type memory 15 | Granularity *ID `json:"granularity"` // memory array or memory partition 16 | Operation *ID `json:"operation"` // mem operation causing the rror 17 | Syndrome uint32 `json:"syndrome"` // vendor-specific ECC syndrome; 0: unknown 18 | ArrayAddress string `json:"array_address"` // fault address relative to mem array; 0x80000000: unknown 19 | DeviceAddress string `json:"device_address"` // fault address relative to mem array; 0x80000000: unknown 20 | Range uint32 `json:"range"` // range within which the error can be determined; 0x80000000: unknown 21 | } 22 | 23 | func (s SmbiosMemory64Error) SmbiosType() SmbiosType { 24 | return s.Type 25 | } 26 | 27 | func NewSmbiosMem64Error(info C.smbios_mem64error_t) (*SmbiosMemory64Error, error) { 28 | return &SmbiosMemory64Error{ 29 | Type: SmbiosTypeMemory64Error, 30 | Handle: int(info.handle), 31 | ErrorType: NewID(info.err_type), 32 | Granularity: NewID(info.granularity), 33 | Operation: NewID(info.operation), 34 | Syndrome: uint32(info.syndrome), 35 | ArrayAddress: fmt.Sprintf("0x%x", uint(info.array_addr)), 36 | DeviceAddress: fmt.Sprintf("0x%x", uint(info.device_addr)), 37 | Range: uint32(info._range), 38 | }, nil 39 | } 40 | -------------------------------------------------------------------------------- /pkg/hwinfo/smbios_memory_array.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | import "fmt" 9 | 10 | // SmbiosMemoryArray captures physical memory array information (consists of several memory devices). 11 | type SmbiosMemoryArray struct { 12 | Type SmbiosType `json:"-"` 13 | Handle int `json:"handle"` 14 | Location *ID `json:"location"` // memory device location 15 | Usage *ID `json:"usage"` // memory usage 16 | ECC *ID `json:"ecc"` // ECC types 17 | MaxSize string `json:"max_size"` // max memory size in KB 18 | ErrorHandle int `json:"error_handle"` // points to error info record; 0xfffe: not supported, 0xffff: no error 19 | Slots uint16 `json:"slots"` // slots or sockets for this device 20 | } 21 | 22 | func (s SmbiosMemoryArray) SmbiosType() SmbiosType { 23 | return s.Type 24 | } 25 | 26 | func NewSmbiosMemArray(info C.smbios_memarray_t) (*SmbiosMemoryArray, error) { 27 | return &SmbiosMemoryArray{ 28 | Type: SmbiosTypeMemoryArray, 29 | Handle: int(info.handle), 30 | Location: NewID(info.location), 31 | Usage: NewID(info.use), 32 | ECC: NewID(info.ecc), 33 | MaxSize: fmt.Sprintf("0x%x", uint(info.max_size)), 34 | ErrorHandle: int(info.error_handle), 35 | Slots: uint16(info.slots), 36 | }, nil 37 | } 38 | -------------------------------------------------------------------------------- /pkg/hwinfo/smbios_memory_array_mapped_address.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | import "fmt" 9 | 10 | // SmbiosMemoryArrayMappedAddress captures physical memory array information (consists of several memory devices). 11 | type SmbiosMemoryArrayMappedAddress struct { 12 | Type SmbiosType `json:"-"` 13 | Handle int `json:"handle"` 14 | ArrayHandle int `json:"array_handle"` // memory array this mapping belongs to 15 | StartAddress string `json:"start_address"` // memory range start address 16 | EndAddress string `json:"end_address"` // end address 17 | PartWidth uint16 `json:"part_width"` // number of memory devices 18 | } 19 | 20 | func (s SmbiosMemoryArrayMappedAddress) SmbiosType() SmbiosType { 21 | return s.Type 22 | } 23 | 24 | func NewSmbiosMemArrayMap(info C.smbios_memarraymap_t) (*SmbiosMemoryArrayMappedAddress, error) { 25 | return &SmbiosMemoryArrayMappedAddress{ 26 | Type: SmbiosTypeMemoryArrayMappedAddress, 27 | Handle: int(info.handle), 28 | ArrayHandle: int(info.array_handle), 29 | StartAddress: fmt.Sprintf("0x%x", uint64(info.start_addr)), 30 | EndAddress: fmt.Sprintf("0x%x", uint64(info.end_addr)), 31 | PartWidth: uint16(info.part_width), 32 | }, nil 33 | } 34 | -------------------------------------------------------------------------------- /pkg/hwinfo/smbios_memory_device.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | 9 | // SmbiosMemoryDevice captures system slot information. 10 | // 11 | //nolint:lll 12 | type SmbiosMemoryDevice struct { 13 | Type SmbiosType `json:"-"` 14 | Handle int `json:"handle"` 15 | Location string `json:"location"` // device location 16 | BankLocation string `json:"bank_location"` // bank location 17 | Manufacturer string `json:"manufacturer"` 18 | Serial string `json:"-"` // omit from json 19 | AssetTag string `json:"-"` 20 | PartNumber string `json:"part_number"` 21 | ArrayHandle int `json:"array_handle"` // memory array this device belongs to 22 | ErrorHandle int `json:"error_handle"` // points to error info record; 0xfffe: not supported, 0xffff: no error 23 | Width uint16 `json:"width"` // data width in bits 24 | ECCBits uint8 `json:"ecc_bits"` // ecc bits 25 | Size uint `json:"size"` // kB 26 | FormFactor *ID `json:"form_factor"` 27 | Set uint8 `json:"set"` // 0: does not belong to a set; 1-0xfe: set number; 0xff: unknown 28 | MemoryType *ID `json:"memory_type"` 29 | MemoryTypeDetails []string `json:"memory_type_details"` 30 | Speed uint32 `json:"speed"` // MHz 31 | } 32 | 33 | func (s SmbiosMemoryDevice) SmbiosType() SmbiosType { 34 | return s.Type 35 | } 36 | 37 | func NewSmbiosMemDevice(info C.smbios_memdevice_t) (*SmbiosMemoryDevice, error) { 38 | return &SmbiosMemoryDevice{ 39 | Type: SmbiosTypeMemoryDevice, 40 | Handle: int(info.handle), 41 | Location: C.GoString(info.location), 42 | BankLocation: C.GoString(info.bank), 43 | Manufacturer: C.GoString(info.manuf), 44 | Serial: C.GoString(info.serial), 45 | AssetTag: C.GoString(info.asset), 46 | PartNumber: C.GoString(info.part), 47 | ArrayHandle: int(info.array_handle), 48 | ErrorHandle: int(info.error_handle), 49 | Width: uint16(info.width), 50 | ECCBits: uint8(info.eccbits), 51 | Size: uint(info.size), 52 | FormFactor: NewID(info.form), 53 | Set: uint8(info.set), 54 | MemoryType: NewID(info.mem_type), 55 | MemoryTypeDetails: ReadStringList(info.type_detail.str), 56 | Speed: uint32(info.speed), 57 | }, nil 58 | } 59 | -------------------------------------------------------------------------------- /pkg/hwinfo/smbios_memory_device_mapped_address.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | import "fmt" 9 | 10 | // SmbiosMemoryDeviceMappedAddress captures physical memory array information (consists of several memory devices). 11 | // 12 | //nolint:lll 13 | type SmbiosMemoryDeviceMappedAddress struct { 14 | Type SmbiosType `json:"-"` 15 | Handle int `json:"handle"` 16 | MemoryDeviceHandle int `json:"memory_device_handle"` 17 | ArrayMapHandle int `json:"array_map_handle"` 18 | StartAddress string `json:"start_address"` 19 | EndAddress string `json:"end_address"` 20 | RowPosition uint16 `json:"row_position"` // position of the referenced memory device in a row of the address partition 21 | InterleavePosition uint16 `json:"interleave_position"` // dto, in an interleave 22 | InterleaveDepth uint16 `json:"interleave_depth"` // number of consecutive rows 23 | } 24 | 25 | func (s SmbiosMemoryDeviceMappedAddress) SmbiosType() SmbiosType { 26 | return s.Type 27 | } 28 | 29 | func NewSmbiosMemDeviceMap(info C.smbios_memdevicemap_t) (*SmbiosMemoryDeviceMappedAddress, error) { 30 | return &SmbiosMemoryDeviceMappedAddress{ 31 | Type: SmbiosTypeMemoryDeviceMappedAddress, 32 | Handle: int(info.handle), 33 | MemoryDeviceHandle: int(info.memdevice_handle), 34 | ArrayMapHandle: int(info.arraymap_handle), 35 | StartAddress: fmt.Sprintf("0x%x", uint64(info.start_addr)), 36 | EndAddress: fmt.Sprintf("0x%x", uint64(info.end_addr)), 37 | RowPosition: uint16(info.row_pos), 38 | InterleavePosition: uint16(info.interleave_pos), 39 | InterleaveDepth: uint16(info.interleave_depth), 40 | }, nil 41 | } 42 | -------------------------------------------------------------------------------- /pkg/hwinfo/smbios_memory_error.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | import "fmt" 9 | 10 | // SmbiosMemoryError captures 32-bit memory error information. 11 | type SmbiosMemoryError struct { 12 | Type SmbiosType `json:"-"` 13 | Handle int `json:"handle"` 14 | ErrorType *ID `json:"error_type"` // error type memory 15 | Granularity *ID `json:"granularity"` // memory array or memory partition 16 | Operation *ID `json:"operation"` // mem operation causing the rror 17 | Syndrome uint32 `json:"syndrome"` // vendor-specific ECC syndrome; 0: unknown 18 | ArrayAddress string `json:"array_address"` // fault address relative to mem array; 0x80000000: unknown 19 | DeviceAddress string `json:"device_address"` // fault address relative to mem array; 0x80000000: unknown 20 | Range uint32 `json:"range"` // range within which the error can be determined; 0x80000000: unknown 21 | } 22 | 23 | func (s SmbiosMemoryError) SmbiosType() SmbiosType { 24 | return s.Type 25 | } 26 | 27 | func NewSmbiosMemError(info C.smbios_memerror_t) (*SmbiosMemoryError, error) { 28 | return &SmbiosMemoryError{ 29 | Type: SmbiosTypeMemoryError, 30 | Handle: int(info.handle), 31 | ErrorType: NewID(info.err_type), 32 | Granularity: NewID(info.granularity), 33 | Operation: NewID(info.operation), 34 | Syndrome: uint32(info.syndrome), 35 | ArrayAddress: fmt.Sprintf("0x%x", uint(info.array_addr)), 36 | DeviceAddress: fmt.Sprintf("0x%x", uint(info.device_addr)), 37 | Range: uint32(info._range), 38 | }, nil 39 | } 40 | -------------------------------------------------------------------------------- /pkg/hwinfo/smbios_oem_strings.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | 9 | // SmbiosOEMStrings captures OEM information. 10 | type SmbiosOEMStrings struct { 11 | Type SmbiosType `json:"-"` 12 | Handle int `json:"handle"` 13 | Strings []string `json:"strings,omitempty"` 14 | } 15 | 16 | func (s SmbiosOEMStrings) SmbiosType() SmbiosType { 17 | return s.Type 18 | } 19 | 20 | func NewSmbiosOEM(info C.smbios_oem_t) (*SmbiosOEMStrings, error) { 21 | return &SmbiosOEMStrings{ 22 | Type: SmbiosTypeOEMStrings, 23 | Handle: int(info.handle), 24 | Strings: ReadStringList(info.oem_strings), 25 | }, nil 26 | } 27 | -------------------------------------------------------------------------------- /pkg/hwinfo/smbios_onboard.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | 7 | char* smbios_onboard_get_name(smbios_onboard_t sm, int i) { return sm.dev[i].name; } 8 | hd_id_t smbios_onboard_get_type(smbios_onboard_t sm, int i) { return sm.dev[i].type; } 9 | unsigned smbios_onboard_get_status(smbios_onboard_t sm, int i) { return sm.dev[i].status; } 10 | */ 11 | import "C" 12 | 13 | type OnboardDevice struct { 14 | Name string `json:"name"` 15 | Type *ID `json:"type"` 16 | Enabled bool `json:"enabled"` 17 | } 18 | 19 | // SmbiosOnboard captures overall system related information. 20 | type SmbiosOnboard struct { 21 | Type SmbiosType `json:"-"` 22 | Handle int `json:"handle"` 23 | Devices []OnboardDevice `json:"devices,omitempty"` 24 | } 25 | 26 | func (s SmbiosOnboard) SmbiosType() SmbiosType { 27 | return s.Type 28 | } 29 | 30 | func NewSmbiosOnboard(info C.smbios_onboard_t) (*SmbiosOnboard, error) { 31 | count := int(info.dev_len) 32 | devices := make([]OnboardDevice, 0, count) 33 | 34 | for i := range count { 35 | devices = append(devices, OnboardDevice{ 36 | Name: C.GoString(C.smbios_onboard_get_name(info, C.int(i))), 37 | Type: NewID(C.smbios_onboard_get_type(info, C.int(i))), 38 | Enabled: uint(C.smbios_onboard_get_status(info, C.int(i))) == 1, 39 | }) 40 | } 41 | 42 | return &SmbiosOnboard{ 43 | Type: SmbiosTypeOnboard, 44 | Handle: int(info.handle), 45 | Devices: devices, 46 | }, nil 47 | } 48 | -------------------------------------------------------------------------------- /pkg/hwinfo/smbios_pointing_device.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | 9 | // SmbiosPointingDevice captures pointing device (aka 'mouse') information. 10 | type SmbiosPointingDevice struct { 11 | Type SmbiosType `json:"-"` 12 | Handle int `json:"handle"` 13 | MouseType *ID `json:"mouse_type"` 14 | Interface *ID `json:"interface"` 15 | Buttons uint16 `json:"buttons"` 16 | } 17 | 18 | func (s SmbiosPointingDevice) SmbiosType() SmbiosType { 19 | return s.Type 20 | } 21 | 22 | func NewSmbiosMouse(info C.smbios_mouse_t) (*SmbiosPointingDevice, error) { 23 | return &SmbiosPointingDevice{ 24 | Type: SmbiosTypePointingDevice, 25 | Handle: int(info.handle), 26 | MouseType: NewID(info.mtype), 27 | Interface: NewID(info._interface), 28 | Buttons: uint16(info.buttons), 29 | }, nil 30 | } 31 | -------------------------------------------------------------------------------- /pkg/hwinfo/smbios_port_connector.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | 9 | // SmbiosPortConnector captures port connector information. 10 | type SmbiosPortConnector struct { 11 | Type SmbiosType `json:"-"` 12 | Handle int `json:"handle"` 13 | PortType *ID `json:"port_type"` 14 | InternalConnectorType *ID `json:"internal_connector_type,omitempty"` 15 | InternalReferenceDesignator string `json:"internal_reference_designator,omitempty"` 16 | ExternalConnectorType *ID `json:"external_connector_type,omitempty"` 17 | ExternalReferenceDesignator string `json:"external_reference_designator,omitempty"` 18 | } 19 | 20 | func (s SmbiosPortConnector) SmbiosType() SmbiosType { 21 | return s.Type 22 | } 23 | 24 | func NewSmbiosConnect(info C.smbios_connect_t) (*SmbiosPortConnector, error) { 25 | return &SmbiosPortConnector{ 26 | Type: SmbiosTypePortConnector, 27 | Handle: int(info.handle), 28 | PortType: NewID(info.port_type), 29 | InternalConnectorType: NewID(info.i_type), 30 | InternalReferenceDesignator: C.GoString(info.i_des), 31 | ExternalConnectorType: NewID(info.x_type), 32 | ExternalReferenceDesignator: C.GoString(info.x_des), 33 | }, nil 34 | } 35 | -------------------------------------------------------------------------------- /pkg/hwinfo/smbios_power_controls.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | 9 | // SmbiosPowerControls captures system power controls information. 10 | type SmbiosPowerControls struct { 11 | Type SmbiosType `json:"-"` 12 | Handle int `json:"handle"` 13 | Month uint8 `json:"month"` // next scheduled power-on month 14 | Day uint16 `json:"day"` // dto, day 15 | Hour uint8 `json:"hour"` // dto, hour 16 | Minute uint8 `json:"minute"` // dto, minute 17 | Second uint8 `json:"second"` // dto, second 18 | } 19 | 20 | func (s SmbiosPowerControls) SmbiosType() SmbiosType { 21 | return s.Type 22 | } 23 | 24 | func NewSmbiosPower(info C.smbios_power_t) (*SmbiosPowerControls, error) { 25 | return &SmbiosPowerControls{ 26 | Type: SmbiosTypePowerControls, 27 | Handle: int(info.handle), 28 | Month: uint8(info.month), 29 | Day: uint16(info.day), 30 | Hour: uint8(info.hour), 31 | Minute: uint8(info.minute), 32 | Second: uint8(info.second), 33 | }, nil 34 | } 35 | -------------------------------------------------------------------------------- /pkg/hwinfo/smbios_processor.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | 9 | // SmbiosProcessor captures processor information. 10 | type SmbiosProcessor struct { 11 | Type SmbiosType `json:"-"` 12 | Handle int `json:"handle"` 13 | Socket string `json:"socket"` 14 | SocketType *ID `json:"socket_type"` 15 | SocketPopulated bool `json:"socket_populated"` // true: populated, false: empty 16 | Manufacturer string `json:"manufacturer"` 17 | Version string `json:"version"` 18 | Serial string `json:"-"` // omit from json output 19 | AssetTag string `json:"-"` // asset tag 20 | Part string `json:"part"` // part number 21 | ProcessorType *ID `json:"processor_type"` 22 | ProcessorFamily *ID `json:"processor_family"` 23 | ProcessorID uint64 `json:"-"` // omit from json 24 | ProcessorStatus *ID `json:"processor_status"` 25 | Voltage uint `json:"-"` 26 | ClockExt uint32 `json:"clock_ext"` // MHz 27 | ClockMax uint32 `json:"clock_max"` // MHz 28 | ClockCurrent uint `json:"-"` // MHz 29 | CacheHandleL1 int `json:"cache_handle_l1"` // handle of L1 cache 30 | CacheHandleL2 int `json:"cache_handle_l2"` // handle of L2 cache 31 | CacheHandleL3 int `json:"cache_handle_l3"` // handle of L3 cache 32 | } 33 | 34 | func (s SmbiosProcessor) SmbiosType() SmbiosType { 35 | return s.Type 36 | } 37 | 38 | func NewSmbiosProcessor(info C.smbios_processor_t) (*SmbiosProcessor, error) { 39 | return &SmbiosProcessor{ 40 | Type: SmbiosTypeProcessor, 41 | Handle: int(info.handle), 42 | Socket: C.GoString(info.socket), 43 | SocketType: NewID(info.upgrade), 44 | SocketPopulated: uint(info.sock_status) == 1, 45 | Manufacturer: C.GoString(info.manuf), 46 | Version: C.GoString(info.version), 47 | Serial: C.GoString(info.serial), 48 | AssetTag: C.GoString(info.asset), 49 | Part: C.GoString(info.part), 50 | ProcessorType: NewID(info.pr_type), 51 | ProcessorFamily: NewID(info.family), 52 | ProcessorID: uint64(info.cpu_id), 53 | ProcessorStatus: NewID(info.cpu_status), 54 | Voltage: uint(info.voltage), 55 | ClockExt: uint32(info.ext_clock), 56 | ClockMax: uint32(info.max_speed), 57 | ClockCurrent: uint(info.current_speed), 58 | CacheHandleL1: int(info.l1_cache), 59 | CacheHandleL2: int(info.l2_cache), 60 | CacheHandleL3: int(info.l3_cache), 61 | }, nil 62 | } 63 | -------------------------------------------------------------------------------- /pkg/hwinfo/smbios_slot.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | 9 | // SmbiosSlot captures system slot information. 10 | type SmbiosSlot struct { 11 | Type SmbiosType `json:"-"` 12 | Handle int `json:"handle"` 13 | Designation string `json:"designation,omitempty"` 14 | SlotType *ID `json:"slot_type"` 15 | BusWidth *ID `json:"bus_width"` 16 | Usage *ID `json:"usage"` 17 | Length *ID `json:"length"` 18 | ID uint16 `json:"id"` 19 | Features []string `json:"features,omitempty"` 20 | } 21 | 22 | func (s SmbiosSlot) SmbiosType() SmbiosType { 23 | return s.Type 24 | } 25 | 26 | func NewSmbiosSlot(info C.smbios_slot_t) (*SmbiosSlot, error) { 27 | return &SmbiosSlot{ 28 | Type: SmbiosTypeSlot, 29 | Handle: int(info.handle), 30 | Designation: C.GoString(info.desig), 31 | SlotType: NewID(info.slot_type), 32 | BusWidth: NewID(info.bus_width), 33 | Usage: NewID(info.usage), 34 | Length: NewID(info.length), 35 | ID: uint16(info.id), 36 | Features: ReadStringList(info.feature.str), 37 | }, nil 38 | } 39 | -------------------------------------------------------------------------------- /pkg/hwinfo/smbios_sys_info.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | 9 | // SmbiosSystem captures overall system related information. 10 | type SmbiosSystem struct { 11 | Type SmbiosType `json:"-"` 12 | Handle int `json:"handle"` 13 | Manufacturer string `json:"manufacturer"` 14 | Product string `json:"product"` 15 | Version string `json:"version"` 16 | Serial string `json:"-"` // omit from json output 17 | UUID string `json:"-"` // universal unique id; all 0x00: undef, all 0xff: undef but settable 18 | WakeUp *ID `json:"wake_up"` // wake-up type 19 | } 20 | 21 | func (s SmbiosSystem) SmbiosType() SmbiosType { 22 | return s.Type 23 | } 24 | 25 | func NewSmbiosSysInfo(info C.smbios_sysinfo_t) (*SmbiosSystem, error) { 26 | return &SmbiosSystem{ 27 | Type: SmbiosTypeSystem, 28 | Handle: int(info.handle), 29 | Manufacturer: C.GoString(info.manuf), 30 | Product: C.GoString(info.product), 31 | Version: C.GoString(info.version), 32 | Serial: C.GoString(info.serial), 33 | // todo uuid 34 | WakeUp: NewID(info.wake_up), 35 | }, nil 36 | } 37 | -------------------------------------------------------------------------------- /pkg/hwinfo/utils.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | /* 4 | #cgo pkg-config: hwinfo 5 | #include 6 | */ 7 | import "C" 8 | import "unsafe" 9 | 10 | func ReadStringList(list *C.str_list_t) []string { 11 | var result []string 12 | for entry := list; entry != nil; entry = entry.next { 13 | result = append(result, C.GoString(entry.str)) 14 | } 15 | return result 16 | } 17 | 18 | func ReadUint64Array(arr unsafe.Pointer, length int) []uint64 { 19 | start := uintptr(arr) 20 | result := make([]uint64, length) 21 | for i := range result { 22 | next := start + uintptr(i*C.sizeof_uint64_t) 23 | result[i] = *((*uint64)(unsafe.Pointer(next))) //nolint:govet 24 | } 25 | return result 26 | } 27 | 28 | func ReadUintArray(arr unsafe.Pointer, length int) []uint { 29 | start := uintptr(arr) 30 | result := make([]uint, length) 31 | for i := range result { 32 | next := start + uintptr(i*C.sizeof_uint) 33 | result[i] = *((*uint)(unsafe.Pointer(next))) //nolint:govet 34 | } 35 | return result 36 | } 37 | 38 | func ReadIntArray(arr unsafe.Pointer, length int) []int { 39 | // TODO see if we can use generics to combine some of these methods 40 | start := uintptr(arr) 41 | result := make([]int, length) 42 | for i := range result { 43 | next := start + uintptr(i*C.sizeof_uint) 44 | result[i] = *((*int)(unsafe.Pointer(next))) //nolint:govet 45 | } 46 | return result 47 | } 48 | 49 | func ReadByteArray(arr unsafe.Pointer, length int) []byte { 50 | start := uintptr(arr) 51 | result := make([]byte, length) 52 | for i := range result { 53 | next := start + uintptr(i*C.sizeof_uint) 54 | result[i] = *((*byte)(unsafe.Pointer(next))) //nolint:govet 55 | } 56 | return result 57 | } 58 | -------------------------------------------------------------------------------- /pkg/hwinfo/virtual_network_devices.go: -------------------------------------------------------------------------------- 1 | package hwinfo 2 | 3 | // original list taken from systemd.network. 4 | var virtualNetworkDevices = map[string]bool{ 5 | "bonding": true, 6 | "bridge": true, 7 | "dummy": true, 8 | "ip_gre": true, 9 | "ip6_gre": true, 10 | "ipip": true, 11 | "ipvlan": true, 12 | "macvlan": true, 13 | "sit": true, 14 | "tun": true, 15 | "veth": true, 16 | "8021q": true, 17 | "ip_vti": true, 18 | "ip6_vti": true, 19 | "vxlan": true, 20 | "geneve": true, 21 | "macsec": true, 22 | "vrf": true, 23 | "vcan": true, 24 | "vxcan": true, 25 | "wireguard": true, 26 | "nlmon": true, 27 | "fou": true, 28 | "ifb": true, 29 | "bareudp": true, 30 | "batman-adv": true, 31 | "ib_ipoib": true, 32 | // FIXME: could not confirm the following netdev drivers: 33 | // "l2tp": true, 34 | // "xfrm": true, 35 | // "erspan": true, 36 | // "ip6tnl": true, 37 | } 38 | -------------------------------------------------------------------------------- /pkg/linux/input/input_test.go: -------------------------------------------------------------------------------- 1 | //nolint:lll 2 | package input_test 3 | 4 | import ( 5 | "bytes" 6 | "io" 7 | "testing" 8 | 9 | "github.com/numtide/nixos-facter/pkg/linux/input" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | const ( 14 | devices = `I: Bus=0003 Vendor=1038 Product=1634 Version=0111 15 | N: Name="SteelSeries SteelSeries Apex 9 TKL" 16 | P: Phys=usb-0000:08:00.3-2.4.2/input2 17 | S: Sysfs=/devices/pci0000:00/0000:00:01.2/0000:02:00.0/0000:03:08.0/0000:08:00.3/usb3/3-2/3-2.4/3-2.4.2/3-2.4.2:1.2/0003:1038:1634.000B/input/input11 18 | U: Uniq= 19 | H: Handlers=sysrq kbd leds event4 20 | B: PROP=0 21 | B: EV=120013 22 | B: KEY=1000000000007 ff9f207ac14057ff febeffdfffefffff fffffffffffffffe 23 | B: MSC=10 24 | B: LED=7 25 | 26 | I: Bus=0003 Vendor=046d Product=407b Version=0111 27 | N: Name="Logitech MX Vertical" 28 | P: Phys=usb-0000:08:00.3-2.2/input2:1 29 | S: Sysfs=/devices/pci0000:00/0000:00:01.2/0000:02:00.0/0000:03:08.0/0000:08:00.3/usb3/3-2/3-2.2/3-2.2:1.2/0003:046D:C52B.0004/0003:046D:407B.0005/input/input8 30 | U: Uniq=5c-c6-5d-d4 31 | H: Handlers=sysrq kbd leds event1 mouse0 32 | B: PROP=0 33 | B: EV=12001f 34 | B: KEY=3f00033fff 0 0 483ffff17aff32d bfd4444600000000 ffff0001 130ff38b17d007 ffff7bfad9415fff ffbeffdfffefffff fffffffffffffffe 35 | B: REL=1943 36 | B: ABS=100000000 37 | B: MSC=10 38 | B: LED=1f 39 | 40 | I: Bus=0003 Vendor=b58e Product=9e84 Version=0100 41 | N: Name="Blue Microphones Yeti Stereo Microphone Consumer Control" 42 | P: Phys=usb-0000:0f:00.3-4/input3 43 | S: Sysfs=/devices/pci0000:00/0000:00:08.1/0000:0f:00.3/usb5/5-4/5-4:1.3/0003:B58E:9E84.0001/input/input0 44 | U: Uniq=797_2020/06/26_02565 45 | H: Handlers=kbd event0 46 | B: PROP=0 47 | B: EV=1b 48 | B: KEY=1 0 7800000000 e000000000000 0 49 | B: ABS=10000000000 50 | B: MSC=10 51 | 52 | ` 53 | ) 54 | 55 | func TestReadDevices(t *testing.T) { 56 | as := require.New(t) 57 | r := io.NopCloser(bytes.NewReader([]byte(devices))) 58 | 59 | devices, err := input.ReadDevices(r) 60 | as.NoError(err) 61 | 62 | expected := []*input.Device{ 63 | { 64 | Bus: input.BusUsb, 65 | Vendor: uint16(0x1038), 66 | Product: uint16(0x1634), 67 | Version: uint16(0x0111), 68 | Name: "SteelSeries SteelSeries Apex 9 TKL", //nolint:dupword 69 | Phys: "usb-0000:08:00.3-2.4.2/input2", 70 | Sysfs: "/devices/pci0000:00/0000:00:01.2/0000:02:00.0/0000:03:08.0/0000:08:00.3/usb3/3-2/3-2.4/3-2.4.2/3-2.4.2:1.2/0003:1038:1634.000B/input/input11", 71 | Handlers: []string{"sysrq", "kbd", "leds", "event4"}, 72 | Capabilities: map[string]string{ 73 | "PROP": "0", 74 | "EV": "120013", 75 | "KEY": "1000000000007 ff9f207ac14057ff febeffdfffefffff fffffffffffffffe", 76 | "MSC": "10", 77 | "LED": "7", 78 | }, 79 | }, 80 | { 81 | Bus: input.BusUsb, 82 | Vendor: uint16(0x046d), 83 | Product: uint16(0x407b), 84 | Version: uint16(0x0111), 85 | Name: "Logitech MX Vertical", 86 | Phys: "usb-0000:08:00.3-2.2/input2:1", 87 | Sysfs: "/devices/pci0000:00/0000:00:01.2/0000:02:00.0/0000:03:08.0/0000:08:00.3/usb3/3-2/3-2.2/3-2.2:1.2/0003:046D:C52B.0004/0003:046D:407B.0005/input/input8", 88 | Handlers: []string{"sysrq", "kbd", "leds", "event1", "mouse0"}, 89 | Capabilities: map[string]string{ 90 | "PROP": "0", 91 | "EV": "12001f", 92 | "KEY": "3f00033fff 0 0 483ffff17aff32d bfd4444600000000 ffff0001 130ff38b17d007 ffff7bfad9415fff ffbeffdfffefffff fffffffffffffffe", 93 | "REL": "1943", 94 | "ABS": "100000000", 95 | "MSC": "10", 96 | "LED": "1f", 97 | }, 98 | }, 99 | { 100 | Bus: input.BusUsb, 101 | Vendor: uint16(0xb58e), 102 | Product: uint16(0x9e84), 103 | Version: uint16(0x0100), 104 | Name: "Blue Microphones Yeti Stereo Microphone Consumer Control", 105 | Phys: "usb-0000:0f:00.3-4/input3", 106 | Sysfs: "/devices/pci0000:00/0000:00:08.1/0000:0f:00.3/usb5/5-4/5-4:1.3/0003:B58E:9E84.0001/input/input0", 107 | Handlers: []string{"kbd", "event0"}, 108 | Capabilities: map[string]string{ 109 | "PROP": "0", 110 | "EV": "1b", 111 | "KEY": "1 0 7800000000 e000000000000 0", 112 | "ABS": "10000000000", 113 | "MSC": "10", 114 | }, 115 | }, 116 | } 117 | 118 | as.EqualValues(expected, devices) 119 | } 120 | -------------------------------------------------------------------------------- /pkg/virt/virt.go: -------------------------------------------------------------------------------- 1 | // Package virt contains utilities for detecting virtualized environments. 2 | // It has been adapted from [systemd]. 3 | // 4 | // [systemd]: https://github.com/systemd/systemd/blob/main/src/basic/virt.c 5 | package virt 6 | 7 | import ( 8 | "fmt" 9 | "log/slog" 10 | "os/exec" 11 | "strings" 12 | ) 13 | 14 | // Type represents various virtualisation and container types. 15 | // 16 | //go:generate enumer -type=Type -json -text -transform=snake -trimprefix Type -output=./virt_enum_type.go 17 | type Type int //nolint:recvcheck 18 | 19 | //nolint:revive,stylecheck 20 | const ( 21 | TypeNone Type = iota 22 | TypeKvm 23 | TypeAmazon 24 | TypeQemu 25 | TypeBochs 26 | TypeXen 27 | TypeUml 28 | TypeVmware 29 | TypeOracle 30 | TypeMicrosoft 31 | TypeZvm 32 | TypeParallels 33 | TypeBhyve 34 | TypeQnx 35 | TypeAcrn 36 | TypePowervm 37 | TypeApple 38 | TypeSre 39 | TypeGoogle 40 | TypeVmOther 41 | TypeSystemdNspawn 42 | TypeLxcLibvirt 43 | TypeLxc 44 | TypeOpenvz 45 | TypeDocker 46 | TypePodman 47 | TypeRkt 48 | TypeWsl 49 | TypeProot 50 | TypePouch 51 | TypeContainerOther 52 | ) 53 | 54 | // Detect identifies the virtualisation type of the current system. 55 | // Returns the detected Type and an error if detection fails. 56 | func Detect() (Type, error) { 57 | out, err := exec.Command("systemd-detect-virt").CombinedOutput() 58 | outStr := strings.Trim(string(out), "\n") 59 | 60 | // note: systemd-detect-virt exits with status 1 when "none" is detected 61 | //nolint:staticcheck 62 | if !(outStr == "none" || err == nil) { 63 | slog.Error("failed to detect virtualisation type", "output", out) 64 | 65 | return TypeNone, fmt.Errorf("failed to detect virtualisation type: %w", err) 66 | } 67 | 68 | // we use snake case, but systemd-detect-virt uses hyphen case 69 | virtType := strings.ReplaceAll(outStr, "-", "_") 70 | 71 | return TypeString(virtType) 72 | } 73 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:recommended"], 4 | "lockFileMaintenance": { "enabled": true }, 5 | "nix": { 6 | "enabled": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /scripts/create-release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu -o pipefail 4 | 5 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" 6 | cd "$SCRIPT_DIR/.." 7 | 8 | version=${1:-} 9 | if [[ -z $version ]]; then 10 | echo "USAGE: $0 version" >&2 11 | exit 1 12 | fi 13 | 14 | if [[ "$(git symbolic-ref --short HEAD)" != "main" ]]; then 15 | echo "must be on main branch" >&2 16 | exit 1 17 | fi 18 | 19 | # ensure we are up-to-date 20 | uncommitted_changes=$(git diff --compact-summary) 21 | if [[ -n $uncommitted_changes ]]; then 22 | echo -e "There are uncommitted changes, exiting:\n${uncommitted_changes}" >&2 23 | exit 1 24 | fi 25 | git pull git@github.com:numtide/nixos-facter main 26 | unpushed_commits=$(git log --format=oneline origin/main..main) 27 | if [[ $unpushed_commits != "" ]]; then 28 | echo -e "\nThere are unpushed changes, exiting:\n$unpushed_commits" >&2 29 | exit 1 30 | fi 31 | sed -i "s/version = \".*\"/version = \"$version\"/" ./nix/packages/nixos-facter/package.nix 32 | git add ./nix/packages/nixos-facter/package.nix 33 | git commit -m "release: v${version}" 34 | nix flake check -vL 35 | git tag "v${version}" 36 | 37 | echo "now run 'git push --tags origin main'" 38 | --------------------------------------------------------------------------------