go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/resources/packages/opkg_packages.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package packages
     5  
     6  import (
     7  	"bufio"
     8  	"fmt"
     9  	"io"
    10  	"regexp"
    11  	"strings"
    12  
    13  	"github.com/rs/zerolog/log"
    14  	"go.mondoo.com/cnquery/providers/os/connection/shared"
    15  )
    16  
    17  const (
    18  	OpkgPkgFormat = "opkg"
    19  )
    20  
    21  var OPKG_LIST_PACKAGE_REGEX = regexp.MustCompile(`^([\w\d\-]+)\s-\s([\w\d\-\.]+)$`)
    22  
    23  // ParseOpkgListPackagesCommand parses the output of `opkg list-installed`
    24  func ParseOpkgListPackagesCommand(input io.Reader) []Package {
    25  	pkgs := []Package{}
    26  	scanner := bufio.NewScanner(input)
    27  	for scanner.Scan() {
    28  		line := scanner.Text()
    29  		m := OPKG_LIST_PACKAGE_REGEX.FindStringSubmatch(line)
    30  		if m != nil {
    31  			pkgs = append(pkgs, Package{
    32  				Name:    m[1],
    33  				Version: m[2],
    34  				Format:  OpkgPkgFormat,
    35  			})
    36  		}
    37  	}
    38  	return pkgs
    39  }
    40  
    41  var (
    42  	OPKG_REGEX        = regexp.MustCompile(`^(.+):\s(.+)$`)
    43  	OPKG_ORIGIN_REGEX = regexp.MustCompile(`^\s*([^\(]*)(?:\((.*)\))?\s*$`)
    44  )
    45  
    46  // ParseOpkgPackages parses the opkg database content located in:
    47  // `/var/lib/opkg/status` or `/usr/lib/opkg/status`
    48  func ParseOpkgPackages(input io.Reader) ([]Package, error) {
    49  	const STATE_RESET = 0
    50  	const STATE_DESC = 1
    51  	pkgs := []Package{}
    52  
    53  	add := func(pkg Package) {
    54  		// do sanitization checks to ensure we have minimal information
    55  		if pkg.Name != "" && pkg.Version != "" {
    56  			pkgs = append(pkgs, pkg)
    57  		} else {
    58  			log.Debug().Msg("ignored opkg packages since information is missing")
    59  		}
    60  	}
    61  
    62  	scanner := bufio.NewScanner(input)
    63  	pkg := Package{Format: OpkgPkgFormat}
    64  	state := STATE_RESET
    65  	var key string
    66  	for scanner.Scan() {
    67  		line := scanner.Text()
    68  
    69  		// reset package definition once we reach a newline
    70  		if len(line) == 0 {
    71  			add(pkg)
    72  			pkg = Package{Format: OpkgPkgFormat}
    73  		}
    74  
    75  		m := OPKG_REGEX.FindStringSubmatch(line)
    76  		key = ""
    77  		if m != nil {
    78  			key = m[1]
    79  			state = STATE_RESET
    80  		}
    81  		switch {
    82  		case key == "Package":
    83  			pkg.Name = strings.TrimSpace(m[2])
    84  		case key == "Version":
    85  			pkg.Version = strings.TrimSpace(m[2])
    86  		case key == "Architecture":
    87  			pkg.Arch = strings.TrimSpace(m[2])
    88  		case key == "Status":
    89  			pkg.Status = strings.TrimSpace(m[2])
    90  		case key == "Source":
    91  			o := OPKG_ORIGIN_REGEX.FindStringSubmatch(m[2])
    92  			if o != nil && len(o) >= 1 {
    93  				pkg.Origin = strings.TrimSpace(o[1])
    94  			} else {
    95  				log.Error().Str("origin", m[2]).Msg("cannot parse opkg origin")
    96  			}
    97  		// description supports multi-line statements, start desc
    98  		case key == "Description":
    99  			pkg.Description = strings.TrimSpace(m[2])
   100  			state = STATE_DESC
   101  		// next desc line, append to previous one
   102  		case state == STATE_DESC:
   103  			pkg.Description += "\n" + strings.TrimSpace(line)
   104  		}
   105  	}
   106  
   107  	// if the last line is not an empty line we have things in flight, lets check it
   108  	add(pkg)
   109  
   110  	return pkgs, nil
   111  }
   112  
   113  type OpkgPkgManager struct {
   114  	conn shared.Connection
   115  }
   116  
   117  func (opkg *OpkgPkgManager) Name() string {
   118  	return "Opkg Package Manager"
   119  }
   120  
   121  func (opkg *OpkgPkgManager) Format() string {
   122  	return OpkgPkgFormat
   123  }
   124  
   125  func (opkg *OpkgPkgManager) List() ([]Package, error) {
   126  	// if we can run commands, we can use `opkg list-installed`
   127  	if opkg.conn.Capabilities().Has(shared.Capability_RunCommand) {
   128  		cmd, err := opkg.conn.RunCommand("opkg list-installed")
   129  		if err != nil {
   130  			return nil, fmt.Errorf("could not read package list")
   131  		}
   132  		return ParseOpkgListPackagesCommand(cmd.Stdout), nil
   133  	}
   134  
   135  	// otherwise let's try to read the package list from file
   136  	return opkg.ListFromFile()
   137  }
   138  
   139  func (opkg *OpkgPkgManager) ListFromFile() ([]Package, error) {
   140  	fs := opkg.conn.FileSystem()
   141  	opkgStatusFiles := []string{
   142  		"/usr/lib/opkg/status",
   143  		"/var/lib/opkg/status",
   144  	}
   145  
   146  	var opkgStatusFile string
   147  	for _, f := range opkgStatusFiles {
   148  		_, err := fs.Stat(f)
   149  		if err == nil {
   150  			opkgStatusFile = f
   151  			break
   152  		}
   153  	}
   154  
   155  	if opkgStatusFile == "" {
   156  		return nil, fmt.Errorf("could not find opkg package list")
   157  	}
   158  
   159  	fi, err := fs.Open(opkgStatusFile)
   160  	if err != nil {
   161  		return nil, fmt.Errorf("could not read opkg package list")
   162  	}
   163  	defer fi.Close()
   164  
   165  	list, err := ParseOpkgPackages(fi)
   166  	if err != nil {
   167  		return nil, fmt.Errorf("could not parse opkg package list")
   168  	}
   169  	return list, nil
   170  }
   171  
   172  func (opkg *OpkgPkgManager) Available() (map[string]PackageUpdate, error) {
   173  	return map[string]PackageUpdate{}, nil
   174  }