]> Cypherpunks.ru repositories - goredo.git/blobdiff - depfix.go
redo-depfix
[goredo.git] / depfix.go
diff --git a/depfix.go b/depfix.go
new file mode 100644 (file)
index 0000000..0e1d54c
--- /dev/null
+++ b/depfix.go
@@ -0,0 +1,214 @@
+/*
+goredo -- djb's redo implementation on pure Go
+Copyright (C) 2020-2022 Sergey Matveev <stargrave@stargrave.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 3 of the License.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package main
+
+import (
+       "errors"
+       "io"
+       "os"
+       "path"
+       "strings"
+
+       "go.cypherpunks.ru/recfile"
+)
+
+func depFix(root string) error {
+       tracef(CDebug, "depfix: entering %s", root)
+       dir, err := os.Open(root)
+       if err != nil {
+               return err
+       }
+       defer dir.Close()
+       for {
+               fis, err := dir.Readdir(1 << 10)
+               if err != nil {
+                       if err == io.EOF {
+                               break
+                       }
+                       return err
+               }
+               for _, fi := range fis {
+                       if fi.IsDir() {
+                               if err = depFix(path.Join(root, fi.Name())); err != nil {
+                                       return err
+                               }
+                       }
+               }
+       }
+       dir.Close()
+
+       redoDir := path.Join(root, RedoDir)
+       dir, err = os.Open(redoDir)
+       if err != nil {
+               if os.IsNotExist(err) {
+                       return nil
+               }
+               return err
+       }
+       defer dir.Close()
+       redoDirChanged := false
+       for {
+               fis, err := dir.Readdir(1 << 10)
+               if err != nil {
+                       if err == io.EOF {
+                               break
+                       }
+                       return err
+               }
+               for _, fi := range fis {
+                       if !strings.HasSuffix(fi.Name(), DepSuffix) {
+                               continue
+                       }
+                       tracef(CDebug, "depfix: checking %s/%s", root, fi.Name())
+                       fdDepPath := path.Join(redoDir, fi.Name())
+                       fdDep, err := os.Open(fdDepPath)
+                       if err != nil {
+                               return err
+                       }
+                       defer fdDep.Close()
+                       r := recfile.NewReader(fdDep)
+                       var fieldses [][]recfile.Field
+                       depChanged := false
+                       for {
+                               fields, err := r.Next()
+                               if err != nil {
+                                       if errors.Is(err, io.EOF) {
+                                               break
+                                       }
+                                       return err
+                               }
+                               fieldses = append(fieldses, fields)
+                               m := make(map[string]string, len(fields))
+                               for _, f := range fields {
+                                       m[f.Name] = f.Value
+                               }
+                               if m["Type"] != DepTypeIfchange {
+                                       continue
+                               }
+                               dep := m["Target"]
+                               if dep == "" {
+                                       return ErrMissingTarget
+                               }
+                               tracef(CDebug, "depfix: checking %s/%s -> %s", root, fi.Name(), dep)
+                               theirInode, err := inodeFromRec(m)
+                               if err != nil {
+                                       return err
+                               }
+                               theirHsh := m["Hash"]
+                               fd, err := os.Open(path.Join(root, dep))
+                               if err != nil {
+                                       if os.IsNotExist(err) {
+                                               tracef(
+                                                       CDebug, "depfix: %s/%s -> %s: not exists",
+                                                       root, fi.Name(), dep,
+                                               )
+                                               continue
+                                       }
+                                       return err
+                               }
+                               inode, err := inodeFromFile(fd)
+                               if err != nil {
+                                       return err
+                               }
+                               if inode.Size != theirInode.Size {
+                                       tracef(
+                                               CDebug, "depfix: %s/%s -> %s: size differs",
+                                               root, fi.Name(), dep,
+                                       )
+                                       fd.Close()
+                                       continue
+                               }
+                               if inode.Equals(theirInode) {
+                                       tracef(
+                                               CDebug, "depfix: %s/%s -> %s: inode is equal",
+                                               root, fi.Name(), dep,
+                                       )
+                                       fd.Close()
+                                       continue
+                               }
+                               hsh, err := fileHash(fd)
+                               fd.Close()
+                               if err != nil {
+                                       return err
+                               }
+                               if hsh != theirHsh {
+                                       tracef(
+                                               CDebug, "depfix: %s/%s -> %s: hash differs",
+                                               root, fi.Name(), dep,
+                                       )
+                                       continue
+                               }
+                               fields = []recfile.Field{
+                                       {Name: "Type", Value: DepTypeIfchange},
+                                       {Name: "Target", Value: dep},
+                                       {Name: "Hash", Value: hsh},
+                               }
+                               fields = append(fields, inode.RecfileFields()...)
+                               fieldses[len(fieldses)-1] = fields
+                               tracef(
+                                       CDebug, "depfix: %s/%s -> %s: inode updated",
+                                       root, fi.Name(), dep,
+                               )
+                               depChanged = true
+                       }
+                       fdDep.Close()
+                       if !depChanged {
+                               continue
+                       }
+                       redoDirChanged = true
+                       fdDep, err = tempfile(redoDir, fi.Name())
+                       if err != nil {
+                               return err
+                       }
+                       defer fdDep.Close()
+                       tracef(
+                               CDebug, "depfix: %s/%s: tmp %s",
+                               root, fi.Name(), fdDep.Name(),
+                       )
+                       w := recfile.NewWriter(fdDep)
+                       if _, err := w.WriteFields(fieldses[0]...); err != nil {
+                               return err
+                       }
+                       fieldses = fieldses[1:]
+                       for _, fields := range fieldses {
+                               if _, err := w.RecordStart(); err != nil {
+                                       return err
+                               }
+                               if _, err := w.WriteFields(fields...); err != nil {
+                                       return err
+                               }
+                       }
+                       if !NoSync {
+                               if err = fdDep.Sync(); err != nil {
+                                       return err
+                               }
+                       }
+                       fdDep.Close()
+                       if err = os.Rename(fdDep.Name(), fdDepPath); err != nil {
+                               return err
+                       }
+                       tracef(CRedo, "%s", fdDepPath)
+               }
+       }
+       if redoDirChanged && !NoSync {
+               if err = syncDir(redoDir); err != nil {
+                       return nil
+               }
+       }
+       return nil
+}