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