You are not logged in.

#1 2011-11-10 04:41:10

i336
Member
Registered: 2011-01-08
Posts: 11

A semiautomatic alternative to /etc/fstab

This is probably highly redundant... the chances are, someone will likely say "XYZ does that for you and you can configure it in 5 minutes", but here goes anyways.

I wanted a simple way to mount the disks in my computer to the same location regardless of where they were in the system (thus via UUID) but what I *didn't* want was to have to copy/type the UUID myself. The following possibly shaky bash script is the result.

First, however, a (very real-world) demonstration of its functionality!

/disks/ + ./domount
Using scriptdir "/disks/.mountscripts".
Running mount... [ok]
[Disk ST3250620A_5QE4M336]
group0-root -> /disks/250gb: [ok]
38067a33-0556-4cab-a5c5-c96b313bd174 -> /disks/250gb/boot: [ok]
21D4-2E62 -> /disks/250gb/data: [ok]
group0-home -> /disks/250gb/home: 
== mount error ==
mount: wrong fs type, bad option, bad superblock on /dev/mapper/group0-home,
       missing codepage or helper program, or other error
       In some cases useful info is found in syslog - try
       dmesg | tail  or so
=================

[R]etry/Skip [P]artition/Skip [D]isk/[Q]uit? q

/disks/ + fsck.jfs /dev/mapper/group0-home
fsck.jfs version 1.1.15, 04-Mar-2011
processing started: 11/10/2011 20:28:10
Using default parameter: -p
The current device is:  /dev/mapper/group0-home
Block size in bytes:  4096
Filesystem size in blocks:  52099072
**Phase 0 - Replay Journal Log
Filesystem is clean.

/disks/ + ./domount
Using scriptdir "/disks/.mountscripts".
Running mount... [ok]
[Disk ST3250620A_5QE4M336]
group0-root -> /disks/250gb: (already mounted)
38067a33-0556-4cab-a5c5-c96b313bd174 -> /disks/250gb/boot: (already mounted)
21D4-2E62 -> /disks/250gb/data: (already mounted)
group0-home -> /disks/250gb/home: [ok]
group0-var -> /disks/250gb/var: [ok]
partition1.vfat -> /disks/250gb/home/backup/80gb/mnt/partition1.vfat: [ok]
partition2.vfat -> /disks/250gb/home/backup/80gb/mnt/partition2.vfat: [ok]
partition3.vfat -> /disks/250gb/home/backup/80gb/mnt/partition3.vfat: [ok]
partition4.ext3 -> /disks/250gb/home/backup/80gb/mnt/partition4.ext3: [ok]
data2 -> /disks/250gb/home/backup/32gb-2/mnt/data2: [ok]

[Disk ST340014A_5MQ4HB90]
0854-08DE -> /disks/20gb-1/data-1: [ok]
4846-D7E2 -> /disks/20gb-1/data-2: [ok]
3070DB1E70DAE99C -> /disks/20gb-1/winnt: [ok]
38BB-158D -> /disks/20gb-1/pool: [ok]

[Disk WDC_WD800BB-22J_WD-WCAM9H677098]
e336c404-fca8-4f2b-9c75-81c22f339741 -> /disks/80gb: [ok]
4738-E723 -> /disks/80gb/vfat: [ok]
a827cfa1-08cf-4a24-a989-aae94ea0801b -> /disks/80gb/boot: [ok]
7bb5df89-3a90-4c92-8aa7-a94271806087 -> /disks/80gb/var: [ok]
09b652b7-4f5e-4895-8464-6f972a44fdd6 -> /disks/80gb/home: [ok]
a2534aa6-b70f-442d-805e-365ee626d4be -> /disks/80gb/tmpspace: [ok]
4871-993D -> /disks/80gb/tmpspace2: [ok]
386a2a83-22e2-425c-bd48-cb0a1fad8a87 -> /disks/80gb/pool: [ok]
/disks/ + 

Here's the script! (I can pastebin it if neccessary)

#!/bin/bash

# ohai from i336 :P <i3367980@gmail.com>
# Oct-Nov 2011
# Public domain, no warranty. Be sure to use the "t" flag on the first run!
 
# This program has two modes: scan mode and run mode.

# Configuration
# ============= 
# You first need to create/go into the directory you want to mount your disks
# in, such as /mnt (I use /disks), and create the subdirectory ".mountscripts", or
# alternatively "programname-mountscripts" (the second directory bearing the
# name of the program/symlink, a simple mechanism to implement some flexibility).
# You can substitute any created symlinks whereever "./domount" is mentioned.
# The existance of this directory indicate that this is the work directory.
# (For added flexibility, the program will look for the second directory, the
# one bearing its name, first, then fall back on ".mountscripts" if this is not
# found.)
 
# Scan Mode
# =========
# After creating this directory for the first time you will want to run
# "./domount s" to generate the mountscripts into the mountscript directory
# (which is selected as specified above).
 
# Run Mode
# ========
# At this point, go into the mountscript directory, open all the files you find
# there in a text editor, and add in the mountpoints you want to use after the
# UUID parameter to 'partop' (an internal function defined in this file for the
# scripts).
# 
# ** The first time you simply MUST run "./domount t" in order to see that the
# 'mount' commands are correct! **
#
# After this is done, run "./domount" and it will go ahead and mount the disks.
# Run "./domount u" and it will unmount everything. (No options exist for
# individual partitions as yet).
 
# Limitations
# ===========
# * If you use domount to mount loopback images inside real partitions and the
#   real partitions are also mounted by domount, well, domount will try to
#   unmount them in the same order as when it mounts... and it will break.
#   Simple solution: skip however many real [p]artitions you have, then
#   re-run domount again. :)
#
# * If you change a disk (eg add a partition), well, you'll have to delete the
#   file for that disk, re-scan (domount will not touch the other scripts) then
#   re-add your partitions back in. This program wasn't really designed to deal
#   with that kind of situation :)
# 
# * This program does not support LVM partitions - quite frankly, it doesn't
#    even realize such things exist. Thus you will not find any LVM partitions
#    listed in the generated scripts, or any "LVM partitions ignored"
#    messages - indeed, if you only have LVM partitions on a given disk, the
#    resulting syntactically incorrect script will contain an 'if' block with
#    no content and the shell will produce an error.

toollist=
needtool=0
for tool in find lsblk blkid cfdisk xargs grep tail mountpoint; do
	
	type -P $tool > /dev/null 2>&1
	
	if [ $? -eq 0 ]; then
		toollist="${toollist} ${tool}"
	else
		toollist="${toollist} [${tool}]"
		needtool=1
	fi
	
done

if [ $needtool -eq 1 ]; then
	echo "This program requires the following tools in order to run. Those marked with"
	echo "brackets cannot be found (using \`type') and their containing packages"
	echo "likely need to be installed."
	echo $toollist
	exit 1
fi

sizes=(bytes KB MB GB TB)
progname=$(basename $0)
if [ -d ".mountscripts" ]; then
	scriptdir="$(pwd)/.mountscripts"
elif [ -d ".${progname}-mountscripts" ]; then
	scriptdir="$(pwd)/.${progname}-mountscripts"
fi

if ([[ ! -d "${scriptdir}" ]] && [[ "$1" != "s" ]]) || [[ "$1" == "h" ]]; then
	cat << EOF
usage: $0 [s] [t]
s = scan
t = test run (USE THIS THE FIRST TIME AFTER YOU HAVE DONE A SCAN)
EOF

	exit 1
fi

if [[ "$1" = "s" ]]; then
	
	echo -n "Scanning disk tables... (by name)"
	parttable=(); while IFS= read -r line; do parttable+=("$line"); done < \
		<(find /dev/disk/by-id/ -name "scsi-SATA*" -name "*-part*" -type l | xargs stat -L -c "%t-%T %n")
	echo -n ", (by UUID)"
	uuidtable=(); while IFS= read -r line; do uuidtable+=("$line"); done < \
		<(find /dev/disk/by-uuid/ -type l | xargs stat -L -c "%t-%T %n")
	echo -ne " [ok]\nRunning blkid..."
	blkidtable=(); while IFS= read -r line; do blkidtable+=("$line"); done < \
		<(blkid)
	echo -ne " [ok]\nRunning lsblk (uno momento)..."
	lsblktable=(); while IFS= read -r line; do lsblktable+=("$line"); done < \
		<(lsblk -bro name,size,fstype,model | grep -v group | tail -n +2)
	echo -e " [ok]\n"
	
	if [ ${#parttable[@]} -ne ${#uuidtable[@]} ]; then
		echo 'Something is very wrong with either this program'
		echo 'or your disk configuration. O.o'
		exit 1
	fi
	
	echo -e "Using scriptdir \"${scriptdir}\".\n"
	
	echo -ne "\e[1GCompiling mapping table... [                                            ]\e[?25l"
	
	
	max=$[${#parttable[@]}*${#parttable[@]}]
	runindex=0
	
	for ((i = 0; i < "${#parttable[@]}"; i++)); do
		partsplit=(${parttable[$i]})
		devok=0
		devname="$(readlink -f ${partsplit[1]})"
		partsize=
		
		for uuid in "${uuidtable[@]}"; do
			
			uuidsplit=($uuid)
			
			c=$[((runindex*43)/$[max-1])]
			
			echo -ne "\e[29G"
			
			if [ $c -gt 0 ]; then eval \printf "%.s#" {0..$c}; else echo -n '.'; fi
			if [ $c -lt 43 ]; then eval \printf "%.s." {$[c+1]..43}; fi
			
			((runindex++))
			
			if [[ "${partsplit[0]}" = "${uuidsplit[0]}" ]]; then
				partlabel=
				devok=1
				
				for entry in "${blkidtable[@]}"; do
					if [[ "${entry:0:$[${#devname}+9]}" != "${devname}: LABEL=\"" ]]; then continue; fi
					partlabel="${entry:$[${#devname}+9]}"
					partlabel=$(echo -n $(echo $partlabel | cut -d'"' -f1))
				done
				
				for entry in "${lsblktable[@]}"; do
					entry=($entry)
					if [[ "/dev/${entry[0]}" != "$devname" ]]; then continue; fi
					partsize=${entry[1]}
					parttype=${entry[2]}
				done
				
				if [ ! partsize ]; then
					echo "$0: error: cannot determine partition size for $devname"
					exit 1
				fi
				
				devline="${partsplit[1]:26} ${uuidsplit[1]:18} ${partsize} ${parttype}${partlabel:+ $partlabel}"
				
				map[${#map[@]}]="$devline"
			fi
		done
		if [ $devok -eq 0 ]; then
			checkparttable[${#checkparttable[@]}]="${parttable[$i]#* }"
		fi
	done
	
	echo -e "\e[?25h\e[75Gdone.\n"
	
	if [[ ${#checkparttable[@]} -gt 0 ]]; then
		cat << EOF

Warning: The following partitions do not have matching UUID entries
         in /dev/disk/by-uuid/.
         Linux seems to be quite smart, and won't list UUIDs for LVM
         members, partitions \`mount' cannot mount without the -t flag,
         or extended partition headers, but /dev/disk/by-id/ will still
         list them. So these are probably not a problem but may still
         warrant a double-check; if these contain valid filesystems you
         will need to insert them manually since their UUIDs cannot be
         calculated.
EOF
		
		for partition in "${checkparttable[@]}"; do
			echo "         >> $(readlink -f $partition) (/dev..by-id/${partition:26})";
		done
		echo
		
	fi
	
	find /dev/disk/by-id/ -name "scsi-SATA*" -not -name "*-part*" -type l | while read disk; do
		
		scriptfile="${scriptdir}/${disk:26}.mount.sh"
		rm -f "${scriptfile}"
		
		if [ ! -f "${scriptfile}" ]; then
			
			echo -ne "No mountscript found for disk ID \"${disk:26}\", creating one...\nRunning cfdisk... "
			
			cfdtable=(); while IFS= read -r line; do cfdtable+=("$line"); done < \
				<(cfdisk -Ps $disk | grep -v "Free Space" | grep -v "Unusable" | tail -n +6)
			
			echo -ne "[ok]\nRunning smartctl... "
			
			smartctlinfo="$(smartctl -i $disk)"
			
			diskdevname="$(readlink -f ${disk})"
			diskdevname=${diskdevname:5}
			
			disk="${disk:26}"
			disktable[${#disktable[@]}]="${disk}"
			tmp=
			diskparttable=
			
			for entry in "${lsblktable[@]}"; do
				entry=($entry)
				if [[ "${diskdevname}" != "${entry[0]}" ]]; then continue; fi
				devicename=$(echo -n $(echo "${entry[@]}" | cut -d' ' -f3-))
			done
			
			echo -e "# Script generated by domount at $(date +'%T on %D (MM/DD/YY)') for disk \"${devicename}\"\n" > "${scriptfile}"
			echo '# '$(echo "$smartctlinfo" | grep '^Model Family:') >> "${scriptfile}"
			echo '# '$(echo "$smartctlinfo" | grep '^Device Model:') >> "${scriptfile}"
			echo -e '# '$(echo "$smartctlinfo" | grep '^User Capacity:')"\n" >> "${scriptfile}"
			
			for part in "${map[@]}"; do
				if [[ "${part:0:$[${#disk}+1]}" != "${disk}-" ]]; then continue; fi
				diskparttable="${diskparttable}${part}\n";
			done
			
			mapfile -t diskparttable < <(echo -ne "${diskparttable%%\\n}" | sort -n -k1.$[${#disk}+6]n)
			
			echo -ne "if diskexists ${disk}; then\n\t\n" >> "${scriptfile}"
			
			for part in "${diskparttable[@]}"; do
				
				partsplit=($part)
				
				parttype=
				for line in "${cfdtable[@]}"; do
					line=($line)
					if [[ "X${partsplit[0]:${#disk}+5}X" != "X${line[0]}X" ]]; then continue; fi
					parttype="${line[1]}"
				done
				
				if [[ "X${parttype}X" = "XX" ]]; then
					echo "$0: error: Cannot parse cfdisk output"
					rm -f "${scriptfile}"
					exit 1
				fi
				
				echo -ne "\t# Partition: #${partsplit[0]:${#disk}+5} (${parttype}, ${partsplit[3]}" >> "${scriptfile}"
				
				if [[ "${partsplit[3]}" = "swap" ]]; then
					
					echo -n " - Skipping" >> "${scriptfile}"
					
				fi
				
				echo -n ");  Size: " >> "${scriptfile}"
				
				sizeidx=0
				size=${partsplit[2]}
				
				while [ $size -gt 0 ]; do
					sizetext="${size}${sizes[$sizeidx]} ${sizetext}"
					size=$(($size/1024))
					((sizeidx++))
				done
				
				sizetext=($sizetext)
				
				for ((i = 0; i < 2; i++)); do
					
					if [ $i -eq 1 ]; then echo -n ' (' >> "${scriptfile}"; fi
					
					if [[ "${sizetext[$i]: -1:1}" = "s" ]]; then
						echo -n "${sizetext[$i]:0:-5} bytes" >> "${scriptfile}"
					else
						echo -n "${sizetext[$i]:0:-2} ${sizetext[$i]: -2:2}" >> "${scriptfile}"
					fi
					
					if [ $i -eq 1 ]; then echo -n ')' >> "${scriptfile}"; fi
					
				done
				
				if [[ "X${partsplit[4]}X" != "XX" ]]; then
					echo -n ";  Label: \"" >> "${scriptfile}"
					echo $(echo -n "${part}" | cut -d' ' -f5-)"\"" >> "${scriptfile}"
				else
					echo >> "${scriptfile}"
				fi
				
				if [[ "${partsplit[3]}" != "swap" ]]; then
					
					echo -e "\tmountpart /dev/disk/by-uuid/${partsplit[1]} \n\t" >> "${scriptfile}"
					
				else
					
					echo -e "\t" >> "${scriptfile}"
					
				fi
				
			done
			
			echo "fi" >> "${scriptfile}"
			
			echo -e "[ok]\nSuccess!\n"
			
			#echo ---; cat $scriptfile; echo ---
			
		else
			
			echo "Script found for disk ID ${disk}"
			
		fi
		
	done
	
	exit
	
fi

trap 'echo; exit' SIGINT

echo -ne "Using scriptdir \"${scriptdir}\".\nRunning mount..."
mapfile -t mounttable < <(mount)
echo -e " [ok]"

function spin() {
	trap 'echo -e "\e[?25h"' SIGINT SIGQUIT SIGKILL
	echo -ne "\e[?25l"
	if [[ $unicode -eq 1 ]]; then s=$(printf \\u2580\\u259C\\u2590\\u259F\\u2584\\u2599\\u258C\\u259B); m=8; d=0.03; else s='/-\|'; m=4; d=0.07; fi
	("$@" & pid=$! ; c=1; while ps -c $pid 2>&1>/dev/null; do echo -ne "\e[s${s:c:1} \e[u"; c=$[c+1]; test $c -eq $m && c=0; sleep $d; done)
	echo -ne "\e[?25h"
	trap SIGINT SIGQUIT SIGKILL
}

function diskexists {
	
	disk=/dev/disk/by-id/scsi-SATA_${@}
	
	if [[ ! -L $disk ]]; then
		echo "(Disk $0 is not installed)"
	else
		echo "[Disk ${1}]"
	fi
	
}

function partop {
	
	if [[ $mode -eq 1 ]]; then
		
		while true; do
			
			echo -n "Unmounting ${1##*/}... "
			
			if ! mountpoint > /dev/null 2>&1 $2; then
				echo "(Not mounted, or not a mountpoint)"
				break;
			fi
			
			if [ ! -d $2 ]; then
				echo "error: Not a directory!"
				break
			fi
			
			cmd="umount $1"
			
			if [[ ! $testmode ]]; then
				output="$(${cmd} 2>&1)"
				err=$?
			else
				echo "{would run: ${cmd}} "
			fi
				
			if [[ $err = 0 ]]; then
				
				if [[ ! $testmode ]]; then echo "[ok]"; fi
				return
				
			else
				
				echo -e "\n== umount error =="
				
				echo -n "${output}"
				
				echo -e "\n=================\n"
				
				c=X;
				while [[ ! $c =~ (R|r|P|p|D|d|Q|q) ]]; do read -sn1 -p"[R]etry/Skip [P]artition/Skip [D]isk/[Q]uit? " c; echo $c; done
				
				echo
				
				case $c in
					
					D|d) skipdisk=1; break ;;
					
					P|p) break ;;
					
					Q|q) exit ;;
					
				esac
				
			fi
			
		done
		
	else
		
		if [[ $skipdisk = 1 ]] && [[ $newdisk = 0 ]]; then return; fi
		err=0
		skipdisk=0
		newdisk=0
		
		while true; do
			
			echo -n "${1##*/} -> $2: "
			
			if mountpoint > /dev/null 2>&1 $2; then
				echo "(already mounted)"
				break;
			fi
			
			if [[ $testmode == 0 ]]; then echo echo -n "Mounting"; fi
			
			if [ ! -d $2 ]; then
				echo -n " (creating dir $2"
				cmd="mkdir -p $2 2>&1"
				if [[ ! $testmode ]]; then
					output="$(eval $cmd)"
					err=$?
				else
					echo -n " {would run: $cmd}"
				fi
				echo -n ') '
			fi
			
			if [[ $err = 0 ]]; then
				
				if [[ $testmode == 0 ]]; then echo -n '... '; fi
				
				cmd="mount $@"
				
				if [[ ! $testmode ]]; then
					output="$(${cmd} 2>&1)"
					err=$?
				else
					echo "{would run: ${cmd}} "
				fi
				
			else
				
				echo
				
			fi
			
			if [[ $err = 0 ]]; then
				
				if [[ ! $testmode ]]; then echo "[ok]"; fi
				return
				
			else
				
				echo -e "\n== mount error =="
				
				echo -n "${output}"
				
				echo -e "\n=================\n"
				
				c=X;
				while [[ ! $c =~ (R|r|P|p|D|d|Q|q) ]]; do read -sn1 -p"[R]etry/Skip [P]artition/Skip [D]isk/[Q]uit? " c; echo $c; done
				
				echo
				
				case $c in
					
					D|d) skipdisk=1; break ;;
					
					P|p) break ;;
					
					Q|q) exit ;;
					
				esac
				
			fi
			
			
		done
		
	fi
	
}

if [[ $1 = "u" ]]; then mode=1; else mode=0; fi

if [[ $1 = "t" ]]; then testmode=1; fi

scripts=(${scriptdir}/*.mount.sh)

for ((i = 0; i < ${#scripts[@]}; i++)); do
	
	newdisk=1
	. ${scripts[$i]}
	
	if (($i < ${#scripts[@]} - 1)); then echo; fi
	
done

echo -ne "\e[?25h"

Hopefully someone else finds this helpful. I am aware of udev/automount; that was overkill, since the disks are always installed, and I don't need a system whose focus is on-the-fly detection of newly inserted media of whatever kind.

big_smile

-i336

Last edited by i336 (2011-11-10 04:42:08)

Offline

#2 2012-09-29 08:34:06

rj.AMDphreak
Member
From: Rolla, MO
Registered: 2010-02-12
Posts: 20

Re: A semiautomatic alternative to /etc/fstab

Thanks. I might use it soon...

Does it automatically make folders named after the volume labels? And does it handle the conversion of spaces and non-alphanumeric characters to octal codes?

I could read the script but it would be faster for everyone reading, if you leave the answer as a reply.

I also think that there should be some major work done on modernizing the fstab, either by replacing it with a better implementation of file system mounting or changing the file structure and adding in better handling of non-alphanumerics. I don't want to have to look up a stupid octal table every time I type in my labels.

Offline

Board footer

Powered by FluxBB