+// meta4ra -- Metalink 4.0 utilities
+// Copyright (C) 2021-2024 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 internal
+
+import (
+ "bytes"
+ "errors"
+ "io"
+ "os/exec"
+ "strings"
+)
+
+type Hasher struct {
+ Names []string
+ Cmds []*exec.Cmd
+ Ins []io.WriteCloser
+ Outs []io.ReadCloser
+}
+
+func NewHasher(hashes string) (*Hasher, error) {
+ h := Hasher{}
+ for _, hc := range strings.Split(hashes, ",") {
+ cols := strings.SplitN(hc, ":", 2)
+ name, cmdline := cols[0], cols[1]
+ if cmdline == BuiltinCmd {
+ newHash, exists := BuiltinHashes[name]
+ if !exists {
+ return nil, errors.New("no builtin hash: " + name)
+ }
+ b := &BuiltinHasher{h: newHash()}
+ h.Names = append(h.Names, name)
+ h.Ins = append(h.Ins, b)
+ h.Outs = append(h.Outs, b)
+ h.Cmds = append(h.Cmds, nil)
+ } else {
+ cmd := exec.Command("/bin/sh", "-e", "-c", cmdline)
+ in, err := cmd.StdinPipe()
+ if err != nil {
+ return &h, err
+ }
+ out, err := cmd.StdoutPipe()
+ if err != nil {
+ return &h, err
+ }
+ h.Names = append(h.Names, name)
+ h.Ins = append(h.Ins, in)
+ h.Outs = append(h.Outs, out)
+ h.Cmds = append(h.Cmds, cmd)
+ }
+ }
+ return &h, nil
+}
+
+func (h *Hasher) Start() (err error) {
+ for _, cmd := range h.Cmds {
+ if cmd == nil {
+ continue
+ }
+ err = cmd.Start()
+ if err != nil {
+ return
+ }
+ }
+ return nil
+}
+
+func (h *Hasher) Stop() {
+ for _, cmd := range h.Cmds {
+ if cmd == nil || cmd.Process == nil {
+ continue
+ }
+ cmd.Process.Kill()
+ cmd.Process.Release()
+ }
+}
+
+func (h *Hasher) Write(p []byte) (n int, rerr error) {
+ errs := make(chan error)
+ for _, in := range h.Ins {
+ go func(in io.WriteCloser) {
+ _, err := io.Copy(in, bytes.NewReader(p))
+ errs <- err
+ }(in)
+ }
+ for i := 0; i < len(h.Names); i++ {
+ if err := <-errs; err != nil {
+ rerr = err
+ }
+ }
+ n = len(p)
+ return
+}
+
+func (h *Hasher) Sums() (sums []Hash, err error) {
+ sums = make([]Hash, 0, len(h.Names))
+ for i, name := range h.Names {
+ if err = h.Ins[i].Close(); err != nil {
+ return
+ }
+ var out []byte
+ out, err = io.ReadAll(h.Outs[i])
+ if err != nil {
+ return
+ }
+ if cmd := h.Cmds[i]; cmd != nil {
+ if err = h.Cmds[i].Wait(); err != nil {
+ return
+ }
+ }
+ cols := strings.Fields(strings.TrimSuffix(string(out), "\n"))
+ sums = append(sums, Hash{Type: name, Hash: cols[0]})
+ }
+ return
+}