Skip to content

Commit

Permalink
new(cmd,pkg/driverbuilder): support env,src-dir and dkms build …
Browse files Browse the repository at this point in the history
…options in local builder.

Signed-off-by: Federico Di Pierro <[email protected]>
  • Loading branch information
FedeDP authored and poiana committed Nov 24, 2023
1 parent 78bb75a commit 2c7d6f0
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 11 deletions.
24 changes: 23 additions & 1 deletion cmd/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,20 @@ import (
"golang.org/x/sys/unix"
"log/slog"
"os"
"os/user"
"runtime"
"strings"
)

type localCmdOptions struct {
useDKMS bool
srcDir string
envMap map[string]string
}

// NewLocalCmd creates the `driverkit local` command.
func NewLocalCmd(rootCommand *RootCmd, rootOpts *RootOptions, rootFlags *pflag.FlagSet) *cobra.Command {
opts := localCmdOptions{}
localCmd := &cobra.Command{
Use: "local",
Short: "Build Falco kernel modules and eBPF probes in local env with local kernel sources and gcc/clang.",
Expand All @@ -26,7 +34,18 @@ func NewLocalCmd(rootCommand *RootCmd, rootOpts *RootOptions, rootFlags *pflag.F
if !b.HasOutputs() {
return
}
if err := driverbuilder.NewLocalBuildProcessor(viper.GetInt("timeout")).Start(b); err != nil {
if opts.useDKMS {
currentUser, err := user.Current()
if err != nil {
slog.With("err", err.Error()).Error("Failed to retrieve user. Exiting.")
os.Exit(1)
}
if currentUser.Username != "root" {
slog.Error("Must be run as root for DKMS build.")
os.Exit(1)
}
}
if err := driverbuilder.NewLocalBuildProcessor(viper.GetInt("timeout"), opts.useDKMS, opts.srcDir, opts.envMap).Start(b); err != nil {
slog.With("err", err.Error()).Error("exiting")
os.Exit(1)
}
Expand Down Expand Up @@ -56,6 +75,9 @@ func NewLocalCmd(rootCommand *RootCmd, rootOpts *RootOptions, rootFlags *pflag.F
flagSet.AddFlag(flag)
}
})
flagSet.BoolVar(&opts.useDKMS, "dkms", false, "Enforce usage of DKMS to build the kernel module.")
flagSet.StringVar(&opts.srcDir, "src-dir", "", "Enforce usage of local source dir to build drivers.")
flagSet.StringToStringVar(&opts.envMap, "env", nil, "Env variables to be enforced during the driver build.")
localCmd.PersistentFlags().AddFlagSet(flagSet)
return localCmd
}
Expand Down
35 changes: 31 additions & 4 deletions pkg/driverbuilder/builder/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ var localTemplate string

type LocalBuilder struct {
GccPath string
UseDKMS bool
SrcDir string
}

func (l *LocalBuilder) Name() string {
Expand All @@ -25,7 +27,7 @@ func (l *LocalBuilder) TemplateScript() string {
return localTemplate
}

func (l *LocalBuilder) URLs(kr kernelrelease.KernelRelease) ([]string, error) {
func (l *LocalBuilder) URLs(_ kernelrelease.KernelRelease) ([]string, error) {
return nil, nil
}

Expand All @@ -36,18 +38,43 @@ func (l *LocalBuilder) MinimumURLs() int {

type localTemplateData struct {
commonTemplateData
UseDKMS bool
DownloadSrc bool
DriverVersion string
KernelRelease string
}

func (l *LocalBuilder) TemplateData(c Config, _ kernelrelease.KernelRelease, _ []string) interface{} {
func (l *LocalBuilder) TemplateData(c Config, kr kernelrelease.KernelRelease, _ []string) interface{} {
return localTemplateData{
commonTemplateData: commonTemplateData{
DriverBuildDir: DriverDirectory,
DriverBuildDir: l.GetDriverBuildDir(),
ModuleDownloadURL: fmt.Sprintf("%s/%s.tar.gz", c.DownloadBaseURL, c.DriverVersion),
ModuleDriverName: c.DriverName,
ModuleFullPath: ModuleFullPath,
ModuleFullPath: l.GetModuleFullPath(c, kr),
BuildModule: len(c.ModuleFilePath) > 0,
BuildProbe: len(c.ProbeFilePath) > 0,
GCCVersion: l.GccPath,
},
UseDKMS: l.UseDKMS,
DownloadSrc: len(l.SrcDir) == 0, // if no srcdir is provided, download src!
DriverVersion: c.DriverVersion,
KernelRelease: c.KernelRelease,
}
}

func (l *LocalBuilder) GetModuleFullPath(c Config, kr kernelrelease.KernelRelease) string {
moduleFullPath := ModuleFullPath
if l.UseDKMS {
// When using dkms, we will use a GLOB to match the pattern; ModuleFullPath won't be used in the templated script anyway.
moduleFullPath = fmt.Sprintf("/var/lib/dkms/%s/%s/%s/%s/module/%s.*", c.DriverName, c.DriverVersion, kr.String(), kr.Architecture.ToNonDeb(), c.DriverName)
}
return moduleFullPath
}

func (l *LocalBuilder) GetDriverBuildDir() string {
driverBuildDir := DriverDirectory
if len(l.SrcDir) > 0 {
driverBuildDir = l.SrcDir
}
return driverBuildDir
}
15 changes: 15 additions & 0 deletions pkg/driverbuilder/builder/templates/local.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
#
set -xeuo pipefail

{{ if .DownloadSrc }}
echo "* Downloading driver sources"
rm -Rf {{ .DriverBuildDir }}
mkdir {{ .DriverBuildDir }}
rm -Rf /tmp/module-download
Expand All @@ -32,8 +34,19 @@ mv /tmp/module-download/*/driver/* {{ .DriverBuildDir }}

cp /tmp/module-Makefile {{ .DriverBuildDir }}/Makefile
bash /tmp/fill-driver-config.sh {{ .DriverBuildDir }}
{{ end }}

{{ if .BuildModule }}
{{ if .UseDKMS }}
echo "* Building kmod with DKMS"
# Build the module using DKMS
echo "#!/usr/bin/env bash" > "/tmp/falco-dkms-make"
echo "make CC={{ .GCCVersion }} \$@" >> "/tmp/falco-dkms-make"
chmod +x "/tmp/falco-dkms-make"
dkms install --directive="MAKE='/tmp/falco-dkms-make'" -m "{{ .ModuleDriverName }}" -v "{{ .DriverVersion }}" -k "{{ .KernelRelease }}"
rm -Rf "/tmp/falco-dkms-make"
{{ else }}
echo "* Building kmod"
# Build the module
cd {{ .DriverBuildDir }}
make CC={{ .GCCVersion }}
Expand All @@ -42,8 +55,10 @@ strip -g {{ .ModuleFullPath }}
# Print results
modinfo {{ .ModuleFullPath }}
{{ end }}
{{ end }}

{{ if .BuildProbe }}
echo "* Building eBPF probe"
# Build the eBPF probe
cd {{ .DriverBuildDir }}/bpf
make
Expand Down
53 changes: 47 additions & 6 deletions pkg/driverbuilder/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import (
_ "embed"
"fmt"
"github.com/falcosecurity/driverkit/pkg/driverbuilder/builder"
"io"
"log/slog"
"os"
"os/exec"
"path"
"path/filepath"
"time"
)
Expand All @@ -18,11 +20,17 @@ const LocalBuildProcessorName = "local"

type LocalBuildProcessor struct {
timeout int
useDKMS bool
srcDir string
envMap map[string]string
}

func NewLocalBuildProcessor(timeout int) *LocalBuildProcessor {
func NewLocalBuildProcessor(timeout int, useDKMS bool, srcDir string, envMap map[string]string) *LocalBuildProcessor {
return &LocalBuildProcessor{
timeout: timeout,
useDKMS: useDKMS,
srcDir: srcDir,
envMap: envMap,
}
}

Expand Down Expand Up @@ -104,6 +112,11 @@ func (lbp *LocalBuildProcessor) Start(b *builder.Build) error {

// Cannot fail
vv, _ := v.(*builder.LocalBuilder)
vv.SrcDir = lbp.srcDir
vv.UseDKMS = lbp.useDKMS

modulePath := vv.GetModuleFullPath(c, kr)
probePath := path.Join(vv.GetDriverBuildDir(), "bpf", builder.ProbeFileName)
for _, gcc := range gccs {
vv.GccPath = gcc

Expand All @@ -115,6 +128,10 @@ func (lbp *LocalBuildProcessor) Start(b *builder.Build) error {
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Duration(lbp.timeout)*time.Second)
defer cancelFunc()
cmd := exec.CommandContext(ctx, "/bin/bash", "-c", driverkitScript)
// Append requested env variables to the command env
for key, val := range lbp.envMap {
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, val))
}
stdout, err := cmd.StdoutPipe()
if err != nil {
slog.Warn("Failed to pipe output. Trying without piping.", "err", err)
Expand All @@ -139,28 +156,52 @@ func (lbp *LocalBuildProcessor) Start(b *builder.Build) error {
}
// If we received an error, perhaps we must just rebuilt the kmod.
// Check if we were able to build anything.
if _, err = os.Stat(builder.ModuleFullPath); !os.IsNotExist(err) {
// we built the kmod; there is no need to loop again.
koFiles, err := filepath.Glob(modulePath)
if err == nil && len(koFiles) > 0 {
break
}
if _, err = os.Stat(builder.ProbeFullPath); !os.IsNotExist(err) {
if _, err = os.Stat(probePath); !os.IsNotExist(err) {
c.ProbeFilePath = ""
}
}

if len(b.ModuleFilePath) > 0 {
if err = os.Rename(builder.ModuleFullPath, b.ModuleFilePath); err != nil {
// If we received an error, perhaps we must just rebuilt the kmod.
// Check if we were able to build anything.
koFiles, err := filepath.Glob(modulePath)
if err != nil || len(koFiles) == 0 {
return fmt.Errorf("failed to find kernel module .ko file: %s", modulePath)
}
if err = copyDataToLocalPath(koFiles[0], b.ModuleFilePath); err != nil {
return err
}
slog.With("path", b.ModuleFilePath).Info("kernel module available")
}

if len(b.ProbeFilePath) > 0 {
if err = os.Rename(builder.ProbeFullPath, b.ProbeFilePath); err != nil {
if err = copyDataToLocalPath(probePath, b.ProbeFilePath); err != nil {
return err
}
slog.With("path", b.ProbeFilePath).Info("eBPF probe available")
}

return nil
}

func copyDataToLocalPath(src, dest string) error {
in, err := os.Open(filepath.Clean(src))
if err != nil {
return err
}
defer in.Close()
err = os.MkdirAll(filepath.Dir(dest), 0o755)
if err != nil {
return err
}
out, err := os.OpenFile(filepath.Clean(dest), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o755)
if err == nil {
defer out.Close()
_, err = io.Copy(out, in)
}
return err
}

0 comments on commit 2c7d6f0

Please sign in to comment.