2 goredo -- djb's redo implementation on pure Go
3 Copyright (C) 2020-2023 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 "go.cypherpunks.ru/recfile"
33 func depFix(root string) error {
34 tracef(CDebug, "depfix: entering %s", root)
35 dir, err := os.Open(root)
41 entries, err := dir.ReadDir(1 << 10)
48 for _, entry := range entries {
50 if err = depFix(path.Join(root, entry.Name())); err != nil {
58 redoDir := path.Join(root, RedoDir)
59 dir, err = os.Open(redoDir)
61 if errors.Is(err, fs.ErrNotExist) {
67 redoDirChanged := false
69 entries, err := dir.ReadDir(1 << 10)
76 for _, entry := range entries {
77 if !strings.HasSuffix(entry.Name(), DepSuffix) {
80 tracef(CDebug, "depfix: checking %s/%s", root, entry.Name())
81 fdDepPath := path.Join(redoDir, entry.Name())
82 fdDep, err := os.Open(fdDepPath)
87 r := recfile.NewReader(fdDep)
88 var fieldses [][]recfile.Field
91 fields, err := r.Next()
93 if errors.Is(err, io.EOF) {
98 fieldses = append(fieldses, fields)
99 m := make(map[string]string, len(fields))
100 for _, f := range fields {
103 if m["Type"] != DepTypeIfchange {
108 return ErrMissingTarget
110 tracef(CDebug, "depfix: checking %s/%s -> %s", root, entry.Name(), dep)
111 theirInode, err := inodeFromRec(m)
115 theirHsh := mustHexDecode(m["Hash"])
116 fd, err := os.Open(path.Join(root, dep))
118 if errors.Is(err, fs.ErrNotExist) {
120 CDebug, "depfix: %s/%s -> %s: not exists",
121 root, entry.Name(), dep,
127 inode, _, err := inodeFromFileByFd(fd)
132 if inode.Size != theirInode.Size {
134 CDebug, "depfix: %s/%s -> %s: size differs",
135 root, entry.Name(), dep,
140 if inode.Equals(theirInode) {
142 CDebug, "depfix: %s/%s -> %s: inode is equal",
143 root, entry.Name(), dep,
148 hsh, err := fileHash(fd)
153 if !bytes.Equal(hsh, theirHsh) {
155 CDebug, "depfix: %s/%s -> %s: hash differs",
156 root, entry.Name(), dep,
160 fields = []recfile.Field{
161 {Name: "Type", Value: DepTypeIfchange},
162 {Name: "Target", Value: dep},
163 {Name: "Hash", Value: hex.EncodeToString(hsh)},
165 fields = append(fields, inode.RecfileFields()...)
166 fieldses[len(fieldses)-1] = fields
168 CDebug, "depfix: %s/%s -> %s: inode updated",
169 root, entry.Name(), dep,
177 redoDirChanged = true
178 fdDep, err = tempfile(redoDir, entry.Name())
184 CDebug, "depfix: %s/%s: tmp %s",
185 root, entry.Name(), fdDep.Name(),
187 w := recfile.NewWriter(fdDep)
188 if _, err := w.WriteFields(fieldses[0]...); err != nil {
191 fieldses = fieldses[1:]
192 for _, fields := range fieldses {
193 if _, err := w.RecordStart(); err != nil {
196 if _, err := w.WriteFields(fields...); err != nil {
201 if err = fdDep.Sync(); err != nil {
206 if err = os.Rename(fdDep.Name(), fdDepPath); err != nil {
209 tracef(CRedo, "%s", fdDepPath)
212 if redoDirChanged && !NoSync {
213 if err = syncDir(redoDir); err != nil {