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