From f241f553e9f74c393f12be596eebf6e443a9b50c Mon Sep 17 00:00:00 2001 From: Vicente Cheng Date: Mon, 29 Jan 2024 00:24:43 +0800 Subject: [PATCH] utils: add process and command funcs - that we can drop some dependency Signed-off-by: Vicente Cheng --- pkg/block/block_device.go | 3 +- pkg/utils/command.go | 83 +++++++++++++++++++++++++++++++++++++++ pkg/utils/process.go | 73 ++++++++++++++++++++++++++++++++++ pkg/utils/utils.go | 9 ++--- 4 files changed, 161 insertions(+), 7 deletions(-) create mode 100644 pkg/utils/command.go create mode 100644 pkg/utils/process.go diff --git a/pkg/block/block_device.go b/pkg/block/block_device.go index e5d9a497..6af6b8d4 100644 --- a/pkg/block/block_device.go +++ b/pkg/block/block_device.go @@ -14,7 +14,6 @@ import ( "github.com/jaypipes/ghw/pkg/linuxpath" "github.com/jaypipes/ghw/pkg/option" "github.com/jaypipes/ghw/pkg/util" - iscsiutil "github.com/longhorn/go-iscsi-helper/util" "github.com/sirupsen/logrus" "golang.org/x/crypto/blake2b" @@ -493,7 +492,7 @@ func partitionInfo(ctx *context.Context, paths *linuxpath.Paths, part string) (s func openProcMounts(ctx *context.Context, paths *linuxpath.Paths) (*os.File, error) { file := paths.ProcMounts if path, ok := ctx.PathOverrides[ndmutils.ProcPath]; ok { - ns := iscsiutil.GetHostNamespacePath(path) + ns := ndmutils.GetHostNamespacePath(path) file = strings.TrimSuffix(ns, "ns/") + "mounts" } return os.Open(file) diff --git a/pkg/utils/command.go b/pkg/utils/command.go new file mode 100644 index 00000000..df96d67b --- /dev/null +++ b/pkg/utils/command.go @@ -0,0 +1,83 @@ +package utils + +import ( + "bytes" + "os/exec" + "path/filepath" + "time" + + "github.com/pkg/errors" +) + +const ( + NSBinary = "nsenter" + cmdTimeoutDefault = 180 * time.Second // 3 minutes by default + cmdTimeoutNone = 0 * time.Second // no timeout +) + +type Executor struct { + namespace string + cmdTimeout time.Duration +} + +func NewExecutor() *Executor { + return &Executor{ + namespace: "", + cmdTimeout: cmdTimeoutDefault, + } +} + +func NewExecutorWithNS(ns string) (*Executor, error) { + exec := NewExecutor() + exec.namespace = ns + + // test if nsenter is available + if _, err := execute(NSBinary, []string{"-V"}, cmdTimeoutNone); err != nil { + return nil, errors.Wrap(err, "cannot find nsenter for namespace switching") + } + return exec, nil +} + +func (exec *Executor) SetTimeout(timeout time.Duration) { + exec.cmdTimeout = timeout +} + +func (exec *Executor) Execute(cmd string, args []string) (string, error) { + command := cmd + cmdArgs := args + if exec.namespace != "" { + cmdArgs = []string{ + "--mount=" + filepath.Join(exec.namespace, "mnt"), + "--net=" + filepath.Join(exec.namespace, "net"), + "--ipc=" + filepath.Join(exec.namespace, "ipc"), + cmd, + } + command = NSBinary + cmdArgs = append(cmdArgs, args...) + } + return execute(command, cmdArgs, exec.cmdTimeout) +} + +func execute(command string, args []string, timeout time.Duration) (string, error) { + cmd := exec.Command(command, args...) + + var output, stderr bytes.Buffer + cmd.Stdout = &output + cmd.Stderr = &stderr + + timer := &time.Timer{} + if timeout != cmdTimeoutNone { + // add timer to kill the process if timeout + timer = time.AfterFunc(timeout, func() { + cmd.Process.Kill() + }) + } + defer timer.Stop() + + if err := cmd.Run(); err != nil { + return "", errors.Wrapf(err, "failed to execute: %v %v, output %s, stderr %s", + command, args, output.String(), stderr.String()) + } + + return output.String(), nil +} diff --git a/pkg/utils/process.go b/pkg/utils/process.go new file mode 100644 index 00000000..d5d22b7a --- /dev/null +++ b/pkg/utils/process.go @@ -0,0 +1,73 @@ +package utils + +import ( + "fmt" + + "github.com/prometheus/procfs" +) + +const ( + DockerdProcess = "dockerd" + ContainerdProcess = "containerd" + ContainerdProcessShim = "containerd-shim" +) + +func getPidProc(hostProcPath string, pid int) (*procfs.Proc, error) { + fs, err := procfs.NewFS(hostProcPath) + if err != nil { + return nil, err + } + proc, err := fs.Proc(pid) + if err != nil { + return nil, err + } + return &proc, nil +} + +func getSelfProc(hostProcPath string) (*procfs.Proc, error) { + fs, err := procfs.NewFS(hostProcPath) + if err != nil { + return nil, err + } + proc, err := fs.Self() + if err != nil { + return nil, err + } + return &proc, nil +} + +func findAncestorByName(hostProcPath string, ancestorProcess string) (*procfs.Proc, error) { + proc, err := getSelfProc(hostProcPath) + if err != nil { + return nil, err + } + + for { + st, err := proc.Stat() + if err != nil { + return nil, err + } + if st.Comm == ancestorProcess { + return proc, nil + } + if st.PPID == 0 { + break + } + proc, err = getPidProc(hostProcPath, st.PPID) + if err != nil { + return nil, err + } + } + return nil, fmt.Errorf("failed to find the ancestor process: %s", ancestorProcess) +} + +func GetHostNamespacePath(hostProcPath string) string { + containerNames := []string{DockerdProcess, ContainerdProcess, ContainerdProcessShim} + for _, name := range containerNames { + proc, err := findAncestorByName(hostProcPath, name) + if err == nil { + return fmt.Sprintf("%s/%d/ns/", hostProcPath, proc.PID) + } + } + return fmt.Sprintf("%s/%d/ns/", hostProcPath, 1) +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 7cd196d0..64c8b7ad 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -8,7 +8,6 @@ import ( "sync" "syscall" - iscsiutil "github.com/longhorn/go-iscsi-helper/util" "github.com/longhorn/longhorn-manager/util" ) @@ -134,8 +133,8 @@ func mountExt4(device, path string, readonly bool) error { // mountExt4OnHostNamespace provides the same functionality as mountExt4 but on host namespace. func mountExt4OnHostNamespace(device, path string, readonly bool) error { - ns := iscsiutil.GetHostNamespacePath(util.HostProcPath) - executor, err := iscsiutil.NewNamespaceExecutor(ns) + ns := GetHostNamespacePath(util.HostProcPath) + executor, err := NewExecutorWithNS(ns) if err != nil { return err } @@ -150,8 +149,8 @@ func mountExt4OnHostNamespace(device, path string, readonly bool) error { } func executeOnHostNamespace(cmd string, args []string) (string, error) { - ns := iscsiutil.GetHostNamespacePath(util.HostProcPath) - executor, err := iscsiutil.NewNamespaceExecutor(ns) + ns := GetHostNamespacePath(util.HostProcPath) + executor, err := NewExecutorWithNS(ns) if err != nil { return "", err }