storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/docs/bucket/versioning/xl-meta.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2020 MinIO, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package main
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/binary"
    22  	"encoding/json"
    23  	"errors"
    24  	"fmt"
    25  	"io"
    26  	"io/ioutil"
    27  	"log"
    28  	"os"
    29  
    30  	"github.com/minio/cli"
    31  	"github.com/tinylib/msgp/msgp"
    32  )
    33  
    34  func main() {
    35  	app := cli.NewApp()
    36  	app.Copyright = "MinIO, Inc."
    37  	app.Usage = "xl.meta to JSON"
    38  	app.HideVersion = true
    39  	app.CustomAppHelpTemplate = `NAME:
    40    {{.Name}} - {{.Usage}}
    41  
    42  USAGE:
    43    {{.Name}} {{if .VisibleFlags}}[FLAGS]{{end}} METAFILES...
    44  {{if .VisibleFlags}}
    45  GLOBAL FLAGS:
    46    {{range .VisibleFlags}}{{.}}
    47    {{end}}{{end}}
    48  `
    49  
    50  	app.HideHelpCommand = true
    51  
    52  	app.Flags = []cli.Flag{
    53  		cli.BoolFlag{
    54  			Usage: "Print each file as a separate line without formatting",
    55  			Name:  "ndjson",
    56  		},
    57  		cli.BoolFlag{
    58  			Usage: "Display inline data keys and sizes",
    59  			Name:  "data",
    60  		},
    61  	}
    62  
    63  	app.Action = func(c *cli.Context) error {
    64  		files := c.Args()
    65  		if len(files) == 0 {
    66  			// If no args, assume xl.meta
    67  			files = []string{"xl.meta"}
    68  		}
    69  		for _, file := range files {
    70  			var r io.Reader
    71  			switch file {
    72  			case "-":
    73  				r = os.Stdin
    74  			default:
    75  				f, err := os.Open(file)
    76  				if err != nil {
    77  					return err
    78  				}
    79  				defer f.Close()
    80  				r = f
    81  			}
    82  
    83  			b, err := ioutil.ReadAll(r)
    84  			if err != nil {
    85  				return err
    86  			}
    87  			b, _, minor, err := checkXL2V1(b)
    88  			if err != nil {
    89  				return err
    90  			}
    91  
    92  			buf := bytes.NewBuffer(nil)
    93  			var data xlMetaInlineData
    94  			switch minor {
    95  			case 0:
    96  				_, err = msgp.CopyToJSON(buf, bytes.NewBuffer(b))
    97  				if err != nil {
    98  					return err
    99  				}
   100  			case 1, 2:
   101  				v, b, err := msgp.ReadBytesZC(b)
   102  				if err != nil {
   103  					return err
   104  				}
   105  				if _, nbuf, err := msgp.ReadUint32Bytes(b); err == nil {
   106  					// Read metadata CRC (added in v2, ignore if not found)
   107  					b = nbuf
   108  				}
   109  
   110  				_, err = msgp.CopyToJSON(buf, bytes.NewBuffer(v))
   111  				if err != nil {
   112  					return err
   113  				}
   114  				data = b
   115  			default:
   116  				return errors.New("unknown metadata version")
   117  			}
   118  
   119  			if c.Bool("data") {
   120  				b, err := data.json()
   121  				if err != nil {
   122  					return err
   123  				}
   124  				buf = bytes.NewBuffer(b)
   125  			}
   126  			if c.Bool("ndjson") {
   127  				fmt.Println(buf.String())
   128  				continue
   129  			}
   130  			var msi map[string]interface{}
   131  			dec := json.NewDecoder(buf)
   132  			// Use number to preserve integers.
   133  			dec.UseNumber()
   134  			err = dec.Decode(&msi)
   135  			if err != nil {
   136  				return err
   137  			}
   138  			b, err = json.MarshalIndent(msi, "", "  ")
   139  			if err != nil {
   140  				return err
   141  			}
   142  			fmt.Println(string(b))
   143  		}
   144  		return nil
   145  	}
   146  	err := app.Run(os.Args)
   147  	if err != nil {
   148  		log.Fatal(err)
   149  	}
   150  }
   151  
   152  var (
   153  	// XL header specifies the format
   154  	xlHeader = [4]byte{'X', 'L', '2', ' '}
   155  
   156  	// Current version being written.
   157  	xlVersionCurrent [4]byte
   158  )
   159  
   160  const (
   161  	// Breaking changes.
   162  	// Newer versions cannot be read by older software.
   163  	// This will prevent downgrades to incompatible versions.
   164  	xlVersionMajor = 1
   165  
   166  	// Non breaking changes.
   167  	// Bumping this is informational, but should be done
   168  	// if any change is made to the data stored, bumping this
   169  	// will allow to detect the exact version later.
   170  	xlVersionMinor = 1
   171  )
   172  
   173  func init() {
   174  	binary.LittleEndian.PutUint16(xlVersionCurrent[0:2], xlVersionMajor)
   175  	binary.LittleEndian.PutUint16(xlVersionCurrent[2:4], xlVersionMinor)
   176  }
   177  
   178  // checkXL2V1 will check if the metadata has correct header and is a known major version.
   179  // The remaining payload and versions are returned.
   180  func checkXL2V1(buf []byte) (payload []byte, major, minor uint16, err error) {
   181  	if len(buf) <= 8 {
   182  		return payload, 0, 0, fmt.Errorf("xlMeta: no data")
   183  	}
   184  
   185  	if !bytes.Equal(buf[:4], xlHeader[:]) {
   186  		return payload, 0, 0, fmt.Errorf("xlMeta: unknown XLv2 header, expected %v, got %v", xlHeader[:4], buf[:4])
   187  	}
   188  
   189  	if bytes.Equal(buf[4:8], []byte("1   ")) {
   190  		// Set as 1,0.
   191  		major, minor = 1, 0
   192  	} else {
   193  		major, minor = binary.LittleEndian.Uint16(buf[4:6]), binary.LittleEndian.Uint16(buf[6:8])
   194  	}
   195  	if major > xlVersionMajor {
   196  		return buf[8:], major, minor, fmt.Errorf("xlMeta: unknown major version %d found", major)
   197  	}
   198  
   199  	return buf[8:], major, minor, nil
   200  }
   201  
   202  const xlMetaInlineDataVer = 1
   203  
   204  type xlMetaInlineData []byte
   205  
   206  // afterVersion returns the payload after the version, if any.
   207  func (x xlMetaInlineData) afterVersion() []byte {
   208  	if len(x) == 0 {
   209  		return x
   210  	}
   211  	return x[1:]
   212  }
   213  
   214  // versionOK returns whether the version is ok.
   215  func (x xlMetaInlineData) versionOK() bool {
   216  	if len(x) == 0 {
   217  		return true
   218  	}
   219  	return x[0] > 0 && x[0] <= xlMetaInlineDataVer
   220  }
   221  
   222  func (x xlMetaInlineData) json() ([]byte, error) {
   223  	if len(x) == 0 {
   224  		return []byte("{}"), nil
   225  	}
   226  	if !x.versionOK() {
   227  		return nil, errors.New("xlMetaInlineData: unknown version")
   228  	}
   229  
   230  	sz, buf, err := msgp.ReadMapHeaderBytes(x.afterVersion())
   231  	if err != nil {
   232  		return nil, err
   233  	}
   234  	res := []byte("{")
   235  
   236  	for i := uint32(0); i < sz; i++ {
   237  		var key, val []byte
   238  		key, buf, err = msgp.ReadMapKeyZC(buf)
   239  		if err != nil {
   240  			return nil, err
   241  		}
   242  		if len(key) == 0 {
   243  			return nil, fmt.Errorf("xlMetaInlineData: key %d is length 0", i)
   244  		}
   245  		// Skip data...
   246  		val, buf, err = msgp.ReadBytesZC(buf)
   247  		if err != nil {
   248  			return nil, err
   249  		}
   250  		if i > 0 {
   251  			res = append(res, ',')
   252  		}
   253  		s := fmt.Sprintf(`"%s":%d`, string(key), len(val))
   254  		res = append(res, []byte(s)...)
   255  	}
   256  	res = append(res, '}')
   257  	return res, nil
   258  }