package packages import ( "bufio" "errors" "io" "os" "runtime" "strings" ) // OSRelease identifies the distribution well enough to select an advisory // feed. VersionID is not optional: Ubuntu 22.04 and 24.04 publish different // fixed versions for the same CVE. type OSRelease struct { Family string VersionID string Arch string } // ParseOSRelease reads the os-release format: KEY=value, one per line, with // values optionally quoted, and # comments. // // The quote stripping handles both ID=ubuntu and ID="rocky", which real // distributions both emit. func ParseOSRelease(r io.Reader) (OSRelease, error) { out := OSRelease{Arch: runtime.GOARCH} sc := bufio.NewScanner(r) for sc.Scan() { line := strings.TrimSpace(sc.Text()) if line == "" || strings.HasPrefix(line, "#") { continue } key, val, ok := strings.Cut(line, "=") if !ok { continue } val = strings.Trim(strings.TrimSpace(val), `"'`) switch strings.TrimSpace(key) { case "ID": out.Family = strings.ToLower(val) case "VERSION_ID": out.VersionID = val } } if err := sc.Err(); err != nil { return OSRelease{}, err } if out.Family == "" { return OSRelease{}, errors.New("os-release has no ID") } return out, nil } // DetectOS reads /etc/os-release. func DetectOS() (OSRelease, error) { f, err := os.Open("/etc/os-release") if err != nil { return OSRelease{}, err } defer f.Close() return ParseOSRelease(f) }