cos_image_analyzer: Milestone 3 - Kernel configs, Kernel Command Line, and Sysctl Settings
Change-Id: Ie6d02da610c1dcbead249ed818b3c1a0de4bdc09
diff --git a/src/cmd/cos_image_analyzer/internal/binary/CompressRootfs.txt b/src/cmd/cos_image_analyzer/internal/binary/CompressRootfs.txt
deleted file mode 100644
index 5cddf4a..0000000
--- a/src/cmd/cos_image_analyzer/internal/binary/CompressRootfs.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-/bin/
-/lib/modules/
-/lib64/
-/usr/libexec/
-/usr/bin/
-/usr/sbin/
-/usr/lib64/
-/usr/share/zoneinfo/
-/usr/share/git/
-/usr/lib/
-/sbin/
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/binary/CompressStateful.txt b/src/cmd/cos_image_analyzer/internal/binary/CompressStateful.txt
deleted file mode 100644
index 38331fc..0000000
--- a/src/cmd/cos_image_analyzer/internal/binary/CompressStateful.txt
+++ /dev/null
@@ -1 +0,0 @@
-/var_overlay/db/
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/binary/diff.go b/src/cmd/cos_image_analyzer/internal/binary/diff.go
index 504fdb1..73f6ce4 100644
--- a/src/cmd/cos_image_analyzer/internal/binary/diff.go
+++ b/src/cmd/cos_image_analyzer/internal/binary/diff.go
@@ -5,6 +5,7 @@
"io/ioutil"
"os"
"path/filepath"
+ "sort"
"strings"
"cos.googlesource.com/cos/tools/src/cmd/cos_image_analyzer/internal/input"
@@ -21,164 +22,232 @@
etcOSRelease = "/etc/os-release"
)
-// Differences is a Intermediate Struct used to store all binary differences
+// Differences is a intermediate Struct used to store all binary differences
// Field names are pre-defined in parse_input.go and will be cross-checked with -binary flag.
type Differences struct {
Version []string
BuildID []string
Rootfs string
- KernelCommandLine string
+ OSConfigs map[string]string
Stateful string
PartitionStructure string
- SysctlSettings string
- OSConfigs map[string]string
KernelConfigs string
+ KernelCommandLine map[string]string
+ SysctlSettings string
}
// versionDiff calculates the Version difference of two images
-func (binaryDiff *Differences) versionDiff(image1, image2 *input.ImageInfo) {
+func (d *Differences) versionDiff(image1, image2 *input.ImageInfo) {
if image1.Version != image2.Version {
- binaryDiff.Version = []string{image1.Version, image2.Version}
+ d.Version = []string{image1.Version, image2.Version}
}
}
// buildDiff calculates the BuildID difference of two images
-func (binaryDiff *Differences) buildDiff(image1, image2 *input.ImageInfo) {
+func (d *Differences) buildDiff(image1, image2 *input.ImageInfo) {
if image1.BuildID != image2.BuildID {
- binaryDiff.BuildID = []string{image1.BuildID, image2.BuildID}
+ d.BuildID = []string{image1.BuildID, image2.BuildID}
}
}
// rootfsDiff calculates the Root FS difference of two images
-func (binaryDiff *Differences) rootfsDiff(image1, image2 *input.ImageInfo, flagInfo *input.FlagInfo) error {
- compressedRoots, err := ioutil.ReadFile(flagInfo.CompressRootfsFile)
+func (d *Differences) rootfsDiff(image1, image2 *input.ImageInfo, flagInfo *input.FlagInfo) error {
+ rootfsDiff, err := directoryDiff(image1.RootfsPartition3, image2.RootfsPartition3, "rootfs", flagInfo.Verbose, flagInfo.CompressRootfsSlice)
if err != nil {
- return fmt.Errorf("failed to convert file %v to slice: %v", flagInfo.CompressRootfsFile, err)
+ return fmt.Errorf("fail to diff Rootfs partitions %v and %v: %v", image1.RootfsPartition3, image2.RootfsPartition3, err)
}
- compressedRootsSlice := strings.Split(string(compressedRoots), "\n")
- rootfsDiff, err := directoryDiff(image1.RootfsPartition3, image2.RootfsPartition3, "rootfs", flagInfo.Verbose, compressedRootsSlice)
- if err != nil {
- return fmt.Errorf("fail to find rootfs difference: %v", err)
- }
- binaryDiff.Rootfs = rootfsDiff
+ d.Rootfs = rootfsDiff
return nil
}
// osConfigDiff calculates the OsConfig difference of two images
-func (binaryDiff *Differences) osConfigDiff(image1, image2 *input.ImageInfo, flagInfo *input.FlagInfo) error {
- if err := findOSConfigs(image1, image2, binaryDiff); err != nil {
+func (d *Differences) osConfigDiff(image1, image2 *input.ImageInfo, flagInfo *input.FlagInfo) error {
+ mapOfEtcEntries, err := findOSConfigs(image1, image2) // Get map of /etc entries for both images
+ if err != nil {
return fmt.Errorf("failed to find OS Configs: %v", err)
}
- for etcEntryName, diff := range binaryDiff.OSConfigs {
- etcEntryPath := filepath.Join(etc, etcEntryName)
- if diff != "" {
- uniqueEntryPath := filepath.Join(diff+"/rootfs/", etcEntryPath)
- info, err := os.Stat(uniqueEntryPath)
- if err != nil {
- return fmt.Errorf("failed to get info on file %v: %v", uniqueEntryPath, err)
+ output := make(map[string]string)
+ for etcEntryName, img := range mapOfEtcEntries {
+ etcEntryPath := filepath.Join(etc, etcEntryName) + "/"
+ if flagInfo.Verbose || !utilities.InArray(etcEntryPath, flagInfo.CompressRootfsSlice) { // Only diff if Verbose or etcEntry is not in CompressRootfs.txt
+ currentImage := img
+ if img != "" { // Unique /etc entry in Image 1 or Image2
+ output[etcEntryPath] += "Only in " + img + "/rootfs/etc: " + etcEntryName
+ } else { // Shared /etc entry in Image 1 and Image 2
+ osConfigDiff, err := pureDiff(filepath.Join(image1.RootfsPartition3, etcEntryPath), filepath.Join(image2.RootfsPartition3, etcEntryPath))
+ if err != nil {
+ return fmt.Errorf("fail to take \"diff -r --no-dereference\" on %v: %v", etcEntryPath, err)
+ }
+ currentImage = image1.TempDir
+ output[etcEntryPath] = osConfigDiff
}
- if info.IsDir() {
- binaryDiff.OSConfigs[etcEntryName] = diff + " has unique directory " + etcEntryPath
- } else {
- binaryDiff.OSConfigs[etcEntryName] = diff + " has unique file " + etcEntryPath
- }
- } else {
- compressedRoots, err := ioutil.ReadFile(flagInfo.CompressRootfsFile)
+ fullPath := filepath.Join(currentImage, "/rootfs/", etcEntryPath)
+ entryFile, err := os.Stat(fullPath)
if err != nil {
- return fmt.Errorf("failed to convert file %v to slice: %v", flagInfo.CompressRootfsFile, err)
+ return fmt.Errorf("failed to get info on file %v: %v", fullPath, err)
}
- compressedRootsSlice := strings.Split(string(compressedRoots), "\n")
-
- osConfigDiff, err := directoryDiff(filepath.Join(image1.RootfsPartition3, etcEntryPath), filepath.Join(image2.RootfsPartition3, etcEntryPath), "rootfs", flagInfo.Verbose, compressedRootsSlice)
- if err != nil {
- return fmt.Errorf("fail to find difference on /etc/ entry %v: %v", etcEntryName, err)
+ if output[etcEntryPath] != "" {
+ if entryFile.IsDir() {
+ output[etcEntryPath] = "Configs for directory " + etcEntryPath + "\n" + output[etcEntryPath]
+ } else {
+ output[etcEntryPath] = "Configs for file " + etcEntryPath + "\n" + output[etcEntryPath]
+ }
}
- binaryDiff.OSConfigs[etcEntryName] = osConfigDiff
}
}
+ d.OSConfigs = output
return nil
}
// statefulDiff calculates the stateful partition difference of two images
-func (binaryDiff *Differences) statefulDiff(image1, image2 *input.ImageInfo, flagInfo *input.FlagInfo) error {
- compressedStateful, err := ioutil.ReadFile(flagInfo.CompressStatefulFile)
+func (d *Differences) statefulDiff(image1, image2 *input.ImageInfo, flagInfo *input.FlagInfo) error {
+ statefulDiff, err := directoryDiff(image1.StatePartition1, image2.StatePartition1, "stateful", flagInfo.Verbose, flagInfo.CompressStatefulSlice)
if err != nil {
- return fmt.Errorf("failed to convert file %v to slice: %v", flagInfo.CompressStatefulFile, err)
+ return fmt.Errorf("failed to diff stateful partitions %v and %v: %v", image1.StatePartition1, image2.StatePartition1, err)
}
- compressedStatefulSlice := strings.Split(string(compressedStateful), "\n")
-
- statefulDiff, err := directoryDiff(image1.StatePartition1, image2.StatePartition1, "stateful", flagInfo.Verbose, compressedStatefulSlice)
- if err != nil {
- return fmt.Errorf("failed to diff %v and %v: %v", image1.StatePartition1, image2.StatePartition1, err)
- }
- binaryDiff.Stateful = statefulDiff
+ d.Stateful = statefulDiff
return nil
}
// partitionStructureDiff calculates the Version difference of two images
-func (binaryDiff *Differences) partitionStructureDiff(image1, image2 *input.ImageInfo) error {
+func (d *Differences) partitionStructureDiff(image1, image2 *input.ImageInfo) error {
if image2.TempDir != "" {
partitionStructureDiff, err := pureDiff(image1.PartitionFile, image2.PartitionFile)
if err != nil {
- return fmt.Errorf("fail to find Partition Structure difference: %v", err)
+ return fmt.Errorf("fail to compare both image's \"partitions.txt\" file: %v", err)
}
- binaryDiff.PartitionStructure = partitionStructureDiff
+ d.PartitionStructure = partitionStructureDiff
} else {
image1Structure, err := ioutil.ReadFile(image1.PartitionFile)
if err != nil {
- return fmt.Errorf("failed to convert file %v to string: %v", image1.PartitionFile, err)
+ return fmt.Errorf("failed to read partition file of image %v: %v", image1.TempDir, err)
}
- binaryDiff.PartitionStructure = string(image1Structure)
+ d.PartitionStructure = string(image1Structure)
+ }
+ return nil
+}
+
+// kernelConfigsDiff calculates the kernel configs difference of two images
+func (d *Differences) kernelConfigsDiff(image1, image2 *input.ImageInfo) error {
+ if image2.TempDir != "" {
+ kernelConfigsDiff, err := pureDiff(image1.KernelConfigsFile, image2.KernelConfigsFile)
+ if err != nil {
+ return fmt.Errorf("fail to compare the two image's kernel configs files: %v", err)
+ }
+ d.KernelConfigs = kernelConfigsDiff
+ } else {
+ image1KernelConfigs, err := ioutil.ReadFile(image1.KernelConfigsFile)
+ if err != nil {
+ return fmt.Errorf("failed to read kernel configs file of image %v: %v", image1.TempDir, err)
+ }
+ d.KernelConfigs = string(image1KernelConfigs)
+ }
+ return nil
+}
+
+// kernelCommandLineDiff calculates the kernel commad line difference of two images
+func (d *Differences) kernelCommandLineDiff(image1, image2 *input.ImageInfo) error {
+ output := make(map[string]string)
+ if image2.TempDir != "" {
+ mapImage1 := getKclMap(strings.Fields(image1.KernelCommandLine))
+ mapImage2 := getKclMap(strings.Fields(image2.KernelCommandLine))
+
+ for key1, value1 := range mapImage1 {
+ if value2, ok := mapImage2[key1]; !ok { // Unique KCL parameter in image1
+ if value1 != "" {
+ output[key1] = "d\n" + "< " + key1 + "=" + value1
+ } else {
+ output[key1] = "d\n" + "< " + key1
+ }
+ } else if value2 != value1 { // Image1 and Image2 KCL parameter values differ
+ output[key1] = "c\n" + "< " + key1 + "=" + value1 + "\n---\n> " + key1 + "=" + value2
+ }
+ }
+ for key2, value2 := range mapImage2 {
+ if _, ok := mapImage1[key2]; !ok { // Unique KCL parameter in image2
+ if value2 != "" {
+ output[key2] = "a\n" + "> " + key2 + "=" + value2
+ } else {
+ output[key2] = "a\n" + "> " + key2
+ }
+ }
+ }
+ } else {
+ output["Image1 KCL"] = image1.KernelCommandLine
+ }
+ d.KernelCommandLine = output
+ return nil
+}
+
+// sysctlSettingsDiff calculates the sysctl Settings difference of two images
+func (d *Differences) sysctlSettingsDiff(image1, image2 *input.ImageInfo) error {
+ if image2.TempDir != "" {
+ sysctlSettingsDiff, err := pureDiff(image1.SysctlSettingsFile, image2.SysctlSettingsFile)
+ if err != nil {
+ return fmt.Errorf("fail to compare the two image's sysctl settings files: %v", err)
+ }
+ d.SysctlSettings = sysctlSettingsDiff
+ } else {
+ image1SysctlSettings, err := ioutil.ReadFile(image1.SysctlSettingsFile)
+ if err != nil {
+ return fmt.Errorf("failed to convert image 1's %v file to string: %v", image1.SysctlSettingsFile, err)
+ }
+ d.SysctlSettings = string(image1SysctlSettings)
}
return nil
}
// FormatVersionDiff returns a formated string of the version difference
-func (binaryDiff *Differences) FormatVersionDiff() string {
- if len(binaryDiff.Version) == 2 {
- if binaryDiff.Version[1] != "" {
- return "-----Version-----\n< " + binaryDiff.Version[0] + "\n> " + binaryDiff.Version[1] + "\n\n"
+func (d *Differences) FormatVersionDiff() string {
+ if len(d.Version) == 2 {
+ if d.Version[1] != "" {
+ return "----------Version----------\n< " + d.Version[0] + "\n> " + d.Version[1] + "\n\n"
}
- return "-----Version-----\n" + binaryDiff.Version[0] + "\n\n"
+ return "----------Version----------\n" + d.Version[0] + "\n\n"
}
return ""
}
// FormatBuildIDDiff returns a formated string of the build difference
-func (binaryDiff *Differences) FormatBuildIDDiff() string {
- if len(binaryDiff.BuildID) == 2 {
- if binaryDiff.BuildID[1] != "" {
- return "-----BuildID-----\n< " + binaryDiff.BuildID[0] + "\n> " + binaryDiff.BuildID[1] + "\n\n"
+func (d *Differences) FormatBuildIDDiff() string {
+ if len(d.BuildID) == 2 {
+ if d.BuildID[1] != "" {
+ return "----------BuildID----------\n< " + d.BuildID[0] + "\n> " + d.BuildID[1] + "\n\n"
}
- return "-----BuildID-----\n" + binaryDiff.BuildID[0] + "\n\n"
+ return "----------BuildID----------\n" + d.BuildID[0] + "\n\n"
}
return ""
}
// FormatRootfsDiff returns a formated string of the rootfs difference
-func (binaryDiff *Differences) FormatRootfsDiff() string {
- if binaryDiff.Rootfs != "" {
- return "-----RootFS-----\n " + binaryDiff.Rootfs + "\n\n"
+func (d *Differences) FormatRootfsDiff() string {
+ if d.Rootfs != "" {
+ return "----------RootFS----------\n" + d.Rootfs + "\n\n"
}
return ""
}
// FormatStatefulDiff returns a formated string of the stateful partition difference
-func (binaryDiff *Differences) FormatStatefulDiff() string {
- if binaryDiff.Stateful != "" {
- return "-----Stateful Partition-----\n " + binaryDiff.Stateful + "\n\n"
+func (d *Differences) FormatStatefulDiff() string {
+ if d.Stateful != "" {
+ return "----------Stateful Partition----------\n" + d.Stateful + "\n\n"
}
return ""
}
// FormatOSConfigDiff returns a formated string of the OS Config difference
-func (binaryDiff *Differences) FormatOSConfigDiff() string {
- if len(binaryDiff.OSConfigs) > 0 {
- osConfigDifference := "-----OS Configurations-----\n"
- for etcEntryName, diff := range binaryDiff.OSConfigs {
- if diff != "" {
- osConfigDifference += etcEntryName + "\n" + diff + "\n\n"
+func (d *Differences) FormatOSConfigDiff() string {
+ if len(d.OSConfigs) > 0 {
+ osConfigDifference := "----------OS Configurations----------\n"
+ keys := make([]string, 0)
+ for k := range d.OSConfigs {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ for _, k := range keys {
+ if d.OSConfigs[k] != "" {
+ osConfigDifference += d.OSConfigs[k] + "\n\n"
}
}
return osConfigDifference
@@ -187,9 +256,44 @@
}
// FormatPartitionStructureDiff returns a formated string of the partition structure difference
-func (binaryDiff *Differences) FormatPartitionStructureDiff() string {
- if binaryDiff.PartitionStructure != "" {
- return "-----Partition Structure-----\n " + binaryDiff.PartitionStructure + "\n\n"
+func (d *Differences) FormatPartitionStructureDiff() string {
+ if d.PartitionStructure != "" {
+ return "----------Partition Structure----------\n" + d.PartitionStructure + "\n\n"
+ }
+ return ""
+}
+
+// FormatKernelConfigsDiff returns a formated string of the kernel configs difference
+func (d *Differences) FormatKernelConfigsDiff() string {
+ if d.KernelConfigs != "" {
+ return "----------Kernel Configs----------\n" + d.KernelConfigs + "\n\n"
+ }
+ return ""
+}
+
+// FormatKernelCommandLineDiff returns a formated string of the KCL difference
+func (d *Differences) FormatKernelCommandLineDiff() string {
+ if len(d.KernelCommandLine) > 0 {
+ kclDifference := "----------Kernel Command Line----------\n"
+ keys := make([]string, 0)
+ for k := range d.KernelCommandLine {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ for _, k := range keys {
+ if d.KernelCommandLine[k] != "" {
+ kclDifference += d.KernelCommandLine[k] + "\n\n"
+ }
+ }
+ return kclDifference
+ }
+ return ""
+}
+
+// FormatSysctlSettingsDiff returns a formated string of the Sysctrl settings difference
+func (d *Differences) FormatSysctlSettingsDiff() string {
+ if d.SysctlSettings != "" {
+ return "----------Sysctl settings----------\n" + d.SysctlSettings + "\n\n"
}
return ""
}
@@ -211,11 +315,27 @@
if utilities.InArray("BuildID", flagInfo.BinaryTypesSelected) {
BinaryDiff.buildDiff(image1, image2)
}
+
if utilities.InArray("Partition-structure", flagInfo.BinaryTypesSelected) {
if err := BinaryDiff.partitionStructureDiff(image1, image2); err != nil {
return BinaryDiff, fmt.Errorf("Failed to get Partition-structure difference: %v", err)
}
}
+ if utilities.InArray("Kernel-configs", flagInfo.BinaryTypesSelected) {
+ if err := BinaryDiff.kernelConfigsDiff(image1, image2); err != nil {
+ return BinaryDiff, fmt.Errorf("failed to get Kernel-configs difference: %v", err)
+ }
+ }
+ if utilities.InArray("Kernel-command-line", flagInfo.BinaryTypesSelected) {
+ if err := BinaryDiff.kernelCommandLineDiff(image1, image2); err != nil {
+ return BinaryDiff, fmt.Errorf("failed to get Kernel-command-line difference: %v", err)
+ }
+ }
+ if utilities.InArray("Sysctl-settings", flagInfo.BinaryTypesSelected) {
+ if err := BinaryDiff.sysctlSettingsDiff(image1, image2); err != nil {
+ return BinaryDiff, fmt.Errorf("failed to get Sysctl-settings difference: %v", err)
+ }
+ }
if image2.TempDir != "" {
if utilities.InArray("Rootfs", flagInfo.BinaryTypesSelected) {
diff --git a/src/cmd/cos_image_analyzer/internal/binary/diff_test.go b/src/cmd/cos_image_analyzer/internal/binary/diff_test.go
index 299c88f..ab7a995 100644
--- a/src/cmd/cos_image_analyzer/internal/binary/diff_test.go
+++ b/src/cmd/cos_image_analyzer/internal/binary/diff_test.go
@@ -9,6 +9,9 @@
// test Diff function
func TestDiff(t *testing.T) {
+ testCompressRootfsSlice := []string{"/proc/", "/usr/lib/", "/util/", "/etc/os-release/", "/etc/sysctl.d/"}
+ testCompressStatefulSlice := []string{"/var_overlay/"}
+
// Rootfs test data
testVerboseRootfsDiff := `Files ../testdata/image1/rootfs/lib64/python.txt and ../testdata/image2/rootfs/lib64/python.txt differ
Files ../testdata/image1/rootfs/proc/security/access.conf and ../testdata/image2/rootfs/proc/security/access.conf differ
@@ -22,17 +25,66 @@
// OS Config test data
testVerboseOSConfig := map[string]string{
- "docker": `Files ../testdata/image1/rootfs/etc/docker/credentials.txt and ../testdata/image2/rootfs/etc/docker/credentials.txt differ
+ "/etc/docker/": `Configs for directory /etc/docker/
+diff -r --no-dereference ../testdata/image1/rootfs/etc/docker/credentials.txt ../testdata/image2/rootfs/etc/docker/credentials.txt
+1,2c1,2
+< Name: docker.10.2.4
+< job: makes micro kernels
+\ No newline at end of file
+---
+> Name: docker.10.2.1
+> job: makes macro kernels
+\ No newline at end of file
Only in ../testdata/image1/rootfs/etc/docker/util: docker.txt
Only in ../testdata/image1/rootfs/etc/docker/util: lib32
Only in ../testdata/image2/rootfs/etc/docker/util: lib64`,
- "os-release": "Files ../testdata/image1/rootfs/etc/os-release and ../testdata/image2/rootfs/etc/os-release differ"}
+ "/etc/os-release/": `Configs for file /etc/os-release/
+1c1
+< BUILD_ID=12871.119.0
+---
+> BUILD_ID=12371.273.0
+3c3
+< KERNEL_COMMIT_ID=fa84f12c6d738af9486e69a006a57df923f9476a
+---
+> KERNEL_COMMIT_ID=5d4ffd91281840f7a118143d77fbefb02e87943c
+5c5
+< VERSION_ID=81
+---
+> VERSION_ID=77
+8c8
+< VERSION=81
+---
+> VERSION=77`,
+ "/etc/sysctl.d/": `Configs for directory /etc/sysctl.d/
+diff -r --no-dereference ../testdata/image1/rootfs/etc/sysctl.d/00-sysctl.conf ../testdata/image2/rootfs/etc/sysctl.d/00-sysctl.conf
+8c8
+< net.ipv4.conf.all.rp_filter = 1
+---
+> net.ipv4.conf.all.rp_filter = 2
+11c11,14
+< net.ipv4.tcp_slow_start_after_idle = 0
+\ No newline at end of file
+---
+> net.ipv4.tcp_slow_start_after_idle = 1
+>
+> # dumby variable
+> net.ipv4.conf = 2
+\ No newline at end of file`}
+
testBriefOSConfig := map[string]string{
- "docker": `Files ../testdata/image1/rootfs/etc/docker/credentials.txt and ../testdata/image2/rootfs/etc/docker/credentials.txt differ
+ "/etc/docker/": `Configs for directory /etc/docker/
+diff -r --no-dereference ../testdata/image1/rootfs/etc/docker/credentials.txt ../testdata/image2/rootfs/etc/docker/credentials.txt
+1,2c1,2
+< Name: docker.10.2.4
+< job: makes micro kernels
+\ No newline at end of file
+---
+> Name: docker.10.2.1
+> job: makes macro kernels
+\ No newline at end of file
Only in ../testdata/image1/rootfs/etc/docker/util: docker.txt
Only in ../testdata/image1/rootfs/etc/docker/util: lib32
-Only in ../testdata/image2/rootfs/etc/docker/util: lib64`,
- "os-release": "Files ../testdata/image1/rootfs/etc/os-release and ../testdata/image2/rootfs/etc/os-release differ"}
+Only in ../testdata/image2/rootfs/etc/docker/util: lib64`}
// Stateful test data
testVerboseStatefulDiff := `Only in ../testdata/image1/stateful/dev_image: image1_dev.txt
@@ -44,7 +96,7 @@
Unique files in ../testdata/image1/stateful/var_overlay
Unique files in ../testdata/image2/stateful/var_overlay`
- // Partition Structure test data
+ // Partition Structure data
testPartitionStructure := `1c1
< Disk /img_disks/cos_81_12871_119_disk/disk.raw: 20971520 sectors, 10.0 GiB
---
@@ -62,6 +114,86 @@
---
> 8 86016 118783 16.0 MiB 0700 OEM`
+ // Kernel configs data
+ testKernelConfigsImage1 := `#
+# Compiler: Chromium OS 10.0_pre377782_p20200113-r10 clang version 10.0.0 (/var/cache/chromeos-cache/distfiles/host/egit-src/llvm-project 4e8231b5cf0f5f62c7a51a857e29f5be5cb55734)
+#
+CONFIG_GCC_VERSION=0
+CONFIG_CC_IS_CLANG=y
+CONFIG_CLANG_VERSION=100000
+
+#
+# General setup
+#
+CONFIG_INIT_ENV_ARG_LIMIT=32
+CONFIG_LOCALVERSION=""
+CONFIG_BUILD_SALT=""
+CONFIG_HAVE_KERNEL_GZIP=y`
+ testKernelConfigsDiff := `2c2
+< # Compiler: Chromium OS 10.0_pre377782_p20200113-r10 clang version 10.0.0 (/var/cache/chromeos-cache/distfiles/host/egit-src/llvm-project 4e8231b5cf0f5f62c7a51a857e29f5be5cb55734)
+---
+> # Compiler: Chromium OS 9.0_pre361749_p20190714-r4 clang version 9.0.0 (/var/cache/chromeos-cache/distfiles/host/egit-src/llvm-project c11de5eada2decd0a495ea02676b6f4838cd54fb) (based on LLVM 9.0.0svn)
+6c6
+< CONFIG_CLANG_VERSION=100000
+---
+> CONFIG_CLANG_VERSION=90000
+11a12
+> # CONFIG_COMPILE_TEST is not set
+12a14
+> # CONFIG_LOCALVERSION_AUTO is not set
+14c16
+< CONFIG_HAVE_KERNEL_GZIP=y
+\ No newline at end of file
+---
+> CONFIG_HAVE_KERNEL_GZIP=y`
+
+ // Kernel Command Line data
+ kclImage1 := `linux /syslinux/vmlinuz.A init=/usr/lib/systemd/systemd boot=local rootwait ro dm_verity.dev_wait=50`
+ kclImage2 := `linux /syslinux/vmlinuz.A init=/usr/lib32/systemd/ ro dm_verity.dev_wait=1 i915.modeset=1 cros_efi`
+ testKCLImage1 := map[string]string{"Image1 KCL": kclImage1}
+ testKCLDiff := map[string]string{
+ "boot": `d
+< boot=local`,
+ "cros_efi": `a
+> cros_efi`,
+ "dm_verity.dev_wait": `c
+< dm_verity.dev_wait=50
+---
+> dm_verity.dev_wait=1`,
+ "i915.modeset": `a
+> i915.modeset=1`,
+ "init": `c
+< init=/usr/lib/systemd/systemd
+---
+> init=/usr/lib32/systemd/`,
+ "rootwait": `d
+< rootwait`}
+
+ // Sysctl settings
+ testSysctlSettingsImage1 := `# /etc/sysctl.conf
+# Look in /proc/sys/ for all the things you can setup.
+#
+
+# Enables source route verification
+net.ipv4.conf.default.rp_filter = 1
+# Enable reverse path
+net.ipv4.conf.all.rp_filter = 1
+
+# Disable shrinking the cwnd when connection is idle
+net.ipv4.tcp_slow_start_after_idle = 0`
+ testSysctlSettingsDiff := `8c8
+< net.ipv4.conf.all.rp_filter = 1
+---
+> net.ipv4.conf.all.rp_filter = 2
+11c11,14
+< net.ipv4.tcp_slow_start_after_idle = 0
+\ No newline at end of file
+---
+> net.ipv4.tcp_slow_start_after_idle = 1
+>
+> # dumby variable
+> net.ipv4.conf = 2
+\ No newline at end of file`
for _, tc := range []struct {
Image1 *input.ImageInfo
Image2 *input.ImageInfo
@@ -95,11 +227,11 @@
want: &Differences{}},
{Image1: &input.ImageInfo{TempDir: "../testdata/image1", RootfsPartition3: "../testdata/image1/rootfs/"},
Image2: &input.ImageInfo{TempDir: "../testdata/image2", RootfsPartition3: "../testdata/image2/rootfs/"},
- FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Rootfs"}, Verbose: true, CompressRootfsFile: "../testdata/CompressRootfsFile.txt"},
+ FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Rootfs"}, Verbose: true, CompressRootfsSlice: testCompressRootfsSlice},
want: &Differences{Rootfs: testVerboseRootfsDiff}},
{Image1: &input.ImageInfo{TempDir: "../testdata/image1", RootfsPartition3: "../testdata/image1/rootfs/"},
Image2: &input.ImageInfo{TempDir: "../testdata/image2", RootfsPartition3: "../testdata/image2/rootfs/"},
- FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Rootfs"}, Verbose: false, CompressRootfsFile: "../testdata/CompressRootfsFile.txt"},
+ FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Rootfs"}, Verbose: false, CompressRootfsSlice: testCompressRootfsSlice},
want: &Differences{Rootfs: testBriefRootfsDiff}},
// OS Config difference test
@@ -109,36 +241,62 @@
want: &Differences{}},
{Image1: &input.ImageInfo{TempDir: "../testdata/image1", RootfsPartition3: "../testdata/image1/rootfs/"},
Image2: &input.ImageInfo{TempDir: "../testdata/image2", RootfsPartition3: "../testdata/image2/rootfs/"},
- FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"OS-config"}, Verbose: true, CompressRootfsFile: "../testdata/CompressRootfsFile.txt"},
+ FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"OS-config"}, Verbose: true, CompressRootfsSlice: testCompressRootfsSlice},
want: &Differences{OSConfigs: testVerboseOSConfig}},
{Image1: &input.ImageInfo{TempDir: "../testdata/image1", RootfsPartition3: "../testdata/image1/rootfs/"},
Image2: &input.ImageInfo{TempDir: "../testdata/image2", RootfsPartition3: "../testdata/image2/rootfs/"},
- FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"OS-config"}, Verbose: false, CompressRootfsFile: "../testdata/CompressRootfsFile.txt"},
+ FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"OS-config"}, Verbose: false, CompressRootfsSlice: testCompressRootfsSlice},
want: &Differences{OSConfigs: testBriefOSConfig}},
// Stateful difference test
- {Image1: &input.ImageInfo{TempDir: "../testdata/image1", RootfsPartition3: "../testdata/image1/rootfs/"},
+ {Image1: &input.ImageInfo{TempDir: "../testdata/image1", StatePartition1: "../testdata/image1/stateful/"},
Image2: &input.ImageInfo{},
FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Stateful-partition"}},
want: &Differences{}},
{Image1: &input.ImageInfo{TempDir: "../testdata/image1", StatePartition1: "../testdata/image1/stateful/"},
Image2: &input.ImageInfo{TempDir: "../testdata/image2", StatePartition1: "../testdata/image2/stateful/"},
- FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Stateful-partition"}, Verbose: true, CompressStatefulFile: "../testdata/CompressStatefulFile.txt"},
+ FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Stateful-partition"}, Verbose: true, CompressStatefulSlice: testCompressStatefulSlice},
want: &Differences{Stateful: testVerboseStatefulDiff}},
{Image1: &input.ImageInfo{TempDir: "../testdata/image1", StatePartition1: "../testdata/image1/stateful/"},
Image2: &input.ImageInfo{TempDir: "../testdata/image2", StatePartition1: "../testdata/image2/stateful/"},
- FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Stateful-partition"}, Verbose: false, CompressStatefulFile: "../testdata/CompressStatefulFile.txt"},
+ FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Stateful-partition"}, Verbose: false, CompressStatefulSlice: testCompressStatefulSlice},
want: &Differences{Stateful: testBriefStatefulDiff}},
// Partition Structure
- {Image1: &input.ImageInfo{TempDir: "../testdata/image1", RootfsPartition3: "../testdata/image1/rootfs/"},
- Image2: &input.ImageInfo{},
- FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Partition-structure"}},
- want: &Differences{}},
{Image1: &input.ImageInfo{TempDir: "../testdata/image1", PartitionFile: "../testdata/image1/partitions.txt"},
Image2: &input.ImageInfo{TempDir: "../testdata/image2", PartitionFile: "../testdata/image2/partitions.txt"},
FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Partition-structure"}},
want: &Differences{PartitionStructure: testPartitionStructure}},
+
+ // Kernel Configs
+ {Image1: &input.ImageInfo{TempDir: "../testdata/image1", KernelConfigsFile: "../testdata/image1/usr/src/linux-headers-4.19.112+/.config"},
+ Image2: &input.ImageInfo{},
+ FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Kernel-configs"}},
+ want: &Differences{KernelConfigs: testKernelConfigsImage1}},
+ {Image1: &input.ImageInfo{TempDir: "../testdata/image1", KernelConfigsFile: "../testdata/image1/usr/src/linux-headers-4.19.112+/.config"},
+ Image2: &input.ImageInfo{TempDir: "../testdata/image2", KernelConfigsFile: "../testdata/image2/usr/src/linux-headers-4.19.112+/.config"},
+ FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Kernel-configs"}},
+ want: &Differences{KernelConfigs: testKernelConfigsDiff}},
+
+ // Kernel command line
+ {Image1: &input.ImageInfo{TempDir: "../testdata/image1", KernelCommandLine: kclImage1},
+ Image2: &input.ImageInfo{},
+ FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Kernel-command-line"}},
+ want: &Differences{KernelCommandLine: testKCLImage1}},
+ {Image1: &input.ImageInfo{TempDir: "../testdata/image1", KernelCommandLine: kclImage1},
+ Image2: &input.ImageInfo{TempDir: "../testdata/image2", KernelCommandLine: kclImage2},
+ FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Kernel-command-line"}},
+ want: &Differences{KernelCommandLine: testKCLDiff}},
+
+ // Sysctl settings
+ {Image1: &input.ImageInfo{TempDir: "../testdata/image1", SysctlSettingsFile: "../testdata/image1/rootfs/etc/sysctl.d/00-sysctl.conf"},
+ Image2: &input.ImageInfo{},
+ FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Sysctl-settings"}},
+ want: &Differences{SysctlSettings: testSysctlSettingsImage1}},
+ {Image1: &input.ImageInfo{TempDir: "../testdata/image1", SysctlSettingsFile: "../testdata/image1/rootfs/etc/sysctl.d/00-sysctl.conf"},
+ Image2: &input.ImageInfo{TempDir: "../testdata/image2", SysctlSettingsFile: "../testdata/image2/rootfs/etc/sysctl.d/00-sysctl.conf"},
+ FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Sysctl-settings"}},
+ want: &Differences{SysctlSettings: testSysctlSettingsDiff}},
} {
got, _ := Diff(tc.Image1, tc.Image2, tc.FlagInfo)
@@ -149,18 +307,29 @@
t.Fatalf("Diff expected BuildID %v, got: %v", tc.want.BuildID, got.BuildID)
}
if tc.want.Rootfs != got.Rootfs {
- t.Fatalf("Diff expected Rootfs diff \n$%v$\ngot:\n$%v$", tc.want.Rootfs, got.Rootfs)
+ t.Fatalf("Diff expected Rootfs diff \n%v\ngot:\n%v", tc.want.Rootfs, got.Rootfs)
}
- for etcEntry := range got.OSConfigs {
+ for etcEntry := range tc.want.OSConfigs {
if res, _ := utilities.CmpMapValues(tc.want.OSConfigs, got.OSConfigs, etcEntry); res != 0 {
- t.Fatalf("Diff expected OSConfigs \n$%v$\ngot:\n$%v$", tc.want.OSConfigs, got.OSConfigs)
+ t.Fatalf("Diff expected OSConfigs \n%v\ngot:\n%v", tc.want.OSConfigs, got.OSConfigs)
}
}
if tc.want.Stateful != got.Stateful {
- t.Fatalf("Diff expected stateful diff \n$%v$\ngot:\n$%v$", tc.want.Stateful, got.Stateful)
+ t.Fatalf("Diff expected stateful diff \n%v\ngot:\n%v", tc.want.Stateful, got.Stateful)
}
if tc.want.PartitionStructure != got.PartitionStructure {
t.Fatalf("Diff expected partition structure diff \n$%v$\ngot:\n$%v$", tc.want.PartitionStructure, got.PartitionStructure)
}
+ for kclParameter, diff := range tc.want.KernelCommandLine {
+ if diff != got.KernelCommandLine[kclParameter] {
+ t.Fatalf("Diff expected kernel command line \n$%v$\ngot:\n$%v$", tc.want.KernelCommandLine, got.KernelCommandLine)
+ }
+ }
+ if tc.want.KernelConfigs != got.KernelConfigs {
+ t.Fatalf("Diff expected kernel configs diff \n$%v$\ngot:\n$%v$", tc.want.KernelConfigs, got.KernelConfigs)
+ }
+ if tc.want.SysctlSettings != got.SysctlSettings {
+ t.Fatalf("Diff expected sysctl settings \n$%v$\ngot:\n$%v$", tc.want.SysctlSettings, got.SysctlSettings)
+ }
}
}
diff --git a/src/cmd/cos_image_analyzer/internal/binary/helpers.go b/src/cmd/cos_image_analyzer/internal/binary/helpers.go
index 125f945..136c4d9 100644
--- a/src/cmd/cos_image_analyzer/internal/binary/helpers.go
+++ b/src/cmd/cos_image_analyzer/internal/binary/helpers.go
@@ -3,6 +3,7 @@
import (
"fmt"
"io/ioutil"
+ "os"
"os/exec"
"path/filepath"
"regexp"
@@ -12,30 +13,30 @@
"cos.googlesource.com/cos/tools/src/cmd/cos_image_analyzer/internal/utilities"
)
-// findOSConfigs finds the list of directory entries in the /etc of both images
-// and stores them into the binaryDiff struct
-/// Input:
-// (*ImageInfo) image1 - Holds needed rootfs path for image 1
-// (*ImageInfo) image2 - Holds needed rootfs path
-// Output:
-// (*Differences) binaryDiff - Holds needed KernelCommandLine map
-func findOSConfigs(image1, image2 *input.ImageInfo, binaryDiff *Differences) error {
+// findOSConfigs creates a map of all /etc entries in both images
+// Format: {etcEntry: ""} if etcEntry is shared in both images
+// {etcEntry: imageName} if etcEntry is unique to "imageName"
+func findOSConfigs(image1, image2 *input.ImageInfo) (map[string]string, error) {
etcFiles1, err := ioutil.ReadDir(image1.RootfsPartition3 + etc)
if err != nil {
- return fmt.Errorf("fail to read contents of directory %v: %v", image1.RootfsPartition3+etc, err)
+ return map[string]string{}, fmt.Errorf("fail to read contents of directory %v: %v", image1.RootfsPartition3+etc, err)
}
etcEntries1 := []string{}
for _, f := range etcFiles1 {
- etcEntries1 = append(etcEntries1, f.Name())
+ if _, err := os.Readlink(filepath.Join(image1.RootfsPartition3, etc, f.Name())); err != nil {
+ etcEntries1 = append(etcEntries1, f.Name())
+ }
}
etcFiles2, err := ioutil.ReadDir(image2.RootfsPartition3 + etc)
if err != nil {
- return fmt.Errorf("fail to read contents of directory %v: %v", image2.RootfsPartition3+etc, err)
+ return map[string]string{}, fmt.Errorf("fail to read contents of directory %v: %v", image2.RootfsPartition3+etc, err)
}
etcEntries2 := []string{}
for _, f := range etcFiles2 {
- etcEntries2 = append(etcEntries2, f.Name())
+ if _, err := os.Readlink(filepath.Join(image1.RootfsPartition3, etc, f.Name())); err != nil {
+ etcEntries2 = append(etcEntries2, f.Name())
+ }
}
osConfigsMap := make(map[string]string)
@@ -51,8 +52,29 @@
osConfigsMap[elem2] = image2.TempDir
}
}
- binaryDiff.OSConfigs = osConfigsMap
- return nil
+ return osConfigsMap, nil
+}
+
+// getKclMap converts a kernel commad line tokenized slice into a map where the keys are
+// kernel Command line parameters and the values are the parameter's value (if it exists)
+// Format: {kclParameter: value} if kclparameter follows form "param=value"
+// {kclParameter: ""}if kclparameter follows form "param"
+func getKclMap(input []string) map[string]string {
+ output := make(map[string]string)
+ for _, elem := range input {
+ if strings.Contains(elem, "=") { // KCl parameter follows form "parameter=value"
+ if startOfEquals := strings.Index(elem, "="); startOfEquals >= 0 {
+ key, value := elem[:startOfEquals], ""
+ if startOfEquals != len(elem)-1 {
+ value = elem[startOfEquals+1:]
+ }
+ output[key] = value
+ }
+ } else { // KCl parameter follows form "parameter"
+ output[elem] = ""
+ }
+ }
+ return output
}
// findDiffDir finds the directory name from the "diff" command
@@ -61,9 +83,9 @@
// (string) line - A single line of output from the "diff -rq" command
// (string) dir1 - Path to directory 1
// (string) dir2 - Path to directory 2
-// (bool) ok - Flag to indicate a directory has been found
// Output:
// (string) dir1 or dir2 - The directory found in "line"
+// (bool) ok - Flag to indicate a directory has been found
func findDiffDir(line, dir1, dir2 string) (string, bool) {
lineSplit := strings.Split(line, " ")
if len(lineSplit) < 3 {
@@ -163,14 +185,20 @@
// Output:
// (string) diff - The file difference output of the "diff" command
func directoryDiff(dir1, dir2, root string, verbose bool, compressedDirs []string) (string, error) {
- diff, err := exec.Command("sudo", "diff", "--no-dereference", "-rq", "-x", "etc", dir1, dir2).Output()
+ var cmd *exec.Cmd
+ if root == "rootfs" { // Only exclude "/etc" for Rootfs difference
+ cmd = exec.Command("sudo", "diff", "--no-dereference", "-rq", "-x", "etc", dir1, dir2)
+ } else {
+ cmd = exec.Command("sudo", "diff", "--no-dereference", "-rq", dir1, dir2)
+ }
+ diff, err := cmd.Output()
if exitError, ok := err.(*exec.ExitError); ok {
if exitError.ExitCode() == 2 {
return "", fmt.Errorf("failed to call 'diff' command on directories %v and %v: %v", dir1, dir2, err)
}
}
- diffStrln := fmt.Sprintf("%s", diff)
- diffStr := strings.TrimSuffix(diffStrln, "\n")
+
+ diffStr := strings.TrimSuffix(string(diff), "\n")
if verbose {
return diffStr, nil
}
@@ -183,13 +211,11 @@
// pureDiff returns the output of a normal diff between two files or directories
func pureDiff(input1, input2 string) (string, error) {
- diff, err := exec.Command("sudo", "diff", input1, input2).Output()
+ diff, err := exec.Command("sudo", "diff", "-r", "--no-dereference", input1, input2).Output()
if exitError, ok := err.(*exec.ExitError); ok {
if exitError.ExitCode() == 2 {
return "", fmt.Errorf("failed to call 'diff' on %v and %v: %v", input1, input2, err)
}
}
- diffStrln := fmt.Sprintf("%s", diff)
- diffStr := strings.TrimSuffix(diffStrln, "\n")
- return diffStr, nil
+ return strings.TrimSuffix(string(diff), "\n"), nil
}
diff --git a/src/cmd/cos_image_analyzer/internal/binary/helpers_test.go b/src/cmd/cos_image_analyzer/internal/binary/helpers_test.go
index d07af4d..da5bbea 100644
--- a/src/cmd/cos_image_analyzer/internal/binary/helpers_test.go
+++ b/src/cmd/cos_image_analyzer/internal/binary/helpers_test.go
@@ -29,7 +29,7 @@
} {
got, _ := directoryDiff(tc.dir1, tc.dir2, tc.root, tc.verbose, tc.compressedDirs)
if got != tc.want {
- t.Fatalf("directoryDiff expected:\n$%v$\ngot:\n$%v$", tc.want, got)
+ t.Fatalf("directoryDiff expected:\n%v\ngot:\n%v", tc.want, got)
}
}
}
@@ -55,7 +55,32 @@
} {
got, _ := pureDiff(tc.input1, tc.input2)
if got != tc.want {
- t.Fatalf("PureDiff expected:\n$%v$\ngot:\n$%v$", tc.want, got)
+ t.Fatalf("PureDiff expected:\n%v\ngot:\n%v", tc.want, got)
+ }
+ }
+}
+
+// test getKclMap function
+func TestGetKclMap(t *testing.T) {
+
+ for _, tc := range []struct {
+ input []string
+ want map[string]string
+ }{
+ {input: []string{"linux", "/syslinux/vmlinuz.A", "init=/usr/lib/systemd/systemd", "boot=local", "rootwait", "ro", "dm_verity.dev_wait=50"},
+ want: map[string]string{"syslinux/vmlinuz.A": "", "boot": "local", "dm_verity.dev_wait": "50", "init": "/usr/lib/systemd/systemd", "linux": "", "ro": "", "rootwait": ""}},
+
+ {input: []string{"linux", "/syslinux/vmlinuz.A", "init=/usr/lib32/systemd/", "ro", "dm_verity.dev_wait=2", "i915.modeset=1", "cros_efi"},
+ want: map[string]string{"syslinux/vmlinuz.A": "", "dm_verity.dev_wait": "2", "init": "/usr/lib32/systemd/", "linux": "", "ro": "", "i915.modeset": "1"}},
+
+ {input: []string{},
+ want: map[string]string{}},
+ } {
+ got := getKclMap(tc.input)
+ for param, value := range tc.want {
+ if value != got[param] {
+ t.Fatalf("getKclMap expected:\n%v\ngot:\n%v", tc.want, got)
+ }
}
}
}
diff --git a/src/cmd/cos_image_analyzer/internal/binary/info.go b/src/cmd/cos_image_analyzer/internal/binary/info.go
index cce8b5e..c0d21c2 100644
--- a/src/cmd/cos_image_analyzer/internal/binary/info.go
+++ b/src/cmd/cos_image_analyzer/internal/binary/info.go
@@ -1,25 +1,112 @@
package binary
import (
+ "bufio"
"errors"
"fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
"cos.googlesource.com/cos/tools/src/cmd/cos_image_analyzer/internal/input"
"cos.googlesource.com/cos/tools/src/cmd/cos_image_analyzer/internal/utilities"
)
+const cosGCSBucket = "cos-tools"
+const kernelHeaderGCSObject = "kernel-headers.tgz"
+const pathToKernelConfigs = "usr/src/linux-headers-4.19.112+/.config"
+
+const pathToKernelCommandLine = "efi/boot/grub.cfg" // Located in partition 12 EFI
+const kclImageName = "verified image A"
+const startOfHashingKCL = "dm="
+
+const pathToSysctlSettings = "/etc/sysctl.d/00-sysctl.conf" // Located in partition 3 Root-A
+
+// getPartitionStructure returns the partition structure of .raw file
+func getPartitionStructure(image *input.ImageInfo) error {
+ if image.TempDir == "" {
+ return nil
+ }
+
+ out, err := exec.Command("sudo", "sgdisk", "-p", image.DiskFile).Output()
+ if err != nil {
+ return fmt.Errorf("failed to call sgdisk -p %v: %v", image.DiskFile, err)
+ }
+
+ partitionFile := filepath.Join(image.TempDir, "partitions.txt")
+ if err := utilities.WriteToNewFile(partitionFile, string(out[:])); err != nil {
+ return fmt.Errorf("failed create file %v and write %v: %v", partitionFile, string(out[:]), err)
+ }
+ image.PartitionFile = partitionFile
+ return nil
+}
+
+// getKernelConfigs downloads the kernel configs for a build from GCS and stores
+// it into the image's temporary directory
+func getKernelConfigs(image *input.ImageInfo) error {
+ gcsObject := filepath.Join(image.BuildID, kernelHeaderGCSObject)
+ tarFile, err := utilities.GcsDowndload(cosGCSBucket, gcsObject, image.TempDir, kernelHeaderGCSObject)
+ if err != nil {
+ return fmt.Errorf("failed to download GCS object %v from bucket %v: %v", gcsObject, cosGCSBucket, err)
+ }
+
+ _, err = exec.Command("tar", "-xf", tarFile, "-C", image.TempDir).Output()
+ if err != nil {
+ return fmt.Errorf("failed to unzip %v into %v: %v", tarFile, image.TempDir, err)
+ }
+ image.KernelConfigsFile = filepath.Join(image.TempDir, pathToKernelConfigs)
+ return nil
+}
+
+// getKernelCommandLine gets the kernel command line from the image's partition 12 EFI
+// located in the /efi/boot/grub.cfg file
+func getKernelCommandLine(image *input.ImageInfo) error {
+ kclPath := filepath.Join(image.EFIPartition12, pathToKernelCommandLine)
+ kclFile, err := os.Open(kclPath)
+ if err != nil {
+ return fmt.Errorf("Failed to open file %v: %v", kclPath, err)
+ }
+ defer kclFile.Close()
+
+ foundKCL := false
+ scanner := bufio.NewScanner(kclFile)
+ for scanner.Scan() { // Scan file line by line for "verified Image A"
+ kcl := string(scanner.Text()[:])
+
+ if foundKCL {
+ if hashStart := strings.Index(kcl, startOfHashingKCL); hashStart >= 0 {
+ kcl = kcl[:hashStart] // Remove hash "dm='....'" from kcl
+ }
+ image.KernelCommandLine = strings.TrimSpace(kcl)
+ return nil
+ }
+ if strings.Contains(kcl, kclImageName) {
+ foundKCL = true
+ }
+ }
+
+ if scanner.Err() != nil {
+ return fmt.Errorf("Failed to scan file %v: %v", kclPath, scanner.Err())
+ }
+ return nil
+}
+
+// getSysctlSettings finds an image's Sysctrl settings file under
+// the /etc/sysctrl.d/00-sysctl.conf
+func getSysctlSettings(image *input.ImageInfo) error {
+ sysctlPath := filepath.Join(image.RootfsPartition3, pathToSysctlSettings)
+ image.SysctlSettingsFile = sysctlPath
+ return nil
+}
+
// GetBinaryInfo finds relevant binary information for the COS image
-// Input:
-// (*ImageInfo) image - A struct that will store binary info for the image
-// (localInput) bool - Flag to determine whether to rename disk.raw file
-// (*FlagInfo) flagInfo - A struct that holds input preference from the user
-// Output: nil on success, else error
func GetBinaryInfo(image *input.ImageInfo, flagInfo *input.FlagInfo) error {
if image.TempDir == "" {
return nil
}
- if image.RootfsPartition3 != "" {
+ if image.RootfsPartition3 != "" { // Get Version and BuildID
osReleaseMap, err := utilities.ReadFileToMap(image.RootfsPartition3+etcOSRelease, "=")
if err != nil {
return fmt.Errorf("Failed to read /etc/os-release file in rootfs of image %v : %v", image.TempDir, err)
@@ -33,11 +120,28 @@
}
}
- if utilities.InArray("Partition-structure", flagInfo.BinaryTypesSelected) {
- if err := image.GetPartitionStructure(); err != nil {
+ if utilities.InArray("Partition-structure", flagInfo.BinaryTypesSelected) { // Get partition structure from "sgdisk -p"
+ if err := getPartitionStructure(image); err != nil {
return fmt.Errorf("failed to get partition structure for image %v: %v", image.TempDir, err)
}
}
+ if utilities.InArray("Kernel-configs", flagInfo.BinaryTypesSelected) { // Get kernel configs from gs://cos-tools/BuildID/kernel-headers.tgz
+ if err := getKernelConfigs(image); err != nil {
+ return fmt.Errorf("failed to get kernel configs for image %v: %v", image.TempDir, err)
+ }
+ }
+
+ if utilities.InArray("Kernel-command-line", flagInfo.BinaryTypesSelected) { // Get kernel command line from partition 12 EFI (efi/boot/grub.cfg)
+ if err := getKernelCommandLine(image); err != nil {
+ return fmt.Errorf("failed to get the kernel command line for image %v: %v", image.TempDir, err)
+ }
+ }
+
+ if utilities.InArray("Sysctl-settings", flagInfo.BinaryTypesSelected) { // Get Sysctl settings from /etc/sysctl.d/00-sysctl.conf
+ if err := getSysctlSettings(image); err != nil {
+ return fmt.Errorf("failed to get Sysctl-settings for image %v: %v", image.TempDir, err)
+ }
+ }
return nil
}
diff --git a/src/cmd/cos_image_analyzer/internal/binary/info_test.go b/src/cmd/cos_image_analyzer/internal/binary/info_test.go
index 890444f..e7ae5e0 100644
--- a/src/cmd/cos_image_analyzer/internal/binary/info_test.go
+++ b/src/cmd/cos_image_analyzer/internal/binary/info_test.go
@@ -8,22 +8,26 @@
// test GetBinaryInf function
func TestGetBinaryInfo(t *testing.T) {
+ kclImage1 := `linux /syslinux/vmlinuz.A init=/usr/lib/systemd/systemd boot=local rootwait ro noresume noswap loglevel=7 noinitrd console=ttyS0 security=apparmor virtio_net.napi_tx=1 systemd.unified_cgroup_hierarchy=false systemd.legacy_systemd_cgroup_controller=false csm.disabled=1 dm_verity.error_behavior=3 dm_verity.max_bios=-1 dm_verity.dev_wait=1 i915.modeset=1 cros_efi root=/dev/dm-0`
for _, tc := range []struct {
image *input.ImageInfo
flagInfo *input.FlagInfo
want *input.ImageInfo
- }{
+ }{ // Version and BuildID
{image: &input.ImageInfo{TempDir: "../testdata/image1/", RootfsPartition3: "../testdata/image1/rootfs/"},
flagInfo: &input.FlagInfo{LocalPtr: true},
want: &input.ImageInfo{RootfsPartition3: "../testdata/image1", Version: "81", BuildID: "12871.119.0"}},
-
{image: &input.ImageInfo{},
flagInfo: &input.FlagInfo{LocalPtr: true},
want: &input.ImageInfo{}},
-
{image: &input.ImageInfo{TempDir: "../testdata/image2/", RootfsPartition3: "../testdata/image2/rootfs/"},
flagInfo: &input.FlagInfo{LocalPtr: true},
want: &input.ImageInfo{RootfsPartition3: "../testdata/image2/rootfs", Version: "77", BuildID: "12371.273.0"}},
+
+ // Kernel Command Line
+ {image: &input.ImageInfo{TempDir: "../testdata/image1", EFIPartition12: "../testdata/image1/efi/"},
+ flagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Kernel-command-line"}},
+ want: &input.ImageInfo{TempDir: "../testdata/image1", EFIPartition12: "../testdata/image1/efi/", KernelCommandLine: kclImage1}},
} {
GetBinaryInfo(tc.image, tc.flagInfo)
@@ -33,5 +37,8 @@
if tc.want.BuildID != tc.image.BuildID {
t.Fatalf("GetBinaryInfo expected: %v, got: %v", tc.want.BuildID, tc.image.BuildID)
}
+ if tc.image.KernelCommandLine != tc.want.KernelCommandLine {
+ t.Fatalf("Diff kernel command line expected:$%v$, got:$%v$", tc.want.KernelCommandLine, tc.image.KernelCommandLine)
+ }
}
}
diff --git a/src/cmd/cos_image_analyzer/internal/input/flaginfo.go b/src/cmd/cos_image_analyzer/internal/input/flaginfo.go
index 7370c4e..3f50d74 100644
--- a/src/cmd/cos_image_analyzer/internal/input/flaginfo.go
+++ b/src/cmd/cos_image_analyzer/internal/input/flaginfo.go
@@ -24,16 +24,25 @@
// Release Notes
ReleaseNotesSelected bool
- //Verbosity of output
+ // Verbosity of output
+ // If true, full Rootfs, Os-Config, and Stateful Partition output is shown.
+ // Else false (default), Rootfs and Stateful Partition directorties listed on files
+ // pointed to by CompressRootfsFile and CompressStatefulFile respectively are compressed.
+ // For OS-configs difference, all /etc entries that are listed in CompressRootfsFile are ignored.
Verbose bool
- // File used to compress output from Rootfs and OS-Config difference
+ // File used to compress directories in the output from Rootfs difference and
+ // for ignore entries under /etc for OS-Config difference
// (either user provided or default CompressRootfs.txt)
CompressRootfsFile string
+ // Slice of CompressRootfsFile
+ CompressRootfsSlice []string
- // File used to compress output from Stateful-partition difference
+ // File used to compress directories in the output from Stateful-partition difference
// (either user provided or default CompressStateful.txt)
CompressStatefulFile string
+ // Slice of CompressRootfsFile
+ CompressStatefulSlice []string
// Output
OutputSelected string
diff --git a/src/cmd/cos_image_analyzer/internal/input/imageinfo.go b/src/cmd/cos_image_analyzer/internal/input/imageinfo.go
index 38089bb..8f75fd5 100644
--- a/src/cmd/cos_image_analyzer/internal/input/imageinfo.go
+++ b/src/cmd/cos_image_analyzer/internal/input/imageinfo.go
@@ -5,32 +5,30 @@
"encoding/json"
"errors"
"fmt"
- "io"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
- "strconv"
"strings"
"cos.googlesource.com/cos/tools/src/cmd/cos_image_analyzer/internal/utilities"
)
-const sectorSize = 512
const gcsObjFormat = ".tar.gz"
const makeDirFilemode = 0700
const timeOut = "7200s"
const imageFormat = "vmdk"
const name = "gcr.io/compute-image-tools/gce_vm_image_export:release"
+const pathToKernelConfigs = "usr/src/linux-headers-4.19.112+/.config"
+const pathToSysctlSettings = "/etc/sysctl.d/00-sysctl.conf" // Located in partition 3 Root-A
// ImageInfo stores all relevant information on a COS image
type ImageInfo struct {
// Input Overhead
TempDir string // Temporary directory holding the mounted image and disk file
DiskFile string // Path to the DOS/MBR disk partition file
- PartitionFile string // Path to the file storing the disk partition structure from "sgdisk"
StatePartition1 string // Path to mounted directory of partition #1, stateful partition
RootfsPartition3 string // Path to mounted directory of partition #3, Rootfs-A
EFIPartition12 string // Path to mounted directory of partition #12, EFI-System
@@ -39,12 +37,12 @@
LoopDevice12 string // Active loop device for mounted image
// Binary info
- Version string
- BuildID string
-
- // Package info
- // Commit info
- // Release notes info
+ Version string // Major cos version
+ BuildID string // Minor cos version
+ PartitionFile string // Path to the file storing the disk partition structure from "sgdisk"
+ SysctlSettingsFile string // Path to the /etc/sysctrl.d/00-sysctl.conf file of an image
+ KernelCommandLine string // The kernel command line boot-time parameters stored in partition 12 efi/boot/grub.cfg
+ KernelConfigsFile string // Path to the ".config" file downloaded from GCS that holds a build's kernel configs
}
// Rename temporary directory and its contents once Version and BuildID are known
@@ -52,113 +50,29 @@
if image.Version != "" && image.BuildID != "" {
fullImageName := "cos-" + image.Version + "-" + image.BuildID
if err := os.Rename(image.TempDir, fullImageName); err != nil {
- return fmt.Errorf("Error: Failed to rename directory %v to %v: %v", image.TempDir, fullImageName, err)
+ return fmt.Errorf("failed to rename directory %v to %v: %v", image.TempDir, fullImageName, err)
}
image.TempDir = fullImageName
if !flagInfo.LocalPtr {
image.DiskFile = filepath.Join(fullImageName, "disk.raw")
}
- if image.RootfsPartition3 != "" {
- image.RootfsPartition3 = filepath.Join(fullImageName, "rootfs")
- }
if image.StatePartition1 != "" {
image.StatePartition1 = filepath.Join(fullImageName, "stateful")
}
+ if image.RootfsPartition3 != "" {
+ image.RootfsPartition3 = filepath.Join(fullImageName, "rootfs")
+ }
if image.EFIPartition12 != "" {
image.EFIPartition12 = filepath.Join(fullImageName, "efi")
}
+ image.PartitionFile = filepath.Join(fullImageName, "partitions.txt")
+ image.KernelConfigsFile = filepath.Join(fullImageName, pathToKernelConfigs)
+ image.SysctlSettingsFile = filepath.Join(image.RootfsPartition3, pathToSysctlSettings)
}
return nil
}
-// getPartitionStart finds the start partition offset of the disk
-// Input:
-// (string) diskFile - Name of DOS/MBR file (ex: disk.raw)
-// (string) partition - The partition number you are pulling the offset from
-// Output:
-// (int) start - The start of the partition on the disk
-func getPartitionStart(partition, diskRaw string) (int, error) {
- //create command
- cmd1 := exec.Command("fdisk", "-l", diskRaw)
- cmd2 := exec.Command("grep", diskRaw+partition)
-
- reader, writer := io.Pipe()
- var buf bytes.Buffer
-
- cmd1.Stdout = writer
- cmd2.Stdin = reader
- cmd2.Stdout = &buf
-
- cmd1.Start()
- cmd2.Start()
- cmd1.Wait()
- writer.Close()
- cmd2.Wait()
- reader.Close()
-
- words := strings.Fields(buf.String())
- if len(words) < 2 {
- return -1, errors.New("Error: " + diskRaw + " is not a valid DOS/MBR boot sector file")
- }
- start, err := strconv.Atoi(words[1])
- if err != nil {
- return -1, fmt.Errorf("failed to convert Ascii %v to string: %v", words[1], err)
- }
-
- return start, nil
-}
-
-// GetPartitionStructure returns the partition structure of .raw file
-// Input:
-// (string) diskRaw - Path to the boot .raw file
-// Output:
-// (string) partitionStructure - The output of the fdisk command
-func (image *ImageInfo) GetPartitionStructure() error {
- if image.TempDir == "" {
- return nil
- }
-
- out, err := exec.Command("sudo", "sgdisk", "-p", image.DiskFile).Output()
- if err != nil {
- return fmt.Errorf("failed to call sgdisk -p %v: %v", image.DiskFile, err)
- }
-
- partitionFile := filepath.Join(image.TempDir, "partitions.txt")
- if err := utilities.WriteToNewFile(partitionFile, string(out[:])); err != nil {
- return fmt.Errorf("failed create file %v and write %v: %v", partitionFile, string(out[:]), err)
- }
- image.PartitionFile = partitionFile
- return nil
-}
-
-// mountDisk finds a free loop device and mounts a DOS/MBR disk file
-// Input:
-// (string) diskFile - Name of DOS/MBR file (ex: disk.raw)
-// (string) mountDir - Mount Destination
-// (string) partition - The partition number you are pulling the offset from
-// Output:
-// (string) loopDevice - Name of the loop device used to mount
-func mountDisk(diskFile, mountDir, partition string) (string, error) {
- startOfPartition, err := getPartitionStart(partition, diskFile)
- if err != nil {
- return "", fmt.Errorf("failed to get start of partition #%v: %v", partition, err)
- }
- offset := strconv.Itoa(sectorSize * startOfPartition)
-
- out, err := exec.Command("sudo", "losetup", "--show", "-fP", diskFile).Output()
- if err != nil {
- return "", fmt.Errorf("failed to create new loop device for %v: %v", diskFile, err)
- }
-
- loopDevice := string(out[:len(out)-1])
- _, err = exec.Command("sudo", "mount", "-o", "ro,loop,offset="+offset, loopDevice, mountDir).Output()
- if err != nil {
- return "", fmt.Errorf("failed to mount loop device %v at %v: %v", loopDevice, mountDir, err)
- }
- return loopDevice, nil
-}
-
// MountImage is an ImagInfo method that mounts partitions 1,3 and 12 of
// the image into the temporary directory
// Input:
@@ -175,7 +89,7 @@
}
image.StatePartition1 = stateful
- loopDevice1, err := mountDisk(image.DiskFile, image.StatePartition1, "1")
+ loopDevice1, err := utilities.MountDisk(image.DiskFile, image.StatePartition1, "1")
if err != nil {
return fmt.Errorf("Failed to mount %v's partition #1 onto %v: %v", image.DiskFile, image.StatePartition1, err)
}
@@ -189,7 +103,7 @@
}
image.RootfsPartition3 = rootfs
- loopDevice3, err := mountDisk(image.DiskFile, image.RootfsPartition3, "3")
+ loopDevice3, err := utilities.MountDisk(image.DiskFile, image.RootfsPartition3, "3")
if err != nil {
return fmt.Errorf("Failed to mount %v's partition #3 onto %v: %v", image.DiskFile, image.RootfsPartition3, err)
}
@@ -203,7 +117,7 @@
}
image.EFIPartition12 = efi
- loopDevice12, err := mountDisk(image.DiskFile, image.EFIPartition12, "12")
+ loopDevice12, err := utilities.MountDisk(image.DiskFile, image.EFIPartition12, "12")
if err != nil {
return fmt.Errorf("Failed to mount %v's partition #12 onto %v: %v", image.DiskFile, image.EFIPartition12, err)
}
@@ -222,13 +136,20 @@
if gcsPath == "" {
return nil
}
- gcsArray := strings.Split(gcsPath, "/")
- if len(gcsArray) != 2 {
+ var gcsBucket, gcsObject string
+ if startOfBucket := strings.Index(gcsPath, "gs://"); startOfBucket < len(gcsPath)-5 {
+ gcsPath = gcsPath[startOfBucket+5:]
+ } else {
printUsage()
- return errors.New("Error: Argument " + gcsPath + " is not a valid gcs path (\"/\" separators)")
+ return errors.New("Error: Argument " + gcsPath + " is not a valid gcs path \"gs://<bucket>/<object_path>.tar.gz\"")
}
- gcsBucket := gcsArray[0]
- gcsObject := gcsArray[1]
+ if startOfObject := strings.Index(gcsPath, "/"); startOfObject > 0 && startOfObject < len(gcsPath)-1 {
+ gcsBucket = gcsPath[:startOfObject]
+ gcsObject = gcsPath[startOfObject+1:]
+ } else {
+ printUsage()
+ return errors.New("Error: Argument " + gcsPath + " is not a valid gcs path \"gs://<bucket>/<object_path>.tar.gz\"")
+ }
tempDir, err := ioutil.TempDir(".", "tempDir") // Removed at end
if err != nil {
@@ -236,7 +157,7 @@
}
image.TempDir = tempDir
- tarFile, err := utilities.GcsDowndload(gcsBucket, gcsObject, image.TempDir)
+ tarFile, err := utilities.GcsDowndload(gcsBucket, gcsObject, image.TempDir, filepath.Base(gcsObject))
if err != nil {
return fmt.Errorf("failed to download GCS object %v from bucket %v: %v", gcsObject, gcsBucket, err)
}
@@ -249,35 +170,6 @@
return nil
}
-// ValidateLocalImages ensures the two images are one or two unique boot files
-// Input:
-// (string) localPath1 - Local path to the first disk.raw file
-// (string) localPath2 - Local path to the second disk.raw file
-// Output: nil on success, else error
-func ValidateLocalImages(localPath1, localPath2 string) error {
- if localPath2 == "" {
- if res := utilities.FileExists(localPath1, "raw"); res == -1 {
- return errors.New("Error: " + localPath1 + " file does not exist")
- } else if res == 0 {
- return errors.New("Error: " + localPath1 + " is not a \".raw\" file")
- }
- return nil
- }
-
- if res := utilities.FileExists(localPath2, "raw"); res == -1 {
- return errors.New("Error: " + localPath2 + " file does not exist")
- } else if res == 0 {
- return errors.New("Error: " + localPath2 + " is not a \".raw\" file")
- }
-
- info1, _ := os.Stat(localPath1)
- info2, _ := os.Stat(localPath2)
- if os.SameFile(info1, info2) {
- return errors.New("Error: Identical image passed in. To analyze single image, pass in one argument")
- }
- return nil
-}
-
// GetLocalImage is an ImageInfo method that creates a temporary directory
// to loop device mount the disk.raw file stored on the local file system
// Input:
@@ -376,7 +268,7 @@
return fmt.Errorf("failed to export %v cos image to GCS bucket %v: %v", publicCosImage, gcsBucket, err)
}
- gcsPath := filepath.Join(gcsBucket, publicCosImage+gcsObjFormat)
+ gcsPath := filepath.Join(gcsBucket, publicCosImage, gcsObjFormat)
if err := image.GetGcsImage(gcsPath); err != nil {
return fmt.Errorf("failed to download image stored on GCS for %v: %v", gcsPath, err)
}
diff --git a/src/cmd/cos_image_analyzer/internal/input/parse.go b/src/cmd/cos_image_analyzer/internal/input/parse.go
index 344f648..b489622 100644
--- a/src/cmd/cos_image_analyzer/internal/input/parse.go
+++ b/src/cmd/cos_image_analyzer/internal/input/parse.go
@@ -4,6 +4,7 @@
"errors"
"flag"
"fmt"
+ "io/ioutil"
"os"
"path/filepath"
"strings"
@@ -11,7 +12,14 @@
"cos.googlesource.com/cos/tools/src/cmd/cos_image_analyzer/internal/utilities"
)
-var binaryDiffTypes = []string{"Version", "BuildID", "Rootfs", "Kernel-command-line", "Stateful-partition", "Partition-structure", "Sysctl-settings", "OS-config"}
+// BinaryDiffTypes is a list of all valid binary differnce types
+var BinaryDiffTypes = []string{"Version", "BuildID", "Rootfs", "Kernel-command-line", "Stateful-partition", "Partition-structure", "Sysctl-settings", "OS-config", "Kernel-configs"}
+
+// Default Rootfs entires that are overridden by the "compress-rootfs" flag
+var defaultCompressRootfs = []string{"/bin/", "/lib/modules/", "/lib64/", "/usr/libexec/", "/usr/bin/", "/usr/sbin/", "/usr/lib64/", "/usr/share/zoneinfo/", "/usr/share/git/", "/usr/lib/", "/sbin/", "/etc/ssh/", "/etc/os-release/", "/etc/package_list/"}
+
+// Default Stateful entires that are overridden by the "compress-stateful" flag
+var defaultCompressStateful = []string{"/var_overlay/db/"}
// Custom usage function. See -h flag
func printUsage() {
@@ -22,13 +30,13 @@
SYNOPSIS
%s [-local] FILE-1 [FILE-2] (default true)
FILE - the local file path to the DOS/MBR boot sector file of your image (Ex: disk.raw)
- Ex: %s -local image-cos-77-12371-273-0/disk.raw image-cos-81-12871-119-0/disk.raw
+ Ex: %s image-cos-77-12371-273-0/disk.raw image-cos-81-12871-119-0/disk.raw
- %s -local -binary=Sysctl-settings,OS-config -release-notes=false cos-77-12371/disk.raw
+ %s -local -binary=Sysctl-settings,OS-config -package=false image-cos-77-12371-273-0/disk.raw
%s -gcs GCS-PATH-1 [GCS-PATH-2]
GCS-PATH - the GCS "bucket/object" path for the COS Image ("object" is type .tar.gz)
- Ex: %s -gcs my-bucket/cos-77-12371-273-0.tar.gz my-bucket/cos-81-12871-119-0.tar.gz
+ Ex: %s -gcs my-bucket/cos-images/cos-77-12371-273-0.tar.gz my-bucket/cos-images/cos-81-12871-119-0.tar.gz
DESCRIPTION
@@ -41,18 +49,13 @@
Difference Flags:
-binary (string)
- specify which type of binary difference to show. Types "Version", "BuildID", "Rootfs", "Kernel-command-line",
- "Stateful-partition", "Partition-structure", "Sysctl-settings", and "OS-config" are supported. To list
- multiple types separate by comma. To NOT list any binary difference, set flag to "false". (default all types)
+ specify which type of binary difference to show. Types "Version", "BuildID", "Kernel-command-line",
+ "Partition-structure", "Sysctl-settings", and "Kernel-configs" are supported for one and two image. "Rootfs",
+ "Stateful-partition", and "OS-config" are only supported for two images. To list multiple types separate by
+ comma. To NOT list any binary difference, set flag to "false". (default all types)
-package
specify whether to show package difference. Shows addition/removal of packages and package version updates.
To NOT list any package difference, set flag to false. (default true)
- -commit
- specify whether to show commit difference. Shows commit changelog between the two images.
- To NOT list any commit difference, set flag to false. (default true)
- -release-notes
- specify whether to show release notes difference. Shows differences in human-written release notes between
- the two images. To NOT list any release notes difference, set flag to false. (default true)
Attribute Flags
-verbose
@@ -60,14 +63,14 @@
"internal/binary/CompressRootfs.txt", "internal/binary/CompressStateful.txt", and "internal/binary/CompressOSConfigs.txt"
files to edit the default directories whose differences are compressed together.
-compress-rootfs (string)
- to customize which directories are compressed in a non-verbose Rootfs and OS-config difference output, provide a local
+ to customize which directories are compressed in a non-verbose Rootfs and OS-config difference output, provide a local
file path to a text file. Format of the file must be one root file path per line with an ending back slash and no commas.
By default the directory(s) that are compressed during a diff are /bin/, /lib/modules/, /lib64/, /usr/libexec/, /usr/bin/,
/usr/sbin/, /usr/lib64/, /usr/share/zoneinfo/, /usr/share/git/, /usr/lib/, and /sbin/.
-compress-stateful (string)
to customize which directories are compressed in a non-verbose Stateful-partition difference output, provide a local
file path to a text file. Format of file must be one root file path per line with no commas. By default the directory(s)
- that are compressed during a diff are /var_overlay/db/.
+ that are compressed during a diff are /var_overlay/db/.
Output Flags:
-output (string)
@@ -96,28 +99,30 @@
}
if flagInfo.BinaryDiffPtr == "" {
- flagInfo.BinaryTypesSelected = binaryDiffTypes
+ flagInfo.BinaryTypesSelected = BinaryDiffTypes
} else {
binaryTypesSelected := strings.Split(flagInfo.BinaryDiffPtr, ",")
for _, elem := range binaryTypesSelected {
- if utilities.InArray(elem, binaryDiffTypes) {
+ if utilities.InArray(elem, BinaryDiffTypes) {
flagInfo.BinaryTypesSelected = append(flagInfo.BinaryTypesSelected, elem)
- } else {
+ } else if elem != "false" {
return errors.New("Error: Invalid option for \"-binary\" flag")
}
}
}
-
- if res := utilities.FileExists(flagInfo.CompressRootfsFile, "txt"); res == -1 {
- return errors.New("Error: " + flagInfo.CompressRootfsFile + " file does not exist")
- } else if res == 0 {
- return errors.New("Error: " + flagInfo.CompressRootfsFile + " is not a \".txt\" file")
+ if flagInfo.CompressRootfsFile != "" {
+ if res := utilities.FileExists(flagInfo.CompressRootfsFile, "txt"); res == -1 {
+ return errors.New("Error: " + flagInfo.CompressRootfsFile + " file does not exist")
+ } else if res == 0 {
+ return errors.New("Error: " + flagInfo.CompressRootfsFile + " is not a \".txt\" file")
+ }
}
-
- if res := utilities.FileExists(flagInfo.CompressStatefulFile, "txt"); res == -1 {
- return errors.New("Error: " + flagInfo.CompressStatefulFile + " file does not exist")
- } else if res == 0 {
- return errors.New("Error: " + flagInfo.CompressStatefulFile + " is not a \".txt\" file")
+ if flagInfo.CompressStatefulFile != "" {
+ if res := utilities.FileExists(flagInfo.CompressStatefulFile, "txt"); res == -1 {
+ return errors.New("Error: " + flagInfo.CompressStatefulFile + " file does not exist")
+ } else if res == 0 {
+ return errors.New("Error: " + flagInfo.CompressStatefulFile + " is not a \".txt\" file")
+ }
}
if flagInfo.OutputSelected != "terminal" && flagInfo.OutputSelected != "json" {
@@ -159,8 +164,8 @@
flag.BoolVar(&flagInfo.ReleaseNotesSelected, "release-notes", true, "")
flag.BoolVar(&flagInfo.Verbose, "verbose", false, "")
- flag.StringVar(&flagInfo.CompressRootfsFile, "compress-rootfs", "internal/binary/CompressRootfs.txt", "")
- flag.StringVar(&flagInfo.CompressStatefulFile, "compress-stateful", "internal/binary/CompressStateful.txt", "")
+ flag.StringVar(&flagInfo.CompressRootfsFile, "compress-rootfs", "", "")
+ flag.StringVar(&flagInfo.CompressStatefulFile, "compress-stateful", "", "")
flag.StringVar(&flagInfo.OutputSelected, "output", "terminal", "")
flag.Parse()
@@ -169,9 +174,58 @@
printUsage()
return &FlagInfo{}, err
}
+
+ if flagInfo.CompressRootfsFile != "" { // Get CompressRootfsslice
+ compressRootsBytes, err := ioutil.ReadFile(flagInfo.CompressRootfsFile)
+ if err != nil {
+ return &FlagInfo{}, fmt.Errorf("failed to read compress-rootfs file %v: %v", flagInfo.CompressRootfsFile, err)
+ }
+ flagInfo.CompressRootfsSlice = strings.Split(string(compressRootsBytes), "\n")
+ } else {
+ flagInfo.CompressRootfsSlice = defaultCompressRootfs
+ }
+
+ if flagInfo.CompressStatefulFile != "" { // Get CompressStatefulFileSlice
+ compressedStatefulBytes, err := ioutil.ReadFile(flagInfo.CompressStatefulFile)
+ if err != nil {
+ return &FlagInfo{}, fmt.Errorf("failed to read compress-stateful file %v: %v", flagInfo.CompressStatefulFile, err)
+ }
+ flagInfo.CompressStatefulSlice = strings.Split(string(compressedStatefulBytes), "\n")
+ } else {
+ flagInfo.CompressStatefulSlice = defaultCompressStateful
+ }
return flagInfo, nil
}
+// validateLocalImages ensures the two images are one or two unique boot files
+// Input:
+// (string) localPath1 - Local path to the first disk.raw file
+// (string) localPath2 - Local path to the second disk.raw file
+// Output: nil on success, else error
+func validateLocalImages(localPath1, localPath2 string) error {
+ if localPath2 == "" {
+ if res := utilities.FileExists(localPath1, "raw"); res == -1 {
+ return errors.New("Error: " + localPath1 + " file does not exist")
+ } else if res == 0 {
+ return errors.New("Error: " + localPath1 + " is not a \".raw\" file")
+ }
+ return nil
+ }
+
+ if res := utilities.FileExists(localPath2, "raw"); res == -1 {
+ return errors.New("Error: " + localPath2 + " file does not exist")
+ } else if res == 0 {
+ return errors.New("Error: " + localPath2 + " is not a \".raw\" file")
+ }
+
+ info1, _ := os.Stat(localPath1)
+ info2, _ := os.Stat(localPath2)
+ if os.SameFile(info1, info2) {
+ return errors.New("Error: Identical image passed in. To analyze single image, pass in one argument")
+ }
+ return nil
+}
+
// GetImages reads in all the flags and handles the input based on its type.
// Input:
// (*FlagInfo) flagInfo - A struct that holds input preference from the user
@@ -208,7 +262,7 @@
} else if flagInfo.LocalPtr {
localPath1, localPath2 := flagInfo.Image1, flagInfo.Image2
- if err := ValidateLocalImages(localPath1, localPath2); err != nil {
+ if err := validateLocalImages(localPath1, localPath2); err != nil {
return image1, image2, fmt.Errorf("failed to validate local images: %v", err)
}
if err := image1.GetLocalImage(localPath1); err != nil {
diff --git a/src/cmd/cos_image_analyzer/internal/output/imagediff.go b/src/cmd/cos_image_analyzer/internal/output/imagediff.go
index 423f91f..59c6a48 100644
--- a/src/cmd/cos_image_analyzer/internal/output/imagediff.go
+++ b/src/cmd/cos_image_analyzer/internal/output/imagediff.go
@@ -17,11 +17,13 @@
// Formater is a ImageDiff function that outputs the image differences based on the "-output" flag.
// Either to the terminal (default) or to a stored json object
// Input:
+// (string) image1 - Temp directory name of image1
+// (string) image2 - Temp directory name of image2
// (*FlagInfo) flagInfo - A struct that holds input preference from the user
// Output:
// ([]string) diffstrings/jsonObjectStr - Based on "-output" flag, either formated string
// for the terminal or a string json object
-func (imageDiff *ImageDiff) Formater(flagInfo *input.FlagInfo) (string, error) {
+func (imageDiff *ImageDiff) Formater(image1, image2 string, flagInfo *input.FlagInfo) (string, error) {
if flagInfo.OutputSelected == "terminal" {
binaryStrings := ""
binaryFunctions := map[string]func() string{
@@ -31,8 +33,11 @@
"Stateful-partition": imageDiff.BinaryDiff.FormatStatefulDiff,
"OS-config": imageDiff.BinaryDiff.FormatOSConfigDiff,
"Partition-structure": imageDiff.BinaryDiff.FormatPartitionStructureDiff,
+ "Kernel-configs": imageDiff.BinaryDiff.FormatKernelConfigsDiff,
+ "Kernel-command-line": imageDiff.BinaryDiff.FormatKernelCommandLineDiff,
+ "Sysctl-settings": imageDiff.BinaryDiff.FormatSysctlSettingsDiff,
}
- for diff := range binaryFunctions {
+ for _, diff := range input.BinaryDiffTypes {
if utilities.InArray(diff, flagInfo.BinaryTypesSelected) {
binaryStrings += binaryFunctions[diff]()
}
@@ -40,9 +45,9 @@
if len(binaryStrings) > 0 {
if flagInfo.Image2 == "" {
- binaryStrings = "================= Binary Info =================\n" + binaryStrings
+ binaryStrings = "================= Binary Info =================\nImage: " + image1 + "\n" + binaryStrings
} else {
- binaryStrings = "================= Binary Differences =================\n" + binaryStrings
+ binaryStrings = "================= Binary Differences =================\nImages: " + image1 + " and " + image2 + "\n" + binaryStrings
}
}
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/CompressRootfsFile.txt b/src/cmd/cos_image_analyzer/internal/testdata/CompressRootfsFile.txt
deleted file mode 100644
index 6219d3d..0000000
--- a/src/cmd/cos_image_analyzer/internal/testdata/CompressRootfsFile.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-/proc/
-/usr/lib/
-/docker/util/
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/CompressStatefulFile.txt b/src/cmd/cos_image_analyzer/internal/testdata/CompressStatefulFile.txt
deleted file mode 100644
index 2e3c219..0000000
--- a/src/cmd/cos_image_analyzer/internal/testdata/CompressStatefulFile.txt
+++ /dev/null
@@ -1 +0,0 @@
-/var_overlay/
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image1/efi/efi/boot/grub.cfg b/src/cmd/cos_image_analyzer/internal/testdata/image1/efi/efi/boot/grub.cfg
new file mode 100644
index 0000000..6fba5a3
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image1/efi/efi/boot/grub.cfg
@@ -0,0 +1,35 @@
+defaultA=2
+defaultB=3
+gptpriority $grubdisk 2 prioA
+gptpriority $grubdisk 4 prioB
+
+if [ $prioA -lt $prioB ]; then
+ set default=$defaultB
+else
+ set default=$defaultA
+fi
+
+set timeout=0
+
+# NOTE: These magic grub variables are a Chrome OS hack. They are not portable.
+
+menuentry "local image A" {
+ linux /syslinux/vmlinuz.A init=/usr/lib/systemd/systemd boot=local rootwait ro noresume noswap loglevel=7 noinitrd console=ttyS0 security=apparmor virtio_net.napi_tx=1 systemd.unified_cgroup_hierarchy=false systemd.legacy_systemd_cgroup_controller=false csm.disabled=1 i915.modeset=1 cros_efi root=PARTUUID=E5822204-E5B9-2848-8A90-37790091EA3E
+}
+
+menuentry "local image B" {
+ linux /syslinux/vmlinuz.B init=/usr/lib/systemd/systemd boot=local rootwait ro noresume noswap loglevel=7 noinitrd console=ttyS0 security=apparmor virtio_net.napi_tx=1 systemd.unified_cgroup_hierarchy=false systemd.legacy_systemd_cgroup_controller=false csm.disabled=1 i915.modeset=1 cros_efi root=PARTUUID=BA6C8ED3-DD99-F046-BF74-3E2CF0F06234
+}
+
+menuentry "verified image A" {
+ linux /syslinux/vmlinuz.A init=/usr/lib/systemd/systemd boot=local rootwait ro noresume noswap loglevel=7 noinitrd console=ttyS0 security=apparmor virtio_net.napi_tx=1 systemd.unified_cgroup_hierarchy=false systemd.legacy_systemd_cgroup_controller=false csm.disabled=1 dm_verity.error_behavior=3 dm_verity.max_bios=-1 dm_verity.dev_wait=1 i915.modeset=1 cros_efi root=/dev/dm-0 dm="1 vroot none ro 1,0 2539520 verity payload=PARTUUID=E5822204-E5B9-2848-8A90-37790091EA3E hashtree=PARTUUID=E5822204-E5B9-2848-8A90-37790091EA3E hashstart=2539520 alg=sha256 root_hexdigest=f24f966c2c8e2dab5caeffd2ca4c406f31d3a7f4ffb3bcf578bd96c535bc01be salt=096198c60913c02972a43985fc8bb97ceb5359e75389d7ce7e3238139dd2078b"
+}
+
+menuentry "verified image B" {
+ linux /syslinux/vmlinuz.B init=/usr/lib/systemd/systemd boot=local rootwait ro noresume noswap loglevel=7 noinitrd console=ttyS0 security=apparmor virtio_net.napi_tx=1 systemd.unified_cgroup_hierarchy=false systemd.legacy_systemd_cgroup_controller=false csm.disabled=1 dm_verity.error_behavior=3 dm_verity.max_bios=-1 dm_verity.dev_wait=1 i915.modeset=1 cros_efi root=/dev/dm-0 dm="1 vroot none ro 1,0 2539520 verity payload=PARTUUID=BA6C8ED3-DD99-F046-BF74-3E2CF0F06234 hashtree=PARTUUID=BA6C8ED3-DD99-F046-BF74-3E2CF0F06234 hashstart=2539520 alg=sha256 root_hexdigest=f24f966c2c8e2dab5caeffd2ca4c406f31d3a7f4ffb3bcf578bd96c535bc01be salt=096198c60913c02972a43985fc8bb97ceb5359e75389d7ce7e3238139dd2078b"
+}
+
+# FIXME: usb doesn't support verified boot for now
+menuentry "Alternate USB Boot" {
+ linux (hd0,3)/boot/vmlinuz init=/usr/lib/systemd/systemd boot=local rootwait ro noresume noswap loglevel=7 noinitrd console=ttyS0 security=apparmor virtio_net.napi_tx=1 systemd.unified_cgroup_hierarchy=false systemd.legacy_systemd_cgroup_controller=false csm.disabled=1 root=PARTUUID=E5822204-E5B9-2848-8A90-37790091EA3E i915.modeset=1 cros_efi
+}
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image1/partitions.txt b/src/cmd/cos_image_analyzer/internal/testdata/image1/partitions.txt
index 928e759..dbc708e 100644
--- a/src/cmd/cos_image_analyzer/internal/testdata/image1/partitions.txt
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image1/partitions.txt
@@ -19,4 +19,4 @@
9 16450 16450 512 bytes 7F02 reserved
10 16451 16451 512 bytes 7F02 reserved
11 64 16447 8.0 MiB EF02 RWFW
- 12 249856 315391 32.0 MiB EF00 EFI-SYSTEM
+ 12 249856 315391 32.0 MiB EF00 EFI-SYSTEM
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/etc/sysctl.d/00-sysctl.conf b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/etc/sysctl.d/00-sysctl.conf
new file mode 100644
index 0000000..27dfc84
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/etc/sysctl.d/00-sysctl.conf
@@ -0,0 +1,11 @@
+# /etc/sysctl.conf
+# Look in /proc/sys/ for all the things you can setup.
+#
+
+# Enables source route verification
+net.ipv4.conf.default.rp_filter = 1
+# Enable reverse path
+net.ipv4.conf.all.rp_filter = 1
+
+# Disable shrinking the cwnd when connection is idle
+net.ipv4.tcp_slow_start_after_idle = 0
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image1/usr/src/linux-headers-4.19.112+/.config b/src/cmd/cos_image_analyzer/internal/testdata/image1/usr/src/linux-headers-4.19.112+/.config
new file mode 100644
index 0000000..bd58ab6
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image1/usr/src/linux-headers-4.19.112+/.config
@@ -0,0 +1,14 @@
+#
+# Compiler: Chromium OS 10.0_pre377782_p20200113-r10 clang version 10.0.0 (/var/cache/chromeos-cache/distfiles/host/egit-src/llvm-project 4e8231b5cf0f5f62c7a51a857e29f5be5cb55734)
+#
+CONFIG_GCC_VERSION=0
+CONFIG_CC_IS_CLANG=y
+CONFIG_CLANG_VERSION=100000
+
+#
+# General setup
+#
+CONFIG_INIT_ENV_ARG_LIMIT=32
+CONFIG_LOCALVERSION=""
+CONFIG_BUILD_SALT=""
+CONFIG_HAVE_KERNEL_GZIP=y
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image2/efi/efi/boot/grub.cfg b/src/cmd/cos_image_analyzer/internal/testdata/image2/efi/efi/boot/grub.cfg
new file mode 100755
index 0000000..58a9425
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image2/efi/efi/boot/grub.cfg
@@ -0,0 +1,35 @@
+defaultA=2
+defaultB=3
+gptpriority $grubdisk 2 prioA
+gptpriority $grubdisk 4 prioB
+
+if [ $prioA -lt $prioB ]; then
+ set default=$defaultB
+else
+ set default=$defaultA
+fi
+
+set timeout=0
+
+# NOTE: These magic grub variables are a Chrome OS hack. They are not portable.
+
+menuentry "local image A" {
+ linux /syslinux/vmlinuz.A init=/usr/lib/systemd/systemd boot=local rootwait ro noresume noswap loglevel=7 noinitrd console=ttyS0 security=apparmor virtio_net.napi_tx=1 systemd.unified_cgroup_hierarchy=false systemd.legacy_systemd_cgroup_controller=false csm.disabled=1 i915.modeset=1 cros_efi root=PARTUUID=D8242B83-5E09-2247-9220-A5581F2ADD0B
+}
+
+menuentry "local image B" {
+ linux /syslinux/vmlinuz.B init=/usr/lib/systemd/systemd boot=local rootwait ro noresume noswap loglevel=7 noinitrd console=ttyS0 security=apparmor virtio_net.napi_tx=1 systemd.unified_cgroup_hierarchy=false systemd.legacy_systemd_cgroup_controller=false csm.disabled=1 i915.modeset=1 cros_efi root=PARTUUID=D09026B0-6BC4-3C4A-A0D5-F7B8C67E96CC
+}
+
+menuentry "verified image A" {
+ linux /syslinux/vmlinuz.A init=/usr/lib/systemd/systemd boot=local rootwait ro noresume noswap loglevel=7 noinitrd console=ttyS0 security=apparmor virtio_net.napi_tx=1 systemd.unified_cgroup_hierarchy=false systemd.legacy_systemd_cgroup_controller=false csm.disabled=1 dm_verity.error_behavior=3 dm_verity.max_bios=-1 dm_verity.dev_wait=1 i915.modeset=1 cros_efi root=/dev/dm-0 dm="1 vroot none ro 1,0 4077568 verity payload=PARTUUID=D8242B83-5E09-2247-9220-A5581F2ADD0B hashtree=PARTUUID=D8242B83-5E09-2247-9220-A5581F2ADD0B hashstart=4077568 alg=sha256 root_hexdigest=1fcc4da1d3e2fa974479ac43cd3fdcc8127ede293888bfb7b0838fa795b4faeb salt=4b4c38200531b5c2f8bc29d1b7fe9f123da0c493ac086cc2dd5084888a61ce4b"
+}
+
+menuentry "verified image B" {
+ linux /syslinux/vmlinuz.B init=/usr/lib/systemd/systemd boot=local rootwait ro noresume noswap loglevel=7 noinitrd console=ttyS0 security=apparmor virtio_net.napi_tx=1 systemd.unified_cgroup_hierarchy=false systemd.legacy_systemd_cgroup_controller=false csm.disabled=1 dm_verity.error_behavior=3 dm_verity.max_bios=-1 dm_verity.dev_wait=1 i915.modeset=1 cros_efi root=/dev/dm-0 dm="1 vroot none ro 1,0 4077568 verity payload=PARTUUID=D09026B0-6BC4-3C4A-A0D5-F7B8C67E96CC hashtree=PARTUUID=D09026B0-6BC4-3C4A-A0D5-F7B8C67E96CC hashstart=4077568 alg=sha256 root_hexdigest=1fcc4da1d3e2fa974479ac43cd3fdcc8127ede293888bfb7b0838fa795b4faeb salt=4b4c38200531b5c2f8bc29d1b7fe9f123da0c493ac086cc2dd5084888a61ce4b"
+}
+
+# FIXME: usb doesn't support verified boot for now
+menuentry "Alternate USB Boot" {
+ linux (hd0,3)/boot/vmlinuz init=/usr/lib/systemd/systemd boot=local rootwait ro noresume noswap loglevel=7 noinitrd console=ttyS0 security=apparmor virtio_net.napi_tx=1 systemd.unified_cgroup_hierarchy=false systemd.legacy_systemd_cgroup_controller=false csm.disabled=1 root=PARTUUID=D8242B83-5E09-2247-9220-A5581F2ADD0B i915.modeset=1 cros_efi
+}
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image2/partitions.txt b/src/cmd/cos_image_analyzer/internal/testdata/image2/partitions.txt
index d202b1a..ad5e7b3 100644
--- a/src/cmd/cos_image_analyzer/internal/testdata/image2/partitions.txt
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image2/partitions.txt
@@ -19,4 +19,4 @@
9 16450 16450 512 bytes 7F02 reserved
10 16451 16451 512 bytes 7F02 reserved
11 64 16447 8.0 MiB EF02 RWFW
- 12 249856 315391 32.0 MiB EF00 EFI-SYSTEM
+ 12 249856 315391 32.0 MiB EF00 EFI-SYSTEM
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/etc/sysctl.d/00-sysctl.conf b/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/etc/sysctl.d/00-sysctl.conf
new file mode 100644
index 0000000..2484c00
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/etc/sysctl.d/00-sysctl.conf
@@ -0,0 +1,14 @@
+# /etc/sysctl.conf
+# Look in /proc/sys/ for all the things you can setup.
+#
+
+# Enables source route verification
+net.ipv4.conf.default.rp_filter = 1
+# Enable reverse path
+net.ipv4.conf.all.rp_filter = 2
+
+# Disable shrinking the cwnd when connection is idle
+net.ipv4.tcp_slow_start_after_idle = 1
+
+# dumby variable
+net.ipv4.conf = 2
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image2/usr/src/linux-headers-4.19.112+/.config b/src/cmd/cos_image_analyzer/internal/testdata/image2/usr/src/linux-headers-4.19.112+/.config
new file mode 100644
index 0000000..75cd72a
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image2/usr/src/linux-headers-4.19.112+/.config
@@ -0,0 +1,16 @@
+#
+# Compiler: Chromium OS 9.0_pre361749_p20190714-r4 clang version 9.0.0 (/var/cache/chromeos-cache/distfiles/host/egit-src/llvm-project c11de5eada2decd0a495ea02676b6f4838cd54fb) (based on LLVM 9.0.0svn)
+#
+CONFIG_GCC_VERSION=0
+CONFIG_CC_IS_CLANG=y
+CONFIG_CLANG_VERSION=90000
+
+#
+# General setup
+#
+CONFIG_INIT_ENV_ARG_LIMIT=32
+# CONFIG_COMPILE_TEST is not set
+CONFIG_LOCALVERSION=""
+# CONFIG_LOCALVERSION_AUTO is not set
+CONFIG_BUILD_SALT=""
+CONFIG_HAVE_KERNEL_GZIP=y
diff --git a/src/cmd/cos_image_analyzer/internal/utilities/gcs_download.go b/src/cmd/cos_image_analyzer/internal/utilities/gcs_download.go
index 6403a2f..8b1a8c1 100644
--- a/src/cmd/cos_image_analyzer/internal/utilities/gcs_download.go
+++ b/src/cmd/cos_image_analyzer/internal/utilities/gcs_download.go
@@ -22,9 +22,10 @@
// (string) bucket - Name of the GCS bucket
// (string) object - Name of the GCS object
// (string) destDir - Destination for downloaded GCS object
+// (string) name - Name for the downloaded file
// Output:
// (string) downloadedFile - Path to downloaded GCS object
-func GcsDowndload(bucket, object, destDir string) (string, error) {
+func GcsDowndload(bucket, object, destDir, name string) (string, error) {
// Call API to download GCS object into tempDir
ctx := context.Background()
client, err := storage.NewClient(ctx)
@@ -42,7 +43,7 @@
}
defer rc.Close()
- downloadedFile, err := os.Create(filepath.Join(destDir, object))
+ downloadedFile, err := os.Create(filepath.Join(destDir, name))
if err != nil {
return "", fmt.Errorf("failed to create file %v/%v: %v", destDir, object, err)
}
@@ -54,6 +55,6 @@
}
bytesStr := strconv.FormatInt(bytesDownloaded, base10)
- log.Print("Blob " + object + " downloaded. \n" + bytesStr + " Total Bytes")
+ log.Print("GCS object: ", object, " downloaded from GCS bucket: ", bucket, ". Total bytes ", bytesStr)
return downloadedFile.Name(), nil
}
diff --git a/src/cmd/cos_image_analyzer/internal/utilities/logic_helper.go b/src/cmd/cos_image_analyzer/internal/utilities/logic_helper.go
index 04b605d..a55efc9 100644
--- a/src/cmd/cos_image_analyzer/internal/utilities/logic_helper.go
+++ b/src/cmd/cos_image_analyzer/internal/utilities/logic_helper.go
@@ -1,13 +1,18 @@
package utilities
import (
+ "bytes"
+ "errors"
"fmt"
"io"
"os"
"os/exec"
+ "strconv"
"strings"
)
+const sectorSize = 512
+
// InArray determines if a string appears in a string array
func InArray(val string, arr []string) bool {
for _, elem := range arr {
@@ -75,9 +80,73 @@
return output
}
+// getPartitionStart finds the start partition offset of the disk
+// Input:
+// (string) diskFile - Name of DOS/MBR file (ex: disk.raw)
+// (string) partition - The partition number you are pulling the offset from
+// Output:
+// (int) start - The start of the partition on the disk
+func getPartitionStart(partition, diskRaw string) (int, error) {
+ //create command
+ cmd1 := exec.Command("fdisk", "-l", diskRaw)
+ cmd2 := exec.Command("grep", diskRaw+partition)
+
+ reader, writer := io.Pipe()
+ var buf bytes.Buffer
+
+ cmd1.Stdout = writer
+ cmd2.Stdin = reader
+ cmd2.Stdout = &buf
+
+ cmd1.Start()
+ cmd2.Start()
+ cmd1.Wait()
+ writer.Close()
+ cmd2.Wait()
+ reader.Close()
+
+ words := strings.Fields(buf.String())
+ if len(words) < 2 {
+ return -1, errors.New("Error: " + diskRaw + " is not a valid DOS/MBR boot sector file")
+ }
+ start, err := strconv.Atoi(words[1])
+ if err != nil {
+ return -1, fmt.Errorf("failed to convert Ascii %v to string: %v", words[1], err)
+ }
+
+ return start, nil
+}
+
+// MountDisk finds a free loop device and mounts a DOS/MBR disk file
+// Input:
+// (string) diskFile - Name of DOS/MBR file (ex: disk.raw)
+// (string) mountDir - Mount Destination
+// (string) partition - The partition number you are pulling the offset from
+// Output:
+// (string) loopDevice - Name of the loop device used to mount
+func MountDisk(diskFile, mountDir, partition string) (string, error) {
+ startOfPartition, err := getPartitionStart(partition, diskFile)
+ if err != nil {
+ return "", fmt.Errorf("failed to get start of partition #%v: %v", partition, err)
+ }
+ offset := strconv.Itoa(sectorSize * startOfPartition)
+
+ out, err := exec.Command("sudo", "losetup", "--show", "-fP", diskFile).Output()
+ if err != nil {
+ return "", fmt.Errorf("failed to create new loop device for %v: %v", diskFile, err)
+ }
+
+ loopDevice := string(out[:len(out)-1])
+ _, err = exec.Command("sudo", "mount", "-o", "ro,loop,offset="+offset, loopDevice, mountDir).Output()
+ if err != nil {
+ return "", fmt.Errorf("failed to mount loop device %v at %v: %v", loopDevice, mountDir, err)
+ }
+ return loopDevice, nil
+}
+
// Unmount umounts a mounted directory and deletes its loop device
func Unmount(mountedDirectory, loopDevice string) error {
- if _, err := exec.Command("sudo", "umount", mountedDirectory).Output(); err != nil {
+ if _, err := exec.Command("sudo", "umount", "-l", mountedDirectory).Output(); err != nil {
return fmt.Errorf("failed to umount directory %v: %v", mountedDirectory, err)
}
if _, err := exec.Command("sudo", "losetup", "-d", loopDevice).Output(); err != nil {
diff --git a/src/cmd/cos_image_analyzer/internal/utilities/logic_helper_test.go b/src/cmd/cos_image_analyzer/internal/utilities/logic_helper_test.go
index 7a2e687..98019dc 100644
--- a/src/cmd/cos_image_analyzer/internal/utilities/logic_helper_test.go
+++ b/src/cmd/cos_image_analyzer/internal/utilities/logic_helper_test.go
@@ -71,3 +71,25 @@
}
}
}
+
+// test SliceToMapStr function
+func TestSliceToMapStr(t *testing.T) {
+ type test struct {
+ input []string
+ want map[string]string
+ }
+
+ tests := []test{
+ {input: []string{"a", "b", "c", "d"}, want: map[string]string{"a": "", "b": "", "c": "", "d": ""}},
+ {input: []string{}, want: map[string]string{}},
+ }
+
+ for _, tc := range tests {
+ got := SliceToMapStr(tc.input)
+ for k, v := range tc.want {
+ if v != got[k] {
+ t.Fatalf("SliceToMapStr call expected: %v, got: %v", tc.want, got)
+ }
+ }
+ }
+}
diff --git a/src/cmd/cos_image_analyzer/main.go b/src/cmd/cos_image_analyzer/main.go
index 15d9a4a..e4894c2 100644
--- a/src/cmd/cos_image_analyzer/main.go
+++ b/src/cmd/cos_image_analyzer/main.go
@@ -42,7 +42,7 @@
}
imageDiff.BinaryDiff = binaryDiff
- output, err := imageDiff.Formater(flagInfo)
+ output, err := imageDiff.Formater(image1.TempDir, image2.TempDir, flagInfo)
if err != nil {
return fmt.Errorf("failed to format image difference: %v", err)
}
@@ -68,43 +68,39 @@
return nil
}
+func analyze(flagInfo *input.FlagInfo) error {
+ var image1, image2 *input.ImageInfo
+ defer func() {
+ if err := image1.Cleanup(); err != nil {
+ log.Printf("failed to clean up image %v: %v", flagInfo.Image1, err)
+ }
+ if err := image2.Cleanup(); err != nil {
+ log.Printf("failed to clean up image %v: %v", flagInfo.Image2, err)
+ }
+ }()
+ var err error
+ image1, image2, err = input.GetImages(flagInfo)
+ if err != nil {
+ return fmt.Errorf("failed to get images: %v", err)
+ }
+ if err := CallCosImageAnalyzer(image1, image2, flagInfo); err != nil {
+ return err
+ }
+ return nil
+}
+
func main() {
if runtime.GOOS != "linux" {
fmt.Printf("Error: This is a Linux tool, can not run on %s", runtime.GOOS)
}
-
flagInfo, err := input.ParseFlags()
if err != nil {
log.Printf("failed to parse flags: %v\n", err)
os.Exit(1)
}
-
- image1, image2, err := input.GetImages(flagInfo)
- if err != nil {
- log.Printf("failed to get images: %v", err)
- if err := image1.Cleanup(); err != nil {
- log.Printf("failed to clean up image %v: %v", flagInfo.Image1, err)
- }
- if err := image2.Cleanup(); err != nil {
- log.Printf("failed to clean up image %v: %v", flagInfo.Image2, err)
- }
- os.Exit(1)
- }
-
- if err = CallCosImageAnalyzer(image1, image2, flagInfo); err != nil {
+ if err := analyze(flagInfo); err != nil {
log.Printf("%v\n", err)
- if err := image1.Cleanup(); err != nil {
- log.Printf("failed to clean up image %v: %v", flagInfo.Image1, err)
- }
- if err := image2.Cleanup(); err != nil {
- log.Printf("failed to clean up image %v: %v", flagInfo.Image2, err)
- }
os.Exit(1)
}
- if err := image1.Cleanup(); err != nil {
- log.Printf("failed to clean up image %v: %v", flagInfo.Image1, err)
- }
- if err := image2.Cleanup(); err != nil {
- log.Printf("failed to clean up image %v: %v", flagInfo.Image2, err)
- }
+ os.Exit(0)
}