#!/bin/sh

# Copyright (c) 2012-2024 Slawomir Wojciech Wojtczak <vermaden@interia.pl>
# Copyright (c) 2019      Rozhuk Ivan                <rozhuk.im@gmail.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that following conditions are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS 'AS IS' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

PATH=${PATH}:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin

__usage() {
  cat << EOF
AUTOMOUNT is a devd(8) based automounter for FreeBSD.

It supports following file systems:
UFS/FAT/exFAT/NTFS/EXT2/EXT3/EXT4/MTP/HFS/ISO9660

Add these to mount NTFS/exFAT/EXT4/HFS/XFS/MTP respectively:
 o sysutils/fusefs-ntfs
 o sysutils/fusefs-exfat
 o sysutils/fusefs-hfsfuse
 o sysutils/fusefs-lkl
 o sysutils/fusefs-simple-mtpfs

By default it mounts/unmounts all removable media but
it is possible to set some additional options at the
/usr/local/etc/automount.conf config file.

Below is a list of possible options with description.

MNT_PREFIX (set to /media by default)
  With this options You can alter the default root
  for mounting the removable media, for example to
  the /mnt directory.

  example: MNT_PREFIX='/media'

MNT_GROUP (wheel by default)
  If set to some group name, the mount command will
  chown(1) the mount directory with the group.

  example: group='operator'

MNT_MODE (set to 775 by default)
  Value for chmod on mount point.

FAT_ENCODING (set to en_US.UTF-8 by default)
  Only used with FAT32 mounts, specifies which
  encoding to use at the mount.

  example: FAT_ENCODING='en_US.ISO8859-1'

FAT_CODEPAGE (set to CP866 by default)
  Only used with FAT32 mounts, specifies which
  code page to use at the mount.

  example: FAT_CODEPAGE='cp437'

ISO9660_CODEPAGE (set to UTF-8 by default)
  Only used with cd9660 mounts, specifies which
  code page to use at the mount.

ATIME (set to NO by default)
  When set to NO it will mount filesystems with
  noatime option when possible.

  example: ATIME='YES'

RETRY_COUNT (set to 3 by default)
  How many times try to get file system type or try to mount.

  example: RETRY_COUNT='1'

RETRY_DELAY (set to 1 second by default)
  Delay beetwin retry attempt.

  example: RETRY_DELAY='2.5'

USERUMOUNT (set to NO by default)
  When set to YES it will 'chmod +s /sbin/umount'
  which would allow an USER to unmount the file
  system with their selected file manager.

  example: USERUMOUNT='YES'

NOTIFY (set to NO by default)
  Use 'notify-send' and 'libnotify' to show notifications
  of mounting and unmounting devices on the desktop.

  example: NOTIFY='YES'

WALL (set to NO by default)
  Use wall(1) to show notifications of mounting and
  unmounting devices on terminals of logged in users.

  example: WALL='YES'

FM ('exo-open --launch FileManager' by default)
  If set to file manager command, the mount will
  launch the specified command after successful
  mount. Works only if USER parameter is also set.

  example: FM='nautilus --browser --no-desktop'

BLACKLIST (unset by default)
  The automount will ignore devices defined here.

  example: BLACKLIST='da0 da3s1a'


BLACKLIST_REGEX (unset by default)
  The boolean flag option complements the above BLACKLIST option
  if one wants regex match instead of exact match for ignoring devices.
  Below will ignore all partitions ada0p1/ada0p2/... of ada0 device.

  example: BLACKLIST='ada0'
           BLACKLIST_REGEX=true

USER (root by default)
  If set to some username, the mount command will
  chown(1) the mount directory with the user and
  its primary user group. If used with FM option
  allows to launch the specified file manager after
  a successful mount.

  example: USER="vermaden"

REMOVEDIRS (set to YES by default)
  If set to YES the automount(8) will remove /media dir after unmount.

  example: REMOVEDIRS=NO

NICENAMES (set to NO by default)
  If set to YES the device/filesystem label will be used for /media dir name.

  example: NICENAMES=YES

IGNORE_SYS_PARTS (set to NO by default)
  If set to YES automount(8) will ignore system partitions like EFI or MSR.

  example: IGNORE_SYS_PARTS=YES

EOF
  exit 0
}

# display version if needed
if [ "${1}" = '--version' -o \
     "${1}" = '-version'  -o \
     "${1}" = 'version'   -o \
     "${1}" = '-v' ]
then
  echo
  echo "                    ___        /\                                ___             "
  echo "                 __/  /_      /  \                              _\  \__          "
  echo "    ____   _____/_   __/__   /  _/\   ___ ___   ____ ______   __\__   _\         "
  echo "   /    \ /  /  //  //    \ /\_/   \ /   /   \ /    \\\  \  \ /    \\\  \        "
  echo "  /  /  //  /  //  //  /  //        \\\  \  \  \\\  \  \\\  \  \\\  \  \\\  \_   "
  echo "  \_____\\\____/ \__\\\____//__________\\\__\__\__\\\____/ \_____\\\__\__\\\___\ "
  echo
  echo "automount 1.8.0 2024/03/05"
  exit 0
fi

# display help if needed
if [ "${1}" = "-h"     -o \
     "${1}" = "--h"    -o \
     "${1}" = "-help"  -o \
     "${1}" = "--help" -o \
     "${#}" -eq "0"    -o \
     "${#}" -eq "1" ]
then
  __usage
fi

# read configuration file
if [ -f /usr/local/etc/automount.conf ] ; then
  . /usr/local/etc/automount.conf
fi

# default values for global variables
: ${MNT_PREFIX='/media'}                # mount prefix
: ${MNT_GROUP='wheel'}                  # use WHEEL group for popup
: ${MNT_MODE='775'}                     # mount point mode
: ${FAT_ENCODING='en_US.UTF-8'}         # US/Canada
: ${FAT_CODEPAGE='cp437'}               # US/Canada
: ${ISO9660_CODEPAGE='UTF-8'}           # UTF-8
: ${ATIME='NO'}                         # when NO mount with noatime
: ${RETRY_COUNT='5'}                    # retry count
: ${RETRY_DELAY='2'}                    # retry delay time
: ${USERUMOUNT='NO'}                    # when YES add suid bit to umount(8)
: ${NOTIFY='NO'}                        # use notify-send(1) (devel/libnotify)
: ${WALL='NO'}                          # use wall(1)
: ${FM='exo-open --launch FileManager'} # which file manager to use
: ${LOG_FILE='/var/log/automount.log'}  # log file
: ${LOG_DATEFMT='%Y-%m-%d %H:%M:%S'}    # 2012-02-20 07:49:09
: ${STATE="/var/run/automount.state"}   # current state file
: ${USER="root"}                        # which user to use for popup
: ${REMOVEDIRS='YES'}                   # remove /media dir after unmount
: ${NICENAMES='NO'}                     # use device label for /media dir name
: ${IGNORE_SYS_PARTS='NO'}              # ignore system partitions like EFI or MSR

# init of main variables
DEV="/dev/${1}"
UID=$( id -u ${USER} )
GID=$( pw group show -n ${MNT_GROUP} | awk -F':' '{print $3}' )
if [ ${?} -ne 0 ]
then
  __log "${MNT_GROUP}: invalid group"
  exit 1
fi

# process ${USERUMOUNT} option
case ${USERUMOUNT} in
  ([Yy][Ee][Ss])
    chmod u+s /sbin/umount    1> /dev/null 2>&1 # WHEEL group member
    chmod u+s /sbin/mount*    1> /dev/null 2>&1 # WHEEL group member
    sysctl -q vfs.usermount=1 1> /dev/null 2>&1 # allow user to mount
    ;;
esac

# read only filesystem types for __guess_fs_type() function
readonly FS_TYPE_UNKNOWN=0
readonly FS_TYPE_ISO9660=1
readonly FS_TYPE_UFS=8
readonly FS_TYPE_EXT2=9
readonly FS_TYPE_EXT3=10
readonly FS_TYPE_EXT4=11
readonly FS_TYPE_XFS=12
readonly FS_TYPE_HFS=13
readonly FS_TYPE_FAT=32
readonly FS_TYPE_EXFAT=33
readonly FS_TYPE_NTFS=34
readonly FS_TYPE_MTP=128

# FUNCTION: guess filesystem type from device
__guess_fs_type() { # 1=DEV
  # first time guess with file(1) tool
  unset FS_TYPE
  local FS_TYPE=$( file -r -b -L -s ${1} 2> /dev/null | sed -E 's/label:\ \".*\"//g' )
  case ${FS_TYPE} in
    (*ISO\ 9660*)        return ${FS_TYPE_ISO9660} ;;
    (*Unix\ Fast\ File*) return ${FS_TYPE_UFS}     ;;
    (*ext2*)             return ${FS_TYPE_EXT2}    ;;
    (*ext3*)             return ${FS_TYPE_EXT3}    ;;
    (*ext4*)             return ${FS_TYPE_EXT4}    ;;
    (*SGI\ XFS*)         return ${FS_TYPE_XFS}     ;;
    (*Macintosh\ HFS*)   return ${FS_TYPE_HFS}     ;;
  esac
  # second time guess with file(1) tool with -k option
  # (do not stop at the first match and keep going)
  unset FS_TYPE
  local FS_TYPE=$( file -k -r -b -L -s ${1} 2> /dev/null | tr '\n' ' ' | sed -E 's/label:\ \".*\"//g' )
  case ${FS_TYPE} in
    (*Unix\ Fast\ File*) return ${FS_TYPE_UFS}   ;;
    (*NTFS*)             return ${FS_TYPE_NTFS}  ;;
    (*ExFAT*)            return ${FS_TYPE_EXFAT} ;;
    (*\ FAT\ *|*MSDOS*)  return ${FS_TYPE_FAT}   ;;
  esac
  # try with fstyp(8) last (exFAT on UFS issue)
  unset FS_TYPE
  local FS_TYPE=$( fstyp ${1} 2> /dev/null )
  case ${FS_TYPE} in
    (cd9660)  return ${FS_TYPE_ISO9660} ;;
    (ufs)     return ${FS_TYPE_UFS}     ;;
    (ext2fs)  return ${FS_TYPE_EXT2}    ;;
    (msdosfs) return ${FS_TYPE_FAT}     ;;
    (exfat)   return ${FS_TYPE_EXFAT}   ;;
    (ntfs)    return ${FS_TYPE_NTFS}    ;;
  esac
  # magic detection code with dd(8)
  unset FS_TYPE
  local FS_TYPE=$( dd if="${1}" conv=sync count=1 bs=1k 2> /dev/null | strings | head -1 )
  case ${FS_TYPE} in
    (*EXFAT*) return ${FS_TYPE_EXFAT} ;;
  esac
  return ${FS_TYPE_UNKNOWN}
}

# FUNCTION: add state to the ${STATE} file
__state_add() { # 1=DEV 2=PROVIDER 3=MNT
  if [ -f ${STATE} ]
  then
    if grep -E "${3}$" ${STATE} 1> /dev/null 2> /dev/null
    then
      __log "${1}: duplicated '${STATE}'"
      exit 0
    fi
  fi
  echo "${1} ${2} ${3}" >> ${STATE}
  if [ "${NOTIFY}" = YES ]
  then
    __show_message "Device '${1}' mounted on '${3}' directory."
  fi
  if [ "${WALL}" = YES ]
  then
    echo "automount: Device '${1}' mounted on '${3}' directory." | wall
  fi
}

# FUNCTION: remove state from the ${STATE} file
__state_remove() { # 1=MNT
  if [ -f ${STATE} ]
  then
    # backslash the slashes ;)
    BSMNT=$( echo ${1} | sed 's/\//\\\//g' )
    sed -i '' "/${BSMNT}\$/d" ${STATE}
    if [ "${NOTIFY}" = YES ]
    then
      __show_message "Device '${1}' unmounted from '${3}' directory."
    fi
    if [ "${WALL}" = YES ]
    then
      echo "automount: Device '${1}' unmounted from '${3}' directory." | wall
    fi
  fi
}

# FUNCTION: add message to the ${LOG_FILE} file
__log() { # @=MESSAGE
  echo $( date +"${LOG_DATEFMT}" ) "${@}" >> "${LOG_FILE}"
}

# FUNCTION: remove temp mount dir from ${MNT_PREFIX} path (like /media/da0 dir)
__remove_dir() { # 1=TARGET
  if [ "${REMOVEDIRS}" = YES ]
  then
    if [ -d "${1}" ]
    then
      sleep 1
    # find "${1}" -type d -empty -maxdepth 1 -exec rm -r {} '+' 2> /dev/null
      find "${MNT_PREFIX}" -depth 1 -empty -prune -delete 2> /dev/null
    fi
  fi
}

# FUNCTION: display wall(1) and/or notify-send(1) message
__show_message() { # 1=MESSAGE
  case ${WALL} in
    ([Yy][Ee][Ss])
      echo "automount: ${1}" | wall
      ;;
  esac
  case ${NOTIFY} in
    ([Yy][Ee][Ss])
      local __DISPLAY_IDS=$( ps aew | sed -n 's|.*DISPLAY=\([-_a-zA-Z0-9:.]*\).*|\1|p' | sort -u | tr '\n' ' ' )
      for __DISPLAY_ID in ${__DISPLAY_IDS}
      do
        local __USER=$( ps aewj | grep "DISPLAY=${__DISPLAY_ID}" | awk '{print $1;}' | sort -u | tr -cd '[:print:]' )
        if [ -z "${__USER}" ]
        then
          continue
        fi
        su -l "${__USER}" -c "env DISPLAY=${__DISPLAY_ID} notify-send automount '${1}' &" 1> /dev/null 2>&1
      done
      ;;
  esac
}

# FUNCTION: check if device or mountpoint not already mounted
__check_already_mounted() { # 1=DEV 2=MNT
  local MOUNT=$( mount )
  if echo "${MOUNT}" | grep -q "^${1} on "
  then
    local MOUNT_POINT=$( echo "${MOUNT}" | grep "^${1} on " | cut -d ' ' -f 3-255 | cut -d '(' -f 1 | sed s/.$// )
    __log "${DEV}: already mounted on '${MOUNT_POINT}' mount point"
    exit 1
  fi
  if echo "${MOUNT}" | grep -q " on ${2} "
  then
    local DEVICE=$( echo "${MOUNT}" | grep " on ${2} " | awk '{print $1}' )
    __log "${DEVICE}: already mounted on '${2}' mount point"
    exit 1
  fi
}

# FUNCTION: wait for device to appear (sometimes needed)
__wait_for_device() { # 1=DEV
  # do not wait for MTP and CD-ROM devices
  case ${1} in
    (*ugen*|iso9660*)
      return
      ;;
  esac
  # try to read from device to ensure that it alive
  local COUNT=0
  while ! dd if="${1}" of=/dev/null conv=sync count=1 bs=8k 1> /dev/null 2>&1
  do
    if [ ! -e "${1}" ]
    then
      __log "${1}: device gone"
      exit 1
    fi
    COUNT=$(( ${COUNT} + 1 ))
    if [ ${COUNT} -ge ${RETRY_COUNT} ]
    then
      return
    fi
    sleep "${RETRY_DELAY}"
    __log "${1}: wait for device retry ${COUNT}/${RETRY_COUNT}"
  done
}

# FUNCTION: check if device is a block device
__check_block_device() { # 1=DEV
  # first check if its block device
  if ! fstyp ${1} 1> /dev/null 2>&1
  then
    __log "${DEV}: not a block device"
    exit 0
  fi
}

# main ATTACH/DETACH block
case ${2} in
  (attach)
    # check if device still exists
    if [ ! -e "${DEV}" ]
    then
      __log "${DEV}: device does not exist"
      exit 1
    fi
    __log "${DEV}: attach"

    # ignore system partitions like EFI or MSR
    if [ "${IGNORE_SYS_PARTS}" = 'YES' ]
    then
      SYS_DEV=$( echo ${1} | grep -E -o '^[a-z]+[0-9]+' )
      SYS_GPART=$( gpart show -p -r ${SYS_DEV} 2> /dev/null | sed 's@=>@@g' | grep " ${1} " | awk '{print $4}' )
      case ${SYS_GPART} in
        (c12a7328-f81f-11d2-ba4b-00a0c93ec93b) exit 0 ;;
        (e3c9e316-0b5c-4db8-817d-f92df00215ae) exit 0 ;;
      esac
    fi

    # code for NICENAMES mounting instead of the /dev/${DEV} default
    MNT_CANDIDATE=$( fstyp -l "/dev/${1}" 2> /dev/null | cut -d " " -f 2-99 | tr ' ' '-' )
    if [ "${NICENAMES}" = "YES" -a -n "${MNT_CANDIDATE}" ]
    then
      # check if dir exists
      if [ -e "${MNT_PREFIX}/${MNT_CANDIDATE}" ]
      then
        # check if something is already mounted there and increment if it is
        if mount | grep -q " ${MNT_PREFIX}/${MNT_CANDIDATE} "
        then
          COUNT=1
          while true
          do
            COUNT=$(( ${COUNT} + 1 ))
            [ ! -e "${MNT_PREFIX}/${MNT_CANDIDATE}-${COUNT}" ] && break
          done
          MNT="${MNT_PREFIX}/${MNT_CANDIDATE}-${COUNT}"
        else
          # dir exists but its not mounted
          MNT="${MNT_PREFIX}/${MNT_CANDIDATE}"
        fi
      else
        # dir does not exist
        MNT="${MNT_PREFIX}/${MNT_CANDIDATE}"
      fi
    else
      # device/filesystem without label
      MNT="${MNT_PREFIX}/${1}"
    fi

    # blacklist check
    if [ -n "${BLACKLIST}" ]
    then
      for I in ${BLACKLIST}
      do
        if [ "${1}" = "${I}" ]
        then
          __log "${DEV}: device blocked by BLACKLIST option"
          exit 0
        elif [ -n "${BLACKLIST_REGEX}" ] &&  echo ${DEV} | grep -q "${I}" 1> /dev/null 2> /dev/null
        then
          __log "${DEV}: device blocked by BLACKLIST_REGEX option"
          exit 0
        fi
      done
    fi

    # check is device already mounted
    __check_already_mounted "${DEV}" "${MNT}"

    # make sure that data can be read from device
    __wait_for_device "${DEV}"

    # load needed kernel modules
    kldload fusefs    1> /dev/null 2> /dev/null
    kldload geom_uzip 1> /dev/null 2> /dev/null

    # detect filesysytem type
    case ${1} in
      (iso9660*)
        FS_TYPE=${FS_TYPE_ISO9660}
        ;;
      (ugen*)
        FS_TYPE=${FS_TYPE_MTP}
        ;;
      (cd*)
        __guess_fs_type "${DEV}"
        FS_TYPE=${?}
        ;;
      (md*.uzip|md*|ada*|da*|mmcsd*)
        __check_block_device "${DEV}"
        __guess_fs_type "${DEV}"
        FS_TYPE=${?}
        ;;
    esac

    # process ATIME option
    case ${ATIME} in
      ([Nn][Oo]) OPTS="-o noatime" ;;
    esac

    # filesystem options abstraction layer
    case ${FS_TYPE} in
      (${FS_TYPE_ISO9660})
        FS_CHECK_CMD=''
        FS_CHECK_ARGS=''
        FS_MOUNT_CMD='mount'
        FS_MOUNT_ARGS="-t cd9660 -o -e,-C=${ISO9660_CODEPAGE} ${DEV} ${MNT}"
        ;;
      (${FS_TYPE_UFS})
        FS_CHECK_CMD='fsck_ufs'
        FS_CHECK_ARGS="-C -y"
        FS_MOUNT_CMD='mount'
        FS_MOUNT_ARGS="-t ufs ${OPTS} ${DEV} ${MNT}"
        ;;
      (${FS_TYPE_EXT2})
        FS_CHECK_PORT='sysutils/e2fsprogs'
        FS_CHECK_CMD='fsck.ext2'
        FS_CHECK_ARGS="-y"
        FS_MOUNT_PORT='sysutils/fusefs-lkl'
        FS_MOUNT_CMD='lklfuse'
        FS_MOUNT_ARGS="-o type=ext2 -o allow_other -o intr -o uid=${UID} -o gid=${GID} -o umask=002 ${DEV} ${MNT}"
        ;;
      (${FS_TYPE_EXT3})
        FS_CHECK_PORT='sysutils/e2fsprogs'
        FS_CHECK_CMD='fsck.ext3'
        FS_CHECK_ARGS="-y"
        FS_MOUNT_PORT='sysutils/fusefs-lkl'
        FS_MOUNT_CMD='lklfuse'
        FS_MOUNT_ARGS="-o type=ext3 -o allow_other -o intr -o uid=${UID} -o gid=${GID} -o umask=002 ${DEV} ${MNT}"
        ;;
      (${FS_TYPE_EXT4})
        FS_CHECK_PORT='sysutils/e2fsprogs'
        FS_CHECK_CMD='fsck.ext4'
        FS_CHECK_ARGS="-y"
        FS_MOUNT_PORT='sysutils/fusefs-lkl'
        FS_MOUNT_CMD='lklfuse'
        FS_MOUNT_ARGS="-o type=ext4 -o allow_other -o intr -o uid=${UID} -o gid=${GID} -o umask=002 ${DEV} ${MNT}"
        ;;
      (${FS_TYPE_XFS})
        FS_CHECK_PORT='sysutils/xfsprogs'
        FS_CHECK_CMD='xfs_repair'
        FS_CHECK_ARGS="-d"
        FS_MOUNT_CMD='lklfuse'
        FS_MOUNT_ARGS="-o type=xfs -o allow_other -o uid=${UID} -o gid=${GID} ${DEV} ${MNT}"
        FS_MOUNT_PORT='sysutils/fusefs-lkl'
        ;;
      (${FS_TYPE_HFS})
        FS_CHECK_CMD=''
        FS_CHECK_ARGS=''
        FS_MOUNT_CMD='hfsfuse'
        FS_MOUNT_ARGS="--force ${OPTS} ${DEV} ${MNT}"
        FS_MOUNT_PORT='sysutils/fusefs-hfsfuse'
        ;;
      (${FS_TYPE_FAT})
        # FreeBSD 12.x and later does not support/need '-o large' option
        case $( sysctl -n kern.osrelease ) in
          (10*) LARGE="-o large" ;;
          (11*) LARGE="-o large" ;;
          (*)   LARGE=""         ;;
        esac
        FS_CHECK_CMD='fsck_msdosfs'
        FS_CHECK_ARGS="-C -y"
        FS_MOUNT_CMD='mount_msdosfs'
        FS_MOUNT_ARGS="-o longnames -m ${MNT_MODE} -M ${MNT_MODE} -D ${FAT_CODEPAGE} -L ${FAT_ENCODING} -u ${UID} -g ${GID} ${OPTS} ${LARGE} ${DEV} ${MNT}"
        ;;
      (${FS_TYPE_EXFAT})
        FS_CHECK_PORT='sysutils/exfat-utils'
        FS_CHECK_CMD='fsck.exfat'
        FS_CHECK_ARGS="-y"
        FS_MOUNT_CMD='mount.exfat'
        FS_MOUNT_UMASK=$( printf "%03o" $((~0775&0777)) )
        FS_MOUNT_ARGS="-o uid=${UID} -o gid=${GID} -o umask=${FS_MOUNT_UMASK} ${OPTS} ${DEV} ${MNT}"
        FS_MOUNT_PORT='sysutils/fusefs-exfat'
        ;;
      (${FS_TYPE_NTFS})
        FS_CHECK_CMD=''
        FS_CHECK_ARGS=''
        if /usr/bin/which -s ntfs-3g
        then
          FS_MOUNT_CMD='ntfs-3g'
          FS_MOUNT_ARGS="-o recover ${OPTS} ${DEV} ${MNT}"
          FS_MOUNT_PORT='sysutils/fusefs-ntfs'
        else
          FS_MOUNT_CMD='mount_ntfs'
          FS_MOUNT_ARGS="-u root -g ${MNT_GROUP} ${OPTS} ${DEV} ${MNT}"
        fi
        ;;
      (${FS_TYPE_MTP})
        FS_PORT='sysutils/fusefs-simple-mtpfs'
        FS_CHECK_CMD=''
        FS_CHECK_ARGS=''
        FS_MOUNT_CMD='simple-mtpfs'
        if ! /usr/bin/which -s "${FS_MOUNT_CMD}"
        then
          __log "command '${FS_MOUNT_CMD}' not found"
          exit 1
        fi
        PHONEDEV=$( simple-mtpfs --list-devices -d ${DEV} 2> /dev/null )
        if [ "${PHONEDEV}" = "No raw devices found." ]
        then
          __log "${DEV}: no raw devices found"
          exit 0
        fi
        PHONEDEV=$( echo "${PHONEDEV}" | awk '{print $1}' | tr -d ':' )
        if [ ! ${PHONEDEV} ]
        then
          __log "${DEV}: no MTP devices found"
          exit 0
        fi
        FS_MOUNT_ARGS="--device ${PHONEDEV} ${MNT} -o allow_other -o uid=${UID} -o gid=${GID}"
        ;;
      (*)
        __log "${DEV}: filesystem not supported or no filesystem"
        exit 0
        ;;
    esac

    # create mount point
    mkdir -m "${MNT_MODE}" -p "${MNT}"
    __log "${DEV}: create '${MNT}' dir"

    # check file system before mount
    if [ -n "${FS_CHECK_CMD}" ]
    then
      if ! /usr/bin/which -s "${FS_CHECK_CMD}"
      then
        __log "command '${FS_CHECK_CMD}' not found"
        __log "please install '${FS_CHECK_PORT}' port or package"
        exit 1
      fi
      ${FS_CHECK_CMD} ${FS_CHECK_ARGS} ${DEV} \
        | while read LINE
          do
            __log "${DEV}: ${FS_CHECK_CMD} ${LINE}"
          done
    fi

    # check is device already mounted
    __check_already_mounted "${DEV}" "${MNT}"

    # try to mount
    if ! /usr/bin/which -s "${FS_MOUNT_CMD}"
    then
      __log "command '${FS_MOUNT_CMD}' not found"
      __log "please install '${FS_MOUNT_PORT}' port or package"
      exit 1
    fi
    __wait_for_device "${DEV}"

    # execute appropriate mount(8) command
    COUNT=0
    while ! ${FS_MOUNT_CMD} ${FS_MOUNT_ARGS} 2> /dev/null
    do
      if [ ! -e "${DEV}" ]
      then
        __log "${DEV}: device gone"
        exit 1
      fi
      COUNT=$(( ${COUNT} + 1 ))
      if [ ${COUNT} -gt ${RETRY_COUNT} ]
      then

        # BEGIN | try to mount read only
        FS_MOUNT_ARGS="-o ro ${FS_MOUNT_ARGS}"
        ${FS_MOUNT_CMD} ${FS_MOUNT_ARGS}

        if [ ${?} -eq 0 ]
        then
          __log "${DEV}: mount OK: '${FS_MOUNT_CMD} ${FS_MOUNT_ARGS}'"
          break
        fi
        # END | try to mount read only

        __log "${DEV}: mount FAIL: '${FS_MOUNT_CMD} ${FS_MOUNT_ARGS}'"
        exit 1
      fi
      sleep "${RETRY_DELAY}"
      __log "${DEV}: filesystem mount retry: ${COUNT}/${RETRY_COUNT}"
    done
    __log "${DEV}: mount OK: '${FS_MOUNT_CMD} ${FS_MOUNT_ARGS}'"

    # add needed rights
    chown "${USER}:${MNT_GROUP}" "${MNT}"
    __log "${DEV}: chown '${MNT}' dir with '${USER}:${MNT_GROUP}' rights"

    # add state
    PROVIDER=$( mount | grep -m 1 " ${MNT} " | awk '{printf $1}' )
    __state_add ${DEV} ${PROVIDER} ${MNT}

    # open file manager and display message
    __show_message "Device '${DEV}' mounted on '${MNT}' directory."
    if [ -n "${FM}" ]
    then
      GROUP_USERS=$( pw group show ${MNT_GROUP} | sed -e 's|.*:||' -e 's|,| |g' )
      for I in ${GROUP_USERS}
      do
        [ "${I}" = "root" ] && continue
        XORG_PID=$( pgrep Xorg )
        [ "${XORG_PID}" = "" ] && continue
        DISPLAY_ID=$( procstat pargs ${XORG_PID} | grep "^argv\[1\]:" | awk '{print $NF}' )
        [ -z "${DISPLAY_ID}" ] && continue
        __log "${DEV}: starting '${FM}' file manager"
        su -l "${I}" -c "env DISPLAY=${DISPLAY_ID} ${FM} ${MNT} &" 1> /dev/null 2>&1
      done
    fi
    ;;

  (detach)
    __log "${DEV}: detach"
    if [ -f ${STATE} ]
    then
      grep -E "^/dev/${1} " ${STATE} \
        | while read DEV PROVIDER MNT
          do
            TARGET=$( mount | grep -v \.gvfs | grep -m 1 -E "^${PROVIDER} " | awk '{print $3}' )
            __state_remove ${MNT}
            if [ -z ${TARGET} ]
            then
              continue
            fi
            ( # put entire umount/find/rm block into background
              # umount(8) two times to make sure its unmounted
              umount -f "${TARGET}" 1> /dev/null 2>&1
              umount -f "${TARGET}" 1> /dev/null 2>&1
              __log "${DEV}: (state) umount '${TARGET}'"
              __remove_dir "${TARGET}" &
              __log "${DEV}: (state) mount point '${TARGET}' removed"
            ) &
            unset TARGET
          done

      # code for NICENAMES mounting instead of the /dev/${DEV} default
      if [ "${NICENAMES}" != YES ]
      then
        # umount(8) two times to make sure its unmounted
        umount -f "${MNT_PREFIX}/${1}" 1> /dev/null 2>&1
        umount -f "${MNT_PREFIX}/${1}" 1> /dev/null 2>&1
        __log "${DEV}: (direct) umount '${MNT_PREFIX}/${1}'"
        __remove_dir "${MNT_PREFIX}/${1}" &
        __log "${DEV}: (direct) mount point '${MNT_PREFIX}/${1}' removed"
      fi
      __show_message "Device '${DEV}' unmounted from '${MNT}' directory."
    fi
    ;;

esac