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