]> Sergey Matveev's repositories - meta4ra.git/blob - internal/hasher.go
*-hashes-detect, *-hash, *-check -stdin/-all-hashes, optional builtins
[meta4ra.git] / internal / hasher.go
1 // meta4ra -- Metalink 4.0 utilities
2 // Copyright (C) 2021-2024 Sergey Matveev <stargrave@stargrave.org>
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, version 3 of the License.
7 //
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
15
16 package internal
17
18 import (
19         "bytes"
20         "errors"
21         "io"
22         "os/exec"
23         "strings"
24 )
25
26 type Hasher struct {
27         Names []string
28         Cmds  []*exec.Cmd
29         Ins   []io.WriteCloser
30         Outs  []io.ReadCloser
31 }
32
33 func NewHasher(hashes string) (*Hasher, error) {
34         h := Hasher{}
35         for _, hc := range strings.Split(hashes, ",") {
36                 cols := strings.SplitN(hc, ":", 2)
37                 name, cmdline := cols[0], cols[1]
38                 if cmdline == BuiltinCmd {
39                         newHash, exists := BuiltinHashes[name]
40                         if !exists {
41                                 return nil, errors.New("no builtin hash: " + name)
42                         }
43                         b := &BuiltinHasher{h: newHash()}
44                         h.Names = append(h.Names, name)
45                         h.Ins = append(h.Ins, b)
46                         h.Outs = append(h.Outs, b)
47                         h.Cmds = append(h.Cmds, nil)
48                 } else {
49                         cmd := exec.Command("/bin/sh", "-e", "-c", cmdline)
50                         in, err := cmd.StdinPipe()
51                         if err != nil {
52                                 return &h, err
53                         }
54                         out, err := cmd.StdoutPipe()
55                         if err != nil {
56                                 return &h, err
57                         }
58                         h.Names = append(h.Names, name)
59                         h.Ins = append(h.Ins, in)
60                         h.Outs = append(h.Outs, out)
61                         h.Cmds = append(h.Cmds, cmd)
62                 }
63         }
64         return &h, nil
65 }
66
67 func (h *Hasher) Start() (err error) {
68         for _, cmd := range h.Cmds {
69                 if cmd == nil {
70                         continue
71                 }
72                 err = cmd.Start()
73                 if err != nil {
74                         return
75                 }
76         }
77         return nil
78 }
79
80 func (h *Hasher) Stop() {
81         for _, cmd := range h.Cmds {
82                 if cmd == nil || cmd.Process == nil {
83                         continue
84                 }
85                 cmd.Process.Kill()
86                 cmd.Process.Release()
87         }
88 }
89
90 func (h *Hasher) Write(p []byte) (n int, rerr error) {
91         errs := make(chan error)
92         for _, in := range h.Ins {
93                 go func(in io.WriteCloser) {
94                         _, err := io.Copy(in, bytes.NewReader(p))
95                         errs <- err
96                 }(in)
97         }
98         for i := 0; i < len(h.Names); i++ {
99                 if err := <-errs; err != nil {
100                         rerr = err
101                 }
102         }
103         n = len(p)
104         return
105 }
106
107 func (h *Hasher) Sums() (sums []Hash, err error) {
108         sums = make([]Hash, 0, len(h.Names))
109         for i, name := range h.Names {
110                 if err = h.Ins[i].Close(); err != nil {
111                         return
112                 }
113                 var out []byte
114                 out, err = io.ReadAll(h.Outs[i])
115                 if err != nil {
116                         return
117                 }
118                 if cmd := h.Cmds[i]; cmd != nil {
119                         if err = h.Cmds[i].Wait(); err != nil {
120                                 return
121                         }
122                 }
123                 cols := strings.Fields(strings.TrimSuffix(string(out), "\n"))
124                 sums = append(sums, Hash{Type: name, Hash: cols[0]})
125         }
126         return
127 }