#!/system/bin/sh # fstab.sh # Reads an fstab-style config and performs mounts/swapon without touching /etc/fstab. # # Usage: # ./fstab.sh [--su-options ''] [--log-file ] path/to/fstab.conf # Examples: # ./fstab.sh --su-options '--mount-master --test' /path/to/fstab.conf # LOG_FILE=/sdcard/fstab.log ./fstab.sh /path/to/fstab.conf set -u # ---- defaults --------------------------------------------------------------- su_options="--mount-master" # kann per --su-options überschrieben werden : "${LOG_FILE:=}" # optional via Environment # ---- helpers --------------------------------------------------------------- usage() { cat >&2 <'] [--log-file ] path/to/fstab.conf Options: --su-options String with options for su (default: '--mount-master') --log-file Path to log file Example: $0 --su-options '--mount-master --test' /path/to/fstab.conf EOF } # ---- parse args ------------------------------------------------------------ args="$*" while [ "$#" -gt 0 ]; do case "$1" in --su-options) [ "$#" -ge 2 ] || { echo "ERROR: --su-options needs exactly 1 argument." >&2 usage exit 2 } su_options="$2" shift 2 ;; --log-file) [ "$#" -ge 2 ] || { echo "ERROR: --log-file needs exactly 1 argument." >&2 usage exit 2 } LOG_FILE="$2" shift 2 ;; --help | -h) usage exit 0 ;; --) shift break ;; -*) echo "Unknown option: $1" >&2 usage exit 2 ;; *) break ;; esac done FSTAB_FILE="${1:-}" if [ -z "$FSTAB_FILE" ]; then usage exit 2 fi if [ ! -r "$FSTAB_FILE" ]; then echo "File '$FSTAB_FILE' not readable." >>"${LOG_FILE:-/dev/null}" 2>&1 echo "File '$FSTAB_FILE' not readable." >&2 exit 2 fi # Ensure 'su' exists if ! command -v su >/dev/null 2>&1; then echo "ERROR: 'su' not found. Cannot perform privileged operations." >>"${LOG_FILE:-/dev/null}" 2>&1 echo "ERROR: 'su' not found. Cannot perform privileged operations." >&2 exit 2 fi error_count=0 line_no=0 # --- logging helpers (both console and file) --- log() { if [ -n "${LOG_FILE:-}" ]; then echo "$*" | tee -a "$LOG_FILE"; else echo "$*"; fi; } warn() { if [ -n "${LOG_FILE:-}" ]; then echo "WARN: $*" | tee -a "$LOG_FILE"; else echo "WARN: $*"; fi; } err() { if [ -n "${LOG_FILE:-}" ]; then echo "ERROR: $*" | tee -a "$LOG_FILE"; else echo "ERROR: $*"; fi error_count=$(expr $error_count + 1) } # --- safe single-quoting for building a command string for su -c --- shell_quote() { # Output a single-quoted representation of $1, handling internal single quotes # 'abc'd' -> 'abc'"'"'d' printf "'%s'" "$(printf "%s" "$1" | sed "s/'/'\"'\"'/g")" } # --- run a command as root via su $su_options -c "..." preserving args safely --- # Usage: run_root CMD ARG1 ARG2 ... run_root() { cmd="$1" shift # Build a single string: CMD 'ARG1' 'ARG2' ... cmdline=$(shell_quote "$cmd") for a in "$@"; do cmdline="$cmdline $(shell_quote "$a")" done log "RUN (as root): $cmd $*" # Execute # shellcheck disable=SC2086 su $su_options -c "$cmdline" rc=$? [ $rc -ne 0 ] && err "Command failed (exit $rc): $cmd $*" return $rc } # --- check if a mountpoint is already mounted (no root needed) --- is_mounted() { mp="$1" if [ -r /proc/mounts ]; then grep -qs " $mp " /proc/mounts return $? fi return 1 } # --- check if swap device is already active (no root needed) --- is_swap_active() { spec="$1" base="$(basename "$spec")" if [ -r /proc/swaps ]; then awk 'NR>1{print $1}' /proc/swaps | grep -Fxq "$spec" && return 0 awk 'NR>1{n=$1; sub(".*/","",n); print n}' /proc/swaps | grep -Fxq "$base" && return 0 fi return 1 } log "$0 $args" # --- process each fstab line --- while IFS= read -r rawline || [ -n "$rawline" ]; do line_no=$(expr $line_no + 1) line=$(echo "$rawline" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') case "$line" in "" | \#*) continue ;; esac # spec file vfstype mntops dump pass (default 0 for last two if missing) set -- $(echo "$line" | awk '{print $1, $2, $3, $4, ($5? $5:0), ($6? $6:0)}') fs_spec=$1 fs_file=$2 fs_vfstype=$3 fs_mntops=$4 fs_freq=$5 fs_pass=$6 if [ -z "$fs_spec" ] || [ -z "$fs_file" ] || [ -z "$fs_vfstype" ]; then warn "Line $line_no: Invalid entry, skipped." continue fi echo "$fs_mntops" | grep -qw noauto && { log "Line $line_no: 'noauto' found, skipping $fs_spec -> $fs_file." continue } # --- swap handling --- if [ "$fs_vfstype" = "swap" ]; then if is_swap_active "$fs_spec"; then log "Line $line_no: Swap '$fs_spec' already active — skipped." continue fi if command -v swapon >/dev/null 2>&1; then log "Line $line_no: Activating swap: $fs_spec" if [ "$fs_mntops" = "sw" ]; then run_root swapon "$fs_spec" || true elif [ "$fs_mntops" = "-" ]; then log "Mount option '-' on swap, skipping." else run_root swapon -o "$fs_mntops" "$fs_spec" || true fi else err "Line $line_no: swapon not available." fi continue fi # --- bindfs filesystem mounts --- if [ "$fs_vfstype" = "bindfs" ]; then if is_mounted "$fs_file"; then log "Line $line_no: $fs_file already mounted — skipped." continue fi if ! command -v bindfs >/dev/null 2>&1; then err "Line $line_no: bindfs not available but requested (vfstype=bindfs)." continue fi if [ ! -e "$fs_file" ]; then log "Line $line_no: Creating mountpoint $fs_file" run_root mkdir -p "$fs_file" || { err "Line $line_no: mkdir failed for $fs_file" continue } fi if [ "$fs_mntops" = "-" ]; then log "Line $line_no: bindfs mount: $fs_spec -> $fs_file" run_root bindfs "$fs_spec" "$fs_file" || true else # bindfs versteht FUSE-Optionen via -o "" log "Line $line_no: bindfs mount: $fs_spec -> $fs_file (opts=$fs_mntops)" run_root bindfs -o "$fs_mntops" "$fs_spec" "$fs_file" || true fi continue fi # --- bind / rbind mounts --- echo "$fs_mntops" | grep -q bind && { if is_mounted("$fs_file"); then log "Line $line_no: $fs_file already mounted — skipped." continue fi if [ ! -e "$fs_file" ]; then log "Line $line_no: Creating mountpoint $fs_file" run_root mkdir -p "$fs_file" || { err "Line $line_no: mkdir failed for $fs_file" continue } fi if echo "$fs_mntops" | grep -qw rbind; then log "Line $line_no: rbind mount: $fs_spec -> $fs_file" run_root mount --rbind "$fs_spec" "$fs_file" || true else log "Line $line_no: bind mount: $fs_spec -> $fs_file" run_root mount --bind "$fs_spec" "$fs_file" || true fi continue } # --- regular filesystem mounts --- if is_mounted "$fs_file"; then log "Line $line_no: $fs_file already mounted — skipped." continue fi if [ ! -e "$fs_file" ]; then log "Line $line_no: Creating mountpoint $fs_file" run_root mkdir -p "$fs_file" || { err "Line $line_no: mkdir failed" continue } fi if [ "$fs_mntops" = "-" ]; then log "Line $line_no: Mounting $fs_spec -> $fs_file (type=$fs_vfstype)" run_root mount -t "$fs_vfstype" "$fs_spec" "$fs_file" || true else log "Line $line_no: Mounting $fs_spec -> $fs_file (type=$fs_vfstype, opts=$fs_mntops)" run_root mount -t "$fs_vfstype" -o "$fs_mntops" "$fs_spec" "$fs_file" || true fi done <"$FSTAB_FILE" if [ $error_count -gt 0 ]; then echo "Done: $error_count errors occurred." | { [ -n "${LOG_FILE:-}" ] && tee -a "$LOG_FILE" || cat; } exit 1 else echo "Done: all entries processed successfully." | { [ -n "${LOG_FILE:-}" ] && tee -a "$LOG_FILE" || cat; } exit 0 fi