/* SPDX-License-Identifier: LGPL-2.1+ */

#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif
#define __STDC_FORMAT_MACROS /* Required for PRIu64 to work. */
#include <inttypes.h>	/* Required for PRIu64 to work. */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "config.h"
#include "log.h"
#include "memory_utils.h"
#include "storage.h"
#include "storage_utils.h"
#include "utils.h"

#ifndef HAVE_STRLCPY
#include "include/strlcpy.h"
#endif

lxc_log_define(rbd, lxc);

struct rbd_args {
	const char *osd_pool_name;
	const char *rbd_name;
	const char *size;
};

static int rbd_create_wrapper(void *data)
{
	struct rbd_args *args = data;

	execlp("rbd", "rbd", "create", "--pool", args->osd_pool_name,
	       args->rbd_name, "--size", args->size, (char *)NULL);

	return -1;
}

static int rbd_map_wrapper(void *data)
{
	struct rbd_args *args = data;

	execlp("rbd", "rbd", "map", "--pool", args->osd_pool_name,
	       args->rbd_name, (char *)NULL);

	return -1;
}

static int rbd_unmap_wrapper(void *data)
{
	struct rbd_args *args = data;

	execlp("rbd", "rbd", "unmap", args->rbd_name, (char *)NULL);

	return -1;
}

static int rbd_delete_wrapper(void *data)
{
	struct rbd_args *args = data;

	execlp("rbd", "rbd", "rm", args->rbd_name, (char *)NULL);

	return -1;
}

int rbd_clonepaths(struct lxc_storage *orig, struct lxc_storage *new,
		   const char *oldname, const char *cname, const char *oldpath,
		   const char *lxcpath, int snap, uint64_t newsize,
		   struct lxc_conf *conf)
{
	ERROR("rbd clonepaths not implemented");
	return -1;
}

int rbd_create(struct lxc_storage *bdev, const char *dest, const char *n,
	       struct bdev_specs *specs, const struct lxc_conf *conf)
{
	const char *rbdpool, *fstype;
	uint64_t size;
	int ret, len;
	char sz[24];
	const char *cmd_args[2];
	char cmd_output[PATH_MAX];
	const char *rbdname = n;
	struct rbd_args args = {0};

	if (!specs)
		return -1;

	rbdpool = specs->rbd.rbdpool;
	if (!rbdpool)
		rbdpool = lxc_global_config_value("lxc.bdev.rbd.rbdpool");

	if (specs->rbd.rbdname)
		rbdname = specs->rbd.rbdname;

	/* source device /dev/rbd/lxc/ctn */
	len = strlen(rbdpool) + strlen(rbdname) + 4 + 11;
	bdev->src = malloc(len);
	if (!bdev->src) {
		ERROR("Failed to allocate memory");
		return -1;
	}

	ret = snprintf(bdev->src, len, "rbd:/dev/rbd/%s/%s", rbdpool, rbdname);
	if (ret < 0 || ret >= len) {
		ERROR("Failed to create string");
		return -1;
	}

	/* fssize is in bytes */
	size = specs->fssize;
	if (!size)
		size = DEFAULT_FS_SIZE;

	/* in megabytes for rbd tool */
	ret = snprintf(sz, 24, "%" PRIu64, size / 1024 / 1024);
	if (ret < 0 || ret >= 24) {
		ERROR("Failed to create string");
		return -1;
	}

	args.osd_pool_name = rbdpool;
	args.rbd_name = rbdname;
	args.size = sz;
	ret = run_command(cmd_output, sizeof(cmd_output), rbd_create_wrapper,
			  (void *)&args);
	if (ret < 0) {
		ERROR("Failed to create rbd storage volume \"%s\": %s", rbdname,
		      cmd_output);
		return -1;
	}

	ret = run_command(cmd_output, sizeof(cmd_output), rbd_map_wrapper,
			  (void *)&args);
	if (ret < 0) {
		ERROR("Failed to map rbd storage volume \"%s\": %s", rbdname,
		      cmd_output);
		return -1;
	}

	fstype = specs->fstype;
	if (!fstype)
		fstype = DEFAULT_FSTYPE;

	cmd_args[0] = fstype;
	cmd_args[1] = lxc_storage_get_path(bdev->src, bdev->type);
	ret = run_command(cmd_output, sizeof(cmd_output), do_mkfs_exec_wrapper,
			  (void *)cmd_args);
	if (ret < 0) {
		ERROR("Failed to map rbd storage volume \"%s\": %s", rbdname,
		      cmd_output);
		return -1;
	}

	bdev->dest = strdup(dest);
	if (!bdev->dest) {
		ERROR("Failed to duplicate string \"%s\"", dest);
		return -1;
	}

	ret = mkdir_p(bdev->dest, 0755);
	if (ret < 0 && errno != EEXIST) {
		ERROR("Failed to create directory \"%s\"", bdev->dest);
		return -1;
	}

	TRACE("Created rbd storage volume \"%s\"", bdev->dest);
	return 0;
}

int rbd_destroy(struct lxc_storage *orig)
{
	__do_free char *rbdfullname = NULL;
	int ret;
	const char *src;
	char cmd_output[PATH_MAX];
	struct rbd_args args = {0};
	size_t len;

	src = lxc_storage_get_path(orig->src, orig->type);
	if (file_exists(src)) {
		args.rbd_name = src;
		ret = run_command(cmd_output, sizeof(cmd_output),
				  rbd_unmap_wrapper, (void *)&args);
		if (ret < 0) {
			ERROR("Failed to map rbd storage volume \"%s\": %s",
			      src, cmd_output);
			return -1;
		}
	}

	len = strlen(src);
	rbdfullname = must_realloc(NULL, len - 8);
	(void)strlcpy(rbdfullname, &src[9], len - 8);
	args.rbd_name = rbdfullname;

	ret = run_command(cmd_output, sizeof(cmd_output),
			rbd_delete_wrapper, (void *)&args);
	if (ret < 0) {
		ERROR("Failed to delete rbd storage volume \"%s\": %s",
		      rbdfullname, cmd_output);
		return -1;
	}

	return 0;
}

bool rbd_detect(const char *path)
{
	if (!strncmp(path, "rbd:", 4))
		return true;

	if (!strncmp(path, "/dev/rbd/", 9))
		return true;

	return false;
}

int rbd_mount(struct lxc_storage *bdev)
{
	const char *src;

	if (strcmp(bdev->type, "rbd"))
		return -22;

	if (!bdev->src || !bdev->dest)
		return -22;

	src = lxc_storage_get_path(bdev->src, bdev->type);
	if (!file_exists(src)) {
		/* If blkdev does not exist it should be mapped, because it is
		 * not persistent on reboot.
		 */
		ERROR("Block device %s is not mapped.", bdev->src);
		return -1;
	}

	return mount_unknown_fs(src, bdev->dest, bdev->mntopts);
}

int rbd_umount(struct lxc_storage *bdev)
{
	if (strcmp(bdev->type, "rbd"))
		return -22;

	if (!bdev->src || !bdev->dest)
		return -22;

	return umount(bdev->dest);
}
