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