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