blob: 125f9459f9bbcc3bd4996936fce00e7937027887 [file] [log] [blame]
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
}