/* SPDX-License-Identifier: MIT */
/* SPDX-FileCopyrightText: (c) Copyright 2024 Andrew Bower <andrew@bower.uk> */

/* xchpst: eXtended Change Process State
 * A tool that is backwards compatible with chpst(8) from runit(8),
 * offering additional options to harden process with namespace isolation
 * and more. */

#include <errno.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/dir.h>
#include <sys/mount.h>
#include <sys/stat.h>

#include "xchpst.h"
#include "options.h"
#include "mount.h"
#include "compat/mount_setattr.h"

int special_mount(char *path, char *fs, char *desc, char *options) {
  const char *op = "mkdirat";
  int rc;

  rc = mkdirat(AT_FDCWD, path, 0777);
  if (rc == 0 || errno == EEXIST) {
    umount2(path, MNT_DETACH);
    op = "mount";
    rc = mount(NULL, path, fs,
               MS_NODEV | MS_NOEXEC | MS_NOSUID, options);
    if (rc == -1)
      fprintf(stderr, "error mounting special: %s (%s) to %s, %s\n",
              path, fs, desc, strerror(errno));
  } else
    if (rc == -1)
      fprintf(stderr, "error creating %s mount: %s: %s, %s\n",
              desc, path, op, strerror(errno));
  return rc;
}

int private_mount(char *path) {
  return special_mount(path, "tmpfs", "private", "mode=0755");
}

int remount_ro(const char *path) {
  struct stat statbuf;
  int rc;
  struct mount_attr attr = {
    .attr_set = MOUNT_ATTR_RDONLY,
    .propagation = MS_PRIVATE,
  };

  if ((rc = stat(path, &statbuf)) == -1 && errno == ENOENT)
    return ENOENT;

  /* Try remount first, in case we don't need a bind mount. */
  rc = mount_setattr(AT_FDCWD, path, AT_RECURSIVE, &attr, sizeof attr);
  if (rc == -1) {
    /* we hope errno == EINVAL but no need to check as will find out later */
    rc = mount(path, path, NULL,
               MS_REC | MS_BIND | MS_PRIVATE, NULL);
    if (rc == -1)
      fprintf(stderr, "error recursive bind mounting %s: %s\n", path, strerror(errno));

    rc = mount_setattr(AT_FDCWD, path, AT_RECURSIVE, &attr, sizeof attr);
    if (rc == -1) {
      fprintf(stderr, "error remounting bind mount for %s read-only: %s\n", path, strerror(errno));
    } else if (is_verbose()) {
      fprintf(stderr, "was able to bind mount %s read-only\n", path);
    }
  } else if (is_verbose()) {
    fprintf(stderr, "was able to remount %s read-only\n", path);
  }
  return rc ? -1 : 0;
}

int remount_sys_ro(void) {
  int rc;

  return (((rc = remount_ro("/usr")) && rc != ENOENT) ||
         ((rc = remount_ro("/boot/efi")) && rc != ENOENT) ||
         ((rc = remount_ro("/boot")) && rc != ENOENT)) ? -1 : 0;
}
