#!/usr/bin/env bash
# Copyright (c) 2016 - 2020 DisplayLink (UK) Ltd.

create_udev_rules_file()
{
  local DISPLAYLINK_RULES_FILE=$1
  local DISPLAYLINK_RULES_FOLDER
  DISPLAYLINK_RULES_FOLDER=$(dirname "$DISPLAYLINK_RULES_FILE")
  if [[ ! -d $DISPLAYLINK_RULES_FOLDER ]]; then
    mkdir -p "$DISPLAYLINK_RULES_FOLDER"
    chmod 0755 "$DISPLAYLINK_RULES_FOLDER"
  fi
  cat > "$DISPLAYLINK_RULES_FILE" <<'EOF'
# Copyright (c) 2016 - 2023 DisplayLink (UK) Ltd.
# File autogenerated by udev-installer.sh script

ENV{PRODUCT}!="17e9/*", GOTO="not_dl"

ACTION=="add|change", SUBSYSTEM=="usb", DRIVERS=="usb", IMPORT{builtin}="usb_id", ENV{DISPLAYLINK_DEVNAME}="$env{DEVNAME}", ENV{DISPLAYLINK_DEVICE_ID}="$env{ID_BUS}-$env{BUSNUM}-$env{DEVNUM}-$env{ID_SERIAL}"

ACTION=="add", SUBSYSTEM=="usb", DRIVERS=="usb", \
   ATTR{bInterfaceClass}=="ff", ATTR{bInterfaceProtocol}=="03", IMPORT{parent}="DISPLAYLINK*", \
   RUN+="/opt/displaylink/udev.sh $root $devpath $env{DISPLAYLINK_DEVICE_ID} $env{DISPLAYLINK_DEVNAME}"

ACTION=="add", SUBSYSTEM=="usb", DRIVERS=="usb", \
   ATTR{bInterfaceClass}=="13", ATTR{bInterfaceProtocol}=="00", IMPORT{parent}="DISPLAYLINK*", \
   RUN+="/opt/displaylink/udev.sh $root $devpath $env{DISPLAYLINK_DEVICE_ID} $env{DISPLAYLINK_DEVNAME}"

ACTION=="remove", RUN+="/opt/displaylink/udev.sh $root $env{DEVNAME}"

# udev rules configuring DisplayLink usb ethernet device

ACTION=="add|move|bind", ATTRS{idProduct}=="[3456]*", IMPORT{builtin}="usb_id", ENV{ID_USB_DRIVER}=="cdc_ncm", \
    ATTR{cdc_ncm/rx_max}=="?*", ATTR{cdc_ncm/dwNtbInMaxSize}=="?*", ATTR{cdc_ncm/rx_max}="$attr{cdc_ncm/dwNtbInMaxSize}"
ACTION=="add|move|bind", ATTRS{idProduct}=="[3456]*", IMPORT{builtin}="usb_id", ENV{ID_USB_DRIVER}=="cdc_ncm", \
    ATTR{cdc_ncm/tx_max}=="?*", ATTR{cdc_ncm/dwNtbOutMaxSize}=="?*", ATTR{cdc_ncm/tx_max}="$attr{cdc_ncm/dwNtbOutMaxSize}"

LABEL="not_dl"

EOF

  chmod 0644 "$DISPLAYLINK_RULES_FILE"
}

systemd_start_stop_functions()
{
  cat <<'EOF'
start_service()
{
  systemctl start --no-block displaylink-driver
}

stop_service()
{
  systemctl stop displaylink-driver
}

EOF
}

upstart_start_stop_functions()
{
  cat <<'EOF'
start_service()
{
  start displaylink-driver
}

stop_service()
{
  stop displaylink-driver
}

EOF
}

runit_start_stop_functions()
{
  cat <<EOF
start_service()
{
  sv up displaylink-driver
}

stop_service()
{
  sv down displaylink-driver
}
EOF
}

displaylink_bootstrapper_code()
{
  cat <<'EOF'
#!/bin/sh
# Copyright (c) 2016 - 2020 DisplayLink (UK) Ltd.
# File autogenerated by udev-installer.sh script

get_displaylink_dev_count()
{
   cat /sys/bus/usb/devices/*/idVendor | grep 17e9 | wc -l
}

get_displaylink_symlink_count()
{
  root=$1

  if [ ! -d "$root/displaylink/by-id" ]; then
    echo "0"
    return
  fi

  for f in $(find $root/displaylink/by-id -type l -exec realpath {} \; 2> /dev/null); do
    test -c $f && echo $f;
  done | wc -l
}

start_displaylink()
{
  if [ "$(get_displaylink_dev_count)" != "0" ]; then
    start_service
  fi
}

stop_displaylink()
{
  root=$1

  if [ "$(get_displaylink_symlink_count $root)" = "0" ]; then
    stop_service
  fi
}

remove_dldir_if_empty()
{
  root=$1
  (cd $root; rmdir -p --ignore-fail-on-non-empty displaylink/by-id)
}

create_displaylink_symlink()
{
  root=$1
  device_id=$2
  devnode=$3

  mkdir -p $root/displaylink/by-id
  ln -sf $devnode $root/displaylink/by-id/$device_id
}

unlink_displaylink_symlink()
{
   root=$1
   devname=$2

   for f in $root/displaylink/by-id/*; do
     if [ ! -e "$f" ] || ([ -L "$f" ] && [ "$f" -ef "$devname" ]); then
       unlink "$f"
     fi
   done
   (cd $root; rmdir -p --ignore-fail-on-non-empty displaylink/by-id)
}

prune_broken_links()
{
  root=$1

  dir="$root/displaylink/by-id"
  find -L "$dir" -name "$dir" -o type d -prune -o -type -l -exec rm {} +
  remove_dldir_if_empty $root
}

disable_u1_u2()
{
    echo 0 > "/sys$1/../port/usb3_lpm_permit"
}

main()
{
  action=$1
  root=$2
  devpath=$3
  devnode=$5

  if [ "$action" = "add" ]; then
    device_id=$4
    create_displaylink_symlink $root $device_id $devnode
    start_displaylink
    disable_u1_u2 "$devpath"
  elif [ "$action" = "remove" ]; then
      devname=$3
      unlink_displaylink_symlink "$root" "$devname"
      stop_displaylink "$root"
  elif [ "$action" = "START" ]; then
    start_displaylink
  fi
}

EOF
}

create_main_function()
{
  cat <<'EOF'

case "$ACTION" in
  add)
    if [ "$#" -ge 3 ]; then
      main $ACTION $1 $2 $3 $4
      return 0
    elif [ "$#" -ge 2 ]; then
      start_displaylink
      return 0
    fi ;;
  remove)
    if [ "$#" -ge 2 ]; then
      main $ACTION $1 $2 $3
      return 0
    else
      prune_broken_links $root
      return 0
    fi ;;
esac

EOF
}

create_bootstrap_file()
{
  local init_daemon=$1
  local filename=$2

  case $init_daemon in
    upstart)
      start_stop_functions=$(upstart_start_stop_functions) ;;
    systemd)
      start_stop_functions=$(systemd_start_stop_functions) ;;
    runit)
      start_stop_functions=$(runit_start_stop_functions) ;;
    *)
      (>&2 echo "Unknown init daemon: $init_daemon")
      exit 1
  esac

  displaylink_bootstrapper_code > "$filename"
  echo "$start_stop_functions" >> "$filename"
  create_main_function >> "$filename"
  chmod 0744 "$filename"
}

trigger_udev_if_devices_connected()
{
  grep -lw 17e9 /sys/bus/usb/devices/*/idVendor | while IFS= read -r device; do
    udevadm trigger --action=add "$(dirname "$device")"
  done
}

main()
{
  local init_daemon=$1
  local rules_path=$2
  local udev_script_path=$3
  create_bootstrap_file "$init_daemon" "$udev_script_path"
  create_udev_rules_file "$rules_path"
}

if [[ $# -eq 3 ]]; then
  main "$1" "$2" "$3"
fi
