]> Cypherpunks.ru repositories - goredo.git/blob - depfix.go
redo-depfix
[goredo.git] / depfix.go
1 /*
2 goredo -- djb's redo implementation on pure Go
3 Copyright (C) 2020-2022 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                 fis, err := dir.Readdir(1 << 10)
39                 if err != nil {
40                         if err == io.EOF {
41                                 break
42                         }
43                         return err
44                 }
45                 for _, fi := range fis {
46                         if fi.IsDir() {
47                                 if err = depFix(path.Join(root, fi.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                 fis, err := dir.Readdir(1 << 10)
67                 if err != nil {
68                         if err == io.EOF {
69                                 break
70                         }
71                         return err
72                 }
73                 for _, fi := range fis {
74                         if !strings.HasSuffix(fi.Name(), DepSuffix) {
75                                 continue
76                         }
77                         tracef(CDebug, "depfix: checking %s/%s", root, fi.Name())
78                         fdDepPath := path.Join(redoDir, fi.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, fi.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, fi.Name(), dep,
119                                                 )
120                                                 continue
121                                         }
122                                         return err
123                                 }
124                                 inode, err := inodeFromFile(fd)
125                                 if err != nil {
126                                         return err
127                                 }
128                                 if inode.Size != theirInode.Size {
129                                         tracef(
130                                                 CDebug, "depfix: %s/%s -> %s: size differs",
131                                                 root, fi.Name(), dep,
132                                         )
133                                         fd.Close()
134                                         continue
135                                 }
136                                 if inode.Equals(theirInode) {
137                                         tracef(
138                                                 CDebug, "depfix: %s/%s -> %s: inode is equal",
139                                                 root, fi.Name(), dep,
140                                         )
141                                         fd.Close()
142                                         continue
143                                 }
144                                 hsh, err := fileHash(fd)
145                                 fd.Close()
146                                 if err != nil {
147                                         return err
148                                 }
149                                 if hsh != theirHsh {
150                                         tracef(
151                                                 CDebug, "depfix: %s/%s -> %s: hash differs",
152                                                 root, fi.Name(), dep,
153                                         )
154                                         continue
155                                 }
156                                 fields = []recfile.Field{
157                                         {Name: "Type", Value: DepTypeIfchange},
158                                         {Name: "Target", Value: dep},
159                                         {Name: "Hash", Value: hsh},
160                                 }
161                                 fields = append(fields, inode.RecfileFields()...)
162                                 fieldses[len(fieldses)-1] = fields
163                                 tracef(
164                                         CDebug, "depfix: %s/%s -> %s: inode updated",
165                                         root, fi.Name(), dep,
166                                 )
167                                 depChanged = true
168                         }
169                         fdDep.Close()
170                         if !depChanged {
171                                 continue
172                         }
173                         redoDirChanged = true
174                         fdDep, err = tempfile(redoDir, fi.Name())
175                         if err != nil {
176                                 return err
177                         }
178                         defer fdDep.Close()
179                         tracef(
180                                 CDebug, "depfix: %s/%s: tmp %s",
181                                 root, fi.Name(), fdDep.Name(),
182                         )
183                         w := recfile.NewWriter(fdDep)
184                         if _, err := w.WriteFields(fieldses[0]...); err != nil {
185                                 return err
186                         }
187                         fieldses = fieldses[1:]
188                         for _, fields := range fieldses {
189                                 if _, err := w.RecordStart(); err != nil {
190                                         return err
191                                 }
192                                 if _, err := w.WriteFields(fields...); err != nil {
193                                         return err
194                                 }
195                         }
196                         if !NoSync {
197                                 if err = fdDep.Sync(); err != nil {
198                                         return err
199                                 }
200                         }
201                         fdDep.Close()
202                         if err = os.Rename(fdDep.Name(), fdDepPath); err != nil {
203                                 return err
204                         }
205                         tracef(CRedo, "%s", fdDepPath)
206                 }
207         }
208         if redoDirChanged && !NoSync {
209                 if err = syncDir(redoDir); err != nil {
210                         return nil
211                 }
212         }
213         return nil
214 }