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") } } }