diff --git a/apt-hook/apthook/chrorder.go b/apt-hook/apthook/chrorder.go new file mode 100644 index 0000000000..c26e155866 --- /dev/null +++ b/apt-hook/apthook/chrorder.go @@ -0,0 +1,21 @@ +// auto-generated, DO NOT EDIT! +package apthook + +var chOrder = [...]int{ + -5, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, + 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, + 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 314, 315, 316, 317, 318, 319, + 320, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 347, 348, 349, 350, 351, + 352, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 379, 380, 381, -10, 383, + 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, + 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, + 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, + 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, + 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, + 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, + 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, + 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, +} diff --git a/apt-hook/apthook/counts.go b/apt-hook/apthook/counts.go new file mode 100644 index 0000000000..b99a17ba89 --- /dev/null +++ b/apt-hook/apthook/counts.go @@ -0,0 +1,285 @@ +package apthook + +import ( + "bufio" + "fmt" + "os" + "strings" + "io/ioutil" +) + +const ( + cloudIDFile = "/run/cloud-init/cloud-id" + osReleaseFile = "/etc/os-release" +) + +type SecurityCounts struct { + Standard int + ESMInfra int + ESMApps int +} + +// verifyOrigin checks if the package is from a specific origin. +func verifyOrigin(version jsonRPCPackageVersion, origin string) bool { + for _, packageOrigin := range version.Origins { + if packageOrigin.Origin == origin { + return true + } + } + return false +} + +// verifyOriginAndArchive checks if the package is from a specific origin and archive. +func verifyOriginAndArchive(version jsonRPCPackageVersion, origin, archive string) bool { + for _, packageOrigin := range version.Origins { + if packageOrigin.Origin == origin && packageOrigin.Archive == archive { + return true + } + } + return false +} + +// getCloudID retrieves the cloud ID. +func getCloudID() string { + file, err := os.Open(cloudIDFile) + if err != nil { + return "" + } + defer file.Close() + + content, err := ioutil.ReadAll(file) + if err != nil { + return "" + } + fileContent := string(content) + if strings.Contains(fileContent, "aws") { + return "AWS" + } else if strings.Contains(fileContent, "gcp") { + return "GCP" + } else if strings.Contains(fileContent, "azure") { + return "Azure" + } + return "" +} + +// ParseOSRelease processes OS release information into a struct. +func ParseOSRelease() OSRelease { + file, err := os.Open(osReleaseFile) + if err != nil { + return OSRelease{} + } + defer file.Close() + + rawData := make(map[string]string) + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + lineSplit := strings.SplitN(line, "=", 2) + if len(lineSplit) != 2 { + continue + } + key := strings.ToLower(lineSplit[0]) + value := strings.Trim(lineSplit[1], `"`) + rawData[key] = value + } + + return OSRelease{ + Name: rawData["name"], + VersionID: rawData["version_id"], + Version: rawData["version"], + VersionCodename: rawData["version_codename"], + ID: rawData["id"], + } +} + +// printPackageNames prints package names from a list. +func printPackageNames(packageNames []string) { + currLine := " " + for _, pkg := range packageNames { + if len(currLine)+1+len(pkg) >= 79 { + fmt.Println(currLine) + currLine = " " + } + currLine = currLine + " " + pkg + } + if len(currLine) > 1 { + fmt.Println(currLine) + } +} + +// getESMInfraSeries checks if the OS series is esm_infra. +func getESMInfraSeries() string { + osRelease := ParseOSRelease() + switch osRelease.VersionCodename { + case "xenial": + return "XENIAL" + case "bionic": + return "BIONIC" + default: + return "NOT_ESM_INFRA" + } +} + +func printLearnMoreContent() { + cloudID := getCloudID() + esmInfraSeries := getESMInfraSeries() + + switch esmInfraSeries { + case "XENIAL": + if cloudID == "Azure" { + fmt.Println("Learn more about Ubuntu Pro for 16.04 on Azure at https://ubuntu.com/16-04/azure") + return + } + fmt.Println("Learn more about Ubuntu Pro for 16.04 at https://ubuntu.com/16-04") + return + case "BIONIC": + if cloudID == "Azure" { + fmt.Println("Learn more about Ubuntu Pro for 18.04 on Azure at https://ubuntu.com/18-04/azure") + return + } + fmt.Println("Learn more about Ubuntu Pro for 18.04 at https://ubuntu.com/18-04") + return + default: + switch cloudID { + case "Azure": + fmt.Println("Learn more about Ubuntu Pro on Azure at https://ubuntu.com/azure/pro") + case "AWS": + fmt.Println("Learn more about Ubuntu Pro on AWS at https://ubuntu.com/aws/pro") + case "GCP": + fmt.Println("Learn more about Ubuntu Pro on GCP at https://ubuntu.com/gcp/pro") + default: + fmt.Println("Learn more about Ubuntu Pro at https://ubuntu.com/pro") + } + } +} + +// CountSecurityUpdates counts security packages from apt stats. +func CountSecurityUpdates(rpc *jsonRPC) *SecurityCounts { + counts := &SecurityCounts{} + for _, pkg := range rpc.Params.Packages { + if pkg.Mode == "upgrade" { + if verifyOriginAndArchive(pkg.Versions.Install, "UbuntuESMApps", "-apps-security") { + counts.ESMApps++ + } + if verifyOriginAndArchive(pkg.Versions.Install, "UbuntuESM", "-infra-security") { + counts.ESMInfra++ + } + if verifyOriginAndArchive(pkg.Versions.Install, "UbuntuStandard", "-security") { + counts.Standard++ + } + } + } + return counts +} + +// GetCountMessage generates a formatted message describing security updates. +func GetCountMessage(counts *SecurityCounts) string { + if counts.Standard == 0 && counts.ESMInfra == 0 && counts.ESMApps == 0 { + return "" + } + + if counts.ESMInfra == 0 && counts.ESMApps == 0 { + if counts.Standard == 1 { + return "1 standard LTS security update" + } + return fmt.Sprintf("%d standard LTS security updates", counts.Standard) + } + + message := make([]string, 0, 3) + + if counts.Standard > 0 { + if counts.Standard == 1 { + message = append(message, "1 standard LTS security update") + } else { + message = append(message, fmt.Sprintf("%d standard LTS security updates", counts.Standard)) + } + } + + if counts.ESMInfra > 0 { + if counts.ESMInfra == 1 { + message = append(message, "1 esm-infra security update") + } else { + message = append(message, fmt.Sprintf("%d esm-infra security updates", counts.ESMInfra)) + } + } + + if counts.ESMApps > 0 { + if counts.ESMApps == 1 { + message = append(message, "1 esm-apps security update") + } else { + message = append(message, fmt.Sprintf("%d esm-apps security updates", counts.ESMApps)) + } + } + + switch len(message) { + case 1: + return message[0] + case 2: + return fmt.Sprintf("%s and %s", message[0], message[1]) + case 3: + return fmt.Sprintf("%s, %s and %s", message[0], message[1], message[2]) + } + + return "" +} + +// CollectProPackagesFromRPC collects packages from RPC. +func CollectProPackagesFromRPC(rpc *jsonRPC) []string { + var expiredPackages []string + + proOrigins := []string{ + "UbuntuESM", + "UbuntuESMApps", + "UbuntuCC", + "UbuntuCIS", + "UbuntuFIPS", + "UbuntuFIPSUpdates", + "UbuntuFIPSPreview", + "UbuntuRealtimeKernel", + "UbuntuROS", + "UbuntuROSUpdates", + } + + for _, pkg := range rpc.Params.Packages { + if pkg.Mode == "upgrade" { + for _, origin := range proOrigins { + if verifyOrigin(pkg.Versions.Install, origin) { + expiredPackages = append(expiredPackages, pkg.Name) + break + } + } + } + } + + return expiredPackages +} + +// PrintExpiredProPackages logs messages about expired pro package status. +func PrintExpiredProPackages(packageNames []string) { + if len(packageNames) == 0 { + return + } + fmt.Println("The following packages will fail to download because your Ubuntu Pro subscription has expired:") + printPackageNames(packageNames) + fmt.Println("Renew your subscription or run `sudo pro detach` to remove these errors") +} + +// printESMPackages prints expired ESM packages. +func PrintESMPackages(esmType string, packageNames []string) { + if esmType == "APPS" { + if len(packageNames) == 1 { + fmt.Println("Get another security update through Ubuntu Pro with 'esm-apps' enabled:") + } else { + fmt.Println("Get more security updates through Ubuntu Pro with 'esm-apps' enabled:") + } + } else if esmType == "INFRA" { + if len(packageNames) == 1 { + fmt.Println("The following security update requires Ubuntu Pro with 'esm-infra' enabled:") + } else { + fmt.Println("The following security updates require Ubuntu Pro with 'esm-infra' enabled:") + } + } + + printPackageNames(packageNames) + printLearnMoreContent() +} diff --git a/apt-hook/apthook/esm_counts.go b/apt-hook/apthook/esm_counts.go new file mode 100644 index 0000000000..c2acbda9d8 --- /dev/null +++ b/apt-hook/apthook/esm_counts.go @@ -0,0 +1,156 @@ +package apthook + +import ( + "fmt" + "path/filepath" + "strings" + "bufio" + "os" + "os/exec" +) + +const ( + systemPkgPath = "/var/lib/dpkg/status" +) + +type PkgStatus struct { + Name string + Version string + Status string + Source string +} + +type ESMUpdates struct { + InfraPackages []string + AppsPackages []string +} + +func getDpkgArch() string { + cmd := exec.Command("dpkg", "--print-architecture") + out, err := cmd.Output() + if err != nil { + return "" + } + arch := strings.TrimSpace(string(out)) + return arch +} + +func getEsmPackagesFilePath(osRelease OSRelease) (string, string) { + curSeries := osRelease.VersionCodename + curArch := getDpkgArch() + + const baseDir = "/var/lib/ubuntu-advantage/apt-esm/var/lib/apt/lists" + + security := filepath.Join(baseDir, + fmt.Sprintf("esm.ubuntu.com_apps_ubuntu_dists_%s-apps-security_main_binary-%s_Packages", + curSeries, curArch)) + + updates := filepath.Join(baseDir, + fmt.Sprintf("esm.ubuntu.com_apps_ubuntu_dists_%s-apps-updates_main_binary-%s_Packages", + curSeries, curArch)) + + return security, updates +} + +func readPackages(filepath string) ([]PkgStatus, error) { + file, err := os.Open(filepath) + if err != nil { + return nil, err + } + defer file.Close() + + var packages []PkgStatus + var curPackage PkgStatus + + scanner := bufio.NewScanner(file) + parsingPackage := false + + for scanner.Scan(){ + line := strings.TrimSpace(scanner.Text()) + + if strings.HasPrefix(line, "Package:") { + if parsingPackage { + packages = append(packages, curPackage) + } + + curPackage = PkgStatus{} + curPackage.Name = strings.TrimSpace(strings.TrimPrefix(line, "Package:")) + parsingPackage = true + } else if strings.HasPrefix(line, "Version:") { + curPackage.Version = strings.TrimSpace(strings.TrimPrefix(line, "Version:")) + } else if strings.HasPrefix(line, "Status:") { + curPackage.Status = strings.TrimSpace(strings.TrimPrefix(line, "Status:")) + } else if strings.HasPrefix(line, "Source:") { + curPackage.Source = strings.TrimSpace(strings.TrimPrefix(line, "Source:")) + } + } + + if err := scanner.Err(); err != nil { + return nil, err + } + + if parsingPackage { + packages = append(packages, curPackage) + } + + // Can return a dict (map) for faster lookup instead of iterating? + return packages, nil +} + +// GetPotentialESMUpdates returns a list of packages that are available for +// updates in the ESM repo +func GetPotentialESMUpdates() (*ESMUpdates, error) { + updates := &ESMUpdates{} + + systemPkgs, err := readPackages(systemPkgPath) + if err != nil { + return nil, fmt.Errorf("reading system packages: %w", err) + } + + // Get ESM package list paths (currently only apps) + osRelease := ParseOSRelease() + securityPath, updatesPath := getEsmPackagesFilePath(osRelease) + + securityPkgs, err := readPackages(securityPath) + if err != nil { + return nil, fmt.Errorf("reading security packages: %w", err) + } + + updatesPkgs, err := readPackages(updatesPath) + if err != nil { + return nil, fmt.Errorf("reading updates packages: %w", err) + } + + for _, sysPkg := range systemPkgs { + // If installed + if !strings.Contains(sysPkg.Status, "installed") { + continue + } + + // Check security packages path + for _, secPkg := range securityPkgs { + if secPkg.Name == sysPkg.Name && CompareVersions(secPkg.Version, sysPkg.Version) < 0{ + // Check ESM Source + if strings.Contains(secPkg.Source, "UbuntuESMApps") { + updates.AppsPackages = append(updates.AppsPackages, secPkg.Name) + } else if strings.Contains(secPkg.Source, "UbuntuESM") { + updates.InfraPackages = append(updates.InfraPackages, secPkg.Name) + } + } + } + + // Check updates packages path + for _, updatesPkg := range updatesPkgs { + if updatesPkg.Name == sysPkg.Name && CompareVersions(updatesPkg.Version, sysPkg.Version) < 0{ + // Check ESM Source + if strings.Contains(updatesPkg.Source, "UbuntuESMApps") { + updates.AppsPackages = append(updates.AppsPackages, updatesPkg.Name) + } else if strings.Contains(updatesPkg.Source, "UbuntuESM") { + updates.InfraPackages = append(updates.InfraPackages, updatesPkg.Name) + } + } + } + } + + return updates, nil +} diff --git a/apt-hook/apthook/go.mod b/apt-hook/apthook/go.mod new file mode 100644 index 0000000000..6cf1184a70 --- /dev/null +++ b/apt-hook/apthook/go.mod @@ -0,0 +1,3 @@ +module jsonhook + +go 1.16 diff --git a/apt-hook/apthook/main.go b/apt-hook/apthook/main.go new file mode 100644 index 0000000000..ff275192f6 --- /dev/null +++ b/apt-hook/apthook/main.go @@ -0,0 +1,83 @@ +package apthook + +import ( + "fmt" + "os" + "strconv" +) + +const ( + methodStats = "org.debian.apt.hooks.install.statistics" + methodPrePrompt = "org.debian.apt.hooks.install.pre-prompt" + aptNewsFile = "/var/lib/ubuntu-advantage/messages/apt-news" + expiredNotice = "/var/lib/ubuntu-advantage/notices/5-contract_expired" +) + +func main() error { + sockFd := os.Getenv("APT_HOOK_SOCKET") + if sockFd == "" { + return fmt.Errorf("pro-apt-hook: missing socket fd") + } + + fd, err := strconv.Atoi(sockFd) + if err != nil { + return fmt.Errorf("pro-apt-hook: invalid socket fd: %w", err) + } + + file := os.NewFile(uintptr(fd), "apt-hook-socket") + if file == nil { + return fmt.Errorf("pro-apt-hook: cannot open file descriptor %d", fd) + } + defer file.Close() + + conn, err := NewConnection(file) + if err != nil { + return fmt.Errorf("pro-apt-hook: failed to create connection: %w", err) + } + defer conn.Close() + + if err := conn.Handshake(); err != nil { + return fmt.Errorf("pro-apt-hook: handshake failed: %w", err) + } + + msg, err := conn.ReadMessage() + if err != nil { + return fmt.Errorf("pro-apt-hook: reading message: %w", err) + } + + switch msg.Method { + + case methodStats: + counts := CountSecurityUpdates(msg) + if message := GetCountMessage(counts); message != "" { + fmt.Println(message) + } + + case methodPrePrompt: + // Get ESM updates + if updates, err := GetPotentialESMUpdates(); err == nil { + if len(updates.InfraPackages) > 0 { + PrintESMPackages("INFRA", updates.InfraPackages) + } else if len(updates.AppsPackages) > 0 { + PrintESMPackages("APPS", updates.AppsPackages) + } + } + + if news, err := os.ReadFile(aptNewsFile); err == nil { + fmt.Print(string(news)) + } + + if _, err := os.Stat(expiredNotice); err == nil { + expiredPkgs := CollectProPackagesFromRPC(msg) + if len(expiredPkgs) > 0 { + PrintExpiredProPackages(expiredPkgs) + } + } + } + + if err := conn.Bye(); err != nil { + return fmt.Errorf("pro-apt-hook: bye failed: %w", err) + } + + return nil +} diff --git a/apt-hook/apthook/rpc.go b/apt-hook/apthook/rpc.go new file mode 100644 index 0000000000..b1ff490a29 --- /dev/null +++ b/apt-hook/apthook/rpc.go @@ -0,0 +1,99 @@ +package apthook + +import ( + "bufio" + "encoding/json" + "io" + "net" + "fmt" + "os" +) + +const ( + MethodHello = "org.debian.apt.hooks.hello" + MethodBye = "org.debian.apt.hooks.bye" +) + +type Connection struct { + conn net.Conn + reader *bufio.Reader +} + +func (c *Connection) Close() error { + return c.conn.Close() +} + +func (c *Connection) ReadMessage() (*jsonRPC, error) { + return readRPC(c.reader) +} + +func (c *Connection) WriteResponse(version string, id int) error { + response := fmt.Sprintf(`{"jsonrpc": "2.0", "id": %d, "result": {"version": "%s"}}`, id, version) + _, err := c.conn.Write([]byte(response + "\n\n")) + return err +} + +func (c *Connection) Handshake() error { + msg, err := c.ReadMessage() + if err != nil { + return fmt.Errorf("reading handshake: %w", err) + } + + if msg.Method != MethodHello { + return fmt.Errorf("expected hello method, got: %v", msg.Method) + } + + if err := c.WriteResponse("0.2", 0); err != nil { + return fmt.Errorf("writing handshake response: %w", err) + } + + return nil +} + +func (c *Connection) Bye() error { + msg, err := c.ReadMessage() + if err != nil { + return fmt.Errorf("reading bye message: %w", err) + } + + if msg.Method != MethodBye { + return fmt.Errorf("expected bye method, got: %v", msg.Method) + } + + return nil +} + +func NewConnection(f *os.File) (*Connection, error) { + conn, err := net.FileConn(f) + if err != nil { + return nil, err + } + return &Connection{ + conn: conn, + reader: bufio.NewReader(conn), + }, nil +} + +func readRPC(r *bufio.Reader) (*jsonRPC, error) { + line, err := r.ReadBytes('\n') + if err != nil && err != io.EOF { + return nil, err + } + + var msg jsonRPC + if err := json.Unmarshal(line, &msg); err != nil { + return nil, fmt.Errorf("parsing json: %w", err) + } + + emptyLine, _, err := r.ReadLine() + + if err != nil { + return nil, fmt.Errorf("reading empty line: %w", err) + } + + if string(emptyLine) != "" { + return nil, fmt.Errorf("unexpected line: %q (empty)", emptyLine) + } + + return &msg, nil +} diff --git a/apt-hook/apthook/types.go b/apt-hook/apthook/types.go new file mode 100644 index 0000000000..8c33c4dc2f --- /dev/null +++ b/apt-hook/apthook/types.go @@ -0,0 +1,52 @@ +package apthook + +type jsonRPC struct { + JsonRPC string `json:"jsonrpc"` + Method string `json:"method"` + Params struct { + Command string `json:"command"` + UnknownPackages []string `json:"unknown-packages"` + Packages []Package `json:"packages"` + } `json:"params"` +} + +type Package struct { + ID int `json:"id"` + Name string `json:"name"` + Architecture string `json:"architecture"` + Mode string `json:"mode"` + Versions Versions `json:"versions,omitempty"` // Optional + Automatic bool `json:"automatic,omitempty"` // Optional + Current string `json:"current,omitempty"` // Optional +} + +type Versions struct { + Candidate jsonRPCPackageVersion `json:"candidate"` + Install jsonRPCPackageVersion `json:"install"` + Current jsonRPCPackageVersion `json:"current"` +} + +type jsonRPCPackageVersion struct { + ID int `json:"id"` + Version string `json:"version"` + Architecture string `json:"architecture"` + Pin int `json:"pin"` + Origins []Origin `json:"origins"` +} + +type Origin struct { + Archive string `json:"archive"` + Codename string `json:"codename"` + Version string `json:"version"` + Origin string `json:"origin"` + Label string `json:"label"` + Site string `json:"site"` +} + +type OSRelease struct { + Name string `json:"name"` + VersionID string `json:"version_id"` + Version string `json:"version"` + VersionCodename string `json:"version_codename"` + ID string `json:"id"` +} diff --git a/apt-hook/apthook/version.go b/apt-hook/apthook/version.go new file mode 100644 index 0000000000..0fd2e93f13 --- /dev/null +++ b/apt-hook/apthook/version.go @@ -0,0 +1,135 @@ +// From the chisel library for debian version comparison + +package apthook + +import ( + "strings" +) + +func max(a, b int) int { + if a < b { + return b + } + return a +} + +//go:generate go run ./chrorder/main.go -package=deb -output=chrorder.go + +func cmpString(as, bs string) int { + for i := 0; i < max(len(as), len(bs)); i++ { + var a uint8 + var b uint8 + if i < len(as) { + a = as[i] + } + if i < len(bs) { + b = bs[i] + } + if chOrder[a] < chOrder[b] { + return -1 + } + if chOrder[a] > chOrder[b] { + return +1 + } + } + return 0 +} + +func trimLeadingZeroes(a string) string { + for i := 0; i < len(a); i++ { + if a[i] != '0' { + return a[i:] + } + } + return "" +} + +// a and b both match /[0-9]+/ +func cmpNumeric(a, b string) int { + a = trimLeadingZeroes(a) + b = trimLeadingZeroes(b) + + switch d := len(a) - len(b); { + case d > 0: + return 1 + case d < 0: + return -1 + } + for i := 0; i < len(a); i++ { + switch { + case a[i] > b[i]: + return 1 + case a[i] < b[i]: + return -1 + } + } + return 0 +} + +func nextFrag(s string) (frag, rest string, numeric bool) { + if len(s) == 0 { + return "", "", false + } + + var i int + if s[0] >= '0' && s[0] <= '9' { + // is digit + for i = 1; i < len(s) && s[i] >= '0' && s[i] <= '9'; i++ { + } + numeric = true + } else { + // not digit + for i = 1; i < len(s) && (s[i] < '0' || s[i] > '9'); i++ { + } + } + return s[:i], s[i:], numeric +} + +func compareSubversion(va, vb string) int { + var a, b string + var anum, bnum bool + var res int + for res == 0 { + a, va, anum = nextFrag(va) + b, vb, bnum = nextFrag(vb) + if a == "" && b == "" { + break + } + if anum && bnum { + res = cmpNumeric(a, b) + } else { + res = cmpString(a, b) + } + } + return res +} + +// CompareVersions compare two version strings that follow the debian +// version policy and +// Returns: +// +// -1 if a is smaller than b +// 0 if a equals b +// +1 if a is bigger than b +func CompareVersions(va, vb string) int { + var sa, sb string + if ia := strings.IndexByte(va, '-'); ia < 0 { + sa = "0" + } else { + va, sa = va[:ia], va[ia+1:] + } + if ib := strings.IndexByte(vb, '-'); ib < 0 { + sb = "0" + } else { + vb, sb = vb[:ib], vb[ib+1:] + } + + // the main version number (before the "-") + res := compareSubversion(va, vb) + if res != 0 { + return res + } + + // the subversion revision behind the "-" + return compareSubversion(sa, sb) +} diff --git a/apt-hook/json-hook.cc b/apt-hook/json-hook.cc index 45650c5961..f9ba69dcca 100644 --- a/apt-hook/json-hook.cc +++ b/apt-hook/json-hook.cc @@ -10,6 +10,7 @@ #include "json-hook.hh" #include "esm-counts.hh" +// Written bool read_jsonrpc_request(std::istream &in, jsonrpc_request &req) { std::string msg_line; std::string empty_line; @@ -193,7 +194,7 @@ bool collect_pro_packages_from_pre_prompt_json(json_object *pre_prompt, std::vec continue; } std::string package_mode(json_object_get_string(tmp)); - + has_key = json_object_object_get_ex(package, "name", &tmp); if (!has_key) { continue;