#!/bin/bash
# License: GPL 
# Author: Steven Shiau <steven _at_ clonezilla org>, Thomas Tsai <thomas _at_ nchc org tw>.
# Description: Program to clean some hardware record, e.g. NIC MAC address.

#
DRBL_SCRIPT_PATH="${DRBL_SCRIPT_PATH:-/usr/share/drbl}"
. $DRBL_SCRIPT_PATH/sbin/drbl-conf-functions
. /etc/drbl/drbl-ocs.conf
. $DRBL_SCRIPT_PATH/sbin/ocs-functions

# Provide get_diskname if not already loaded from ocs-functions
if ! type get_diskname &>/dev/null; then
  get_diskname() {
    local dev="${1#/dev/}"
    if [[ "$dev" =~ ^(.*[0-9])p[0-9]*$ ]]; then
      echo "${BASH_REMATCH[1]}"
    else
      echo "${dev%%[0-9]*}"
    fi
  }
fi

# Provide get_restored_os_extra_root_path if not already loaded from ocs-functions
if ! type get_restored_os_extra_root_path &>/dev/null; then
  get_restored_os_extra_root_path() {
    # Function to find the subvolume root path under the mount point for btrfs or similar layouts
    # Returns extra path like "/@/.snapshots/1/snapshot", "/@", "/root", or ""
    local target_mnt="$1"
    local def_subvol_out def_path isnap cand sub

    # 1. Standard flat root: if /boot and (/etc/os-release or /etc/fstab) exist directly at root
    if [ -d "$target_mnt/boot" ] && [ -f "$target_mnt/etc/os-release" -o -f "$target_mnt/etc/fstab" ]; then
      echo ""
      return 0
    fi

    # 2. btrfs subvolume get-default
    if type btrfs &>/dev/null; then
      def_subvol_out="$(btrfs subvolume get-default "$target_mnt" 2>/dev/null)"
      if [ -n "$def_subvol_out" ] && [ -z "$(echo "$def_subvol_out" | grep "(FS_TREE)")" ]; then
        def_path="$(echo "$def_subvol_out" | sed -r -e 's/.*path[[:space:]]+//')"
        if [ -n "$def_path" ] && [ -d "$target_mnt/$def_path/boot" -o -f "$target_mnt/$def_path/etc/os-release" -o -f "$target_mnt/$def_path/etc/fstab" ]; then
          echo "/$def_path"
          return 0
        fi
      fi
    fi

    # 3. openSUSE Snapper layout: @/.snapshots/*/snapshot
    if [ -d "$target_mnt/@/.snapshots" ]; then
      for isnap in $(ls -vd "$target_mnt"/@/.snapshots/*/snapshot 2>/dev/null | tac); do
        if [ -d "$isnap/boot" -o -f "$isnap/etc/os-release" -o -f "$isnap/etc/fstab" ]; then
          echo "${isnap#$target_mnt}"
          return 0
        fi
      done
    fi

    # 4. Ubuntu / Debian / openSUSE flat / Arch @ layout
    if [ -d "$target_mnt/@/boot" -o -f "$target_mnt/@/etc/os-release" -o -f "$target_mnt/@/etc/fstab" ]; then
      echo "/@"
      return 0
    fi

    # 5. Fedora >= 38 root layout
    if [ -d "$target_mnt/root/boot" -o -f "$target_mnt/root/etc/os-release" -o -f "$target_mnt/root/etc/fstab" ]; then
      echo "/root"
      return 0
    fi

    # 6. Fallback: check any snapshot or subvolume with boot
    cand="$(find "$target_mnt" -maxdepth 4 -type d -name "boot" 2>/dev/null | head -n 1)"
    if [ -n "$cand" ]; then
      sub="${cand%/boot}"
      echo "${sub#$target_mnt}"
      return 0
    fi

    echo ""
    return 0
  }
fi

#
USAGE() {
    echo "$ocs - To remove the Linux udev persistent files"
    echo "Usage:"
    echo "To run $ocs:"
    echo "$ocs [OPTION] [PARTITION]"
    echo "PARTITION name can be with or without /dev/, e.g., /dev/sda1 or sda1."
    echo "If it is not assigned, all the partitions on the system will be searched."
    echo
    echo "OPTION:"
    echo "-c, --cd-only    Remove Linux udev persistent CD file only."
    echo "-n, --net-only   Remove Linux udev persistent net file only."
    echo "By default both CD and net persistent files will be removed."
    echo
    echo "Ex:"
    echo "To remove both CD and net persistent files in disk sda1, run"
    echo "   $ocs sda1"
    echo
} # end of USAGE

# check available partitions
rm_udev_persistent_record() {
  local selected_dev="$*"  # selected_dev is like "sda1 sda2"
  local fs target_p
  local gen_ma_id_flag=""

  if [ -z "$selected_dev" ]; then
    # Here we also search LVM, since udev persistent record could be inside that.
    get_partition_list true
    # partition_list is got from get_partition_list
    selected_dev="$partition_list"
  fi

  echo "Trying to remove udev persistent files. The devices to be searched: $selected_dev... "
  for ipartition in $selected_dev; do
    # Rest the flag in the for loop.
    gen_ma_id_flag="no"
    target_p="/dev/$ipartition"
    fs="$(LC_ALL=C ocs-get-dev-info $target_p filesystem)"
    case "$fs" in
      # Skip swap, FAT, NTFS, HFS+ partition...
      fat*|vfat*|FAT*|VFAT*|ntfs|hfs|hfs+|hfsplus|ufs|vmfs|swap|"")
        [ -z "$fs" ] && fs="No file system. Extended partition?"
	echo "Skip $target_p ($fs)."
	;;
      *)
        echo "Now searching possible device $target_p..."
        hd_img="$(mktemp -d /tmp/hd_img.XXXXXX)"
        mount $target_p $hd_img >/dev/null 2>&1
        mrc=$?
	if [ "$mrc" -gt 0 ]; then
          [ -d "$hd_img" -a -n "$hd_img" ] && rmdir $hd_img
	  continue
        fi
        extra_root_path=""
        if type get_restored_os_extra_root_path &>/dev/null; then
          extra_root_path="$(get_restored_os_extra_root_path "$hd_img")"
        fi
        target_sys_root="${hd_img}${extra_root_path}"

	# NIC
	for inet in `get-nic-devs`; do
          # If MAC address is not found in 70-persistent-net.rules, it means the image is not restored to the same machine, therefore remove it. Even just one does not match.
          imac="$(LC_ALL=C drbl-get-macadd $inet)"
          if [ -e "$target_sys_root/etc/udev/rules.d/70-persistent-net.rules" -a \
          	"$remove_udev_nic" = "true" ]; then
             if ! grep -q -Ei "^SUBSYSTEM.*${imac}" $target_sys_root/etc/udev/rules.d/70-persistent-net.rules 2>/dev/null; then
               echo "MAC address $imac of $inet was not found in $target_sys_root/etc/udev/rules.d/70-persistent-net.rules. This persistent net file does not fit the hardware. Remove it."
               LC_ALL=C rm -fv $target_sys_root/etc/udev/rules.d/70-persistent-net.rules
	       break
             fi
          fi
        done
	for inet in `get-nic-devs`; do
          # If MAC address is not found in /etc/sysconfig/network-scripts/ifcfg-eth*, it means the image is not restored to the same machine, therefore remove it. Even just one does not match.
          imac="$(LC_ALL=C drbl-get-macadd $inet)"
	  if ! grep -q -Ei "^HWADDR=$imac" $target_sys_root/etc/sysconfig/network-scripts/ifcfg-eth* 2>/dev/null; then
            if ls $target_sys_root/etc/sysconfig/network-scripts/ifcfg-eth* &>/dev/null; then
              echo "MAC address $imac of $inet was not found in $target_sys_root/etc/sysconfig/network-scripts/ifcfg-eth*. This MAC data does not fit the hardware. Comment it."
              LC_ALL=C perl -pi -e 's/(^HWADDR=.*)/#$1 # Commented by Clonezilla/g' $target_sys_root/etc/sysconfig/network-scripts/ifcfg-eth*
	      break
            fi
          fi
        done
	# CD
        if [ -e "$target_sys_root/etc/udev/rules.d/70-persistent-cd.rules" -a \
		"$remove_udev_cd" = "true" ]; then
          rm -fv $target_sys_root/etc/udev/rules.d/70-persistent-cd.rules
        fi
	# Dbus machine id 
	# Ref: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=639261
	#      http://unix.stackexchange.com/questions/120612/why-cant-i-run-gnome-apps-over-remote-ssh-session
	# Thanks to Peter Sun for reporting this.
	# gen_ma_id_flag is where we will know if it's the right partition to deal with.
        if [ -e "$target_sys_root/var/lib/dbus/machine-id" -a \
		"$remove_dbus_machine_id" = "true" ]; then
          rm -fv $target_sys_root/var/lib/dbus/machine-id
	  gen_ma_id_flag="yes"
        fi
        if [ -e "$target_sys_root/etc/machine-id" -a \
		"$remove_dbus_machine_id" = "true" ]; then
          rm -fv $target_sys_root/etc/machine-id
	  gen_ma_id_flag="yes"
        fi
	if [ "$gen_ma_id_flag" = "yes" ]; then
          echo "Found the file system and mount point to re-generate machine id: $target_p, $target_sys_root"
          systemd-machine-id-setup --root=$target_sys_root
          if [ -e "$target_sys_root/etc/machine-id" ]; then
            chmod 444 $target_sys_root/etc/machine-id
	    # TODO: maybe a better file to replace /etc/issue?
            chcon --reference=$target_sys_root/etc/issue $target_sys_root/etc/machine-id
            chcon -u system_u -t machineid_t $target_sys_root/etc/machine-id
            (cd $target_sys_root/var/lib/dbus/; ln -fs /etc/machine-id .)
          else
            [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
            echo "Failed to create /etc/machine-id in the deployed GNU/Linux $target_sys_root/."
            [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
          fi
        fi
        # LVM system.devices handling for RHEL 10 / AlmaLinux 10+ and others using LVM devices file.
        # This file contains a whitelist of disk device IDs. When restored/cloned to different hardware or
        # different VM/disk, the device IDs change, causing LVM to ignore the volumes, resulting in boot failure.
	# Ref: https://sourceforge.net/p/clonezilla/discussion/Open_discussion/thread/d587cfc539/
        if [ -f "$target_sys_root/etc/lvm/devices/system.devices" ]; then
          echo "Found LVM system.devices in target OS. Clearing it to prevent boot failure on cloned disk..."
          rm -f "$target_sys_root/etc/lvm/devices/system.devices"
          
          # Try to regenerate the LVM devices file using vgimportdevices inside the target chroot.
          # We only run vgimportdevices if the target OS has it installed and chroot is executable.
          # Standard paths for vgimportdevices: /sbin/vgimportdevices, /usr/sbin/vgimportdevices
          if [ -x "$target_sys_root/sbin/vgimportdevices" -o -x "$target_sys_root/usr/sbin/vgimportdevices" ]; then
            echo "Regenerating LVM devices file inside chroot..."
            
            # Bind mount necessary virtual file systems for LVM to scan hardware devices.
            mount --bind /dev "$target_sys_root/dev"
            mount --bind /proc "$target_sys_root/proc"
            mount --bind /sys "$target_sys_root/sys"
            bind_run="no"
            if [ -d "$target_sys_root/run" ]; then
              mount --bind /run "$target_sys_root/run"
              bind_run="yes"
            fi
            
            # Construct device filter for target disk(s) to avoid duplicate VG collisions with source disk in d2d mode
            target_disks=""
            for t_dev in $selected_dev; do
              t_dsk="$(get_diskname "$t_dev")"
              [ -n "$t_dsk" ] && target_disks="$target_disks $t_dsk"
            done
            target_disks="$(echo $target_disks | tr ' ' '\n' | sort -u | xargs)"
            if [ -z "$target_disks" -a -n "$target_p" ]; then
              target_disks="$(get_diskname "$target_p")"
            fi
            chroot_filter_rules=""
            if [ -n "$target_disks" ]; then
              for td in $target_disks; do
                chroot_filter_rules="${chroot_filter_rules}\"a|^/dev/${td}.*|\", "
              done
              chroot_filter_rules="${chroot_filter_rules}\"r|.*|\""
            fi

            # Run vgimportdevices in chroot environment (try with target disk filter first to avoid duplicate VGs)
            rc_vgimport=1
            if [ -n "$chroot_filter_rules" ]; then
              LC_ALL=C chroot "$target_sys_root" vgimportdevices --config "devices { filter = [ ${chroot_filter_rules} ] }" -a 2>/dev/null
              rc_vgimport=$?
            fi
            if [ "$rc_vgimport" -ne 0 ]; then
              LC_ALL=C chroot "$target_sys_root" vgimportdevices -a 2>/dev/null
              rc_vgimport=$?
            fi
            if [ "$rc_vgimport" -ne 0 ]; then
              echo "Warning: vgimportdevices failed in chroot (exit code $rc_vgimport), possibly due to architecture mismatch. Leaving it deleted for auto-generation on next boot."
              if [ -f "$target_sys_root/etc/lvm/lvm.conf" ]; then
                if grep -q -Ei "^[[:space:]]*#?[[:space:]]*use_devicesfile" "$target_sys_root/etc/lvm/lvm.conf"; then
                  echo "Setting use_devicesfile = 0 in $target_sys_root/etc/lvm/lvm.conf to allow LVM to scan all devices..."
                  perl -pi -e 's/^[[:space:]]*#?[[:space:]]*use_devicesfile[[:space:]]*=.*/\tuse_devicesfile = 0/g' "$target_sys_root/etc/lvm/lvm.conf"
                fi
              fi
            fi
            
            # Clean up the bind mounts
            if [ "$bind_run" = "yes" ]; then
              unmount_wait_and_try "$target_sys_root/run"
            fi
            unmount_wait_and_try "$target_sys_root/sys"
            unmount_wait_and_try "$target_sys_root/proc"
            unmount_wait_and_try "$target_sys_root/dev"
          else
            echo "vgimportdevices command not found in target OS. Leaving it deleted for auto-generation on next boot."
            if [ -f "$target_sys_root/etc/lvm/lvm.conf" ]; then
              if grep -q -Ei "^[[:space:]]*#?[[:space:]]*use_devicesfile" "$target_sys_root/etc/lvm/lvm.conf"; then
                echo "Setting use_devicesfile = 0 in $target_sys_root/etc/lvm/lvm.conf to allow LVM to scan all devices..."
                perl -pi -e 's/^[[:space:]]*#?[[:space:]]*use_devicesfile[[:space:]]*=.*/\tuse_devicesfile = 0/g' "$target_sys_root/etc/lvm/lvm.conf"
              fi
            fi
          fi
        fi
        [ "$mrc" -eq 0 ] && unmount_wait_and_try $target_p
        [ -d "$hd_img" -a -n "$hd_img" ] && rmdir $hd_img
	;;
    esac
  done
  echo "done!"
} # end of rm_udev_persistent_record
#

####################
### Main program ###
####################

ocs_file="$0"
ocs=`basename $ocs_file`

# Default settings
remove_udev_cd="true"
remove_udev_nic="true"
remove_dbus_machine_id="true"
#
while [ $# -gt 0 ]; do
 case "$1" in
   -c|--cd-only) remove_udev_cd="true"
                 remove_udev_nic="false"
                 remove_dbus_machine_id="false"
	         shift;;
   -n|--nic-only) 
	         remove_udev_cd="false"
                 remove_udev_nic="true"
                 remove_dbus_machine_id="false"
	         shift;;
   -m|--machine-id-only) 
	         remove_udev_cd="false"
                 remove_udev_nic="false"
                 remove_dbus_machine_id="true"
	         shift;;
   -*)     echo "${0}: ${1}: invalid option" >&2
           USAGE >& 2
           exit 2 ;;
   *)      break ;;
 esac
done

ocs_hw_record_dev="$(strip_leading_dev $*)"

#
check_if_root
ask_and_load_lang_set

#
echo "Trying to remove udev hardware record in the restored OS..."
if [ -n "$ocs_hw_record_dev" ]; then
  echo "The specified destination device: $ocs_hw_record_dev"
fi
rm_udev_persistent_record $ocs_hw_record_dev
