package debian
import (
"bufio"
"crypto/md5"
"crypto/sha256"
"errors"
"fmt"
"io"
"os"
"strings"
"go.wit.com/lib/cobol"
"go.wit.com/lib/config"
"go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/zoopb"
"go.wit.com/log"
"google.golang.org/protobuf/types/known/timestamppb"
)
// runs dpkg -I on a .deb and returns a PB of the information
func RunDpkg(p *zoopb.Package, filename string) error {
// SIMPLE SANITY CHECKS
if p.DebInfo != nil {
// already added p.DebInfo
return nil
}
// filename := filepath.Join(me.pb.BaseDir, p.Filename)
cmd := []string{"dpkg", "-I", filename}
r := shell.Run(cmd)
if r.Error != nil {
return r.Error
}
if r.Exit != 0 {
return errors.New("dpkg returned -1")
}
stat, err := os.Stat(filename)
if err != nil {
return err
}
filedata, err := os.Open(filename)
if err != nil {
return err
}
defer filedata.Close()
// SIMPLE SANITY CHECKS END
// SHA256 HASH
p.DebInfo = new(zoopb.DebInfo)
hSHA256 := sha256.New()
hMD5 := md5.New() // probably deprecate, but love md5sum too much
// hSHA1 := sha1.New() // deprecated
// TeeReader allows writing to multiple hashers at once
// multiWriter := io.MultiWriter(hMD5, hSHA1, hSHA256)
multiWriter := io.MultiWriter(hSHA256, hMD5)
if _, err := io.Copy(multiWriter, filedata); err != nil {
return err
}
p.DebInfo.SHA256 = fmt.Sprintf("%x", hSHA256.Sum(nil)) // should be the standard now
p.DebInfo.MD5SUM = fmt.Sprintf("%x", hMD5.Sum(nil)) // probably deprecate
// p.DebInfo.SHA1 = fmt.Sprintf("%x", hSHA1.Sum(nil)) // deprecated
// SHA256 HASH END
// set file create time
p.Ctime = timestamppb.New(stat.ModTime())
// PARSE "dpkg -I
", then exit as we are done
all := strings.Join(r.Stdout, "\n")
ParseDpkgOutputIntoPB(p, all)
return nil
}
// PARSES "dpkg -I "
// panic() on anything missing or unknown
func ParseDpkgOutputIntoPB(pb *zoopb.Package, all string) {
if pb.DebInfo == nil {
pb.DebInfo = new(zoopb.DebInfo)
}
starting := true
scanner := bufio.NewScanner(strings.NewReader(all))
for scanner.Scan() {
line := scanner.Text()
parts := strings.Fields(line)
if len(parts) == 0 {
continue
}
if starting {
if parts[0] == "new" {
if config.Verbose() {
log.Printf("new: %v\n", parts)
}
// skip the first dpkg -I line
continue
}
if parts[0] == "size" {
if config.Verbose() {
log.Printf("size: %v\n", parts)
}
// todo: make this correct
pb.DebInfo.Size = parts[1]
pb.DebInfo.InstalledSize = parts[1]
// scan all the entries from size
for scanner.Scan() {
line = scanner.Text()
parts = strings.Fields(line)
if strings.HasPrefix(line, " ") {
if config.Verbose() {
log.Printf("sizeline: %v\n", parts)
}
continue
}
starting = false
break
}
}
if starting {
if config.Verbose() {
log.Printf("probably real data: %v\n", parts)
}
if strings.HasSuffix(parts[0], ":") {
// log.Printf("yep, real data: %s\n", parts[0])
starting = false
} else {
continue
}
}
}
varname := strings.TrimRight(parts[0], ":")
varval := strings.Join(parts[1:], " ")
if config.Verbose() {
log.Printf("varname:%s varval:%s\n", varname, varval)
}
switch varname {
case "Package":
pb.Package = varval
case "Filename":
// maybe should check this. sometimes it's passed in
if pb.Filename == "" {
pb.Filename = varval
} else {
log.Info("Filename was already set to", pb.Filename)
}
case "Version":
pb.Version = varval
case "Architecture":
pb.Architecture = varval
case "GoPath":
pb.Namespace = varval
case "Maintainer":
pb.DebInfo.Maintainer = varval
case "Packager":
pb.DebInfo.Packager = varval
case "Depends":
pb.DebInfo.Depends = varval
case "Recommends":
pb.DebInfo.Recommends = varval
case "Source":
pb.DebInfo.Source = varval
case "URL":
pb.DebInfo.URL = varval
case "Build-Depends":
pb.DebInfo.BuildDepends = varval
case "Namespace":
pb.Namespace = varval
case "MD5Sum":
pb.DebInfo.MD5SUM = varval
case "SHA256":
pb.DebInfo.SHA256 = varval
case "Size":
pb.DebInfo.Size = varval
case "Installed-Size":
pb.DebInfo.InstalledSize = varval
case "Homepage":
pb.DebInfo.URL = varval
case "Conflicts":
pb.DebInfo.Conflicts = varval
case "Source-Date":
t, err := cobol.GetTime(varval)
if t != nil {
pb.GitDate = timestamppb.New(*t)
} else if config.Verbose() {
log.Info("FIXME: Package-Build-Date", varval, err)
}
case "Deb-File-Date":
pb.DebInfo.DebCtime = varval
t, err := cobol.GetTime(varval)
if t != nil {
pb.GitDate = timestamppb.New(*t)
} else if config.Verbose() {
log.Info("FIXME: Package-Build-Date", varval, err)
}
case "Build-Date":
pb.DebInfo.BuildDate = varval
t, err := cobol.GetTime(varval)
if t != nil {
pb.BuildDate = timestamppb.New(*t)
} else if config.Verbose() {
log.Info("FIXME: Package-Build-Date", varval, err)
}
case "Package-Build-Date":
t, err := cobol.GetTime(varval)
if t != nil {
pb.BuildDate = timestamppb.New(*t)
} else if config.Verbose() {
log.Info("FIXME: Package-Build-Date", varval, err)
}
case "Git-Tag-Date":
if config.Verbose() {
log.Info("FIXME: Git-Tag-Date", varval)
}
case "Description":
description := varval + "\n"
// log.Info("SCANNED DESC=", description)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, " ") {
line = strings.TrimSpace(line)
description += line + "\n"
continue
}
break
}
// log.Info("SCANNED DESC=", description)
pb.DebInfo.Description = description
default:
// This forces me(it could be you!) to fix this parser
varname2 := strings.TrimSuffix(varname, ":")
log.Println("LINE:", line)
log.Printf("UNKNOWN: varname:%s varval:%s varname2=%s\n", varname, varval, varname2)
panic("fix go.wit.com/lib/debian/parseDpkgOutputIntoPB.go")
}
}
}