| 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 |
| } |