#!/bin/sh

set -eu
export LC_ALL=C

SELF=${0##*/}
SWAPFILE=/swapfile
SWAPFILE_CREATED=0
SWAPFILE_ACTIVE=0
FSTAB_TOUCHED=0
FSTAB_BACKUP=/etc/fstab.swapfile.bak
FSTAB_TMP=

usage() {
    cat <<-EOF
	Utilisation :
	  sudo $SELF [TAILLE]

	Crée $SWAPFILE, l'initialise comme espace d'échange, ajoute son entrée dans
	/etc/fstab et l'active immédiatement lorsque le système n'est pas un chroot.

	La taille par défaut est 4G. Exemples de tailles : 512M, 2G, 8G.

	Le script est limité aux systèmes de fichiers racine ext3 et ext4. Il refuse
	d'écraser un fichier $SWAPFILE existant.
	EOF
}

die() {
    printf 'Erreur : %s\n' "$*" >&2
    exit 1
}

require_commands() {
    for required_command in \
        awk cat chmod cp df fallocate findmnt grep id mkswap mktemp mv numfmt \
        rm stat swapoff swapon
    do
        command -v "$required_command" >/dev/null 2>&1 || \
            die "commande requise absente : $required_command"
    done
}

format_bytes() {
    numfmt --to=iec-i --suffix=B "$1"
}

in_chroot() {
    [ -e /proc/1/root/. ] || return 2
    ic_root=$(stat -Lc '%d:%i' /) || return 2
    ic_init_root=$(stat -Lc '%d:%i' /proc/1/root/.) || return 2
    [ "$ic_root" != "$ic_init_root" ]
}

update_fstab() {
    uf_fstab=/etc/fstab
    FSTAB_TMP=$(mktemp /etc/.fstab.swapfile.XXXXXX)

    [ -f "$uf_fstab" ] || : >"$uf_fstab"
    cp -p -- "$uf_fstab" "$FSTAB_BACKUP"

    awk -v swapfile="$SWAPFILE" '
        BEGIN { written = 0 }
        {
            original = $0
            line = $0
            sub(/^[[:space:]]*/, "", line)
            sub(/^#[[:space:]]*/, "", line)
            count = split(line, fields, /[[:space:]]+/)

            if (count >= 3 && fields[1] == swapfile && fields[3] == "swap") {
                if (!written) {
                    print swapfile " none swap sw 0 0"
                    written = 1
                }
                next
            }
            print original
        }
        END {
            if (!written)
                print swapfile " none swap sw 0 0"
        }
    ' "$uf_fstab" >"$FSTAB_TMP"

    FSTAB_TOUCHED=1
    cat "$FSTAB_TMP" >"$uf_fstab"
    rm -f -- "$FSTAB_TMP"
    FSTAB_TMP=
}

cleanup_on_exit() {
    coe_status=$?
    trap - 0 INT TERM

    if [ "$coe_status" -ne 0 ]; then
        if [ "$FSTAB_TOUCHED" -eq 1 ] && [ -f "$FSTAB_BACKUP" ]; then
            cp -p -- "$FSTAB_BACKUP" /etc/fstab || :
            printf '/etc/fstab a été restauré depuis sa sauvegarde.\n' >&2
        fi
        if [ "$SWAPFILE_ACTIVE" -eq 1 ]; then
            swapoff "$SWAPFILE" || :
        fi
        if [ "$SWAPFILE_CREATED" -eq 1 ]; then
            rm -f -- "$SWAPFILE"
            printf 'Le fichier incomplet %s a été supprimé.\n' "$SWAPFILE" >&2
        fi
        if [ -n "$FSTAB_TMP" ]; then
            rm -f -- "$FSTAB_TMP"
        fi
    fi
    exit "$coe_status"
}

case "${1:-}" in
    -h|--help|help)
        usage
        exit 0
        ;;
esac

[ "$#" -le 1 ] || {
    usage >&2
    exit 2
}

[ "$(id -u)" -eq 0 ] || die "ce script doit être lancé avec sudo ou en root"
require_commands

SIZE_TEXT=${1:-4G}
case "$SIZE_TEXT" in
    *[!0-9KMGTPEkmgtpe]*|'')
        die "taille invalide : $SIZE_TEXT (exemples : 512M, 2G, 8G)"
        ;;
esac

SIZE_BYTES=$(numfmt --from=iec "$SIZE_TEXT" 2>/dev/null) || \
    die "taille invalide : $SIZE_TEXT"
[ "$SIZE_BYTES" -ge 40960 ] || die "la taille doit être d'au moins 40 KiB"

ROOT_FSTYPE=$(findmnt -n -o FSTYPE -M /) || \
    die "impossible d'identifier le système de fichiers racine"
case "$ROOT_FSTYPE" in
    ext3|ext4) ;;
    *) die "système de fichiers racine non pris en charge : $ROOT_FSTYPE" ;;
esac

[ ! -e "$SWAPFILE" ] && [ ! -L "$SWAPFILE" ] || \
    die "$SWAPFILE existe déjà ; aucune modification effectuée"

if [ -r /proc/swaps ] && \
   awk -v target="$SWAPFILE" 'NR > 1 && $1 == target { found = 1 } END { exit !found }' /proc/swaps; then
    die "$SWAPFILE est déjà actif comme swap"
fi

AVAILABLE_BYTES=$(df -B1 -P / | awk 'NR == 2 { print $4 }')
[ -n "$AVAILABLE_BYTES" ] || die "impossible de déterminer l'espace disponible"
if [ "$SIZE_BYTES" -gt "$AVAILABLE_BYTES" ]; then
    die "espace insuffisant : $(format_bytes "$AVAILABLE_BYTES") disponibles, $(format_bytes "$SIZE_BYTES") demandés"
fi

printf 'Fichier             : %s\n' "$SWAPFILE"
printf 'Taille demandée     : %s\n' "$(format_bytes "$SIZE_BYTES")"
printf 'Espace disponible   : %s\n' "$(format_bytes "$AVAILABLE_BYTES")"
printf 'Espace restant      : %s\n' "$(format_bytes "$((AVAILABLE_BYTES - SIZE_BYTES))")"

trap cleanup_on_exit 0
trap 'exit 130' INT
trap 'exit 143' TERM

printf '\nCréation de %s...\n' "$SWAPFILE"
SWAPFILE_CREATED=1
fallocate -l "$SIZE_BYTES" -- "$SWAPFILE"
chmod 600 -- "$SWAPFILE"

printf '\nInitialisation du swap...\n'
mkswap "$SWAPFILE"

CHROOT_STATE=unknown
if in_chroot; then
    CHROOT_STATE=yes
else
    ic_status=$?
    [ "$ic_status" -eq 1 ] && CHROOT_STATE=no
fi

if [ "$CHROOT_STATE" = no ]; then
    printf '\nActivation du swap...\n'
    swapon "$SWAPFILE"
    SWAPFILE_ACTIVE=1
fi

printf '\nMise à jour de /etc/fstab...\n'
update_fstab

SWAPFILE_CREATED=0
trap - 0 INT TERM

if [ "$CHROOT_STATE" = no ]; then
    printf '\nSwap créé et activé :\n'
    swapon --show
elif [ "$CHROOT_STATE" = yes ]; then
    printf '\nSwap créé dans un chroot ; il sera activé au prochain démarrage du système.\n'
else
    printf '\nSwap créé, mais le contexte chroot n a pas pu être déterminé.\n'
    printf 'Par sécurité, il n a pas été activé. Il le sera au prochain démarrage.\n'
fi

printf 'Sauvegarde de fstab : /etc/fstab.swapfile.bak\n'
