Line data Source code
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 : "path/filepath"
10 : "strconv"
11 : "strings"
12 :
13 : "github.com/cockroachdb/errors/oserror"
14 : "github.com/cockroachdb/pebble/vfs"
15 : "github.com/cockroachdb/redact"
16 : )
17 :
18 : // FileNum is an internal DB identifier for a table. Tables can be physical (in
19 : // which case the FileNum also identifies the backing object) or virtual.
20 : type FileNum uint64
21 :
22 : // String returns a string representation of the file number.
23 1 : func (fn FileNum) String() string { return fmt.Sprintf("%06d", fn) }
24 :
25 : // SafeFormat implements redact.SafeFormatter.
26 1 : func (fn FileNum) SafeFormat(w redact.SafePrinter, _ rune) {
27 1 : w.Printf("%06d", redact.SafeUint(fn))
28 1 : }
29 :
30 : // PhysicalTableDiskFileNum converts the FileNum of a physical table to the
31 : // backing DiskFileNum. The underlying numbers always match for physical tables.
32 1 : func PhysicalTableDiskFileNum(n FileNum) DiskFileNum {
33 1 : return DiskFileNum(n)
34 1 : }
35 :
36 : // PhysicalTableFileNum converts the DiskFileNum backing a physical table into
37 : // the table's FileNum. The underlying numbers always match for physical tables.
38 1 : func PhysicalTableFileNum(f DiskFileNum) FileNum {
39 1 : return FileNum(f)
40 1 : }
41 :
42 : // A DiskFileNum identifies a file or object with exists on disk.
43 : type DiskFileNum uint64
44 :
45 1 : func (dfn DiskFileNum) String() string { return fmt.Sprintf("%06d", dfn) }
46 :
47 : // SafeFormat implements redact.SafeFormatter.
48 1 : func (dfn DiskFileNum) SafeFormat(w redact.SafePrinter, verb rune) {
49 1 : w.Printf("%06d", redact.SafeUint(dfn))
50 1 : }
51 :
52 : // FileType enumerates the types of files found in a DB.
53 : type FileType int
54 :
55 : // The FileType enumeration.
56 : const (
57 : FileTypeLog FileType = iota
58 : FileTypeLock
59 : FileTypeTable
60 : FileTypeManifest
61 : FileTypeOptions
62 : FileTypeOldTemp
63 : FileTypeTemp
64 : )
65 :
66 : // MakeFilename builds a filename from components.
67 1 : func MakeFilename(fileType FileType, dfn DiskFileNum) string {
68 1 : switch fileType {
69 0 : case FileTypeLog:
70 0 : panic("the pebble/wal pkg is responsible for constructing WAL filenames")
71 1 : case FileTypeLock:
72 1 : return "LOCK"
73 1 : case FileTypeTable:
74 1 : return fmt.Sprintf("%s.sst", dfn)
75 1 : case FileTypeManifest:
76 1 : return fmt.Sprintf("MANIFEST-%s", dfn)
77 1 : case FileTypeOptions:
78 1 : return fmt.Sprintf("OPTIONS-%s", dfn)
79 0 : case FileTypeOldTemp:
80 0 : return fmt.Sprintf("CURRENT.%s.dbtmp", dfn)
81 1 : case FileTypeTemp:
82 1 : return fmt.Sprintf("temporary.%s.dbtmp", dfn)
83 : }
84 0 : panic("unreachable")
85 : }
86 :
87 : // MakeFilepath builds a filepath from components.
88 1 : func MakeFilepath(fs vfs.FS, dirname string, fileType FileType, dfn DiskFileNum) string {
89 1 : return fs.PathJoin(dirname, MakeFilename(fileType, dfn))
90 1 : }
91 :
92 : // ParseFilename parses the components from a filename.
93 1 : func ParseFilename(fs vfs.FS, filename string) (fileType FileType, dfn DiskFileNum, ok bool) {
94 1 : filename = fs.PathBase(filename)
95 1 : switch {
96 1 : case filename == "LOCK":
97 1 : return FileTypeLock, 0, true
98 1 : case strings.HasPrefix(filename, "MANIFEST-"):
99 1 : dfn, ok = ParseDiskFileNum(filename[len("MANIFEST-"):])
100 1 : if !ok {
101 0 : break
102 : }
103 1 : return FileTypeManifest, dfn, true
104 1 : case strings.HasPrefix(filename, "OPTIONS-"):
105 1 : dfn, ok = ParseDiskFileNum(filename[len("OPTIONS-"):])
106 1 : if !ok {
107 0 : break
108 : }
109 1 : return FileTypeOptions, dfn, ok
110 0 : case strings.HasPrefix(filename, "CURRENT.") && strings.HasSuffix(filename, ".dbtmp"):
111 0 : s := strings.TrimSuffix(filename[len("CURRENT."):], ".dbtmp")
112 0 : dfn, ok = ParseDiskFileNum(s)
113 0 : if !ok {
114 0 : break
115 : }
116 0 : return FileTypeOldTemp, dfn, ok
117 0 : case strings.HasPrefix(filename, "temporary.") && strings.HasSuffix(filename, ".dbtmp"):
118 0 : s := strings.TrimSuffix(filename[len("temporary."):], ".dbtmp")
119 0 : dfn, ok = ParseDiskFileNum(s)
120 0 : if !ok {
121 0 : break
122 : }
123 0 : return FileTypeTemp, dfn, ok
124 1 : default:
125 1 : i := strings.IndexByte(filename, '.')
126 1 : if i < 0 {
127 1 : break
128 : }
129 1 : dfn, ok = ParseDiskFileNum(filename[:i])
130 1 : if !ok {
131 1 : break
132 : }
133 : // TODO(sumeer): stop handling FileTypeLog in this function.
134 1 : switch filename[i+1:] {
135 1 : case "sst":
136 1 : return FileTypeTable, dfn, true
137 : }
138 : }
139 1 : return 0, dfn, false
140 : }
141 :
142 : // ParseDiskFileNum parses the provided string as a disk file number.
143 1 : func ParseDiskFileNum(s string) (dfn DiskFileNum, ok bool) {
144 1 : u, err := strconv.ParseUint(s, 10, 64)
145 1 : if err != nil {
146 1 : return dfn, false
147 1 : }
148 1 : return DiskFileNum(u), true
149 : }
150 :
151 : // A Fataler fatals a process with a message when called.
152 : type Fataler interface {
153 : Fatalf(format string, args ...interface{})
154 : }
155 :
156 : // MustExist checks if err is an error indicating a file does not exist.
157 : // If it is, it lists the containing directory's files to annotate the error
158 : // with counts of the various types of files and invokes the provided fataler.
159 : // See cockroachdb/cockroach#56490.
160 1 : func MustExist(fs vfs.FS, filename string, fataler Fataler, err error) {
161 1 : if err == nil || !oserror.IsNotExist(err) {
162 1 : return
163 1 : }
164 :
165 0 : ls, lsErr := fs.List(fs.PathDir(filename))
166 0 : if lsErr != nil {
167 0 : // TODO(jackson): if oserror.IsNotExist(lsErr), the data directory
168 0 : // doesn't exist anymore. Another process likely deleted it before
169 0 : // killing the process. We want to fatal the process, but without
170 0 : // triggering error reporting like Sentry.
171 0 : fataler.Fatalf("%s:\norig err: %s\nlist err: %s", redact.Safe(fs.PathBase(filename)), err, lsErr)
172 0 : }
173 0 : var total, unknown, tables, logs, manifests int
174 0 : total = len(ls)
175 0 : for _, f := range ls {
176 0 : // The file format of log files is an implementation detail of the wal/
177 0 : // package that the internal/base package is not privy to. We can't call
178 0 : // into the wal package because that would introduce a cyclical
179 0 : // dependency. For our purposes, an exact count isn't important and we
180 0 : // just count files with .log extensions.
181 0 : if filepath.Ext(f) == ".log" {
182 0 : logs++
183 0 : continue
184 : }
185 0 : typ, _, ok := ParseFilename(fs, f)
186 0 : if !ok {
187 0 : unknown++
188 0 : continue
189 : }
190 0 : switch typ {
191 0 : case FileTypeTable:
192 0 : tables++
193 0 : case FileTypeManifest:
194 0 : manifests++
195 : }
196 : }
197 :
198 0 : fataler.Fatalf("%s:\n%s\ndirectory contains %d files, %d unknown, %d tables, %d logs, %d manifests",
199 0 : fs.PathBase(filename), err, total, unknown, tables, logs, manifests)
200 : }
201 :
202 : // FileInfo provides some rudimentary information about a file.
203 : type FileInfo struct {
204 : FileNum DiskFileNum
205 : FileSize uint64
206 : }
|