Skip to content

Commit 0197a4b

Browse files
authored
Consider nonexistent device errors when closing a LUKS device
1 parent 79510e1 commit 0197a4b

File tree

3 files changed

+279
-159
lines changed

3 files changed

+279
-159
lines changed

utils/devices/devices_linux.go

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ import (
2323
)
2424

2525
const (
26-
luksCloseTimeout = 30 * time.Second
27-
luksCloseMaxWaitDuration = 2 * time.Minute
26+
luksCloseTimeout = 30 * time.Second
27+
luksCloseMaxWaitDuration = 2 * time.Minute
28+
luksCloseDeviceSafelyClosedExitCode = 0
29+
luksCloseDeviceAlreadyClosedExitCode = 4
2830
)
2931

3032
var (
@@ -47,12 +49,11 @@ func (c *Client) FlushOneDevice(ctx context.Context, devicePath string) error {
4749
ctx, "blockdev", deviceOperationsTimeout, true, "--flushbufs", devicePath,
4850
)
4951
if err != nil {
50-
Logc(ctx).WithFields(
51-
LogFields{
52-
"error": err,
53-
"output": string(out),
54-
"device": devicePath,
55-
}).Debug("blockdev --flushbufs failed.")
52+
Logc(ctx).WithFields(LogFields{
53+
"error": err,
54+
"output": string(out),
55+
"device": devicePath,
56+
}).Debug("blockdev --flushbufs failed.")
5657
return fmt.Errorf("flush device failed for %s : %s", devicePath, err)
5758
}
5859

@@ -112,6 +113,7 @@ func (c *Client) VerifyMultipathDeviceSize(
112113
}
113114

114115
// CloseLUKSDevice performs a luksClose on the device at the specified path (example: "/dev/mapper/<luks>").
116+
// It gracefully handles the cases where a LUKS device has already been closed or the device doesn't exist.
115117
func (c *Client) CloseLUKSDevice(ctx context.Context, devicePath string) error {
116118
if err := beforeLuksClose.Inject(); err != nil {
117119
return err
@@ -126,9 +128,22 @@ func (c *Client) CloseLUKSDevice(ctx context.Context, devicePath string) error {
126128
}
127129

128130
if err != nil {
129-
fields := LogFields{"luksDevicePath": devicePath, "output": string(output)}
130-
Logc(ctx).WithFields(fields).WithError(err).Debug("Failed to close LUKS device")
131-
return fmt.Errorf("failed to close LUKS device %s; %w", devicePath, err)
131+
fields := LogFields{"luksDevicePath": devicePath, "output": string(output), "err": err.Error()}
132+
var exitErr execCmd.ExitError
133+
if !errors.As(err, &exitErr) {
134+
Logc(ctx).WithFields(fields).Error("Failed to close LUKS device with unknown error.")
135+
return fmt.Errorf("failed to close LUKS device %s; %w", devicePath, err)
136+
}
137+
138+
switch exitErr.ExitCode() {
139+
// exit code "0" and "4" are safe to ignore. "0" will likely never be hit but check for it regardless.
140+
case luksCloseDeviceSafelyClosedExitCode, luksCloseDeviceAlreadyClosedExitCode:
141+
Logc(ctx).WithFields(fields).Debug("LUKS device is already closed or did not exist.")
142+
return nil
143+
default:
144+
Logc(ctx).WithFields(fields).Error("Failed to close LUKS device.")
145+
return fmt.Errorf("exit code '%d' when closing LUKS device '%s'; %w", exitErr.ExitCode(), devicePath, err)
146+
}
132147
}
133148

134149
Logc(ctx).WithField("luksDevicePath", devicePath).Debug("Closed LUKS device.")
@@ -138,19 +153,19 @@ func (c *Client) CloseLUKSDevice(ctx context.Context, devicePath string) error {
138153
// EnsureLUKSDeviceClosed ensures there is no open LUKS device at the specified path (example: "/dev/mapper/<luks>").
139154
func (c *Client) EnsureLUKSDeviceClosed(ctx context.Context, devicePath string) error {
140155
GenerateRequestContextForLayer(ctx, LogLayerUtils)
141-
_, err := c.osFs.Stat(devicePath)
142-
if err == nil {
143-
return c.CloseLUKSDevice(ctx, devicePath)
144-
} else if !os.IsNotExist(err) {
145-
Logc(ctx).WithFields(LogFields{
146-
"device": devicePath,
147-
"error": err.Error(),
148-
}).Debug("Failed to stat device.")
149-
return fmt.Errorf("could not stat device: %s; %v", devicePath, err)
156+
fields := LogFields{"luksDevicePath": devicePath}
157+
158+
if err := c.CloseLUKSDevice(ctx, devicePath); err != nil {
159+
Logc(ctx).WithFields(fields).WithError(err).Error("Could not close LUKS device.")
160+
return fmt.Errorf("could not close LUKS device %s; %w", devicePath, err)
161+
}
162+
163+
// If LUKS close succeeded, the block device node should be gone.
164+
// It's the responsibility of the kernel and udev to manage /dev/mapper entries.
165+
// If the /dev/mapper entry lives on, log a warning and return success.
166+
if _, err := c.osFs.Stat(devicePath); err == nil {
167+
Logc(ctx).WithFields(fields).Warn("Stale device mapper file found for LUKS device. Is udev is running?")
150168
}
151-
Logc(ctx).WithFields(LogFields{
152-
"device": devicePath,
153-
}).Debug("LUKS device not found.")
154169

155170
return nil
156171
}
@@ -163,12 +178,11 @@ func (c *Client) EnsureLUKSDeviceClosedWithMaxWaitLimit(ctx context.Context, luk
163178
return durationErr
164179
}
165180
if elapsed > luksCloseMaxWaitDuration {
166-
Logc(ctx).WithFields(
167-
LogFields{
168-
"device": luksDevicePath,
169-
"elapsed": elapsed,
170-
"maxWait": luksDevicePath,
171-
}).Debug("LUKS close max wait time expired, continuing with removal.")
181+
Logc(ctx).WithFields(LogFields{
182+
"device": luksDevicePath,
183+
"elapsed": elapsed,
184+
"maxWait": luksDevicePath,
185+
}).Debug("LUKS close max wait time expired, continuing with removal.")
172186
return errors.MaxWaitExceededError(fmt.Sprintf("LUKS close wait time expired. Elapsed: %v", elapsed))
173187
}
174188
return err
@@ -184,7 +198,7 @@ func (c *Client) GetDeviceFSType(ctx context.Context, device string) (string, er
184198
// blkid return status=2 both in case of an unformatted filesystem as well as for the case when it is
185199
// unable to get the filesystem (e.g. IO error), therefore ensure device is available before calling blkid
186200
if err := c.WaitForDevice(ctx, device); err != nil {
187-
return "", fmt.Errorf("could not find device before checking for the filesystem %v; %s.", device, err)
201+
return "", fmt.Errorf("could not find device before checking for the filesystem %v; %s", device, err)
188202
}
189203

190204
out, err := c.command.ExecuteWithTimeout(ctx, "blkid", 5*time.Second, true, device)

0 commit comments

Comments
 (0)