diff --git a/xorriso-dd-target/xorriso-dd-target b/xorriso-dd-target/xorriso-dd-target new file mode 100755 index 00000000..c1e45c90 --- /dev/null +++ b/xorriso-dd-target/xorriso-dd-target @@ -0,0 +1,469 @@ +#!/bin/sh + +# Copyright (c) 2019 +# Nio Wiklund alias sudodus +# Thomas Schmitt +# Provided under GPL version 2 or later. + +# Check whether we are on GNU/Linux +if uname -s | grep -v '^Linux' >/dev/null +then + echo "This program is entirely specialized on Linux kernel device names." >&2 + echo "Found to be on: '$(uname -s)'" >&2 + exit 2 +fi + +# Accept sudo-executable commands only in well known directories. +# (Listed with increasing priority.) +lsblk_cmd= +dd_cmd= +if test "$(whoami)" = "root" +then + sudo_x_dir_list="/usr/bin /bin /usr/sbin /sbin" +else + sudo_x_dir_list="/usr/sbin /sbin /usr/bin /bin" +fi +for i in $sudo_x_dir_list +do + if test -x "$i"/lsblk + then + lsblk_cmd="$i"/lsblk + fi + if test -x "$i"/dd + then + dd_cmd="$i"/dd + fi + if test -x "$i"/umount + then + umount_cmd="$i"/umount + fi +done +if test -z "$lsblk_cmd" +then + echo "No executable program lsblk found in: $sudo_x_dir_list" >&2 + exit 5 +fi + +print_usage() { + echo "usage: $0 [options] [device_name [device_name ...]]" + echo + echo "Looks on GNU/Linux for USB and Memory Card devices and evaluates" + echo "whether the found devices are plausible targets for image copying." + echo "If no device names and no -list_all are given, then a plain list of" + echo "advisable device names is printed to stdout. One per line." + echo "Device names must not begin by '-' and must be single words. They must" + echo "not contain '/'. E.g. 'sdc' is valid, '/dev/sdc' is not valid." + echo "If devices names are given, then they get listed with advice shown." + echo "If one of the given device names gets not advised, the exit value is 1." + echo + echo "Only if option -DO_WRITE is given and -list_all is not, and if exactly" + echo "one advisable device is listed, it really gets overwritten by the" + echo "file content of the given -image_file. In this case the exit value" + echo "is zero if writing succeeded, non-zero else." + echo + echo "Options:" + echo " -list_all Print list of all found devices with advice, vendor" + echo " and model, One per line. Ignore any device names." + echo " Ignore -DO_WRITE." + echo " -with_vendor_model Print vendor and model with each submitted device." + echo + echo " -max_size n[M|G|T] Set upper byte size limit for advisable devices." + echo " Plain numbers get rounded down to full millions." + echo " Suffix: M = million, G = billion, T = trillion." + echo " Be generous to avoid problems with GB < GiB." + echo " -min_size n[M|G|T] Set lower byte size limit for advisable devices." + echo " After processing like with -max_size, one million" + echo " gets added to the size limit." + echo " -look_for_iso Demand presence of an ISO 9660 filesystem. If so," + echo " any further filesystem type is acceptable on that" + echo " device. Else only ISO 9660 and VFAT are accepted." + echo " -with_sudo Run '$lsblk_cmd -o FSTYPE' by sudo." + echo " If so, consider to enable this without password." + echo " If no filesystems are detected and the program" + echo " has no superuser power, the device is not advised." + echo " If -DO_WRITE is given, run umount and dd by sudo." + echo " -image_file PATH Set the path of the image file which shall be" + echo " written to a device. Its size will be set as" + echo " -min_size." + echo " -DO_WRITE Write the given -image_file to the one advisable" + echo " device that is found. If more than one such device" + echo " is found, then they get listed but no writing" + echo " happens. In this case, re-run with one of the" + echo " advised device names to get a real write run." + echo + echo " -help Print this text to stdout and then end the program." + echo "Examples:" + echo " $0 -with_sudo -list_all" + echo " $0 sdc" + echo " $0 -with_sudo -image_file debian-live-10.0.0-amd64-xfce.iso -DO_WRITE" + echo +} + +# Roughly convert human readable sizes and plain numbers to 1 / million +round_down_div_million() { + sed -e 's/\.[0-9]*//' -e 's/[0-9][0-9][0-9][0-9][0-9][0-9]$//' \ + -e 's/[Mm]//' -e 's/[Gg]/000/' -e 's/[Tt]/000000/' +} + +### Assessing arguments and setting up the job + +# Settings +reset_job() { + list_all= + show_reasons= + look_for_iso= + devs= + devs_named= + max_size= + with_vendor_model= + with_sudo= + image_file= + do_write= + + # Status + sudo_cmd= + have_su_power= +} + +arg_interpreter() { + next_is= + for i in "$@" + do + # The next_is option parameter readers get programmed by the -options + if test "$next_is" = "max_size" + then + max_size="$(echo "$i" | round_down_div_million)" + next_is= + elif test "$next_is" = "min_size" + then + min_size="$(echo "$i" | round_down_div_million)" + min_size="$(expr $min_size + 1)" + next_is= + elif test "$next_is" = "image_file" + then + image_file="$i" + min_size="$(stat -c '%s' "$i" | round_down_div_million)" + if test -z "$min_size" + then + echo "WARNING: Cannot obtain size of -image_file '$i'" >&2 + else + min_size="$(expr $min_size + 1)" + fi + next_is= + elif test "$i" = "-list_all" + then + list_all=y + with_vendor_model=y + show_reasons=y + elif test "$i" = "-max_size" + then + next_is="max_size" + elif test "$i" = "-min_size" + then + next_is="min_size" + elif test "$i" = "-with_vendor_model" + then + with_vendor_model=y + elif test "$i" = "-look_for_iso" + then + look_for_iso=y + elif test "$i" = "-with_sudo" + then + with_sudo=y + elif test "$i" = "-image_file" + then + next_is="image_file" + elif test "$i" = "-DO_WRITE" + then + do_write=y + elif test "$i" = "-help" + then + print_usage + exit 0 + elif echo "$i" | grep -v '^-' >/dev/null + then + devs_named=y + devs="$devs $i" + show_reasons=y + else + echo "$0 : Unknown option: $i" >&2 + echo >&2 + print_usage >&2 + exit 1 + fi + done + + # Predict superuser power. Possibly enable sudo with lsblk -o FSTYPE and dd. + if test "$(whoami)" = "root" + then + have_su_power=y + elif test -n "$with_sudo" + then + sudo_cmd=sudo + have_su_power=y + fi +} + +## Evaluation of available devices and suitability +list_devices() { + if test -n "$list_all" + then + devs= + fi + if test -z "$devs" + then + # Obtain list of top-level names which do not look like CD or floppy. + devs=$($lsblk_cmd -d -n -o NAME | grep -v '^sr[0-9]' | grep -v '^fd[0-9]') + fi + + not_advised=0 + for name in $devs + do + # Collect reasons + tasty= + yucky= + reasons= + good_trans= + good_fs= + bad_fs= + + # Unwanted device name patterns + if (echo "$name" | grep '^sd[a-z][1-9]' >/dev/null) \ + || (echo "$name" | grep '^mmcblk.*p[0-9]' >/dev/null) \ + || (echo "$name" | grep '^nvme.*p[0-9]' >/dev/null) + then + yucky=y + reasons="${reasons}looks_like_disk_partition- " + fi + if echo "$name" | grep '^sr[0-9]' >/dev/null + then + yucky=y + reasons="${reasons}looks_like_cd_drive- " + fi + + # >>> recognize the device from which Debian Live booted + + # Connection type. Normally by lsblk TRAN, but in case of mmcblk artificial. + if echo "$name" | grep '^mmcblk[0-9]' >/dev/null + then + transports="mmcblk" + elif echo "$name" | fgrep "/" >/dev/null + then + echo "NOTE: The device name must not contain '/' characters" >&2 + transports=not_an_expected_name + reasons="${reasons}name_with_slash- " + else + transports=$($lsblk_cmd -n -o TRAN /dev/"$name") + fi + if test -z "$transports" + then + transports=lsblk_failed_tran + fi + for trans in $transports + do + if test "$trans" = "usb" -o "$trans" = "mmcblk" + then + good_trans="${trans}+" + tasty=y + elif test -n "$trans" + then + yucky=y + if test "$transports" = "not_an_expected_name" + then + dummy=dummy + else + if echo "$reasons" | fgrep -v "not_usb" >/dev/null + then + reasons="${reasons}not_usb- " + fi + fi + fi + done + + # Wanted or unwanted filesystem types + fstypes=$($sudo_cmd $lsblk_cmd -n -o FSTYPE /dev/"$name") + if test "$?" -gt 0 + then + fstypes="lsblk_fstype_error" + fi + # Get overview of filesystems + has_iso= + has_vfat= + has_other= + for fstype in $fstypes + do + if test "$fstype" = "iso9660" + then + has_iso=y + if echo "$good_fs" | fgrep -v "has_$fstype" >/dev/null + then + good_fs="${good_fs}has_${fstype}+ " + fi + elif test "$fstype" = "vfat" + then + has_vfat=y + if echo "$good_fs" | fgrep -v "has_$fstype" >/dev/null + then + good_fs="${good_fs}has_${fstype}+ " + fi + elif test -n "$fstype" + then + has_other=y + if echo "$bad_fs" | fgrep -v "has_$fstype" >/dev/null + then + bad_fs="${bad_fs}has_${fstype}- " + fi + fi + done + # Decide whether the found filesystems look dispensible enough + reasons="${reasons}${good_fs}${bad_fs}" + if test "${bad_fs}${good_fs}" = "" -a -z "$have_su_power" + then + yucky=y + reasons="${reasons}no_fs_while_not_su- " + elif test -n "$look_for_iso" + then + if test -n "$has_iso" + then + tasty=y + reasons="${reasons}look_for_iso++ " + else + yucky=y + reasons="${reasons}no_iso9660- " + fi + elif test -n "$has_other" + then + yucky=y + else + tasty=y + fi + + # Optional tests for size + if test -n "$max_size" -o -n "$min_size" + then + size=$($lsblk_cmd -n -b -o SIZE /dev/"$name" | head -1 | round_down_div_million) + if test -z "$size" + then + yucky=y + reasons="${reasons}lsblk_no_size- " + fi + fi + if test -n "$max_size" -a -n "$size" + then + if test "$size" -gt "$max_size" + then + yucky=y + reasons="${reasons}size_too_large- " + fi + fi + if test -n "$min_size" -a -n "$size" + then + if test "$size" -lt "$min_size" + then + yucky=y + reasons="${reasons}size_too_small- " + fi + fi + + # Now decide overall and report + descr= + if test -n "$with_vendor_model" + then + descr=": "$($lsblk_cmd -n -o VENDOR,MODEL /dev/"$name" | tr '\n\r' ' ' | tr -s ' ') + fi + if test -n "$yucky" + then + if test -n "$show_reasons" + then + echo "$name : NO : $reasons$descr" + fi + not_advised=1 + else + if test -n "$show_reasons" + then + echo "$name : YES : $good_trans $reasons$descr" + else + echo "$name" + fi + fi + done + return 0; +} + +write_image() { + + echo "--- not activated yet ---" + + if test -z "$umount_cmd" + then + echo "No executable program umount found in: $sudo_x_dir_list" >&2 + return 6 + fi + partitions=$($lsblk_cmd -n -p -o NAME /dev/"$2" | grep -v '^'/dev/"$2"'$' \ + | sed -e 's/[^a-zA-Z0-9_+@:.,/-]//g' | tr '\n\r' ' ') + echo "Would do: $sudo_cmd $umount_cmd /dev/"$2" $partitions" + + if test -z "$dd_cmd" + then + echo "No executable program dd found in: $sudo_x_dir_list" >&2 + return 6 + fi + echo "Would do: $sudo_cmd $dd_cmd if='${1}' bs=1M of=/dev/${2} ; sync" + + echo "--- not activated yet ---" + + return 0 +} + +reset_job +arg_interpreter "$@" +list_devices +if test -n "$list_all" +then + dummy=dummy +elif test -n "$do_write" +then + with_vendor_model= + show_reasons= + candidates=$(list_devices | tr '\n\r' ' ') + num=$(echo $candidates | wc -w) + if test "$num" -eq 1 + then + if test -n "$image_file" + then + write_image "$image_file" $candidates + exit $? + else + echo "Would write to /dev/$candidates if an -image_file were given." >&2 + exit 0 + fi + elif test "$num" -gt 1 + then + echo "WILL NOT WRITE ! More than one candidate found for target device:" >&2 + show_reasons=y + with_vendor_model=y + devs="$candidates" + list_devices >&2 + echo "HINT: Re-run with one of the names {$(echo $candidates | sed -e 's/ /,/g')} as additional argument." >&2 + exit 3 + else + if test -n "$devs_named" + then + echo "NO CANDIDATE FOR TARGET DEVICE AMONG THE GIVEN NAMES !" >&2 + echo "Overall available devices:" + else + echo "NO CANDIDATE FOR TARGET DEVICE FOUND !" >&2 + fi + list_all=y + show_reasons=y + with_vendor_model=y + list_devices >&2 + exit 4 + fi +fi + +if test -n "$devs" +then + exit $not_advised +fi +