github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/xl-storage-meta-inline.go (about)

     1  // Copyright (c) 2015-2021 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package cmd
    19  
    20  import (
    21  	"errors"
    22  	"fmt"
    23  
    24  	"github.com/minio/minio/internal/logger"
    25  	"github.com/tinylib/msgp/msgp"
    26  )
    27  
    28  // xlMetaInlineData is serialized data in [string][]byte pairs.
    29  type xlMetaInlineData []byte
    30  
    31  // xlMetaInlineDataVer indicates the version of the inline data structure.
    32  const xlMetaInlineDataVer = 1
    33  
    34  // versionOK returns whether the version is ok.
    35  func (x xlMetaInlineData) versionOK() bool {
    36  	if len(x) == 0 {
    37  		return true
    38  	}
    39  	return x[0] > 0 && x[0] <= xlMetaInlineDataVer
    40  }
    41  
    42  // afterVersion returns the payload after the version, if any.
    43  func (x xlMetaInlineData) afterVersion() []byte {
    44  	if len(x) == 0 {
    45  		return x
    46  	}
    47  	return x[1:]
    48  }
    49  
    50  // find the data with key s.
    51  // Returns nil if not for or an error occurs.
    52  func (x xlMetaInlineData) find(key string) []byte {
    53  	if len(x) == 0 || !x.versionOK() {
    54  		return nil
    55  	}
    56  	sz, buf, err := msgp.ReadMapHeaderBytes(x.afterVersion())
    57  	if err != nil || sz == 0 {
    58  		return nil
    59  	}
    60  	for i := uint32(0); i < sz; i++ {
    61  		var found []byte
    62  		found, buf, err = msgp.ReadMapKeyZC(buf)
    63  		if err != nil || sz == 0 {
    64  			return nil
    65  		}
    66  		if string(found) == key {
    67  			val, _, _ := msgp.ReadBytesZC(buf)
    68  			return val
    69  		}
    70  		// Skip it
    71  		_, buf, err = msgp.ReadBytesZC(buf)
    72  		if err != nil {
    73  			return nil
    74  		}
    75  	}
    76  	return nil
    77  }
    78  
    79  // validate checks if the data is valid.
    80  // It does not check integrity of the stored data.
    81  func (x xlMetaInlineData) validate() error {
    82  	if len(x) == 0 {
    83  		return nil
    84  	}
    85  
    86  	if !x.versionOK() {
    87  		return fmt.Errorf("xlMetaInlineData: unknown version 0x%x", x[0])
    88  	}
    89  
    90  	sz, buf, err := msgp.ReadMapHeaderBytes(x.afterVersion())
    91  	if err != nil {
    92  		return fmt.Errorf("xlMetaInlineData: %w", err)
    93  	}
    94  
    95  	for i := uint32(0); i < sz; i++ {
    96  		var key []byte
    97  		key, buf, err = msgp.ReadMapKeyZC(buf)
    98  		if err != nil {
    99  			return fmt.Errorf("xlMetaInlineData: %w", err)
   100  		}
   101  		if len(key) == 0 {
   102  			return fmt.Errorf("xlMetaInlineData: key %d is length 0", i)
   103  		}
   104  		_, buf, err = msgp.ReadBytesZC(buf)
   105  		if err != nil {
   106  			return fmt.Errorf("xlMetaInlineData: %w", err)
   107  		}
   108  	}
   109  
   110  	return nil
   111  }
   112  
   113  // repair will copy all seemingly valid data entries from a corrupted set.
   114  // This does not ensure that data is correct, but will allow all operations to complete.
   115  func (x *xlMetaInlineData) repair() {
   116  	data := *x
   117  	if len(data) == 0 {
   118  		return
   119  	}
   120  
   121  	if !data.versionOK() {
   122  		*x = nil
   123  		return
   124  	}
   125  
   126  	sz, buf, err := msgp.ReadMapHeaderBytes(data.afterVersion())
   127  	if err != nil {
   128  		*x = nil
   129  		return
   130  	}
   131  
   132  	// Remove all current data
   133  	keys := make([][]byte, 0, sz)
   134  	vals := make([][]byte, 0, sz)
   135  	for i := uint32(0); i < sz; i++ {
   136  		var key, val []byte
   137  		key, buf, err = msgp.ReadMapKeyZC(buf)
   138  		if err != nil {
   139  			break
   140  		}
   141  		if len(key) == 0 {
   142  			break
   143  		}
   144  		val, buf, err = msgp.ReadBytesZC(buf)
   145  		if err != nil {
   146  			break
   147  		}
   148  		keys = append(keys, key)
   149  		vals = append(vals, val)
   150  	}
   151  	x.serialize(-1, keys, vals)
   152  }
   153  
   154  // validate checks if the data is valid.
   155  // It does not check integrity of the stored data.
   156  func (x xlMetaInlineData) list() ([]string, error) {
   157  	if len(x) == 0 {
   158  		return nil, nil
   159  	}
   160  	if !x.versionOK() {
   161  		return nil, errors.New("xlMetaInlineData: unknown version")
   162  	}
   163  
   164  	sz, buf, err := msgp.ReadMapHeaderBytes(x.afterVersion())
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  	keys := make([]string, 0, sz)
   169  	for i := uint32(0); i < sz; i++ {
   170  		var key []byte
   171  		key, buf, err = msgp.ReadMapKeyZC(buf)
   172  		if err != nil {
   173  			return keys, err
   174  		}
   175  		if len(key) == 0 {
   176  			return keys, fmt.Errorf("xlMetaInlineData: key %d is length 0", i)
   177  		}
   178  		keys = append(keys, string(key))
   179  		// Skip data...
   180  		_, buf, err = msgp.ReadBytesZC(buf)
   181  		if err != nil {
   182  			return keys, err
   183  		}
   184  	}
   185  	return keys, nil
   186  }
   187  
   188  // serialize will serialize the provided keys and values.
   189  // The function will panic if keys/value slices aren't of equal length.
   190  // Payload size can give an indication of expected payload size.
   191  // If plSize is <= 0 it will be calculated.
   192  func (x *xlMetaInlineData) serialize(plSize int, keys [][]byte, vals [][]byte) {
   193  	if len(keys) != len(vals) {
   194  		panic(fmt.Errorf("xlMetaInlineData.serialize: keys/value number mismatch"))
   195  	}
   196  	if len(keys) == 0 {
   197  		*x = nil
   198  		return
   199  	}
   200  	if plSize <= 0 {
   201  		plSize = 1 + msgp.MapHeaderSize
   202  		for i := range keys {
   203  			plSize += len(keys[i]) + len(vals[i]) + msgp.StringPrefixSize + msgp.ArrayHeaderSize
   204  		}
   205  	}
   206  	payload := make([]byte, 1, plSize)
   207  	payload[0] = xlMetaInlineDataVer
   208  	payload = msgp.AppendMapHeader(payload, uint32(len(keys)))
   209  	for i := range keys {
   210  		payload = msgp.AppendStringFromBytes(payload, keys[i])
   211  		payload = msgp.AppendBytes(payload, vals[i])
   212  	}
   213  	*x = payload
   214  }
   215  
   216  // entries returns the number of entries in the data.
   217  func (x xlMetaInlineData) entries() int {
   218  	if len(x) == 0 || !x.versionOK() {
   219  		return 0
   220  	}
   221  	sz, _, _ := msgp.ReadMapHeaderBytes(x.afterVersion())
   222  	return int(sz)
   223  }
   224  
   225  // replace will add or replace a key/value pair.
   226  func (x *xlMetaInlineData) replace(key string, value []byte) {
   227  	in := x.afterVersion()
   228  	sz, buf, _ := msgp.ReadMapHeaderBytes(in)
   229  	keys := make([][]byte, 0, sz+1)
   230  	vals := make([][]byte, 0, sz+1)
   231  
   232  	// Version plus header...
   233  	plSize := 1 + msgp.MapHeaderSize
   234  	replaced := false
   235  	for i := uint32(0); i < sz; i++ {
   236  		var found, foundVal []byte
   237  		var err error
   238  		found, buf, err = msgp.ReadMapKeyZC(buf)
   239  		if err != nil {
   240  			break
   241  		}
   242  		foundVal, buf, err = msgp.ReadBytesZC(buf)
   243  		if err != nil {
   244  			break
   245  		}
   246  		plSize += len(found) + msgp.StringPrefixSize + msgp.ArrayHeaderSize
   247  		keys = append(keys, found)
   248  		if string(found) == key {
   249  			vals = append(vals, value)
   250  			plSize += len(value)
   251  			replaced = true
   252  		} else {
   253  			vals = append(vals, foundVal)
   254  			plSize += len(foundVal)
   255  		}
   256  	}
   257  
   258  	// Add one more.
   259  	if !replaced {
   260  		keys = append(keys, []byte(key))
   261  		vals = append(vals, value)
   262  		plSize += len(key) + len(value) + msgp.StringPrefixSize + msgp.ArrayHeaderSize
   263  	}
   264  
   265  	// Reserialize...
   266  	x.serialize(plSize, keys, vals)
   267  }
   268  
   269  // rename will rename a key.
   270  // Returns whether the key was found.
   271  func (x *xlMetaInlineData) rename(oldKey, newKey string) bool {
   272  	in := x.afterVersion()
   273  	sz, buf, _ := msgp.ReadMapHeaderBytes(in)
   274  	keys := make([][]byte, 0, sz)
   275  	vals := make([][]byte, 0, sz)
   276  
   277  	// Version plus header...
   278  	plSize := 1 + msgp.MapHeaderSize
   279  	found := false
   280  	for i := uint32(0); i < sz; i++ {
   281  		var foundKey, foundVal []byte
   282  		var err error
   283  		foundKey, buf, err = msgp.ReadMapKeyZC(buf)
   284  		if err != nil {
   285  			break
   286  		}
   287  		foundVal, buf, err = msgp.ReadBytesZC(buf)
   288  		if err != nil {
   289  			break
   290  		}
   291  		plSize += len(foundVal) + msgp.StringPrefixSize + msgp.ArrayHeaderSize
   292  		vals = append(vals, foundVal)
   293  		if string(foundKey) != oldKey {
   294  			keys = append(keys, foundKey)
   295  			plSize += len(foundKey)
   296  		} else {
   297  			keys = append(keys, []byte(newKey))
   298  			plSize += len(newKey)
   299  			found = true
   300  		}
   301  	}
   302  	// If not found, just return.
   303  	if !found {
   304  		return false
   305  	}
   306  
   307  	// Reserialize...
   308  	x.serialize(plSize, keys, vals)
   309  	return true
   310  }
   311  
   312  // remove will remove one or more keys.
   313  // Returns true if any key was found.
   314  func (x *xlMetaInlineData) remove(keys ...string) bool {
   315  	in := x.afterVersion()
   316  	sz, buf, _ := msgp.ReadMapHeaderBytes(in)
   317  	newKeys := make([][]byte, 0, sz)
   318  	newVals := make([][]byte, 0, sz)
   319  	var removeKey func(s []byte) bool
   320  
   321  	// Copy if big number of compares...
   322  	if len(keys) > 5 && sz > 5 {
   323  		mKeys := make(map[string]struct{}, len(keys))
   324  		for _, key := range keys {
   325  			mKeys[key] = struct{}{}
   326  		}
   327  		removeKey = func(s []byte) bool {
   328  			_, ok := mKeys[string(s)]
   329  			return ok
   330  		}
   331  	} else {
   332  		removeKey = func(s []byte) bool {
   333  			for _, key := range keys {
   334  				if key == string(s) {
   335  					return true
   336  				}
   337  			}
   338  			return false
   339  		}
   340  	}
   341  
   342  	// Version plus header...
   343  	plSize := 1 + msgp.MapHeaderSize
   344  	found := false
   345  	for i := uint32(0); i < sz; i++ {
   346  		var foundKey, foundVal []byte
   347  		var err error
   348  		foundKey, buf, err = msgp.ReadMapKeyZC(buf)
   349  		if err != nil {
   350  			break
   351  		}
   352  		foundVal, buf, err = msgp.ReadBytesZC(buf)
   353  		if err != nil {
   354  			break
   355  		}
   356  		if !removeKey(foundKey) {
   357  			plSize += msgp.StringPrefixSize + msgp.ArrayHeaderSize + len(foundKey) + len(foundVal)
   358  			newKeys = append(newKeys, foundKey)
   359  			newVals = append(newVals, foundVal)
   360  		} else {
   361  			found = true
   362  		}
   363  	}
   364  	// If not found, just return.
   365  	if !found {
   366  		return false
   367  	}
   368  	// If none left...
   369  	if len(newKeys) == 0 {
   370  		*x = nil
   371  		return true
   372  	}
   373  
   374  	// Reserialize...
   375  	x.serialize(plSize, newKeys, newVals)
   376  	return true
   377  }
   378  
   379  // xlMetaV2TrimData will trim any data from the metadata without unmarshalling it.
   380  // If any error occurs the unmodified data is returned.
   381  func xlMetaV2TrimData(buf []byte) []byte {
   382  	metaBuf, min, maj, err := checkXL2V1(buf)
   383  	if err != nil {
   384  		return buf
   385  	}
   386  	if maj == 1 && min < 1 {
   387  		// First version to carry data.
   388  		return buf
   389  	}
   390  	// Skip header
   391  	_, metaBuf, err = msgp.ReadBytesZC(metaBuf)
   392  	if err != nil {
   393  		logger.LogIf(GlobalContext, err)
   394  		return buf
   395  	}
   396  	// Skip CRC
   397  	if maj > 1 || min >= 2 {
   398  		_, metaBuf, err = msgp.ReadUint32Bytes(metaBuf)
   399  		logger.LogIf(GlobalContext, err)
   400  	}
   401  	//   =  input - current pos
   402  	ends := len(buf) - len(metaBuf)
   403  	if ends > len(buf) {
   404  		return buf
   405  	}
   406  
   407  	return buf[:ends]
   408  }