golang.org/x/build@v0.0.0-20240506185731-218518f32b70/maintner/reclog/reclog.go (about)

     1  // Copyright 2017 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package reclog contains readers and writers for a record wrapper
     6  // format used by maintner.
     7  package reclog
     8  
     9  import (
    10  	"bufio"
    11  	"bytes"
    12  	"fmt"
    13  	"io"
    14  	"os"
    15  	"strconv"
    16  )
    17  
    18  // The reclog format is as follows:
    19  //
    20  // The log is a series of binary blobs. Each record begins with the
    21  // variably-lengthed prefix "REC@XXX+YYY=" where the 0+ XXXX digits
    22  // are the hex offset on disk (where the 'R' on disk is written) and
    23  // the 0+ YYY digits are the hex length of the blob. After the YYY
    24  // digits there is a '=' byte before the YYY bytes of blob. There is
    25  // no record footer.
    26  var (
    27  	headerPrefix = []byte("REC@")
    28  	headerSuffix = []byte("=")
    29  	plus         = []byte("+")
    30  )
    31  
    32  // RecordCallback is the callback signature accepted by
    33  // ForeachFileRecord and ForeachRecord, which read the mutation log
    34  // format used by DiskMutationLogger.
    35  //
    36  // Offset is the offset in the logical of physical file.
    37  // hdr and bytes are only valid until the function returns
    38  // and must not be retained.
    39  //
    40  // hdr is the record header, in the form "REC@c765c9a+1d3=" (REC@ <hex
    41  // offset> + <hex len(rec)> + '=').
    42  //
    43  // rec is the proto3 binary marshalled representation of
    44  // *maintpb.Mutation.
    45  //
    46  // If the callback returns an error, iteration stops.
    47  type RecordCallback func(off int64, hdr, rec []byte) error
    48  
    49  // ForeachFileRecord calls fn for each record in the named file.
    50  // Calls to fn are made serially.
    51  // If fn returns an error, iteration ends and that error is returned.
    52  func ForeachFileRecord(path string, fn RecordCallback) error {
    53  	f, err := os.Open(path)
    54  	if err != nil {
    55  		return err
    56  	}
    57  	defer f.Close()
    58  	if err := ForeachRecord(f, 0, fn); err != nil {
    59  		return fmt.Errorf("error in %s: %v", path, err)
    60  	}
    61  	return nil
    62  }
    63  
    64  // ForeachRecord calls fn for each record in r.
    65  // Calls to fn are made serially.
    66  // If fn returns an error, iteration ends and that error is returned.
    67  // The startOffset is where in the file r represents. It should be 0
    68  // if reading from the beginning of a file.
    69  func ForeachRecord(r io.Reader, startOffset int64, fn RecordCallback) error {
    70  	off := startOffset
    71  	br := bufio.NewReader(r)
    72  	var buf bytes.Buffer
    73  	var hdrBuf bytes.Buffer
    74  	for {
    75  		startOff := off
    76  		hdr, err := br.ReadSlice('=')
    77  		if err != nil {
    78  			if err == io.EOF && len(hdr) == 0 {
    79  				return nil
    80  			}
    81  			return err
    82  		}
    83  		if len(hdr) > 40 {
    84  			return fmt.Errorf("malformed overlong header %q at offset %v", hdr[:40], startOff)
    85  		}
    86  		hdrBuf.Reset()
    87  		hdrBuf.Write(hdr)
    88  		if !bytes.HasPrefix(hdr, headerPrefix) || !bytes.HasSuffix(hdr, headerSuffix) || bytes.Count(hdr, plus) != 1 {
    89  			return fmt.Errorf("malformed header %q at offset %v", hdr, startOff)
    90  		}
    91  		plusPos := bytes.IndexByte(hdr, '+')
    92  		hdrOff, err := strconv.ParseInt(string(hdr[len(headerPrefix):plusPos]), 16, 64)
    93  		if err != nil {
    94  			return fmt.Errorf("malformed header %q (malformed offset) at offset %v", hdr, startOff)
    95  		}
    96  		if hdrOff != startOff {
    97  			return fmt.Errorf("malformed header %q with offset %v doesn't match expected offset %v", hdr, hdrOff, startOff)
    98  		}
    99  		hdrSize, err := strconv.ParseInt(string(hdr[plusPos+1:len(hdr)-1]), 16, 64)
   100  		if err != nil {
   101  			return fmt.Errorf("malformed header %q (bad size) at offset %v", hdr, startOff)
   102  		}
   103  		off += int64(len(hdr))
   104  
   105  		buf.Reset()
   106  		if _, err := io.CopyN(&buf, br, hdrSize); err != nil {
   107  			return fmt.Errorf("truncated record at offset %v: %v", startOff, err)
   108  		}
   109  		off += hdrSize
   110  		if err := fn(startOff, hdrBuf.Bytes(), buf.Bytes()); err != nil {
   111  			return err
   112  		}
   113  	}
   114  }
   115  
   116  // AppendRecordToFile opens the named filename for append (creating it
   117  // if necessary) and adds the provided data record to the end.
   118  // The caller is responsible for file locking.
   119  func AppendRecordToFile(filename string, data []byte) error {
   120  	f, err := os.OpenFile(filename, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600)
   121  	if err != nil {
   122  		return err
   123  	}
   124  	off, err := f.Seek(0, io.SeekEnd)
   125  	if err != nil {
   126  		return err
   127  	}
   128  	st, err := f.Stat()
   129  	if err != nil {
   130  		return err
   131  	}
   132  	if off != st.Size() {
   133  		return fmt.Errorf("Size %v != offset %v", st.Size(), off)
   134  	}
   135  	if err := WriteRecord(f, off, data); err != nil {
   136  		f.Close()
   137  		return err
   138  	}
   139  	return f.Close()
   140  }
   141  
   142  // WriteRecord writes the record data to w, formatting the record
   143  // wrapper with the given offset off. It is the caller's
   144  // responsibility to pass the correct offset. Exactly one Write
   145  // call will be made to w.
   146  func WriteRecord(w io.Writer, off int64, data []byte) error {
   147  	_, err := fmt.Fprintf(w, "REC@%x+%x=%s", off, len(data), data)
   148  	return err
   149  }