/*
 * Copyright (C) 2011,2013 Colin Walters <walters@verbum.org>
 *
 * SPDX-License-Identifier: LGPL-2.0+
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <https://www.gnu.org/licenses/>.
 */

#pragma once

#include "config.h"
#include "ostree-ref.h"
#include "ostree-remote-private.h"
#include "ostree-repo.h"
#include "otutil.h"
#include <sys/statvfs.h>

G_BEGIN_DECLS

#define OSTREE_DELTAPART_VERSION (0)

#define _OSTREE_SUMMARY_CACHE_DIR "summaries"
#define _OSTREE_CACHE_DIR "cache"

#define _OSTREE_MAX_OUTSTANDING_DELTAPART_REQUESTS 2

/* We want some parallelism with disk writes, but we also
 * want to avoid starting tens or hundreds of threads
 * (via GTask) all writing to disk.  Eventually we may
 * use io_uring which handles backpressure correctly.
 * Also, in "immediate fsync" mode, this helps provide
 * much more backpressure, helping our I/O patterns
 * be nicer for any concurrent processes, such as etcd
 * or other databases.
 * https://github.com/openshift/machine-config-operator/issues/1897
 * */
#define _OSTREE_MAX_OUTSTANDING_WRITE_REQUESTS 3

/* Well-known keys for the additional metadata field in a summary file. */
#define OSTREE_SUMMARY_LAST_MODIFIED "ostree.summary.last-modified"
#define OSTREE_SUMMARY_EXPIRES "ostree.summary.expires"
#define OSTREE_SUMMARY_COLLECTION_ID "ostree.summary.collection-id"
#define OSTREE_SUMMARY_COLLECTION_MAP "ostree.summary.collection-map"
#define OSTREE_SUMMARY_MODE "ostree.summary.mode"
#define OSTREE_SUMMARY_TOMBSTONE_COMMITS "ostree.summary.tombstone-commits"
#define OSTREE_SUMMARY_INDEXED_DELTAS "ostree.summary.indexed-deltas"

#define _OSTREE_PAYLOAD_LINK_PREFIX "../"
#define _OSTREE_PAYLOAD_LINK_PREFIX_LEN (sizeof (_OSTREE_PAYLOAD_LINK_PREFIX) - 1)

/* Well-known keys for the additional metadata field in a commit in a ref entry
 * in a summary file. */
#define OSTREE_COMMIT_TIMESTAMP "ostree.commit.timestamp"
#define OSTREE_COMMIT_VERSION "ostree.commit.version"

// The metadata key for composefs
#define OSTREE_COMPOSEFS_META_PREFIX "ostree.composefs"
// The fs-verity digest of the composefs, version 0
#define OSTREE_COMPOSEFS_DIGEST_KEY_V0 OSTREE_COMPOSEFS_META_PREFIX ".digest.v0"

#define _OSTREE_INTEGRITY_SECTION "ex-integrity"

typedef enum
{
  OSTREE_REPO_TEST_ERROR_PRE_COMMIT = (1 << 0),
  OSTREE_REPO_TEST_ERROR_INVALID_CACHE = (1 << 1),
} OstreeRepoTestErrorFlags;

struct OstreeRepoCommitModifier
{
  gint refcount; /* atomic */

  OstreeRepoCommitModifierFlags flags;
  OstreeRepoCommitFilter filter;
  gpointer user_data;
  GDestroyNotify destroy_notify;

  OstreeRepoCommitModifierXattrCallback xattr_callback;
  GDestroyNotify xattr_destroy;
  gpointer xattr_user_data;

  GLnxTmpDir sepolicy_tmpdir;
  OstreeSePolicy *sepolicy;
  GHashTable *devino_cache;
};

typedef enum
{
  OSTREE_REPO_SYSROOT_KIND_UNKNOWN,
  OSTREE_REPO_SYSROOT_KIND_NO,                /* Not a system repo */
  OSTREE_REPO_SYSROOT_KIND_VIA_SYSROOT,       /* Constructed via ostree_sysroot_get_repo() */
  OSTREE_REPO_SYSROOT_KIND_IS_SYSROOT_OSTREE, /* We match /ostree/repo */
} OstreeRepoSysrootKind;

typedef struct
{
  GHashTable *refs;            /* (element-type utf8 utf8) */
  GHashTable *collection_refs; /* (element-type OstreeCollectionRef utf8) */
  OstreeRepoTransactionStats stats;
  /* Implementation of min-free-space-percent */
  gulong blocksize;
  fsblkcnt_t max_blocks;
  gboolean disable_auto_summary;
} OstreeRepoTxn;

typedef struct
{
  GMutex mutex;    /* All other members should only be accessed with this held */
  int fd;          /* The open file or flock file descriptor */
  guint shared;    /* Number of shared locks curently held */
  guint exclusive; /* Number of exclusive locks currently held */
} OstreeRepoLock;

typedef enum
{
  _OSTREE_FEATURE_NO,
  _OSTREE_FEATURE_MAYBE,
  _OSTREE_FEATURE_YES,
} _OstreeFeatureSupport;

/* Possible values for the sysroot.bootloader configuration variable */
typedef enum
{
  CFG_SYSROOT_BOOTLOADER_OPT_AUTO = 0,
  CFG_SYSROOT_BOOTLOADER_OPT_NONE,
  CFG_SYSROOT_BOOTLOADER_OPT_GRUB2,
  CFG_SYSROOT_BOOTLOADER_OPT_SYSLINUX,
  CFG_SYSROOT_BOOTLOADER_OPT_UBOOT,
  CFG_SYSROOT_BOOTLOADER_OPT_ZIPL,
  CFG_SYSROOT_BOOTLOADER_OPT_ABOOT,
  /* Non-exhaustive */
} OstreeCfgSysrootBootloaderOpt;

#if !defined(__s390x__)
#define CFG_SYSROOT_BOOTLOADER_DEFAULT_STR "auto"
#else
// There's nothing else on s390x.
#define CFG_SYSROOT_BOOTLOADER_DEFAULT_STR "zipl"
#endif

static const char *const CFG_SYSROOT_BOOTLOADER_OPTS_STR[] = {
  /* This must be kept in the same order as the enum */
  "auto", "none", "grub2", "syslinux", "uboot", "zipl", "aboot", NULL,
};

/**
 * OstreeRepo:
 *
 * Private instance structure.
 */
struct OstreeRepo
{
  GObject parent;

  char *stagedir_prefix;
  GLnxTmpDir commit_stagedir;
  GLnxLockFile commit_stagedir_lock;

  /* A cached fd-relative version, distinct from the case where we may have a
   * user-provided absolute path.
   */
  GFile *repodir_fdrel;
  GFile *repodir; /* May be %NULL if we were opened via ostree_repo_open_at() */
  int repo_dir_fd;
  int tmp_dir_fd;
  int cache_dir_fd;
  char *cache_dir;
  int objects_dir_fd;
  int uncompressed_objects_dir_fd;
  GFile *sysroot_dir;
  GWeakRef sysroot; /* Weak to avoid a circular ref; see also `is_system` */
  char *remotes_config_dir;

  OstreeRepoLock lock;

  GMutex txn_lock;
  OstreeRepoTxn txn;
  gboolean txn_locked;
  _OstreeFeatureSupport fs_verity_wanted;
  _OstreeFeatureSupport fs_verity_supported;
  OtTristate composefs_wanted;
  gboolean composefs_supported;

  GMutex cache_lock;
  guint dirmeta_cache_refcount;
  /* char * checksum → GVariant * for dirmeta objects, used in the checkout path */
  GHashTable *dirmeta_cache;

  gboolean inited;
  gboolean writable;
  gboolean is_on_fuse; /* TRUE if the repository is on a FUSE filesystem */
  OstreeRepoSysrootKind sysroot_kind;
  GError *writable_error;
  gboolean in_transaction;
  gboolean disable_fsync;
  gboolean per_object_fsync;
  gboolean disable_xattrs;
  guint zlib_compression_level;
  GHashTable *loose_object_devino_hash;
  GHashTable *updated_uncompressed_dirs;

  /* FIXME: The object sizes hash table is really per-commit state, not repo
   * state. Using a single table for the repo means that commits cannot be
   * built simultaneously if they're adding size information. This data should
   * probably be in OstreeMutableTree, but that's gone by the time the actual
   * commit is constructed. At that point the only commit state is in the root
   * OstreeRepoFile.
   */
  GHashTable *object_sizes;

  /* Cache the repo's device/inode to use for comparisons elsewhere */
  dev_t device;
  ino_t inode;
  uid_t owner_uid;              /* Cache of repo's owner uid */
  guint min_free_space_percent; /* See the min-free-space-percent config option */
  guint64 min_free_space_mb;    /* See the min-free-space-size config option */
  guint64 reserved_blocks;
  gboolean cleanup_stagedir;

  guint test_error_flags; /* OstreeRepoTestErrorFlags */

  GKeyFile *config;
  GHashTable *remotes;
  GMutex remotes_lock;
  OstreeRepoMode mode;
  gboolean enable_uncompressed_cache;
  gboolean generate_sizes;
  guint64 tmp_expiry_seconds;
  gchar *collection_id;
  gboolean add_remotes_config_dir; /* Add new remotes in remotes.d dir */
  gint lock_timeout_seconds;
  guint64 payload_link_threshold;
  gint fs_support_reflink; /* The underlying filesystem has support for ioctl (FICLONE..) */
  gchar **repo_finders;
  OstreeCfgSysrootBootloaderOpt bootloader; /* Configure which bootloader to use. */
  GHashTable
      *bls_append_values;     /* Parsed key-values from bls-append-except-default key in config. */
  gboolean enable_bootprefix; /* If true, prepend bootloader entries with /boot */
  guint boot_counting;

  OstreeRepo *parent_repo;
};

typedef struct
{
  dev_t dev;
  ino_t ino;
  char checksum[OSTREE_SHA256_STRING_LEN + 1];
} OstreeDevIno;

/* A MemoryCacheRef is an in-memory cache of objects (currently just DIRMETA).  This can
 * be used when performing an operation that traverses a repository in someway.  Currently,
 * the primary use case is ostree_repo_checkout_at() avoiding lots of duplicate dirmeta
 * lookups.
 */
typedef struct
{
  OstreeRepo *repo;
} OstreeRepoMemoryCacheRef;

void _ostree_repo_memory_cache_ref_init (OstreeRepoMemoryCacheRef *state, OstreeRepo *repo);

void _ostree_repo_memory_cache_ref_destroy (OstreeRepoMemoryCacheRef *state);
G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (OstreeRepoMemoryCacheRef, _ostree_repo_memory_cache_ref_destroy)

#define OSTREE_REPO_TMPDIR_STAGING "staging-"

gboolean _ostree_repo_allocate_tmpdir (int tmpdir_dfd, const char *tmpdir_prefix,
                                       GLnxTmpDir *tmpdir_out, GLnxLockFile *file_lock_out,
                                       gboolean *reusing_dir_out, GCancellable *cancellable,
                                       GError **error);

gboolean _ostree_repo_has_staging_prefix (const char *filename);

gboolean _ostree_repo_try_lock_tmpdir (int tmpdir_dfd, const char *tmpdir_name,
                                       GLnxLockFile *file_lock_out, gboolean *out_did_lock,
                                       GError **error);

gboolean _ostree_repo_ensure_loose_objdir_at (int dfd, const char *loose_path,
                                              GCancellable *cancellable, GError **error);

GFile *_ostree_repo_get_commit_metadata_loose_path (OstreeRepo *self, const char *checksum);

gboolean _ostree_repo_has_loose_object (OstreeRepo *self, const char *checksum,
                                        OstreeObjectType objtype, gboolean *out_is_stored,
                                        GCancellable *cancellable, GError **error);

gboolean _ostree_write_bareuser_metadata (int fd, guint32 uid, guint32 gid, guint32 mode,
                                          GVariant *xattrs, GError **error);

gboolean _ostree_repo_write_directory_meta (OstreeRepo *self, GFileInfo *file_info,
                                            GVariant *xattrs, guchar **out_csum,
                                            GCancellable *cancellable, GError **error);
gboolean _ostree_repo_update_refs (OstreeRepo *self, GHashTable *refs, GCancellable *cancellable,
                                   GError **error);

gboolean _ostree_repo_update_collection_refs (OstreeRepo *self, GHashTable *refs,
                                              GCancellable *cancellable, GError **error);

gboolean _ostree_repo_file_replace_contents (OstreeRepo *self, int dfd, const char *path,
                                             const guint8 *buf, gsize len,
                                             GCancellable *cancellable, GError **error);

gboolean _ostree_repo_write_ref (OstreeRepo *self, const char *remote,
                                 const OstreeCollectionRef *ref, const char *rev, const char *alias,
                                 GCancellable *cancellable, GError **error);

OstreeRepoFile *_ostree_repo_file_new_for_commit (OstreeRepo *repo, const char *commit,
                                                  GError **error);

OstreeRepoFile *_ostree_repo_file_new_root (OstreeRepo *repo, const char *contents_checksum,
                                            const char *metadata_checksum);

gboolean _ostree_repo_traverse_dirtree_internal (OstreeRepo *repo, const char *dirtree_checksum,
                                                 int recursion_depth, GHashTable *inout_reachable,
                                                 GHashTable *inout_content_names,
                                                 GCancellable *cancellable, GError **error);

OstreeRepoCommitFilterResult _ostree_repo_commit_modifier_apply (OstreeRepo *self,
                                                                 OstreeRepoCommitModifier *modifier,
                                                                 const char *path,
                                                                 GFileInfo *file_info,
                                                                 GFileInfo **out_modified_info);

void _ostree_repo_setup_generate_sizes (OstreeRepo *self, OstreeRepoCommitModifier *modifier);

gboolean _ostree_repo_remote_name_is_file (const char *remote_name);

#ifndef OSTREE_DISABLE_GPGME
OstreeGpgVerifyResult *_ostree_repo_gpg_verify_with_metadata (
    OstreeRepo *self, GBytes *signed_data, GVariant *metadata, const char *remote_name,
    GFile *keyringdir, GFile *extra_keyring, GCancellable *cancellable, GError **error);

OstreeGpgVerifyResult *_ostree_repo_verify_commit_internal (
    OstreeRepo *self, const char *commit_checksum, const char *remote_name, GFile *keyringdir,
    GFile *extra_keyring, GCancellable *cancellable, GError **error);
#endif /* OSTREE_DISABLE_GPGME */

typedef enum
{
  _OSTREE_REPO_IMPORT_FLAGS_NONE = 0,
  _OSTREE_REPO_IMPORT_FLAGS_TRUSTED = (1 << 0),
  _OSTREE_REPO_IMPORT_FLAGS_VERIFY_BAREUSERONLY = (1 << 1),
} OstreeRepoImportFlags;

gboolean _ostree_repo_import_object (OstreeRepo *self, OstreeRepo *source, OstreeObjectType objtype,
                                     const char *checksum, OstreeRepoImportFlags flags,
                                     GCancellable *cancellable, GError **error);

gboolean _ostree_repo_commit_tmpf_final (OstreeRepo *self, const char *checksum,
                                         OstreeObjectType objtype, GLnxTmpfile *tmpf,
                                         GCancellable *cancellable, GError **error);

typedef struct
{
  gboolean initialized;
  gpointer opaque0[10];
  guint opaque1[10];
} OstreeRepoBareContent;
void _ostree_repo_bare_content_cleanup (OstreeRepoBareContent *regwrite);
G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (OstreeRepoBareContent, _ostree_repo_bare_content_cleanup)

gboolean _ostree_repo_bare_content_open (OstreeRepo *self, const char *checksum,
                                         guint64 content_len, guint uid, guint gid, guint mode,
                                         GVariant *xattrs, OstreeRepoBareContent *out_regwrite,
                                         GCancellable *cancellable, GError **error);

gboolean _ostree_repo_bare_content_write (OstreeRepo *repo, OstreeRepoBareContent *barewrite,
                                          const guint8 *buf, size_t len, GCancellable *cancellable,
                                          GError **error);

gboolean _ostree_repo_bare_content_commit (OstreeRepo *self, OstreeRepoBareContent *barewrite,
                                           char *checksum_buf, size_t buflen,
                                           GCancellable *cancellable, GError **error);

OstreeContentWriter *_ostree_content_writer_new (OstreeRepo *repo, const char *checksum, guint uid,
                                                 guint gid, guint mode, guint64 content_len,
                                                 GVariant *xattrs, GError **error);

gboolean _ostree_repo_load_file_bare (OstreeRepo *self, const char *checksum, int *out_fd,
                                      struct stat *out_stbuf, char **out_symlink,
                                      GVariant **out_xattrs, GCancellable *cancellable,
                                      GError **error);

gboolean _ostree_repo_update_mtime (OstreeRepo *self, GError **error);
gboolean _ostree_repo_syncfs (OstreeRepo *self, GError **error);

gboolean _ostree_repo_add_remote (OstreeRepo *self, OstreeRemote *remote);
gboolean _ostree_repo_remove_remote (OstreeRepo *self, OstreeRemote *remote);
OstreeRemote *_ostree_repo_get_remote (OstreeRepo *self, const char *name, GError **error);
OstreeRemote *_ostree_repo_get_remote_inherited (OstreeRepo *self, const char *name,
                                                 GError **error);

gboolean _ostree_repo_maybe_regenerate_summary (OstreeRepo *self, GCancellable *cancellable,
                                                GError **error);

gboolean _ostree_repo_parse_fsverity_config (OstreeRepo *self, GError **error);
gboolean _ostree_repo_parse_composefs_config (OstreeRepo *self, GError **error);

gboolean _ostree_tmpf_fsverity_core (GLnxTmpfile *tmpf, _OstreeFeatureSupport fsverity_requested,
                                     GBytes *signature, gboolean *supported, GError **error);

gboolean _ostree_tmpf_fsverity (OstreeRepo *self, GLnxTmpfile *tmpf, GBytes *signature,
                                GError **error);

gboolean _ostree_ensure_fsverity (OstreeRepo *self, gboolean allow_enoent, int dirfd,
                                  const char *path, gboolean *supported, GError **error);

gboolean _ostree_repo_verify_bindings (const char *collection_id, const char *ref_name,
                                       GVariant *commit, GError **error);

GHashTable *ostree_repo_list_objects_set (OstreeRepo *self, OstreeRepoListObjectsFlags flags,
                                          GCancellable *cancellable, GError **error);

gboolean _ostree_repo_transaction_write_repo_metadata (OstreeRepo *self,
                                                       GVariant *additional_metadata,
                                                       char **out_checksum,
                                                       GCancellable *cancellable, GError **error);

/**
 * OstreeRepoAutoTransaction:
 *
 * A transaction guard for a specific #OstreeRepo. It can be explicitly
 * completed through abort/commit. If the guard has not been completed
 * beforehand, on cleanup it is automatically aborted.
 *
 * Taken from flatpak; may be made into public API later
 */
typedef struct
{
  gint atomic_refcount;
  OstreeRepo *repo;
} OstreeRepoAutoTransaction;

OstreeRepoAutoTransaction *
_ostree_repo_auto_transaction_start (OstreeRepo *repo, GCancellable *cancellable, GError **error);

gboolean _ostree_repo_auto_transaction_abort (OstreeRepoAutoTransaction *txn,
                                              GCancellable *cancellable, GError **error);

gboolean _ostree_repo_auto_transaction_commit (OstreeRepoAutoTransaction *txn,
                                               OstreeRepoTransactionStats *out_stats,
                                               GCancellable *cancellable, GError **error);

OstreeRepoAutoTransaction *_ostree_repo_auto_transaction_ref (OstreeRepoAutoTransaction *txn);

void _ostree_repo_auto_transaction_unref (OstreeRepoAutoTransaction *txn);

GType _ostree_repo_auto_transaction_get_type (void);

G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoAutoTransaction, _ostree_repo_auto_transaction_unref);

/* Internal function to break a circular dependency:
 * should not be made into public API, even if the rest is */
OstreeRepoAutoTransaction *_ostree_repo_auto_transaction_new (OstreeRepo *repo);

typedef struct OstreeComposefsTarget OstreeComposefsTarget;

GType ostree_composefs_target_get_type (void) G_GNUC_CONST;
OstreeComposefsTarget *ostree_composefs_target_new (void);
OstreeComposefsTarget *ostree_composefs_target_ref (OstreeComposefsTarget *target);
void ostree_composefs_target_unref (OstreeComposefsTarget *target);
gboolean ostree_composefs_target_write (OstreeComposefsTarget *target, int fd,
                                        guchar **out_fsverity_digest, GCancellable *cancellable,
                                        GError **error);

gboolean _ostree_repo_checkout_composefs (OstreeRepo *self, OtTristate verity,
                                          OstreeComposefsTarget *target, OstreeRepoFile *source,
                                          GCancellable *cancellable, GError **error);
static inline gboolean
composefs_not_supported (GError **error)
{
  g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
               "composefs is not supported in this ostree build");
  return FALSE;
}

G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeComposefsTarget, ostree_composefs_target_unref)

G_END_DECLS
