// SPDX-License-Identifier: GPL-3.0-or-later

#include <errno.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

#include "execute.h"
#include "parse.h"
#include "perms.h"
#include "util.h"
#include "offline-password.h"

#define VERSION "1.4.2"



static const char *const system_config_dirs[] = {
  "/etc/tmpfiles.d",
  "/run/tmpfiles.d",
  "/usr/local/lib/tmpfiles.d",
  "/usr/lib/tmpfiles.d",
  NULL,
};

static void build_user_config_dirs(const char ***dirs, size_t *n) {
  const char *config_home = getenv("XDG_CONFIG_HOME");
  const char *runtime_dir = getenv("XDG_RUNTIME_DIR");
  const char *data_home = getenv("XDG_DATA_HOME");
  const char *home = getenv("HOME");

  *dirs = NULL;
  *n = 0;

#define APPEND(d)							\
  do {									\
    const char **tmp = realloc(*dirs, (*n + 1) * sizeof(const char *));	\
    if (!tmp)								\
      return;								\
    *dirs = tmp;							\
    (*dirs)[(*n)++] = (d);						\
  } while (0)

  static char buf1[4096];
  if (config_home && *config_home) {
    snprintf(buf1, sizeof(buf1), "%s/user-tmpfiles.d", config_home);
    APPEND(buf1);
  } else if (home && *home) {
    snprintf(buf1, sizeof(buf1), "%s/.config/user-tmpfiles.d", home);
    APPEND(buf1);
  }

  static char buf2[4096];
  if (runtime_dir && *runtime_dir) {
    snprintf(buf2, sizeof(buf2), "%s/user-tmpfiles.d", runtime_dir);
    APPEND(buf2);
  }

  static char buf3[4096];
  if (data_home && *data_home) {
    snprintf(buf3, sizeof(buf3), "%s/user-tmpfiles.d", data_home);
    APPEND(buf3);
  } else if (home && *home) {
    snprintf(buf3, sizeof(buf3), "%s/.local/share/user-tmpfiles.d", home);
    APPEND(buf3);
  }

  APPEND("/usr/local/lib/user-tmpfiles.d");
  APPEND("/usr/lib/user-tmpfiles.d");

#undef APPEND
}

static void usage(void) {
  printf("Usage: seedfiles [OPTIONS...] [CONFIG_FILE...]\n"
         "\n"
         "Manage temporary files and directories.\n"
         "\n"
         "Commands:\n"
         "      --create             Create files and directories\n"
         "      --clean              Clean up old files\n"
         "      --remove             Remove files and directories\n"
         "\n"
         "Options:\n"
         "      --boot               Also process entries with the '!' "
         "modifier\n"
         "      --dry-run            Only print what would be done\n"
         "      --prefix=PATH        Only process entries with matching "
         "prefix\n"
         "      --exclude-prefix=PATH\n"
         "                           Exclude entries with matching prefix\n"
         "      --user               Run in user mode\n"
		 "      --root               Operate on an alternative filesystem root\n"
         "      --verbose            Enable verbose logging\n"
         "  -h, --help               Show this help\n"
         "      --version            Show version\n");
}

enum {
  OPT_CREATE = 0x100,
  OPT_CLEAN,
  OPT_REMOVE,
  OPT_BOOT,
  OPT_DRY_RUN,
  OPT_PREFIX,
  OPT_EXCLUDE_PREFIX,
  OPT_USER,
  OPT_ROOT,
  OPT_VERBOSE,
  OPT_VERSION,
};

static const struct option long_options[] = {
  {"create", no_argument, NULL, OPT_CREATE},
  {"clean", no_argument, NULL, OPT_CLEAN},
  {"remove", no_argument, NULL, OPT_REMOVE},
  {"boot", no_argument, NULL, OPT_BOOT},
  {"dry-run", no_argument, NULL, OPT_DRY_RUN},
  {"prefix", required_argument, NULL, OPT_PREFIX},
  {"exclude-prefix", required_argument, NULL, OPT_EXCLUDE_PREFIX},
  {"user", no_argument, NULL, OPT_USER},
  {"root",  required_argument, NULL, OPT_ROOT},
  {"verbose", no_argument, NULL, OPT_VERBOSE},
  {"help", no_argument, NULL, 'h'},
  {"version", no_argument, NULL, OPT_VERSION},
  {NULL, 0, NULL, 0},
};

static int add_prefix(char ***list, size_t *n, const char *prefix) {
  char **tmp = realloc(*list, (*n + 1) * sizeof(char *));
  if (!tmp)
    return -ENOMEM;
  *list = tmp;
  (*list)[*n] = strdup(prefix);
  if (!(*list)[*n])
    return -ENOMEM;
  (*n)++;
  return 0;
}

int main(int argc, char *argv[]) {
  OperationMask ops = 0;
  int c;

  while ((c = getopt_long(argc, argv, "+h", long_options, NULL)) != -1) {
    switch (c) {
    case OPT_CREATE:
      ops |= OPERATION_CREATE;
      break;
    case OPT_CLEAN:
      ops |= OPERATION_CLEAN;
      break;
    case OPT_REMOVE:
      ops |= OPERATION_REMOVE;
      break;
    case OPT_BOOT:
      arg_boot = true;
      break;
    case OPT_DRY_RUN:
      arg_dry_run = true;
      break;
    case OPT_PREFIX:
      if (add_prefix(&arg_include_prefixes, &n_include_prefixes, optarg) < 0) {
        log_error("Out of memory.");
        return EXIT_FAILURE;
      }
      break;
    case OPT_EXCLUDE_PREFIX:
      if (add_prefix(&arg_exclude_prefixes, &n_exclude_prefixes, optarg) < 0) {
        log_error("Out of memory.");
        return EXIT_FAILURE;
      }
      break;
    case OPT_USER:
      arg_user_mode = true;
      break;
	case OPT_ROOT:
		arg_root = optarg;
		break;
    case OPT_VERBOSE:
      log_level = LOG_DEBUG;
      break;
    case 'h':
      usage();
      return EXIT_SUCCESS;
    case OPT_VERSION:
      printf("seedfiles %s\n", VERSION);
      return EXIT_SUCCESS;
    default:
      return EXIT_FAILURE;
    }
  }

  if (ops == 0) {
    log_error("No operation specified. Use --create, --clean, or --remove.");
    return EXIT_FAILURE;
  }

  if (arg_user_mode && arg_root) {
	  log_error("Option --user and --root may not be specified at the same time.");
	  return EXIT_FAILURE;
  }

  struct stat st;
  if (stat("/proc/self", &st) < 0) {
    log_error("/proc/ is not mounted, but is required for operation.");
    return EXIT_FAILURE;
  }

  umask(0022);

  const char *const *config_dirs;
  const char **user_dirs = NULL;
  size_t n_user_dirs = 0;

  if (arg_user_mode) {
    build_user_config_dirs(&user_dirs, &n_user_dirs);
    const char **tmp = realloc(user_dirs, (n_user_dirs + 1) * sizeof(char *));
    if (!tmp) {
      free(user_dirs);
      log_error("Out of memory.");
      return EXIT_FAILURE;
    }
    user_dirs = tmp;
    user_dirs[n_user_dirs] = NULL;
    config_dirs = user_dirs;
  } else {
    config_dirs = system_config_dirs;
  }

  Context ctx;
  context_init(&ctx);

  int r = parse_config_files(&ctx, config_dirs, argc - optind, argv + optind);

  if (r < 0)
    log_warning("Errors occurred while parsing configuration files.");

  context_link_parents(&ctx);

  int ret = execute_all(&ctx, ops);

  context_done(&ctx);
  free(user_dirs);
  offline_passwd_cache_clear();

  for (size_t i = 0; i < n_include_prefixes; i++)
    free(arg_include_prefixes[i]);
  free(arg_include_prefixes);

  for (size_t i = 0; i < n_exclude_prefixes; i++)
    free(arg_exclude_prefixes[i]);
  free(arg_exclude_prefixes);

  if (ret < 0)
    return EXIT_FAILURE;

  return EXIT_SUCCESS;
}
