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  }