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 2 : func (fn FileNum) String() string { return fmt.Sprintf("%06d", fn) }
24 :
25 : // SafeFormat implements redact.SafeFormatter.
26 2 : func (fn FileNum) SafeFormat(w redact.SafePrinter, _ rune) {
27 2 : w.Printf("%06d", redact.SafeUint(fn))
28 2 : }
29 :
30 : // PhysicalTableDiskFileNum converts the FileNum of a physical table to the
31 : // backing DiskFileNum. The underlying numbers always match for physical tables.
32 2 : func PhysicalTableDiskFileNum(n FileNum) DiskFileNum {
33 2 : return DiskFileNum(n)
34 2 : }
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 2 : func PhysicalTableFileNum(f DiskFileNum) FileNum {
39 2 : return FileNum(f)
40 2 : }
41 :
42 : // A DiskFileNum identifies a file or object with exists on disk.
43 : type DiskFileNum uint64
44 :
45 2 : func (dfn DiskFileNum) String() string { return fmt.Sprintf("%06d", dfn) }
46 :
47 : // SafeFormat implements redact.SafeFormatter.
48 2 : func (dfn DiskFileNum) SafeFormat(w redact.SafePrinter, verb rune) {
49 2 : w.Printf("%06d", redact.SafeUint(dfn))
50 2 : }
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 2 : func MakeFilename(fileType FileType, dfn DiskFileNum) string {
68 2 : switch fileType {
69 0 : case FileTypeLog:
70 0 : panic("the pebble/wal pkg is responsible for constructing WAL filenames")
71 2 : case FileTypeLock:
72 2 : return "LOCK"
73 2 : case FileTypeTable:
74 2 : return fmt.Sprintf("%s.sst", dfn)
75 2 : case FileTypeManifest:
76 2 : return fmt.Sprintf("MANIFEST-%s", dfn)
77 2 : case FileTypeOptions:
78 2 : return fmt.Sprintf("OPTIONS-%s", dfn)
79 1 : case FileTypeOldTemp:
80 1 : return fmt.Sprintf("CURRENT.%s.dbtmp", dfn)
81 2 : case FileTypeTemp:
82 2 : return fmt.Sprintf("temporary.%s.dbtmp", dfn)
83 : }
84 0 : panic("unreachable")
85 : }
86 :
87 : // MakeFilepath builds a filepath from components.
88 2 : func MakeFilepath(fs vfs.FS, dirname string, fileType FileType, dfn DiskFileNum) string {
89 2 : return fs.PathJoin(dirname, MakeFilename(fileType, dfn))
90 2 : }
91 :
92 : // ParseFilename parses the components from a filename.
93 2 : func ParseFilename(fs vfs.FS, filename string) (fileType FileType, dfn DiskFileNum, ok bool) {
94 2 : filename = fs.PathBase(filename)
95 2 : switch {
96 2 : case filename == "LOCK":
97 2 : return FileTypeLock, 0, true
98 2 : case strings.HasPrefix(filename, "MANIFEST-"):
99 2 : dfn, ok = ParseDiskFileNum(filename[len("MANIFEST-"):])
100 2 : if !ok {
101 1 : break
102 : }
103 2 : return FileTypeManifest, dfn, true
104 2 : case strings.HasPrefix(filename, "OPTIONS-"):
105 2 : dfn, ok = ParseDiskFileNum(filename[len("OPTIONS-"):])
106 2 : if !ok {
107 1 : break
108 : }
109 2 : return FileTypeOptions, dfn, ok
110 1 : case strings.HasPrefix(filename, "CURRENT.") && strings.HasSuffix(filename, ".dbtmp"):
111 1 : s := strings.TrimSuffix(filename[len("CURRENT."):], ".dbtmp")
112 1 : dfn, ok = ParseDiskFileNum(s)
113 1 : if !ok {
114 1 : break
115 : }
116 1 : return FileTypeOldTemp, dfn, ok
117 1 : case strings.HasPrefix(filename, "temporary.") && strings.HasSuffix(filename, ".dbtmp"):
118 1 : s := strings.TrimSuffix(filename[len("temporary."):], ".dbtmp")
119 1 : dfn, ok = ParseDiskFileNum(s)
120 1 : if !ok {
121 0 : break
122 : }
123 1 : return FileTypeTemp, dfn, ok
124 2 : default:
125 2 : i := strings.IndexByte(filename, '.')
126 2 : if i < 0 {
127 2 : break
128 : }
129 2 : dfn, ok = ParseDiskFileNum(filename[:i])
130 2 : if !ok {
131 2 : break
132 : }
133 : // TODO(sumeer): stop handling FileTypeLog in this function.
134 2 : switch filename[i+1:] {
135 2 : case "sst":
136 2 : return FileTypeTable, dfn, true
137 : }
138 : }
139 2 : return 0, dfn, false
140 : }
141 :
142 : // ParseDiskFileNum parses the provided string as a disk file number.
143 2 : func ParseDiskFileNum(s string) (dfn DiskFileNum, ok bool) {
144 2 : u, err := strconv.ParseUint(s, 10, 64)
145 2 : if err != nil {
146 2 : return dfn, false
147 2 : }
148 2 : 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 2 : func MustExist(fs vfs.FS, filename string, fataler Fataler, err error) {
161 2 : if err == nil || !oserror.IsNotExist(err) {
162 2 : return
163 2 : }
164 :
165 1 : ls, lsErr := fs.List(fs.PathDir(filename))
166 1 : 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 1 : var total, unknown, tables, logs, manifests int
174 1 : total = len(ls)
175 1 : for _, f := range ls {
176 1 : // The file format of log files is an implementation detail of the wal/
177 1 : // package that the internal/base package is not privy to. We can't call
178 1 : // into the wal package because that would introduce a cyclical
179 1 : // dependency. For our purposes, an exact count isn't important and we
180 1 : // just count files with .log extensions.
181 1 : if filepath.Ext(f) == ".log" {
182 1 : logs++
183 1 : continue
184 : }
185 1 : typ, _, ok := ParseFilename(fs, f)
186 1 : if !ok {
187 1 : unknown++
188 1 : continue
189 : }
190 1 : switch typ {
191 1 : case FileTypeTable:
192 1 : tables++
193 1 : case FileTypeManifest:
194 1 : manifests++
195 : }
196 : }
197 :
198 1 : fataler.Fatalf("%s:\n%s\ndirectory contains %d files, %d unknown, %d tables, %d logs, %d manifests",
199 1 : 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 : }
|