]> Cypherpunks.ru repositories - goredo.git/blob - depfix.go
bc4452cef820c3a40b507bd860ec9094be701d5a
[goredo.git] / depfix.go
1 /*
2 goredo -- djb's redo implementation on pure Go
3 Copyright (C) 2020-2023 Sergey Matveev <stargrave@stargrave.org>
4
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.
8
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.
13
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/>.
16 */
17
18 package main
19
20 import (
21         "errors"
22         "io"
23         "io/fs"
24         "os"
25         "path"
26         "strings"
27
28         "go.cypherpunks.ru/recfile"
29 )
30
31 func depFix(root string) error {
32         tracef(CDebug, "depfix: entering %s", root)
33         dir, err := os.Open(root)
34         if err != nil {
35                 return ErrLine(err)
36         }
37         defer dir.Close()
38         for {
39                 entries, err := dir.ReadDir(1 << 10)
40                 if err != nil {
41                         if err == io.EOF {
42                                 break
43                         }
44                         return ErrLine(err)
45                 }
46                 for _, entry := range entries {
47                         if entry.IsDir() {
48                                 if err = depFix(path.Join(root, entry.Name())); err != nil {
49                                         return err
50                                 }
51                         }
52                 }
53         }
54         dir.Close()
55
56         redoDir := path.Join(root, RedoDir)
57         dir, err = os.Open(redoDir)
58         if err != nil {
59                 if errors.Is(err, fs.ErrNotExist) {
60                         return nil
61                 }
62                 return ErrLine(err)
63         }
64         defer dir.Close()
65         redoDirChanged := false
66         for {
67                 entries, err := dir.ReadDir(1 << 10)
68                 if err != nil {
69                         if err == io.EOF {
70                                 break
71                         }
72                         return ErrLine(err)
73                 }
74                 for _, entry := range entries {
75                         if !strings.HasSuffix(entry.Name(), DepSuffix) {
76                                 continue
77                         }
78                         tracef(CDebug, "depfix: checking %s/%s", root, entry.Name())
79                         fdDepPath := path.Join(redoDir, entry.Name())
80                         fdDep, err := os.Open(fdDepPath)
81                         if err != nil {
82                                 return ErrLine(err)
83                         }
84                         defer fdDep.Close()
85                         r := recfile.NewReader(fdDep)
86                         var fieldses [][]recfile.Field
87                         depChanged := false
88                         for {
89                                 fields, err := r.Next()
90                                 if err != nil {
91                                         if errors.Is(err, io.EOF) {
92                                                 break
93                                         }
94                                         return ErrLine(err)
95                                 }
96                                 fieldses = append(fieldses, fields)
97                                 m := make(map[string]string, len(fields))
98                                 for _, f := range fields {
99                                         m[f.Name] = f.Value
100                                 }
101                                 if m["Type"] != DepTypeIfchange {
102                                         continue
103                                 }
104                                 dep := m["Target"]
105                                 if dep == "" {
106                                         return ErrMissingTarget
107                                 }
108                                 tracef(CDebug, "depfix: checking %s/%s -> %s", root, entry.Name(), dep)
109                                 theirInode, err := inodeFromRec(m)
110                                 if err != nil {
111                                         return ErrLine(err)
112                                 }
113                                 theirHsh := mustHashDecode(m["Hash"])
114                                 fd, err := os.Open(path.Join(root, dep))
115                                 if err != nil {
116                                         if errors.Is(err, fs.ErrNotExist) {
117                                                 tracef(
118                                                         CDebug, "depfix: %s/%s -> %s: not exists",
119                                                         root, entry.Name(), dep,
120                                                 )
121                                                 continue
122                                         }
123                                         return ErrLine(err)
124                                 }
125                                 inode, _, err := inodeFromFileByFd(fd)
126                                 if err != nil {
127                                         fd.Close()
128                                         return ErrLine(err)
129                                 }
130                                 if inode.Size != theirInode.Size {
131                                         tracef(
132                                                 CDebug, "depfix: %s/%s -> %s: size differs",
133                                                 root, entry.Name(), dep,
134                                         )
135                                         fd.Close()
136                                         continue
137                                 }
138                                 if inode.Equals(theirInode) {
139                                         tracef(
140                                                 CDebug, "depfix: %s/%s -> %s: inode is equal",
141                                                 root, entry.Name(), dep,
142                                         )
143                                         fd.Close()
144                                         continue
145                                 }
146                                 hsh, err := fileHash(fd)
147                                 fd.Close()
148                                 if err != nil {
149                                         return ErrLine(err)
150                                 }
151                                 if hsh != theirHsh {
152                                         tracef(
153                                                 CDebug, "depfix: %s/%s -> %s: hash differs",
154                                                 root, entry.Name(), dep,
155                                         )
156                                         continue
157                                 }
158                                 fields = []recfile.Field{
159                                         {Name: "Type", Value: DepTypeIfchange},
160                                         {Name: "Target", Value: dep},
161                                         {Name: "Hash", Value: hsh.String()},
162                                 }
163                                 fields = append(fields, inode.RecfileFields()...)
164                                 fieldses[len(fieldses)-1] = fields
165                                 tracef(
166                                         CDebug, "depfix: %s/%s -> %s: inode updated",
167                                         root, entry.Name(), dep,
168                                 )
169                                 depChanged = true
170                         }
171                         fdDep.Close()
172                         if !depChanged {
173                                 continue
174                         }
175                         redoDirChanged = true
176                         fdDep, err = tempfile(redoDir, entry.Name())
177                         if err != nil {
178                                 return ErrLine(err)
179                         }
180                         defer fdDep.Close()
181                         tracef(
182                                 CDebug, "depfix: %s/%s: tmp %s",
183                                 root, entry.Name(), fdDep.Name(),
184                         )
185                         w := recfile.NewWriter(fdDep)
186                         if _, err := w.WriteFields(fieldses[0]...); err != nil {
187                                 return ErrLine(err)
188                         }
189                         fieldses = fieldses[1:]
190                         for _, fields := range fieldses {
191                                 if _, err := w.RecordStart(); err != nil {
192                                         return ErrLine(err)
193                                 }
194                                 if _, err := w.WriteFields(fields...); err != nil {
195                                         return ErrLine(err)
196                                 }
197                         }
198                         if !NoSync {
199                                 if err = fdDep.Sync(); err != nil {
200                                         return ErrLine(err)
201                                 }
202                         }
203                         fdDep.Close()
204                         if err = os.Rename(fdDep.Name(), fdDepPath); err != nil {
205                                 return ErrLine(err)
206                         }
207                         tracef(CRedo, "%s", fdDepPath)
208                 }
209         }
210         if redoDirChanged && !NoSync {
211                 if err = syncDir(redoDir); err != nil {
212                         return err
213                 }
214         }
215         return nil
216 }