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