]> Cypherpunks.ru repositories - goredo.git/blob - dep.go
Download link for 2.6.2 release
[goredo.git] / dep.go
1 // goredo -- djb's redo implementation on pure Go
2 // Copyright (C) 2020-2024 Sergey Matveev <stargrave@stargrave.org>
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, version 3 of the License.
7 //
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
15
16 // Dependencies saver
17
18 package main
19
20 import (
21         "bufio"
22         "bytes"
23         "encoding/binary"
24         "errors"
25         "io"
26         "os"
27         "path"
28
29         "github.com/google/uuid"
30         "lukechampine.com/blake3"
31 )
32
33 const (
34         BinMagic     = "GOREDO"
35         BinVersionV1 = 0x01
36         UUIDLen      = 16
37
38         DepTypeIfcreate      = 0x01
39         DepTypeIfchange      = 0x02
40         DepTypeAlways        = 0x03
41         DepTypeStamp         = 0x04
42         DepTypeIfchangeNonex = 0x05
43 )
44
45 var (
46         DirPrefix string
47         DepCwd    string
48
49         IfchangeCache = make(map[string][]*Ifchange)
50         DepCache      = make(map[string]*Dep)
51
52         NullUUID uuid.UUID
53 )
54
55 func chunkWrite(in []byte) (out []byte) {
56         l := len(in) + 2
57         if l > 1<<16 {
58                 panic("too long")
59         }
60         out = make([]byte, l)
61         binary.BigEndian.PutUint16(out[:2], uint16(l))
62         copy(out[2:], in)
63         return
64 }
65
66 type Ifchange struct {
67         tgt  *Tgt
68         meta [InodeLen + HashLen]byte
69 }
70
71 func (ifchange *Ifchange) Inode() *Inode {
72         inode := Inode(ifchange.meta[:InodeLen])
73         return &inode
74 }
75
76 func (ifchange *Ifchange) Hash() Hash {
77         return Hash(ifchange.meta[InodeLen:])
78 }
79
80 type Dep struct {
81         build     uuid.UUID
82         always    bool
83         stamp     Hash
84         ifcreates []*Tgt
85         ifchanges []*Ifchange
86 }
87
88 func ifcreate(w io.Writer, fdDepName string, tgt string) (err error) {
89         tracef(CDebug, "ifcreate: %s <- %s", fdDepName, tgt)
90         _, err = io.Copy(w, bytes.NewBuffer(chunkWrite(append(
91                 []byte{DepTypeIfcreate}, []byte(tgt)...,
92         ))))
93         return
94 }
95
96 func always(w io.Writer, fdDepName string) (err error) {
97         tracef(CDebug, "always: %s", fdDepName)
98         _, err = io.Copy(w, bytes.NewBuffer(chunkWrite(
99                 []byte{DepTypeAlways},
100         )))
101         return
102 }
103
104 func stamp(w io.Writer, fdDepName string, hsh Hash) (err error) {
105         tracef(CDebug, "stamp: %s <- %s", fdDepName, hsh)
106         _, err = io.Copy(w, bytes.NewBuffer(chunkWrite(append(
107                 []byte{DepTypeStamp}, []byte(hsh)...,
108         ))))
109         return
110 }
111
112 func fileHash(fd io.Reader) (Hash, error) {
113         h := blake3.New(HashLen, nil)
114         if _, err := io.Copy(h, bufio.NewReader(fd)); err != nil {
115                 return "", err
116         }
117         return Hash(h.Sum(nil)), nil
118 }
119
120 func depWrite(w io.Writer, fdDepName, cwd string, tgt *Tgt, hsh Hash) (err error) {
121         tracef(CDebug, "ifchange: %s <- %s", fdDepName, tgt)
122         fd, err := os.Open(tgt.a)
123         if err != nil {
124                 return ErrLine(err)
125         }
126         defer fd.Close()
127         inode, isDir, err := inodeFromFileByFd(fd)
128         if err != nil {
129                 return ErrLine(err)
130         }
131         if isDir {
132                 return nil
133         }
134         if hsh == "" {
135                 hsh, err = fileHash(fd)
136                 if err != nil {
137                         return ErrLine(err)
138                 }
139         }
140         _, err = io.Copy(w, bytes.NewBuffer(chunkWrite(bytes.Join([][]byte{
141                 {DepTypeIfchange},
142                 inode[:],
143                 []byte(hsh),
144                 []byte(tgt.RelTo(cwd)),
145         }, nil))))
146         return
147 }
148
149 func depWriteNonex(w io.Writer, fdDepName, tgtRel string) (err error) {
150         tracef(CDebug, "ifchange: %s <- %s (non-existing)", fdDepName, tgtRel)
151         _, err = io.Copy(w, bytes.NewBuffer(chunkWrite(bytes.Join([][]byte{
152                 {DepTypeIfchangeNonex},
153                 []byte(tgtRel),
154         }, nil))))
155         return
156 }
157
158 func depsWrite(fdDep *os.File, tgts []*Tgt) error {
159         if fdDep == nil {
160                 tracef(CDebug, "no opened fdDep: %s", tgts)
161                 return nil
162         }
163         var err error
164         var cwd string
165         fdDepW := bufio.NewWriter(fdDep)
166         for _, tgt := range tgts {
167                 cwd = Cwd
168                 if DepCwd != "" && Cwd != DepCwd {
169                         cwd = DepCwd
170                 }
171                 tgtDir := path.Join(cwd, DirPrefix)
172                 if _, errStat := os.Stat(tgt.a); errStat == nil {
173                         err = ErrLine(depWrite(fdDepW, fdDep.Name(), tgtDir, tgt, ""))
174                 } else {
175                         tgtRel := tgt.RelTo(tgtDir)
176                         err = ErrLine(depWriteNonex(fdDepW, fdDep.Name(), tgtRel))
177                 }
178                 if err != nil {
179                         return err
180                 }
181         }
182         return fdDepW.Flush()
183 }
184
185 func depHeadParse(data []byte) (build uuid.UUID, tail []byte, err error) {
186         if len(data) < len(BinMagic)+1+UUIDLen {
187                 err = errors.New("too short header")
188                 return
189         }
190         if !bytes.Equal(data[:len(BinMagic)], []byte(BinMagic)) {
191                 err = errors.New("bad magic")
192                 return
193         }
194         data = data[len(BinMagic):]
195         switch data[0] {
196         case BinVersionV1:
197         default:
198                 err = errors.New("unknown version")
199                 return
200         }
201         build = uuid.Must(uuid.FromBytes(data[1 : 1+UUIDLen]))
202         tail = data[1+UUIDLen:]
203         return
204 }
205
206 func chunkRead(data []byte) (typ byte, chunk []byte, tail []byte, err error) {
207         if len(data) < 2 {
208                 err = errors.New("no length")
209                 return
210         }
211         l := binary.BigEndian.Uint16(data[:2])
212         if l == 0 {
213                 err = errors.New("zero length chunk")
214                 return
215         }
216         if len(data) < int(l) {
217                 err = errors.New("not enough data")
218                 return
219         }
220         typ, chunk, tail = data[2], data[3:l], data[l:]
221         return
222 }
223
224 func depBinIfchangeParse(tgt *Tgt, chunk []byte) (*Ifchange, string, error) {
225         if len(chunk) < InodeLen+HashLen+1 {
226                 return nil, "", errors.New("too short \"ifchange\" format")
227         }
228
229         tgtH, _ := pathSplit(tgt.a)
230         name := string(chunk[InodeLen+HashLen:])
231         ifchange := &Ifchange{
232                 tgt:  NewTgt(path.Join(tgtH, name)),
233                 meta: ([InodeLen + HashLen]byte)(chunk),
234         }
235         for _, cached := range IfchangeCache[ifchange.tgt.rel] {
236                 if ifchange.meta == cached.meta {
237                         return cached, name, nil
238                 }
239         }
240         if IfchangeCache != nil {
241                 IfchangeCache[ifchange.tgt.rel] = append(IfchangeCache[ifchange.tgt.rel], ifchange)
242         }
243         return ifchange, name, nil
244 }
245
246 func depParse(tgt *Tgt, data []byte) (*Dep, error) {
247         build, data, err := depHeadParse(data)
248         if err != nil {
249                 return nil, err
250         }
251         dep := Dep{build: build}
252         var typ byte
253         var chunk []byte
254         for len(data) > 0 {
255                 typ, chunk, data, err = chunkRead(data)
256                 if err != nil {
257                         return nil, ErrLine(err)
258                 }
259                 switch typ {
260                 case DepTypeAlways:
261                         if len(chunk) != 0 {
262                                 return nil, ErrLine(errors.New("bad \"always\" format"))
263                         }
264                         dep.always = true
265                 case DepTypeStamp:
266                         if len(chunk) != HashLen {
267                                 return nil, ErrLine(errors.New("bad \"stamp\" format"))
268                         }
269                         dep.stamp = Hash(chunk)
270                 case DepTypeIfcreate:
271                         if len(chunk) < 1 {
272                                 return nil, ErrLine(errors.New("too short \"ifcreate\" format"))
273                         }
274                         tgtH, _ := pathSplit(tgt.a)
275                         dep.ifcreates = append(dep.ifcreates, NewTgt(path.Join(tgtH, string(chunk))))
276                 case DepTypeIfchange:
277                         ifchange, _, err := depBinIfchangeParse(tgt, chunk)
278                         if err != nil {
279                                 return nil, ErrLine(err)
280                         }
281                         dep.ifchanges = append(dep.ifchanges, ifchange)
282                 case DepTypeIfchangeNonex:
283                         if len(chunk) < 1 {
284                                 return nil, ErrLine(errors.New("too short \"ifchange\" format"))
285                         }
286                         dep.ifchanges = append(dep.ifchanges, &Ifchange{tgt: NewTgt(string(chunk))})
287                 default:
288                         return nil, ErrLine(errors.New("unknown type"))
289                 }
290         }
291         return &dep, nil
292 }
293
294 func depRead(tgt *Tgt) (*Dep, error) {
295         data, err := os.ReadFile(tgt.dep)
296         if err != nil {
297                 return nil, err
298         }
299         return depParse(tgt, data)
300 }
301
302 func depReadOnlyIfchanges(pth string) (ifchanges []string, err error) {
303         data, err := os.ReadFile(pth)
304         if err != nil {
305                 return
306         }
307         _, data, err = depHeadParse(data)
308         if err != nil {
309                 return nil, err
310         }
311         var typ byte
312         var chunk []byte
313         var tgt string
314         tgtDummy := NewTgt("")
315         for len(data) > 0 {
316                 typ, chunk, data, err = chunkRead(data)
317                 if err != nil {
318                         return nil, ErrLine(err)
319                 }
320                 switch typ {
321                 case DepTypeIfchange:
322                         _, tgt, err = depBinIfchangeParse(tgtDummy, chunk)
323                         if err != nil {
324                                 return nil, ErrLine(err)
325                         }
326                         ifchanges = append(ifchanges, tgt)
327                 case DepTypeIfchangeNonex:
328                         ifchanges = append(ifchanges, string(chunk))
329                 }
330         }
331         return
332 }
333
334 func depBuildRead(pth string) (uuid.UUID, error) {
335         fd, err := os.Open(pth)
336         if err != nil {
337                 return NullUUID, err
338         }
339         data := make([]byte, len(BinMagic)+1+UUIDLen)
340         _, err = io.ReadFull(fd, data)
341         fd.Close()
342         if err != nil {
343                 return NullUUID, err
344         }
345         build, _, err := depHeadParse(data)
346         return build, err
347 }
348
349 func depBuildWrite(w io.Writer, build uuid.UUID) (err error) {
350         _, err = io.Copy(w, bytes.NewBuffer(bytes.Join([][]byte{
351                 []byte(BinMagic),
352                 {BinVersionV1},
353                 build[:],
354         }, nil)))
355         return
356 }