#!/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 + . 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