From c5c593c5beb7d5598131fcc8816df0de5ef6d346 Mon Sep 17 00:00:00 2001 From: Keith Gable Date: Mon, 26 Feb 2024 15:07:42 -0800 Subject: [PATCH] Add support for early boot (#2) This PR adds support for early boot by creating a Dracut module and rewriting the ebsnvme-id script in Bash using nvme-cli >= 1.13 (not provided by Amazon Linux). It also changes the udev rules so they use udev env vars to set the block device mapping up, which should make the rules more resilient on failure. --- .editorconfig | 19 +++ .gitignore | 3 + 70-ec2-nvme-devices.rules | 12 +- Makefile | 14 ++ amazon-ec2-utils.spec | 110 ++++++++----- doc/ebsnvme-id.8 | 23 +-- dracut/dracut.conf | 7 + dracut/module-setup.sh | 35 ++++ ebsnvme-id | 337 +++++++++++++++++++------------------- 9 files changed, 336 insertions(+), 224 deletions(-) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 dracut/dracut.conf create mode 100644 dracut/module-setup.sh mode change 100755 => 100644 ebsnvme-id diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5780041 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 2 + +[Makefile] +indent_style = tab + +[*.{diff,md}] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..63a543f --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.rpm +*.tar +*.tar.gz diff --git a/70-ec2-nvme-devices.rules b/70-ec2-nvme-devices.rules index 6fae458..d6d301b 100644 --- a/70-ec2-nvme-devices.rules +++ b/70-ec2-nvme-devices.rules @@ -13,9 +13,15 @@ KERNEL=="nvme[0-9]*n[0-9]*", ENV{DEVTYPE}=="disk", ATTRS{serial}=="?*", ATTRS{mo KERNEL=="nvme[0-9]*n[0-9]*p[0-9]*", ENV{DEVTYPE}=="partition", ATTRS{serial}=="?*", ATTRS{model}=="?*", IMPORT{program}="/usr/sbin/ec2nvme-nsid %k" KERNEL=="nvme[0-9]*n[0-9]*p[0-9]*", ENV{DEVTYPE}=="partition", ATTRS{serial}=="?*", ATTRS{model}=="?*", ENV{_NS_ID}=="?*", SYMLINK+="disk/by-id/nvme-$attr{model}_$attr{serial}-ns-$env{_NS_ID}-part%n", OPTIONS+="string_escape=replace" -# ebs nvme devices -KERNEL=="nvme[0-9]*n[0-9]*", ENV{DEVTYPE}=="disk", ATTRS{model}=="Amazon Elastic Block Store", PROGRAM="/usr/sbin/ebsnvme-id -u /dev/%k", SYMLINK+="%c" -KERNEL=="nvme[0-9]*n[0-9]*p[0-9]*", ENV{DEVTYPE}=="partition", ATTRS{model}=="Amazon Elastic Block Store", PROGRAM="/usr/sbin/ebsnvme-id -u /dev/%k", SYMLINK+="%c%n" +# Import variables for EBS volumes +KERNEL=="nvme[0-9]*n[0-9]*", ENV{DEVTYPE}=="disk", ATTRS{model}=="Amazon Elastic Block Store", IMPORT{program}+="/usr/sbin/ebsnvme-id -U %k" +KERNEL=="nvme[0-9]*n[0-9]*p[0-9]*", ENV{DEVTYPE}=="partition", ATTRS{model}=="Amazon Elastic Block Store", IMPORT{program}+="/usr/sbin/ebsnvme-id -U %k" + +# Assign symlinks +KERNEL=="nvme[0-9]*n[0-9]*", ENV{DEVTYPE}=="disk", ATTRS{model}=="Amazon Elastic Block Store", ENV{_EC2_BLOCK_DEVICE_MAPPING}=="?*", SYMLINK+="$env{_EC2_BLOCK_DEVICE_MAPPING}" +KERNEL=="nvme[0-9]*n[0-9]*", ENV{DEVTYPE}=="disk", ATTRS{model}=="Amazon Elastic Block Store", ENV{_EBS_VOLUME_ID}=="?*", SYMLINK+="disk/by-id/ebs-$env{_EBS_VOLUME_ID}" +KERNEL=="nvme[0-9]*n[0-9]*p[0-9]*", ENV{DEVTYPE}=="partition", ATTRS{model}=="Amazon Elastic Block Store", ENV{_EC2_BLOCK_DEVICE_MAPPING}=="?*", SYMLINK+="$env{_EC2_BLOCK_DEVICE_MAPPING}$env{ID_PART_ENTRY_NUMBER}" +KERNEL=="nvme[0-9]*n[0-9]*p[0-9]*", ENV{DEVTYPE}=="partition", ATTRS{model}=="Amazon Elastic Block Store", ENV{_EBS_VOLUME_ID}=="?*", SYMLINK+="disk/by-id/ebs-$env{_EBS_VOLUME_ID}-part$env{ID_PART_ENTRY_NUMBER}" # Do not timeout I/O operations on EBS volumes. KERNEL=="nvme[0-9]*n[0-9]*", ENV{DEVTYPE}=="disk", ATTRS{model}=="Amazon Elastic Block Store", ATTR{queue/io_timeout}="4294967295" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0caffed --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the MIT License. See the LICENSE accompanying this file +# for the specific language governing permissions and limitations under +# the License. + +RPM_BUILD_DIR := $(shell mktemp -d /tmp/amazon-linux-utils.rpm-XXXXXXXX) + +.PHONY: rpm +rpm: + git archive --format=tar HEAD > amazon-ec2-utils.tar + gzip -f -9 amazon-ec2-utils.tar + rpmbuild --define "%_topdir $(RPM_BUILD_DIR)" -ta amazon-ec2-utils.tar.gz -v + cp -fv $(RPM_BUILD_DIR)/RPMS/**/*.rpm $(RPM_BUILD_DIR)/SRPMS/*.rpm . diff --git a/amazon-ec2-utils.spec b/amazon-ec2-utils.spec index 34332e8..1be482b 100644 --- a/amazon-ec2-utils.spec +++ b/amazon-ec2-utils.spec @@ -1,20 +1,12 @@ +%define dracutlibdir %{_prefix}/lib/dracut + Name: amazon-ec2-utils Summary: A set of tools for running in EC2 Version: 2.2.0 Release: 1%{?dist} License: MIT Group: System Tools - -Source0: ec2-metadata -Source1: ec2udev-vbd -Source2: 51-ec2-hvm-devices.rules -Source16: 60-cdrom_id.rules -Source22: 70-ec2-nvme-devices.rules -Source23: ec2nvme-nsid -Source24: ebsnvme-id -Source25: 51-ec2-xen-vbd-devices.rules -Source26: 53-ec2-read-ahead-kb.rules - +Source: amazon-ec2-utils.tar.gz URL: https://github.com/aws/amazon-ec2-utils BuildArch: noarch Provides: ec2-utils = %{version}-%{release} @@ -22,63 +14,93 @@ Obsoletes: ec2-utils < 2.1 Provides: ec2-metadata = %{version}-%{release} Obsoletes: ec2-metadata <= 0.1 Requires: curl -Requires: python3 -BuildRequires: python3-devel -BuildRequires: systemd-rpm-macros -BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX) +Requires: nvme-cli >= 1.13 +BuildRequires: gzip systemd-rpm-macros +BuildRoot: %{_tmppath}/%{name}-root %description -amazon-ec2-utils contains a set of utilities for running in ec2. +amazon-ec2-utils contains a set of utilities for running in EC2. %prep +%autosetup -c %build +# compress manpages +gzip -f -9 doc/*.8 %install -rm -rf $RPM_BUILD_ROOT -mkdir -p $RPM_BUILD_ROOT%{_bindir} -mkdir -p $RPM_BUILD_ROOT%{_udevrulesdir} -mkdir -p $RPM_BUILD_ROOT/%{_sbindir} -mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/udev/rules.d/ -mkdir -p $RPM_BUILD_ROOT%{_mandir}/man8/ - -install -m755 %{SOURCE0} $RPM_BUILD_ROOT%{_bindir} -install -m755 %{SOURCE1} $RPM_BUILD_ROOT/%{_sbindir} -install -m644 %{SOURCE2} $RPM_BUILD_ROOT%{_udevrulesdir} -install -m644 %{SOURCE25} $RPM_BUILD_ROOT%{_udevrulesdir} -install -m644 %{SOURCE26} $RPM_BUILD_ROOT%{_udevrulesdir} +# Directories +%{__install} -d -pm 755 %{buildroot}%{_bindir} +%{__install} -d -pm 755 %{buildroot}%{_mandir}/man8 +%{__install} -d -pm 755 %{buildroot}%{_sbindir} +%{__install} -d -pm 755 %{buildroot}%{_sysconfdir}/dracut.conf.d +%{__install} -d -pm 755 %{buildroot}%{_sysconfdir}/udev/rules.d +%{__install} -d -pm 755 %{buildroot}%{_udevrulesdir} +%{__install} -d -pm 755 %{buildroot}%{dracutlibdir}/modules.d/96ec2-utils + +# Scripts +%{__install} -pm 755 ebsnvme-id %{buildroot}%{_sbindir}/ebsnvme-id +%{__install} -pm 755 ec2-metadata %{buildroot}%{_bindir}/ec2-metadata +%{__install} -pm 755 ec2nvme-nsid %{buildroot}%{_sbindir}/ec2nvme-nsid +%{__install} -pm 755 ec2udev-vbd %{buildroot}%{_sbindir}/ec2udev-vbd + +# man pages +%{__install} -pm 644 doc/ebsnvme-id.8.gz %{buildroot}%{_mandir}/man8/ebsnvme-id.8.gz +%{__install} -pm 644 doc/ec2-metadata.8.gz %{buildroot}%{_mandir}/man8/ec2-metadata.8.gz + +# udev rules +%{__install} -pm 644 51-ec2-hvm-devices.rules %{buildroot}%{_udevrulesdir}/51-ec2-hvm-devices.rules +%{__install} -pm 644 51-ec2-xen-vbd-devices.rules %{buildroot}%{_udevrulesdir}/51-ec2-xen-vbd-devices.rules +%{__install} -pm 644 53-ec2-read-ahead-kb.rules %{buildroot}%{_udevrulesdir}/53-ec2-read-ahead-kb.rules +%{__install} -pm 644 70-ec2-nvme-devices.rules %{buildroot}%{_udevrulesdir}/70-ec2-nvme-devices.rules + # Install 60-cdrom_id.rules to /etc rather than %{_udevrulesdir} # because it is intended as an override of a systemd-provided rules # file: -install -m644 %{SOURCE16} $RPM_BUILD_ROOT%{_sysconfdir}/udev/rules.d/ - -#udev rules for nvme block devices and supporting scripts -install -m644 %{SOURCE22} $RPM_BUILD_ROOT%{_udevrulesdir} -install -m755 %{SOURCE23} $RPM_BUILD_ROOT%{_sbindir}/ec2nvme-nsid -install -m755 %{SOURCE24} $RPM_BUILD_ROOT/%{_sbindir} - -%check -%{python3} -m py_compile %{SOURCE24} +%{__install} -pm 644 60-cdrom_id.rules %{buildroot}%{_sysconfdir}/udev/rules.d/60-cdrom_id.rules -%clean -rm -rf $RPM_BUILD_ROOT +# Dracut module +%{__install} -pm 644 dracut/dracut.conf %{buildroot}%{_sysconfdir}/dracut.conf.d/ec2-utils.conf +%{__install} -pm 755 dracut/module-setup.sh %{buildroot}%{dracutlibdir}/modules.d/96ec2-utils/module-setup.sh %files +%license LICENSE +%doc README.md %{_bindir}/ec2-metadata +%{_mandir}/man8/ebsnvme-id.8.gz +%{_mandir}/man8/ec2-metadata.8.gz %{_sbindir}/ec2nvme-nsid %{_sbindir}/ebsnvme-id %{_sbindir}/ec2udev-vbd -/usr/lib/udev/rules.d/51-ec2-hvm-devices.rules -/usr/lib/udev/rules.d/51-ec2-xen-vbd-devices.rules -/usr/lib/udev/rules.d/53-ec2-read-ahead-kb.rules -/usr/lib/udev/rules.d/70-ec2-nvme-devices.rules -/etc/udev/rules.d/60-cdrom_id.rules +%{_udevrulesdir}/51-ec2-hvm-devices.rules +%{_udevrulesdir}/51-ec2-xen-vbd-devices.rules +%{_udevrulesdir}/53-ec2-read-ahead-kb.rules +%{_udevrulesdir}/70-ec2-nvme-devices.rules +%{_sysconfdir}/udev/rules.d/60-cdrom_id.rules + +%package dracut +Summary: Dracut module providing early boot support for EC2 devices +Requires: amazon-ec2-utils = %{version}-%{release} +Requires: dracut + +%description dracut +This subpackage contains the Dracut module that provides early boot support for EC2 +devices, like making EBS NVMe devices have names in /dev that match their EC2 block +device mapping names. + +%files dracut +%license LICENSE +%doc README.md +%{_sysconfdir}/dracut.conf.d/ec2-utils.conf +%{dracutlibdir}/modules.d/96ec2-utils/module-setup.sh %changelog * Thu Jan 18 2024 Keith Gable - 2.2.0-1 - Corrected issue where an ec2-metadata error was written to stdout - Change ec2nvme-nsid to use Bash string manipulation to improve performance and reliability +- Rewrite ebsnvme-id in Bash so it is usable in early boot +- Add Dracut module to support block device naming in early boot * Mon Jun 5 2023 Guillaume Delacour - 2.2.0-1 - Add `--quiet` option to `ec2-metadata --help` output diff --git a/doc/ebsnvme-id.8 b/doc/ebsnvme-id.8 index 0e04c3a..dbede45 100644 --- a/doc/ebsnvme-id.8 +++ b/doc/ebsnvme-id.8 @@ -5,32 +5,35 @@ \" of this license, visit \" http://creativecommons.org/licenses/by-sa/4.0/. \" SPDX-License-Identifier: CC-BY-SA-4.0 -.TH EBSNVME-ID 8 "May 4 2020" +.TH EBSNVME-ID 8 "Jan 19 2024" .SH NAME ebsnvme-id \- Reads and prints EBS information from NVMe devices .SH SYNOPSIS .B ebsnvme-id -.RI [ options ] " DEVICE" +.RI [ options ] .br .SH DESCRIPTION .B ebsnvme-id -uses the NVME Management Interface to read various metadata about -NVME-attached EBS storage volumes. It can be used to retrive the EBS +uses the NVMe Management Interface to read various metadata about +NVMe-attached EBS storage volumes. It can be used to retrive the EBS Volume ID or EC2 API device mapping value given the local block device name. .SH OPTIONS .TP -.B \-v, \-\-volume +.B \-v Return volume-id .TP -.B \-b, \-\-block-dev -Return block device mapping +.B \-b , \-u +Return block device mapping (\-u is a deprecated alias) .TP -.B \-u, \-\-udev -Output data in format suitable for udev rules +.B \-U +Output data for the device as udev environment environment variables +suitable for use with +.B IMPORT{program} +in a udev rule .TP -.B \-h, \-\-help +.B \-h Show summary of options. .SH SEE ALSO .BR nvme (1), diff --git a/dracut/dracut.conf b/dracut/dracut.conf new file mode 100644 index 0000000..dde93c6 --- /dev/null +++ b/dracut/dracut.conf @@ -0,0 +1,7 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the MIT License. See the LICENSE accompanying this file +# for the specific language governing permissions and limitations under +# the License. + +add_dracutmodules+=" ec2-utils " diff --git a/dracut/module-setup.sh b/dracut/module-setup.sh new file mode 100644 index 0000000..3a50e96 --- /dev/null +++ b/dracut/module-setup.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the MIT License. See the LICENSE accompanying this file +# for the specific language governing permissions and limitations under +# the License. + +# called by dracut +check() { + # don't install this module unless the configuration explicitly requests it + return 255 +} + +# called by dracut +depends() { + echo bash udev-rules + return 0 +} + +# called by dracut +install() { + # Install udev rules + inst_rules \ + 51-ec2-hvm-devices.rules \ + 51-ec2-xen-vbd-devices.rules \ + 53-ec2-read-ahead-kb.rules \ + 70-ec2-nvme-devices.rules + + # Install scripts + inst_multiple \ + ebsnvme-id \ + ec2nvme-nsid \ + ec2udev-vbd +} diff --git a/ebsnvme-id b/ebsnvme-id old mode 100755 new mode 100644 index 972e938..3b930cd --- a/ebsnvme-id +++ b/ebsnvme-id @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/bash # Copyright Amazon.com, Inc. and its affiliates. All Rights Reserved. # @@ -6,169 +6,172 @@ # for the specific language governing permissions and limitations under # the License. -""" -Usage: -Read EBS device information and provide information about -the volume. -""" - -from __future__ import print_function -import argparse -from ctypes import Structure, c_uint8, c_uint16, \ - c_uint32, c_uint64, c_char, addressof, sizeof -from fcntl import ioctl -import sys - -NVME_ADMIN_IDENTIFY = 0x06 -NVME_IOCTL_ADMIN_CMD = 0xC0484E41 -AMZN_NVME_VID = 0x1D0F -AMZN_NVME_EBS_MN = "Amazon Elastic Block Store" - - -class nvme_admin_command(Structure): - _pack_ = 1 - _fields_ = [("opcode", c_uint8), # op code - ("flags", c_uint8), # fused operation - ("cid", c_uint16), # command id - ("nsid", c_uint32), # namespace id - ("reserved0", c_uint64), - ("mptr", c_uint64), # metadata pointer - ("addr", c_uint64), # data pointer - ("mlen", c_uint32), # metadata length - ("alen", c_uint32), # data length - ("cdw10", c_uint32), - ("cdw11", c_uint32), - ("cdw12", c_uint32), - ("cdw13", c_uint32), - ("cdw14", c_uint32), - ("cdw15", c_uint32), - ("reserved1", c_uint64)] - - -class nvme_identify_controller_amzn_vs(Structure): - _pack_ = 1 - _fields_ = [("bdev", c_char * 32), # block device name - ("reserved0", c_char * (1024 - 32))] - - -class nvme_identify_controller_psd(Structure): - _pack_ = 1 - _fields_ = [("mp", c_uint16), # maximum power - ("reserved0", c_uint16), - ("enlat", c_uint32), # entry latency - ("exlat", c_uint32), # exit latency - ("rrt", c_uint8), # relative read throughput - ("rrl", c_uint8), # relative read latency - ("rwt", c_uint8), # relative write throughput - ("rwl", c_uint8), # relative write latency - ("reserved1", c_char * 16)] - - -class nvme_identify_controller(Structure): - _pack_ = 1 - _fields_ = [("vid", c_uint16), # PCI Vendor ID - ("ssvid", c_uint16), # PCI Subsystem Vendor ID - ("sn", c_char * 20), # Serial Number - ("mn", c_char * 40), # Module Number - ("fr", c_char * 8), # Firmware Revision - ("rab", c_uint8), # Recommend Arbitration Burst - ("ieee", c_uint8 * 3), # IEEE OUI Identifier - ("mic", c_uint8), # Multi-Interface Capabilities - ("mdts", c_uint8), # Maximum Data Transfer Size - ("reserved0", c_uint8 * (256 - 78)), - ("oacs", c_uint16), # Optional Admin Command Support - ("acl", c_uint8), # Abort Command Limit - ("aerl", c_uint8), # Asynchronous Event Request Limit - ("frmw", c_uint8), # Firmware Updates - ("lpa", c_uint8), # Log Page Attributes - ("elpe", c_uint8), # Error Log Page Entries - ("npss", c_uint8), # Number of Power States Support - ("avscc", c_uint8), # Admin Vendor Specific Command Configuration # noqa - ("reserved1", c_uint8 * (512 - 265)), - ("sqes", c_uint8), # Submission Queue Entry Size - ("cqes", c_uint8), # Completion Queue Entry Size - ("reserved2", c_uint16), - ("nn", c_uint32), # Number of Namespaces - ("oncs", c_uint16), # Optional NVM Command Support - ("fuses", c_uint16), # Fused Operation Support - ("fna", c_uint8), # Format NVM Attributes - ("vwc", c_uint8), # Volatile Write Cache - ("awun", c_uint16), # Atomic Write Unit Normal - ("awupf", c_uint16), # Atomic Write Unit Power Fail - ("nvscc", c_uint8), # NVM Vendor Specific Command Configuration # noqa - ("reserved3", c_uint8 * (704 - 531)), - ("reserved4", c_uint8 * (2048 - 704)), - ("psd", nvme_identify_controller_psd * 32), # Power State Descriptor # noqa - ("vs", nvme_identify_controller_amzn_vs)] # Vendor Specific - - -class ebs_nvme_device: - def __init__(self, device): - self.device = device - self.ctrl_identify() - - def _nvme_ioctl(self, id_response, id_len): - admin_cmd = nvme_admin_command(opcode=NVME_ADMIN_IDENTIFY, - addr=id_response, - alen=id_len, - cdw10=1) - - with open(self.device, "r") as nvme: - ioctl(nvme, NVME_IOCTL_ADMIN_CMD, admin_cmd) - - def ctrl_identify(self): - self.id_ctrl = nvme_identify_controller() - self._nvme_ioctl(addressof(self.id_ctrl), sizeof(self.id_ctrl)) - - if self.id_ctrl.vid != AMZN_NVME_VID \ - or self.id_ctrl.mn.decode().strip() != AMZN_NVME_EBS_MN: - raise TypeError("[ERROR] Not an EBS device: '{0}'".format(self.device)) # noqa - - def get_volume_id(self): - vol = self.id_ctrl.sn.decode() - - if vol.startswith("vol") and vol[3] != "-": - vol = "vol-" + vol[3:] - - return vol - - def get_block_device(self, stripped=False): - dev = self.id_ctrl.vs.bdev.decode().strip() - - if stripped and dev.startswith("/dev/"): - dev = dev[5:] - - return dev - - -if __name__ == "__main__": - parser = \ - argparse.ArgumentParser(description="Reads EBS information from NVMe devices.") # noqa - parser.add_argument("device", nargs=1, help="Device to query") - - display = parser.add_argument_group("Display Options") - display.add_argument("-v", "--volume", action="store_true", - help="Return volume-id") - display.add_argument("-b", "--block-dev", action="store_true", - help="Return block device mapping") - display.add_argument("-u", "--udev", action="store_true", - help="Output data in format suitable for udev rules") - - if len(sys.argv) < 2: - parser.print_help() - sys.exit(1) - - args = parser.parse_args() - - get_all = not (args.udev or args.volume or args.block_dev) - - try: - dev = ebs_nvme_device(args.device[0]) - except (IOError, TypeError) as err: - print(err, file=sys.stderr) - sys.exit(1) - - if get_all or args.volume: - print("Volume ID: {0}".format(dev.get_volume_id())) - if get_all or args.block_dev or args.udev: - print(dev.get_block_device(args.udev)) +# Bash/Unix implementation of ebsnvme-id that requires nvme-cli >= 1.13 +# Since this has to run inside initramfs, this is not using getopt/getopts. + +# Change this in case your distro puts nvme-cli in a different location +NVME=/usr/sbin/nvme + +# The EBS model name +EBS_MN="Amazon Elastic Block Store" + +## +# Ensures that the kernel device name in $1 is actually an EBS volume. +ensure_ebs_volume() { + local device=$1 + local mn + + if [[ ! -b /dev/$device ]]; then + echo "[ERROR] /dev/$device is not a block device" >&2 + exit 1 + fi + + mn="$(read_nvme_attr "$device" mn)" + + if [[ "$mn" != "${EBS_MN}" ]]; then + echo "[ERROR] /dev/$device is not an EBS device, but is ${mn:-}" >&2 + exit 1 + fi +} + +## +# Prints the block device mapping name for the device specified in $1 +print_block_device_mapping() { + local device=$1 + local block_device_name + + block_device_name=$(read_block_device_name "$device") + + echo "${block_device_name}" +} + +## +# Prints udev env vars for the device specified in $1 +print_udev_env() { + local device=$1 + local bdev + local volid + + bdev=$(read_block_device_name "$device") + volid=$(read_volume_id "$device") + + if [[ -z $bdev ]] || [[ -z $volid ]]; then + echo "[ERROR] /dev/$device cannot determine udev environment" >&2 + exit 1 + fi + + echo "_EC2_BLOCK_DEVICE_MAPPING=${bdev}" + echo "_EBS_VOLUME_ID=${volid}" +} + +## +# Prints the volume ID for the device specified in $1 +print_volume_id() { + local device=$1 + local vol_id + + vol_id=$(read_volume_id "$device") + + echo "Volume ID: ${vol_id}" +} + +## +# Reads the block device name from the device specified in $1 and strips off +# any leading /dev/ that might be present +read_block_device_name() { + local device=$1 + local bdev + + ensure_ebs_volume "$device" + + bdev=$(read_nvme_attr "$device" bdev) + + if [[ -z $bdev ]]; then + echo "[ERROR] /dev/$device has no block device mapping available" >&2 + exit 1 + fi + + printf "%s" "${bdev#/dev/}" +} + +## +# Reads the NVMe attribute specified by $2 from the device specified in $1 +read_nvme_attr() { + local device=$1 + local nvme_attr=$2 + + ${NVME} amzn id-ctrl "/dev/${device}" | while read -r line; do + if [[ $line =~ ^${nvme_attr}[[:space:]]*: ]]; then + printf "%s" "${line#*: }" + return 0 + fi + done + + return 1 +} + +## +# Reads the volume ID for the device specified in $1 and parses it into a +# regular EBS vol-abcdef12345 format. +read_volume_id() { + local device=$1 + local sn + + ensure_ebs_volume "$device" + + sn=$(read_nvme_attr "$device" sn) + + if [[ -z $sn ]]; then + echo "[ERROR] /dev/$device has no volume ID available" >&2 + exit 1 + fi + + # Strip the prefix, which is usually vol, but we should tolerate vol- too + local without_prefix=${sn#vol} + + # Print out vol-XXX after stripping the - prefix which is probably not present + printf "vol-%s" "${without_prefix#-}" +} + +## +# Prints program usage information and exits +usage() { + echo "usage: ebsnvme-id [OPTION] + +Reads EBS information from NVMe devices. + +Options: + -h show this help message and exit + -v DEVICE Return volume ID + -b DEVICE Return block device mapping + -u DEVICE Deprecated alias for -b DEVICE + -U DEVICE Output data in a format suitable to be used as an IMPORT{program} in a udev rule +" +} + +# Check if we have enough arguments +if [[ $# -ne 2 ]]; then + usage + exit 1 +fi + +# Name the arguments and strip any leading /dev +option="$1" +device="${2#/dev/}" + +# Parse options +case $option in + -U) + print_udev_env "$device" + ;; + -b | -u) + print_block_device_mapping "$device" + ;; + -v) + print_volume_id "$device" + ;; + *) + usage + exit 1 + ;; +esac