2 goredo -- djb's redo implementation on pure Go
3 Copyright (C) 2020-2023 Sergey Matveev <stargrave@stargrave.org>
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, version 3 of the License.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
31 "github.com/google/uuid"
32 "lukechampine.com/blake3"
40 DepTypeIfcreate = 0x01
41 DepTypeIfchange = 0x02
44 DepTypeIfchangeDummy = 0x05
51 IfchangeCache = make(map[string][]*Ifchange)
52 DepCache = make(map[string]*Dep)
57 func chunkWrite(in []byte) (out []byte) {
63 binary.BigEndian.PutUint16(out[:2], uint16(l))
68 type Ifchange struct {
73 func (ifchange *Ifchange) Inode() Inode {
74 return Inode(ifchange.meta[:InodeLen])
77 func (ifchange *Ifchange) Hash() Hash {
78 return Hash(ifchange.meta[InodeLen:])
89 func ifcreate(w io.Writer, fdDepName string, tgt string) (err error) {
90 tracef(CDebug, "ifcreate: %s <- %s", fdDepName, tgt)
91 _, err = io.Copy(w, bytes.NewBuffer(chunkWrite(append(
92 []byte{DepTypeIfcreate}, []byte(tgt)...,
97 func always(w io.Writer, fdDepName string) (err error) {
98 tracef(CDebug, "always: %s", fdDepName)
99 _, err = io.Copy(w, bytes.NewBuffer(chunkWrite(
100 []byte{DepTypeAlways},
105 func stamp(w io.Writer, fdDepName string, hsh Hash) (err error) {
106 tracef(CDebug, "stamp: %s <- %s", fdDepName, hsh)
107 _, err = io.Copy(w, bytes.NewBuffer(chunkWrite(append(
108 []byte{DepTypeStamp}, []byte(hsh)...,
113 func fileHash(fd io.Reader) (Hash, error) {
114 h := blake3.New(HashLen, nil)
115 if _, err := io.Copy(h, bufio.NewReader(fd)); err != nil {
118 return Hash(h.Sum(nil)), nil
121 func depWrite(w io.Writer, fdDepName, cwd string, tgt *Tgt, hsh Hash) (err error) {
122 tracef(CDebug, "ifchange: %s <- %s", fdDepName, tgt)
123 fd, err := os.Open(tgt.a)
128 inode, isDir, err := inodeFromFileByFd(fd)
136 hsh, err = fileHash(fd)
141 _, err = io.Copy(w, bytes.NewBuffer(chunkWrite(bytes.Join([][]byte{
145 []byte(tgt.RelTo(cwd)),
150 func depWriteDummy(w io.Writer, fdDepName, tgtRel string) (err error) {
151 tracef(CDebug, "ifchange: %s <- %s (non-existing)", fdDepName, tgtRel)
152 _, err = io.Copy(w, bytes.NewBuffer(chunkWrite(bytes.Join([][]byte{
153 {DepTypeIfchangeDummy},
159 func depsWrite(fdDep *os.File, tgts []*Tgt) error {
161 tracef(CDebug, "no opened fdDep: %s", tgts)
166 fdDepW := bufio.NewWriter(fdDep)
167 for _, tgt := range tgts {
169 if DepCwd != "" && Cwd != DepCwd {
172 tgtDir := path.Join(cwd, DirPrefix)
173 if _, errStat := os.Stat(tgt.a); errStat == nil {
174 err = ErrLine(depWrite(fdDepW, fdDep.Name(), tgtDir, tgt, ""))
176 tgtRel := tgt.RelTo(tgtDir)
177 err = ErrLine(depWriteDummy(fdDepW, fdDep.Name(), tgtRel))
183 return fdDepW.Flush()
186 func depHeadParse(data []byte) (build uuid.UUID, tail []byte, err error) {
187 if len(data) < len(BinMagic)+1+UUIDLen {
188 err = errors.New("too short header")
191 if !bytes.Equal(data[:len(BinMagic)], []byte(BinMagic)) {
192 err = errors.New("bad magic")
195 data = data[len(BinMagic):]
199 err = errors.New("unknown version")
202 build = uuid.Must(uuid.FromBytes(data[1 : 1+UUIDLen]))
203 tail = data[1+UUIDLen:]
207 func chunkRead(data []byte) (typ byte, chunk []byte, tail []byte, err error) {
209 err = errors.New("no length")
212 l := binary.BigEndian.Uint16(data[:2])
214 err = errors.New("zero length chunk")
217 if len(data) < int(l) {
218 err = errors.New("not enough data")
221 typ, chunk, tail = data[2], data[3:l], data[l:]
225 func depBinIfchangeParse(tgt *Tgt, chunk []byte) (*Ifchange, string, error) {
226 if len(chunk) < InodeLen+HashLen+1 {
227 return nil, "", errors.New("too short \"ifchange\" format")
229 name := string(chunk[InodeLen+HashLen:])
230 meta := string(chunk[:InodeLen+HashLen])
232 tgtH, _ := pathSplit(tgt.a)
233 ifchange := &Ifchange{tgt: NewTgt(path.Join(tgtH, name)), meta: meta}
235 for _, cached := range IfchangeCache[ifchange.tgt.rel] {
236 if ifchange.meta == cached.meta {
242 if IfchangeCache != nil && !cachedFound {
243 IfchangeCache[ifchange.tgt.rel] = append(IfchangeCache[ifchange.tgt.rel], ifchange)
245 return ifchange, name, nil
248 func depParse(tgt *Tgt, data []byte) (*Dep, error) {
249 build, data, err := depHeadParse(data)
253 dep := Dep{build: build}
257 typ, chunk, data, err = chunkRead(data)
259 return nil, ErrLine(err)
264 return nil, ErrLine(errors.New("bad \"always\" format"))
268 if len(chunk) != HashLen {
269 return nil, ErrLine(errors.New("bad \"stamp\" format"))
271 dep.stamp = Hash(chunk)
272 case DepTypeIfcreate:
274 return nil, ErrLine(errors.New("too short \"ifcreate\" format"))
276 tgtH, _ := pathSplit(tgt.a)
277 dep.ifcreates = append(dep.ifcreates, NewTgt(path.Join(tgtH, string(chunk))))
278 case DepTypeIfchange:
279 ifchange, _, err := depBinIfchangeParse(tgt, chunk)
281 return nil, ErrLine(err)
283 dep.ifchanges = append(dep.ifchanges, ifchange)
284 case DepTypeIfchangeDummy:
286 return nil, ErrLine(errors.New("too short \"ifchange\" format"))
288 dep.ifchanges = append(dep.ifchanges, &Ifchange{tgt: NewTgt(string(chunk))})
290 return nil, ErrLine(errors.New("unknown type"))
296 func depRead(tgt *Tgt) (*Dep, error) {
297 data, err := os.ReadFile(tgt.dep)
301 return depParse(tgt, data)
304 func depReadOnlyIfchanges(pth string) (ifchanges []string, err error) {
305 data, err := os.ReadFile(pth)
309 _, data, err = depHeadParse(data)
316 tgtDummy := NewTgt("")
318 typ, chunk, data, err = chunkRead(data)
320 return nil, ErrLine(err)
323 case DepTypeIfchange:
324 _, tgt, err = depBinIfchangeParse(tgtDummy, chunk)
326 return nil, ErrLine(err)
328 ifchanges = append(ifchanges, tgt)
329 case DepTypeIfchangeDummy:
330 ifchanges = append(ifchanges, string(chunk))
336 func depBuildRead(pth string) (uuid.UUID, error) {
337 fd, err := os.Open(pth)
341 data := make([]byte, len(BinMagic)+1+UUIDLen)
342 _, err = io.ReadFull(fd, data)
347 build, _, err := depHeadParse(data)
351 func depBuildWrite(w io.Writer, build uuid.UUID) (err error) {
352 _, err = io.Copy(w, bytes.NewBuffer(bytes.Join([][]byte{