]> Cypherpunks.ru repositories - goredo.git/blob - ood.go
Always OOD unexistent targets
[goredo.git] / ood.go
1 /*
2 goredo -- djb's redo implementation on pure Go
3 Copyright (C) 2020-2021 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
51 type TgtErr struct {
52         Tgt string
53         Err error
54 }
55
56 func (e TgtErr) Unwrap() error { return e.Err }
57
58 func (e TgtErr) Error() string {
59         return fmt.Sprintf("%s: %s", e.Tgt, e.Err)
60 }
61
62 func cwdMustRel(paths ...string) string {
63         rel, err := filepath.Rel(Cwd, path.Join(paths...))
64         if err != nil {
65                 panic(err)
66         }
67         return rel
68 }
69
70 func cwdAndTgt(tgt string) (string, string) {
71         cwd, tgt := path.Split(tgt)
72         cwd, err := filepath.Abs(cwd)
73         if err != nil {
74                 panic(err)
75         }
76         return cwd, tgt
77 }
78
79 func isSrc(cwd, tgt string) bool {
80         d, f := path.Split(path.Join(cwd, tgt))
81         if _, err := os.Stat(path.Join(d, f)); err != nil {
82                 return false
83         }
84         if _, err := os.Stat(path.Join(d, f+".do")); err == nil {
85                 return false
86         }
87         if _, err := os.Stat(path.Join(d, RedoDir, f+DepSuffix)); err == nil {
88                 return false
89         }
90         return true
91 }
92
93 func isOOD(cwd, tgtOrig string, level int, seen map[string]struct{}) (bool, error) {
94         indent := strings.Repeat(". ", level)
95         trace(CDebug, "ood: %s%s checking", indent, tgtOrig)
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                 trace(CDebug, "ood: %s%s -> no dep: %s", indent, tgtOrig, depPath)
101                 return true, nil
102         }
103         depInfo, err := depRead(fdDep)
104         fdDep.Close()
105         if err != nil {
106                 return true, TgtErr{tgtOrig, err}
107         }
108
109         if depInfo.build == BuildUUID {
110                 trace(CDebug, "ood: %s%s -> already built", indent, tgtOrig)
111                 return false, nil
112         }
113         if _, err := os.Stat(path.Join(cwd, tgt)); err != nil && os.IsNotExist(err) {
114                 trace(CDebug, "ood: %s%s -> non-existent", indent, tgtOrig)
115                 return true, nil
116         }
117         ood := false
118
119         for _, dep := range depInfo.ifcreates {
120                 if _, err := os.Stat(path.Join(cwd, dep)); err == nil {
121                         trace(CDebug, "ood: %s%s -> %s created", indent, tgtOrig, dep)
122                         ood = true
123                         goto Done
124                 }
125         }
126
127         for _, m := range depInfo.ifchanges {
128                 dep := m["Target"]
129                 if dep == "" {
130                         return ood, TgtErr{tgtOrig, errors.New("invalid format of .rec: missing Target")}
131                 }
132                 theirInode, err := inodeFromRec(m)
133                 if err != nil {
134                         return ood, TgtErr{tgtOrig, fmt.Errorf("invalid format of .rec: %w", err)}
135                 }
136                 theirHsh := m["Hash"]
137                 trace(CDebug, "ood: %s%s -> %s: checking", indent, tgtOrig, dep)
138
139                 fd, err := os.Open(path.Join(cwd, dep))
140                 if err != nil {
141                         if os.IsNotExist(err) {
142                                 trace(CDebug, "ood: %s%s -> %s: not exists", indent, tgtOrig, dep)
143                                 ood = true
144                                 goto Done
145                         }
146                         return ood, TgtErr{tgtOrig, err}
147                 }
148                 defer fd.Close()
149
150                 inode, err := inodeFromFile(fd)
151                 if err != nil {
152                         return ood, TgtErr{tgtOrig, err}
153                 }
154                 if inode.Size != theirInode.Size {
155                         trace(CDebug, "ood: %s%s -> %s: size differs", indent, tgtOrig, dep)
156                         ood = true
157                         goto Done
158                 }
159                 if InodeTrust && inode.Equals(theirInode) {
160                         trace(CDebug, "ood: %s%s -> %s: same inode", indent, tgtOrig, dep)
161                 } else {
162                         trace(CDebug, "ood: %s%s -> %s: inode differs", indent, tgtOrig, dep)
163                         hsh, err := fileHash(fd)
164                         if err != nil {
165                                 return ood, TgtErr{tgtOrig, err}
166                         }
167                         if theirHsh != hsh {
168                                 trace(CDebug, "ood: %s%s -> %s: hash differs", indent, tgtOrig, dep)
169                                 ood = true
170                                 goto Done
171                         }
172                         trace(CDebug, "ood: %s%s -> %s: same hash", indent, tgtOrig, dep)
173                 }
174                 fd.Close() // optimization not to hold it for long
175
176                 if dep == tgt {
177                         trace(CDebug, "ood: %s%s -> %s: same target", indent, tgtOrig, dep)
178                         continue
179                 }
180                 if isSrc(cwd, dep) {
181                         trace(CDebug, "ood: %s%s -> %s: is source", indent, tgtOrig, dep)
182                         continue
183                 }
184
185                 if _, ok := seen[cwdMustRel(cwd, dep)]; ok {
186                         trace(CDebug, "ood: %s%s -> %s: was always built", indent, tgtOrig, dep)
187                         continue
188                 }
189
190                 depOod, err := isOODWithTrace(cwd, dep, level+1, seen)
191                 if err != nil {
192                         return ood, TgtErr{tgtOrig, err}
193                 }
194                 if depOod {
195                         trace(CDebug, "ood: %s%s -> %s: ood", indent, tgtOrig, dep)
196                         ood = true
197                         goto Done
198                 }
199                 trace(CDebug, "ood: %s%s -> %s: !ood", indent, tgtOrig, dep)
200         }
201
202 Done:
203         trace(CDebug, "ood: %s%s: %v", indent, tgtOrig, ood)
204         return ood, nil
205 }
206
207 func isOODWithTrace(
208         cwd, tgtOrig string,
209         level int,
210         seen map[string]struct{},
211 ) (bool, error) {
212         p, err := filepath.Abs(path.Join(cwd, tgtOrig))
213         if err != nil {
214                 panic(err)
215         }
216         _, ood := OODTgts[p]
217         if ood {
218                 trace(
219                         CDebug,
220                         "ood: %s%s true, external decision",
221                         strings.Repeat(". ", level), tgtOrig,
222                 )
223                 goto RecordOODTgt
224         }
225         ood, err = isOOD(cwd, tgtOrig, level, seen)
226         if !ood {
227                 return ood, err
228         }
229 RecordOODTgt:
230         if err = unix.Flock(int(FdOODTgtsLock.Fd()), unix.LOCK_EX); err != nil {
231                 log.Fatalln(err)
232         }
233         if _, err = FdOODTgts.Seek(0, io.SeekEnd); err != nil {
234                 log.Fatalln(err)
235         }
236         if _, err := FdOODTgts.WriteString(p + "\x00"); err != nil {
237                 log.Fatalln(err)
238         }
239         unix.Flock(int(FdOODTgtsLock.Fd()), unix.LOCK_UN)
240         return true, nil
241 }
242
243 func oodTgtsClear() {
244         if err := unix.Flock(int(FdOODTgtsLock.Fd()), unix.LOCK_EX); err != nil {
245                 log.Fatalln(err)
246         }
247         if err := FdOODTgts.Truncate(0); err != nil {
248                 log.Fatalln(err)
249         }
250         unix.Flock(int(FdOODTgtsLock.Fd()), unix.LOCK_UN)
251 }