#! /bin/sh # Copyright (c) 2012-2015 Slawomir Wojciech Wojtczak (vermaden) # 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 Add these to mount NTFS/exFAT/EXT4/MTP/HFS/XFS respectively: o sysutils/fusefs-ntfs o sysutils/fusefs-exfat o sysutils/fusefs-ext4fuse o sysutils/fusefs-simple-mtpfs o sysutils/fusefs-hfsfuse o sysutils/fusefs-lkl 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. MNTPREFIX (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: MNTPREFIX="/media" ENCODING (set to en_US.ISO8859-1 by default) Only used with FAT32 mounts, specifies which encoding to use at the mount. example: ENCODING="pl_PL.ISO8859-2" CODEPAGE (set to cp437 by default) Only used with FAT32 mounts, specifies which code page to use at the mount. example: CODEPAGE="cp852" USER (unset 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" FM (unset 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" 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" ATIME (set to YES by default) When set to NO it will mount filesystems with noatime options when possible. example: ATIME="NO" REMOVEDIRS (set to NO by default) When set to YES it will remove empty directories under the used ${MNTPREFIX} after device detach. example: REMOVEDIRS="YES" BLACKLIST (unset by default) The automount will ignore devices defined here. example: BLACKLIST="da0 da3s1a" TIMEOUT (set to 8 by default) Do not wait longer then the specified timeout for the device node to appear in /dev and be accessible. example: TIMEOUT="8" DELAY (set to 1 second by default) How often to check for device availability. example: DELAY="2.5" BOOTDELAY (set to 45 seconds by default) How long to wait for boot process to complete. example: BOOTDELAY="30" NOTIFY (set to NO by default) Use 'notify-send' and 'libnotify' to show notifications of mounting and unmounting devices on the desktop. Note that you have to call 'xhost +local:' command in your '~/.xinitrc' file for this to work. 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" EOF exit 0 } if [ "${1}" = "--version" -o "${1}" = "-version" -o "${1}" = "version" ] then echo "automount 1.5.9 2018/12/08" exit 0 fi if [ "${1}" = "-h" -o "${1}" = "--help" -o ${#} -eq 0 -o ${#} -eq 1 ] then __usage fi if [ -f /usr/local/etc/automount.conf ] then . /usr/local/etc/automount.conf fi : ${MNTPREFIX="/media"} # mount prefix : ${LOG="/var/log/automount.log"} # log file : ${STATE="/var/run/automount.state"} # current state file : ${ENCODING="en_US.ISO8859-1"} # US/Canada : ${CODEPAGE="cp437"} # US/Canada : ${DATEFMT="%Y-%m-%d %H:%M:%S"} # 2012-02-20 07:49:09 : ${USERUMOUNT="NO"} # when YES add suid bit to umount(8) : ${ATIME="YES"} # when NO mount with noatime : ${REMOVEDIRS="NO"} # remove empty dirs under ${MNTPREFIX} : ${USER="0"} # which user to use for popup : ${FM="0"} # which file manager to use : ${TIMEOUT="8"} # stop waiting for device after that time : ${DELAY="0.1"} # check for the device node that often : ${BOOTDELAY="45"} # wait for boot process to complete : ${NOTIFY="NO"} # use 'notify-send' and 'libnotify' : ${WALL="NO"} # use 'wall(1)' if [ "${USERUMOUNT}" = YES ] then chmod u+s /sbin/umount 1> /dev/null 2> /dev/null # WHEEL group member chmod u+s /sbin/mount* 1> /dev/null 2> /dev/null # WHEEL group member sysctl vfs.usermount=1 1> /dev/null 2> /dev/null # allow USER to mount fi __create_mount_point() { # 1=DEV local DEVICE=${1##*/} mkdir -p ${MNTPREFIX}/${DEVICE} if [ "${USER}" != 0 ] then chown ${USER}:$( id -g -n ${USER} ) ${MNTPREFIX}/${DEVICE} UID=$( id -u ${USER} ) GID=$( id -g ${USER} ) else UID=0 GID=0 fi } __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 env DISPLAY=:0 notify-send automount "Device '${1}' mounted on '${3}' directory." fi if [ "${WALL}" = YES ] then echo "automount: Device '${1}' mounted on '${3}' directory." | wall fi } __state_remove() { # 1=MNT if [ -f ${STATE} ] then BSMNT=$( echo ${1} | sed 's/\//\\\//g' ) # backslash the slashes ;) sed -i '' "/${BSMNT}\$/d" ${STATE} if [ "${NOTIFY}" = YES ] then env DISPLAY=:0 notify-send automount "Device '${1}' unmounted from '${3}' directory." fi if [ "${WALL}" = YES ] then echo "automount: Device '${1}' unmounted from '${3}' directory." | wall fi fi } __remove_dir() { # 1=TARGET if [ "${REMOVEDIRS}" = YES ] then find "${1}" -type d -empty -maxdepth 1 -exec rm -r {} '+' 2> /dev/null fi } __log() { # @=MESSAGE echo $( date +"${DATEFMT}" ) "${@}" >> ${LOG} } __check_already_mounted() { # 1=(-d|-m) 2=(DEV|MNT) local MOUNT="$( mount )" case ${1} in (-d) if echo "${MOUNT}" | grep -q "^${2} on " then local MOUNT="$( echo "${MOUNT}" | grep "^${2} on " | cut -d ' ' -f 3-255 | cut -d '(' -f 1 | sed s/.$// )" __log "${DEV}: already mounted on '${MOUNT}' mount point" exit 0 fi ;; (-m) 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 0 fi ;; esac } __wait_for_device() { # 1=DEV local COUNT=0 while ! head -c 1 ${1} 1> /dev/null 2> /dev/null do sleep ${DELAY} local COUNT=$( echo ${COUNT} + ${DELAY} | bc -l ) if ! echo ${COUNT} | grep -q -E '^[0-9]' then local COUNT=0${COUNT} fi local COUNT_INT=$( echo ${COUNT} | cut -d '.' -f 1 ) if [ ${COUNT_INT} -gt ${TIMEOUT} ] then __log "${DEV}: device node not available" exit 0 fi done } __wait_for_boot() { local BOOTTIME=$( sysctl -n kern.boottime | awk -F',' '{print $1}' | awk '{print $NF }' ) local CURRTIME=$( date +%s ) local UPTIME=$(( ${CURRTIME} - ${BOOTTIME} )) local WAIT=0 while [ ${UPTIME} -lt ${BOOTDELAY} ] do sleep 1 local WAIT=1 local CURRTIME=$( date +%s ) local UPTIME=$(( ${CURRTIME} - ${BOOTTIME} )) done if [ ${WAIT} -eq 1 ] then __log "${DEV}: done waiting '${BOOTDELAY}' seconds for boot process to complete" fi } # __random_wait() { # RANDOM=$( head -c 256 /dev/urandom | env LC_ALL=C tr -c -d '1-9' ) # MODULO=$(( ${RANDOM} % 24 )) # WAIT=$( echo ${MODULO} / 10 | bc -l ) # WAIT_TEST=$( echo ${WAIT} | awk -F'.' '{print $1}' ) # if [ "${WAIT_TEST}" = "" ] # then # WAIT="0${WAIT}" # fi # WAIT=$( printf "%.1f" ${WAIT} ) # sleep ${WAIT} # __log "${DEV}: random wait for '${WAIT}' seconds before 'attach' action" # } __fstype() { # 1=DEV TYPE=$( dd < ${DEV} count=1 2> /dev/null | strings | head -1 ) if echo "${TYPE}" | grep -q 'EXFAT' then TYPE=EXFAT return fi TYPE='' TYPE=$( file -r -b -L -s ${DEV} | sed -E 's/label:\ \".*\"//g' ) if echo "${TYPE}" | grep -q 'Unix Fast File' then TYPE=UFS return fi if echo "${TYPE}" | grep -q 'ext2' then TYPE=EXT2 return fi if echo "${TYPE}" | grep -q 'ext3' then TYPE=EXT3 return fi if echo "${TYPE}" | grep -q 'ext4' then TYPE=EXT4 return fi if echo "${TYPE}" | grep -q 'SGI XFS' then TYPE=XFS return fi if echo "${TYPE}" | grep -q 'Macintosh HFS' then TYPE=HFS return fi if echo "${TYPE}" | grep -q 'boot sector' then TYPE=$( file -r -k -b -L -s ${DEV} | sed -E 's/label:\ \".*\"//g' ) if echo "${TYPE}" | grep -q 'Unix Fast File' then TYPE=UFS return fi if echo "${TYPE}" | grep -q 'NTFS' then TYPE=NTFS return fi if echo "${TYPE}" | grep -q 'MSDOS' then TYPE=FAT return fi if echo "${TYPE}" | grep -q 'FAT (32 bit)' then TYPE=FAT return fi if echo "${TYPE}" | grep -q 'FAT' then TYPE=FAT return fi fi TYPE=-1 return } DEV=/dev/${1} __wait_for_boot case ${1} in (ugen*) case ${2} in (attach) # __random_wait __log "${DEV}: attach" if [ "${BLACKLIST}" != "" ] then __log "${DEV}: using BLACKLIST='${BLACKLIST}'" for I in ${BLACKLIST} do if [ ${1} = "${I}" ] then __log "${DEV}: device blocked by BLACKLIST option" exit 0 fi done fi ADD=0 MNT="${MNTPREFIX}/${1}" __check_already_mounted -d ${DEV} __check_already_mounted -m ${MNT} __wait_for_device ${DEV} PHONEDEV=$( simple-mtpfs --list-devices -d ${DEV} 2>&1 ) if [ "${PHONEDEV}" = "No raw devices found." ] then __log "${DEV}: no raw devices found" exit 0 else PHONEDEV=$( echo "${PHONEDEV}" | awk '{print $1}' | tr -d ':' ) fi __create_mount_point ${DEV} simple-mtpfs --device ${PHONEDEV} ${MNT} -o uid=${UID} -o gid=${GID} -o allow_other if [ ${?} -ne 0 ] then su - ${USER} -c "env DISPLAY=:0 zenity --info --text 'Allow on the Phone.\n\nThen click OK.' --no-wrap" exit 0 else # simple-mtpfs --device ${PHONEDEV} ${MNT} -o uid=${UID} -o gid=${GID} -o allow_other PROVIDER=$( mount | grep -m 1 " ${MNT} " | awk '{printf $1}' ) __state_add ${DEV} ${PROVIDER} ${MNT} if [ "${USER}" != 0 -a "${FM}" != 0 ] then su - ${USER} -c "env DISPLAY=:0 ${FM} ${MNT} &" fi fi ;; (detach) __log "${DEV}: detach" if [ -f ${STATE} ] then grep -E "${MNTPREFIX}/${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 -f ${TARGET} __remove_dir "${TARGET}" __log "${DEV}: removed '${TARGET}'" ) & unset TARGET __log "${DEV}: umount" done __remove_dir "${MNTPREFIX}/${1}" __log "${DEV}: mount point '${MNTPREFIX}/${1}' removed" fi ;; esac ;; (da*|mmcsd*) case ${2} in (attach) # __random_wait __log "${DEV}: attach" if [ "${BLACKLIST}" != "" ] then __log "${DEV}: using BLACKLIST='${BLACKLIST}'" for I in ${BLACKLIST} do if [ ${1} = "${I}" ] then __log "${DEV}: device blocked by BLACKLIST option" exit 0 fi done fi ADD=0 MNT="${MNTPREFIX}/${1}" __check_already_mounted -d ${DEV} __check_already_mounted -m ${MNT} if [ "${ATIME}" = NO ] then OPTS="-o noatime" fi __wait_for_device ${DEV} __fstype ${DEV} case ${TYPE} in (UFS) __create_mount_point ${DEV} __wait_for_device ${DEV} fsck_ufs -C -y ${DEV} \ | while read LINE do __log "${DEV}: fsck_ufs ${LINE}" done __wait_for_device ${DEV} if mount -t ufs ${OPTS} ${DEV} ${MNT} then ADD=1 else __log "${DEV}: mount failed (ufs) 'mount -t ufs ${OPTS} ${DEV} ${MNT}'" exit 1 fi __log "${DEV}: mount (ufs) 'mount -t ufs ${OPTS} ${DEV} ${MNT}'" ;; (FAT) # must be before NTFS section because: newfs_msdos -O NTFS -L NTFS __create_mount_point ${DEV} __wait_for_device ${DEV} fsck_msdosfs -C -y ${DEV} \ | while read LINE do __log "${DEV}: fsck_msdosfs ${LINE}" done __wait_for_device ${DEV} if [ "${USER}" != 0 ] then USEROPTS="-u ${UID} -g ${GID}" else USEROPTS="" fi FATCMD="mount_msdosfs ${OPTS} -o longnames -m 644 -M 755 -D ${CODEPAGE} -L ${ENCODING} ${USEROPTS} ${DEV} ${MNT}" if ${FATCMD} then ADD=1 else FATOUT=16 FATCUR=0 while sleep 1 do FATCUR=$(( ${FATCUR} + 1 )) if [ ${FATCUR} -gt ${FATOUT} ] then __log "${DEV}: mount failed (fat) '${FATCMD}'" break fi ${FATCMD} if [ ${?} -eq 0 ] then ADD=1 break else continue fi done exit 1 fi if [ ${ADD} -eq 1 ] then __log "${DEV}: mount (fat) '${FATCMD}'" else __log "${DEV}: mount failed (fat) '${FATCMD}'" fi ;; (NTFS) # must be after FAT section: newfs_msdos -O NTFS -L NTFS __create_mount_point ${DEV} __wait_for_device ${DEV} if which ntfs-3g 1> /dev/null 2> /dev/null # sysutils/fusefs-ntfs then __wait_for_device ${DEV} if ntfs-3g -o recover -o remove_hiberfile ${OPTS} ${DEV} ${MNT} then ADD=1 else # make nested mount try because sometimes second mount works if ntfs-3g -o recover -o remove_hiberfile ${OPTS} ${DEV} ${MNT} then ADD=1 else __log "${DEV}: mount failed (ntfs) 'ntfs-3g ${OPTS} ${DEV} ${MNT}'" exit 1 fi fi else if ! [ "${USER}" = 0 ] then OPTS="${OPTS} -u ${USER} -g $( id -g -n ${USER} )" fi if mount_ntfs ${OPTS} ${DEV} ${MNT} then ADD=1 else __log "${DEV}: mount failed (ntfs) 'mount_ntfs ${OPTS} ${DEV} ${MNT}'" exit 1 fi fi __log "${DEV}: mount (ntfs) 'mount_ntfs ${OPTS} ${DEV} ${MNT}'" ;; (EXT2) __create_mount_point ${DEV} __wait_for_device ${DEV} e2fsck -y ${DEV} \ | while read LINE do __log "${DEV}: fsck.ext2 ${LINE}" done __wait_for_device ${DEV} if mount -t ext2fs ${OPTS} ${DEV} ${MNT} then ADD=1 else __log "${DEV}: mount failed (ext2) 'mount -t ext2fs ${OPTS} ${DEV} ${MNT}'" exit 1 fi __log "${DEV}: mount (ext2) 'mount -t ext2fs ${OPTS} ${DEV} ${MNT}'" ;; (EXT3) __create_mount_point ${DEV} __wait_for_device ${DEV} e2fsck -y ${DEV} \ | while read LINE do __log "${DEV}: fsck.ext3 ${LINE}" done __wait_for_device ${DEV} if mount -t ext2fs ${OPTS} ${DEV} ${MNT} then ADD=1 else __log "${DEV}: mount failed (ext3) 'mount -t ext2fs ${OPTS} ${DEV} ${MNT}'" exit 1 fi __log "${DEV}: mount (ext3) 'mount -t ext2fs ${OPTS} ${DEV} ${MNT}'" ;; (EXT4) __create_mount_point ${DEV} __wait_for_device ${DEV} e2fsck -y ${DEV} \ | while read LINE do __log "${DEV}: fsck.ext4 ${LINE}" done __wait_for_device ${DEV} if ext4fuse ${DEV} ${MNT} # sysutils/fusefs-ext4fuse then ADD=1 else __log "${DEV}: mount failed (ext4) 'ext4fuse ${DEV} ${MNT}'" exit 1 fi __log "${DEV}: mount (ext4) 'ext4fuse ${DEV} ${MNT}'" ;; (EXFAT) __create_mount_point ${DEV} __wait_for_device ${DEV} if [ "${USER}" != 0 ] then USEROPTS="-o uid=${UID} -o gid=${GID}" else USEROPTS="" fi # sysutils/fusefs-exfat if mount.exfat ${OPTS} ${USEROPTS} -o dmask=022 -o fmask=133 \ -o noatime ${DEV} ${MNT} then ADD=1 else __log "${DEV}: mount failed (exfat) 'mount.exfat ${OPTS} ${USEROPTS} -o dmask=022 -o fmask=133 -o noatime ${DEV} ${MNT}'" exit 1 fi __log "${DEV}: mount (exfat) 'mount.exfat ${OPTS} ${USEROPTS} -o dmask=022 -o fmask=133 -o noatime ${DEV} ${MNT}'" ;; (XFS) __create_mount_point ${DEV} __wait_for_device ${DEV} xfs_repair -d ${DEV} \ | while read LINE do __log "${DEV}: xfs_repair ${LINE}" done __wait_for_device ${DEV} if lklfuse -o type=xfs -o allow_other -o uid=${UID} -o gid=${GID} ${DEV} ${MNT} # sysutils/fusefs-lkl then ADD=1 else __log "${DEV}: mount failed (xfs) 'lklfuse -o type=xfs -o allow_other -o uid=${UID} -o gid=${GID} ${DEV} ${MNT}'" exit 1 fi __log "${DEV}: mount (xfs) 'lklfuse -o type=xfs -o allow_other -o uid=${UID} -o gid=${GID} ${DEV} ${MNT}'" ;; (HFS) __create_mount_point ${DEV} __wait_for_device ${DEV} if hfsfuse --force -o noatime ${DEV} ${MNT} # sysutils/fusefs-hfsfuse then ADD=1 else __log "${DEV}: mount failed (hfs) 'hfsfuse --force -o noatime ${DEV} ${MNT}'" exit 1 fi __log "${DEV}: mount (hfs) 'hfsfuse --force -o noatime ${DEV} ${MNT}'" ;; (*) __log "${DEV}: filesystem not supported or no filesystem" exit 0 ;; esac if [ ${ADD} -eq 1 ] then ADD=0 PROVIDER=$( mount | grep -m 1 " ${MNT} " | awk '{printf $1}' ) __state_add ${DEV} ${PROVIDER} ${MNT} if [ "${USER}" != 0 -a "${FM}" != 0 ] then su - ${USER} -c "env DISPLAY=:0 ${FM} ${MNT} &" fi fi ;; (detach) __log "${DEV}: detach" if [ -f ${STATE} ] then grep -E "${MNTPREFIX}/${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 -f ${TARGET} __remove_dir "${TARGET}" __log "${DEV}: removed '${TARGET}'" ) & unset TARGET __log "${DEV}: umount" done __remove_dir "${MNTPREFIX}/${1}" __log "${DEV}: mount point '${MNTPREFIX}/${1}' removed" fi ;; esac ;; esac