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