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