github.com/pingcap/failpoint@v0.0.0-20240412033321-fd0796e60f86/code/restorer.go (about) 1 // Copyright 2019 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package code 16 17 import ( 18 "bytes" 19 "fmt" 20 "io/ioutil" 21 "os" 22 "path/filepath" 23 "strings" 24 25 "github.com/sergi/go-diff/diffmatchpatch" 26 ) 27 28 const ( 29 failpointStashFileSuffix = "__failpoint_stash__" 30 failpointBindingFileName = "binding__failpoint_binding__.go" 31 ) 32 33 // Restorer represents a manager to restore currentFile tree which has been modified by 34 // `failpoint-ctl enable`, e.g: 35 /* 36 // ├── foo 37 // │ ├── foo.go 38 // │ └── foo.go__failpoint_stash__ 39 // ├── bar 40 // │ ├── bar.go 41 // │ └── bar.go__failpoint_stash__ 42 // └── foobar 43 // ├── foobar.go 44 // └── foobar.go__failpoint_stash__ 45 // Which will be restored as below: 46 // ├── foo 47 // │ └── foo.go <- foo.go__failpoint_stash__ 48 // ├── bar 49 // │ └── bar.go <- bar.go__failpoint_stash__ 50 // └── foobar 51 // └── foobar.go <- foobar.go__failpoint_stash__ 52 */ 53 type Restorer struct { 54 path string 55 } 56 57 // NewRestorer returns a non-nil restorer which is used to clean the workspace 58 // of the specified path 59 func NewRestorer(path string) *Restorer { 60 return &Restorer{path: path} 61 } 62 63 // Restore restores the currentFile tree which will delete all files generated 64 // by `failpoint-ctl enable` and replace it by fail point stashed currentFile 65 func (r Restorer) Restore() error { 66 var stashFiles []string 67 err := filepath.Walk(r.path, func(path string, info os.FileInfo, err error) error { 68 if err != nil { 69 return err 70 } 71 if info.IsDir() { 72 return nil 73 } 74 if strings.HasSuffix(path, failpointStashFileSuffix) || 75 strings.HasSuffix(path, failpointBindingFileName) { 76 stashFiles = append(stashFiles, path) 77 } 78 return nil 79 }) 80 if err != nil { 81 return err 82 } 83 for _, filePath := range stashFiles { 84 if strings.HasSuffix(filePath, failpointBindingFileName) { 85 if err := os.Remove(filePath); err != nil { 86 return err 87 } 88 continue 89 } 90 originFileName := filePath[:len(filePath)-len(failpointStashFileSuffix)] 91 rewritedContent, err := ioutil.ReadFile(originFileName) 92 if err != nil { 93 return err 94 } 95 originContent, err := ioutil.ReadFile(filePath) 96 if err != nil { 97 return err 98 } 99 // Rewrite original file 100 rewriter := NewRewriter(filePath) 101 buffer := &bytes.Buffer{} 102 rewriter.SetOutput(buffer) 103 if err := rewriter.RewriteFile(filePath); err != nil { 104 return err 105 } 106 107 // Merge modifications after `failpoint-ctl enable` 108 patcher := diffmatchpatch.New() 109 diffs := patcher.DiffMain(buffer.String(), string(rewritedContent), true) 110 patches := patcher.PatchMake(diffs) 111 pathedContent, results := patcher.PatchApply(patches, string(originContent)) 112 for i, result := range results { 113 if !result { 114 return fmt.Errorf("cannot merge modifications back automatically %s", patches[i].String()) 115 } 116 } 117 if err := ioutil.WriteFile(filePath, []byte(pathedContent), 0644); err != nil { 118 return err 119 } 120 if err := os.Remove(originFileName); err != nil { 121 return err 122 } 123 if err := os.Rename(filePath, originFileName); err != nil { 124 return err 125 } 126 } 127 return nil 128 } 129 130 func failpointBindingPath(path string) string { 131 return filepath.Join(filepath.Dir(path), failpointBindingFileName) 132 } 133 134 func isBindingFileExists(path string) (bool, error) { 135 bindingFile := failpointBindingPath(path) 136 _, err := os.Stat(bindingFile) 137 if err != nil && os.IsNotExist(err) { 138 return false, nil 139 } 140 return true, err 141 } 142 143 func writeBindingFile(path, pak string) error { 144 bindingFile := failpointBindingPath(path) 145 bindingContent := fmt.Sprintf(` 146 package %s 147 148 import "reflect" 149 150 type __failpointBindingType struct {pkgpath string} 151 var __failpointBindingCache = &__failpointBindingType{} 152 153 func init() { 154 __failpointBindingCache.pkgpath = reflect.TypeOf(__failpointBindingType{}).PkgPath() 155 } 156 func %s(name string) string { 157 return __failpointBindingCache.pkgpath + "/" + name 158 } 159 `, pak, ExtendPkgName) 160 return ioutil.WriteFile(bindingFile, []byte(bindingContent), 0644) 161 }