automount/automount

733 lines
21 KiB
Bash

#! /bin/sh
# Copyright (c) 2012-2019 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.
BOOTTIME=$( sysctl -n kern.boottime | awk '{print $4}' | tr -d ',' )
CURRTIME=$( date +%s )
DIFFTIME=$(( ${CURRTIME} - ${BOOTTIME} ))
if [ ${DIFFTIME} -lt 45 ]
then
exit 0
fi
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/HFS/XFS/MTP respectively:
o sysutils/fusefs-ntfs
o sysutils/fusefs-exfat
o sysutils/fusefs-ext4fuse
o sysutils/fusefs-hfsfuse
o sysutils/fusefs-lkl
o sysutils/fusefs-simple-mtpfs
o x11/zenity
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"
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.6.1 2019/01/11"
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
: ${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
}
__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}
case ${1} in
(ugen*)
case ${2} in
(attach)
__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
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)
__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}
fsck.ext2 -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}
fsck.ext3 -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}
fsck.ext4 -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}
fsck.exfat ${DEV} \
| while read LINE
do
__log "${DEV}: fsck.exfat ${LINE}"
done
__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
umount -f "${MNTPREFIX}/${1}"
__remove_dir "${MNTPREFIX}/${1}"
__log "${DEV}: mount point '${MNTPREFIX}/${1}' removed"
fi
;;
esac
;;
esac