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