mirror of
https://github.com/minio/minio.git
synced 2025-11-10 05:59:43 -05:00
Add documentation for debugging tools (#13484)
Move `xl-meta` so it can be installed out-of-repo with a single command.
This commit is contained in:
@@ -23,7 +23,6 @@ To trace entire HTTP request and also internode communication
|
||||
mc admin trace --all --verbose myminio
|
||||
```
|
||||
|
||||
|
||||
### Subnet Health
|
||||
Subnet Health diagnostics help ensure that the underlying infrastructure that runs MinIO is configured correctly, and is functioning properly. This test is one-shot long running one, that is recommended to be run as soon as the cluster is first provisioned, and each time a failure scenario is encountered. Note that the test incurs majority of the available resources on the system. Care must be taken when using this to debug failure scenario, so as to prevent larger outages. Health tests can be triggered using `mc admin subnet health` command.
|
||||
|
||||
@@ -57,3 +56,95 @@ mc: Health data saved to dc-11-health_20200321053323.json.gz
|
||||
```
|
||||
|
||||
The gzipped output contains debugging information for your system
|
||||
|
||||
### Decoding Metadata
|
||||
|
||||
Metadata is stored in `xl.meta` files for erasure coded objects.
|
||||
Each disk in the set containing the object has this file.
|
||||
The file format is a binary format and therefore requires tools to view values.
|
||||
|
||||
#### Installing xl-meta
|
||||
|
||||
To install, [Go](https://golang.org/dl/) must be installed.
|
||||
|
||||
Once installed, execute this to install the binary:
|
||||
|
||||
```bash
|
||||
go install github.com/minio/minio/docs/debugging/xl-meta@latest
|
||||
```
|
||||
|
||||
#### Using xl-meta
|
||||
|
||||
Executing `xl-meta` will look for an `xl.meta` in the current folder and decode it to JSON.
|
||||
|
||||
It is also possible to specify multiple files or wildcards, for example `xl-meta ./**/xl.meta` will output decoded metadata recursively.
|
||||
|
||||
It is possible to view what inline data is stored inline in the metadata using `--data` parameter `xl-meta -data xl.json` will display an id -> data size.
|
||||
To export inline data to a file use the `--export` option.
|
||||
|
||||
### Remotely Inspecting backend data
|
||||
|
||||
`mc admin inspect` allows collecting files based on *path* from all backend drives.
|
||||
|
||||
Matching files will be collected in a zip file with their respective host+drive+path.
|
||||
|
||||
A MinIO host from October 2021 or later is required for full functionality.
|
||||
|
||||
Syntax is `mc admin inspect ALIAS/path/to/files`. This can for example be used to collect `xl.meta` from objects that are misbehaving.
|
||||
|
||||
To collect `xl.meta` from a specific object, for example placed at `ALIAS/bucket/path/to/file.txt` append `/xl.meta`, for instance `mc admin inspect ALIAS/bucket/path/to/file.txt/xl.meta`.
|
||||
All files can be collected, so this can also be used to retrieve `part.*` files, etc.
|
||||
|
||||
Wildcards can be used, for example `mc admin inspect ALIAS/bucket/path/**/xl.meta` will collect all `xl.meta` recursively.
|
||||
`mc admin inspect ALIAS/bucket/path/to/file.txt/*/part.*` will collect parts for all versions for the object located at `bucket/path/to/file.txt`.
|
||||
|
||||
`xl-meta` accepts zip files as input and will output all `xl.meta` files found within the archive.
|
||||
For example:
|
||||
|
||||
```
|
||||
$ mc admin inspect play/test123/test*/xl.meta
|
||||
mc: File data successfully downloaded as inspect.6f96b336.zip
|
||||
$ xl-meta inspect.6f96b336.zip
|
||||
{
|
||||
"bf6178f9-4014-4008-9699-86f2fac62226/test123/testw3c.pdf/xl.meta": {"Versions":[{"Type":1,"V2Obj":{"ID":"aGEA/ZUOR4ueRIZsAgfDqA==","DDir":"9MMwM47bS+K6KvQqN3hlDw==","EcAlgo":1,"EcM":2,"EcN":2,"EcBSize":1048576,"EcIndex":4,"EcDist":[4,1,2,3],"CSumAlgo":1,"PartNums":[1],"PartETags":[""],"PartSizes":[101974],"PartASizes":[176837],"Size":101974,"MTime":1634106631319256439,"MetaSys":{"X-Minio-Internal-compression":"a2xhdXNwb3N0L2NvbXByZXNzL3My","X-Minio-Internal-actual-size":"MTc2ODM3","x-minio-internal-objectlock-legalhold-timestamp":"MjAyMS0xMC0xOVQyMjozNTo0Ni4zNTE4MDU3NTda"},"MetaUsr":{"x-amz-object-lock-mode":"COMPLIANCE","x-amz-object-lock-retain-until-date":"2022-10-13T06:30:31.319Z","etag":"67ed8f49b7137cb957858ce468f2e79e","content-type":"application/pdf","x-amz-object-lock-legal-hold":"OFF"}}}]},
|
||||
"fe012443-6ba9-4ef2-bb94-b729d2060c78/test123/testw3c.pdf/xl.meta": {"Versions":[{"Type":1,"V2Obj":{"ID":"aGEA/ZUOR4ueRIZsAgfDqA==","DDir":"9MMwM47bS+K6KvQqN3hlDw==","EcAlgo":1,"EcM":2,"EcN":2,"EcBSize":1048576,"EcIndex":1,"EcDist":[4,1,2,3],"CSumAlgo":1,"PartNums":[1],"PartETags":[""],"PartSizes":[101974],"PartASizes":[176837],"Size":101974,"MTime":1634106631319256439,"MetaSys":{"X-Minio-Internal-compression":"a2xhdXNwb3N0L2NvbXByZXNzL3My","X-Minio-Internal-actual-size":"MTc2ODM3","x-minio-internal-objectlock-legalhold-timestamp":"MjAyMS0xMC0xOVQyMjozNTo0Ni4zNTE4MDU3NTda"},"MetaUsr":{"content-type":"application/pdf","x-amz-object-lock-legal-hold":"OFF","x-amz-object-lock-mode":"COMPLIANCE","x-amz-object-lock-retain-until-date":"2022-10-13T06:30:31.319Z","etag":"67ed8f49b7137cb957858ce468f2e79e"}}}]},
|
||||
"5dcb9f38-08ea-4728-bb64-5cecc7102436/test123/testw3c.pdf/xl.meta": {"Versions":[{"Type":1,"V2Obj":{"ID":"aGEA/ZUOR4ueRIZsAgfDqA==","DDir":"9MMwM47bS+K6KvQqN3hlDw==","EcAlgo":1,"EcM":2,"EcN":2,"EcBSize":1048576,"EcIndex":2,"EcDist":[4,1,2,3],"CSumAlgo":1,"PartNums":[1],"PartETags":[""],"PartSizes":[101974],"PartASizes":[176837],"Size":101974,"MTime":1634106631319256439,"MetaSys":{"X-Minio-Internal-compression":"a2xhdXNwb3N0L2NvbXByZXNzL3My","X-Minio-Internal-actual-size":"MTc2ODM3","x-minio-internal-objectlock-legalhold-timestamp":"MjAyMS0xMC0xOVQyMjozNTo0Ni4zNTE4MDU3NTda"},"MetaUsr":{"content-type":"application/pdf","x-amz-object-lock-legal-hold":"OFF","x-amz-object-lock-mode":"COMPLIANCE","x-amz-object-lock-retain-until-date":"2022-10-13T06:30:31.319Z","etag":"67ed8f49b7137cb957858ce468f2e79e"}}}]},
|
||||
"48beacc7-4be0-4660-9026-4eceaf147504/test123/testw3c.pdf/xl.meta": {"Versions":[{"Type":1,"V2Obj":{"ID":"aGEA/ZUOR4ueRIZsAgfDqA==","DDir":"9MMwM47bS+K6KvQqN3hlDw==","EcAlgo":1,"EcM":2,"EcN":2,"EcBSize":1048576,"EcIndex":3,"EcDist":[4,1,2,3],"CSumAlgo":1,"PartNums":[1],"PartETags":[""],"PartSizes":[101974],"PartASizes":[176837],"Size":101974,"MTime":1634106631319256439,"MetaSys":{"X-Minio-Internal-compression":"a2xhdXNwb3N0L2NvbXByZXNzL3My","X-Minio-Internal-actual-size":"MTc2ODM3","x-minio-internal-objectlock-legalhold-timestamp":"MjAyMS0xMC0xOVQyMjozNTo0Ni4zNTE4MDU3NTda"},"MetaUsr":{"x-amz-object-lock-retain-until-date":"2022-10-13T06:30:31.319Z","x-amz-object-lock-legal-hold":"OFF","etag":"67ed8f49b7137cb957858ce468f2e79e","content-type":"application/pdf","x-amz-object-lock-mode":"COMPLIANCE"}}}]}
|
||||
}
|
||||
```
|
||||
|
||||
Optionally `--encrypt` can be specified. This will output an encrypted file and a decryption key:
|
||||
|
||||
```
|
||||
$ mc admin inspect --encrypt play/test123/test*/*/part.*
|
||||
mc: Encrypted file data successfully downloaded as inspect.ad2b43d8.enc
|
||||
mc: Decryption key: ad2b43d847fdb14e54c5836200177f7158b3f745433525f5d23c0e0208e50c9948540b54
|
||||
|
||||
mc: The decryption key will ONLY be shown here. It cannot be recovered.
|
||||
mc: The encrypted file can safely be shared without the decryption key.
|
||||
mc: Even with the decryption key, data stored with encryption cannot be accessed.
|
||||
```
|
||||
|
||||
This file can be decrypted using the decryption tool below:
|
||||
|
||||
#### Installing decryption tool
|
||||
|
||||
To install, [Go](https://golang.org/dl/) must be installed.
|
||||
|
||||
Once installed, execute this to install the binary:
|
||||
|
||||
```bash
|
||||
go install github.com/minio/minio/docs/debugging/inspect@latest
|
||||
```
|
||||
#### Usage
|
||||
|
||||
To decrypt the file above:
|
||||
|
||||
```
|
||||
$ inspect -key=ad2b43d847fdb14e54c5836200177f7158b3f745433525f5d23c0e0208e50c9948540b54 inspect.ad2b43d8.enc
|
||||
Output decrypted to inspect.ad2b43d8.zip
|
||||
```
|
||||
|
||||
If `--key` is not specified an interactive prompt will ask for it.
|
||||
|
||||
The file name will contain the beginning of the key. This can be used to verify that the key is for the encrypted file.
|
||||
|
||||
413
docs/debugging/xl-meta/main.go
Normal file
413
docs/debugging/xl-meta/main.go
Normal file
@@ -0,0 +1,413 @@
|
||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/klauspost/compress/zip"
|
||||
"github.com/minio/cli"
|
||||
"github.com/tinylib/msgp/msgp"
|
||||
"github.com/yargevad/filepathx"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
app.Copyright = "MinIO, Inc."
|
||||
app.Usage = "xl.meta to JSON"
|
||||
app.HideVersion = true
|
||||
app.CustomAppHelpTemplate = `NAME:
|
||||
{{.Name}} - {{.Usage}}
|
||||
|
||||
USAGE:
|
||||
{{.Name}} {{if .VisibleFlags}}[FLAGS]{{end}} METAFILES...
|
||||
|
||||
Multiple files can be added. Files ending in ".zip" will be searched for 'xl.meta' files.
|
||||
Wildcards are accepted: testdir/*.txt will compress all files in testdir ending with .txt
|
||||
Directories can be wildcards as well. testdir/*/*.txt will match testdir/subdir/b.txt
|
||||
Double stars means full recursive. testdir/**/xl.meta will search for all xl.meta recursively.
|
||||
|
||||
{{if .VisibleFlags}}
|
||||
GLOBAL FLAGS:
|
||||
{{range .VisibleFlags}}{{.}}
|
||||
{{end}}{{end}}
|
||||
`
|
||||
|
||||
app.HideHelpCommand = true
|
||||
|
||||
app.Flags = []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Usage: "Print each file as a separate line without formatting",
|
||||
Name: "ndjson",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Usage: "Display inline data keys and sizes",
|
||||
Name: "data",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Usage: "Export inline data",
|
||||
Name: "export",
|
||||
},
|
||||
}
|
||||
|
||||
app.Action = func(c *cli.Context) error {
|
||||
ndjson := c.Bool("ndjson")
|
||||
decode := func(r io.Reader, file string) ([]byte, error) {
|
||||
b, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b, _, minor, err := checkXL2V1(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
var data xlMetaInlineData
|
||||
switch minor {
|
||||
case 0:
|
||||
_, err = msgp.CopyToJSON(buf, bytes.NewBuffer(b))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case 1, 2:
|
||||
v, b, err := msgp.ReadBytesZC(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, nbuf, err := msgp.ReadUint32Bytes(b); err == nil {
|
||||
// Read metadata CRC (added in v2, ignore if not found)
|
||||
b = nbuf
|
||||
}
|
||||
|
||||
_, err = msgp.CopyToJSON(buf, bytes.NewBuffer(v))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data = b
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown metadata version %d", minor)
|
||||
}
|
||||
|
||||
if c.Bool("data") {
|
||||
b, err := data.json()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf = bytes.NewBuffer(b)
|
||||
}
|
||||
if c.Bool("export") {
|
||||
file := strings.Map(func(r rune) rune {
|
||||
switch {
|
||||
case r >= 'a' && r <= 'z':
|
||||
return r
|
||||
case r >= 'A' && r <= 'Z':
|
||||
return r
|
||||
case r >= '0' && r <= '9':
|
||||
return r
|
||||
case strings.ContainsAny(string(r), "+=-_()!@."):
|
||||
return r
|
||||
default:
|
||||
return '_'
|
||||
}
|
||||
}, file)
|
||||
err := data.files(func(name string, data []byte) {
|
||||
err = ioutil.WriteFile(fmt.Sprintf("%s-%s.data", file, name), data, os.ModePerm)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if ndjson {
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
var msi map[string]interface{}
|
||||
dec := json.NewDecoder(buf)
|
||||
// Use number to preserve integers.
|
||||
dec.UseNumber()
|
||||
err = dec.Decode(&msi)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b, err = json.MarshalIndent(msi, "", " ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
args := c.Args()
|
||||
if len(args) == 0 {
|
||||
// If no args, assume xl.meta
|
||||
args = []string{"xl.meta"}
|
||||
}
|
||||
var files []string
|
||||
|
||||
for _, pattern := range args {
|
||||
if pattern == "-" {
|
||||
files = append(files, pattern)
|
||||
continue
|
||||
}
|
||||
found, err := filepathx.Glob(pattern)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(found) == 0 {
|
||||
return fmt.Errorf("unable to find file %v", pattern)
|
||||
}
|
||||
files = append(files, found...)
|
||||
}
|
||||
if len(files) == 0 {
|
||||
return fmt.Errorf("no files found")
|
||||
}
|
||||
multiple := len(files) > 1 || strings.HasSuffix(files[0], ".zip")
|
||||
if multiple {
|
||||
ndjson = true
|
||||
fmt.Println("{")
|
||||
}
|
||||
|
||||
hasWritten := false
|
||||
for _, file := range files {
|
||||
var r io.Reader
|
||||
var sz int64
|
||||
switch file {
|
||||
case "-":
|
||||
r = os.Stdin
|
||||
default:
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if st, err := f.Stat(); err == nil {
|
||||
sz = st.Size()
|
||||
}
|
||||
defer f.Close()
|
||||
r = f
|
||||
}
|
||||
if strings.HasSuffix(file, ".zip") {
|
||||
zr, err := zip.NewReader(r.(io.ReaderAt), sz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, file := range zr.File {
|
||||
if !file.FileInfo().IsDir() && strings.HasSuffix(file.Name, "xl.meta") {
|
||||
r, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Quote string...
|
||||
b, _ := json.Marshal(file.Name)
|
||||
if hasWritten {
|
||||
fmt.Print(",\n")
|
||||
}
|
||||
fmt.Printf("\t%s: ", string(b))
|
||||
|
||||
b, err = decode(r, file.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Print(string(b))
|
||||
hasWritten = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if multiple {
|
||||
// Quote string...
|
||||
b, _ := json.Marshal(file)
|
||||
if hasWritten {
|
||||
fmt.Print(",\n")
|
||||
}
|
||||
fmt.Printf("\t%s: ", string(b))
|
||||
}
|
||||
|
||||
b, err := decode(r, file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hasWritten = true
|
||||
fmt.Print(string(b))
|
||||
}
|
||||
}
|
||||
fmt.Println("")
|
||||
if multiple {
|
||||
fmt.Println("}")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
err := app.Run(os.Args)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// XL header specifies the format
|
||||
xlHeader = [4]byte{'X', 'L', '2', ' '}
|
||||
|
||||
// Current version being written.
|
||||
xlVersionCurrent [4]byte
|
||||
)
|
||||
|
||||
const (
|
||||
// Breaking changes.
|
||||
// Newer versions cannot be read by older software.
|
||||
// This will prevent downgrades to incompatible versions.
|
||||
xlVersionMajor = 1
|
||||
|
||||
// Non breaking changes.
|
||||
// Bumping this is informational, but should be done
|
||||
// if any change is made to the data stored, bumping this
|
||||
// will allow to detect the exact version later.
|
||||
xlVersionMinor = 1
|
||||
)
|
||||
|
||||
func init() {
|
||||
binary.LittleEndian.PutUint16(xlVersionCurrent[0:2], xlVersionMajor)
|
||||
binary.LittleEndian.PutUint16(xlVersionCurrent[2:4], xlVersionMinor)
|
||||
}
|
||||
|
||||
// checkXL2V1 will check if the metadata has correct header and is a known major version.
|
||||
// The remaining payload and versions are returned.
|
||||
func checkXL2V1(buf []byte) (payload []byte, major, minor uint16, err error) {
|
||||
if len(buf) <= 8 {
|
||||
return payload, 0, 0, fmt.Errorf("xlMeta: no data")
|
||||
}
|
||||
|
||||
if !bytes.Equal(buf[:4], xlHeader[:]) {
|
||||
return payload, 0, 0, fmt.Errorf("xlMeta: unknown XLv2 header, expected %v, got %v", xlHeader[:4], buf[:4])
|
||||
}
|
||||
|
||||
if bytes.Equal(buf[4:8], []byte("1 ")) {
|
||||
// Set as 1,0.
|
||||
major, minor = 1, 0
|
||||
} else {
|
||||
major, minor = binary.LittleEndian.Uint16(buf[4:6]), binary.LittleEndian.Uint16(buf[6:8])
|
||||
}
|
||||
if major > xlVersionMajor {
|
||||
return buf[8:], major, minor, fmt.Errorf("xlMeta: unknown major version %d found", major)
|
||||
}
|
||||
|
||||
return buf[8:], major, minor, nil
|
||||
}
|
||||
|
||||
const xlMetaInlineDataVer = 1
|
||||
|
||||
type xlMetaInlineData []byte
|
||||
|
||||
// afterVersion returns the payload after the version, if any.
|
||||
func (x xlMetaInlineData) afterVersion() []byte {
|
||||
if len(x) == 0 {
|
||||
return x
|
||||
}
|
||||
return x[1:]
|
||||
}
|
||||
|
||||
// versionOK returns whether the version is ok.
|
||||
func (x xlMetaInlineData) versionOK() bool {
|
||||
if len(x) == 0 {
|
||||
return true
|
||||
}
|
||||
return x[0] > 0 && x[0] <= xlMetaInlineDataVer
|
||||
}
|
||||
|
||||
func (x xlMetaInlineData) json() ([]byte, error) {
|
||||
if len(x) == 0 {
|
||||
return []byte("{}"), nil
|
||||
}
|
||||
if !x.versionOK() {
|
||||
return nil, errors.New("xlMetaInlineData: unknown version")
|
||||
}
|
||||
|
||||
sz, buf, err := msgp.ReadMapHeaderBytes(x.afterVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := []byte("{")
|
||||
|
||||
for i := uint32(0); i < sz; i++ {
|
||||
var key, val []byte
|
||||
key, buf, err = msgp.ReadMapKeyZC(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(key) == 0 {
|
||||
return nil, fmt.Errorf("xlMetaInlineData: key %d is length 0", i)
|
||||
}
|
||||
// Skip data...
|
||||
val, buf, err = msgp.ReadBytesZC(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if i > 0 {
|
||||
res = append(res, ',')
|
||||
}
|
||||
s := fmt.Sprintf(`"%s":%d`, string(key), len(val))
|
||||
res = append(res, []byte(s)...)
|
||||
}
|
||||
res = append(res, '}')
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// files returns files as callback.
|
||||
func (x xlMetaInlineData) files(fn func(name string, data []byte)) error {
|
||||
if len(x) == 0 {
|
||||
return nil
|
||||
}
|
||||
if !x.versionOK() {
|
||||
return errors.New("xlMetaInlineData: unknown version")
|
||||
}
|
||||
|
||||
sz, buf, err := msgp.ReadMapHeaderBytes(x.afterVersion())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := uint32(0); i < sz; i++ {
|
||||
var key, val []byte
|
||||
key, buf, err = msgp.ReadMapKeyZC(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(key) == 0 {
|
||||
return fmt.Errorf("xlMetaInlineData: key %d is length 0", i)
|
||||
}
|
||||
// Read data...
|
||||
val, buf, err = msgp.ReadBytesZC(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Call back.
|
||||
fn(string(key), val)
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user