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