ospatch/updates.go (106 lines of code) (raw):

// Copyright 2019 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ospatch import ( "bufio" "bytes" "errors" "fmt" "os" "os/exec" "strconv" "github.com/GoogleCloudPlatform/osconfig/packages" ) const ( rpmquery = "/usr/bin/rpmquery" ) func getBtime(stat string) (int64, error) { f, err := os.Open(stat) if err != nil { return 0, fmt.Errorf("error opening %s: %v", stat, err) } defer f.Close() var btime int64 scnr := bufio.NewScanner(f) for scnr.Scan() { if bytes.HasPrefix(scnr.Bytes(), []byte("btime")) { split := bytes.SplitN(scnr.Bytes(), []byte(" "), 2) if len(split) != 2 { return 0, fmt.Errorf("error parsing btime from %s: %q", stat, scnr.Text()) } btime, err = strconv.ParseInt(string(bytes.TrimSpace(split[1])), 10, 64) if err != nil { return 0, fmt.Errorf("error parsing btime: %v", err) } break } } if err := scnr.Err(); err != nil && btime == 0 { return 0, fmt.Errorf("error scanning %s: %v", stat, err) } if btime == 0 { return 0, fmt.Errorf("could not find btime in %s", stat) } return btime, nil } func rpmRebootRequired(pkgs []byte, btime int64) bool { // Scanning this output is best effort, false negatives are much prefered // to false positives, and keeping this as simple as possible is // beneficial. scnr := bufio.NewScanner(bytes.NewReader(pkgs)) for scnr.Scan() { itime, err := strconv.ParseInt(scnr.Text(), 10, 64) if err != nil { continue } if itime > btime { return true } } return false } // rpmReboot returns whether an rpm based system should reboot in order to // finish installing updates. // To get this signal we look at a set of well known packages and whether // install time > system boot time. This list is not meant to be exhastive, // just to provide a signal when core system packages are updated. func rpmReboot() (bool, error) { provides := []string{ // Common packages. "kernel", "glibc", "gnutls", // EL packages. "linux-firmware", "openssl-libs", "dbus", // Suse packages. "kernel-firmware", "libopenssl1_1", "libopenssl1_0_0", "dbus-1", } args := append([]string{"--queryformat", "%{INSTALLTIME}\n", "--whatprovides"}, provides...) out, err := exec.Command(rpmquery, args...).Output() if err != nil { // We don't care about return codes as we know some of these packages won't be installed. if _, ok := err.(*exec.ExitError); !ok { return false, fmt.Errorf("error running %s: %v", rpmquery, err) } } btime, err := getBtime("/proc/stat") if err != nil { return false, err } return rpmRebootRequired(out, btime), nil } func shouldPackageBeExcluded(excludes []*Exclude, packageName *string) bool { for _, exclude := range excludes { if exclude.MatchesName(packageName) { return true } } return false } func containsString(ss []string, c string) bool { for _, s := range ss { if s == c { return true } } return false } func filterPackages(pkgs []*packages.PkgInfo, exclusivePackages []string, excludes []*Exclude) ([]*packages.PkgInfo, error) { if len(exclusivePackages) != 0 && len(excludes) != 0 { return nil, errors.New("exclusivePackages and excludes can not both be non 0") } var fPkgs = []*packages.PkgInfo{} for _, pkg := range pkgs { if shouldPackageBeExcluded(excludes, &pkg.Name) { continue } if exclusivePackages == nil || containsString(exclusivePackages, pkg.Name) { fPkgs = append(fPkgs, pkg) } } return fPkgs, nil }