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