]> Cypherpunks.ru repositories - goredo.git/blob - ood.go
Use more shorter Cwd-relative paths
[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         DepTypeIfcreate = "ifcreate"
36         DepTypeIfchange = "ifchange"
37         DepTypeAlways   = "always"
38         DepTypeStamp    = "stamp"
39
40         EnvOODTgtsFd     = "REDO_OOD_TGTS_FD"
41         EnvOODTgtsLockFd = "REDO_OOD_TGTS_LOCK_FD"
42 )
43
44 var (
45         OODTgts       map[string]struct{}
46         FdOODTgts     *os.File
47         FdOODTgtsLock *os.File
48
49         OODCache        = make(map[string]bool)
50         FileExistsCache = make(map[string]bool)
51         DepInfoCache    = make(map[string]*DepInfo)
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.rel]
104         if cached {
105                 tracef(CDebug, "ood: %s%s -> cached: %v", indent, tgt, ood)
106                 return ood, nil
107         }
108         depInfo := DepInfoCache[tgt.rel]
109         var err error
110         if depInfo == nil {
111                 depInfo, err = depRead(tgt)
112                 if err != nil {
113                         if errors.Is(err, fs.ErrNotExist) {
114                                 if isSrc(tgt) {
115                                         ood = false
116                                         tracef(CDebug, "ood: %s%s -> is source", indent, tgt)
117                                 } else {
118                                         ood = true
119                                         tracef(CDebug, "ood: %s%s -> no dep: %s", indent, tgt, tgt.Dep())
120                                 }
121                                 OODCache[tgt.rel] = ood
122                                 return ood, nil
123                         }
124                         if err != nil {
125                                 return true, TgtError{tgt, ErrLine(err)}
126                         }
127                 }
128                 if DepInfoCache != nil {
129                         DepInfoCache[tgt.rel] = depInfo
130                 }
131         }
132
133         if depInfo.build == BuildUUID {
134                 tracef(CDebug, "ood: %s%s -> already built", indent, tgt)
135                 OODCache[tgt.rel] = false
136                 return false, nil
137         }
138         if !FileExists(tgt.a) {
139                 tracef(CDebug, "ood: %s%s -> non-existent", indent, tgt)
140                 OODCache[tgt.rel] = true
141                 return true, nil
142         }
143
144         for _, dep := range depInfo.ifcreates {
145                 if FileExists(dep.a) {
146                         tracef(CDebug, "ood: %s%s -> %s created", indent, tgt, dep)
147                         ood = true
148                         goto Done
149                 }
150         }
151
152         for _, dep := range depInfo.ifchanges {
153                 tracef(CDebug, "ood: %s%s -> %s: checking", indent, tgt, dep.tgt)
154                 ood, cached = OODCache[dep.tgt.rel]
155                 if cached {
156                         tracef(CDebug, "ood: %s%s -> %s: cached: %v", indent, tgt, dep.tgt, ood)
157                         if ood {
158                                 goto Done
159                         }
160                         continue
161                 }
162
163                 inode, err := inodeFromFileByPath(dep.tgt.a)
164                 if err != nil {
165                         if errors.Is(err, fs.ErrNotExist) {
166                                 tracef(CDebug, "ood: %s%s -> %s: not exists", indent, tgt, dep.tgt)
167                                 ood = true
168                                 OODCache[dep.tgt.rel] = ood
169                                 goto Done
170                         }
171                         return ood, TgtError{tgt, ErrLine(err)}
172                 }
173
174                 if inode.Size != dep.inode.Size {
175                         tracef(CDebug, "ood: %s%s -> %s: size differs", indent, tgt, dep.tgt)
176                         ood = true
177                         OODCache[dep.tgt.rel] = ood
178                         goto Done
179                 }
180                 if InodeTrust != InodeTrustNone && inode.Equals(dep.inode) {
181                         tracef(CDebug, "ood: %s%s -> %s: same inode", indent, tgt, dep.tgt)
182                 } else {
183                         tracef(CDebug, "ood: %s%s -> %s: inode differs", indent, tgt, dep.tgt)
184                         fd, err := os.Open(dep.tgt.a)
185                         if err != nil {
186                                 return ood, TgtError{tgt, ErrLine(err)}
187                         }
188                         hsh, err := fileHash(fd)
189                         fd.Close()
190                         if err != nil {
191                                 return ood, TgtError{tgt, ErrLine(err)}
192                         }
193                         if dep.hash != hsh {
194                                 tracef(CDebug, "ood: %s%s -> %s: hash differs", indent, tgt, dep.tgt)
195                                 ood = true
196                                 OODCache[dep.tgt.rel] = ood
197                                 goto Done
198                         }
199                         tracef(CDebug, "ood: %s%s -> %s: same hash", indent, tgt, dep.tgt)
200                 }
201
202                 if dep.tgt.rel == tgt.rel {
203                         tracef(CDebug, "ood: %s%s -> %s: same target", indent, tgt, dep.tgt)
204                         continue
205                 }
206                 if isSrc(dep.tgt) {
207                         tracef(CDebug, "ood: %s%s -> %s: is source", indent, tgt, dep.tgt)
208                         OODCache[dep.tgt.rel] = false
209                         continue
210                 }
211
212                 if _, ok := seen[dep.tgt.rel]; ok {
213                         tracef(CDebug, "ood: %s%s -> %s: was always built", indent, tgt, dep.tgt)
214                         OODCache[dep.tgt.rel] = false
215                         continue
216                 }
217
218                 depOOD, err := isOODWithTrace(dep.tgt, level+1, seen)
219                 if err != nil {
220                         return ood, TgtError{tgt, err}
221                 }
222                 if depOOD {
223                         tracef(CDebug, "ood: %s%s -> %s: ood", indent, tgt, dep.tgt)
224                         ood = true
225                         goto Done
226                 }
227                 tracef(CDebug, "ood: %s%s -> %s: !ood", indent, tgt, dep.tgt)
228         }
229
230 Done:
231         tracef(CDebug, "ood: %s%s: %v", indent, tgt, ood)
232         OODCache[tgt.rel] = ood
233         return ood, nil
234 }
235
236 func isOODWithTrace(tgt *Tgt, level int, seen map[string]*Tgt) (bool, error) {
237         _, ood := OODTgts[tgt.a]
238         var err error
239         if ood {
240                 if !isOODByBuildUUID(tgt) {
241                         tracef(CDebug, "ood: %s%s -> already built", strings.Repeat(". ", level), tgt)
242                         return false, nil
243                 }
244                 tracef(CDebug, "ood: %s%s true, external decision", strings.Repeat(". ", level), tgt)
245                 goto RecordOODTgt
246         }
247         ood, err = isOOD(tgt, level, seen)
248         if !ood {
249                 return ood, err
250         }
251 RecordOODTgt:
252         flock := unix.Flock_t{
253                 Type:   unix.F_WRLCK,
254                 Whence: io.SeekStart,
255         }
256         if err = unix.FcntlFlock(FdOODTgtsLock.Fd(), unix.F_SETLKW, &flock); err != nil {
257                 log.Fatal(err)
258         }
259         if _, err = FdOODTgts.Seek(0, io.SeekEnd); err != nil {
260                 log.Fatal(err)
261         }
262         if _, err := FdOODTgts.WriteString(tgt.a + "\x00"); err != nil {
263                 log.Fatal(err)
264         }
265         flock.Type = unix.F_UNLCK
266         if err = unix.FcntlFlock(FdOODTgtsLock.Fd(), unix.F_SETLK, &flock); err != nil {
267                 log.Fatal(err)
268         }
269         return true, nil
270 }
271
272 func oodTgtsClear() {
273         var err error
274         flock := unix.Flock_t{
275                 Type:   unix.F_WRLCK,
276                 Whence: io.SeekStart,
277         }
278         if err = unix.FcntlFlock(FdOODTgtsLock.Fd(), unix.F_SETLKW, &flock); err != nil {
279                 log.Fatal(err)
280         }
281         if err = FdOODTgts.Truncate(0); err != nil {
282                 log.Fatal(err)
283         }
284         flock.Type = unix.F_UNLCK
285         if err = unix.FcntlFlock(FdOODTgtsLock.Fd(), unix.F_SETLK, &flock); err != nil {
286                 log.Fatal(err)
287         }
288 }