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

#include <sys/sysmacros.h>
#include <stdlib.h>

#include "create.h"
#include "chase.h"

static int mkdir_parents_host(const char *path, mode_t mode) {
  _cleanup_free_ char *parent = path_parent(path);
  if (!parent || streq(parent, "/"))
    return 0;
  int r = mkdir_parents_host(parent, mode);
  if (r < 0)
    return r;
  if (mkdir(parent, mode) < 0 && errno != EEXIST)
    return -errno;
  return 0;
}

int mkdir_parents(const char *path, mode_t mode) {
  _cleanup_free_ char *parent = path_parent(path);
  if (!parent || streq(parent, "/"))
    return 0;

  _cleanup_free_ char *parent_host = NULL;
  int r = chase(parent, arg_root,
                CHASE_PREFIX_ROOT | CHASE_NONEXISTENT,
                &parent_host, NULL);
  if (r < 0)
    return r;

  r = mkdir_parents_host(parent_host, mode);
  if (r < 0)
    return r;

  if (mkdir(parent_host, mode) < 0 && errno != EEXIST)
    return -errno;
  return 0;
}

static int open_parent_safe(const char *path, const char **ret_bn) {
  _cleanup_free_ char *parent = path_parent(path);
  if (!parent)
    return -EINVAL;

  const char *bn = path_basename(path);
  if (!bn || streq(bn, "/") || streq(bn, ""))
    return -EINVAL;

  int fd = chase_and_open(parent, arg_root, CHASE_PREFIX_ROOT,
                          O_DIRECTORY | O_CLOEXEC | O_PATH, NULL);
  if (fd < 0)
    return fd;

  *ret_bn = bn;
  return fd;
}

static int write_argument_data(Item *i, int fd, const char *path) {
  if (!i->argument || i->argument[0] == '\0')
    return 0;

  size_t len = strlen(i->argument);
  size_t written = 0;
  while (written < len) {
    ssize_t n = write(fd, i->argument + written, len - written);
    if (n < 0) {
      if (errno == EINTR)
        continue;
      return log_error_errno(-errno, "Failed to write to '%s': %s", path,
                             strerror(errno));
    }
    written += (size_t)n;
  }
  return 0;
}

static int create_file(Item *i) {
  const char *bn;
  _cleanup_close_ int pfd = open_parent_safe(i->path, &bn);
  if (pfd < 0)
    return log_error_errno(pfd, "Failed to open parent of '%s': %s", i->path,
                           strerror(-pfd));

  CreationMode creation;
  _cleanup_close_ int fd = openat(
				  pfd, bn, O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC | O_WRONLY | O_NOCTTY,
				  i->mode_set ? i->mode : 0644);
  if (fd < 0) {
    if (errno != EEXIST)
      return log_error_errno(-errno, "Failed to create file '%s': %s", i->path,
                             strerror(errno));
    fd = openat(pfd, bn, O_NOFOLLOW | O_CLOEXEC | O_PATH | O_RDONLY);
    if (fd < 0)
      return log_error_errno(-errno, "Failed to open '%s': %s", i->path,
                             strerror(errno));

    struct stat st;
    if (fstat(fd, &st) < 0)
      return log_error_errno(-errno, "fstat(%s) failed: %s", i->path,
                             strerror(errno));
    if (!S_ISREG(st.st_mode)) {
      log_debug("'%s' already exists and is not a regular file.", i->path);
      return 0;
    }
    creation = CREATION_EXISTING;
  } else {
    int r = write_argument_data(i, fd, i->path);
    if (r < 0)
      return r;

    int path_fd = openat(pfd, bn, O_NOFOLLOW | O_CLOEXEC | O_PATH | O_RDONLY);
    if (path_fd >= 0) {
      close(fd);
      fd = path_fd;
    }
    creation = CREATION_NORMAL;
  }

  int r = fd_set_perms(i, fd, i->path, NULL, creation);
  if (r < 0)
    return r;
  r = fd_set_xattrs(i, fd, i->path);
  if (r < 0)
    return r;
  return fd_set_acls(i, fd, i->path, NULL, creation);
}

static int truncate_file(Item *i) {
  const char *bn;
  _cleanup_close_ int pfd = open_parent_safe(i->path, &bn);
  if (pfd < 0)
    return log_error_errno(pfd, "Failed to open parent of '%s': %s", i->path,
                           strerror(-pfd));

  CreationMode creation;
  _cleanup_close_ int fd =
    openat(pfd, bn, O_NOFOLLOW | O_CLOEXEC | O_WRONLY | O_NOCTTY);
  if (fd < 0) {
    if (errno != ENOENT)
      return log_error_errno(-errno, "Failed to open '%s': %s", i->path,
                             strerror(errno));
    fd = openat(pfd, bn, O_CREAT | O_NOFOLLOW | O_CLOEXEC | O_WRONLY | O_NOCTTY,
                i->mode_set ? i->mode : 0644);
    if (fd < 0)
      return log_error_errno(-errno, "Failed to create file '%s': %s", i->path,
                             strerror(errno));
    creation = CREATION_NORMAL;
  } else {
    struct stat st;
    if (fstat(fd, &st) < 0)
      return log_error_errno(-errno, "fstat(%s) failed: %s", i->path,
                             strerror(errno));
    if (!S_ISREG(st.st_mode)) {
      log_warning("'%s' exists but is not a regular file.", i->path);
      return 0;
    }
    if (st.st_size > 0) {
      if (ftruncate(fd, 0) < 0)
        return log_error_errno(-errno, "Failed to truncate '%s': %s", i->path,
                               strerror(errno));
    }
    creation = CREATION_EXISTING;
  }

  int r = write_argument_data(i, fd, i->path);
  if (r < 0)
    return r;

  int path_fd = openat(pfd, bn, O_NOFOLLOW | O_CLOEXEC | O_PATH | O_RDONLY);
  if (path_fd >= 0) {
    close(fd);
    fd = path_fd;
  }

  r = fd_set_perms(i, fd, i->path, NULL, creation);
  if (r < 0)
    return r;
  r = fd_set_xattrs(i, fd, i->path);
  if (r < 0)
    return r;
  return fd_set_acls(i, fd, i->path, NULL, creation);
}

static int create_directory(Item *i) {
  mode_t mode = i->mode_set ? i->mode : 0755;

  const char *bn;
  _cleanup_close_ int pfd = open_parent_safe(i->path, &bn);
  if (pfd < 0)
    return log_error_errno(pfd, "Failed to open parent of '%s': %s", i->path,
                           strerror(-pfd));

  bool already_existed = false;
  if (mkdirat(pfd, bn, mode) < 0) {
    if (errno != EEXIST)
      return log_error_errno(-errno, "Failed to create directory '%s': %s",
                             i->path, strerror(errno));
    already_existed = true;
  }

  _cleanup_close_ int fd =
    openat(pfd, bn, O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC | O_PATH | O_RDONLY);
  if (fd < 0)
    return log_error_errno(-errno, "Failed to open directory '%s': %s", i->path,
                           strerror(errno));

  struct stat st;
  if (fstat(fd, &st) < 0)
    return log_error_errno(-errno, "fstat(%s) failed: %s", i->path,
                           strerror(errno));
  if (!S_ISDIR(st.st_mode)) {
    log_warning("'%s' already exists and is not a directory.", i->path);
    return 0;
  }

  CreationMode creation = already_existed ? CREATION_EXISTING : CREATION_NORMAL;

  int r = fd_set_perms(i, fd, i->path, &st, creation);
  if (r < 0)
    return r;
  r = fd_set_xattrs(i, fd, i->path);
  if (r < 0)
    return r;
  return fd_set_acls(i, fd, i->path, &st, creation);
}

static int create_symlink(Item *i) {
  const char *target = i->argument;
  if (!target || target[0] == '\0') {
    log_error("Symlink '%s' has no target specified.", i->path);
    return -EINVAL;
  }

  const char *bn;
  _cleanup_close_ int pfd = open_parent_safe(i->path, &bn);
  if (pfd < 0)
    return log_error_errno(pfd, "Failed to open parent of '%s': %s", i->path,
                           strerror(-pfd));

  if (symlinkat(target, pfd, bn) < 0) {
    if (errno != EEXIST)
      return log_error_errno(-errno, "Failed to create symlink '%s': %s",
                             i->path, strerror(errno));

    _cleanup_close_ int fd = openat(pfd, bn, O_NOFOLLOW | O_CLOEXEC | O_PATH | O_RDONLY);
    struct stat st;
    if (fd < 0) {
      if (errno == ELOOP) {
        /* On systems without O_PATH, O_NOFOLLOW on a symlink returns ELOOP.
         * Fall back to fstatat via the parent directory fd. */
        if (fstatat(pfd, bn, &st, AT_SYMLINK_NOFOLLOW) < 0)
          return log_error_errno(-errno, "fstatat(%s) failed: %s", i->path,
                                 strerror(errno));
      } else {
        return log_error_errno(-errno, "Failed to open '%s': %s", i->path,
                               strerror(errno));
      }
    } else {
      if (fstat(fd, &st) < 0)
        return log_error_errno(-errno, "fstat(%s) failed: %s", i->path,
                               strerror(errno));
    }

    if (S_ISLNK(st.st_mode)) {
      char *buf = NULL;
      ssize_t n = readlinkat_dynamic(pfd, bn, &buf);
      if (n >= 0) {
        if (streq(buf, target)) {
          free(buf);
          if (fd >= 0)
            return fd_set_perms(i, fd, i->path, &st, CREATION_EXISTING);
          return 0;
        }
      }
      free(buf);
    }

    if (!i->append_or_force) {
      log_debug("'%s' is not a symlink or points to wrong target, ignoring.",
                i->path);
      return 0;
    }

    if (unlinkat(pfd, bn, S_ISDIR(st.st_mode) ? AT_REMOVEDIR : 0) < 0) {
      if (errno == ENOTEMPTY) {
        int r = rm_rf(i->path);
        if (r < 0)
          return log_error_errno(r, "Failed to remove '%s' for replacement: %s",
                                 i->path, strerror(-r));
      } else {
        return log_error_errno(-errno, "Failed to remove '%s': %s", i->path,
                               strerror(errno));
      }
    }

    if (symlinkat(target, pfd, bn) < 0)
      return log_error_errno(-errno,
                             "Failed to create symlink '%s' -> '%s': %s",
                             i->path, target, strerror(errno));
  }

  _cleanup_close_ int fd = openat(pfd, bn, O_NOFOLLOW | O_CLOEXEC | O_PATH | O_RDONLY);
  if (fd < 0) {
    log_debug("Failed to open newly created symlink '%s': '%s', ignoring.", i->path,
              strerror(errno));
    return 0;
  }

  return fd_set_perms(i, fd, i->path, NULL, CREATION_NORMAL);
}

static int copy_tree(int src_fd, int dst_fd) {
  _cleanup_closedir_ DIR *d = xfdopendir(src_fd);
  if (!d)
    return -errno;

  struct dirent *de;
  int ret = 0;
  while ((de = readdir(d))) {
    if (streq(de->d_name, ".") || streq(de->d_name, ".."))
      continue;

    struct stat st;
    if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
      ret = ret ?: -errno;
      continue;
    }

    if (S_ISDIR(st.st_mode)) {
      if (mkdirat(dst_fd, de->d_name, st.st_mode & 07777) < 0 &&
          errno != EEXIST) {
        ret = ret ?: -errno;
        continue;
      }
      int child_src =
		  openat(dirfd(d), de->d_name, O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC | O_RDONLY);
      if (child_src < 0) {
        ret = ret ?: -errno;
        continue;
      }
      int child_dst =
	openat(dst_fd, de->d_name, O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC | O_RDONLY);
      if (child_dst < 0) {
        close(child_src);
        ret = ret ?: -errno;
        continue;
      }
      int r = copy_tree(child_src, child_dst);
      close(child_src);
      close(child_dst);
      ret = ret ?: r;

    } else if (S_ISLNK(st.st_mode)) {
      char *target = NULL;
      ssize_t n = readlinkat_dynamic(dirfd(d), de->d_name, &target);
      if (n < 0) {
        free(target);
        ret = ret ?: n;
        continue;
      }
      target[n] = '\0';
      if (symlinkat(target, dst_fd, de->d_name) < 0 && errno != EEXIST)
        ret = ret ?: -errno;
      free(target);

    } else if (S_ISREG(st.st_mode)) {
      int src = openat(dirfd(d), de->d_name, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
      if (src < 0) {
        ret = ret ?: -errno;
        continue;
      }
      int dst = openat(dst_fd, de->d_name,
                       O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW | O_CLOEXEC,
                       st.st_mode & 07777);
      if (dst < 0) {
        close(src);
        ret = ret ?: -errno;
        continue;
      }
      char buf[65536];
      for (;;) {
        ssize_t nr = read(src, buf, sizeof(buf));
        if (nr <= 0) {
          if (nr < 0)
            ret = ret ?: -errno;
          break;
        }
        ssize_t nw = 0;
        while (nw < nr) {
          ssize_t w = write(dst, buf + nw, (size_t)(nr - nw));
          if (w < 0) {
            if (errno == EINTR)
              continue;
            ret = ret ?: -errno;
            goto copy_done;
          }
          nw += w;
        }
      }
      struct stat src_st;
      struct timespec times[2];
      if (fstat(src, &src_st) != 0) {
        log_warning("Failed to get times of '%s'. Ignoring", de->d_name);
        goto copy_done;
      }
      times[0] = src_st.st_atim;
      times[1] = src_st.st_mtim;
      if (futimens(dst, times) != 0)
        log_warning("Failed to set times of '%s'. Ignoring", de->d_name);
    copy_done:
      close(src);
      close(dst);
      if (fchownat(dst_fd, de->d_name, st.st_uid, st.st_gid,
                   AT_SYMLINK_NOFOLLOW) < 0)
        ret = ret ?: -errno;

    } else {
      log_debug("Skipping special file '%s' during copy.", de->d_name);
    }
  }

  return ret;
}

static int copy_files(Item *i) {
  if (!i->argument || i->argument[0] == '\0') {
    log_error("Copy item '%s' has no source specified.", i->path);
    return -EINVAL;
  }

  _cleanup_free_ char *src_host = NULL;
  int cr = chase(i->argument, arg_root, CHASE_PREFIX_ROOT, &src_host, NULL);
  if (cr < 0)
    return log_error_errno(cr, "Failed to resolve source '%s': %s",
                           i->argument, strerror(-cr));

  _cleanup_free_ char *dst_host = NULL;
  cr = chase(i->path, arg_root, CHASE_PREFIX_ROOT | CHASE_NONEXISTENT,
             &dst_host, NULL);
  if (cr < 0)
    return log_error_errno(cr, "Failed to resolve dest '%s': %s",
                           i->path, strerror(-cr));

  struct stat src_st;
  if (stat(src_host, &src_st) < 0)
    return log_error_errno(-errno, "Failed to stat source '%s': %s",
                           i->argument, strerror(errno));

  if (S_ISDIR(src_st.st_mode)) {
    mode_t mode = i->mode_set ? i->mode : (src_st.st_mode & 07777);
    if (mkdir(dst_host, mode) < 0 && errno != EEXIST) {
      if (!i->append_or_force)
        return log_error_errno(-errno, "Failed to create directory '%s': %s",
                               i->path, strerror(errno));
    }

    int src_fd =
      open(src_host, O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC | O_RDONLY);
    if (src_fd < 0)
      return log_error_errno(-errno, "Failed to open source '%s': %s",
                             i->argument, strerror(errno));

    int dst_fd = open(dst_host, O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC | O_RDONLY);
    if (dst_fd < 0) {
      close(src_fd);
      return log_error_errno(-errno, "Failed to open dest '%s': %s", i->path,
                             strerror(errno));
    }

    if (!i->append_or_force) {
      _cleanup_closedir_ DIR *check =
	xfdopendir(openat(dst_fd, ".", O_DIRECTORY | O_CLOEXEC | O_RDONLY));
      if (check) {
        struct dirent *de;
        bool empty = true;
        while ((de = readdir(check))) {
          if (!streq(de->d_name, ".") && !streq(de->d_name, "..")) {
            empty = false;
            break;
          }
        }
        if (!empty) {
          log_debug("'%s' is not empty, not copying (use C+ to merge).",
                    i->path);
          close(src_fd);
          close(dst_fd);
          return 0;
        }
      }
    }

    int r = copy_tree(src_fd, dst_fd);
    close(src_fd);
    close(dst_fd);
    if (r < 0)
      log_warning("Errors during copy '%s' -> '%s': %s", i->argument, i->path,
                  strerror(-r));

  } else if (S_ISREG(src_st.st_mode)) {
    const char *bn;
    _cleanup_close_ int pfd = open_parent_safe(i->path, &bn);
    if (pfd < 0)
      return log_error_errno(pfd, "Failed to open parent of '%s': %s", i->path,
                             strerror(-pfd));

    _cleanup_close_ int src =
      open(src_host, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
    if (src < 0)
      return log_error_errno(-errno, "Failed to open source '%s': %s",
                             i->argument, strerror(errno));

    mode_t mode = i->mode_set ? i->mode : (src_st.st_mode & 07777);
    int flags = O_WRONLY | O_NOFOLLOW | O_CLOEXEC | O_NOCTTY;
    if (i->append_or_force)
      flags |= O_CREAT | O_TRUNC;
    else
      flags |= O_CREAT | O_EXCL;

    _cleanup_close_ int dst = openat(pfd, bn, flags, mode);
    if (dst < 0) {
      if (errno == EEXIST && !i->append_or_force) {
        log_debug("'%s' already exists, not copying.", i->path);
        return 0;
      }
      return log_error_errno(-errno, "Failed to create '%s': %s", i->path,
                             strerror(errno));
    }

    char buf[65536];
    for (;;) {
      ssize_t nr = read(src, buf, sizeof(buf));
      if (nr <= 0) {
        if (nr < 0)
          return log_error_errno(-errno, "Read error from '%s': %s",
                                 i->argument, strerror(errno));
        break;
      }
      ssize_t nw = 0;
      while (nw < nr) {
        ssize_t w = write(dst, buf + nw, (size_t)(nr - nw));
        if (w < 0) {
          if (errno == EINTR)
            continue;
          return log_error_errno(-errno, "Write error to '%s': %s", i->path,
                                 strerror(errno));
        }
        nw += w;
      }
    }

    struct stat src_st;
    struct timespec times[2];
    if (fstat(src, &src_st) != 0) {
      return log_error_errno(-errno, "Failed to get times of '%s'. Ignoring",
                             i->argument);
    }
    times[0] = src_st.st_atim;
    times[1] = src_st.st_mtim;
    if (futimens(dst, times) != 0)
      return log_error_errno(-errno, "Failed to set times of '%s'. Ignoring",
                             i->path);
  } else {
    log_warning("Source '%s' is not a regular file or directory.", i->argument);
    return 0;
  }

  _cleanup_close_ int fd = chase_and_open(i->path, arg_root,
                                          CHASE_PREFIX_ROOT | CHASE_NOFOLLOW,
                                          O_CLOEXEC | O_PATH, NULL);
  if (fd < 0)
    return log_error_errno(fd, "Failed to open '%s': %s", i->path,
                           strerror(-fd));

  int r = fd_set_perms(i, fd, i->path, NULL, CREATION_NORMAL);
  if (r < 0)
    return r;
  return fd_set_xattrs(i, fd, i->path);
}

static int create_fifo(Item *i) {
  const char *bn;
  _cleanup_close_ int pfd = open_parent_safe(i->path, &bn);
  if (pfd < 0)
    return log_error_errno(pfd, "Failed to open parent of '%s': %s", i->path,
                           strerror(-pfd));

  mode_t mode = i->mode_set ? i->mode : 0644;
  CreationMode creation;

  if (mkfifoat(pfd, bn, mode) < 0) {
    if (errno != EEXIST)
      return log_error_errno(-errno, "Failed to create FIFO '%s': %s", i->path,
                             strerror(errno));

    _cleanup_close_ int fd = openat(pfd, bn, O_NOFOLLOW | O_CLOEXEC | O_PATH | O_RDONLY);
    if (fd < 0)
      return log_error_errno(-errno, "Failed to open '%s': %s", i->path,
                             strerror(errno));

    struct stat st;
    if (fstat(fd, &st) < 0)
      return log_error_errno(-errno, "fstat(%s) failed: %s", i->path,
                             strerror(errno));

    if (S_ISFIFO(st.st_mode)) {
      creation = CREATION_EXISTING;
      return fd_set_perms(i, fd, i->path, &st, creation);
    }

    if (!i->append_or_force) {
      log_warning("'%s' already exists and is not a FIFO.", i->path);
      return 0;
    }

    if (unlinkat(pfd, bn, S_ISDIR(st.st_mode) ? AT_REMOVEDIR : 0) < 0) {
      if (errno == ENOTEMPTY) {
        int r = rm_rf(i->path);
        if (r < 0)
          return r;
      } else {
        return log_error_errno(-errno, "Failed to remove '%s': %s", i->path,
                               strerror(errno));
      }
    }

    if (mkfifoat(pfd, bn, mode) < 0)
      return log_error_errno(-errno, "Failed to create FIFO '%s': %s", i->path,
                             strerror(errno));
    creation = CREATION_FORCE;
  } else {
    creation = CREATION_NORMAL;
  }

  _cleanup_close_ int fd = openat(pfd, bn, O_NOFOLLOW | O_CLOEXEC | O_PATH);
  if (fd < 0)
    return log_error_errno(-errno, "Failed to open FIFO '%s': %s", i->path,
                           strerror(errno));

  return fd_set_perms(i, fd, i->path, NULL, creation);
}

static int create_device(Item *i, mode_t file_type) {
  const char *bn;
  _cleanup_close_ int pfd = open_parent_safe(i->path, &bn);
  if (pfd < 0)
    return log_error_errno(pfd, "Failed to open parent of '%s': %s", i->path,
                           strerror(-pfd));

  mode_t mode = (i->mode_set ? i->mode : 0644) | file_type;
  CreationMode creation;

  if (mknodat(pfd, bn, mode, i->major_minor) < 0) {
    if (errno != EEXIST) {
      if (errno == EPERM || errno == EACCES) {
        log_debug("No permission to create device node '%s', ignoring.",
                  i->path);
        return 0;
      }
      return log_error_errno(-errno, "Failed to create device '%s': %s",
                             i->path, strerror(errno));
    }

    _cleanup_close_ int fd = openat(pfd, bn, O_NOFOLLOW | O_CLOEXEC | O_PATH | O_RDONLY);
    if (fd < 0)
      return log_error_errno(-errno, "Failed to open '%s': %s", i->path,
                             strerror(errno));

    struct stat st;
    if (fstat(fd, &st) < 0)
      return log_error_errno(-errno, "fstat(%s) failed: %s", i->path,
                             strerror(errno));

    if ((st.st_mode & S_IFMT) == file_type && st.st_rdev == i->major_minor) {
      creation = CREATION_EXISTING;
      return fd_set_perms(i, fd, i->path, &st, creation);
    }

    if (!i->append_or_force) {
      log_warning("'%s' already exists and is not the expected device.",
                  i->path);
      return 0;
    }

    if (unlinkat(pfd, bn, S_ISDIR(st.st_mode) ? AT_REMOVEDIR : 0) < 0) {
      if (errno == ENOTEMPTY) {
        int r = rm_rf(i->path);
        if (r < 0)
          return r;
      } else {
        return log_error_errno(-errno, "Failed to remove '%s': %s", i->path,
                               strerror(errno));
      }
    }

    if (mknodat(pfd, bn, mode, i->major_minor) < 0) {
      if (errno == EPERM || errno == EACCES) {
        log_debug("No permission to create device node '%s', ignoring.",
                  i->path);
        return 0;
      }
      return log_error_errno(-errno, "Failed to create device '%s': %s",
                             i->path, strerror(errno));
    }
    creation = CREATION_FORCE;
  } else {
    creation = CREATION_NORMAL;
  }

  _cleanup_close_ int fd = openat(pfd, bn, O_NOFOLLOW | O_CLOEXEC | O_PATH | O_RDONLY);
  if (fd < 0)
    return log_error_errno(-errno, "Failed to open device '%s': %s", i->path,
                           strerror(errno));

  return fd_set_perms(i, fd, i->path, NULL, creation);
}

int write_one_file(Item *i, const char *path) {
  if (arg_dry_run) {
    log_info("Would write '%s'.", path);
    return 0;
  }
  const char *bn;
  _cleanup_close_ int pfd = open_parent_safe(path, &bn);
  if (pfd < 0)
    return log_error_errno(pfd, "Failed to open parent of '%s': %s", path,
                           strerror(-pfd));

  _cleanup_close_ int fd = openat(pfd, bn,
                                  O_WRONLY | O_NONBLOCK | O_CLOEXEC | O_NOCTTY |
				  (i->append_or_force ? O_APPEND : 0));
  if (fd < 0) {
    if (errno != ENOENT)
      return log_error_errno(-errno, "Failed to open file '%s': %s", path,
                             strerror(errno));
    else
      log_debug("Not writing missing file \"%s\"", path);
    return 0;
  }
  int r = write_argument_data(i, fd, path);
  if (r < 0)
    return r;
  return fd_set_perms(i, fd, path, NULL, CREATION_EXISTING);
}

int create_item(Context *c, Item *i) {
  (void)c;

  if (arg_dry_run) {
    log_info("Would create '%s' (type %c).", i->path, (char)i->type);
    return 0;
  }

  int r;

  switch (i->type) {
  case CREATE_FILE:
    r = mkdir_parents(i->path, 0755);
    if (r < 0)
      log_debug("mkdir_parents(%s) failed: %s", i->path, strerror(-r));
    if (i->append_or_force)
      return truncate_file(i);
    return create_file(i);

  case TRUNCATE_FILE:
    r = mkdir_parents(i->path, 0755);
    if (r < 0)
      log_debug("mkdir_parents(%s) failed: %s", i->path, strerror(-r));
    return truncate_file(i);

  case CREATE_DIRECTORY:
  case TRUNCATE_DIRECTORY:
  case CREATE_SUBVOLUME:
  case CREATE_SUBVOLUME_INHERIT_QUOTA:
  case CREATE_SUBVOLUME_NEW_QUOTA:
    r = mkdir_parents(i->path, 0755);
    if (r < 0)
      log_debug("mkdir_parents(%s) failed: %s", i->path, strerror(-r));
    return create_directory(i);

  case CREATE_SYMLINK:
    r = mkdir_parents(i->path, 0755);
    if (r < 0)
      log_debug("mkdir_parents(%s) failed: %s", i->path, strerror(-r));
    return create_symlink(i);

  case COPY_FILES:
    r = mkdir_parents(i->path, 0755);
    if (r < 0)
      log_debug("mkdir_parents(%s) failed: %s", i->path, strerror(-r));
    return copy_files(i);

  case CREATE_FIFO:
    r = mkdir_parents(i->path, 0755);
    if (r < 0)
      log_debug("mkdir_parents(%s) failed: %s", i->path, strerror(-r));
    return create_fifo(i);

  case CREATE_CHAR_DEVICE:
    r = mkdir_parents(i->path, 0755);
    if (r < 0)
      log_debug("mkdir_parents(%s) failed: %s", i->path, strerror(-r));
    return create_device(i, S_IFCHR);

  case CREATE_BLOCK_DEVICE:
    r = mkdir_parents(i->path, 0755);
    if (r < 0)
      log_debug("mkdir_parents(%s) failed: %s", i->path, strerror(-r));
    return create_device(i, S_IFBLK);

  case SET_XATTR:
  case RECURSIVE_SET_XATTR:
  case SET_ACL:
  case RECURSIVE_SET_ACL:
  case SET_ATTRIBUTE:
  case RECURSIVE_SET_ATTRIBUTE:
  case IGNORE_PATH:
  case IGNORE_DIRECTORY_PATH:
  case REMOVE_PATH:
  case RECURSIVE_REMOVE_PATH:
  case RELABEL_PATH:
  case RECURSIVE_RELABEL_PATH:
  case ADJUST_MODE:
  case WRITE_FILE:
    return 0;
  }

  log_warning("Unknown item type '%c' for '%s'.", (char)i->type, i->path);
  return 0;
}
