#!/bin/ash
# Get all partitions with given id (list of /dev/sdXX)
# Works for MBR/DOS by looking at the type (1 byte)
# and for GPT by looking for the label 'OpenSLX-ID$1'
# in case an id was given, or with the given UUID,
# or with the given name.
# The output will be a list of matching devices,
# sorted from largest to smallest.
dev_find_partitions() {
local ID dev target rw
# check if only interested in writable devices
rw=
if [ "$1" = "--rw" ]; then
shift
rw=1
fi
# target for the scan, defaults to /dev to check everything
if [ "${1:0:1}" = "/" ] && [ -b "$1" ]; then
target="$1"
shift
elif [ "${1:0:1}" = "/" ] && [ -d "$1" ]; then
target="$1/"
shift
else
target="/dev/"
fi
local want_label="never match this#"
local want_type="never match this#"
local want_uuid="never match this#"
while [ "$#" -gt 0 ]; do
ID="$1"
shift
[ -z "$ID" ] && continue
if regex_imatch "$ID" "^[0-9a-f]$"; then
want_type="$want_type|0$ID"
want_label="$want_label|OpenSLX-ID0$ID"
elif regex_imatch "$ID" "^[0-9a-f]{2}$"; then
want_type="$want_type|$ID"
want_label="$want_label|OpenSLX-ID$ID"
elif regex_imatch "$ID" "^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$"; then
want_uuid="$want_uuid|$ID"
elif [ "${#ID}" -gt 3 ]; then # Safety measure: Want label length >= 4 chars
want_label="$want_label|$( regex_escape "$ID" )"
fi
done
local label number mbrid uuid
for dev in $(find $target* -maxdepth 0 -type b); do
[ -n "$rw" ] && [ "$( cat "/sys/class/block/${dev##*/}/ro" )" = "1" ] && continue
dev_get_type "$dev" || continue
is_on "$SLX_DEBUG" && echo "$dev is $number - MBR=$mbrid, UUID=$uuid, label=$label" >&2
if regex_imatch "$mbrid" "^($want_type)$" || regex_imatch "$uuid" "^($want_uuid)$" \
|| regex_match "$label" "^($want_label)$"; then
printf "%s\n" "$(blockdev --getsize64 "$dev") $dev"
fi
done | sort -n -k1 -r | cut -d' ' -f2
}
# Pass partition block device. If it could be identified successfully,
# fills the variables number, mbrid, uuid, label, depending on MBR/GPT.
# Otherwise, return code is != 0 and contents are undefined.
# This only makes sense if called within this script, or if slx-tools
# was sourced, otherwise the variables will be inaccessible.
dev_get_type() {
local part dev partn devlist devn pstart
dev=
mbrid=
uuid=
label=
number=
[ -b "$1" ] || return 1
part="$( readlink -f "$1" )"
partn="${part##*/}"
devlist="$( printf "%s" "$partn" | sed -r 's/[0-9]+$//' )"
[ "$partn" = "$devlist" ] && return 1
for devn in "${devlist}" "${devlist%p}"; do
if [ -f "/sys/block/${devn}/${partn}/uevent" ]; then
dev="/dev/${devn}"
break
fi
done
[ -z "$dev" ] && return 1
pstart="$( cat "/sys/block/${devn}/${partn}/start" )" # For validation
label="$( grep -Po '(?<=^PARTNAME=).*$' "/sys/block/${devn}/${partn}/uevent" )"
number="$( grep -Po '(?<=^PARTN=).*$' "/sys/block/${devn}/${partn}/uevent" )"
number="$( cat "/sys/block/${devn}/${partn}/partition" )"
local gpt=
if [ "$( dd if="$dev" bs=1 count=8 skip=512 )" = "EFI PART" ]; then
gpt=512
elif [ "$( dd if="$dev" bs=1 count=8 skip=4096 )" = "EFI PART" ]; then
gpt=4096
fi 2> /dev/null
is_on "$SLX_DEBUG" && echo "Checking $partn (on $dev) GPT='$gpt', label='$label'" >&2
# Check if mbr signatur is there, no GPT label was in uevent, and no protective MBR
local t1="$( __read_mbrid "$dev" 0 1 )"
if [ -z "$label" ] && [ "$number" -lt 1000 ] && [ "$number" -gt 0 ] \
&& [ "$t1" != "ee" ] && [ "$( __read_mbrsig "$dev" 0 )" = "55aa" ]; then
# Then we consider parsing this as MBR
if [ "$number" -le 4 ]; then # 1-4 are primary partitions
# Validate start LBA matches sysfs
log_start="$( __read_mbrstart "$dev" 0 "$number" )"
if [ "$pstart" != "$log_start" ]; then
echo "Found partition $number on $dev, but start mismatch (want: $pstart found: $log_start)" >&2
else
mbrid="$( __read_mbrid "$dev" 0 "$number" )"
return 0
fi
else # part number 5+ -- must be logical volume in extended partition
# Scan for Primary type 05/0f, scan linked list of fake MBRs from there
local no id ex_start log_id log_start next_id next_start current
for no in 1 2 3 4; do
id="$( __read_mbrid "$dev" 0 "$no" )"
if [ "$id" = "05" ] || [ "$id" = "0f" ]; then
id="found"
break
fi
done
if [ "$id" != "found" ]; then
echo "No matching extended primary partition for $number found on $dev" >&2
else
is_on "$SLX_DEBUG" && echo "Primary $no is extended partition" >&2
ex_start="$( __read_mbrstart "$dev" 0 "$no" )"
current="$ex_start"
no=5 # Count through logical partitions. These start at 5.
while [ "$no" -le "$number" ]; do
if [ "$( __read_mbrsig "$dev" "$current" )" != "55aa" ]; then
echo "Unexpected end of logical partition at $no of $number on $dev" >&2
break
fi
log_id="$( __read_mbrid "$dev" "$current" 1 )"
if [ "$no" -eq "$number" ]; then
log_start="$( __read_mbrstart "$dev" "$current" 1 )"
# logical partition's start is relative to its fake MBR
log_start="$(( log_start + current ))"
if [ "$pstart" != "$log_start" ]; then
echo "Found partition $no on $dev, but start mismatch (want: $pstart found: $log_start)" >&2
break
fi
mbrid="$log_id"
return 0
fi
next_id="$( __read_mbrid "$dev" "$current" 2 )"
# next logical partition's fake MBR is relative to the extended partition's start
next_start="$( __read_mbrstart "$dev" "$current" 2 )"
is_on "$SLX_DEBUG" && echo "Extended id $log_id, next $next_id, $next_start" >&2
[ "$next_id" = "0f" ] && next_id="05"
if [ "$next_id" = "05" ] && [ "$next_start" -ge 512 ]; then
current="$(( next_start + ex_start ))"
no="$(( no + 1 ))" # Increase counter for next logical partition
else
break
fi
done
fi
fi
fi
if [ -n "$gpt" ]; then
# GPT
local table_start current entries no entry_size log_start readoff num byte
table_start="$( __read_le "$dev" "$(( gpt + 72 ))" 8 )"
entries="$( __read_le "$dev" "$(( gpt + 80 ))" 4 )"
entry_size="$( __read_le "$dev" "$(( gpt + 84 ))" 4 )"
current="$(( table_start * gpt ))"
if ! [ "$current" -ge "$(( gpt * 2 ))" ] || ! [ "$entries" -gt 0 ] \
|| [ "$entries" -lt "$number" ] || ! [ "$entry_size" -le 4096 ]; then
echo "Bad GPT. Start: $current, Partition count: $entries. Want: $number" >&2
return 1
fi
log_start="$( __read_le "$dev" "$(( current + entry_size * (number - 1) + 32 ))" 8 )"
if [ "$log_start" != "$pstart" ]; then
echo "Found partition $number, but start mismatch (want: $pstart found: $log_start)" >&2
return 1
fi
# Convert raw hex stream to proper string representation. First 3 groups are little endian.
uuid="$( dd if="$dev" bs=1 count=16 skip="$(( current + entry_size * (number - 1) ))" 2> /dev/null \
| __bin2hex | sed -r 's/^(..)(..)(..)(..)(..)(..)(..)(..)(....)/\4\3\2\1-\6\5-\8\7-\9-/' )"
if [ -z "$label" ]; then
readoff="$(( current + entry_size * (number - 1) + 56 ))"
num=0
while [ "$num" -lt 36 ]; do
byte="$( __read_le "$dev" "$readoff" "1" )"
if [ "$byte" -ge 32 ] && [ "$byte" -le 126 ]; then
byte="$( dd if="$dev" bs=1 count="1" skip="$readoff" 2> /dev/null )"
label="$label$byte"
fi
readoff=$(( readoff + 2 ))
num=$(( num + 1 ))
done
fi
return 0
fi
# Unknown
return 1
}
# Get version of given swap partition. $1 will be checked for the
# swap signature, and if found, the version number will be returned.
# old-style swap will be reported as version 1,
# new-style as 1 + <version_field>.
dev_swap_version() {
local sig version
sig="$( dd if="$1" bs=1 count=10 skip=4086 2> /dev/null )"
if [ "$sig" = "SWAP-SPACE" ]; then
echo 1
return 0
elif [ "$sig" = "SWAPSPACE2" ]; then
version="$( __read_le "$1" 1024 4 )"
# I think there is only '1' currently, but try to be clever...
if [ "$version" -gt 0 ] && [ "$version" -lt 8 ]; then
echo $(( 1 + version ))
return 0
fi
fi
return 1
}
# stdin = binary data, stdout = raw, unformatted hex
# __hex2bin()
# stdin = binary data, stdout = raw, unformatted hex
# $1 = number of bytes to read and reverse (1, 2, 4, 8)
# __hex2bin_le()
# Get MBR type of partition (1 byte) as hex
# Pass "$device" "$lba_offset" "$partition_number"
__read_mbrid() {
dd if="$1" bs=1 skip=$(( 512 * $2 + 446 + ($3 - 1) * 16 + 4 )) count=1 2> /dev/null | __bin2hex
}
# Get LBA start address of MBR partition
# Pass "$device" "$lba_offset" "$partition_number"
__read_mbrstart() {
__read_le "$1" "$(( 512 * $2 + 446 + ($3 - 1) * 16 + 8 ))" 4
}
# Read the MBR signature in the given sector
# Pass "$device" "$lba_offset"
__read_mbrsig() {
dd if="$1" bs=1 skip=$(( 512 * $2 + 510 )) count=2 2> /dev/null | __bin2hex
}
# Read a little endian value at given byte offset
# Pass "$source_path" "$byte_offset" "$size"
__read_le() {
local v="$( dd if="$1" bs=1 count="$3" skip="$2" 2> /dev/null | __bin2hex_le "$3" )"
echo $(( 0x$v ))
}
__init() {
local t
# init __hex2bin
t="7a32337465737431323374657374"
if [ "$( printf z23test123test | xxd -p | tr -d '\n ' )" = "$t" ]; then
#echo "Using xxd" >&2
__bin2hex() { xxd -p | tr -d '\n '; }
elif [ "$( printf z23test123test | od -t x1 -An | tr -d '\n ' )" = "$t" ]; then
#echo "Using od" >&2
__bin2hex() { od -t x1 -An | tr -d '\n '; }
elif [ "$( printf z23test123test | hexdump -ve '/1 "%02x"' )" = "$t" ]; then
#echo "Using hexdump" >&2
__bin2hex() { hexdump -ve '/1 "%02x"'; }
else
echo "No suitable tool for converting binary to hex"
exit 1
fi >&2 2> /dev/null
# init __hex2bin_le
t="383736353433327a"
if [ "$( printf z2345678 | xxd -e -g 8 | cut -d' ' -f2 )" = "$t" ]; then
#echo "Using xxd" >&2
__bin2hex_le() { xxd -e -g "$1" | cut -d' ' -f2; }
elif [ "$( printf z2345678 | od -t x8 -An | tr -d '\n ' )" = "$t" ]; then
#echo "Using od" >&2
__bin2hex_le() { od -t "x$1" -An | tr -d '\n '; }
elif [ "$( printf z2345678 | hexdump -ve '/4 "%02x"' \
| sed 's/\(........\)\(........\)/\2\1/' )" = "$t" ]; then
#echo "Using hexdump" >&2
__bin2hex_le() {
if [ "$1" -lt 8 ]; then
hexdump -ve "/$1"' "%02x"'
else
hexdump -ve '/4 "%02x"' | sed 's/\(........\)\(........\)/\2\1/'
fi
}
elif [ "$( printf z2345678 | xxd -g 8 | cut -d' ' -f2 | awk '{ for (i=length-1;i>0;i-=2)x=x substr($0,i,2);}END{print x}' )" = "$t" ]; then
__bin2hex_le() { xxd -g "$1" | cut -d' ' -f2 | awk '{ for (i=length-1;i>0;i-=2)x=x substr($0,i,2);}END{print x}'; }
else
echo "No suitable tool for converting binary data to little endian hex"
exit 1
fi >&2 2> /dev/null
unset -f __init
}
__init