github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/internal/base/filenames.go (about) 1 // Copyright 2012 The LevelDB-Go and Pebble Authors. All rights reserved. Use 2 // of this source code is governed by a BSD-style license that can be found in 3 // the LICENSE file. 4 5 package base 6 7 import ( 8 "fmt" 9 "strconv" 10 "strings" 11 12 "github.com/cockroachdb/errors/oserror" 13 "github.com/cockroachdb/pebble/vfs" 14 "github.com/cockroachdb/redact" 15 ) 16 17 // FileNum is an internal DB identifier for a file. 18 type FileNum uint64 19 20 // String returns a string representation of the file number. 21 func (fn FileNum) String() string { return fmt.Sprintf("%06d", fn) } 22 23 // SafeFormat implements redact.SafeFormatter. 24 func (fn FileNum) SafeFormat(w redact.SafePrinter, _ rune) { 25 w.Printf("%06d", redact.SafeUint(fn)) 26 } 27 28 // DiskFileNum converts a FileNum to a DiskFileNum. DiskFileNum should only be 29 // called if the caller can ensure that the FileNum belongs to a physical file 30 // on disk. These could be manifests, log files, physical sstables on disk, the 31 // options file, but not virtual sstables. 32 func (fn FileNum) DiskFileNum() DiskFileNum { 33 return DiskFileNum{fn} 34 } 35 36 // A DiskFileNum is just a FileNum belonging to a file which exists on disk. 37 // Note that a FileNum is an internal DB identifier and it could belong to files 38 // which don't exist on disk. An example would be virtual sstable FileNums. 39 // Converting a DiskFileNum to a FileNum is always valid, whereas converting a 40 // FileNum to DiskFileNum may not be valid and care should be taken to prove 41 // that the FileNum actually exists on disk. 42 type DiskFileNum struct { 43 fn FileNum 44 } 45 46 func (dfn DiskFileNum) String() string { return dfn.fn.String() } 47 48 // SafeFormat implements redact.SafeFormatter. 49 func (dfn DiskFileNum) SafeFormat(w redact.SafePrinter, verb rune) { 50 dfn.fn.SafeFormat(w, verb) 51 } 52 53 // FileNum converts a DiskFileNum to a FileNum. This conversion is always valid. 54 func (dfn DiskFileNum) FileNum() FileNum { 55 return dfn.fn 56 } 57 58 // FileType enumerates the types of files found in a DB. 59 type FileType int 60 61 // The FileType enumeration. 62 const ( 63 FileTypeLog FileType = iota 64 FileTypeLock 65 FileTypeTable 66 FileTypeManifest 67 FileTypeCurrent 68 FileTypeOptions 69 FileTypeOldTemp 70 FileTypeTemp 71 ) 72 73 // MakeFilename builds a filename from components. 74 func MakeFilename(fileType FileType, dfn DiskFileNum) string { 75 switch fileType { 76 case FileTypeLog: 77 return fmt.Sprintf("%s.log", dfn) 78 case FileTypeLock: 79 return "LOCK" 80 case FileTypeTable: 81 return fmt.Sprintf("%s.sst", dfn) 82 case FileTypeManifest: 83 return fmt.Sprintf("MANIFEST-%s", dfn) 84 case FileTypeCurrent: 85 return "CURRENT" 86 case FileTypeOptions: 87 return fmt.Sprintf("OPTIONS-%s", dfn) 88 case FileTypeOldTemp: 89 return fmt.Sprintf("CURRENT.%s.dbtmp", dfn) 90 case FileTypeTemp: 91 return fmt.Sprintf("temporary.%s.dbtmp", dfn) 92 } 93 panic("unreachable") 94 } 95 96 // MakeFilepath builds a filepath from components. 97 func MakeFilepath(fs vfs.FS, dirname string, fileType FileType, dfn DiskFileNum) string { 98 return fs.PathJoin(dirname, MakeFilename(fileType, dfn)) 99 } 100 101 // ParseFilename parses the components from a filename. 102 func ParseFilename(fs vfs.FS, filename string) (fileType FileType, dfn DiskFileNum, ok bool) { 103 filename = fs.PathBase(filename) 104 switch { 105 case filename == "CURRENT": 106 return FileTypeCurrent, DiskFileNum{0}, true 107 case filename == "LOCK": 108 return FileTypeLock, DiskFileNum{0}, true 109 case strings.HasPrefix(filename, "MANIFEST-"): 110 dfn, ok = parseDiskFileNum(filename[len("MANIFEST-"):]) 111 if !ok { 112 break 113 } 114 return FileTypeManifest, dfn, true 115 case strings.HasPrefix(filename, "OPTIONS-"): 116 dfn, ok = parseDiskFileNum(filename[len("OPTIONS-"):]) 117 if !ok { 118 break 119 } 120 return FileTypeOptions, dfn, ok 121 case strings.HasPrefix(filename, "CURRENT.") && strings.HasSuffix(filename, ".dbtmp"): 122 s := strings.TrimSuffix(filename[len("CURRENT."):], ".dbtmp") 123 dfn, ok = parseDiskFileNum(s) 124 if !ok { 125 break 126 } 127 return FileTypeOldTemp, dfn, ok 128 case strings.HasPrefix(filename, "temporary.") && strings.HasSuffix(filename, ".dbtmp"): 129 s := strings.TrimSuffix(filename[len("temporary."):], ".dbtmp") 130 dfn, ok = parseDiskFileNum(s) 131 if !ok { 132 break 133 } 134 return FileTypeTemp, dfn, ok 135 default: 136 i := strings.IndexByte(filename, '.') 137 if i < 0 { 138 break 139 } 140 dfn, ok = parseDiskFileNum(filename[:i]) 141 if !ok { 142 break 143 } 144 switch filename[i+1:] { 145 case "sst": 146 return FileTypeTable, dfn, true 147 case "log": 148 return FileTypeLog, dfn, true 149 } 150 } 151 return 0, dfn, false 152 } 153 154 func parseDiskFileNum(s string) (dfn DiskFileNum, ok bool) { 155 u, err := strconv.ParseUint(s, 10, 64) 156 if err != nil { 157 return dfn, false 158 } 159 return DiskFileNum{FileNum(u)}, true 160 } 161 162 // A Fataler fatals a process with a message when called. 163 type Fataler interface { 164 Fatalf(format string, args ...interface{}) 165 } 166 167 // MustExist checks if err is an error indicating a file does not exist. 168 // If it is, it lists the containing directory's files to annotate the error 169 // with counts of the various types of files and invokes the provided fataler. 170 // See cockroachdb/cockroach#56490. 171 func MustExist(fs vfs.FS, filename string, fataler Fataler, err error) { 172 if err == nil || !oserror.IsNotExist(err) { 173 return 174 } 175 176 ls, lsErr := fs.List(fs.PathDir(filename)) 177 if lsErr != nil { 178 // TODO(jackson): if oserror.IsNotExist(lsErr), the the data directory 179 // doesn't exist anymore. Another process likely deleted it before 180 // killing the process. We want to fatal the process, but without 181 // triggering error reporting like Sentry. 182 fataler.Fatalf("%s:\norig err: %s\nlist err: %s", redact.Safe(fs.PathBase(filename)), err, lsErr) 183 } 184 var total, unknown, tables, logs, manifests int 185 total = len(ls) 186 for _, f := range ls { 187 typ, _, ok := ParseFilename(fs, f) 188 if !ok { 189 unknown++ 190 continue 191 } 192 switch typ { 193 case FileTypeTable: 194 tables++ 195 case FileTypeLog: 196 logs++ 197 case FileTypeManifest: 198 manifests++ 199 } 200 } 201 202 fataler.Fatalf("%s:\n%s\ndirectory contains %d files, %d unknown, %d tables, %d logs, %d manifests", 203 fs.PathBase(filename), err, total, unknown, tables, logs, manifests) 204 }