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/>.
28 "github.com/google/uuid"
29 "go.cypherpunks.ru/recfile"
32 var DepFixHashCache map[string]Hash
34 func recfileWrite(fdDep io.StringWriter, fields ...recfile.Field) error {
35 w := recfile.NewWriter(fdDep)
36 if _, err := w.RecordStart(); err != nil {
39 if _, err := w.WriteFields(fields...); err != nil {
45 func depFix(root string) error {
46 tracef(CDebug, "depfix: entering %s", root)
47 dir, err := os.Open(root)
53 entries, err := dir.ReadDir(1 << 10)
60 for _, entry := range entries {
62 if err = depFix(path.Join(root, entry.Name())); err != nil {
70 redoDir := path.Join(root, RedoDir)
71 dir, err = os.Open(redoDir)
73 if errors.Is(err, fs.ErrNotExist) {
80 entries, err := dir.ReadDir(1 << 10)
87 for _, entry := range entries {
88 switch path.Ext(entry.Name()) {
94 tracef(CDebug, "depfix: checking %s/%s", root, entry.Name())
95 fdDepPath := path.Join(redoDir, entry.Name())
96 data, err := os.ReadFile(fdDepPath)
100 fdDep, err := tempfile(redoDir, entry.Name())
104 defer os.Remove(fdDep.Name())
106 CDebug, "depfix: %s/%s: tmp %s",
107 root, entry.Name(), fdDep.Name(),
109 fdDepW := bufio.NewWriter(fdDep)
110 switch path.Ext(entry.Name()) {
112 if _, err = depParse(NewTgt(""), data); err != nil {
115 build, data, err := depHeadParse(data)
119 if err = depBuildWrite(fdDepW, build); err != nil {
125 typ, chunk, data, _ = chunkRead(data)
128 err = always(fdDepW, fdDep.Name())
130 p := mustAbs(path.Join(root,
131 strings.TrimSuffix(entry.Name(), DepSuffix)))
132 hsh, ok := DepFixHashCache[p]
139 hsh, err = fileHash(fd)
144 DepFixHashCache[p] = hsh
146 err = stamp(fdDepW, fdDep.Name(), hsh)
147 case DepTypeIfcreate:
148 err = ifcreate(fdDepW, fdDep.Name(), string(chunk))
149 case DepTypeIfchange:
150 name := string(chunk[InodeLen+HashLen:])
151 p := mustAbs(path.Join(root, name))
158 inode, _, err = inodeFromFileByFd(fd)
163 hsh, ok := DepFixHashCache[p]
165 hsh, err = fileHash(fd)
169 DepFixHashCache[p] = hsh
172 _, err = io.Copy(fdDepW, bytes.NewBuffer(
173 chunkWrite(bytes.Join([][]byte{
179 case DepTypeIfchangeNonex:
180 err = depWriteNonex(fdDepW, fdDep.Name(), string(chunk))
187 defer os.Remove(fdDepPath)
188 fdDepPath = fdDepPath[:len(fdDepPath)-4] + DepSuffix
189 r := recfile.NewReader(bytes.NewReader(data))
190 m, err := r.NextMap()
195 build, err = uuid.Parse(m["Build"])
199 if err = depBuildWrite(fdDepW, build); err != nil {
203 m, err := r.NextMap()
205 if errors.Is(err, io.EOF) {
212 err = always(fdDepW, m["Target"])
214 p := mustAbs(path.Join(root,
215 strings.TrimSuffix(entry.Name(), DepSuffix)))
216 hsh, ok := DepFixHashCache[p]
223 hsh, err = fileHash(fd)
228 DepFixHashCache[p] = hsh
230 err = stamp(fdDepW, fdDep.Name(), hsh)
232 err = ifcreate(fdDepW, fdDep.Name(), m["Target"])
235 err = depWriteNonex(fdDepW, fdDep.Name(), m["Target"])
238 name := string(m["Target"])
239 p := mustAbs(path.Join(root, name))
246 inode, _, err = inodeFromFileByFd(fd)
251 hsh, ok := DepFixHashCache[p]
253 hsh, err = fileHash(fd)
257 DepFixHashCache[p] = hsh
260 _, err = io.Copy(fdDepW, bytes.NewBuffer(
261 chunkWrite(bytes.Join([][]byte{
278 if err = fdDep.Sync(); err != nil {
283 if err = os.Rename(fdDep.Name(), fdDepPath); err != nil {
286 tracef(CRedo, "%s", fdDepPath)
289 if err = syncDir(redoDir); err != nil {