]> Cypherpunks.ru repositories - goredo.git/blob - ood.go
DRY filepath.Abs/Rel
[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         "log"
27         "os"
28         "path"
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        map[string]bool = make(map[string]bool)
50         FileExistsCache map[string]bool = make(map[string]bool)
51
52         ErrMissingTarget = errors.New("invalid format of .rec: missing Target")
53 )
54
55 func FileExists(p string) bool {
56         if exists, known := FileExistsCache[p]; known {
57                 return exists
58         }
59         _, err := os.Stat(p)
60         if err == nil {
61                 FileExistsCache[p] = true
62                 return true
63         }
64         if os.IsNotExist(err) {
65                 FileExistsCache[p] = false
66         }
67         return false
68 }
69
70 type TgtError struct {
71         Tgt string
72         Err error
73 }
74
75 func (e TgtError) Unwrap() error { return e.Err }
76
77 func (e TgtError) Error() string {
78         return fmt.Sprintf("%s: %s", e.Tgt, e.Err)
79 }
80
81 func isSrc(cwd, tgt string) bool {
82         d, f := path.Split(path.Join(cwd, tgt))
83         if !FileExists(path.Join(d, f)) {
84                 return false
85         }
86         if FileExists(path.Join(d, f+".do")) {
87                 return false
88         }
89         if FileExists(path.Join(d, RedoDir, f+DepSuffix)) {
90                 return false
91         }
92         return true
93 }
94
95 func isOODByBuildUUID(cwd, tgtOrig string) bool {
96         cwd, tgt := cwdAndTgt(path.Join(cwd, tgtOrig))
97         depPath := path.Join(cwd, RedoDir, tgt+DepSuffix)
98         fdDep, err := os.Open(depPath)
99         if err != nil {
100                 return true
101         }
102         depInfo, err := depRead(fdDep)
103         fdDep.Close()
104         if err != nil || depInfo.build != BuildUUID {
105                 return true
106         }
107         return false
108 }
109
110 func isOOD(cwd, tgtOrig string, level int, seen map[string]struct{}) (bool, error) {
111         indent := strings.Repeat(". ", level)
112         tracef(CDebug, "ood: %s%s checking", indent, tgtOrig)
113         cwd, tgt := cwdAndTgt(path.Join(cwd, tgtOrig))
114         ood, cached := OODCache[path.Join(cwd, tgt)]
115         if cached {
116                 tracef(CDebug, "ood: %s%s -> cached: %v", indent, tgtOrig, ood)
117                 return ood, nil
118         }
119         depPath := path.Join(cwd, RedoDir, tgt+DepSuffix)
120         fdDep, err := os.Open(depPath)
121         if err != nil {
122                 if isSrc(cwd, tgt) {
123                         ood = false
124                         tracef(CDebug, "ood: %s%s -> is source", indent, tgtOrig)
125                 } else {
126                         ood = true
127                         tracef(CDebug, "ood: %s%s -> no dep: %s", indent, tgtOrig, depPath)
128                 }
129                 OODCache[path.Join(cwd, tgt)] = ood
130                 return ood, nil
131         }
132         depInfo, err := depRead(fdDep)
133         fdDep.Close()
134         if err != nil {
135                 return true, TgtError{tgtOrig, ErrLine(err)}
136         }
137
138         if depInfo.build == BuildUUID {
139                 tracef(CDebug, "ood: %s%s -> already built", indent, tgtOrig)
140                 OODCache[path.Join(cwd, tgt)] = false
141                 return false, nil
142         }
143         if !FileExists(path.Join(cwd, tgt)) {
144                 tracef(CDebug, "ood: %s%s -> non-existent", indent, tgtOrig)
145                 OODCache[path.Join(cwd, tgt)] = true
146                 return true, nil
147         }
148
149         for _, dep := range depInfo.ifcreates {
150                 if FileExists(path.Join(cwd, dep)) {
151                         tracef(CDebug, "ood: %s%s -> %s created", indent, tgtOrig, dep)
152                         ood = true
153                         goto Done
154                 }
155         }
156
157         for _, m := range depInfo.ifchanges {
158                 dep := m["Target"]
159                 if dep == "" {
160                         return ood, TgtError{tgtOrig, ErrMissingTarget}
161                 }
162                 theirInode, err := inodeFromRec(m)
163                 if err != nil {
164                         return ood, TgtError{tgtOrig, fmt.Errorf("invalid format of .rec: %w", err)}
165                 }
166                 theirHsh := m["Hash"]
167                 tracef(CDebug, "ood: %s%s -> %s: checking", indent, tgtOrig, dep)
168                 ood, cached = OODCache[path.Join(cwd, dep)]
169                 if cached {
170                         tracef(CDebug, "ood: %s%s -> %s: cached: %v", indent, tgtOrig, dep, ood)
171                         if ood {
172                                 goto Done
173                         }
174                         continue
175                 }
176
177                 inode, err := inodeFromFileByPath(path.Join(cwd, dep))
178                 if err != nil {
179                         if os.IsNotExist(err) {
180                                 tracef(CDebug, "ood: %s%s -> %s: not exists", indent, tgtOrig, dep)
181                                 ood = true
182                                 OODCache[path.Join(cwd, dep)] = ood
183                                 goto Done
184                         }
185                         return ood, TgtError{tgtOrig, ErrLine(err)}
186                 }
187
188                 if inode.Size != theirInode.Size {
189                         tracef(CDebug, "ood: %s%s -> %s: size differs", indent, tgtOrig, dep)
190                         ood = true
191                         OODCache[path.Join(cwd, dep)] = ood
192                         goto Done
193                 }
194                 if InodeTrust != InodeTrustNone && inode.Equals(theirInode) {
195                         tracef(CDebug, "ood: %s%s -> %s: same inode", indent, tgtOrig, dep)
196                 } else {
197                         tracef(CDebug, "ood: %s%s -> %s: inode differs", indent, tgtOrig, dep)
198                         fd, err := os.Open(path.Join(cwd, dep))
199                         if err != nil {
200                                 return ood, TgtError{tgtOrig, ErrLine(err)}
201                         }
202                         hsh, err := fileHash(fd)
203                         fd.Close()
204                         if err != nil {
205                                 return ood, TgtError{tgtOrig, ErrLine(err)}
206                         }
207                         if theirHsh != hsh {
208                                 tracef(CDebug, "ood: %s%s -> %s: hash differs", indent, tgtOrig, dep)
209                                 ood = true
210                                 OODCache[path.Join(cwd, dep)] = ood
211                                 goto Done
212                         }
213                         tracef(CDebug, "ood: %s%s -> %s: same hash", indent, tgtOrig, dep)
214                 }
215
216                 if dep == tgt {
217                         tracef(CDebug, "ood: %s%s -> %s: same target", indent, tgtOrig, dep)
218                         continue
219                 }
220                 if isSrc(cwd, dep) {
221                         tracef(CDebug, "ood: %s%s -> %s: is source", indent, tgtOrig, dep)
222                         OODCache[path.Join(cwd, dep)] = false
223                         continue
224                 }
225
226                 if _, ok := seen[cwdMustRel(cwd, dep)]; ok {
227                         tracef(CDebug, "ood: %s%s -> %s: was always built", indent, tgtOrig, dep)
228                         OODCache[path.Join(cwd, dep)] = false
229                         continue
230                 }
231
232                 depOOD, err := isOODWithTrace(cwd, dep, level+1, seen)
233                 if err != nil {
234                         return ood, TgtError{tgtOrig, err}
235                 }
236                 if depOOD {
237                         tracef(CDebug, "ood: %s%s -> %s: ood", indent, tgtOrig, dep)
238                         ood = true
239                         goto Done
240                 }
241                 tracef(CDebug, "ood: %s%s -> %s: !ood", indent, tgtOrig, dep)
242         }
243
244 Done:
245         tracef(CDebug, "ood: %s%s: %v", indent, tgtOrig, ood)
246         OODCache[path.Join(cwd, tgt)] = ood
247         return ood, nil
248 }
249
250 func isOODWithTrace(
251         cwd, tgtOrig string,
252         level int,
253         seen map[string]struct{},
254 ) (bool, error) {
255         p := mustAbs(path.Join(cwd, tgtOrig))
256         _, ood := OODTgts[p]
257         var err error
258         if ood {
259                 if !isOODByBuildUUID(cwd, tgtOrig) {
260                         tracef(
261                                 CDebug,
262                                 "ood: %s%s -> already built",
263                                 strings.Repeat(". ", level), tgtOrig,
264                         )
265                         return false, nil
266                 }
267                 tracef(
268                         CDebug,
269                         "ood: %s%s true, external decision",
270                         strings.Repeat(". ", level), tgtOrig,
271                 )
272                 goto RecordOODTgt
273         }
274         ood, err = isOOD(cwd, tgtOrig, level, seen)
275         if !ood {
276                 return ood, err
277         }
278 RecordOODTgt:
279         flock := unix.Flock_t{
280                 Type:   unix.F_WRLCK,
281                 Whence: io.SeekStart,
282         }
283         if err = unix.FcntlFlock(FdOODTgtsLock.Fd(), unix.F_SETLKW, &flock); err != nil {
284                 log.Fatal(err)
285         }
286         if _, err = FdOODTgts.Seek(0, io.SeekEnd); err != nil {
287                 log.Fatal(err)
288         }
289         if _, err := FdOODTgts.WriteString(p + "\x00"); err != nil {
290                 log.Fatal(err)
291         }
292         flock.Type = unix.F_UNLCK
293         if err = unix.FcntlFlock(FdOODTgtsLock.Fd(), unix.F_SETLK, &flock); err != nil {
294                 log.Fatal(err)
295         }
296         return true, nil
297 }
298
299 func oodTgtsClear() {
300         var err error
301         flock := unix.Flock_t{
302                 Type:   unix.F_WRLCK,
303                 Whence: io.SeekStart,
304         }
305         if err = unix.FcntlFlock(FdOODTgtsLock.Fd(), unix.F_SETLKW, &flock); err != nil {
306                 log.Fatal(err)
307         }
308         if err = FdOODTgts.Truncate(0); err != nil {
309                 log.Fatal(err)
310         }
311         flock.Type = unix.F_UNLCK
312         if err = unix.FcntlFlock(FdOODTgtsLock.Fd(), unix.F_SETLK, &flock); err != nil {
313                 log.Fatal(err)
314         }
315 }