#!/bin/bash
set -e
# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
# ex: ts=8 sw=4 sts=4 et filetype=sh

# Safe to run also after an official first boot and we do this now
# for consistency regardless if first_boot= is passed to create missing
# files if needed and make it easier to debug. In fact, it was required
# to use first_boot=1 with PXE to get a proper rootfs which is strange.

function usrbin() {
  local cmd="$1"
  shift
  LD_LIBRARY_PATH=/sysusr/usr/lib64 /sysusr/usr/bin/"${cmd}" "$@"
}

function walksysroot() {
  local topdir="$1"
  shift
  local action="$1"
  shift
  while IFS= read -r -d '' entry; do
    "${action}" "${entry}" || true
  done < <(chroot /sysroot /usr/bin/find "${topdir}" -xdev -depth "$@" -print0)
  # Do a chroot to be able to pass "-regex /etc/something" without having to prepend /sysroot
  # in the regex. Also do the print0 as last action, after filtering.
  true # Do not carry any last condition evaluation over as return code
}

# /etc/machine-id after a new image is created:
COREOS_BLANK_MACHINE_ID="42000000000000000000000000000042"
MACHINE_ID_FILE="/sysroot/etc/machine-id"

# Allow to rerun the script
if usrbin mountpoint -q /sysroot/etc; then
  umount /sysroot/etc
fi

function selectiveosreset() {
  local entry="/sysroot$1"
  # Don't remove /sysroot itself
  [ "${entry}" = "/sysroot" ] && return 0
  [ "${entry}" = "/sysroot/" ] && return 0
  # Don't remove the active /usr mount point
  [ "${entry}" = "/sysroot/usr" ] && return 0
  # Not really needed because find doesn't add a trailing slash but to be safe:
  [ "${entry}" = "/sysroot/usr/" ] && return 0
  if [ -d "${entry}" ]; then
    # Try to delete dir, will fail if its contents are preserved
    usrbin rmdir "${entry}" 2>/dev/null || true
  else
    # Delete file, Report wrong paths to nonexisting files or any other errors
    # (journalctl -u initrd-setup-root) but don't hard fail the boot
    rm "${entry}" || true
  fi
  true # Do not carry any last condition evaluation over as return code
}

# Do the selective OS reset as prepared by flatcar-reset
if [ -s /sysroot/selective-os-reset ]; then
  walksysroot / selectiveosreset -regextype egrep -not -regex "$(cat /sysroot/selective-os-reset)"
  rm -f /sysroot/selective-os-reset
  # Always remove the machine-id file because otherwise it's not a first boot.
  # The previous value can be preserved through the systemd.machine_id=
  # kernel parameter.
  rm -f /sysroot/etc/machine-id
fi

# Remove any user-created whiteouts for files that have a tmpfiles
# rule which normally would recreate them (we use the lowerdir for that).
while IFS="" read -r entry ; do
  entry="/sysroot${entry}"
  # The -c check for character devs also guards against empty strings and nonexisting files
  # The stat command prints the major and minor device type in decimal
  if [ -c "${entry}" ] && [ "$(stat --printf='%Hr %Lr\n' "${entry}")" = "0 0" ]; then
    rm "${entry}" || true
  fi
done < /sysroot/usr/share/flatcar/etc-no-whiteouts

# This creates the modifiable users/groups in /sysroot/etc,
# initializing the shadow database in the process. This needs to
# happen early, so systemd-tmpfiles can read the user info from
# /sysroot and Ignition can operate on these users.
for DBFILE in passwd group shadow gshadow; do
  # First, to be able to write to the files, check that the user
  # didn't somehow delete the files at which point they are special
  # character devices in the upperdir and to recreate the files we
  # need to remove them first.
  if [ -c "/sysroot/etc/${DBFILE}" ]; then
    rm -f "/sysroot/etc/${DBFILE}"
  fi
done
/usr/sbin/flatcar-tmpfiles /sysroot

# Initialize base filesystem without /etc
# Don't run all tmpfiles rules but only the essential ones
# required to let systemd do its work correctly
# (baselayout.conf must contain /var/log/journal for it to
# be created early enough, base_image_var.conf has other
# directories that packages installed). The rest will be
# done later as usual through systemd-tmpfiles-setup.service.
systemd-tmpfiles --root=/sysroot --create \
    baselayout.conf baselayout-usr.conf \
    baselayout-home.conf base_image_var.conf

# Remove our phony id. systemd will initialize this during boot.
if grep -qs "${COREOS_BLANK_MACHINE_ID}" "${MACHINE_ID_FILE}"; then
    rm "${MACHINE_ID_FILE}"
fi

function overlaycleanup() {
  local entry="/sysroot$1"
  local usrentry="/sysroot/usr/share/flatcar$1"
  # Return if we hit the /etc directory itself which we don't want to delete
  [ "${entry}" = "/sysroot/etc" ] && return 0
  # Return if there is no symlink and no file/dir for the entry in /usr/share/flatcar/etc
  # (First check -L because -e fails if the symlink target is not found which will be the case due to added /sysroot/)
  [ ! -L "${usrentry}" ] && [ ! -e "${usrentry}" ] && return 0
  # Ensure that both entries match for user, group, permissions, and type
  [ "$(stat --printf='%a %u %g %F\n' "${entry}")" != "$(stat --printf='%a %u %g %F\n' "${usrentry}")" ] && return 0
  # Also check ACLs but only if not symlink
  [ ! -L "${usrentry}" ] && [ "$(usrbin getfacl -nc "${entry}" 2>/dev/null)" != "$(usrbin getfacl -nc "${usrentry}" 2>/dev/null)" ] && return 0
  # If any parent dir is an opaque overlayfs dir (i.e. recreated by the user), we don't remove any equal contents compared to
  # the lowerdir because they won't be propagated from the lowerdir
  local parent="${entry}"
  while true; do
    parent=$(usrbin dirname "${parent}") || return 0
    if [ "${parent}" = "" ] || [ "${parent}" = "/" ] || [ "${parent}" = "." ]; then
      return 0 # Stop processing this entry on unexpected results (but continue boot)
    fi
    if [ "${parent}" = "/sysroot" ]; then
      break
    fi
    [ "$(usrbin attr -R -q -g overlay.opaque "${parent}" 2>/dev/null)" = "y" ] && return 0
  done
  # Ignore the SELinux labels because we probably would want to relabel later.
  # Xattrs are ignored (we could list them and check each non-SELinux entry for equality).
  # When checking the type, L must be checked first because d/f dereference
  if [ -L "${entry}" ]; then
    [ "$(readlink "${entry}")" = "$(readlink "${usrentry}")" ] && { rm "${entry}" || true ; }
  elif [ -d "${entry}" ]; then
    # Try to remove empty directories (fails if not empty) but skip if they are opaque overlayfs dirs
    [ "$(usrbin attr -R -q -g overlay.opaque "${entry}" 2>/dev/null)" != "y" ] && { usrbin rmdir "${entry}" 2>/dev/null || true ; }
  elif [ -f "${entry}" ]; then
    # We directly compare contents, this also covers size
    usrbin cmp -s "${entry}" "${usrentry}" && { rm "${entry}" || true ; }
  fi
  true # Even if not needed at the moment, do not carry any return code over from a previous "false && true"
}

mkdir -p /sysroot/etc
# Remove any files that haven't changed compared to the (new) overlay,
# this helps that future updates will be able to take effect.
# An opt-out flag file is used for users that want to prevent that their
# current /etc/ files get auto-updated in the future if they happen to
# be identical to what Flatcar ships and thus get cleaned up but later
# Flatcar ships an updated file with changes the user didn't want.
if [ ! -e "/sysroot/etc/.no-dup-update" ]; then
  walksysroot /etc overlaycleanup
fi

# Set up overlay mount for /etc (as long as we can't use syscfg for that)
mkdir -p /sysroot/.etc-work
mount -t overlay overlay -o lowerdir=/sysroot/usr/share/flatcar/etc,upperdir=/sysroot/etc,workdir=/sysroot/.etc-work,redirect_dir=on,metacopy=off,noatime /sysroot/etc

# PXE initrds may provide OEM. Despite OEM partition being moved to
# /oem in general, we keep checking /usr/share/oem in initrds to avoid
# breaking compatibility.
if [ -d /usr/share/oem ] && mountpoint --quiet /sysroot/oem; then
    cp -Ra /usr/share/oem/. /sysroot/oem
fi
