blob: 3e9116cfdea12ad867a07d85e763576b35f608fe [file] [log] [blame]
package binary
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"cos.googlesource.com/cos/tools.git/src/cmd/cos_image_analyzer/internal/input"
"cos.googlesource.com/cos/tools.git/src/cmd/cos_image_analyzer/internal/utilities"
)
// 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 map[string]string{}, fmt.Errorf("fail to read contents of directory %v: %v", image1.RootfsPartition3+etc, err)
}
etcEntries1 := []string{}
for _, f := range etcFiles1 {
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 map[string]string{}, fmt.Errorf("fail to read contents of directory %v: %v", image2.RootfsPartition3+etc, err)
}
etcEntries2 := []string{}
for _, f := range etcFiles2 {
if _, err := os.Readlink(filepath.Join(image1.RootfsPartition3, etc, f.Name())); err != nil {
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
}
}
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
// 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
// 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 {
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) {
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)
}
}
diffStr := strings.TrimSuffix(string(diff), "\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", "-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)
}
}
return strings.TrimSuffix(string(diff), "\n"), nil
}