Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 31 additions & 20 deletions utils/devices_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"time"
"unsafe"

execCmd "github.com/netapp/trident/utils/exec"
log "github.com/sirupsen/logrus"

"github.com/cenkalti/backoff/v4"
Expand All @@ -29,6 +30,8 @@ const (
luksType = "luks2"
// Return code for "no permission (bad passphrase)" from cryptsetup command
luksCryptsetupBadPassphraseReturnCode = 2
luksCloseDeviceSafelyClosedExitCode = 0
luksCloseDeviceAlreadyClosedExitCode = 4
)

// flushOneDevice flushes any outstanding I/O to a disk
Expand Down Expand Up @@ -175,17 +178,28 @@ func (d *LUKSDevice) Close(ctx context.Context) error {
}

// closeLUKSDevice performs a luksClose on the specified LUKS device
// It gracefully handles the cases where a LUKS device has already been closed or the device doesn't exist.
func closeLUKSDevice(ctx context.Context, luksDevicePath string) error {
output, err := command.ExecuteWithTimeoutAndInput(
ctx, "cryptsetup", luksCommandTimeout, true, "", "luksClose", luksDevicePath,
)
if nil != err {
Log().WithFields(LogFields{
"MappedDeviceName": luksDevicePath,
"error": err.Error(),
"output": string(output),
}).Debug("Failed to Close LUKS device")
return fmt.Errorf("failed to Close LUKS device %s; %v", luksDevicePath, err)
fields := LogFields{"luksDevicePath": luksDevicePath, "output": string(output), "err": err.Error()}
var exitErr execCmd.ExitError
if !errors.As(err, &exitErr) {
Logc(ctx).WithFields(fields).Error("Failed to close LUKS device with unknown error.")
return fmt.Errorf("failed to close LUKS device %s; %w", luksDevicePath, err)
}

switch exitErr.ExitCode() {
// exit code "0" and "4" are safe to ignore. "0" will likely never be hit but check for it regardless.
case luksCloseDeviceSafelyClosedExitCode, luksCloseDeviceAlreadyClosedExitCode:
Logc(ctx).WithFields(fields).Debug("LUKS device is already closed or did not exist.")
return nil
default:
Logc(ctx).WithFields(fields).Error("Failed to close LUKS device.")
return fmt.Errorf("exit code '%d' when closing LUKS device '%s'; %w", exitErr.ExitCode(), luksDevicePath, err)
}
}
return nil
}
Expand All @@ -209,21 +223,18 @@ func IsLUKSDeviceOpen(ctx context.Context, luksDevicePath string) (bool, error)
// EnsureLUKSDeviceClosed ensures there is not an open LUKS device at the specified path
func EnsureLUKSDeviceClosed(ctx context.Context, luksDevicePath string) error {
GenerateRequestContextForLayer(ctx, LogLayerUtils)
fields := LogFields{"luksDevicePath": luksDevicePath}

_, err := osFs.Stat(luksDevicePath)
if err == nil {
// Need to Close the LUKS device
return closeLUKSDevice(ctx, luksDevicePath)
} else if os.IsNotExist(err) {
Logc(ctx).WithFields(LogFields{
"device": luksDevicePath,
}).Debug("LUKS device not found.")
} else {
Logc(ctx).WithFields(LogFields{
"device": luksDevicePath,
"error": err.Error(),
}).Debug("Failed to stat device")
return fmt.Errorf("could not stat device: %s %v.", luksDevicePath, err)
if err := closeLUKSDevice(ctx, luksDevicePath); err != nil {
Logc(ctx).WithFields(fields).WithError(err).Error("Could not close LUKS device.")
return fmt.Errorf("could not close LUKS device %s; %w", luksDevicePath, err)
}

// If LUKS close succeeded, the block device node should be gone.
// It's the responsibility of the kernel and udev to manage /dev/mapper entries.
// If the /dev/mapper entry lives on, log a warning and return success.
if _, err := osFs.Stat(luksDevicePath); err != nil {
Copy link
Copy Markdown
Contributor

@jwebster7 jwebster7 Jul 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is only a proactive check for stale /dev/mapper entries. If Stat fails to find the file (the positive case) this will add incorrect log statements.

s/err != nil/err == nil to have the correct behavior.

Logc(ctx).WithFields(fields).Warn("Stale device mapper file found for LUKS device. Is udev is running?")
}
return nil
}
Expand Down
11 changes: 10 additions & 1 deletion utils/exec/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,18 @@ import (
var (
xtermControlRegex = regexp.MustCompile(`\x1B\[[0-9;]*[a-zA-Z]`)

_ Command = NewCommand()
// Ensure these structures always implement these interfaces at compilation.
_ Command = NewCommand()
_ ExitError = &exec.ExitError{}
)

// ExitError defines the methods that exec.ExitError implements.
// This enables unit testing and mocking of exit codes.
type ExitError interface {
error
ExitCode() int
}

// Command defines a set of behaviors for executing commands on a host.
type Command interface {
Execute(ctx context.Context, name string, args ...string) ([]byte, error)
Expand Down