#!/bin/sh
# fs-ensure - Ensure a file or directory exists with the correct ownership and permissions.
#
# Usage: fs-ensure [-p] <dir|file> <path> <class>
#
# Options:
#   -p     Create missing parent directories (owned by root:root 0755)
#
# Arguments:
#   type   - Resource type: "dir" or "file"
#   path   - Absolute path to create/verify (must be under the active CSL_WS)
#   class  - Permission class (see below)
#
# Workspace/runtime identity is loaded from:
#   /etc/csl/csl.env
#   /etc/csl/runtime.env
# Static policy is loaded from:
#   /etc/csl/fs-ensure.conf
#
# Examples:
#   fs-ensure dir  /var/lib/csl/instances/prod/config                    control_dir
#   fs-ensure dir  /var/lib/csl/instances/prod/logs                      runtime_dir
#   fs-ensure -p   dir /var/lib/csl/instances/prod/installer/releases    installer_dir
#   fs-ensure file /var/lib/csl/instances/prod/.env                      runtime_file

set -eu

CONFIG="/etc/csl/fs-ensure.conf"
COMMON_ENV="/etc/csl/csl.env"
RUNTIME_ENV="/etc/csl/runtime.env"

err() { echo "fs-ensure: $*" >&2; exit 1; }

[ -r "$COMMON_ENV" ] || err "missing config $COMMON_ENV"
[ -r "$RUNTIME_ENV" ] || err "missing config $RUNTIME_ENV"
[ -r "$CONFIG" ] || err "missing config $CONFIG"

. "$COMMON_ENV"
. "$RUNTIME_ENV"

: "${CSL_HOME:=/var/lib/csl}"
: "${CSL_INSTANCE:=prod}"
: "${CSL_WS:=${CSL_HOME}/instances/${CSL_INSTANCE}}"
: "${CSL_RUNTIME_USER:?missing CSL_RUNTIME_USER in $RUNTIME_ENV}"
: "${CSL_RUNTIME_GROUP:?missing CSL_RUNTIME_GROUP in $RUNTIME_ENV}"

BASE_DIR="$CSL_WS"
BASE_DIR_REGEX="$(printf '%s\n' "$BASE_DIR" | awk '{ gsub(/[][(){}.^$*+?|\\]/, "\\\\&"); gsub(/\//, "\\/"); print }')"
CONTROL_USER="csl"
CONTROL_GROUP="csl"
RUNTIME_USER="$CSL_RUNTIME_USER"
RUNTIME_GROUP="$CSL_RUNTIME_GROUP"
IPC_GROUP="${CSL_SOCKET_GROUP:-cslipc}"

. "$CONFIG"

# Parse optional -p flag
CREATE_PARENTS=0
if [ "${1:-}" = "-p" ]; then
  CREATE_PARENTS=1
  shift
fi

[ $# -eq 3 ] || err "Usage: fs-ensure [-p] <dir|file> <path> <class>"

TYPE="$1"
TARGET="$2"
CLASS="$3"

case "$TYPE" in
  dir|file) ;;
  *) err "invalid type" ;;
esac

# Validate base path
case "$TARGET" in
  "$BASE_DIR"/*) ;;
  *) err "path outside BASE_DIR" ;;
esac

# Reject traversal
case "$TARGET" in
  *"/../"*|*"//"*|*"./"*) err "invalid path" ;;
esac

# Resolve class using eval (POSIX replacement for bash ${!var})
eval "OWNER=\${CLASS_${CLASS}_OWNER:-}"
eval "GROUP=\${CLASS_${CLASS}_GROUP:-}"
eval "MODE=\${CLASS_${CLASS}_MODE:-}"

[ -n "$OWNER" ] || err "unknown class $CLASS (no OWNER)"
[ -n "$GROUP" ] || err "unknown class $CLASS (no GROUP)"
[ -n "$MODE"  ] || err "unknown class $CLASS (no MODE)"

# Check allowed patterns
match_allowed() {
  _type="$1"
  _target="$2"
  _TYPE="$(echo "$_type" | tr '[:lower:]' '[:upper:]')"
  _i=1

  while true; do
    _var="ALLOW_${_TYPE}_PATTERN_${_i}"
    eval "_pattern=\${$_var:-}"
    [ -z "$_pattern" ] && break

    if echo "$_target" | grep -qE "$_pattern"; then
      return 0
    fi
    _i=$((_i + 1))
  done

  return 1
}

match_builtin_allowed() {
  _type="$1"
  _target="$2"

  case "$_type:$_target" in
    file:"$BASE_DIR"/config/*|file:"$BASE_DIR"/runtime_config/*|file:"$BASE_DIR"/data/*)
      return 0
      ;;
  esac

  return 1
}

match_allowed "$TYPE" "$TARGET" || match_builtin_allowed "$TYPE" "$TARGET" || err "path not allowed: $TARGET"

# Reject symlinks
if [ -L "$TARGET" ]; then
  err "symlink not allowed: $TARGET"
fi

# Handle parent directories
PARENT="$(dirname "$TARGET")"
if [ ! -d "$PARENT" ]; then
  if [ "$CREATE_PARENTS" = "1" ]; then
    # Create parents with safe default permissions (root:root 0755)
    # Only create directories that are within BASE_DIR
    case "$PARENT" in
      "$BASE_DIR"/*) ;;
      *) err "parent path outside BASE_DIR: $PARENT" ;;
    esac
    mkdir -p "$PARENT"
    # Note: parents are created as root:root 0755 by default.
    # Use explicit fs-ensure calls per directory for custom ownership.
  else
    err "parent does not exist: $PARENT (use -p to create)"
  fi
fi

# Apply
if [ "$TYPE" = "dir" ]; then
  install -d -o "$OWNER" -g "$GROUP" -m "$MODE" "$TARGET"
else
  if [ ! -e "$TARGET" ]; then
    install -o "$OWNER" -g "$GROUP" -m "$MODE" /dev/null "$TARGET"
  else
    chown "$OWNER:$GROUP" "$TARGET"
    chmod "$MODE" "$TARGET"
  fi
fi

# Verify (normalize modes to strip leading zeros before comparing)
A_OWNER="$(stat -c '%U' "$TARGET")"
A_GROUP="$(stat -c '%G' "$TARGET")"
A_MODE="$(stat -c '%a' "$TARGET")"

A_MODE_NORM="$(echo "$A_MODE" | sed 's/^0*//')"
MODE_NORM="$(echo "$MODE"   | sed 's/^0*//')"

[ "$A_OWNER"     = "$OWNER"     ] || err "owner mismatch (got $A_OWNER, want $OWNER)"
[ "$A_GROUP"     = "$GROUP"     ] || err "group mismatch (got $A_GROUP, want $GROUP)"
[ "$A_MODE_NORM" = "$MODE_NORM" ] || err "mode mismatch (got $A_MODE, want $MODE)"
