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 }