Commit d7e13eb9 authored by songlinfeng's avatar songlinfeng
Browse files

add dtk-container-toolkit

parent fcdba4f3
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package discover
// None is a null discoverer that returns an empty list of devices and
// mounts.
type None struct{}
var _ Discover = (*None)(nil)
// Devices returns an empty list of devices
func (e None) Devices() ([]Device, error) {
return []Device{}, nil
}
// Mounts returns an empty list of mounts
func (e None) Mounts() ([]Mount, error) {
return []Mount{}, nil
}
// Hooks returns an empty list of hooks
func (e None) Hooks() ([]Hook, error) {
return []Hook{}, nil
}
// AdditionalGIDs return an empty list of additionalGIDs
func (e None) AdditionalGIDs() ([]uint32, error) {
return []uint32{}, nil
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package discover
import (
"dtk-container-toolkit/internal/logger"
"dtk-container-toolkit/internal/lookup"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
)
type pciMounts struct {
None
logger logger.Interface
lookup lookup.Locator
root string
required []string
sync.Mutex
cache []Mount
}
var _ Discover = (*pciMounts)(nil)
// NewPciMounts creates a discoverer for the required pci mounts using the specified locator.
func NewPciMounts(logger logger.Interface, lookup lookup.Locator, root string, required []string) Discover {
return &pciMounts{
logger: logger,
lookup: lookup,
root: root,
required: required,
}
}
func (d *pciMounts) Mounts() ([]Mount, error) {
if d.lookup == nil {
return nil, fmt.Errorf("no lookup defined")
}
if d.cache != nil {
d.logger.Debugf("returning cached mounts")
return d.cache, nil
}
d.Lock()
defer d.Unlock()
d.logger.Infof("Locating %v in %v", d.required, d.root)
uniqueMounts := make(map[string]Mount)
for _, candidate := range d.required {
d.logger.Infof("Locating %v", candidate)
located, err := d.lookup.Locate(candidate)
if err != nil {
d.logger.Warningf("Could not locate %v: %v", candidate, err)
continue
}
if len(located) == 0 {
d.logger.Warningf("Missing %v", candidate)
continue
}
d.logger.Debugf("Located %v as %v", candidate, located)
for _, p := range located {
node, err := os.Readlink(p)
if err != nil {
d.logger.Warningf("Failed to evaluate symlink %v; ignoring", p)
continue
}
if !filepath.IsAbs(node) {
node = filepath.Join(filepath.Dir(p), node)
}
if _, ok := uniqueMounts[node]; ok {
d.logger.Debugf("Skipping duplicate mount %v", node)
continue
}
r := d.relativeTo(node)
if r == "" {
r = node
}
d.logger.Infof("Selecting %v as %v", node, r)
uniqueMounts[node] = Mount{
HostPath: node,
Path: r,
Options: []string{
"rbind",
"rprivate",
},
}
}
}
var mounts []Mount
for _, m := range uniqueMounts {
mounts = append(mounts, m)
}
d.cache = mounts
return d.cache, nil
}
// relativeTo returns the path relative to the root for the file locator
func (d *pciMounts) relativeTo(path string) string {
if d.root == "/" {
return path
}
return strings.TrimPrefix(path, d.root)
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package edits
import (
"dtk-container-toolkit/internal/discover"
"os"
"tags.cncf.io/container-device-interface/pkg/cdi"
"tags.cncf.io/container-device-interface/specs-go"
)
type device discover.Device
// toEdits converts a discovered device to CDI Container Edits.
func (d device) toEdits() (*cdi.ContainerEdits, error) {
deviceNode, err := d.toSpec()
if err != nil {
return nil, err
}
e := cdi.ContainerEdits{
ContainerEdits: &specs.ContainerEdits{
DeviceNodes: []*specs.DeviceNode{deviceNode},
},
}
return &e, nil
}
// toSpec converts a discovered Device to a CDI Spec Device. Note
// that missing info is filled in when edits are applied by querying the Device node.
func (d device) toSpec() (*specs.DeviceNode, error) {
// The HostPath field was added in the v0.5.0 CDI specification.
// The cdi package uses strict unmarshalling when loading specs from file causing failures for
// unexpected fields.
// Since the behaviour for HostPath == "" and HostPath == Path are equivalent, we clear HostPath
// if it is equal to Path to ensure compatibility with the widest range of specs.
hostPath := d.HostPath
if hostPath == d.Path {
hostPath = ""
}
s := specs.DeviceNode{
HostPath: hostPath,
Path: d.Path,
}
if hostPath == "/dev/mem" {
fileMode := os.FileMode(0666)
s.FileMode = &fileMode
s.Permissions = "rw"
}
return &s, nil
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package edits
import (
"dtk-container-toolkit/internal/discover"
"dtk-container-toolkit/internal/logger"
"dtk-container-toolkit/internal/oci"
"fmt"
"tags.cncf.io/container-device-interface/pkg/cdi"
"tags.cncf.io/container-device-interface/specs-go"
ociSpecs "github.com/opencontainers/runtime-spec/specs-go"
)
type edits struct {
cdi.ContainerEdits
logger logger.Interface
}
// NewSpecEdits creates a SpecModifier that defines the required OCI spec edits (as CDI ContainerEdits) from the specified
// discoverer.
func NewSpecEdits(logger logger.Interface, d discover.Discover) (oci.SpecModifier, error) {
c, err := FromDiscoverer(logger, d)
if err != nil {
return nil, fmt.Errorf("error constructing container edits: %v", err)
}
e := edits{
ContainerEdits: *c,
logger: logger,
}
return &e, nil
}
// FromDiscoverer creates CDI container edits for the specified discoverer.
func FromDiscoverer(logger logger.Interface, d discover.Discover) (*cdi.ContainerEdits, error) {
devices, err := d.Devices()
if err != nil {
return nil, fmt.Errorf("failed to discover devices: %v", err)
}
mounts, err := d.Mounts()
if err != nil {
return nil, fmt.Errorf("failed to discover mounts: %v", err)
}
hooks, err := d.Hooks()
if err != nil {
return nil, fmt.Errorf("failed to discover hooks: %v", err)
}
gids, err := d.AdditionalGIDs()
if err != nil {
return nil, fmt.Errorf("failed to discover additionalGids: %v", err)
}
c := NewContainerEdits()
for _, d := range devices {
edits, err := device(d).toEdits()
if err != nil {
return nil, fmt.Errorf("failed to created container edits for device: %v", err)
}
c.Append(edits)
}
for _, m := range mounts {
c.Append(mount(m).toEdits())
}
for _, h := range hooks {
c.Append(hook(h).toEdits())
}
c.Append(&cdi.ContainerEdits{
ContainerEdits: &specs.ContainerEdits{
AdditionalGIDs: gids,
},
})
return c, nil
}
// NewContainerEdits is a utility function to create a CDI ContainerEdits struct.
func NewContainerEdits() *cdi.ContainerEdits {
c := cdi.ContainerEdits{
ContainerEdits: &specs.ContainerEdits{},
}
return &c
}
// Modify applies the defined edits to the incoming OCI spec
func (e *edits) Modify(spec *ociSpecs.Spec) error {
if e == nil || e.ContainerEdits.ContainerEdits == nil {
return nil
}
e.logger.Info("Mounts:")
for _, mount := range e.Mounts {
e.logger.Infof("Mounting %v at %v", mount.HostPath, mount.ContainerPath)
}
e.logger.Infof("Devices:")
for _, device := range e.DeviceNodes {
e.logger.Infof("Injecting %v", device.Path)
}
e.logger.Infof("Hooks:")
for _, hook := range e.Hooks {
e.logger.Infof("Injecting %v %v", hook.Path, hook.Args)
}
return e.Apply(spec)
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package edits
import (
"dtk-container-toolkit/internal/discover"
"tags.cncf.io/container-device-interface/pkg/cdi"
"tags.cncf.io/container-device-interface/specs-go"
)
type hook discover.Hook
// toEdits converts a discovered hook to CDI Container Edits.
func (d hook) toEdits() *cdi.ContainerEdits {
e := cdi.ContainerEdits{
ContainerEdits: &specs.ContainerEdits{
Hooks: []*specs.Hook{d.toSpec()},
},
}
return &e
}
// toSpec converts a discovered Hook to a CDI Spec Hook. Note
// that missing info is filled in when edits are applied by querying the Hook node.
func (d hook) toSpec() *specs.Hook {
s := specs.Hook{
HookName: d.Lifecycle,
Path: d.Path,
Args: d.Args,
}
return &s
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package edits
import (
"dtk-container-toolkit/internal/discover"
"tags.cncf.io/container-device-interface/pkg/cdi"
"tags.cncf.io/container-device-interface/specs-go"
)
type mount discover.Mount
// toEdits converts a discovered mount to CDI Container Edits.
func (d mount) toEdits() *cdi.ContainerEdits {
e := cdi.ContainerEdits{
ContainerEdits: &specs.ContainerEdits{
Mounts: []*specs.Mount{d.toSpec()},
},
}
return &e
}
// toSpec converts a discovered Mount to a CDI Spec Mount. Note
// that missing info is filled in when edits are applied by querying the Mount node.
func (d mount) toSpec() *specs.Mount {
s := specs.Mount{
HostPath: d.HostPath,
ContainerPath: d.Path,
Options: d.Options,
}
return &s
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package info
import (
"dtk-container-toolkit/internal/config/image"
"dtk-container-toolkit/internal/logger"
)
// ResolveAutoMode determines the correct mode for the platform if set to "auto"
func ResolveAutoMode(logger logger.Interface, mode string, image image.DTK) (rmode string) {
return resolveMode(logger, mode, image)
}
func resolveMode(logger logger.Interface, mode string, image image.DTK) (rmode string) {
if mode != "auto" {
logger.Infof("Using requested mode '%s'", mode)
return mode
}
defer func() {
logger.Infof("Auto-detected mode as '%v'", rmode)
}()
if image.OnlyFullyQualifiedCDIDevices() {
return "cdi"
}
return "legacy"
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package drm
import (
"fmt"
"path/filepath"
)
// GetDeviceNodesByBusID returns the DRM devices associated with the specified PCI bus ID
func GetDeviceNodesByBusID(busID string) ([]string, error) {
drmRoot := filepath.Join("/sys/bus/pci/devices", busID, "drm")
matches, err := filepath.Glob(fmt.Sprintf("%s/*", drmRoot))
if err != nil {
return nil, err
}
var drmDeviceNodes []string
for _, m := range matches {
drmDeviceNode := filepath.Join("/dev/dri", filepath.Base(m))
drmDeviceNodes = append(drmDeviceNodes, drmDeviceNode)
}
return drmDeviceNodes, nil
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package info
import (
"bufio"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
)
// Get groups to add to container
func GetAdditionalGroups() ([]string, error) {
groups := []string{"video"}
subGrps, err := GetSubsystemGroups("kfd")
if err != nil {
return nil, err
}
groups = append(groups, subGrps...)
return groups, nil
}
func GetSubsystemGroups(subsystem string) ([]string, error) {
ruleFiles, err := filepath.Glob("/lib/udev/rules.d/*.rules")
if err != nil {
return nil, err
}
var groups []string
for _, f := range ruleFiles {
g, err := parseRuleFile(f, subsystem)
if err != nil {
return nil, err
}
if g != "" {
groups = append(groups, g)
}
}
return groups, nil
}
func parseRuleFile(path string, subsystem string) (string, error) {
infoFile, err := os.Open(path)
if err != nil {
return "", fmt.Errorf("failed to open %v: %v", path, err)
}
defer infoFile.Close()
key := fmt.Sprintf(`SUBSYSTEM=="%s"`, subsystem)
reg := regexp.MustCompile(`GROUP="(\w+)"`)
scanner := bufio.NewScanner(infoFile)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, key) {
found := reg.FindStringSubmatch(line)
if len(found) < 2 {
continue
}
return found[1], nil
}
}
return "", nil
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package info
import "strings"
// version must be set by go build's -X main.version= option in the Makefile.
var version = "unknown"
// gitCommit will be the hash that the binary was built from
// and will be populated by the Makefile
var gitCommit = ""
// GetVersionParts returns the different version components
func GetVersionParts() []string {
v := []string{version}
if gitCommit != "" {
v = append(v, "commit: "+gitCommit)
}
return v
}
// GetVersionString returns the string representation of the version
func GetVersionString(more ...string) string {
v := append(GetVersionParts(), more...)
return strings.Join(v, "\n")
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package ldcache
import "dtk-container-toolkit/internal/logger"
type empty struct {
logger logger.Interface
path string
}
var _ LDCache = (*empty)(nil)
// List always returns nil for an empty ldcache
func (e *empty) List() ([]string, []string) {
return nil, nil
}
// Lookup logs a debug message and returns nil for an empty ldcache
func (e *empty) Lookup(prefixes ...string) ([]string, []string) {
e.logger.Debugf("Calling Lookup(%v) on empty ldcache: %v", prefixes, e.path)
return nil, nil
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package ldcache
import (
"bytes"
"dtk-container-toolkit/internal/logger"
"dtk-container-toolkit/internal/lookup/symlinks"
"encoding/binary"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"syscall"
"unsafe"
)
const ldcachePath = "/etc/ld.so.cache"
const (
magicString1 = "ld.so-1.7.0"
magicString2 = "glibc-ld.so.cache"
magicVersion = "1.1"
)
const (
flagTypeMask = 0x00ff
flagTypeELF = 0x0001
flagArchMask = 0xff00
flagArchI386 = 0x0000
flagArchX8664 = 0x0300
flagArchX32 = 0x0800
flagArchPpc64le = 0x0500
)
var errInvalidCache = errors.New("invalid ld.so.cache file")
type header1 struct {
Magic [len(magicString1) + 1]byte // include null delimiter
NLibs uint32
}
type entry1 struct {
Flags int32
Key, Value uint32
}
type header2 struct {
Magic [len(magicString2)]byte
Version [len(magicVersion)]byte
NLibs uint32
TableSize uint32
_ [3]uint32 // unused
_ uint64 // force 8 byte alignment
}
type entry2 struct {
Flags int32
Key, Value uint32
OSVersion uint32
HWCap uint64
}
// LDCache represents the interface for performing lookups into the LDCache
//
//go:generate moq -out ldcache_mock.go . LDCache
type LDCache interface {
List() ([]string, []string)
Lookup(...string) ([]string, []string)
}
type ldcache struct {
*bytes.Reader
data, libs []byte
header header2
entries []entry2
root string
logger logger.Interface
}
// New creates a new LDCache with the specified logger and root.
func New(logger logger.Interface, root string) (LDCache, error) {
path := filepath.Join(root, ldcachePath)
logger.Debugf("Opening ld.conf at %v", path)
f, err := os.Open(path)
if os.IsNotExist(err) {
logger.Warningf("Could not find ld.so.cache at %v; creating empty cache", path)
e := &empty{
logger: logger,
path: path,
}
return e, nil
} else if err != nil {
return nil, err
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
return nil, err
}
d, err := syscall.Mmap(int(f.Fd()), 0, int(fi.Size()),
syscall.PROT_READ, syscall.MAP_PRIVATE)
if err != nil {
return nil, err
}
cache := &ldcache{
data: d,
Reader: bytes.NewReader(d),
root: root,
logger: logger,
}
return cache, cache.parse()
}
func (c *ldcache) Close() error {
return syscall.Munmap(c.data)
}
func (c *ldcache) Magic() string {
return string(c.header.Magic[:])
}
func (c *ldcache) Version() string {
return string(c.header.Version[:])
}
func strn(b []byte, n int) string {
return string(b[:n])
}
func (c *ldcache) parse() error {
var header header1
// Check for the old format (< glibc-2.2)
if c.Len() <= int(unsafe.Sizeof(header)) {
return errInvalidCache
}
if strn(c.data, len(magicString1)) == magicString1 {
if err := binary.Read(c, binary.LittleEndian, &header); err != nil {
return err
}
n := int64(header.NLibs) * int64(unsafe.Sizeof(entry1{}))
offset, err := c.Seek(n, 1) // skip old entries
if err != nil {
return err
}
n = (-offset) & int64(unsafe.Alignof(c.header)-1)
_, err = c.Seek(n, 1) // skip padding
if err != nil {
return err
}
}
c.libs = c.data[c.Size()-int64(c.Len()):] // kv offsets start here
if err := binary.Read(c, binary.LittleEndian, &c.header); err != nil {
return err
}
if c.Magic() != magicString2 || c.Version() != magicVersion {
return errInvalidCache
}
c.entries = make([]entry2, c.header.NLibs)
if err := binary.Read(c, binary.LittleEndian, &c.entries); err != nil {
return err
}
return nil
}
type entry struct {
libname string
bits int
value string
}
// getEntries returns the entires of the ldcache in a go-friendly struct.
func (c *ldcache) getEntries(selected func(string) bool) []entry {
var entries []entry
for _, e := range c.entries {
bits := 0
if ((e.Flags & flagTypeMask) & flagTypeELF) == 0 {
continue
}
switch e.Flags & flagArchMask {
case flagArchX8664:
fallthrough
case flagArchPpc64le:
bits = 64
case flagArchX32:
fallthrough
case flagArchI386:
bits = 32
default:
continue
}
if e.Key > uint32(len(c.libs)) || e.Value > uint32(len(c.libs)) {
continue
}
lib := bytesToString(c.libs[e.Key:])
if lib == "" {
c.logger.Debugf("Skipping invalid lib")
continue
}
if !selected(lib) {
continue
}
value := bytesToString(c.libs[e.Value:])
if value == "" {
c.logger.Debugf("Skipping invalid value for lib %v", lib)
continue
}
e := entry{
libname: lib,
bits: bits,
value: value,
}
entries = append(entries, e)
}
return entries
}
// List creates a list of libraries in the ldcache.
// The 32-bit and 64-bit libraries are returned separately.
func (c *ldcache) List() ([]string, []string) {
all := func(s string) bool { return true }
return c.resolveSelected(all)
}
// Lookup searches the ldcache for the specified prefixes.
// The 32-bit and 64-bit libraries matching the prefixes are returned.
func (c *ldcache) Lookup(libPrefixes ...string) ([]string, []string) {
c.logger.Debugf("Looking up %v in cache", libPrefixes)
// We define a functor to check whether a given library name matches any of the prefixes
matchesAnyPrefix := func(s string) bool {
for _, p := range libPrefixes {
if strings.HasPrefix(s, p) {
return true
}
}
return false
}
return c.resolveSelected(matchesAnyPrefix)
}
// resolveSelected process the entries in the LDCach based on the supplied filter and returns the resolved paths.
// The paths are separated by bittage.
func (c *ldcache) resolveSelected(selected func(string) bool) ([]string, []string) {
paths := make(map[int][]string)
processed := make(map[string]bool)
for _, e := range c.getEntries(selected) {
path, err := c.resolve(e.value)
if err != nil {
c.logger.Debugf("Could not resolve entry: %v", err)
continue
}
if processed[path] {
continue
}
paths[e.bits] = append(paths[e.bits], path)
processed[path] = true
}
return paths[32], paths[64]
}
// resolve resolves the specified ldcache entry based on the value being processed.
// The input is the name of the entry in the cache.
func (c *ldcache) resolve(target string) (string, error) {
name := filepath.Join(c.root, target)
c.logger.Debugf("checking %v", name)
link, err := symlinks.Resolve(name)
if err != nil {
return "", fmt.Errorf("failed to resolve symlink: %v", err)
}
if link == name {
return name, nil
}
// We return absolute paths for all targets
if !filepath.IsAbs(link) || strings.HasPrefix(link, ".") {
link = filepath.Join(filepath.Dir(target), link)
}
return c.resolve(link)
}
// bytesToString converts a byte slice to a string.
// This assumes that the byte slice is null-terminated
func bytesToString(value []byte) string {
n := bytes.IndexByte(value, 0)
if n < 0 {
return ""
}
return strn(value, n)
}
// Code generated by moq; DO NOT EDIT.
// github.com/matryer/moq
package ldcache
import (
"sync"
)
// Ensure, that LDCacheMock does implement LDCache.
// If this is not the case, regenerate this file with moq.
var _ LDCache = &LDCacheMock{}
// LDCacheMock is a mock implementation of LDCache.
//
// func TestSomethingThatUsesLDCache(t *testing.T) {
//
// // make and configure a mocked LDCache
// mockedLDCache := &LDCacheMock{
// ListFunc: func() ([]string, []string) {
// panic("mock out the List method")
// },
// LookupFunc: func(strings ...string) ([]string, []string) {
// panic("mock out the Lookup method")
// },
// }
//
// // use mockedLDCache in code that requires LDCache
// // and then make assertions.
//
// }
type LDCacheMock struct {
// ListFunc mocks the List method.
ListFunc func() ([]string, []string)
// LookupFunc mocks the Lookup method.
LookupFunc func(strings ...string) ([]string, []string)
// calls tracks calls to the methods.
calls struct {
// List holds details about calls to the List method.
List []struct {
}
// Lookup holds details about calls to the Lookup method.
Lookup []struct {
// Strings is the strings argument value.
Strings []string
}
}
lockList sync.RWMutex
lockLookup sync.RWMutex
}
// List calls ListFunc.
func (mock *LDCacheMock) List() ([]string, []string) {
if mock.ListFunc == nil {
panic("LDCacheMock.ListFunc: method is nil but LDCache.List was just called")
}
callInfo := struct {
}{}
mock.lockList.Lock()
mock.calls.List = append(mock.calls.List, callInfo)
mock.lockList.Unlock()
return mock.ListFunc()
}
// ListCalls gets all the calls that were made to List.
// Check the length with:
//
// len(mockedLDCache.ListCalls())
func (mock *LDCacheMock) ListCalls() []struct {
} {
var calls []struct {
}
mock.lockList.RLock()
calls = mock.calls.List
mock.lockList.RUnlock()
return calls
}
// Lookup calls LookupFunc.
func (mock *LDCacheMock) Lookup(strings ...string) ([]string, []string) {
if mock.LookupFunc == nil {
panic("LDCacheMock.LookupFunc: method is nil but LDCache.Lookup was just called")
}
callInfo := struct {
Strings []string
}{
Strings: strings,
}
mock.lockLookup.Lock()
mock.calls.Lookup = append(mock.calls.Lookup, callInfo)
mock.lockLookup.Unlock()
return mock.LookupFunc(strings...)
}
// LookupCalls gets all the calls that were made to Lookup.
// Check the length with:
//
// len(mockedLDCache.LookupCalls())
func (mock *LDCacheMock) LookupCalls() []struct {
Strings []string
} {
var calls []struct {
Strings []string
}
mock.lockLookup.RLock()
calls = mock.calls.Lookup
mock.lockLookup.RUnlock()
return calls
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package logger
// Interface defines the API for the logger package
type Interface interface {
Debugf(string, ...interface{})
Errorf(string, ...interface{})
Info(...interface{})
Infof(string, ...interface{})
Warning(...interface{})
Warningf(string, ...interface{})
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package logger
import "github.com/sirupsen/logrus"
// New returns a new logger
func New() Interface {
return logrus.StandardLogger()
}
// NullLogger is a logger that does nothing
type NullLogger struct{}
var _ Interface = (*NullLogger)(nil)
// Debugf is a no-op for the null logger
func (l *NullLogger) Debugf(string, ...interface{}) {}
// Errorf is a no-op for the null logger
func (l *NullLogger) Errorf(string, ...interface{}) {}
// Info is a no-op for the null logger
func (l *NullLogger) Info(...interface{}) {}
// Infof is a no-op for the null logger
func (l *NullLogger) Infof(string, ...interface{}) {}
// Warning is a no-op for the null logger
func (l *NullLogger) Warning(...interface{}) {}
// Warningf is a no-op for the null logger
func (l *NullLogger) Warningf(string, ...interface{}) {}
/**
# Copyright (c) Advanced Micro Devices, Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the \"License\");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an \"AS IS\" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
**/
package logger
import (
"log"
"os"
"os/user"
"path/filepath"
"sync"
)
var (
Log *log.Logger
logdir = "/var/log/"
logfile = "amd-container-runtime.log"
logPrefix = "amd-container-runtime "
once sync.Once
)
// SetLogPrefix sets prefix in the log to be exporter or testrunner
func SetLogPrefix(prefix string) {
logPrefix = prefix
}
// SetLogFile sets the log file name
func SetLogFile(file string) {
logfile = file
}
// SetLogDir sets the path to the directory of logs
func SetLogDir() {
isWriteable := func(path string) bool {
// Create a temporary file in the specified directory.
// os.CreateTemp will return an error if the directory is not writable.
file, err := os.CreateTemp(path, "tmp-test-")
if err != nil {
return false
}
file.Close() // Close the file
os.Remove(file.Name()) // Clean up the temporary file
return true
}
if os.Getenv("LOGDIR") != "" {
logdir = os.Getenv("LOGDIR")
//check if the user has permission to write to this location
if !isWriteable(logdir) {
log.Fatalf("User doesn't have write permission for the specified directory: %v", logdir)
}
return
}
// Get the current user's information.
currentUser, err := user.Current()
if err != nil {
log.Fatalf("Failed to get current user: %v", err)
}
// for root user, log dir is /var/log
if currentUser.Uid != "0" {
//Non-Root user, setting log directory to user's home directory
homeDir, err := os.UserHomeDir()
if err != nil {
log.Fatalf("Failed to get user home directory: %v", err)
}
logdir = homeDir
}
}
func initLogger(console bool) {
if console {
Log = log.New(os.Stdout, logPrefix, log.Lmsgprefix)
} else {
SetLogDir()
outfile, err := os.OpenFile(filepath.Join(logdir, logfile),
os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
if err != nil {
return
}
Log = log.New(outfile, "", 0)
}
Log.SetFlags(log.LstdFlags | log.Lshortfile)
}
func Init(console bool) {
init := func() {
initLogger(console)
}
once.Do(init)
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package lookup
import (
"fmt"
"os"
)
const (
devRoot = "/dev"
)
// NewCharDeviceLocator creates a Locator that can be used to find char devices at the specified root. A logger is
// also specified.
func NewCharDeviceLocator(opts ...Option) Locator {
opts = append(opts,
WithSearchPaths("", devRoot),
WithFilter(assertCharDevice),
)
return NewFileLocator(
opts...,
)
}
// assertCharDevice checks whether the specified path is a char device and returns an error if this is not the case.
func assertCharDevice(filename string) error {
info, err := os.Lstat(filename)
if err != nil {
return fmt.Errorf("error getting info: %v", err)
}
if info.Mode()&os.ModeCharDevice == 0 {
return fmt.Errorf("%v is not a char device", filename)
}
return nil
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package lookup
import (
"fmt"
"os"
)
// NewDirectoryLocator creates a Locator that can be used to find directories at the specified root.
func NewDirectoryLocator(opts ...Option) Locator {
return NewFileLocator(
append(
opts,
WithFilter(assertDirectory),
)...,
)
}
// assertDirectory checks wither the specified path is a directory.
func assertDirectory(filename string) error {
info, err := os.Stat(filename)
if err != nil {
return fmt.Errorf("error getting info for %v: %v", filename, err)
}
if !info.IsDir() {
return fmt.Errorf("specified path '%v' is not a directory", filename)
}
return nil
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package lookup
import (
"dtk-container-toolkit/internal/logger"
"fmt"
"os"
"strings"
)
type executable struct {
file
}
// NewExecutableLocator creates a locator to fine executable files in the path. A logger can also be specified.
func NewExecutableLocator(logger logger.Interface, root string, paths ...string) Locator {
paths = append(paths, GetPaths(root)...)
return newExecutableLocator(logger, root, paths...)
}
func newExecutableLocator(logger logger.Interface, root string, paths ...string) *executable {
f := newFileLocator(
WithLogger(logger),
WithRoot(root),
WithSearchPaths(paths...),
WithFilter(assertExecutable),
WithCount(1),
)
l := executable{
file: *f,
}
return &l
}
var _ Locator = (*executable)(nil)
// Locate finds executable files with the specified pattern in the path.
// If a relative or absolute path is specified, the prefix paths are not considered.
func (p executable) Locate(pattern string) ([]string, error) {
// For absolute paths we ensure that it is executable
if strings.Contains(pattern, "/") {
err := assertExecutable(pattern)
if err != nil {
return nil, fmt.Errorf("absolute path %v is not an executable file: %v", pattern, err)
}
return []string{pattern}, nil
}
return p.file.Locate(pattern)
}
// assertExecutable checks whether the specified path is an execuable file.
func assertExecutable(filename string) error {
err := assertFile(filename)
if err != nil {
return err
}
info, err := os.Stat(filename)
if err != nil {
return err
}
if info.Mode()&0111 == 0 {
return fmt.Errorf("specified file '%v' is not executable", filename)
}
return nil
}
/**
# Copyright (c) 2024, HCUOpt CORPORATION. All rights reserved.
**/
package lookup
import (
"dtk-container-toolkit/internal/logger"
"fmt"
"os"
"path/filepath"
)
// file can be used to locate file (or file-like elements) at a specified set of
// prefixes. The validity of a file is determined by a filter function.
type file struct {
builder
prefixes []string
}
// builder defines the builder for a file locator.
type builder struct {
logger logger.Interface
root string
searchPaths []string
filter func(string) error
count int
isOptional bool
}
// Option defines a function for passing builder to the NewFileLocator() call
type Option func(*builder)
// WithRoot sets the root for the file locator
func WithRoot(root string) Option {
return func(f *builder) {
f.root = root
}
}
// WithLogger sets the logger for the file locator
func WithLogger(logger logger.Interface) Option {
return func(f *builder) {
f.logger = logger
}
}
// WithSearchPaths sets the search paths for the file locator.
func WithSearchPaths(paths ...string) Option {
return func(f *builder) {
f.searchPaths = paths
}
}
// WithFilter sets the filter for the file locator
// The filter is called for each candidate file and candidates that return nil are considered.
func WithFilter(assert func(string) error) Option {
return func(f *builder) {
f.filter = assert
}
}
// WithCount sets the maximum number of candidates to discover
func WithCount(count int) Option {
return func(f *builder) {
f.count = count
}
}
// WithOptional sets the optional flag for the file locator
// If the optional flag is set, the locator will not return an error if the file is not found.
func WithOptional(optional bool) Option {
return func(f *builder) {
f.isOptional = optional
}
}
func newBuilder(opts ...Option) *builder {
o := &builder{}
for _, opt := range opts {
opt(o)
}
if o.logger == nil {
o.logger = logger.New()
}
if o.filter == nil {
o.filter = assertFile
}
return o
}
func (o builder) build() *file {
f := file{
builder: o,
// Since the `Locate` implementations rely on the root already being specified we update
// the prefixes to include the root.
prefixes: getSearchPrefixes(o.root, o.searchPaths...),
}
return &f
}
// NewFileLocator creates a Locator that can be used to find files with the specified builder.
func NewFileLocator(opts ...Option) Locator {
return newFileLocator(opts...)
}
func newFileLocator(opts ...Option) *file {
return newBuilder(opts...).build()
}
// getSearchPrefixes generates a list of unique paths to be searched by a file locator.
//
// For each of the unique prefixes <p> specified, the path <root><p> is searched, where <root> is the
// specified root. If no prefixes are specified, <root> is returned as the only search prefix.
//
// Note that an empty root is equivalent to searching relative to the current working directory, and
// if the root filesystem should be searched instead, root should be specified as "/" explicitly.
//
// Also, a prefix of "" forces the root to be included in returned set of paths. This means that if
// the root in addition to another prefix must be searched the function should be called with:
//
// getSearchPrefixes("/root", "", "another/path")
//
// and will result in the search paths []{"/root", "/root/another/path"} being returned.
func getSearchPrefixes(root string, prefixes ...string) []string {
seen := make(map[string]bool)
var uniquePrefixes []string
for _, p := range prefixes {
if seen[p] {
continue
}
seen[p] = true
uniquePrefixes = append(uniquePrefixes, filepath.Join(root, p))
}
if len(uniquePrefixes) == 0 {
uniquePrefixes = append(uniquePrefixes, root)
}
return uniquePrefixes
}
var _ Locator = (*file)(nil)
// Locate attempts to find files with names matching the specified pattern.
// All prefixes are searched and any matching candidates are returned. If no matches are found, an error is returned.
func (p file) Locate(pattern string) ([]string, error) {
var filenames []string
p.logger.Debugf("Locating %q in %v", pattern, p.prefixes)
visit:
for _, prefix := range p.prefixes {
pathPattern := filepath.Join(prefix, pattern)
candidates, err := filepath.Glob(pathPattern)
if err != nil {
p.logger.Debugf("Checking pattern '%v' failed: %v", pathPattern, err)
}
for _, candidate := range candidates {
p.logger.Debugf("Checking candidate '%v'", candidate)
err := p.filter(candidate)
if err != nil {
p.logger.Debugf("Candidate '%v' does not meet requirements: %v", candidate, err)
continue
}
filenames = append(filenames, candidate)
if p.count > 0 && len(filenames) == p.count {
p.logger.Debugf("Found %d candidates; ignoring further candidates", len(filenames))
break visit
}
}
}
if !p.isOptional && len(filenames) == 0 {
return nil, fmt.Errorf("pattern %v %w", pattern, ErrNotFound)
}
return filenames, nil
}
// assertFile checks whether the specified path is a regular file
func assertFile(filename string) error {
info, err := os.Stat(filename)
if err != nil {
return fmt.Errorf("error getting info for %v: %v", filename, err)
}
if info.IsDir() {
return fmt.Errorf("specified path '%v' is a directory", filename)
}
return nil
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment