├── go.mod ├── .gitignore ├── abi-report ├── main.go └── cmd │ ├── root.go │ ├── version.go │ ├── scan_tree.go │ └── scan_packages.go ├── Makefile ├── explode ├── dpkg.go ├── rpm.go ├── eopkg.go └── main.go ├── libabi ├── record.go ├── machine.go ├── analyze.go ├── report.go └── walker.go ├── man └── abireport.1.md ├── README.md ├── LICENSE └── go.sum /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/clearlinux/abireport 2 | 3 | go 1.13 4 | 5 | require github.com/spf13/cobra v0.0.6 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .*~ 2 | *~ 3 | *.swp 4 | vendor/ 5 | man/abireport.1 6 | man/abireport.1.html 7 | *.tar.xz 8 | abireport 9 | -------------------------------------------------------------------------------- /abi-report/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2016-2017 Intel Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package main 18 | 19 | import ( 20 | "github.com/clearlinux/abireport/abi-report/cmd" 21 | "os" 22 | ) 23 | 24 | // Proxy the command through cobra to handle CLI 25 | func main() { 26 | if err := cmd.RootCmd.Execute(); err != nil { 27 | os.Exit(1) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /abi-report/cmd/root.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2016-2017 Intel Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package cmd 18 | 19 | import ( 20 | "github.com/clearlinux/abireport/libabi" 21 | "github.com/spf13/cobra" 22 | ) 23 | 24 | var ( 25 | // Prefix is applied to the base of all report files to enable easier 26 | // integration. 27 | Prefix string 28 | ) 29 | 30 | // RootCmd is the "default command" of abireport 31 | var RootCmd = &cobra.Command{ 32 | Use: "abireport", 33 | Short: "Generate ABI reports for binary files", 34 | } 35 | 36 | func init() { 37 | RootCmd.PersistentFlags().StringVarP(&Prefix, "prefix", "p", "", "Prefix for generated files") 38 | RootCmd.PersistentFlags().StringVarP(&libabi.ReportOutputDir, "output-dir", "D", ".", "Output directory for reports") 39 | } 40 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION = 1.0.11 2 | 3 | .DEFAULT_GOAL := all 4 | 5 | clean: 6 | rm -f abireport abireport-*.tar.xz man/abireport.1 man/abireport.1.html 7 | rm -fr vendor 8 | 9 | install: all 10 | test -d $(DESTDIR)/usr/bin || install -D -d -m 00755 $(DESTDIR)/usr/bin; \ 11 | install -m 00755 abireport $(DESTDIR)/usr/bin/.; \ 12 | test -d $(DESTDIR)/usr/share/man/man1 || install -D -d -m 00755 $(DESTDIR)/usr/share/man/man1; \ 13 | install -m 00644 man/*.1 $(DESTDIR)/usr/share/man/man1/.; \ 14 | 15 | gen_docs: man/abireport.1.md 16 | pandoc -s -f markdown -t man man/abireport.1.md --output man/abireport.1 17 | pandoc -s -f markdown -t html man/abireport.1.md --output man/abireport.1.html 18 | 19 | vendor: 20 | @go mod vendor 21 | 22 | VARIABLE = github.com/clearlinux/abireport/abi-report/cmd.ABIReportVersion 23 | all: vendor 24 | (cd abi-report && go build --buildmode=pie -mod=vendor -ldflags="-X $(VARIABLE)=$(VERSION)" -o ../abireport) 25 | 26 | dist: vendor gen_docs 27 | @rm -f abireport-$(VERSION).tar.xz 28 | @git tag -l | grep -q v$(VERSION) || (echo "tag v$(VERSION) not found"; exit 1) 29 | $(eval TMP := $(shell mktemp -d)) 30 | @cp -r . $(TMP)/abireport-$(VERSION) 31 | @( \ 32 | cd $(TMP)/abireport-$(VERSION); \ 33 | git reset --hard v$(VERSION) &>/dev/null; \ 34 | git clean -xf -e man/abireport.1 -e man/abireport.1.html; \ 35 | rm -fr .git .gitignore; \ 36 | ); 37 | @tar -C $(TMP) -cf abireport-$(VERSION).tar abireport-$(VERSION) 38 | @xz abireport-$(VERSION).tar 39 | @rm -fr $(TMP) 40 | -------------------------------------------------------------------------------- /abi-report/cmd/version.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2016-2017 Intel Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package cmd 18 | 19 | import ( 20 | "fmt" 21 | "github.com/spf13/cobra" 22 | ) 23 | 24 | // ABIReportVersion is the public version of the tool. The value is initialized 25 | // with a linker option to `go build`. See the toplevel Makefile for details. 26 | var ABIReportVersion = "UNKNOWN" 27 | 28 | // versionCmd handles "abireport version" 29 | var versionCommand = &cobra.Command{ 30 | Use: "version", 31 | Short: "Print the version and exit", 32 | Long: "Print the version & license information for abireport.", 33 | Run: printVersion, 34 | } 35 | 36 | func init() { 37 | RootCmd.AddCommand(versionCommand) 38 | } 39 | 40 | // Print the application version and exit. 41 | func printVersion(cmd *cobra.Command, args []string) { 42 | fmt.Printf("abireport version %v\n\nCopyright © 2016-2017 Intel Corporation\n", ABIReportVersion) 43 | fmt.Printf("Licensed under the Apache License, Version 2.0\n") 44 | } 45 | -------------------------------------------------------------------------------- /explode/dpkg.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2016-2017 Intel Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package explode 18 | 19 | import ( 20 | "io/ioutil" 21 | "os" 22 | "os/exec" 23 | "path/filepath" 24 | ) 25 | 26 | // Dpkg will explode all .deb's specified and then return 27 | // the install/ path inside that exploded tree. 28 | func Dpkg(pkgs []string) (string, error) { 29 | rootDir, err := ioutil.TempDir("/var/tmp", "abireport-dpkg") 30 | if err != nil { 31 | return "", err 32 | } 33 | // Ensure cleanup happens 34 | OutputDir = rootDir 35 | 36 | for _, archive := range pkgs { 37 | fp, err := filepath.Abs(archive) 38 | if err != nil { 39 | return "", err 40 | } 41 | dpkg := exec.Command("dpkg", []string{ 42 | "-X", 43 | fp, 44 | filepath.Join(rootDir, "install"), 45 | }...) 46 | dpkg.Stdout = nil 47 | dpkg.Stderr = os.Stderr 48 | dpkg.Dir = rootDir 49 | 50 | if err = dpkg.Run(); err != nil { 51 | return "", err 52 | } 53 | } 54 | 55 | return filepath.Join(rootDir, "install"), nil 56 | } 57 | -------------------------------------------------------------------------------- /explode/rpm.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2016-2017 Intel Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package explode 18 | 19 | import ( 20 | "io/ioutil" 21 | "os/exec" 22 | "path/filepath" 23 | ) 24 | 25 | // RPM will explode all RPMs passed to it and return the path to 26 | // the "root" to walk. 27 | func RPM(pkgs []string) (string, error) { 28 | rootDir, err := ioutil.TempDir("/var/tmp", "abireport-rpm") 29 | if err != nil { 30 | return "", err 31 | } 32 | // Ensure cleanup happens 33 | OutputDir = rootDir 34 | 35 | for _, archive := range pkgs { 36 | fp, err := filepath.Abs(archive) 37 | if err != nil { 38 | return "", err 39 | } 40 | rpm := exec.Command("rpm2cpio", []string{ 41 | fp, 42 | }...) 43 | cpio := exec.Command("cpio", []string{ 44 | "-i", 45 | "-m", 46 | "-d", 47 | "--quiet", 48 | "-u", 49 | }...) 50 | 51 | cpio.Stdin, _ = rpm.StdoutPipe() 52 | cpio.Stdout = nil 53 | cpio.Stderr = nil 54 | cpio.Dir = rootDir 55 | cpio.Start() 56 | if err := rpm.Run(); err != nil { 57 | return "", err 58 | } 59 | if err := cpio.Wait(); err != nil { 60 | return "", err 61 | } 62 | } 63 | 64 | return rootDir, nil 65 | } 66 | -------------------------------------------------------------------------------- /libabi/record.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2016-2017 Intel Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package libabi 18 | 19 | import ( 20 | "debug/elf" 21 | ) 22 | 23 | // A RecordType is a flag which can be OR'd to define a type of Record 24 | // encountered. 25 | type RecordType uint8 26 | 27 | const ( 28 | // RecordTypeExecutable indicates the file was an executable 29 | RecordTypeExecutable RecordType = 1 << iota 30 | 31 | // RecordTypeLibrary indicates a shared library 32 | RecordTypeLibrary RecordType = 1 << iota 33 | 34 | // RecordType64bit indicates a 64-bit file 35 | RecordType64bit RecordType = 1 << iota 36 | 37 | // RecordType32bit indicates a 32-bit file 38 | RecordType32bit RecordType = 1 << iota 39 | 40 | // RecordTypeExport is set when we encounter valid ABI, i.e. a library 41 | // with a soname that we can export. 42 | RecordTypeExport RecordType = 1 << iota 43 | ) 44 | 45 | // A Record is literally a recording of an encounter, with a file that 46 | // we believe to hold some interest. 47 | type Record struct { 48 | Path string // Where we found the file 49 | Flags RecordType // A bitwise set of RecordType 50 | Name string // Either the soname or the basename 51 | Dependencies []string // DT_NEEDED dependencies 52 | Symbols []string // Dynamic defined symbols 53 | Machine elf.Machine // Corresponding machine 54 | } 55 | -------------------------------------------------------------------------------- /explode/eopkg.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2016-2017 Intel Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package explode 18 | 19 | import ( 20 | "io" 21 | "io/ioutil" 22 | "os/exec" 23 | "path/filepath" 24 | "strings" 25 | ) 26 | 27 | // Eopkg will explode all eopkgs passed to it and return the path to 28 | // the "root" to walk. 29 | func Eopkg(pkgs []string) (string, error) { 30 | rootDir, err := ioutil.TempDir("/var/tmp", "abireport-eopkg") 31 | if err != nil { 32 | return "", err 33 | } 34 | 35 | // Ensure cleanup happens 36 | OutputDir = rootDir 37 | 38 | for _, archive := range pkgs { 39 | fp, err := filepath.Abs(archive) 40 | if err != nil { 41 | return "", err 42 | } 43 | // Don't want partials 44 | if strings.HasSuffix(archive, ".delta.eopkg") { 45 | continue 46 | } 47 | 48 | eopkg := exec.Command("unzip", []string{ 49 | "-p", 50 | fp, 51 | "install.tar.xz", 52 | }...) 53 | tar := exec.Command("tar", []string{ 54 | "-xJf", 55 | "-", 56 | }...) 57 | // Pipe eopkg into tar 58 | r, w := io.Pipe() 59 | defer r.Close() 60 | eopkg.Stdout = w 61 | tar.Stdin = r 62 | tar.Stdout = nil 63 | tar.Stderr = nil 64 | tar.Dir = rootDir 65 | 66 | eopkg.Start() 67 | tar.Start() 68 | go func() { 69 | defer w.Close() 70 | eopkg.Wait() 71 | }() 72 | if err := tar.Wait(); err != nil { 73 | r.Close() 74 | return "", err 75 | } 76 | r.Close() 77 | } 78 | 79 | return rootDir, nil 80 | } 81 | -------------------------------------------------------------------------------- /explode/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2016-2017 Intel Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | // Package explode is a utility library for exploding various package 18 | // types, so that abireport can traverse their root directory. 19 | package explode 20 | 21 | import ( 22 | "strings" 23 | ) 24 | 25 | var ( 26 | // OutputDir is where the package implementation dumped 27 | // the packages to and extracted inside. This is automatically removed 28 | // at shutdown. 29 | OutputDir string 30 | ) 31 | 32 | func init() { 33 | OutputDir = "" 34 | } 35 | 36 | // An Func is a function prototype for abireport extraction methods 37 | type Func func(pkgs []string) (string, error) 38 | 39 | var ( 40 | // Impls is the valid set of packages understood by abireport 41 | Impls = map[string]Func{ 42 | "*.rpm": RPM, 43 | "*.eopkg": Eopkg, 44 | "*.deb": Dpkg, 45 | } 46 | ) 47 | 48 | // GetTypeForFilename will return the appropriate Impls key for the 49 | // given input file, if it can be found. 50 | func GetTypeForFilename(name string) string { 51 | if strings.HasSuffix(name, ".rpm") { 52 | return "*.rpm" 53 | } 54 | if strings.HasSuffix(name, ".eopkg") { 55 | return "*.eopkg" 56 | } 57 | if strings.HasSuffix(name, ".deb") { 58 | return "*.deb" 59 | } 60 | return "" 61 | } 62 | 63 | // ShouldSkipName is a utility to help with skipping any unwanted packages 64 | func ShouldSkipName(name string) bool { 65 | if strings.HasSuffix(name, ".src.rpm") { 66 | return true 67 | } 68 | return false 69 | } 70 | -------------------------------------------------------------------------------- /abi-report/cmd/scan_tree.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2016-2017 Intel Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package cmd 18 | 19 | import ( 20 | "fmt" 21 | "github.com/clearlinux/abireport/libabi" 22 | "github.com/spf13/cobra" 23 | "os" 24 | ) 25 | 26 | // RootCmd is the "default command" of abireport 27 | var scanTreeCommand = &cobra.Command{ 28 | Use: "scan-tree [root]", 29 | Short: "Generate report from a filesystem tree", 30 | Long: `Examine the file tree beginning at [root] and generate an ABI report 31 | for it. This is assumed to be the root directory with a compliant tree underneath 32 | it containing /usr/lib, etc.`, 33 | Example: ` 34 | abireport scan-tree extractedRootfs/`, 35 | RunE: scanTree, 36 | } 37 | 38 | func init() { 39 | RootCmd.AddCommand(scanTreeCommand) 40 | } 41 | 42 | // scanTree is the CLI handler for "scan-tree". 43 | func scanTree(cmd *cobra.Command, args []string) error { 44 | if len(args) != 1 { 45 | return fmt.Errorf("scan-tree takes exactly one argument") 46 | } 47 | 48 | dir := args[0] 49 | 50 | if !libabi.PathExists(dir) { 51 | fmt.Fprintf(os.Stderr, "Directory %s doesn't exist\n", dir) 52 | os.Exit(1) 53 | } 54 | 55 | // Generate the ABI Report walker 56 | abi, err := libabi.NewReport(dir) 57 | if err != nil { 58 | fmt.Fprintf(os.Stderr, "Error initialising libabi: %v\n", err) 59 | os.Exit(1) 60 | } 61 | 62 | // Walk the exploded package tree 63 | if err = abi.Walk(); err != nil { 64 | fmt.Fprintf(os.Stderr, "Error walking rootfs: %v\n", err) 65 | os.Exit(1) 66 | } 67 | 68 | // Ensure we clean up existing reports 69 | if err = libabi.TruncateAll(Prefix); err != nil { 70 | fmt.Fprintf(os.Stderr, "Cannot truncate existing reports: %v\n", err) 71 | os.Exit(1) 72 | } 73 | 74 | // Finally, create the report 75 | for _, arch := range abi.Arches { 76 | if err := abi.Report(Prefix, arch); err != nil { 77 | fmt.Fprintf(os.Stderr, "Cannot generate report: %v\n", err) 78 | os.Exit(1) 79 | } 80 | } 81 | 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /libabi/machine.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2016-2017 Intel Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package libabi 18 | 19 | import ( 20 | "debug/elf" 21 | ) 22 | 23 | // An Architecture is created for each ELF Machine type, and is used to group 24 | // similar libraries and binaries in one place. This enables accounting for 25 | // multilib/multiarch builds, to enable separate reports per architecture. 26 | // 27 | // This is required because we can enable different options per architecture 28 | // in packaging, and base symbols will be different between them in almost all 29 | // cases, i.e. ld-linux* 30 | type Architecture struct { 31 | Machine elf.Machine // Corresponding machine for this configuration 32 | Symbols map[string]map[string]bool // Symbols exported for this architecture 33 | HiddenSymbols map[string]map[string]bool // Symbols found but not exported 34 | Dependencies map[string]bool // Dependencies for this architecture 35 | } 36 | 37 | // NewArchitecture will create a new Architecture and initialise the fields 38 | func NewArchitecture(m elf.Machine) *Architecture { 39 | return &Architecture{ 40 | Machine: m, 41 | Symbols: make(map[string]map[string]bool), 42 | HiddenSymbols: make(map[string]map[string]bool), 43 | Dependencies: make(map[string]bool), 44 | } 45 | } 46 | 47 | // GetSymbolsTarget will return the appropriate symbol store for the 48 | // given record, based on it's symbol visibility (soname presence) 49 | func (a *Architecture) GetSymbolsTarget(r *Record) map[string]map[string]bool { 50 | if r.Flags&RecordTypeExport == RecordTypeExport { 51 | return a.Symbols 52 | } 53 | return a.HiddenSymbols 54 | } 55 | 56 | // GetBucket will return an appropriate storage slot for the given 57 | // record. If a bucket does not exist it will be created. 58 | func (a *Report) GetBucket(record *Record) *Architecture { 59 | if arch, ok := a.Arches[record.Machine]; ok { 60 | return arch 61 | } 62 | 63 | bucket := NewArchitecture(record.Machine) 64 | a.Arches[record.Machine] = bucket 65 | return bucket 66 | } 67 | 68 | // GetPathSuffix will return an appropriate descriptor to use for the 69 | // bucket configuration. This is used in the generated filenames 70 | func (a *Architecture) GetPathSuffix() string { 71 | // TODO: Flesh out with other types. 72 | switch a.Machine { 73 | case elf.EM_X86_64: 74 | return "" 75 | case elf.EM_386: 76 | return "32" 77 | default: 78 | return a.Machine.String() 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /man/abireport.1.md: -------------------------------------------------------------------------------- 1 | % ABIREPORT(1) 2 | 3 | ## NAME 4 | 5 | `abireport -- Generate ELF ABI reports` 6 | 7 | ## SYNOPSIS 8 | 9 | `abireport [subcommand] ` 10 | 11 | 12 | ## DESCRIPTION 13 | 14 | `abireport(1)` is tool to generate an ABI (Application Binary Interface) report 15 | from ELF binaries. In either operational mode, it will scan all dynamic libraries 16 | and executables that it encounteres, and emitting files to describe the ABI. 17 | 18 | These file names may be different if you pass the `-p`,`--prefix` option. Also 19 | note that for a non `x86_64` architecture, a unique suffix will be used for the 20 | report file to enable tracking of multilib/multiarch configurations. The default 21 | filename suffix for `x86` is `32`. 22 | 23 | * `symbols` 24 | 25 | A file containing a `$soname`:`$symbol` mapping for the entire scanned set. 26 | This file is sorted first by `$soname`, i.e. `libz.so.1`, and all symbols 27 | for that library are listed, sorted by alphabetical order. 28 | 29 | Running the tool will automatically truncate this file if it exists prior 30 | to creating a new report. 31 | 32 | * `used_libs` 33 | 34 | A file containing an alphabetically sorted list of dependencies required 35 | for the given data set. Any symbol that one of the files depends on that 36 | can be satisfied within the given data, set, even if it is not **exported** 37 | as a symbol, will omit the `$soname` dependency from the list. 38 | 39 | Used libs are determined by the `DT_NEEDED` section of the ELF file. 40 | 41 | Running the tool will automatically truncate this file if it exists prior 42 | to creating a new report. 43 | 44 | ## OPTIONS 45 | 46 | These options apply to all subcommands within `abireport(1)`. 47 | 48 | * `-p`, `--prefix` 49 | 50 | Set the prefix used in filenames created by `abireport(1)`. This may be 51 | used to assist integration by providing more unique filenames. 52 | 53 | * `-D`, `--output-dir` 54 | 55 | Set the output directory for files created by `abireport(1)`. This is 56 | limited strictly to the report files. 57 | 58 | This option defaults to the current working directory (`.`). 59 | 60 | * `-h`, `--help` 61 | 62 | Help provides an explanation for any command or subcommand. Without any 63 | specified subcommands it will list the main subcommands for the application. 64 | 65 | 66 | ## SUBCOMMANDS 67 | 68 | Subcommands are mutually exclusive, you may only use one at a time. Note that 69 | all subcommands respect the global flags. 70 | 71 | ### scan-tree [root] 72 | 73 | Generate a report from the contents of the indicated root directory. 74 | It is assumed that this is the true root of that filesystem, containing a 75 | legitimate hierarchy, i.e. `/usr/lib`, etc. 76 | 77 | 78 | ### scan-packages [package] [package] 79 | 80 | Generate a report from the contents of the given packages. They 81 | will all be extracted into a temporary directory and analysed in 82 | one go. You may pass multiple file names and directories here. 83 | 84 | When using directories, `abireport(1)` will not recurse, it 85 | will only look for a glob pattern of **supported** package types: 86 | 87 | * `*.rpm` - requires `rpm2cpio` and `cpio` on the host 88 | * `*.deb` - requires `dpkg` on the host 89 | * `*.eokpg` - requires `uneopkg` on the host. 90 | 91 | 92 | ### version 93 | 94 | Print the version and copyright notice of `abireport(1)` and exit. 95 | 96 | 97 | ## EXIT STATUS 98 | 99 | On success, 0 is returned. A non-zero return code signals a failure. 100 | 101 | 102 | ## COPYRIGHT 103 | 104 | * Copyright © 2016 Intel Corporation, License: CC-BY-SA-3.0 105 | 106 | 107 | ## NOTES 108 | 109 | Creative Commons Attribution-ShareAlike 3.0 Unported 110 | 111 | * http://creativecommons.org/licenses/by-sa/3.0/ 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | abireport 2 | ---------- 3 | 4 | [![Report](https://goreportcard.com/badge/github.com/clearlinux/abireport)](https://goreportcard.com/report/github.com/clearlinux/abireport) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 5 | 6 | 7 | A distro-agnostic tool for creating Application Binary Interface (`ABI`) reports from a set of binary packages. This tool is intended to assist packagers & developers of Linux distributions. It should be run at the end of the build, or even during the final stages of the build using the `scan-tree` root to be as unobtrusive as possible. 8 | 9 | The tool, when invoked with `scan-packages`, will explode the packages requested, and perform an analysis of the binaries inside. From this, we create two files: 10 | 11 | **symbols** 12 | 13 | A `$soname`:`$symbol` mapping which describes the ABI of the package as a whole. This is sorted first by soname, second by symbol name: 14 | 15 | libgdk-3.so.0:gdk_x11_window_set_utf8_property 16 | libgdk-3.so.0:gdk_x11_xatom_to_atom 17 | libgdk-3.so.0:gdk_x11_xatom_to_atom_for_display 18 | libgtk-3.so.0:gtk_about_dialog_add_credit_section 19 | libgtk-3.so.0:gtk_about_dialog_get_artists 20 | libgtk-3.so.0:gtk_about_dialog_get_authors 21 | 22 | 23 | **used_libs** 24 | 25 | This file contains an alphabetically sorted list of binary dependencies for the package(s) as a whole. This is taken from the `DT_NEEDED` ELF tag. This helps to verify that a change to the package has really taken, such as using new ABI (soname version) or a new library appearing in the list due to enabling. 26 | 27 | libX11.so.6 28 | libXcomposite.so.1 29 | libXcursor.so.1 30 | libXdamage.so.1 31 | libXext.so.6 32 | 33 | 34 | These files, when used with diff tools (i.e. `git diff`) make it very trivial to anticipite and deal with ABI breaks. 35 | 36 | **Multiple architectures** 37 | 38 | In many distributions, multilib or multiarch is employed. `abireport` will assign a unique suffix to each of these architectures to have a view on a per architecture basis. Currently, an `x86_64` file will have no suffix, and `x86` file will have the `32` suffix, etc. If you need a suffix added, please just open an issue. 39 | 40 | Integrating 41 | ----------- 42 | 43 | After your normal build routine, issue a call to `abireport scan-packages`. Note that this will decompress the binary packages in the directory first, so for maximum performance you should integrate into the build system itself. By default `abireport` is highly parallel so almost all of the time spent executing abireport is the decompression of packages. 44 | 45 | Currently, `abireport` knows how to handle 3 package types: 46 | 47 | - `*.deb` 48 | - `*.rpm` 49 | - `*.eopkg` 50 | 51 | More will be accepted by issue or pull request. In the event of a pull request, please ensure you run `make compliant` before sending, to ensure speedy integration of your code. 52 | 53 | You may encapsulate `abireport` by using the `scan-tree` command after having decompressed the files yourself. If used against the true package build root, this zero-copy approach is significantly faster. 54 | 55 | You should always store the current abireport results in your tree history, i.e. in git. Subsequent rebuilds will then automatically create a diff so that you can immediately see any actions that should be taken. 56 | 57 | 58 | Implementation details 59 | ---------------------- 60 | 61 | The dependencies are evaluated using the `DT_NEEDED` tag, thus only direct dependencies are considered. Before emitting the report, `abireport` will check in the library names (`ET_DYN` files) to see if the name is provided. If so, it is omitted. 62 | 63 | Symbols are only exported if they meet certain export criteria. That is, they must be an `ET_DYN` ELF with a valid `soname`, and living in a valid library directory. That means that `RPATH`-bound libraries are not exported. 64 | 65 | This may affect some package which use a private RPATH'd library. From the viewpoint of `abireport`, such private libraries do not constitute a true ABI, given that many distributions are opposed to the use of `RPATH`. In effect, these are actually plugins (unversioned libraries). 66 | 67 | License 68 | ------- 69 | 70 | `Apache-2.0` 71 | 72 | Copyright © 2016-2017 Intel Corporation 73 | -------------------------------------------------------------------------------- /libabi/analyze.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2016-2017 Intel Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package libabi 18 | 19 | import ( 20 | "debug/elf" 21 | "fmt" 22 | "path/filepath" 23 | "strings" 24 | ) 25 | 26 | // isLibraryDir will determine if a path is a valid library path worth 27 | // exporting, i.e. /usr/lib64, etc. 28 | func (a *Report) isLibraryDir(dir string) bool { 29 | for _, p := range a.libDirs { 30 | if dir == p { 31 | return true 32 | } 33 | } 34 | return false 35 | } 36 | 37 | // analyzeLibrary will examine the given shared library and populate 38 | // the soname and symbols fields of the record 39 | func (a *Report) analyzeLibrary(record *Record, file *elf.File) error { 40 | dynstring, err := file.DynString(elf.DT_SONAME) 41 | if err != nil { 42 | return err 43 | } 44 | 45 | // Export the symbol when we find a soname 46 | if len(dynstring) > 0 { 47 | record.Name = dynstring[0] 48 | record.Flags |= RecordTypeExport 49 | } else { 50 | record.Name = filepath.Base(record.Path) 51 | } 52 | 53 | // Unexport anything not in a library directory 54 | dirName := filepath.Dir(record.Path) 55 | if !a.isLibraryDir(dirName) && record.Flags&RecordTypeExport == RecordTypeExport { 56 | record.Flags ^= RecordTypeExport 57 | } 58 | 59 | symbols, err := file.DynamicSymbols() 60 | if err != nil { 61 | return err 62 | } 63 | 64 | nSections := elf.SectionIndex(len(file.Sections)) 65 | 66 | // Try out best to emulate nm -g --defined-only --dynamic behaviour, 67 | // as used in autospec's older abireport. 68 | for _, sym := range symbols { 69 | // We only care for defined symbols 70 | // We want things in the .text section, and absolute symbols. 71 | inText := false 72 | sbind := elf.ST_BIND(sym.Info) 73 | // Skip weak symbols 74 | if sbind&elf.STB_WEAK == elf.STB_WEAK { 75 | continue 76 | } 77 | if sym.Section < nSections && file.Sections[sym.Section].Name == ".text" { 78 | inText = true 79 | } 80 | // If its not an absolute *and* its not in text, skip it too. 81 | if sym.Section&elf.SHN_ABS != elf.SHN_ABS && !inText { 82 | continue 83 | } 84 | symType := elf.ST_TYPE(sym.Info) 85 | // Skip STT_GNU_IFUNC 86 | if symType == elf.STT_LOOS { 87 | continue 88 | } 89 | // Skip unnamed ABI 90 | nom := strings.TrimSpace(sym.Name) 91 | if nom == "" { 92 | continue 93 | } 94 | record.Symbols = append(record.Symbols, nom) 95 | } 96 | return nil 97 | } 98 | 99 | // AnalyzeOne will attempt to analyze the given record, and store 100 | // the appropriate details for a later report. 101 | func (a *Report) AnalyzeOne(record *Record) error { 102 | file, err := elf.Open(record.Path) 103 | if err != nil { 104 | return err 105 | } 106 | defer file.Close() 107 | 108 | // Determine if it's a shared library or executable 109 | if file.FileHeader.Type == elf.ET_DYN { 110 | record.Flags |= RecordTypeLibrary 111 | } else if file.FileHeader.Type == elf.ET_EXEC { 112 | record.Flags |= RecordTypeExecutable 113 | } else { 114 | return nil 115 | } 116 | 117 | // Analyze library elements 118 | if record.Flags&RecordTypeLibrary == RecordTypeLibrary { 119 | if err := a.analyzeLibrary(record, file); err != nil { 120 | return err 121 | } 122 | } else { 123 | // Set name to the executable name. 124 | record.Name = filepath.Base(record.Path) 125 | } 126 | 127 | // Set the appropriate class 128 | if file.FileHeader.Class == elf.ELFCLASS32 { 129 | record.Flags |= RecordType32bit 130 | } else if file.FileHeader.Class == elf.ELFCLASS64 { 131 | record.Flags |= RecordType64bit 132 | } else { 133 | return fmt.Errorf("Unknown ELF Class: %s", record.Path) 134 | } 135 | 136 | // Store the machine also 137 | record.Machine = file.FileHeader.Machine 138 | 139 | // Grab the required dependencies 140 | used, err := file.DynString(elf.DT_NEEDED) 141 | if err != nil { 142 | return err 143 | } 144 | 145 | record.Dependencies = used 146 | 147 | return nil 148 | } 149 | -------------------------------------------------------------------------------- /libabi/report.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2016-2017 Intel Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package libabi 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | "path/filepath" 23 | "sort" 24 | ) 25 | 26 | var ( 27 | // KnownExtensions is a set of filename extensions applied by the 28 | // Architecture's GetPathSuffix function. We use this in our 29 | // TruncateAll function. 30 | KnownExtensions = []string{ 31 | "", 32 | "32", 33 | } 34 | 35 | // ReportOutputDir is where report files will be dumped to. This 36 | // is set to the current working directory by default. 37 | ReportOutputDir = "." 38 | ) 39 | 40 | // TruncateAll will truncate all files matching the current prefix 41 | // with all known extensions in abireport. We do this to ensure that 42 | // missing libs & reports are made obvious in git diffs. 43 | func TruncateAll(prefix string) error { 44 | for _, ext := range KnownExtensions { 45 | p1 := filepath.Join(ReportOutputDir, fmt.Sprintf("%ssymbols%s", prefix, ext)) 46 | p2 := filepath.Join(ReportOutputDir, fmt.Sprintf("%sused_libs%s", prefix, ext)) 47 | 48 | if err := truncateFile(p1); err != nil { 49 | return err 50 | } 51 | if err := truncateFile(p2); err != nil { 52 | return err 53 | } 54 | } 55 | return nil 56 | } 57 | 58 | // PathExists is a simple test for path existence 59 | func PathExists(p string) bool { 60 | if st, err := os.Stat(p); err == nil && st != nil { 61 | return true 62 | } 63 | return false 64 | } 65 | 66 | // truncateFile will truncate the file only if it already existed 67 | func truncateFile(path string) error { 68 | if !PathExists(path) { 69 | return nil 70 | } 71 | fi, err := os.Create(path) 72 | if err != nil { 73 | return err 74 | } 75 | fi.Close() 76 | return nil 77 | } 78 | 79 | // writeSymbols will take care of writing out all the symbols provided by 80 | // the given Architecture bucket, in a $soname:$symbol mapping, sorted first 81 | // by soname, second by symbol. 82 | func (a *Report) writeSymbols(prefix string, bucket *Architecture) error { 83 | suffix := bucket.GetPathSuffix() 84 | symbolsPath := filepath.Join(ReportOutputDir, fmt.Sprintf("%ssymbols%s", prefix, suffix)) 85 | 86 | // Grab the sonames 87 | var sonames []string 88 | for key := range bucket.Symbols { 89 | sonames = append(sonames, key) 90 | } 91 | sort.Strings(sonames) 92 | 93 | if len(sonames) < 1 { 94 | // Truncate the file if it did exist. 95 | if err := truncateFile(symbolsPath); err != nil { 96 | return err 97 | } 98 | return nil 99 | } 100 | 101 | // The symbols file 102 | symsFi, err := os.Create(symbolsPath) 103 | if err != nil { 104 | return err 105 | } 106 | defer symsFi.Close() 107 | 108 | // Emit soname:symbol mapping 109 | for _, soname := range sonames { 110 | var foundSymbols []string 111 | for symbol := range bucket.Symbols[soname] { 112 | foundSymbols = append(foundSymbols, symbol) 113 | } 114 | sort.Strings(foundSymbols) 115 | for _, symbol := range foundSymbols { 116 | if _, err = fmt.Fprintf(symsFi, "%s:%s\n", soname, symbol); err != nil { 117 | return err 118 | } 119 | } 120 | } 121 | return nil 122 | } 123 | 124 | // writeDeps will write out a sorted list of soname's that this architecture 125 | // bucket depends on. 126 | // It will also filter out the names that are provided to generate a true 127 | // reported based on DT_NEEDED requirements. 128 | func (a *Report) writeDeps(prefix string, bucket *Architecture) error { 129 | suffix := bucket.GetPathSuffix() 130 | depsPath := filepath.Join(ReportOutputDir, fmt.Sprintf("%sused_libs%s", prefix, suffix)) 131 | 132 | // Emit dependencies 133 | var depNames []string 134 | for nom := range bucket.Dependencies { 135 | // Skip provided 136 | if _, ok := bucket.Symbols[nom]; ok { 137 | continue 138 | } 139 | if _, ok := bucket.HiddenSymbols[nom]; ok { 140 | continue 141 | } 142 | depNames = append(depNames, nom) 143 | } 144 | sort.Strings(depNames) 145 | 146 | if len(depNames) < 1 { 147 | if err := truncateFile(depsPath); err != nil { 148 | return err 149 | } 150 | return nil 151 | } 152 | 153 | // The "used_libs" dependencies file 154 | depsFi, err := os.Create(depsPath) 155 | if err != nil { 156 | return err 157 | } 158 | defer depsFi.Close() 159 | 160 | for _, dep := range depNames { 161 | if _, err = fmt.Fprintf(depsFi, "%s\n", dep); err != nil { 162 | return err 163 | } 164 | } 165 | 166 | return nil 167 | } 168 | 169 | // Report will dump the report to the given writer for the specified 170 | // machine configuration 171 | func (a *Report) Report(prefix string, bucket *Architecture) error { 172 | if err := a.writeSymbols(prefix, bucket); err != nil { 173 | return err 174 | } 175 | 176 | return a.writeDeps(prefix, bucket) 177 | } 178 | -------------------------------------------------------------------------------- /abi-report/cmd/scan_packages.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2016-2017 Intel Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package cmd 18 | 19 | import ( 20 | "fmt" 21 | "github.com/clearlinux/abireport/explode" 22 | "github.com/clearlinux/abireport/libabi" 23 | "github.com/spf13/cobra" 24 | "os" 25 | "path/filepath" 26 | ) 27 | 28 | // RootCmd is the "default command" of abireport 29 | var scanPkgsCommand = &cobra.Command{ 30 | Use: "scan-packages", 31 | Short: "Extract packages and generate report", 32 | Long: `Extract a set of packages, and create an ABI Report based 33 | from their root directory. 34 | 35 | If you do not pass a list of packages or a directory to scan, abireport 36 | will look for them in the current directory.`, 37 | Run: scanPackages, 38 | } 39 | 40 | // Selected glob type 41 | var exploderType string 42 | 43 | func init() { 44 | RootCmd.AddCommand(scanPkgsCommand) 45 | } 46 | 47 | // scanPackages is the CLI handler for "scan-packages". 48 | func scanPackages(cmd *cobra.Command, args []string) { 49 | 50 | // If no path is passed, use the current path 51 | var searchLocations []string 52 | if len(args) < 1 { 53 | searchLocations = append(searchLocations, ".") 54 | } else { 55 | searchLocations = append(searchLocations, args...) 56 | } 57 | 58 | extracts, err := locatePackages(searchLocations) 59 | if err != nil { 60 | fmt.Fprintf(os.Stderr, "%v\n", err) 61 | os.Exit(1) 62 | } 63 | 64 | // i.e. passing a glob of *.src.rpm 65 | if len(extracts) < 1 { 66 | fmt.Fprintf(os.Stderr, "No usable packages found for extraction\n") 67 | os.Exit(1) 68 | } 69 | 70 | abi, err := explodeAndScan(extracts) 71 | if err != nil { 72 | fmt.Fprintf(os.Stderr, "Error in explode step: %v\n", err) 73 | os.Exit(1) 74 | } 75 | 76 | // Ensure we clean up existing reports 77 | if err = libabi.TruncateAll(Prefix); err != nil { 78 | fmt.Fprintf(os.Stderr, "Cannot truncate existing reports: %v\n", err) 79 | os.Exit(1) 80 | } 81 | 82 | // Finally, create the report 83 | for _, arch := range abi.Arches { 84 | if err := abi.Report(Prefix, arch); err != nil { 85 | fmt.Fprintf(os.Stderr, "Cannot generate report: %v\n", err) 86 | os.Exit(1) 87 | } 88 | } 89 | } 90 | 91 | // explodeAndScan will take care of exploding the packages, ensuring 92 | // that a deferred cleanup always happen. 93 | // Should it be successful, it will return a new ABI report that 94 | // has walked the root. 95 | func explodeAndScan(packages []string) (*libabi.Report, error) { 96 | exFunc := explode.Impls[exploderType] 97 | defer func() { 98 | if explode.OutputDir != "" && libabi.PathExists(explode.OutputDir) { 99 | os.RemoveAll(explode.OutputDir) 100 | } 101 | }() 102 | // Actually extract them 103 | root, err := exFunc(packages) 104 | if err != nil { 105 | return nil, err 106 | } 107 | 108 | // Generate the ABI Report walker 109 | abi, err := libabi.NewReport(root) 110 | if err != nil { 111 | return nil, err 112 | } 113 | 114 | // Walk the exploded package tree 115 | if err = abi.Walk(); err != nil { 116 | return nil, err 117 | } 118 | 119 | // Pass off to next step 120 | return abi, nil 121 | } 122 | 123 | // locatePackages will do the initial ground work of locating the 124 | // packages. 125 | func locatePackages(searchLocations []string) ([]string, error) { 126 | var discoveredPkgs []string 127 | 128 | for _, location := range searchLocations { 129 | found, err := discoverPackages(location) 130 | if err != nil { 131 | return nil, fmt.Errorf("error locating packages: %v", err) 132 | } 133 | discoveredPkgs = append(discoveredPkgs, found...) 134 | } 135 | 136 | var extracts []string 137 | for _, e := range discoveredPkgs { 138 | if explode.ShouldSkipName(e) { 139 | fmt.Fprintf(os.Stderr, "debug: Skipping '%s'\n", e) 140 | continue 141 | } 142 | extracts = append(extracts, e) 143 | } 144 | return extracts, nil 145 | } 146 | 147 | // discoverPackages will look at the given path and return either a new 148 | // slice of found packages, or an error. 149 | func discoverPackages(where string) ([]string, error) { 150 | st, err := os.Stat(where) 151 | if err != nil { 152 | return nil, err 153 | } 154 | 155 | // Search for a valid set of packages in the given directory 156 | if st.IsDir() { 157 | var paths []string 158 | var exp string 159 | for glo := range explode.Impls { 160 | fp := filepath.Join(where, glo) 161 | glob, _ := filepath.Glob(fp) 162 | if len(glob) > 0 { 163 | paths = append(paths, glob...) 164 | exp = glo 165 | break 166 | } 167 | } 168 | if len(paths) > 0 { 169 | // Ensure exploder type didn't change 170 | if exploderType != "" && exp != exploderType { 171 | return nil, fmt.Errorf("Cannot mix package type '%s' with '%s'", exp, exploderType) 172 | } 173 | exploderType = exp 174 | return paths, nil 175 | } 176 | return nil, fmt.Errorf("No packages in directory %s", where) 177 | } 178 | 179 | // Must be a file. 180 | exp := explode.GetTypeForFilename(where) 181 | if exp == "" { 182 | return nil, fmt.Errorf("No known package handler for %v", where) 183 | } 184 | // Don't allow mixing types.. 185 | if exploderType != "" && exp != exploderType { 186 | return nil, fmt.Errorf("Cannot mix package type '%s' with '%s'", exp, exploderType) 187 | } 188 | exploderType = exp 189 | return []string{where}, nil 190 | } 191 | -------------------------------------------------------------------------------- /libabi/walker.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2016-2017 Intel Corporation 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package libabi 18 | 19 | import ( 20 | "debug/elf" 21 | "fmt" 22 | "os" 23 | "path/filepath" 24 | "runtime" 25 | "strings" 26 | "sync" 27 | ) 28 | 29 | // A Report is used to traverse a given tree and identify any and all files 30 | // that seem "interesting". 31 | type Report struct { 32 | Root string // Root directory that we're scanning 33 | Arches map[elf.Machine]*Architecture // Mapping of architectures 34 | 35 | wg *sync.WaitGroup // Our wait group for multiprocessing 36 | jobChan chan *Record // Jobs are pushed from the walker 37 | storeChan chan *Record // Single channel pulls all of the processed records 38 | libDirs []string // Valid library directories 39 | nRecords int // The total number of records encountered 40 | jobMutex *sync.Mutex // Lock for njobs decrement 41 | nJobs int // Number of jobs, decrementing through runtime 42 | } 43 | 44 | // NewReport will create a new walker instance, and attempt to initialise 45 | // the magic library 46 | func NewReport(root string) (*Report, error) { 47 | // Only want the absolute path here. 48 | if absPath, err := filepath.Abs(root); err == nil { 49 | root = absPath 50 | } else { 51 | return nil, err 52 | } 53 | libDirs := []string{ 54 | filepath.Join(root, "usr", "lib64"), 55 | filepath.Join(root, "usr", "lib"), 56 | filepath.Join(root, "usr", "lib32"), 57 | filepath.Join(root, "usr", "lib", "x86_64-linux-gnu"), 58 | filepath.Join(root, "lib", "x86_64-linux-gnu"), 59 | filepath.Join(root, "usr", "lib", "i386-linux-gnu"), 60 | filepath.Join(root, "lib", "i386-linux-gnu"), 61 | } 62 | return &Report{ 63 | Root: root, 64 | jobChan: make(chan *Record), 65 | storeChan: make(chan *Record), 66 | wg: new(sync.WaitGroup), 67 | Arches: make(map[elf.Machine]*Architecture), 68 | libDirs: libDirs, 69 | nRecords: 0, 70 | jobMutex: new(sync.Mutex), 71 | nJobs: runtime.NumCPU(), 72 | }, nil 73 | } 74 | 75 | // IsAnELF determines if a file is an ELF file or not 76 | // Loose reinterpetation of debug/elf magic checking 77 | func IsAnELF(p string) (bool, error) { 78 | o, err := os.Open(p) 79 | if err != nil { 80 | return false, err 81 | } 82 | defer o.Close() 83 | var h [16]uint8 84 | if _, err := o.ReadAt(h[0:], 0); err != nil { 85 | return false, err 86 | } 87 | 88 | if h[0] == '\x7f' && h[1] == 'E' && h[2] == 'L' && h[3] == 'F' { 89 | return true, nil 90 | } 91 | return false, nil 92 | } 93 | 94 | // recordPath will make a record of the "interesting" path if it determines 95 | // this path to be of value: i.e. a dynamic ELF file. 96 | func (a *Report) recordPath(path string) *Record { 97 | isElf, err := IsAnELF(path) 98 | if err != nil { 99 | return nil 100 | } 101 | if !isElf { 102 | return nil 103 | } 104 | 105 | record := &Record{Path: path} 106 | 107 | return record 108 | } 109 | 110 | // isPathInteresting determines whether we care about a path or not. Simply, 111 | // is it the right *type*. 112 | func (a *Report) isPathInteresting(path string, info os.FileInfo) bool { 113 | if info.IsDir() { 114 | return false 115 | } 116 | // Also care not for symlinks. 117 | if info.Mode()&os.ModeSymlink == os.ModeSymlink { 118 | return false 119 | } 120 | // ELF Header & content, rudimentary "is ELF, not empty" 121 | if info.Size() < 5 { 122 | return false 123 | } 124 | // Really don't want to examine .debug files 125 | if strings.Contains(path, "/debug/") && strings.HasSuffix(path, ".debug") { 126 | return false 127 | } 128 | return true 129 | } 130 | 131 | // walkTree is our callback for filepath.Walk, to actually do the initial 132 | // tree traversal. 133 | func (a *Report) walkTree(path string, info os.FileInfo, err error) error { 134 | // no info, i.e. failed open on file /proc/2/fd, etc. 135 | if info == nil { 136 | return nil 137 | } 138 | // Determine if the path is actually worth looking at 139 | if !a.isPathInteresting(path, info) { 140 | return nil 141 | } 142 | // Send the file off for further processing 143 | if record := a.recordPath(path); record != nil { 144 | a.jobChan <- record 145 | } 146 | return nil 147 | } 148 | 149 | // jobDone is used to close the channel once all jobs have finished 150 | func (a *Report) jobDone() { 151 | a.jobMutex.Lock() 152 | defer a.jobMutex.Unlock() 153 | a.nJobs-- 154 | 155 | if a.nJobs == 0 { 156 | close(a.storeChan) 157 | } 158 | } 159 | 160 | // the jobProcessor will keep pulling record jobs created from the walker, 161 | // which has done preliminary groundwork based on the magic type strings. 162 | func (a *Report) jobProcessor() { 163 | defer a.wg.Done() 164 | for i := 0; i < a.nJobs; i++ { 165 | go func() { 166 | defer a.wg.Done() 167 | for job := range a.jobChan { 168 | if err := a.AnalyzeOne(job); err != nil { 169 | fmt.Fprintf(os.Stderr, "Error analyzing %s: %v\n", job.Path, err) 170 | continue 171 | } 172 | a.storeChan <- job 173 | } 174 | a.jobDone() 175 | }() 176 | } 177 | defer close(a.jobChan) 178 | filepath.Walk(a.Root, a.walkTree) 179 | } 180 | 181 | // storeProcessor is responsible for storing into memory 182 | func (a *Report) storeProcessor() { 183 | defer a.wg.Done() 184 | 185 | for record := range a.storeChan { 186 | var symbolsMap map[string]bool 187 | var ok bool 188 | a.nRecords++ 189 | 190 | bucket := a.GetBucket(record) 191 | symbolsTgt := bucket.GetSymbolsTarget(record) 192 | 193 | // Ensure map is here so that the .soname provider is known 194 | if symbolsMap, ok = symbolsTgt[record.Name]; !ok { 195 | symbolsMap = make(map[string]bool) 196 | symbolsTgt[record.Name] = symbolsMap 197 | } 198 | 199 | // Store a soname -> symbol mapping 200 | // Quicker than actually using lists 201 | for _, symbol := range record.Symbols { 202 | symbolsMap[symbol] = true 203 | } 204 | 205 | for _, dep := range record.Dependencies { 206 | bucket.Dependencies[dep] = true 207 | } 208 | } 209 | } 210 | 211 | // Walk will attempt to walk the preconfigured tree, and collect a set 212 | // of "interesting" files along the way. 213 | func (a *Report) Walk() error { 214 | a.wg.Add(2 + a.nJobs) 215 | go a.jobProcessor() 216 | go a.storeProcessor() 217 | a.wg.Wait() 218 | return nil 219 | } 220 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 4 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 5 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 6 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 7 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 8 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 9 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 10 | github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= 11 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 12 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 13 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 14 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 15 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 16 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 17 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 18 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 19 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 20 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 21 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 22 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 23 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 24 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 25 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 26 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 27 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 28 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 29 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 30 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 31 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 32 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 33 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 34 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 35 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 36 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 37 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 38 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 39 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 40 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 41 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 42 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 43 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 44 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 45 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 46 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 47 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 48 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 49 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 50 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 51 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 52 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 53 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 54 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 55 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 56 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 57 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 58 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 59 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 60 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 61 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 62 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 63 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 64 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 65 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 66 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 67 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 68 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 69 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 70 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 71 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 72 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 73 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 74 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 75 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 76 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 77 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 78 | github.com/spf13/cobra v0.0.6 h1:breEStsVwemnKh2/s6gMvSdMEkwW0sK8vGStnlVBMCs= 79 | github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= 80 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 81 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 82 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 83 | github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= 84 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 85 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 86 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 87 | github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= 88 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 89 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 90 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 91 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 92 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 93 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 94 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 95 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 96 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 97 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 98 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 99 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 100 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 101 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 102 | golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 103 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 104 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 105 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 106 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 107 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 108 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 109 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 110 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 111 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 112 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 113 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 114 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 115 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 116 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 117 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 118 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 119 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 120 | google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 121 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 122 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 123 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 124 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 125 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 126 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 127 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 128 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 129 | --------------------------------------------------------------------------------