package deb import ( "fmt" "github.com/smira/aptly/aptly" "github.com/smira/aptly/utils" "io/ioutil" "os" "path/filepath" "sort" "strings" ) // Changes is a result of .changes file parsing type Changes struct { Changes string Distribution string Files PackageFiles BasePath, ChangesName string TempDir string Source string Binary []string Architectures []string Stanza Stanza SignatureKeys []utils.GpgKey } // NewChanges moves .changes file into temporary directory and creates Changes structure func NewChanges(path string) (*Changes, error) { var err error c := &Changes{ BasePath: filepath.Dir(path), ChangesName: filepath.Base(path), } c.TempDir, err = ioutil.TempDir(os.TempDir(), "aptly") if err != nil { return nil, err } // copy .changes file into temporary directory err = utils.CopyFile(filepath.Join(c.BasePath, c.ChangesName), filepath.Join(c.TempDir, c.ChangesName)) if err != nil { return nil, err } return c, nil } // VerifyAndParse does optional signature verification and parses changes files func (c *Changes) VerifyAndParse(acceptUnsigned, ignoreSignature bool, verifier utils.Verifier) error { input, err := os.Open(filepath.Join(c.TempDir, c.ChangesName)) if err != nil { return err } defer input.Close() isClearSigned, err := verifier.IsClearSigned(input) if err != nil { return err } input.Seek(0, 0) if !isClearSigned && !acceptUnsigned { return fmt.Errorf(".changes file is not signed and unsigned processing hasn't been enabled") } if isClearSigned && !ignoreSignature { keyInfo, err := verifier.VerifyClearsigned(input, false) if err != nil { return err } input.Seek(0, 0) c.SignatureKeys = keyInfo.GoodKeys } var text *os.File if isClearSigned { text, err = verifier.ExtractClearsigned(input) if err != nil { return err } defer text.Close() } else { text = input } reader := NewControlFileReader(text) c.Stanza, err = reader.ReadStanza() if err != nil { return err } c.Distribution = c.Stanza["Distribution"] c.Changes = c.Stanza["Changes"] c.Source = c.Stanza["Source"] c.Binary = strings.Fields(c.Stanza["Binary"]) c.Architectures = strings.Fields(c.Stanza["Architecture"]) c.Files, err = c.Files.ParseSumFields(c.Stanza) if err != nil { return err } return nil } // Prepare creates temporary directory, copies file there and verifies checksums func (c *Changes) Prepare() error { var err error for _, file := range c.Files { if filepath.Dir(file.Filename) != "." { return fmt.Errorf("file is not in the same folder as .changes file: %s", file.Filename) } file.Filename = filepath.Base(file.Filename) err = utils.CopyFile(filepath.Join(c.BasePath, file.Filename), filepath.Join(c.TempDir, file.Filename)) if err != nil { return err } } for _, file := range c.Files { var info utils.ChecksumInfo info, err = utils.ChecksumsForFile(filepath.Join(c.TempDir, file.Filename)) if err != nil { return err } if info.Size != file.Checksums.Size { return fmt.Errorf("size mismatch: expected %v != obtained %v", file.Checksums.Size, info.Size) } if info.MD5 != file.Checksums.MD5 { return fmt.Errorf("checksum mismatch MD5: expected %v != obtained %v", file.Checksums.MD5, info.MD5) } if info.SHA1 != file.Checksums.SHA1 { return fmt.Errorf("checksum mismatch SHA1: expected %v != obtained %v", file.Checksums.SHA1, info.SHA1) } if info.SHA256 != file.Checksums.SHA256 { return fmt.Errorf("checksum mismatch SHA256 expected %v != obtained %v", file.Checksums.SHA256, info.SHA256) } } return nil } // Cleanup removes all temporary files func (c *Changes) Cleanup() error { if c.TempDir == "" { return nil } return os.RemoveAll(c.TempDir) } // PackageQuery returns query that every package should match to be included func (c *Changes) PackageQuery() (PackageQuery, error) { var archQuery PackageQuery = &FieldQuery{Field: "$Architecture", Relation: VersionEqual, Value: ""} for _, arch := range c.Architectures { archQuery = &OrQuery{L: &FieldQuery{Field: "$Architecture", Relation: VersionEqual, Value: arch}, R: archQuery} } // if c.Source is empty, this would never match sourceQuery := &AndQuery{ L: &FieldQuery{Field: "$PackageType", Relation: VersionEqual, Value: "source"}, R: &FieldQuery{Field: "Name", Relation: VersionEqual, Value: c.Source}, } var binaryQuery PackageQuery if len(c.Binary) > 0 { binaryQuery = &FieldQuery{Field: "Name", Relation: VersionEqual, Value: c.Binary[0]} for _, binary := range c.Binary[1:] { binaryQuery = &OrQuery{ L: &FieldQuery{Field: "Name", Relation: VersionEqual, Value: binary}, R: binaryQuery, } } binaryQuery = &AndQuery{ L: &NotQuery{Q: &FieldQuery{Field: "$PackageType", Relation: VersionEqual, Value: "source"}}, R: binaryQuery} } var nameQuery PackageQuery if binaryQuery == nil { nameQuery = sourceQuery } else { nameQuery = &OrQuery{L: sourceQuery, R: binaryQuery} } return &AndQuery{L: archQuery, R: nameQuery}, nil } // GetField implements PackageLike interface func (c *Changes) GetField(field string) string { return c.Stanza[field] } // MatchesDependency implements PackageLike interface func (c *Changes) MatchesDependency(d Dependency) bool { return false } // MatchesArchitecture implements PackageLike interface func (c *Changes) MatchesArchitecture(arch string) bool { return false } // GetName implements PackageLike interface func (c *Changes) GetName() string { return "" } // GetVersion implements PackageLike interface func (c *Changes) GetVersion() string { return "" } // GetArchitecture implements PackageLike interface func (c *Changes) GetArchitecture() string { return "" } // CollectChangesFiles walks filesystem collecting all .changes files func CollectChangesFiles(locations []string, reporter aptly.ResultReporter) (changesFiles, failedFiles []string) { for _, location := range locations { info, err2 := os.Stat(location) if err2 != nil { reporter.Warning("Unable to process %s: %s", location, err2) failedFiles = append(failedFiles, location) continue } if info.IsDir() { err2 = filepath.Walk(location, func(path string, info os.FileInfo, err3 error) error { if err3 != nil { return err3 } if info.IsDir() { return nil } if strings.HasSuffix(info.Name(), ".changes") { changesFiles = append(changesFiles, path) } return nil }) } else if strings.HasSuffix(info.Name(), ".changes") { changesFiles = append(changesFiles, location) } } sort.Strings(changesFiles) return }