github.com/cockroachdb/pebble@v0.0.0-20231214172447-ab4952c5f87b/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 uint64 43 44 func (dfn DiskFileNum) String() string { return fmt.Sprintf("%06d", dfn) } 45 46 // SafeFormat implements redact.SafeFormatter. 47 func (dfn DiskFileNum) SafeFormat(w redact.SafePrinter, verb rune) { 48 w.Printf("%06d", redact.SafeUint(dfn)) 49 } 50 51 // FileNum converts a DiskFileNum to a FileNum. This conversion is always valid. 52 func (dfn DiskFileNum) FileNum() FileNum { 53 return FileNum(dfn) 54 } 55 56 // FileType enumerates the types of files found in a DB. 57 type FileType int 58 59 // The FileType enumeration. 60 const ( 61 FileTypeLog FileType = iota 62 FileTypeLock 63 FileTypeTable 64 FileTypeManifest 65 FileTypeCurrent 66 FileTypeOptions 67 FileTypeOldTemp 68 FileTypeTemp 69 ) 70 71 // MakeFilename builds a filename from components. 72 func MakeFilename(fileType FileType, dfn DiskFileNum) string { 73 switch fileType { 74 case FileTypeLog: 75 return fmt.Sprintf("%s.log", dfn) 76 case FileTypeLock: 77 return "LOCK" 78 case FileTypeTable: 79 return fmt.Sprintf("%s.sst", dfn) 80 case FileTypeManifest: 81 return fmt.Sprintf("MANIFEST-%s", dfn) 82 case FileTypeCurrent: 83 return "CURRENT" 84 case FileTypeOptions: 85 return fmt.Sprintf("OPTIONS-%s", dfn) 86 case FileTypeOldTemp: 87 return fmt.Sprintf("CURRENT.%s.dbtmp", dfn) 88 case FileTypeTemp: 89 return fmt.Sprintf("temporary.%s.dbtmp", dfn) 90 } 91 panic("unreachable") 92 } 93 94 // MakeFilepath builds a filepath from components. 95 func MakeFilepath(fs vfs.FS, dirname string, fileType FileType, dfn DiskFileNum) string { 96 return fs.PathJoin(dirname, MakeFilename(fileType, dfn)) 97 } 98 99 // ParseFilename parses the components from a filename. 100 func ParseFilename(fs vfs.FS, filename string) (fileType FileType, dfn DiskFileNum, ok bool) { 101 filename = fs.PathBase(filename) 102 switch { 103 case filename == "CURRENT": 104 return FileTypeCurrent, 0, true 105 case filename == "LOCK": 106 return FileTypeLock, 0, true 107 case strings.HasPrefix(filename, "MANIFEST-"): 108 dfn, ok = parseDiskFileNum(filename[len("MANIFEST-"):]) 109 if !ok { 110 break 111 } 112 return FileTypeManifest, dfn, true 113 case strings.HasPrefix(filename, "OPTIONS-"): 114 dfn, ok = parseDiskFileNum(filename[len("OPTIONS-"):]) 115 if !ok { 116 break 117 } 118 return FileTypeOptions, dfn, ok 119 case strings.HasPrefix(filename, "CURRENT.") && strings.HasSuffix(filename, ".dbtmp"): 120 s := strings.TrimSuffix(filename[len("CURRENT."):], ".dbtmp") 121 dfn, ok = parseDiskFileNum(s) 122 if !ok { 123 break 124 } 125 return FileTypeOldTemp, dfn, ok 126 case strings.HasPrefix(filename, "temporary.") && strings.HasSuffix(filename, ".dbtmp"): 127 s := strings.TrimSuffix(filename[len("temporary."):], ".dbtmp") 128 dfn, ok = parseDiskFileNum(s) 129 if !ok { 130 break 131 } 132 return FileTypeTemp, dfn, ok 133 default: 134 i := strings.IndexByte(filename, '.') 135 if i < 0 { 136 break 137 } 138 dfn, ok = parseDiskFileNum(filename[:i]) 139 if !ok { 140 break 141 } 142 switch filename[i+1:] { 143 case "sst": 144 return FileTypeTable, dfn, true 145 case "log": 146 return FileTypeLog, dfn, true 147 } 148 } 149 return 0, dfn, false 150 } 151 152 func parseDiskFileNum(s string) (dfn DiskFileNum, ok bool) { 153 u, err := strconv.ParseUint(s, 10, 64) 154 if err != nil { 155 return dfn, false 156 } 157 return DiskFileNum(u), true 158 } 159 160 // A Fataler fatals a process with a message when called. 161 type Fataler interface { 162 Fatalf(format string, args ...interface{}) 163 } 164 165 // MustExist checks if err is an error indicating a file does not exist. 166 // If it is, it lists the containing directory's files to annotate the error 167 // with counts of the various types of files and invokes the provided fataler. 168 // See cockroachdb/cockroach#56490. 169 func MustExist(fs vfs.FS, filename string, fataler Fataler, err error) { 170 if err == nil || !oserror.IsNotExist(err) { 171 return 172 } 173 174 ls, lsErr := fs.List(fs.PathDir(filename)) 175 if lsErr != nil { 176 // TODO(jackson): if oserror.IsNotExist(lsErr), the the data directory 177 // doesn't exist anymore. Another process likely deleted it before 178 // killing the process. We want to fatal the process, but without 179 // triggering error reporting like Sentry. 180 fataler.Fatalf("%s:\norig err: %s\nlist err: %s", redact.Safe(fs.PathBase(filename)), err, lsErr) 181 } 182 var total, unknown, tables, logs, manifests int 183 total = len(ls) 184 for _, f := range ls { 185 typ, _, ok := ParseFilename(fs, f) 186 if !ok { 187 unknown++ 188 continue 189 } 190 switch typ { 191 case FileTypeTable: 192 tables++ 193 case FileTypeLog: 194 logs++ 195 case FileTypeManifest: 196 manifests++ 197 } 198 } 199 200 fataler.Fatalf("%s:\n%s\ndirectory contains %d files, %d unknown, %d tables, %d logs, %d manifests", 201 fs.PathBase(filename), err, total, unknown, tables, logs, manifests) 202 }