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

#include <fnmatch.h>

#include "clean.h"
#include "create.h"
#include "execute.h"
#include "perms.h"
#include "glob_portable.h"
#include "chase.h"

static bool should_process(const char *path) {
  if (n_include_prefixes > 0) {
    bool match = false;
    for (size_t i = 0; i < n_include_prefixes; i++) {
      if (path_startswith(path, arg_include_prefixes[i])) {
        match = true;
        break;
      }
    }
    if (!match)
      return false;
  }

  for (size_t i = 0; i < n_exclude_prefixes; i++) {
    if (path_startswith(path, arg_exclude_prefixes[i]))
      return false;
  }

  return true;
}

struct glob_apply_ctx {
  Item *item;
  int (*func)(Item *, const char *);
};

static int glob_apply_trampoline(const char *logical_path, void *userdata) {
  struct glob_apply_ctx *ctx = userdata;
  return ctx->func(ctx->item, logical_path);
}

static int glob_apply(Item *i, int (*func)(Item *, const char *)) {
  struct glob_apply_ctx ctx = { .item = i, .func = func };
  return glob_in_root(i->path, arg_root, glob_apply_trampoline, &ctx);
}

static int apply_glob_perms(Item *i, const char *instance) {
  _cleanup_close_ int fd = chase_and_open(instance, arg_root,
                                          CHASE_PREFIX_ROOT | CHASE_NOFOLLOW,
                                          O_RDONLY | O_CLOEXEC | O_PATH, NULL);
  if (fd < 0) {
    if (fd == -ENOENT)
      return 0;
    return fd;
  }

  struct stat st;
  if (fstat(fd, &st) < 0)
    return -errno;

  int r = 0;
  switch (i->type) {
  case RELABEL_PATH:
  case ADJUST_MODE:
    r = fd_set_perms(i, fd, instance, &st, CREATION_EXISTING);
    break;
  case SET_XATTR:
    r = fd_set_xattrs(i, fd, instance);
    break;
  case SET_ACL:
    r = fd_set_acls(i, fd, instance, &st, CREATION_EXISTING);
    break;
  case SET_ATTRIBUTE:
    r = fd_set_attribute(i, fd, instance, &st);
    break;
  default:
    break;
  }
  return r;
}

static int apply_glob_recursive_walk(Item *i, int dir_fd, const char *path);

static int apply_recursive_entry(Item *i, int dir_fd, const char *base,
                                 const char *parent_path) {
  _cleanup_free_ char *full = path_join(parent_path, base);
  if (!full)
    return -ENOMEM;

  struct stat st;
  if (fstatat(dir_fd, base, &st, AT_SYMLINK_NOFOLLOW) < 0) {
    if (errno == ENOENT)
      return 0;
    return -errno;
  }

  _cleanup_close_ int fd =
    openat(dir_fd, base, O_RDONLY | O_NOFOLLOW | O_CLOEXEC | O_PATH);
  if (fd < 0) {
    if (errno == ENOENT)
      return 0;
    return -errno;
  }

  int r = 0;
  switch (i->type) {
  case RECURSIVE_RELABEL_PATH:
    r = fd_set_perms(i, fd, full, &st, CREATION_EXISTING);
    break;
  case RECURSIVE_SET_XATTR:
    r = fd_set_xattrs(i, fd, full);
    break;
  case RECURSIVE_SET_ACL:
    r = fd_set_acls(i, fd, full, &st, CREATION_EXISTING);
    break;
  case RECURSIVE_SET_ATTRIBUTE:
    r = fd_set_attribute(i, fd, full, &st);
    break;
  default:
    break;
  }

  if (S_ISDIR(st.st_mode)) {
    int q = apply_glob_recursive_walk(i, fd, full);
    r = r ?: q;
  }

  return r;
}

static int apply_glob_recursive_walk(Item *i, int dir_fd, const char *path) {
  int sub_fd = openat(dir_fd, ".", O_DIRECTORY | O_CLOEXEC | O_RDONLY);
  if (sub_fd < 0)
    return -errno;

  _cleanup_closedir_ DIR *d = xfdopendir(sub_fd);
  if (!d) {
    close(sub_fd);
    return -errno;
  }

  int r = 0;
  struct dirent *de;
  while ((de = readdir(d))) {
    if (streq(de->d_name, ".") || streq(de->d_name, ".."))
      continue;
    int q = apply_recursive_entry(i, dirfd(d), de->d_name, path);
    r = r ?: q;
  }
  return r;
}

static int apply_glob_recursive(Item *i, const char *instance) {
  int r = apply_glob_perms(i, instance);

  _cleanup_close_ int fd = chase_and_open(instance, arg_root,
                                          CHASE_PREFIX_ROOT | CHASE_NOFOLLOW,
                                          O_DIRECTORY | O_CLOEXEC | O_RDONLY,
                                          NULL);
  if (fd < 0) {
    if (fd == -ENOENT || fd == -ENOTDIR)
      return r;
    return r ?: fd;
  }

  int q = apply_glob_recursive_walk(i, fd, instance);
  return r ?: q;
}

static int process_item(Context *c, Item *i, OperationMask ops) {
  if (!should_process(i->path))
    return 0;

  int r = 0;

  if (ops & OPERATION_REMOVE) {
    if (!(i->done & OPERATION_REMOVE)) {
      int q = remove_item(c, i);
      if (i->allow_failure && q < 0)
        q = 0;
      r = r ?: q;
      i->done |= OPERATION_REMOVE;
    }
  }

  if (ops & OPERATION_CLEAN) {
    if (!(i->done & OPERATION_CLEAN)) {
      int q = clean_item(c, i);
      if (i->allow_failure && q < 0)
        q = 0;
      r = r ?: q;
      i->done |= OPERATION_CLEAN;
    }
  }

  if (ops & OPERATION_CREATE) {
    if (!(i->done & OPERATION_CREATE)) {
      int q;
      switch (i->type) {
      case CREATE_FILE:
      case TRUNCATE_FILE:
      case CREATE_DIRECTORY:
      case TRUNCATE_DIRECTORY:
      case CREATE_SUBVOLUME:
      case CREATE_SUBVOLUME_INHERIT_QUOTA:
      case CREATE_SUBVOLUME_NEW_QUOTA:
      case CREATE_FIFO:
      case CREATE_SYMLINK:
      case CREATE_CHAR_DEVICE:
      case CREATE_BLOCK_DEVICE:
      case COPY_FILES:
        q = create_item(c, i);
        break;

      case RELABEL_PATH:
      case ADJUST_MODE:
      case SET_XATTR:
      case SET_ACL:
      case SET_ATTRIBUTE:
        q = glob_apply(i, apply_glob_perms);
        break;

      case RECURSIVE_RELABEL_PATH:
      case RECURSIVE_SET_XATTR:
      case RECURSIVE_SET_ACL:
      case RECURSIVE_SET_ATTRIBUTE:
        q = glob_apply(i, apply_glob_recursive);
        break;

      case WRITE_FILE:
	q = glob_apply(i, write_one_file);
	break;

      case IGNORE_PATH:
      case IGNORE_DIRECTORY_PATH:
      case REMOVE_PATH:
      case RECURSIVE_REMOVE_PATH:
        q = 0;
        break;

      default:
        q = 0;
        break;
      }

      if (i->allow_failure && q < 0)
        q = 0;
      r = r ?: q;
      i->done |= OPERATION_CREATE;
    }
  }

  return r;
}

static int process_item_array(Context *c, ItemArray *a, OperationMask ops) {
  int r = 0;
  for (size_t j = 0; j < a->n_items; j++) {
    int q = process_item(c, &a->items[j], ops);
    r = r ?: q;
  }
  return r;
}

static int process_children_first(Context *c, ItemArray *a, OperationMask ops) {
  int r = 0;
  for (size_t i = 0; i < a->n_children; i++) {
    int q = process_children_first(c, a->children[i], ops);
    r = r ?: q;
  }
  int q = process_item_array(c, a, ops);
  return r ?: q;
}

static int process_parents_first(Context *c, ItemArray *a, OperationMask ops) {
  int r = process_item_array(c, a, ops);
  for (size_t i = 0; i < a->n_children; i++) {
    int q = process_parents_first(c, a->children[i], ops);
    r = r ?: q;
  }
  return r;
}

int execute_all(Context *c, OperationMask ops) {
  int r = 0;

  if (ops & (OPERATION_REMOVE | OPERATION_CLEAN)) {
    OperationMask phase1 = ops & (OPERATION_REMOVE | OPERATION_CLEAN);

    const char *key;
    void *v;
    ORDERED_HASHMAP_FOREACH(key, v, c->items) {
      ItemArray *a = v;
      if (!a->parent) {
        int q = process_children_first(c, a, phase1);
        r = r ?: q;
      }
    }

    ORDERED_HASHMAP_FOREACH(key, v, c->globs) {
      ItemArray *a = v;
      if (!a->parent) {
        int q = process_children_first(c, a, phase1);
        r = r ?: q;
      }
    }
  }

  if (ops & OPERATION_CREATE) {
    const char *key;
    void *v;

    ORDERED_HASHMAP_FOREACH(key, v, c->items) {
      ItemArray *a = v;
      if (!a->parent) {
        int q = process_parents_first(c, a, OPERATION_CREATE);
        r = r ?: q;
      }
    }

    ORDERED_HASHMAP_FOREACH(key, v, c->globs) {
      ItemArray *a = v;
      if (!a->parent) {
        int q = process_parents_first(c, a, OPERATION_CREATE);
        r = r ?: q;
      }
    }
  }

  return r;
}
