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