1 // goredo -- djb's redo implementation on pure Go
2 // Copyright (C) 2020-2024 Sergey Matveev <stargrave@stargrave.org>
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, version 3 of the License.
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License
14 // along with this program. If not, see <http://www.gnu.org/licenses/>.
16 // Out-of-date determination
30 "golang.org/x/sys/unix"
34 EnvOODTgtsFd = "REDO_OOD_TGTS_FD"
35 EnvOODTgtsLockFd = "REDO_OOD_TGTS_LOCK_FD"
39 OODTgts map[string]struct{}
41 FdOODTgtsLock *os.File
43 OODCache = make(map[string]bool)
44 FileExistsCache = make(map[string]bool)
47 func FileExists(p string) bool {
48 if exists, known := FileExistsCache[p]; known {
53 FileExistsCache[p] = true
56 if errors.Is(err, fs.ErrNotExist) {
57 FileExistsCache[p] = false
62 type TgtError struct {
67 func (e TgtError) Unwrap() error { return e.Err }
69 func (e TgtError) Error() string {
70 return fmt.Sprintf("%s: %s", e.Tgt, e.Err)
73 func isSrc(tgt *Tgt) bool {
74 if !FileExists(tgt.a) {
77 if FileExists(tgt.a + ".do") {
80 if FileExists(tgt.dep) {
86 func isOODByBuildUUID(tgt *Tgt) bool {
87 build, err := depBuildRead(tgt.dep)
88 return err != nil || build != BuildUUID
91 func isOOD(tgt *Tgt, level int, seen map[string]*Tgt) (bool, error) {
92 indent := strings.Repeat(". ", level)
93 tracef(CDebug, "ood: %s%s checking", indent, tgt)
94 ood, cached := OODCache[tgt.rel]
96 tracef(CDebug, "ood: %s%s -> cached: %v", indent, tgt, ood)
99 dep := DepCache[tgt.rel]
102 dep, err = depRead(tgt)
104 if errors.Is(err, fs.ErrNotExist) {
107 tracef(CDebug, "ood: %s%s -> is source", indent, tgt)
110 tracef(CDebug, "ood: %s%s -> no dep: %s", indent, tgt, tgt.dep)
112 OODCache[tgt.rel] = ood
116 return true, TgtError{tgt, ErrLine(err)}
119 DepCache[tgt.rel] = dep
122 if dep.build == BuildUUID {
123 tracef(CDebug, "ood: %s%s -> already built", indent, tgt)
124 OODCache[tgt.rel] = false
127 if !FileExists(tgt.a) {
128 tracef(CDebug, "ood: %s%s -> non-existent", indent, tgt)
129 OODCache[tgt.rel] = true
133 for _, ifcreate := range dep.ifcreates {
134 if FileExists(ifcreate.a) {
135 tracef(CDebug, "ood: %s%s -> %s created", indent, tgt, ifcreate)
141 for _, ifchange := range dep.ifchanges {
142 tracef(CDebug, "ood: %s%s -> %s: checking", indent, tgt, ifchange.tgt)
143 ood, cached = OODCache[ifchange.tgt.rel]
145 tracef(CDebug, "ood: %s%s -> %s: cached: %v", indent, tgt, ifchange.tgt, ood)
152 inode, err := inodeFromFileByPath(ifchange.tgt.a)
154 if errors.Is(err, fs.ErrNotExist) {
155 tracef(CDebug, "ood: %s%s -> %s: not exists", indent, tgt, ifchange.tgt)
157 OODCache[ifchange.tgt.rel] = ood
160 return ood, TgtError{tgt, ErrLine(err)}
163 if !bytes.Equal(inode[:8], ifchange.Inode()[:8]) {
164 tracef(CDebug, "ood: %s%s -> %s: size differs", indent, tgt, ifchange.tgt)
166 OODCache[ifchange.tgt.rel] = ood
169 if InodeTrust != InodeTrustNone && inode.Equals(ifchange.Inode()) {
170 tracef(CDebug, "ood: %s%s -> %s: same inode", indent, tgt, ifchange.tgt)
172 tracef(CDebug, "ood: %s%s -> %s: inode differs", indent, tgt, ifchange.tgt)
173 fd, err := os.Open(ifchange.tgt.a)
175 return ood, TgtError{tgt, ErrLine(err)}
177 hsh, err := fileHash(fd)
180 return ood, TgtError{tgt, ErrLine(err)}
182 if ifchange.Hash() != hsh {
183 tracef(CDebug, "ood: %s%s -> %s: hash differs", indent, tgt, ifchange.tgt)
185 OODCache[ifchange.tgt.rel] = ood
188 tracef(CDebug, "ood: %s%s -> %s: same hash", indent, tgt, ifchange.tgt)
191 if ifchange.tgt.rel == tgt.rel {
192 tracef(CDebug, "ood: %s%s -> %s: same target", indent, tgt, ifchange.tgt)
195 if isSrc(ifchange.tgt) {
196 tracef(CDebug, "ood: %s%s -> %s: is source", indent, tgt, ifchange.tgt)
197 OODCache[ifchange.tgt.rel] = false
201 if _, ok := seen[ifchange.tgt.rel]; ok {
202 tracef(CDebug, "ood: %s%s -> %s: was always built", indent, tgt, ifchange.tgt)
203 OODCache[ifchange.tgt.rel] = false
207 depOOD, err := isOODWithTrace(ifchange.tgt, level+1, seen)
209 return ood, TgtError{tgt, err}
212 tracef(CDebug, "ood: %s%s -> %s: ood", indent, tgt, ifchange.tgt)
216 tracef(CDebug, "ood: %s%s -> %s: !ood", indent, tgt, ifchange.tgt)
220 tracef(CDebug, "ood: %s%s: %v", indent, tgt, ood)
221 OODCache[tgt.rel] = ood
225 func isOODWithTrace(tgt *Tgt, level int, seen map[string]*Tgt) (bool, error) {
226 _, ood := OODTgts[tgt.a]
229 if !isOODByBuildUUID(tgt) {
230 tracef(CDebug, "ood: %s%s -> already built", strings.Repeat(". ", level), tgt)
233 tracef(CDebug, "ood: %s%s true, external decision", strings.Repeat(". ", level), tgt)
236 ood, err = isOOD(tgt, level, seen)
241 flock := unix.Flock_t{
243 Whence: io.SeekStart,
245 if err = unix.FcntlFlock(FdOODTgtsLock.Fd(), unix.F_SETLKW, &flock); err != nil {
248 if _, err = FdOODTgts.Seek(0, io.SeekEnd); err != nil {
251 if _, err := FdOODTgts.WriteString(tgt.a + "\x00"); err != nil {
254 flock.Type = unix.F_UNLCK
255 if err = unix.FcntlFlock(FdOODTgtsLock.Fd(), unix.F_SETLK, &flock); err != nil {
261 func oodTgtsClear() {
263 flock := unix.Flock_t{
265 Whence: io.SeekStart,
267 if err = unix.FcntlFlock(FdOODTgtsLock.Fd(), unix.F_SETLKW, &flock); err != nil {
270 if err = FdOODTgts.Truncate(0); err != nil {
273 flock.Type = unix.F_UNLCK
274 if err = unix.FcntlFlock(FdOODTgtsLock.Fd(), unix.F_SETLK, &flock); err != nil {