github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/cmd/snap-repair/trace.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2017 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package main 21 22 import ( 23 "bufio" 24 "fmt" 25 "io" 26 "os" 27 "path/filepath" 28 "regexp" 29 "strconv" 30 "strings" 31 32 "github.com/snapcore/snapd/dirs" 33 ) 34 35 // newRepairTraces returns all repairTrace about the given "brand" and "seq" 36 // that can be found. brand, seq can be filepath.Glob expressions. 37 func newRepairTraces(brand, seq string) ([]*repairTrace, error) { 38 matches, err := filepath.Glob(filepath.Join(dirs.SnapRepairRunDir, brand, seq, "*")) 39 if err != nil { 40 return nil, err 41 } 42 43 var repairTraces []*repairTrace 44 for _, match := range matches { 45 if trace := newRepairTraceFromPath(match); trace != nil { 46 repairTraces = append(repairTraces, trace) 47 } 48 } 49 50 return repairTraces, nil 51 } 52 53 // repairTrace holds information about a repair that was run. 54 type repairTrace struct { 55 path string 56 } 57 58 // validRepairTraceName checks that the given name looks like a valid repair 59 // trace 60 var validRepairTraceName = regexp.MustCompile(`^r[0-9]+\.(done|skip|retry|running)$`) 61 62 // newRepairTraceFromPath takes a repair log path like 63 // the path /var/lib/snapd/repair/run/my-brand/1/r2.done 64 // and contructs a repair log from that. 65 func newRepairTraceFromPath(path string) *repairTrace { 66 rt := &repairTrace{path: path} 67 if !validRepairTraceName.MatchString(filepath.Base(path)) { 68 return nil 69 } 70 return rt 71 } 72 73 // Repair returns the repair human readable string in the form $brand-$id 74 func (rt *repairTrace) Repair() string { 75 seq := filepath.Base(filepath.Dir(rt.path)) 76 brand := filepath.Base(filepath.Dir(filepath.Dir(rt.path))) 77 78 return fmt.Sprintf("%s-%s", brand, seq) 79 } 80 81 // Revision returns the revision of the repair 82 func (rt *repairTrace) Revision() string { 83 rev, err := revFromFilepath(rt.path) 84 if err != nil { 85 // this can never happen because we check that path starts 86 // with the right prefix. However handle the case just in 87 // case. 88 return "-" 89 } 90 return rev 91 } 92 93 // Summary returns the summary of the repair that was run 94 func (rt *repairTrace) Summary() string { 95 f, err := os.Open(rt.path) 96 if err != nil { 97 return "-" 98 } 99 defer f.Close() 100 101 needle := "summary: " 102 scanner := bufio.NewScanner(f) 103 for scanner.Scan() { 104 s := scanner.Text() 105 if strings.HasPrefix(s, needle) { 106 return s[len(needle):] 107 } 108 } 109 110 return "-" 111 } 112 113 // Status returns the status of the given repair {done,skip,retry,running} 114 func (rt *repairTrace) Status() string { 115 return filepath.Ext(rt.path)[1:] 116 } 117 118 func indentPrefix(level int) string { 119 return strings.Repeat(" ", level) 120 } 121 122 // WriteScriptIndented outputs the script that produced this repair output 123 // to the given writer w with the indent level given by indent. 124 func (rt *repairTrace) WriteScriptIndented(w io.Writer, indent int) error { 125 scriptPath := rt.path[:strings.LastIndex(rt.path, ".")] + ".script" 126 f, err := os.Open(scriptPath) 127 if err != nil { 128 return err 129 } 130 defer f.Close() 131 132 scanner := bufio.NewScanner(f) 133 for scanner.Scan() { 134 fmt.Fprintf(w, "%s%s\n", indentPrefix(indent), scanner.Text()) 135 } 136 if scanner.Err() != nil { 137 return scanner.Err() 138 } 139 return nil 140 } 141 142 // WriteOutputIndented outputs the repair output to the given writer w 143 // with the indent level given by indent. 144 func (rt *repairTrace) WriteOutputIndented(w io.Writer, indent int) error { 145 f, err := os.Open(rt.path) 146 if err != nil { 147 return err 148 } 149 defer f.Close() 150 151 scanner := bufio.NewScanner(f) 152 // move forward in the log to where the actual script output starts 153 for scanner.Scan() { 154 if scanner.Text() == "output:" { 155 break 156 } 157 } 158 // write the script output to w 159 for scanner.Scan() { 160 fmt.Fprintf(w, "%s%s\n", indentPrefix(indent), scanner.Text()) 161 } 162 if scanner.Err() != nil { 163 return scanner.Err() 164 } 165 return nil 166 } 167 168 // revFromFilepath is a helper that extracts the revision number from the 169 // filename of the repairTrace 170 func revFromFilepath(name string) (string, error) { 171 var rev int 172 if _, err := fmt.Sscanf(filepath.Base(name), "r%d.", &rev); err == nil { 173 return strconv.Itoa(rev), nil 174 } 175 return "", fmt.Errorf("cannot find revision in %q", name) 176 }