2 goredo -- djb's redo implementation on pure Go
3 Copyright (C) 2020-2024 Sergey Matveev <stargrave@stargrave.org>
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.
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.
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/>.
30 "github.com/google/uuid"
31 "go.cypherpunks.ru/recfile"
34 var DepFixHashCache map[string]Hash
36 func recfileWrite(fdDep io.StringWriter, fields ...recfile.Field) error {
37 w := recfile.NewWriter(fdDep)
38 if _, err := w.RecordStart(); err != nil {
41 if _, err := w.WriteFields(fields...); err != nil {
47 func depFix(root string) error {
48 tracef(CDebug, "depfix: entering %s", root)
49 dir, err := os.Open(root)
55 entries, err := dir.ReadDir(1 << 10)
62 for _, entry := range entries {
64 if err = depFix(path.Join(root, entry.Name())); err != nil {
72 redoDir := path.Join(root, RedoDir)
73 dir, err = os.Open(redoDir)
75 if errors.Is(err, fs.ErrNotExist) {
82 entries, err := dir.ReadDir(1 << 10)
89 for _, entry := range entries {
90 switch path.Ext(entry.Name()) {
96 tracef(CDebug, "depfix: checking %s/%s", root, entry.Name())
97 fdDepPath := path.Join(redoDir, entry.Name())
98 data, err := os.ReadFile(fdDepPath)
102 fdDep, err := tempfile(redoDir, entry.Name())
106 defer os.Remove(fdDep.Name())
108 CDebug, "depfix: %s/%s: tmp %s",
109 root, entry.Name(), fdDep.Name(),
111 fdDepW := bufio.NewWriter(fdDep)
112 switch path.Ext(entry.Name()) {
114 if _, err = depParse(NewTgt(""), data); err != nil {
117 build, data, err := depHeadParse(data)
121 if err = depBuildWrite(fdDepW, build); err != nil {
127 typ, chunk, data, _ = chunkRead(data)
130 err = always(fdDepW, fdDep.Name())
132 p := mustAbs(path.Join(root,
133 strings.TrimSuffix(entry.Name(), DepSuffix)))
134 hsh, ok := DepFixHashCache[p]
141 hsh, err = fileHash(fd)
146 DepFixHashCache[p] = hsh
148 err = stamp(fdDepW, fdDep.Name(), hsh)
149 case DepTypeIfcreate:
150 err = ifcreate(fdDepW, fdDep.Name(), string(chunk))
151 case DepTypeIfchange:
152 name := string(chunk[InodeLen+HashLen:])
153 p := mustAbs(path.Join(root, name))
160 inode, _, err = inodeFromFileByFd(fd)
165 hsh, ok := DepFixHashCache[p]
167 hsh, err = fileHash(fd)
171 DepFixHashCache[p] = hsh
174 _, err = io.Copy(fdDepW, bytes.NewBuffer(
175 chunkWrite(bytes.Join([][]byte{
181 case DepTypeIfchangeNonex:
182 err = depWriteNonex(fdDepW, fdDep.Name(), string(chunk))
189 defer os.Remove(fdDepPath)
190 fdDepPath = fdDepPath[:len(fdDepPath)-4] + DepSuffix
191 r := recfile.NewReader(bytes.NewReader(data))
192 m, err := r.NextMap()
197 build, err = uuid.Parse(m["Build"])
201 if err = depBuildWrite(fdDepW, build); err != nil {
205 m, err := r.NextMap()
207 if errors.Is(err, io.EOF) {
214 err = always(fdDepW, m["Target"])
216 p := mustAbs(path.Join(root,
217 strings.TrimSuffix(entry.Name(), DepSuffix)))
218 hsh, ok := DepFixHashCache[p]
225 hsh, err = fileHash(fd)
230 DepFixHashCache[p] = hsh
232 err = stamp(fdDepW, fdDep.Name(), hsh)
234 err = ifcreate(fdDepW, fdDep.Name(), m["Target"])
237 err = depWriteNonex(fdDepW, fdDep.Name(), m["Target"])
240 name := string(m["Target"])
241 p := mustAbs(path.Join(root, name))
248 inode, _, err = inodeFromFileByFd(fd)
253 hsh, ok := DepFixHashCache[p]
255 hsh, err = fileHash(fd)
259 DepFixHashCache[p] = hsh
262 _, err = io.Copy(fdDepW, bytes.NewBuffer(
263 chunkWrite(bytes.Join([][]byte{
280 if err = fdDep.Sync(); err != nil {
285 if err = os.Rename(fdDep.Name(), fdDepPath); err != nil {
288 tracef(CRedo, "%s", fdDepPath)
291 if err = syncDir(redoDir); err != nil {