]> Cypherpunks.ru repositories - goredo.git/blob - ood.go
Binary format and many optimisations
[goredo.git] / ood.go
1 /*
2 goredo -- djb's redo implementation on pure Go
3 Copyright (C) 2020-2023 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 // Out-of-date determination
19
20 package main
21
22 import (
23         "errors"
24         "fmt"
25         "io"
26         "io/fs"
27         "log"
28         "os"
29         "strings"
30
31         "golang.org/x/sys/unix"
32 )
33
34 const (
35         EnvOODTgtsFd     = "REDO_OOD_TGTS_FD"
36         EnvOODTgtsLockFd = "REDO_OOD_TGTS_LOCK_FD"
37 )
38
39 var (
40         OODTgts       map[string]struct{}
41         FdOODTgts     *os.File
42         FdOODTgtsLock *os.File
43
44         OODCache        = make(map[string]bool)
45         FileExistsCache = make(map[string]bool)
46 )
47
48 func FileExists(p string) bool {
49         if exists, known := FileExistsCache[p]; known {
50                 return exists
51         }
52         _, err := os.Stat(p)
53         if err == nil {
54                 FileExistsCache[p] = true
55                 return true
56         }
57         if errors.Is(err, fs.ErrNotExist) {
58                 FileExistsCache[p] = false
59         }
60         return false
61 }
62
63 type TgtError struct {
64         Tgt *Tgt
65         Err error
66 }
67
68 func (e TgtError) Unwrap() error { return e.Err }
69
70 func (e TgtError) Error() string {
71         return fmt.Sprintf("%s: %s", e.Tgt, e.Err)
72 }
73
74 func isSrc(tgt *Tgt) bool {
75         if !FileExists(tgt.a) {
76                 return false
77         }
78         if FileExists(tgt.a + ".do") {
79                 return false
80         }
81         if FileExists(tgt.dep) {
82                 return false
83         }
84         return true
85 }
86
87 func isOODByBuildUUID(tgt *Tgt) bool {
88         build, err := depBuildRead(tgt.dep)
89         return err != nil || build != BuildUUID
90 }
91
92 func isOOD(tgt *Tgt, level int, seen map[string]*Tgt) (bool, error) {
93         indent := strings.Repeat(". ", level)
94         tracef(CDebug, "ood: %s%s checking", indent, tgt)
95         ood, cached := OODCache[tgt.rel]
96         if cached {
97                 tracef(CDebug, "ood: %s%s -> cached: %v", indent, tgt, ood)
98                 return ood, nil
99         }
100         dep := DepCache[tgt.rel]
101         var err error
102         if dep == nil {
103                 dep, err = depRead(tgt)
104                 if err != nil {
105                         if errors.Is(err, fs.ErrNotExist) {
106                                 if isSrc(tgt) {
107                                         ood = false
108                                         tracef(CDebug, "ood: %s%s -> is source", indent, tgt)
109                                 } else {
110                                         ood = true
111                                         tracef(CDebug, "ood: %s%s -> no dep: %s", indent, tgt, tgt.dep)
112                                 }
113                                 OODCache[tgt.rel] = ood
114                                 return ood, nil
115                         }
116                         if err != nil {
117                                 return true, TgtError{tgt, ErrLine(err)}
118                         }
119                 }
120                 DepCache[tgt.rel] = dep
121         }
122
123         if dep.build == BuildUUID {
124                 tracef(CDebug, "ood: %s%s -> already built", indent, tgt)
125                 OODCache[tgt.rel] = false
126                 return false, nil
127         }
128         if !FileExists(tgt.a) {
129                 tracef(CDebug, "ood: %s%s -> non-existent", indent, tgt)
130                 OODCache[tgt.rel] = true
131                 return true, nil
132         }
133
134         for _, ifcreate := range dep.ifcreates {
135                 if FileExists(ifcreate.a) {
136                         tracef(CDebug, "ood: %s%s -> %s created", indent, tgt, ifcreate)
137                         ood = true
138                         goto Done
139                 }
140         }
141
142         for _, ifchange := range dep.ifchanges {
143                 tracef(CDebug, "ood: %s%s -> %s: checking", indent, tgt, ifchange.tgt)
144                 ood, cached = OODCache[ifchange.tgt.rel]
145                 if cached {
146                         tracef(CDebug, "ood: %s%s -> %s: cached: %v", indent, tgt, ifchange.tgt, ood)
147                         if ood {
148                                 goto Done
149                         }
150                         continue
151                 }
152
153                 inode, err := inodeFromFileByPath(ifchange.tgt.a)
154                 if err != nil {
155                         if errors.Is(err, fs.ErrNotExist) {
156                                 tracef(CDebug, "ood: %s%s -> %s: not exists", indent, tgt, ifchange.tgt)
157                                 ood = true
158                                 OODCache[ifchange.tgt.rel] = ood
159                                 goto Done
160                         }
161                         return ood, TgtError{tgt, ErrLine(err)}
162                 }
163
164                 if inode[:8] != ifchange.Inode()[:8] {
165                         tracef(CDebug, "ood: %s%s -> %s: size differs", indent, tgt, ifchange.tgt)
166                         ood = true
167                         OODCache[ifchange.tgt.rel] = ood
168                         goto Done
169                 }
170                 if InodeTrust != InodeTrustNone && inode.Equals(ifchange.Inode()) {
171                         tracef(CDebug, "ood: %s%s -> %s: same inode", indent, tgt, ifchange.tgt)
172                 } else {
173                         tracef(CDebug, "ood: %s%s -> %s: inode differs", indent, tgt, ifchange.tgt)
174                         fd, err := os.Open(ifchange.tgt.a)
175                         if err != nil {
176                                 return ood, TgtError{tgt, ErrLine(err)}
177                         }
178                         hsh, err := fileHash(fd)
179                         fd.Close()
180                         if err != nil {
181                                 return ood, TgtError{tgt, ErrLine(err)}
182                         }
183                         if ifchange.Hash() != hsh {
184                                 tracef(CDebug, "ood: %s%s -> %s: hash differs", indent, tgt, ifchange.tgt)
185                                 ood = true
186                                 OODCache[ifchange.tgt.rel] = ood
187                                 goto Done
188                         }
189                         tracef(CDebug, "ood: %s%s -> %s: same hash", indent, tgt, ifchange.tgt)
190                 }
191
192                 if ifchange.tgt.rel == tgt.rel {
193                         tracef(CDebug, "ood: %s%s -> %s: same target", indent, tgt, ifchange.tgt)
194                         continue
195                 }
196                 if isSrc(ifchange.tgt) {
197                         tracef(CDebug, "ood: %s%s -> %s: is source", indent, tgt, ifchange.tgt)
198                         OODCache[ifchange.tgt.rel] = false
199                         continue
200                 }
201
202                 if _, ok := seen[ifchange.tgt.rel]; ok {
203                         tracef(CDebug, "ood: %s%s -> %s: was always built", indent, tgt, ifchange.tgt)
204                         OODCache[ifchange.tgt.rel] = false
205                         continue
206                 }
207
208                 depOOD, err := isOODWithTrace(ifchange.tgt, level+1, seen)
209                 if err != nil {
210                         return ood, TgtError{tgt, err}
211                 }
212                 if depOOD {
213                         tracef(CDebug, "ood: %s%s -> %s: ood", indent, tgt, ifchange.tgt)
214                         ood = true
215                         goto Done
216                 }
217                 tracef(CDebug, "ood: %s%s -> %s: !ood", indent, tgt, ifchange.tgt)
218         }
219
220 Done:
221         tracef(CDebug, "ood: %s%s: %v", indent, tgt, ood)
222         OODCache[tgt.rel] = ood
223         return ood, nil
224 }
225
226 func isOODWithTrace(tgt *Tgt, level int, seen map[string]*Tgt) (bool, error) {
227         _, ood := OODTgts[tgt.a]
228         var err error
229         if ood {
230                 if !isOODByBuildUUID(tgt) {
231                         tracef(CDebug, "ood: %s%s -> already built", strings.Repeat(". ", level), tgt)
232                         return false, nil
233                 }
234                 tracef(CDebug, "ood: %s%s true, external decision", strings.Repeat(". ", level), tgt)
235                 goto RecordOODTgt
236         }
237         ood, err = isOOD(tgt, level, seen)
238         if !ood {
239                 return ood, err
240         }
241 RecordOODTgt:
242         flock := unix.Flock_t{
243                 Type:   unix.F_WRLCK,
244                 Whence: io.SeekStart,
245         }
246         if err = unix.FcntlFlock(FdOODTgtsLock.Fd(), unix.F_SETLKW, &flock); err != nil {
247                 log.Fatal(err)
248         }
249         if _, err = FdOODTgts.Seek(0, io.SeekEnd); err != nil {
250                 log.Fatal(err)
251         }
252         if _, err := FdOODTgts.WriteString(tgt.a + "\x00"); err != nil {
253                 log.Fatal(err)
254         }
255         flock.Type = unix.F_UNLCK
256         if err = unix.FcntlFlock(FdOODTgtsLock.Fd(), unix.F_SETLK, &flock); err != nil {
257                 log.Fatal(err)
258         }
259         return true, nil
260 }
261
262 func oodTgtsClear() {
263         var err error
264         flock := unix.Flock_t{
265                 Type:   unix.F_WRLCK,
266                 Whence: io.SeekStart,
267         }
268         if err = unix.FcntlFlock(FdOODTgtsLock.Fd(), unix.F_SETLKW, &flock); err != nil {
269                 log.Fatal(err)
270         }
271         if err = FdOODTgts.Truncate(0); err != nil {
272                 log.Fatal(err)
273         }
274         flock.Type = unix.F_UNLCK
275         if err = unix.FcntlFlock(FdOODTgtsLock.Fd(), unix.F_SETLK, &flock); err != nil {
276                 log.Fatal(err)
277         }
278 }