Merge "cos_image_analyzer: Milestone 3 - Rootfs, Stateful, OS-Configs, and Partition-Structure difference"
diff --git a/src/cmd/cos_image_analyzer/internal/binary/CompressRootfs.txt b/src/cmd/cos_image_analyzer/internal/binary/CompressRootfs.txt
new file mode 100644
index 0000000..5cddf4a
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/binary/CompressRootfs.txt
@@ -0,0 +1,11 @@
+/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
new file mode 100644
index 0000000..38331fc
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/binary/CompressStateful.txt
@@ -0,0 +1 @@
+/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 a866278..504fdb1 100644
--- a/src/cmd/cos_image_analyzer/internal/binary/diff.go
+++ b/src/cmd/cos_image_analyzer/internal/binary/diff.go
@@ -1,12 +1,22 @@
 package binary
 
 import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"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"
 )
 
 // Global variables
 var (
 	// Command-line path strings
+	// /etc is the OS configurations directory
+	etc = "/etc/"
+
 	// /etc/os-release is the file describing COS versioning
 	etcOSRelease = "/etc/os-release"
 )
@@ -14,36 +24,172 @@
 // 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
+	Version            []string
+	BuildID            []string
+	Rootfs             string
+	KernelCommandLine  string
+	Stateful           string
+	PartitionStructure string
+	SysctlSettings     string
+	OSConfigs          map[string]string
+	KernelConfigs      string
 }
 
-// VersionDiff calculates the Version difference of two images
-func (binaryDiff *Differences) VersionDiff(image1 *input.ImageInfo, image2 *input.ImageInfo) {
+// versionDiff calculates the Version difference of two images
+func (binaryDiff *Differences) versionDiff(image1, image2 *input.ImageInfo) {
 	if image1.Version != image2.Version {
 		binaryDiff.Version = []string{image1.Version, image2.Version}
 	}
 }
 
-// BuildDiff calculates the BuildID difference of two images
-func (binaryDiff *Differences) BuildDiff(image1 *input.ImageInfo, image2 *input.ImageInfo) {
+// buildDiff calculates the BuildID difference of two images
+func (binaryDiff *Differences) buildDiff(image1, image2 *input.ImageInfo) {
 	if image1.BuildID != image2.BuildID {
 		binaryDiff.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)
+	if err != nil {
+		return fmt.Errorf("failed to convert file %v to slice: %v", flagInfo.CompressRootfsFile, 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
+	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 {
+		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)
+			}
+
+			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)
+			if err != nil {
+				return fmt.Errorf("failed to convert file %v to slice: %v", flagInfo.CompressRootfsFile, 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)
+			}
+			binaryDiff.OSConfigs[etcEntryName] = osConfigDiff
+		}
+	}
+	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)
+	if err != nil {
+		return fmt.Errorf("failed to convert file %v to slice: %v", flagInfo.CompressStatefulFile, 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
+	return nil
+}
+
+// partitionStructureDiff calculates the Version difference of two images
+func (binaryDiff *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)
+		}
+		binaryDiff.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)
+		}
+		binaryDiff.PartitionStructure = string(image1Structure)
+	}
+	return nil
+}
+
 // FormatVersionDiff returns a formated string of the version difference
-func (binaryDiff *Differences) FormatVersionDiff(flagInfo *input.FlagInfo) string {
+func (binaryDiff *Differences) FormatVersionDiff() string {
 	if len(binaryDiff.Version) == 2 {
-		return "Version\n< " + binaryDiff.Version[0] + "\n> " + binaryDiff.Version[1] + "\n"
+		if binaryDiff.Version[1] != "" {
+			return "-----Version-----\n< " + binaryDiff.Version[0] + "\n> " + binaryDiff.Version[1] + "\n\n"
+		}
+		return "-----Version-----\n" + binaryDiff.Version[0] + "\n\n"
 	}
 	return ""
 }
 
 // FormatBuildIDDiff returns a formated string of the build difference
-func (binaryDiff *Differences) FormatBuildIDDiff(flagInfo *input.FlagInfo) string {
+func (binaryDiff *Differences) FormatBuildIDDiff() string {
 	if len(binaryDiff.BuildID) == 2 {
-		return "BuildID\n< " + binaryDiff.BuildID[0] + "\n> " + binaryDiff.BuildID[1] + "\n"
+		if binaryDiff.BuildID[1] != "" {
+			return "-----BuildID-----\n< " + binaryDiff.BuildID[0] + "\n> " + binaryDiff.BuildID[1] + "\n\n"
+		}
+		return "-----BuildID-----\n" + binaryDiff.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"
+	}
+	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"
+	}
+	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"
+			}
+		}
+		return osConfigDifference
+	}
+	return ""
+}
+
+// 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"
 	}
 	return ""
 }
@@ -53,12 +199,40 @@
 // Input:
 //   (*ImageInfo) image1 - A struct that will store binary info for image1
 //   (*ImageInfo) image2 - A struct that will store binary info for image2
+//   (*FlagInfo) flagInfo - A struct that holds input preference from the user
 // Output:
 //   (*Differences) BinaryDiff - A struct that will store the binary differences
-func Diff(image1, image2 *input.ImageInfo) (*Differences, error) {
+func Diff(image1, image2 *input.ImageInfo, flagInfo *input.FlagInfo) (*Differences, error) {
 	BinaryDiff := &Differences{}
-	BinaryDiff.VersionDiff(image1, image2)
-	BinaryDiff.BuildDiff(image1, image2)
 
+	if utilities.InArray("Version", flagInfo.BinaryTypesSelected) {
+		BinaryDiff.versionDiff(image1, image2)
+	}
+	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 image2.TempDir != "" {
+		if utilities.InArray("Rootfs", flagInfo.BinaryTypesSelected) {
+			if err := BinaryDiff.rootfsDiff(image1, image2, flagInfo); err != nil {
+				return BinaryDiff, fmt.Errorf("Failed to get Roofs difference: %v", err)
+			}
+		}
+		if utilities.InArray("OS-config", flagInfo.BinaryTypesSelected) {
+			if err := BinaryDiff.osConfigDiff(image1, image2, flagInfo); err != nil {
+				return BinaryDiff, fmt.Errorf("Failed to get OS-config difference: %v", err)
+			}
+		}
+		if utilities.InArray("Stateful-partition", flagInfo.BinaryTypesSelected) {
+			if err := BinaryDiff.statefulDiff(image1, image2, flagInfo); err != nil {
+				return BinaryDiff, fmt.Errorf("Failed to get Stateful-partition difference: %v", err)
+			}
+		}
+	}
 	return BinaryDiff, nil
 }
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 7438155..299c88f 100644
--- a/src/cmd/cos_image_analyzer/internal/binary/diff_test.go
+++ b/src/cmd/cos_image_analyzer/internal/binary/diff_test.go
@@ -7,36 +7,160 @@
 	"cos.googlesource.com/cos/tools/src/cmd/cos_image_analyzer/internal/utilities"
 )
 
-// test ReadFileToMap function
+// test Diff function
 func TestDiff(t *testing.T) {
+	// 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
+Files ../testdata/image1/rootfs/proc/security/configs and ../testdata/image2/rootfs/proc/security/configs differ
+Only in ../testdata/image1/rootfs/usr/lib: usr-lib-image1
+Only in ../testdata/image2/rootfs/usr/lib: usr-lib-image2`
+	testBriefRootfsDiff := `Files ../testdata/image1/rootfs/lib64/python.txt and ../testdata/image2/rootfs/lib64/python.txt differ
+Files in ../testdata/image1/rootfs/proc and ../testdata/image2/rootfs/proc differ
+Unique files in ../testdata/image1/rootfs/usr/lib
+Unique files in ../testdata/image2/rootfs/usr/lib`
+
+	// 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
+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"}
+	testBriefOSConfig := map[string]string{
+		"docker": `Files ../testdata/image1/rootfs/etc/docker/credentials.txt and ../testdata/image2/rootfs/etc/docker/credentials.txt differ
+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"}
+
+	// Stateful test data
+	testVerboseStatefulDiff := `Only in ../testdata/image1/stateful/dev_image: image1_dev.txt
+Only in ../testdata/image2/stateful/dev_image: image2_dev.txt
+Only in ../testdata/image1/stateful/var_overlay/db: image1_data.txt
+Only in ../testdata/image2/stateful/var_overlay/db: image2_data.txt`
+	testBriefStatefulDiff := `Only in ../testdata/image1/stateful/dev_image: image1_dev.txt
+Only in ../testdata/image2/stateful/dev_image: image2_dev.txt
+Unique files in ../testdata/image1/stateful/var_overlay
+Unique files in ../testdata/image2/stateful/var_overlay`
+
+	// Partition Structure test data
+	testPartitionStructure := `1c1
+< Disk /img_disks/cos_81_12871_119_disk/disk.raw: 20971520 sectors, 10.0 GiB
+---
+> Disk /img_disks/cos_77_12371_273_disk/disk.raw: 20971520 sectors, 10.0 GiB
+3c3
+< Disk identifier (GUID): 0274E604-5DE3-5E4E-A4FD-F4D00FBBD7AA
+---
+> Disk identifier (GUID): AB9719F2-3174-4F46-8079-1CF470D2D9BC
+11c11
+<    1         8704000        18874476   4.8 GiB     8300  STATE
+---
+>    1         8704000        18874476   4.8 GiB     0700  STATE
+18c18
+<    8           86016          118783   16.0 MiB    8300  OEM
+---
+>    8           86016          118783   16.0 MiB    0700  OEM`
+
 	for _, tc := range []struct {
-		Image1 *input.ImageInfo
-		Image2 *input.ImageInfo
-		want   *Differences
-	}{
-		{Image1: &input.ImageInfo{RootfsPartition3: "../testdata/image1", Version: "81", BuildID: "12871.119.0"},
-			Image2: &input.ImageInfo{},
-			want:   &Differences{Version: []string{"81", ""}, BuildID: []string{"12871.119.0", ""}}},
+		Image1   *input.ImageInfo
+		Image2   *input.ImageInfo
+		FlagInfo *input.FlagInfo
+		want     *Differences
+	}{ // Version and BuildID difference tests
+		{Image1: &input.ImageInfo{RootfsPartition3: "../testdata/image1/rootfs/", Version: "81", BuildID: "12871.119.0"},
+			Image2:   &input.ImageInfo{},
+			FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Version", "BuildID"}},
+			want:     &Differences{Version: []string{"81", ""}, BuildID: []string{"12871.119.0", ""}}},
 
-		{Image1: &input.ImageInfo{RootfsPartition3: "../testdata/image1", Version: "81", BuildID: "12871.119.0"},
-			Image2: &input.ImageInfo{RootfsPartition3: "../testdata/image2", Version: "77", BuildID: "12371.273.0"},
-			want:   &Differences{Version: []string{"81", "77"}, BuildID: []string{"12871.119.0", "12371.273.0"}}},
+		{Image1: &input.ImageInfo{RootfsPartition3: "../testdata/image1/rootfs/", Version: "81", BuildID: "12871.119.0"},
+			Image2:   &input.ImageInfo{RootfsPartition3: "../testdata/image2/rootfs/", Version: "77", BuildID: "12371.273.0"},
+			FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Version", "BuildID"}},
+			want:     &Differences{Version: []string{"81", "77"}, BuildID: []string{"12871.119.0", "12371.273.0"}}},
 
-		{Image1: &input.ImageInfo{RootfsPartition3: "../testdata/image1", Version: "81", BuildID: "12871.119.0"},
-			Image2: &input.ImageInfo{RootfsPartition3: "../testdata/image1", Version: "81", BuildID: "12871.119.1"},
-			want:   &Differences{Version: []string{}, BuildID: []string{"12871.119.0", "12871.119.1"}}},
+		{Image1: &input.ImageInfo{RootfsPartition3: "../testdata/image1/rootfs/", Version: "81", BuildID: "12871.119.0"},
+			Image2:   &input.ImageInfo{RootfsPartition3: "../testdata/image1/rootfs/", Version: "81", BuildID: "12871.119.1"},
+			FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Version", "BuildID"}},
+			want:     &Differences{Version: []string{}, BuildID: []string{"12871.119.0", "12871.119.1"}}},
 
-		{Image1: &input.ImageInfo{RootfsPartition3: "../testdata/image1", Version: "81", BuildID: "12871.119.0"},
-			Image2: &input.ImageInfo{RootfsPartition3: "../testdata/image1", Version: "81", BuildID: "12871.119.0"},
-			want:   &Differences{}},
+		{Image1: &input.ImageInfo{RootfsPartition3: "../testdata/image1/rootfs/", Version: "81", BuildID: "12871.119.0"},
+			Image2:   &input.ImageInfo{RootfsPartition3: "../testdata/image1/rootfs/", Version: "81", BuildID: "12871.119.0"},
+			FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Version", "BuildID"}},
+			want:     &Differences{}},
+
+		// Rootfs difference test
+		{Image1: &input.ImageInfo{TempDir: "../testdata/image1", RootfsPartition3: "../testdata/image1/rootfs/"},
+			Image2:   &input.ImageInfo{},
+			FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"Rootfs"}},
+			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"},
+			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"},
+			want:     &Differences{Rootfs: testBriefRootfsDiff}},
+
+		// OS Config difference test
+		{Image1: &input.ImageInfo{TempDir: "../testdata/image1", RootfsPartition3: "../testdata/image1/rootfs/"},
+			Image2:   &input.ImageInfo{},
+			FlagInfo: &input.FlagInfo{BinaryTypesSelected: []string{"OS-config"}},
+			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"},
+			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"},
+			want:     &Differences{OSConfigs: testBriefOSConfig}},
+
+		// Stateful difference test
+		{Image1: &input.ImageInfo{TempDir: "../testdata/image1", RootfsPartition3: "../testdata/image1/rootfs/"},
+			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"},
+			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"},
+			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}},
 	} {
-		got, _ := Diff(tc.Image1, tc.Image2)
+		got, _ := Diff(tc.Image1, tc.Image2, tc.FlagInfo)
 
 		if !utilities.EqualArrays(tc.want.Version, got.Version) {
-			t.Fatalf("Diff expected: %v, got: %v", tc.want.Version, got.Version)
+			t.Fatalf("Diff expected version %v, got: %v", tc.want.Version, got.Version)
 		}
 		if !utilities.EqualArrays(tc.want.BuildID, got.BuildID) {
-			t.Fatalf("Diff expected: %v, got: %v", tc.want.BuildID, got.BuildID)
+			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)
+		}
+		for etcEntry := range got.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)
+			}
+		}
+		if 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)
 		}
 	}
 }
diff --git a/src/cmd/cos_image_analyzer/internal/binary/helpers.go b/src/cmd/cos_image_analyzer/internal/binary/helpers.go
new file mode 100644
index 0000000..125f945
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/binary/helpers.go
@@ -0,0 +1,195 @@
+package binary
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os/exec"
+	"path/filepath"
+	"regexp"
+	"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"
+)
+
+// 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 {
+	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)
+	}
+	etcEntries1 := []string{}
+	for _, f := range etcFiles1 {
+		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)
+	}
+	etcEntries2 := []string{}
+	for _, f := range etcFiles2 {
+		etcEntries2 = append(etcEntries2, f.Name())
+	}
+
+	osConfigsMap := make(map[string]string)
+	for _, elem1 := range etcEntries1 {
+		if !utilities.InArray(elem1, etcEntries2) { // Unique file or directory in image 1
+			osConfigsMap[elem1] = image1.TempDir
+		} else { // Common /etc files or directories for image 1 and 2
+			osConfigsMap[elem1] = ""
+		}
+	}
+	for _, elem2 := range etcEntries2 {
+		if _, ok := osConfigsMap[elem2]; !ok { // Unique file or directory in image 2
+			osConfigsMap[elem2] = image2.TempDir
+		}
+	}
+	binaryDiff.OSConfigs = osConfigsMap
+	return nil
+}
+
+// findDiffDir finds the directory name from the "diff" command
+// for the "Only in [file path]" case.
+// Input:
+//   (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"
+func findDiffDir(line, dir1, dir2 string) (string, bool) {
+	lineSplit := strings.Split(line, " ")
+	if len(lineSplit) < 3 {
+		return "", false
+	}
+
+	for _, word := range lineSplit {
+		if strings.Contains(word, dir1) && strings.Contains(word, dir2) {
+			return "", false
+		}
+		if strings.Contains(word, dir1) {
+			return dir1, true
+		}
+		if strings.Contains(word, dir2) {
+			return dir2, true
+		}
+	}
+	return "", false
+}
+
+// compressString compresses lines of a string that fit a pattern
+// Input:
+//   (string) dir1 - Path to directory 1
+//   (string) dir2 - Path to directory 2
+//   (string) root - Name of the root for directories 1 and 2
+//   (string) input - The string to be filtered
+//   ([]string) patterns - The list of patterns to be filtered out
+// Output:
+//   (string) output - The compacted version of the input string
+func compressString(dir1, dir2, root, input string, patterns []string) (string, error) {
+	patternMap := utilities.SliceToMapStr(patterns)
+
+	lines := strings.Split(string(input), "\n")
+	for i, line := range lines {
+		for pat, count := range patternMap {
+			fullPattern := filepath.Join(root, pat)
+			fileInPattern := fullPattern + "/"
+			onlyInPattern := fullPattern + ":"
+			if strings.Contains(line, fileInPattern) || strings.Contains(line, onlyInPattern) {
+				lineSplit := strings.Split(line, " ")
+				if len(lineSplit) < 3 {
+					continue
+				}
+
+				typeOfDiff := lineSplit[0]
+				if typeOfDiff == "Files" || typeOfDiff == "Symbolic" || typeOfDiff == "File" {
+					if strings.Contains(count, "differentFilesFound") {
+						lines[i] = ""
+						continue
+					}
+					lines[i] = "Files in " + filepath.Join(dir1, pat) + " and " + filepath.Join(dir2, pat) + " differ"
+					patternMap[pat] += "differentFilesFound"
+				} else if typeOfDiff == "Only" {
+					if strings.Contains(count, "dir1_UniqueFileFound") && strings.Contains(count, "dir2_UniqueFileFound") {
+						lines[i] = ""
+						continue
+					}
+					if onlyDir, ok := findDiffDir(line, dir1, dir2); ok {
+						if onlyDir == dir1 {
+							if !strings.Contains(count, "dir1_UniqueFileFound") {
+								lines[i] = "Unique files in " + filepath.Join(onlyDir, pat)
+								patternMap[pat] += "dir1_UniqueFileFound"
+							} else {
+								lines[i] = ""
+								continue
+							}
+						} else if onlyDir == dir2 {
+							if !strings.Contains(count, "dir2_UniqueFileFound") {
+								lines[i] = "Unique files in " + filepath.Join(onlyDir, pat)
+								patternMap[pat] += "dir2_UniqueFileFound"
+							} else {
+								lines[i] = ""
+								continue
+							}
+						}
+					}
+				} else { // Compress any other diff output not described above
+					lines[i] = ""
+				}
+			}
+		}
+	}
+
+	output := strings.Join(lines, "\n")
+	output = regexp.MustCompile(`[\t\r\n]+`).ReplaceAllString(strings.TrimSpace(output), "\n")
+	return output, nil
+}
+
+// DirectoryDiff finds the recursive file difference between two directories.
+// If verbose is true return full difference, else compress based on compressedDirs
+// Input:
+//   (string) dir1 - Path to directory 1
+//   (string) dir2 - Path to directory 2
+//   (string) root - Name of the root for directories 1 and 2
+//   ([]string) compressedDirs - List of directories to compress by
+//   (bool) verbose - Flag that determines whether to show full or compressed difference
+// 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()
+	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")
+	if verbose {
+		return diffStr, nil
+	}
+	compressedDiffStr, err := compressString(dir1, dir2, root, diffStr, compressedDirs)
+	if err != nil {
+		return "", fmt.Errorf("failed to call compress 'diff' output between %v and %v: %v", dir1, dir2, err)
+	}
+	return compressedDiffStr, nil
+}
+
+// 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()
+	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
+}
diff --git a/src/cmd/cos_image_analyzer/internal/binary/helpers_test.go b/src/cmd/cos_image_analyzer/internal/binary/helpers_test.go
new file mode 100644
index 0000000..d07af4d
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/binary/helpers_test.go
@@ -0,0 +1,61 @@
+package binary
+
+import (
+	"testing"
+)
+
+// test DirectoryDiff function
+func TestDirectoryDiff(t *testing.T) {
+	testVerboseOutput := `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
+Files ../testdata/image1/rootfs/proc/security/configs and ../testdata/image2/rootfs/proc/security/configs differ
+Only in ../testdata/image1/rootfs/usr/lib: usr-lib-image1
+Only in ../testdata/image2/rootfs/usr/lib: usr-lib-image2`
+	testBriefOutput := `Files ../testdata/image1/rootfs/lib64/python.txt and ../testdata/image2/rootfs/lib64/python.txt differ
+Files in ../testdata/image1/rootfs/proc and ../testdata/image2/rootfs/proc differ
+Unique files in ../testdata/image1/rootfs/usr/lib
+Unique files in ../testdata/image2/rootfs/usr/lib`
+
+	for _, tc := range []struct {
+		dir1           string
+		dir2           string
+		root           string
+		verbose        bool
+		compressedDirs []string
+		want           string
+	}{
+		{dir1: "../testdata/image1/rootfs/", dir2: "../testdata/image2/rootfs/", root: "rootfs", verbose: true, compressedDirs: []string{"/proc/", "/usr/lib/"}, want: testVerboseOutput},
+		{dir1: "../testdata/image1/rootfs/", dir2: "../testdata/image2/rootfs/", root: "rootfs", verbose: false, compressedDirs: []string{"/proc/", "/usr/lib/"}, want: testBriefOutput},
+	} {
+		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)
+		}
+	}
+}
+
+// test PureDiff function
+func TestPureDiff(t *testing.T) {
+	testOutput1 := `1c1
+< testing 123 can you hear me?
+---
+> testing 456 can you hear me?`
+	testOutput2 := `1c1
+< These are not the configs you are looking for
+---
+> These are the configs you are looking for`
+	for _, tc := range []struct {
+		input1 string
+		input2 string
+		want   string
+	}{
+		{input1: "../testdata/image1/rootfs/proc/security/access.conf", input2: "../testdata/image2/rootfs/proc/security/access.conf", want: testOutput1},
+		{input1: "../testdata/image1/rootfs/proc/security/configs", input2: "../testdata/image2/rootfs/proc/security/configs", want: testOutput2},
+		{input1: "../testdata/image1/rootfs/proc/security/lib-image1", input2: "../testdata/image2/rootfs/proc/security/lib-image2", want: ""},
+	} {
+		got, _ := pureDiff(tc.input1, tc.input2)
+		if got != tc.want {
+			t.Fatalf("PureDiff 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 30e7ce6..cce8b5e 100644
--- a/src/cmd/cos_image_analyzer/internal/binary/info.go
+++ b/src/cmd/cos_image_analyzer/internal/binary/info.go
@@ -11,22 +11,33 @@
 // 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) error {
-	if image.RootfsPartition3 == "" {
+func GetBinaryInfo(image *input.ImageInfo, flagInfo *input.FlagInfo) error {
+	if image.TempDir == "" {
 		return nil
 	}
-	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", err)
-	}
-	var ok bool
-	if image.Version, ok = osReleaseMap["VERSION"]; !ok {
-		return errors.New("Error: \"Version\" field not found in /etc/os-release file")
+
+	if image.RootfsPartition3 != "" {
+		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)
+		}
+		var ok bool
+		if image.Version, ok = osReleaseMap["VERSION"]; !ok {
+			return errors.New("Error: \"Version\" field not found in /etc/os-release file")
+		}
+		if image.BuildID, ok = osReleaseMap["BUILD_ID"]; !ok {
+			return errors.New("Error: \"Build_ID\" field not found in /etc/os-release file")
+		}
 	}
 
-	if image.BuildID, ok = osReleaseMap["BUILD_ID"]; !ok {
-		return errors.New("Error: \"Build_ID\" field not found in /etc/os-release file")
+	if utilities.InArray("Partition-structure", flagInfo.BinaryTypesSelected) {
+		if err := image.GetPartitionStructure(); err != nil {
+			return fmt.Errorf("failed to get partition structure 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 a5b922b..890444f 100644
--- a/src/cmd/cos_image_analyzer/internal/binary/info_test.go
+++ b/src/cmd/cos_image_analyzer/internal/binary/info_test.go
@@ -6,37 +6,32 @@
 	"cos.googlesource.com/cos/tools/src/cmd/cos_image_analyzer/internal/input"
 )
 
-// test ReadFileToMap function
+// test GetBinaryInf function
 func TestGetBinaryInfo(t *testing.T) {
-	// test normal file
-	testImage1 := &input.ImageInfo{RootfsPartition3: "../testdata/image1"}
-	testImage2 := &input.ImageInfo{}
-	testImage3 := &input.ImageInfo{RootfsPartition3: "../testdata/image2"}
+	for _, tc := range []struct {
+		image    *input.ImageInfo
+		flagInfo *input.FlagInfo
+		want     *input.ImageInfo
+	}{
+		{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"}},
 
-	expectedImage1 := &input.ImageInfo{RootfsPartition3: "../testdata/image1", Version: "81", BuildID: "12871.119.0"}
-	expectedImage2 := &input.ImageInfo{}
-	expectedImage3 := &input.ImageInfo{RootfsPartition3: "../testdata/image2", Version: "77", BuildID: "12371.273.0"}
+		{image: &input.ImageInfo{},
+			flagInfo: &input.FlagInfo{LocalPtr: true},
+			want:     &input.ImageInfo{}},
 
-	type test struct {
-		Image1 *input.ImageInfo
-		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"}},
+	} {
+		GetBinaryInfo(tc.image, tc.flagInfo)
 
-	tests := []test{
-		{Image1: testImage1, want: expectedImage1},
-		{Image1: testImage2, want: expectedImage2},
-		{Image1: testImage3, want: expectedImage3},
-	}
-
-	for _, tc := range tests {
-		GetBinaryInfo(tc.Image1)
-
-		if tc.want.Version != tc.Image1.Version {
-			t.Fatalf("Diff expected: %v, got: %v", tc.want.Version, tc.Image1.Version)
+		if tc.want.Version != tc.image.Version {
+			t.Fatalf("GetBinaryInfo expected: %v, got: %v", tc.want.Version, tc.image.Version)
 		}
-		if tc.want.BuildID != tc.Image1.BuildID {
-			t.Fatalf("Diff expected: %v, got: %v", tc.want.BuildID, tc.Image1.BuildID)
+		if tc.want.BuildID != tc.image.BuildID {
+			t.Fatalf("GetBinaryInfo expected: %v, got: %v", tc.want.BuildID, tc.image.BuildID)
 		}
 	}
-
 }
diff --git a/src/cmd/cos_image_analyzer/internal/input/flaginfo.go b/src/cmd/cos_image_analyzer/internal/input/flaginfo.go
index f8dbe4e..7370c4e 100644
--- a/src/cmd/cos_image_analyzer/internal/input/flaginfo.go
+++ b/src/cmd/cos_image_analyzer/internal/input/flaginfo.go
@@ -23,6 +23,18 @@
 	CommitSelected bool
 	// Release Notes
 	ReleaseNotesSelected bool
+
+	//Verbosity of output
+	Verbose bool
+
+	// File used to compress output from Rootfs and OS-Config difference
+	// (either user provided or default CompressRootfs.txt)
+	CompressRootfsFile string
+
+	// File used to compress output from Stateful-partition difference
+	// (either user provided or default CompressStateful.txt)
+	CompressStatefulFile 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 ad55179..38089bb 100644
--- a/src/cmd/cos_image_analyzer/internal/input/imageinfo.go
+++ b/src/cmd/cos_image_analyzer/internal/input/imageinfo.go
@@ -20,7 +20,7 @@
 
 const sectorSize = 512
 const gcsObjFormat = ".tar.gz"
-const filemode = 0700
+const makeDirFilemode = 0700
 const timeOut = "7200s"
 const imageFormat = "vmdk"
 const name = "gcr.io/compute-image-tools/gce_vm_image_export:release"
@@ -30,8 +30,13 @@
 	// 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
+	LoopDevice1      string // Active loop device for mounted image
 	LoopDevice3      string // Active loop device for mounted image
+	LoopDevice12     string // Active loop device for mounted image
 
 	// Binary info
 	Version string
@@ -42,6 +47,31 @@
 	// Release notes info
 }
 
+// Rename temporary directory and its contents once Version and BuildID are known
+func (image *ImageInfo) Rename(flagInfo *FlagInfo) error {
+	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)
+		}
+		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.EFIPartition12 != "" {
+			image.EFIPartition12 = filepath.Join(fullImageName, "efi")
+		}
+	}
+	return nil
+}
+
 // getPartitionStart finds the start partition offset of the disk
 // Input:
 //   (string) diskFile - Name of DOS/MBR file (ex: disk.raw)
@@ -79,6 +109,29 @@
 	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)
@@ -108,25 +161,54 @@
 
 // MountImage is an ImagInfo method that mounts partitions 1,3 and 12 of
 // the image into the temporary directory
-// Input: None
+// Input:
+//   (string) arr - List of binary types selected from the user
 // Output: nil on success, else error
-func (image *ImageInfo) MountImage() error {
+func (image *ImageInfo) MountImage(arr []string) error {
 	if image.TempDir == "" {
 		return nil
 	}
+	if utilities.InArray("Stateful-partition", arr) {
+		stateful := filepath.Join(image.TempDir, "stateful")
+		if err := os.Mkdir(stateful, makeDirFilemode); err != nil {
+			return fmt.Errorf("failed to create make directory %v: %v", stateful, err)
+		}
+		image.StatePartition1 = stateful
 
-	rootfs := filepath.Join(image.TempDir, "rootFS")
-	if err := os.Mkdir(rootfs, filemode); err != nil {
-		return fmt.Errorf("failed to create make directory %v: %v", rootfs, err)
+		loopDevice1, err := 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)
+		}
+		image.LoopDevice1 = loopDevice1
 	}
-	image.RootfsPartition3 = rootfs
 
-	loopDevice3, err := 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)
+	if utilities.InArray("Version", arr) || utilities.InArray("BuildID", arr) || utilities.InArray("Rootfs", arr) || utilities.InArray("Sysctl-settings", arr) || utilities.InArray("OS-config", arr) || utilities.InArray("Kernel-configs", arr) {
+		rootfs := filepath.Join(image.TempDir, "rootfs")
+		if err := os.Mkdir(rootfs, makeDirFilemode); err != nil {
+			return fmt.Errorf("failed to create make directory %v: %v", rootfs, err)
+		}
+		image.RootfsPartition3 = rootfs
+
+		loopDevice3, err := 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)
+		}
+		image.LoopDevice3 = loopDevice3
 	}
-	image.LoopDevice3 = loopDevice3
 
+	if utilities.InArray("Kernel-command-line", arr) {
+		efi := filepath.Join(image.TempDir, "efi")
+		if err := os.Mkdir(efi, makeDirFilemode); err != nil {
+			return fmt.Errorf("failed to create make directory %v: %v", efi, err)
+		}
+		image.EFIPartition12 = efi
+
+		loopDevice12, err := 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)
+		}
+		image.LoopDevice12 = loopDevice12
+	}
 	return nil
 }
 
@@ -309,13 +391,19 @@
 	if image.TempDir == "" {
 		return nil
 	}
-
-	if image.LoopDevice3 != "" {
-		if _, err := exec.Command("sudo", "umount", image.RootfsPartition3).Output(); err != nil {
-			return fmt.Errorf("failed to umount directory %v: %v", image.RootfsPartition3, err)
+	if image.LoopDevice1 != "" {
+		if err := utilities.Unmount(image.StatePartition1, image.LoopDevice1); err != nil {
+			return fmt.Errorf("failed to unmount mount directory %v and/or loop device %v: %v", image.StatePartition1, image.LoopDevice1, err)
 		}
-		if _, err := exec.Command("sudo", "losetup", "-d", image.LoopDevice3).Output(); err != nil {
-			return fmt.Errorf("failed to delete loop device %v: %v", image.LoopDevice3, err)
+	}
+	if image.LoopDevice3 != "" {
+		if err := utilities.Unmount(image.RootfsPartition3, image.LoopDevice3); err != nil {
+			return fmt.Errorf("failed to unmount mount directory %v and/or loop device %v: %v", image.RootfsPartition3, image.LoopDevice3, err)
+		}
+	}
+	if image.LoopDevice12 != "" {
+		if err := utilities.Unmount(image.EFIPartition12, image.LoopDevice12); err != nil {
+			return fmt.Errorf("failed to unmount mount directory %v and/or loop device %v: %v", image.EFIPartition12, image.LoopDevice12, err)
 		}
 	}
 
diff --git a/src/cmd/cos_image_analyzer/internal/input/parse.go b/src/cmd/cos_image_analyzer/internal/input/parse.go
index 49a7bc3..344f648 100644
--- a/src/cmd/cos_image_analyzer/internal/input/parse.go
+++ b/src/cmd/cos_image_analyzer/internal/input/parse.go
@@ -39,10 +39,6 @@
 		input is one or two objects stored on Google Cloud Storage of type (.tar.gz). This flag temporarily downloads,
 		unzips, and loop device mounts the images into this tool's directory.
 
-	Authentication Flags:
-	-projectID
-		projectID of the Google Cloud Project holding the images. Mandatory for "-cos-cloud" input
-
 	Difference Flags:
 	-binary (string)
 		specify which type of binary difference to show. Types "Version", "BuildID", "Rootfs", "Kernel-command-line",
@@ -58,6 +54,21 @@
 		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
+		include flag to increase verbosity of Rootfs, Stateful-partition, and OS-config differences. See the
+		"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 
+		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/. 
+
 	Output Flags:
 	-output (string)
 		Specify format of output. Only "terminal" stdout or "json" object is supported. (default "terminal")
@@ -97,6 +108,18 @@
 		}
 	}
 
+	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.OutputSelected != "terminal" && flagInfo.OutputSelected != "json" {
 		return errors.New("Error: \"-output\" flag must be ethier \"terminal\" or \"json\"")
 	}
@@ -135,6 +158,10 @@
 	flag.BoolVar(&flagInfo.CommitSelected, "commit", true, "")
 	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.OutputSelected, "output", "terminal", "")
 	flag.Parse()
 
diff --git a/src/cmd/cos_image_analyzer/internal/output/imagediff.go b/src/cmd/cos_image_analyzer/internal/output/imagediff.go
index 13e1d6f..423f91f 100644
--- a/src/cmd/cos_image_analyzer/internal/output/imagediff.go
+++ b/src/cmd/cos_image_analyzer/internal/output/imagediff.go
@@ -24,13 +24,17 @@
 func (imageDiff *ImageDiff) Formater(flagInfo *input.FlagInfo) (string, error) {
 	if flagInfo.OutputSelected == "terminal" {
 		binaryStrings := ""
-		binaryFunctions := map[string]func(*input.FlagInfo) string{
-			"Version": imageDiff.BinaryDiff.FormatVersionDiff,
-			"BuildID": imageDiff.BinaryDiff.FormatBuildIDDiff,
+		binaryFunctions := map[string]func() string{
+			"Version":             imageDiff.BinaryDiff.FormatVersionDiff,
+			"BuildID":             imageDiff.BinaryDiff.FormatBuildIDDiff,
+			"Rootfs":              imageDiff.BinaryDiff.FormatRootfsDiff,
+			"Stateful-partition":  imageDiff.BinaryDiff.FormatStatefulDiff,
+			"OS-config":           imageDiff.BinaryDiff.FormatOSConfigDiff,
+			"Partition-structure": imageDiff.BinaryDiff.FormatPartitionStructureDiff,
 		}
 		for diff := range binaryFunctions {
 			if utilities.InArray(diff, flagInfo.BinaryTypesSelected) {
-				binaryStrings += binaryFunctions[diff](flagInfo)
+				binaryStrings += binaryFunctions[diff]()
 			}
 		}
 
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/CompressRootfsFile.txt b/src/cmd/cos_image_analyzer/internal/testdata/CompressRootfsFile.txt
new file mode 100644
index 0000000..6219d3d
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/CompressRootfsFile.txt
@@ -0,0 +1,3 @@
+/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
new file mode 100644
index 0000000..2e3c219
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/CompressStatefulFile.txt
@@ -0,0 +1 @@
+/var_overlay/
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image1/partitions.txt b/src/cmd/cos_image_analyzer/internal/testdata/image1/partitions.txt
new file mode 100644
index 0000000..928e759
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image1/partitions.txt
@@ -0,0 +1,22 @@
+Disk /img_disks/cos_81_12871_119_disk/disk.raw: 20971520 sectors, 10.0 GiB
+Sector size (logical): 512 bytes
+Disk identifier (GUID): 0274E604-5DE3-5E4E-A4FD-F4D00FBBD7AA
+Partition table holds up to 128 entries
+Main partition table begins at sector 2 and ends at sector 33
+First usable sector is 34, last usable sector is 18874491
+Partitions will be aligned on 1-sector boundaries
+Total free space is 135145 sectors (66.0 MiB)
+
+Number  Start (sector)    End (sector)  Size       Code  Name
+   1         8704000        18874476   4.8 GiB     8300  STATE
+   2           20480           53247   16.0 MiB    7F00  KERN-A
+   3         4509696         8703999   2.0 GiB     7F01  ROOT-A
+   4           53248           86015   16.0 MiB    7F00  KERN-B
+   5          315392         4509695   2.0 GiB     7F01  ROOT-B
+   6           16448           16448   512 bytes   7F00  KERN-C
+   7           16449           16449   512 bytes   7F01  ROOT-C
+   8           86016          118783   16.0 MiB    8300  OEM
+   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
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/etc/docker/credentials.txt b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/etc/docker/credentials.txt
new file mode 100644
index 0000000..e2b7a3d
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/etc/docker/credentials.txt
@@ -0,0 +1,2 @@
+Name: docker.10.2.4
+job: makes micro kernels
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/etc/docker/util/docker.txt b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/etc/docker/util/docker.txt
new file mode 100644
index 0000000..3ad3b6d
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/etc/docker/util/docker.txt
@@ -0,0 +1 @@
+docker configurations for image 1
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/etc/docker/util/lib32/lib32.txt b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/etc/docker/util/lib32/lib32.txt
new file mode 100644
index 0000000..0433c78
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/etc/docker/util/lib32/lib32.txt
@@ -0,0 +1 @@
+32 bit library for docker
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image1/etc/os-release b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/etc/os-release
similarity index 100%
rename from src/cmd/cos_image_analyzer/internal/testdata/image1/etc/os-release
rename to src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/etc/os-release
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/lib64/python.txt b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/lib64/python.txt
new file mode 100644
index 0000000..392bd39
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/lib64/python.txt
@@ -0,0 +1 @@
+Image 1's python info
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/proc/security/access.conf b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/proc/security/access.conf
new file mode 100644
index 0000000..764baa6
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/proc/security/access.conf
@@ -0,0 +1 @@
+testing 123 can you hear me?
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/proc/security/configs b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/proc/security/configs
new file mode 100644
index 0000000..fb6d1d0
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/proc/security/configs
@@ -0,0 +1 @@
+These are not the configs you are looking for
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/usr/lib/usr-lib-image1 b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/usr/lib/usr-lib-image1
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image1/rootfs/usr/lib/usr-lib-image1
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image1/stateful/dev_image/image1_dev.txt b/src/cmd/cos_image_analyzer/internal/testdata/image1/stateful/dev_image/image1_dev.txt
new file mode 100644
index 0000000..acf25dc
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image1/stateful/dev_image/image1_dev.txt
@@ -0,0 +1 @@
+dev space for image1
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image1/stateful/lost+found/Theseus.txt b/src/cmd/cos_image_analyzer/internal/testdata/image1/stateful/lost+found/Theseus.txt
new file mode 100644
index 0000000..7a19555
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image1/stateful/lost+found/Theseus.txt
@@ -0,0 +1 @@
+This is just test data, if you are lost check the README file
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image1/stateful/var_overlay/db/image1_data.txt b/src/cmd/cos_image_analyzer/internal/testdata/image1/stateful/var_overlay/db/image1_data.txt
new file mode 100644
index 0000000..0cc08a0
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image1/stateful/var_overlay/db/image1_data.txt
@@ -0,0 +1 @@
+writeable data for the user on image1
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image2/partitions.txt b/src/cmd/cos_image_analyzer/internal/testdata/image2/partitions.txt
new file mode 100644
index 0000000..d202b1a
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image2/partitions.txt
@@ -0,0 +1,22 @@
+Disk /img_disks/cos_77_12371_273_disk/disk.raw: 20971520 sectors, 10.0 GiB
+Sector size (logical): 512 bytes
+Disk identifier (GUID): AB9719F2-3174-4F46-8079-1CF470D2D9BC
+Partition table holds up to 128 entries
+Main partition table begins at sector 2 and ends at sector 33
+First usable sector is 34, last usable sector is 18874491
+Partitions will be aligned on 1-sector boundaries
+Total free space is 135145 sectors (66.0 MiB)
+
+Number  Start (sector)    End (sector)  Size       Code  Name
+   1         8704000        18874476   4.8 GiB     0700  STATE
+   2           20480           53247   16.0 MiB    7F00  KERN-A
+   3         4509696         8703999   2.0 GiB     7F01  ROOT-A
+   4           53248           86015   16.0 MiB    7F00  KERN-B
+   5          315392         4509695   2.0 GiB     7F01  ROOT-B
+   6           16448           16448   512 bytes   7F00  KERN-C
+   7           16449           16449   512 bytes   7F01  ROOT-C
+   8           86016          118783   16.0 MiB    0700  OEM
+   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
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/etc/docker/credentials.txt b/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/etc/docker/credentials.txt
new file mode 100644
index 0000000..357d7c7
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/etc/docker/credentials.txt
@@ -0,0 +1,2 @@
+Name: docker.10.2.1
+job: makes macro kernels
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/etc/docker/util/lib64/lib64.txt b/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/etc/docker/util/lib64/lib64.txt
new file mode 100644
index 0000000..2936bb7
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/etc/docker/util/lib64/lib64.txt
@@ -0,0 +1 @@
+64-bit library for docker
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image2/etc/os-release b/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/etc/os-release
similarity index 100%
rename from src/cmd/cos_image_analyzer/internal/testdata/image2/etc/os-release
rename to src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/etc/os-release
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/lib64/python.txt b/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/lib64/python.txt
new file mode 100644
index 0000000..ce445c8
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/lib64/python.txt
@@ -0,0 +1 @@
+Image 2's python info
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/proc/security/access.conf b/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/proc/security/access.conf
new file mode 100644
index 0000000..72ab259
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/proc/security/access.conf
@@ -0,0 +1 @@
+testing 456 can you hear me?
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/proc/security/configs b/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/proc/security/configs
new file mode 100644
index 0000000..87be8a5
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/proc/security/configs
@@ -0,0 +1 @@
+These are the configs you are looking for
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/usr/lib/usr-lib-image2 b/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/usr/lib/usr-lib-image2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image2/rootfs/usr/lib/usr-lib-image2
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image2/stateful/dev_image/image2_dev.txt b/src/cmd/cos_image_analyzer/internal/testdata/image2/stateful/dev_image/image2_dev.txt
new file mode 100644
index 0000000..acf25dc
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image2/stateful/dev_image/image2_dev.txt
@@ -0,0 +1 @@
+dev space for image1
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image2/stateful/lost+found/Theseus.txt b/src/cmd/cos_image_analyzer/internal/testdata/image2/stateful/lost+found/Theseus.txt
new file mode 100644
index 0000000..7a19555
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image2/stateful/lost+found/Theseus.txt
@@ -0,0 +1 @@
+This is just test data, if you are lost check the README file
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/image2/stateful/var_overlay/db/image2_data.txt b/src/cmd/cos_image_analyzer/internal/testdata/image2/stateful/var_overlay/db/image2_data.txt
new file mode 100644
index 0000000..979e8c3
--- /dev/null
+++ b/src/cmd/cos_image_analyzer/internal/testdata/image2/stateful/var_overlay/db/image2_data.txt
@@ -0,0 +1 @@
+writeable data for the user on image2
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/os-release-77 b/src/cmd/cos_image_analyzer/internal/testdata/os-release-77
deleted file mode 100644
index b21554c..0000000
--- a/src/cmd/cos_image_analyzer/internal/testdata/os-release-77
+++ /dev/null
@@ -1,2 +0,0 @@
-BUILD_ID=12371.273.0
-ID=cos
\ No newline at end of file
diff --git a/src/cmd/cos_image_analyzer/internal/testdata/os-release-81 b/src/cmd/cos_image_analyzer/internal/testdata/os-release-81
deleted file mode 100644
index 826d8f8..0000000
--- a/src/cmd/cos_image_analyzer/internal/testdata/os-release-81
+++ /dev/null
@@ -1,3 +0,0 @@
-BUILD_ID=12871.119.0
-VERSION=81
-ID=cos
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 f74cd8f..04b605d 100644
--- a/src/cmd/cos_image_analyzer/internal/utilities/logic_helper.go
+++ b/src/cmd/cos_image_analyzer/internal/utilities/logic_helper.go
@@ -1,10 +1,10 @@
 package utilities
 
 import (
-	"errors"
+	"fmt"
 	"io"
 	"os"
-	"path/filepath"
+	"os/exec"
 	"strings"
 )
 
@@ -51,34 +51,37 @@
 	return 1
 }
 
-// CopyFile copies a file over to a new destinaton
-// Input:
-//   (string) path - Local path to the file
-//   (string) dest - Destination to copy the file
-// Output:
-//   (string) copiedFile - path to the newly copied file
-func CopyFile(path, dest string) (string, error) {
-	info, err := os.Stat(path)
-	if os.IsNotExist(err) || info.IsDir() {
-		return "", errors.New("Error: " + path + " is not a file")
-	}
-
-	sourceFile, err := os.Open(path)
+// WriteToNewFile creates a file and writes a string into it
+func WriteToNewFile(filename string, data string) error {
+	file, err := os.Create(filename)
 	if err != nil {
-		return "", errors.New("Error: failed to open file " + path)
+		return err
 	}
-	defer sourceFile.Close()
+	defer file.Close()
 
-	// Create new file
-	copiedFile := filepath.Join(dest, info.Name())
-	newFile, err := os.Create(copiedFile)
+	_, err = io.WriteString(file, data)
 	if err != nil {
-		return "", errors.New("Error: failed to create file " + copiedFile)
+		return err
 	}
-	defer newFile.Close()
+	return file.Sync()
+}
 
-	if _, err := io.Copy(newFile, sourceFile); err != nil {
-		return "", errors.New("Error: failed to copy " + path + " into " + copiedFile)
+// SliceToMapStr initializes a map with keys from input and empty strings as values
+func SliceToMapStr(input []string) map[string]string {
+	output := make(map[string]string)
+	for _, elem := range input {
+		output[elem] = ""
 	}
-	return copiedFile, nil
+	return output
+}
+
+// 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 {
+		return fmt.Errorf("failed to umount directory %v: %v", mountedDirectory, err)
+	}
+	if _, err := exec.Command("sudo", "losetup", "-d", loopDevice).Output(); err != nil {
+		return fmt.Errorf("failed to delete loop device %v: %v", loopDevice, err)
+	}
+	return nil
 }
diff --git a/src/cmd/cos_image_analyzer/internal/utilities/map_helpers.go b/src/cmd/cos_image_analyzer/internal/utilities/map_helpers.go
index b0b9e27..b513fc5 100644
--- a/src/cmd/cos_image_analyzer/internal/utilities/map_helpers.go
+++ b/src/cmd/cos_image_analyzer/internal/utilities/map_helpers.go
@@ -43,7 +43,6 @@
 //   (map[string]string) map2 - Second map to be compared
 //   (string) key - The key of the value be compared in both maps
 // Output:
-//   (stdout) terminal - If equal, print nothing. Else print difference
 //   (int) result - -1 for error, 0 for no difference, 1 for difference
 func CmpMapValues(map1, map2 map[string]string, key string) (int, error) {
 	value1, ok1 := map1[key]
diff --git a/src/cmd/cos_image_analyzer/internal/utilities/map_helpers_test.go b/src/cmd/cos_image_analyzer/internal/utilities/map_helpers_test.go
index 8e43712..64640f1 100644
--- a/src/cmd/cos_image_analyzer/internal/utilities/map_helpers_test.go
+++ b/src/cmd/cos_image_analyzer/internal/utilities/map_helpers_test.go
@@ -7,7 +7,7 @@
 // test ReadFileToMap function
 func TestReadFileToMap(t *testing.T) {
 	// test normal file
-	testFile, sep := "../testdata/os-release-77", "="
+	testFile, sep := "../testdata/image2/rootfs/etc/os-release", "="
 	expectedMap := map[string]string{"BUILD_ID": "12371.273.0", "ID": "cos"}
 	resultMap, _ := ReadFileToMap(testFile, sep)
 
diff --git a/src/cmd/cos_image_analyzer/main.go b/src/cmd/cos_image_analyzer/main.go
index 003c111..15d9a4a 100644
--- a/src/cmd/cos_image_analyzer/main.go
+++ b/src/cmd/cos_image_analyzer/main.go
@@ -23,13 +23,20 @@
 	imageDiff := &output.ImageDiff{}
 
 	err := *new(error)
-	if err := binary.GetBinaryInfo(image1); err != nil {
+	if err := binary.GetBinaryInfo(image1, flagInfo); err != nil {
 		return fmt.Errorf("failed to get GetBinaryInfo from image %v: %v", flagInfo.Image1, err)
 	}
-	if err := binary.GetBinaryInfo(image2); err != nil {
+	if err := binary.GetBinaryInfo(image2, flagInfo); err != nil {
 		return fmt.Errorf("failed to GetBinaryInfo from image %v: %v", flagInfo.Image2, err)
 	}
-	binaryDiff, err := binary.Diff(image1, image2)
+	if err := image1.Rename(flagInfo); err != nil {
+		return fmt.Errorf("failed to rename image %v: %v", flagInfo.Image1, err)
+	}
+	if err := image2.Rename(flagInfo); err != nil {
+		return fmt.Errorf("failed to rename image %v: %v", flagInfo.Image2, err)
+	}
+
+	binaryDiff, err := binary.Diff(image1, image2, flagInfo)
 	if err != nil {
 		return fmt.Errorf("failed to get Binary Difference: %v", err)
 	}
@@ -48,18 +55,11 @@
 }
 
 // CallCosImageAnalyzer is wrapper that gets the images, calls cosImageAnalyzer, and cleans up
-func CallCosImageAnalyzer(flagInfo *input.FlagInfo) error {
-	image1, image2, err := input.GetImages(flagInfo)
-	defer image1.Cleanup()
-	defer image2.Cleanup()
-	if err != nil {
-		return fmt.Errorf("failed to get images: %v", err)
-	}
-
-	if err := image1.MountImage(); err != nil {
+func CallCosImageAnalyzer(image1, image2 *input.ImageInfo, flagInfo *input.FlagInfo) error {
+	if err := image1.MountImage(flagInfo.BinaryTypesSelected); err != nil {
 		return fmt.Errorf("failed to mount first image %v: %v", flagInfo.Image1, err)
 	}
-	if err := image2.MountImage(); err != nil {
+	if err := image2.MountImage(flagInfo.BinaryTypesSelected); err != nil {
 		return fmt.Errorf("failed to mount second image %v: %v", flagInfo.Image2, err)
 	}
 	if err := cosImageAnalyzer(image1, image2, flagInfo); err != nil {
@@ -79,8 +79,32 @@
 		os.Exit(1)
 	}
 
-	if err = CallCosImageAnalyzer(flagInfo); err != nil {
-		log.Printf("%v\n", err)
+	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 {
+		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)
+	}
 }