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/>.
29 "go.cypherpunks.ru/recfile"
32 func depFix(root string) error {
33 tracef(CDebug, "depfix: entering %s", root)
34 dir, err := os.Open(root)
40 entries, err := dir.ReadDir(1 << 10)
47 for _, entry := range entries {
49 if err = depFix(path.Join(root, entry.Name())); err != nil {
57 redoDir := path.Join(root, RedoDir)
58 dir, err = os.Open(redoDir)
60 if errors.Is(err, fs.ErrNotExist) {
66 redoDirChanged := false
68 entries, err := dir.ReadDir(1 << 10)
75 for _, entry := range entries {
76 if !strings.HasSuffix(entry.Name(), DepSuffix) {
79 tracef(CDebug, "depfix: checking %s/%s", root, entry.Name())
80 fdDepPath := path.Join(redoDir, entry.Name())
81 fdDep, err := os.Open(fdDepPath)
86 r := recfile.NewReader(fdDep)
87 var fieldses [][]recfile.Field
90 fields, err := r.Next()
92 if errors.Is(err, io.EOF) {
97 fieldses = append(fieldses, fields)
98 m := make(map[string]string, len(fields))
99 for _, f := range fields {
102 if m["Type"] != DepTypeIfchange {
107 return ErrMissingTarget
109 tracef(CDebug, "depfix: checking %s/%s -> %s", root, entry.Name(), dep)
110 theirInode, err := inodeFromRec(m)
114 theirHsh := mustHexDecode(m["Hash"])
115 fd, err := os.Open(path.Join(root, dep))
117 if errors.Is(err, fs.ErrNotExist) {
119 CDebug, "depfix: %s/%s -> %s: not exists",
120 root, entry.Name(), dep,
126 inode, _, err := inodeFromFileByFd(fd)
131 if inode.Size != theirInode.Size {
133 CDebug, "depfix: %s/%s -> %s: size differs",
134 root, entry.Name(), dep,
139 if inode.Equals(theirInode) {
141 CDebug, "depfix: %s/%s -> %s: inode is equal",
142 root, entry.Name(), dep,
147 hsh, err := fileHash(fd)
152 if hsh != string(theirHsh) {
154 CDebug, "depfix: %s/%s -> %s: hash differs",
155 root, entry.Name(), dep,
159 fields = []recfile.Field{
160 {Name: "Type", Value: DepTypeIfchange},
161 {Name: "Target", Value: dep},
162 {Name: "Hash", Value: hex.EncodeToString([]byte(hsh))},
164 fields = append(fields, inode.RecfileFields()...)
165 fieldses[len(fieldses)-1] = fields
167 CDebug, "depfix: %s/%s -> %s: inode updated",
168 root, entry.Name(), dep,
176 redoDirChanged = true
177 fdDep, err = tempfile(redoDir, entry.Name())
183 CDebug, "depfix: %s/%s: tmp %s",
184 root, entry.Name(), fdDep.Name(),
186 w := recfile.NewWriter(fdDep)
187 if _, err := w.WriteFields(fieldses[0]...); err != nil {
190 fieldses = fieldses[1:]
191 for _, fields := range fieldses {
192 if _, err := w.RecordStart(); err != nil {
195 if _, err := w.WriteFields(fields...); err != nil {
200 if err = fdDep.Sync(); err != nil {
205 if err = os.Rename(fdDep.Name(), fdDepPath); err != nil {
208 tracef(CRedo, "%s", fdDepPath)
211 if redoDirChanged && !NoSync {
212 if err = syncDir(redoDir); err != nil {