code.gitea.io/gitea@v1.22.3/modules/dump/dumper.go (about) 1 // Copyright 2024 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package dump 5 6 import ( 7 "fmt" 8 "io" 9 "os" 10 "path" 11 "path/filepath" 12 "slices" 13 "strings" 14 15 "code.gitea.io/gitea/modules/log" 16 "code.gitea.io/gitea/modules/setting" 17 "code.gitea.io/gitea/modules/timeutil" 18 19 "github.com/mholt/archiver/v3" 20 ) 21 22 var SupportedOutputTypes = []string{"zip", "tar", "tar.sz", "tar.gz", "tar.xz", "tar.bz2", "tar.br", "tar.lz4", "tar.zst"} 23 24 // PrepareFileNameAndType prepares the output file name and type, if the type is not supported, it returns an empty "outType" 25 func PrepareFileNameAndType(argFile, argType string) (outFileName, outType string) { 26 if argFile == "" && argType == "" { 27 outType = SupportedOutputTypes[0] 28 outFileName = fmt.Sprintf("gitea-dump-%d.%s", timeutil.TimeStampNow(), outType) 29 } else if argFile == "" { 30 outType = argType 31 outFileName = fmt.Sprintf("gitea-dump-%d.%s", timeutil.TimeStampNow(), outType) 32 } else if argType == "" { 33 if filepath.Ext(outFileName) == "" { 34 outType = SupportedOutputTypes[0] 35 outFileName = argFile 36 } else { 37 for _, t := range SupportedOutputTypes { 38 if strings.HasSuffix(argFile, "."+t) { 39 outFileName = argFile 40 outType = t 41 } 42 } 43 } 44 } else { 45 outFileName, outType = argFile, argType 46 } 47 if !slices.Contains(SupportedOutputTypes, outType) { 48 return "", "" 49 } 50 return outFileName, outType 51 } 52 53 func IsSubdir(upper, lower string) (bool, error) { 54 if relPath, err := filepath.Rel(upper, lower); err != nil { 55 return false, err 56 } else if relPath == "." || !strings.HasPrefix(relPath, ".") { 57 return true, nil 58 } 59 return false, nil 60 } 61 62 type Dumper struct { 63 Writer archiver.Writer 64 Verbose bool 65 66 globalExcludeAbsPaths []string 67 } 68 69 func (dumper *Dumper) AddReader(r io.ReadCloser, info os.FileInfo, customName string) error { 70 if dumper.Verbose { 71 log.Info("Adding file %s", customName) 72 } 73 74 return dumper.Writer.Write(archiver.File{ 75 FileInfo: archiver.FileInfo{ 76 FileInfo: info, 77 CustomName: customName, 78 }, 79 ReadCloser: r, 80 }) 81 } 82 83 func (dumper *Dumper) AddFile(filePath, absPath string) error { 84 file, err := os.Open(absPath) 85 if err != nil { 86 return err 87 } 88 defer file.Close() 89 fileInfo, err := file.Stat() 90 if err != nil { 91 return err 92 } 93 return dumper.AddReader(file, fileInfo, filePath) 94 } 95 96 func (dumper *Dumper) normalizeFilePath(absPath string) string { 97 absPath = filepath.Clean(absPath) 98 if setting.IsWindows { 99 absPath = strings.ToLower(absPath) 100 } 101 return absPath 102 } 103 104 func (dumper *Dumper) GlobalExcludeAbsPath(absPaths ...string) { 105 for _, absPath := range absPaths { 106 dumper.globalExcludeAbsPaths = append(dumper.globalExcludeAbsPaths, dumper.normalizeFilePath(absPath)) 107 } 108 } 109 110 func (dumper *Dumper) shouldExclude(absPath string, excludes []string) bool { 111 norm := dumper.normalizeFilePath(absPath) 112 return slices.Contains(dumper.globalExcludeAbsPaths, norm) || slices.Contains(excludes, norm) 113 } 114 115 func (dumper *Dumper) AddRecursiveExclude(insidePath, absPath string, excludes []string) error { 116 excludes = slices.Clone(excludes) 117 for i := range excludes { 118 excludes[i] = dumper.normalizeFilePath(excludes[i]) 119 } 120 return dumper.addFileOrDir(insidePath, absPath, excludes) 121 } 122 123 func (dumper *Dumper) addFileOrDir(insidePath, absPath string, excludes []string) error { 124 absPath, err := filepath.Abs(absPath) 125 if err != nil { 126 return err 127 } 128 dir, err := os.Open(absPath) 129 if err != nil { 130 return err 131 } 132 defer dir.Close() 133 134 files, err := dir.Readdir(0) 135 if err != nil { 136 return err 137 } 138 for _, file := range files { 139 currentAbsPath := filepath.Join(absPath, file.Name()) 140 if dumper.shouldExclude(currentAbsPath, excludes) { 141 continue 142 } 143 144 currentInsidePath := path.Join(insidePath, file.Name()) 145 if file.IsDir() { 146 if err := dumper.AddFile(currentInsidePath, currentAbsPath); err != nil { 147 return err 148 } 149 if err = dumper.addFileOrDir(currentInsidePath, currentAbsPath, excludes); err != nil { 150 return err 151 } 152 } else { 153 // only copy regular files and symlink regular files, skip non-regular files like socket/pipe/... 154 shouldAdd := file.Mode().IsRegular() 155 if !shouldAdd && file.Mode()&os.ModeSymlink == os.ModeSymlink { 156 target, err := filepath.EvalSymlinks(currentAbsPath) 157 if err != nil { 158 return err 159 } 160 targetStat, err := os.Stat(target) 161 if err != nil { 162 return err 163 } 164 shouldAdd = targetStat.Mode().IsRegular() 165 } 166 if shouldAdd { 167 if err = dumper.AddFile(currentInsidePath, currentAbsPath); err != nil { 168 return err 169 } 170 } 171 } 172 } 173 return nil 174 }