Viewing: liblustreapi_pcc.c

// SPDX-License-Identifier: LGPL-2.1+
/*
 * Copyright (c) 2017, DDN Storage Corporation.
 */
/*
 * This file is part of Lustre, http://www.lustre.org/
 *
 * lustreapi library for Persistent Client Cache.
 *
 * Author: Li Xi <lixi@ddn.com>
 * Author: Qian Yingjin <qian@ddn.com>
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/xattr.h>
#include <fcntl.h>
#include <lustre/lustreapi.h>
#include <linux/lustre/lustre_user.h>
#include <linux/lustre/lustre_fid.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <lnetconfig/cyaml.h>
#include "lustreapi_internal.h"
#include "libhsm_scanner.h"

/*
 * Fetch and attach a file to readwrite PCC.
 */
static int llapi_pcc_attach_rw_fd(int fd, __u32 archive_id)
{
	int rc;
	struct ll_ioc_lease *data;

	rc = llapi_lease_acquire(fd, LL_LEASE_WRLCK);
	if (rc < 0) {
		llapi_error(LLAPI_MSG_ERROR, rc, "cannot get lease");
		return rc;
	}

	data = malloc(offsetof(typeof(*data), lil_ids[1]));
	if (!data) {
		rc = -ENOMEM;
		llapi_err_noerrno(LLAPI_MSG_ERROR,
				  "failed to allocate memory");
		return rc;
	}

	data->lil_mode = LL_LEASE_UNLCK;
	data->lil_flags = LL_LEASE_PCC_ATTACH;
	data->lil_count = 1;
	data->lil_ids[0] = archive_id;
	rc = llapi_lease_set(fd, data);
	if (rc <= 0) {
		if (rc == 0) /* lost lease lock */
			rc = -EBUSY;
		llapi_error(LLAPI_MSG_ERROR, rc,
			    "cannot attach with ID: %u", archive_id);
	} else {
		rc = 0;
	}

	free(data);
	return rc;
}

static int llapi_pcc_attach_rw(const char *path, __u32 archive_id)
{
	int fd;
	int rc;

	fd = open(path, O_RDWR | O_NONBLOCK);
	if (fd < 0) {
		rc = -errno;
		llapi_error(LLAPI_MSG_ERROR, rc, "cannot open '%s'",
			    path);
		return rc;
	}

	rc = llapi_pcc_attach_rw_fd(fd, archive_id);

	close(fd);
	return rc;
}

static int llapi_pcc_attach_ro_fd(int fd, __u32 roid)
{
	struct lu_pcc_attach attach;
	int rc;

	attach.pcca_id = roid;
	attach.pcca_type = LU_PCC_READONLY;
	rc = ioctl(fd, LL_IOC_PCC_ATTACH, &attach);
	if (rc) {
		rc = -errno;
		llapi_error(LLAPI_MSG_ERROR, rc,
			    "cannot attach the file to PCC with ID %u failed",
			    roid);
	}

	return rc;
}

static int llapi_pcc_attach_ro(const char *path, __u32 roid)
{
	int fd;
	int rc;

	if (strlen(path) <= 0 || path[0] != '/') {
		rc = -EINVAL;
		llapi_err_noerrno(LLAPI_MSG_ERROR, "invalid file path: %s",
				  path);
		return rc;
	}

	fd = open(path, O_RDONLY);
	if (fd < 0) {
		rc = -errno;
		llapi_error(LLAPI_MSG_ERROR, rc, "open file: %s failed",
			    path);
		return rc;
	}

	rc = llapi_pcc_attach_ro_fd(fd, roid);

	close(fd);
	return rc;
}

int llapi_pcc_attach(const char *path, __u32 id, enum lu_pcc_type type)
{
	int rc;

	switch (type & LU_PCC_TYPE_MASK) {
	case LU_PCC_READWRITE:
		rc = llapi_pcc_attach_rw(path, id);
		break;
	case LU_PCC_READONLY:
		rc = llapi_pcc_attach_ro(path, id);
		break;
	default:
		rc = -EINVAL;
		break;
	}
	return rc;
}

static int llapi_pcc_attach_rw_fid(const char *mntpath,
				   const struct lu_fid *fid,
				   __u32 rwid)
{
	int rc;
	int fd;

	fd = llapi_open_by_fid(mntpath, fid, O_RDWR | O_NONBLOCK);
	if (fd < 0) {
		rc = -errno;
		llapi_error(LLAPI_MSG_ERROR, rc,
			    "llapi_open_by_fid for " DFID "failed",
			    PFID(fid));
		return rc;
	}

	rc = llapi_pcc_attach_rw_fd(fd, rwid);

	close(fd);
	return rc;
}

static int llapi_pcc_attach_ro_fid(const char *mntpath,
				   const struct lu_fid *fid,
				   __u32 roid)
{
	int rc;
	int fd;

	fd = llapi_open_by_fid(mntpath, fid, O_RDONLY);
	if (fd < 0) {
		rc = -errno;
		llapi_error(LLAPI_MSG_ERROR, rc,
			    "llapi_open_by_fid for " DFID "failed",
			    PFID(fid));
		return rc;
	}

	rc = llapi_pcc_attach_ro_fd(fd, roid);

	close(fd);
	return rc;
}

int llapi_pcc_attach_fid(const char *mntpath, const struct lu_fid *fid,
			 __u32 id, enum lu_pcc_type type)
{
	int rc;

	switch (type & LU_PCC_TYPE_MASK) {
	case LU_PCC_READWRITE:
		rc = llapi_pcc_attach_rw_fid(mntpath, fid, id);
		break;
	case LU_PCC_READONLY:
		rc = llapi_pcc_attach_ro_fid(mntpath, fid, id);
		break;
	default:
		rc = -EINVAL;
		break;
	}
	return rc;
}

/**
 * llapi_pcc_attach_fid_str() - Attach file to the PCC
 * @mntpath: Fullpath to the client mount point.
 * @fidstr: file to be attach (FID)
 * @id: unique id
 * @type: PCC type
 *
 * Return %0 on successful attachment or %negative on error
 */
int llapi_pcc_attach_fid_str(const char *mntpath, const char *fidstr,
			     __u32 id, enum lu_pcc_type type)
{
	int rc;
	struct lu_fid fid;

	rc = llapi_fid_parse(fidstr, &fid, NULL);
	if (rc) {
		llapi_err_noerrno(LLAPI_MSG_ERROR,
				  "PCC: '%s' is not a valid FID", fidstr);
		return -EINVAL;
	}

	rc = llapi_pcc_attach_fid(mntpath, &fid, id, type);

	return rc;
}

/**
 * llapi_pcc_detach_fd() - detach PCC cache of a file by using fd.
 * @fd: File handle.
 * @flags: Detach flags.
 *
 * Return %0 on success or %-errno on failure
 */
int llapi_pcc_detach_fd(int fd, __u32 flags)
{
	struct lu_pcc_detach detach;
	int rc;

	detach.pccd_flags = flags;
	rc = ioctl(fd, LL_IOC_PCC_DETACH, &detach);
	/* If error, save errno value */
	rc = rc ? -errno : 0;

	return rc;
}

/**
 * llapi_pcc_detach_at() - detach PCC cache of a file via FID.
 * @dirfd: Dir file handle.
 * @fid: FID of the file.
 * @flags: Detach flags.
 *
 * Return %0 on success or %-errno on failure
 */
int llapi_pcc_detach_at(int dirfd, const struct lu_fid *fid,
			enum lu_pcc_detach_flags flags)
{
	struct lu_pcc_detach_fid detach = {
		.pccd_fid = *fid,
		.pccd_flags = flags,
	};
	int rc;

	rc = ioctl(dirfd, LL_IOC_PCC_DETACH_BY_FID, &detach);
	return rc ? -errno : 0;
}

/**
 * llapi_pcc_detach_fid() - detach PCC cache of a file via FID.
 * @mntpath: Fullpath to the client mount point.
 * @fid: FID of the file.
 * @flags: Detach flags.
 *
 * Return %0 on success or %-errno on failure
 */
int llapi_pcc_detach_fid(const char *mntpath, const struct lu_fid *fid,
			 __u32 flags)
{
	int rc;
	int fd;
	struct lu_pcc_detach_fid detach;

	rc = llapi_root_path_open(mntpath, &fd);
	if (rc) {
		llapi_error(LLAPI_MSG_ERROR, rc, "cannot get root path: %s",
			    mntpath);
		return rc;
	}

	/*
	 * PCC prefetching algorithm scans Lustre OPEN/CLOSE changelogs
	 * to determine the candidate files needing to prefetch into
	 * PCC. To avoid generattion of unnecessary open/close changelogs,
	 * we implement a new dir ioctl LL_IOC_PCC_DETACH_BY_FID to detach
	 * files.
	 */
	detach.pccd_fid = *fid;
	detach.pccd_flags = flags;
	rc = ioctl(fd, LL_IOC_PCC_DETACH_BY_FID, &detach);
	rc = rc ? -errno : 0;

	close(fd);
	return rc;
}

/**
 * llapi_pcc_detach_fid_str() - detach PCC cache of a file via FID.
 * @mntpath: Fullpath to the client mount point.
 * @fidstr: FID string of the file.
 * @flags: Detach flags.
 *
 * Return %0 on success or %-errno on failure
 */
int llapi_pcc_detach_fid_str(const char *mntpath, const char *fidstr,
			     __u32 flags)
{
	int rc;
	struct lu_fid fid;

	rc = llapi_fid_parse(fidstr, &fid, NULL);
	if (rc) {
		llapi_err_noerrno(LLAPI_MSG_ERROR,
				  "PCC: '%s' is not a valid FID", fidstr);
		return -EINVAL;
	}

	rc = llapi_pcc_detach_fid(mntpath, &fid, flags);

	return rc;
}

/**
 * llapi_pcc_detach_file() - detach PCC cache of a file.
 * @path: Fullpath to the file to operate on.
 * @flags: Detach flags.
 *
 * Return %0 on success or %-errno on failure
 */
int llapi_pcc_detach_file(const char *path, __u32 flags)
{
	int rc;
	int fd;

	/* Specify O_CIPHERTEXT | O_DIRECT flags to allow pcc detach
	 * on encrypted file without the key.
	 */
	fd = open(path, O_RDWR | O_NONBLOCK | O_CIPHERTEXT | O_DIRECT);
	if (fd < 0) {
		rc = -errno;
		llapi_error(LLAPI_MSG_ERROR, rc, "cannot open '%s'",
			    path);
		return rc;
	}

	rc = llapi_pcc_detach_fd(fd, flags);
	close(fd);
	return rc;
}

/**
 * llapi_pcc_state_get_fd() - Return the current PCC state related to a file.
 * @fd: File handle for the parent directory.
 * @state: PCC state info.
 *
 * Return %0 on success or %-errno on failure
 */
int llapi_pcc_state_get_fd(int fd, struct lu_pcc_state *state)
{
	int rc;

	rc = ioctl(fd, LL_IOC_PCC_STATE, state);
	/* If error, save errno value */
	rc = rc ? -errno : 0;

	return rc;
}

/*
 * Return the current PCC state related to file pointed by a path.
 *
 * see llapi_pcc_state_get_fd() for args use and return
 */
int llapi_pcc_state_get(const char *path, struct lu_pcc_state *state)
{
	char *path_copy;
	char *filename;
	int fd;
	int rc;

	fd = open_parent(path);
	if (fd == -1) {
		rc = -errno;
		llapi_error(LLAPI_MSG_ERROR, rc, "can not open %s", path);
		return rc;
	}

	path_copy = strdup(path);
	if (path_copy == NULL) {
		close(fd);
		return -ENOMEM;
	}

	filename = basename(path_copy);
	state->pccs_namelen = strlen(filename) + 1;
	strncpy(state->pccs_path, filename, sizeof(state->pccs_path) - 1);

	rc = llapi_pcc_state_get_fd(fd, state);
	if (rc != 0) {
		rc = -errno;
		llapi_error(LLAPI_MSG_ERROR, rc, "Get PCC state on %s failed",
			    path);
	}

	close(fd);
	free(path_copy);

	return rc;
}

/**
 * llapi_pccdev_set() - Add/delete a PCC backend on a client.
 * @mntpath: Fullpath to the client mount point.
 * @cmd: command to be executed
 *
 * Return %0 on success or %-errno on failure
 */
int llapi_pccdev_set(const char *mntpath, const char *cmd)
{
	char buf[sizeof(struct obd_uuid)];
	glob_t path;
	ssize_t count;
	int fd;
	int rc;

	rc = llapi_getname(mntpath, buf, sizeof(buf));
	if (rc < 0) {
		llapi_error(LLAPI_MSG_ERROR, rc,
			    "cannot get name for '%s'", mntpath);
		return rc;
	}

	rc = cfs_get_param_paths(&path, "llite/%s/pcc", buf);
	if (rc != 0)
		return -errno;

	fd = open(path.gl_pathv[0], O_WRONLY);
	if (fd < 0) {
		rc = -errno;
		llapi_error(LLAPI_MSG_ERROR, rc, "error opening %s",
			    path.gl_pathv[0]);
		goto out;
	}

	count = write(fd, cmd, strlen(cmd));
	if (count < 0) {
		rc = errno;
		if (errno != EIO)
			llapi_error(LLAPI_MSG_ERROR, rc,
				    "error: setting llite.%s.pcc='%s'",
				    buf, cmd);
	} else if (count < strlen(cmd)) { /* Truncate case */
		rc = -EINVAL;
		llapi_error(LLAPI_MSG_ERROR, rc,
			    "setting llite.%s.pcc='%s': wrote only %zd",
			    buf, cmd, count);
	}
	close(fd);
out:
	cfs_free_param_data(&path);
	return rc;
}

/**
 * llapi_pccdev_get() - List all PCC backend devices on a client.
 * @mntpath: Fullpath to the client mount point.
 *
 * Return %0 on success or %-errno on failure
 */
int llapi_pccdev_get(const char *mntpath)
{
	char pathbuf[sizeof(struct obd_uuid)];
	char buf[65536]; /* large engough to hold PPC dev list */
	glob_t path;
	int fd;
	int rc;

	rc = llapi_getname(mntpath, pathbuf, sizeof(pathbuf));
	if (rc < 0) {
		llapi_error(LLAPI_MSG_ERROR, rc,
			    "cannot get name for '%s'", mntpath);
		return rc;
	}

	rc = cfs_get_param_paths(&path, "llite/%s/pcc", pathbuf);
	if (rc != 0)
		return -errno;

	/* Read the contents of file to stdout */
	fd = open(path.gl_pathv[0], O_RDONLY);
	if (fd < 0) {
		rc = -errno;
		llapi_error(LLAPI_MSG_ERROR, rc,
			    "error: pccdev_get: opening '%s'",
			    path.gl_pathv[0]);
		goto out_free_param;
	}

	while (1) {
		ssize_t count = read(fd, buf, sizeof(buf));

		if (count == 0)
			break;
		if (count < 0) {
			rc = -errno;
			if (errno != EIO) {
				llapi_error(LLAPI_MSG_ERROR, rc,
					   "error: pccdev_get: reading failed");
			}
			break;
		}

		if (fwrite(buf, 1, count, stdout) != count) {
			rc = -errno;
			llapi_error(LLAPI_MSG_ERROR, rc,
				    "error: get_param: write to stdout");
			break;
		}
	}

	close(fd);
	cfs_free_param_data(&path);
	return rc;
out_free_param:
	cfs_free_param_data(&path);
	return rc;
}

static int llapi_pcc_scan_detach(const char *pname, const char *fname,
				 struct hsm_scan_control *hsc)
{
	struct lu_pcc_detach_fid detach;
	char fullname[PATH_MAX];
	char fidstr[FID_LEN + 1];
	const char *fidname;
	bool lov_file;
	int rc;

	/* It is the saved lov file when archive on HSM backend. */
	detach.pccd_flags = PCC_DETACH_FL_UNCACHE;
	lov_file = endswith(fname, ".lov");
	if (lov_file) {
		size_t len;

		len = strlen(fname) - strlen(".lov");
		if (len >= sizeof(fidstr)) {
			rc = -ENAMETOOLONG;
			errno = ENAMETOOLONG;
			llapi_error(LLAPI_MSG_ERROR, rc,
				    "Too long PCC-RO fname %s/%s",
				    pname, fname);
			return rc;
		}
		strncpy(fidstr, fname, FID_LEN);
		fidstr[len] = '\0';
		detach.pccd_flags |= PCC_DETACH_FL_KNOWN_READWRITE;
		fidname = fidstr;
	} else {
		fidname = fname;
	}

	rc = llapi_fid_parse(fidname, &detach.pccd_fid, NULL);
	if (rc) {
		llapi_err_noerrno(LLAPI_MSG_ERROR,
				  "PCC: '%s' is not a valid FID", fidname);
		return rc;
	}

	llapi_printf(LLAPI_MSG_DEBUG, "Handle the file: %s\n", fidname);

	rc = ioctl(hsc->hsc_mntfd, LL_IOC_PCC_DETACH_BY_FID, &detach);
	if (rc) {
		rc = -errno;
		llapi_error(LLAPI_MSG_ERROR, rc,
			    "failed to detach file '%s'\n", fidname);
		return rc;
	}

	if (detach.pccd_flags & PCC_DETACH_FL_CACHE_REMOVED) {
		llapi_printf(LLAPI_MSG_DEBUG,
			     "Detach and remove the PCC cached file: %s\n",
			     fidname);
	} else if (detach.pccd_flags & PCC_DETACH_FL_ATTACHING) {
		llapi_printf(LLAPI_MSG_DEBUG,
			     "'%s' is being attached, skip it", fidname);
	} else {
		snprintf(fullname, sizeof(fullname), "%s/%s", pname, fidname);
		llapi_printf(LLAPI_MSG_DEBUG,
			     "Remove non-cached file: %s flags: %X\n",
			     fullname, detach.pccd_flags);
		if (unlink(fullname) && errno != ENOENT) {
			rc = -errno;
			llapi_error(LLAPI_MSG_ERROR, rc,
				    "Unlink %s failed", fullname);
		}

		if (detach.pccd_flags & PCC_DETACH_FL_KNOWN_READWRITE) {
			snprintf(fullname, sizeof(fullname), "%s/%s",
				 pname, fname);
			/* Remove *.lov file */
			unlink(fullname);
		}
	}

	return rc;
}

static int llapi_pcc_del_internal(const char *mntpath, const char *pccpath,
				  enum hsmtool_type type,
				  enum lu_pcc_cleanup_flags flags)
{
	struct hsm_scan_control hsc = {
		.hsc_type = type,
		.hsc_mntpath = mntpath,
		.hsc_hsmpath = pccpath,
		.hsc_mntfd = -1,
		.hsc_func = llapi_pcc_scan_detach,
		.hsc_errnum = 0,
	};
	char cmd[PATH_MAX];
	int rc;

	snprintf(cmd, sizeof(cmd), "del %s", pccpath);
	rc = llapi_pccdev_set(mntpath, cmd);
	if (rc < 0) {
		llapi_error(LLAPI_MSG_ERROR, rc,
			    "failed to run '%s' on %s", cmd, mntpath);
		return rc;
	}

	if (flags & PCC_CLEANUP_FL_KEEP_DATA)
		return 0;

	hsc.hsc_mntfd = open(mntpath, O_RDONLY);
	if (hsc.hsc_mntfd < 0) {
		rc = -errno;
		llapi_error(LLAPI_MSG_ERROR, rc,
			    "cannot open '%s'", mntpath);
		return rc;
	}

	rc = hsm_scan_process(&hsc);
	close(hsc.hsc_mntfd);

	return rc;
}

struct pcc_cmd_handler;

typedef int (*pcc_handler_t)(struct cYAML *node, struct pcc_cmd_handler *pch);

enum pcc_cmd_t {
	PCC_CMD_DEL,
	PCC_CMD_CLEAR,
	PCC_CMD_BACKEND_SELECT,
};

struct pcc_cmd_handler {
	enum pcc_cmd_t			 pch_cmd;
	enum lu_pcc_type		 pch_type;
	bool				 pch_iter_cont;
	enum lu_pcc_cleanup_flags	 pch_flags;
	const char			*pch_mntpath;
	const char			*pch_pccpath;
	pcc_handler_t			 pch_cb;
	__u32				 pch_id;
};

static int llapi_pcc_yaml_cb_helper(struct pcc_cmd_handler *pch)
{
	struct cYAML *tree = NULL, *err_rc = NULL, *pcc_node = NULL,
		     *node = NULL;
	char pathbuf[sizeof(struct obd_uuid)];
	glob_t path;
	int rc;

	rc = llapi_getname(pch->pch_mntpath, pathbuf, sizeof(pathbuf));
	if (rc < 0) {
		llapi_error(LLAPI_MSG_ERROR, rc,
			    "cannot get name for '%s'\n", pch->pch_mntpath);
		return rc;
	}

	rc = cfs_get_param_paths(&path, "llite/%s/pcc", pathbuf);
	if (rc != 0)
		return -errno;

	tree = cYAML_build_tree(path.gl_pathv[0], NULL, 0, &err_rc, false);
	if (!tree) {
		rc = -EINVAL;
		llapi_error(LLAPI_MSG_ERROR, rc,
			    "cannot parse YAML file %s\n", path.gl_pathv[0]);
		cYAML_build_error(rc, -1, "yaml", "from PCC yaml",
				  "can't parse", &err_rc);
		cYAML_print_tree2file(stderr, err_rc);
		cYAML_free_tree(err_rc);
		goto out_free;
	}

	pcc_node = cYAML_get_object_item(tree, "pcc");
	if (!pcc_node)
		goto out_free;

	if (!cYAML_is_sequence(pcc_node)) {
		rc = -EINVAL;
		llapi_error(LLAPI_MSG_ERROR, rc,
			    "bad PCC backend Array!");
		goto out_free;
	}

	while (cYAML_get_next_seq_item(pcc_node, &node) != NULL &&
	       pch->pch_iter_cont) {
		int ret;

		ret = pch->pch_cb(node, pch);
		if (ret && !rc)
			rc = ret;
	}

out_free:
	/* Not found the given PCC backend on the client. */
	if (pch->pch_iter_cont && (pch->pch_cmd == PCC_CMD_DEL ||
	    pch->pch_cmd == PCC_CMD_BACKEND_SELECT))
		rc = -ENOENT;

	if (tree)
		cYAML_free_tree(tree);
	cfs_free_param_data(&path);
	return rc;

}

static int llapi_handle_yaml_pcc_del(struct cYAML *node,
				     struct pcc_cmd_handler *pch)
{
	struct cYAML *pccpath, *hsmtool;
	enum hsmtool_type type;

	pccpath = cYAML_get_object_item(node, PCC_YAML_PCCPATH);
	hsmtool = cYAML_get_object_item(node, PCC_YAML_HSMTOOL);

	if (!pccpath || !pccpath->cy_valuestring ||
	    !hsmtool || !hsmtool->cy_valuestring)
		return 0;

	if (strcmp(pccpath->cy_valuestring, pch->pch_pccpath))
		return 0;

	pch->pch_iter_cont = false;
	type = hsmtool_string2type(hsmtool->cy_valuestring);
	return llapi_pcc_del_internal(pch->pch_mntpath, pch->pch_pccpath,
				      type, pch->pch_flags);
}

int llapi_pcc_del(const char *mntpath, const char *pccpath,
		  enum lu_pcc_cleanup_flags flags)
{
	struct pcc_cmd_handler pch;

	pch.pch_cmd = PCC_CMD_DEL;
	pch.pch_iter_cont = true;
	pch.pch_mntpath = mntpath;
	pch.pch_pccpath = pccpath;
	pch.pch_flags = flags;
	pch.pch_cb = llapi_handle_yaml_pcc_del;

	return llapi_pcc_yaml_cb_helper(&pch);
}


static int llapi_handle_yaml_pcc_clear(struct cYAML *node,
				       struct pcc_cmd_handler *pch)
{
	struct cYAML *pccpath, *hsmtool;
	enum hsmtool_type type;

	pccpath = cYAML_get_object_item(node, PCC_YAML_PCCPATH);
	hsmtool = cYAML_get_object_item(node, PCC_YAML_HSMTOOL);

	if (!pccpath || !pccpath->cy_valuestring ||
	    !hsmtool || !hsmtool->cy_valuestring)
		return 0;

	type = hsmtool_string2type(hsmtool->cy_valuestring);
	return llapi_pcc_del_internal(pch->pch_mntpath,
				      pccpath->cy_valuestring,
				      type, pch->pch_flags);
}

int llapi_pcc_clear(const char *mntpath, enum lu_pcc_cleanup_flags flags)
{
	struct pcc_cmd_handler pch;

	pch.pch_cmd = PCC_CMD_CLEAR;
	pch.pch_iter_cont = true;
	pch.pch_mntpath = mntpath;
	pch.pch_pccpath = NULL;
	pch.pch_flags = flags;
	pch.pch_cb = llapi_handle_yaml_pcc_clear;

	return llapi_pcc_yaml_cb_helper(&pch);
}

static int llapi_pcc_yaml_backend_get(struct cYAML *node,
				      struct pcc_cmd_handler *pch)
{
	struct cYAML *pccid;

	/* TODO: check the flags of PCC backends. */
	pccid = cYAML_get_object_item(node, pch->pch_type == LU_PCC_READWRITE ?
				      PCC_YAML_RWID : PCC_YAML_ROID);
	if (!pccid || pccid->cy_valueint == 0)
		return 0;

	pch->pch_iter_cont = false;
	pch->pch_id = pccid->cy_valueint;
	return 0;
}

int llapi_pcc_backend_id_get(const char *path, enum lu_pcc_type type, __u32 *id)
{
	struct pcc_cmd_handler pch;
	int rc;

	memset(&pch, 0, sizeof(pch));
	pch.pch_cmd = PCC_CMD_BACKEND_SELECT;
	pch.pch_iter_cont = true;
	pch.pch_mntpath = path;
	pch.pch_type = type;
	pch.pch_cb = llapi_pcc_yaml_backend_get;

	rc = llapi_pcc_yaml_cb_helper(&pch);
	if (rc == 0)
		*id = pch.pch_id;

	return rc;
}

#define PIN_YAML_HSM_STR	"hsm"

int verify_pin_xattr_object(struct cYAML *yaml)
{
	struct cYAML *node = NULL;

	if (yaml->cy_type != CYAML_TYPE_OBJECT)
		return -EINVAL;

	for (node = yaml->cy_child; node != NULL; node = node->cy_next) {
		/*
		 * Pin entries are stored as a flat mapping from key -> value,
		 * where existing PCC/HSM pins use numeric values
		 * (CYAML_TYPE_NUMBER) and pool pins use string values
		 * (CYAML_TYPE_STRING).
		 */
		if (node->cy_type != CYAML_TYPE_NUMBER &&
		    node->cy_type != CYAML_TYPE_STRING)
			return -EINVAL;
	}

	return 0;
}

int dump_pin_object(struct cYAML *yaml, char *buff, int buflen)
{
	int rc = 0, remained;
	struct cYAML *node;
	char *p = buff;

	if (yaml->cy_child == NULL) {
		buff[0] = '\0';
		goto out;
	}

	*p++ = '[';
	for (node = yaml->cy_child; node != NULL; node = node->cy_next) {
		if (node != yaml->cy_child)
			*p++ = ',';

		remained = buff + buflen - p - 1;
		if (node->cy_type == CYAML_TYPE_NUMBER) {
			rc = snprintf(p, remained, "%s: %ld",
				      node->cy_string, node->cy_valueint);
		} else if (node->cy_type == CYAML_TYPE_STRING &&
			   node->cy_valuestring != NULL) {
			rc = snprintf(p, remained, "%s: %s",
				      node->cy_string, node->cy_valuestring);
		} else {
			rc = -EINVAL;
			goto out;
		}
		if (rc <= 0) {
			rc = -errno;
			goto out;
		} else if (rc > remained) {
			rc = -EOVERFLOW;
			goto out;
		}
		p += rc;
	}
	*p++ = ']';
	*p = '\0';

	rc = 0;
out:
	return rc;
}

struct cYAML *read_pin_xattr_object(const char *path)
{
	int rc, i;
	struct cYAML *yaml = NULL;
	char buff[XATTR_SIZE_MAX];

	rc = getxattr(path, XATTR_LUSTRE_PIN, buff, sizeof(buff));
	if (rc < 0)
		goto out;

	if (buff[0] != '[' || buff[rc - 1] != ']') {
		llapi_error(LLAPI_MSG_ERROR, EINVAL,
			    "invalid pin string '%s'.", buff);
		rc = -EINVAL;
		goto out;
	}

	for (i = 0; i < rc; i++)
		if (buff[i] == ',')
			buff[i] = '\n';

	yaml = cYAML_build_tree(NULL, buff + 1, rc - 2, NULL, false);
	if (yaml == NULL) {
		llapi_error(LLAPI_MSG_ERROR, EINVAL,
			    "invalid pin string '%s'.", buff);
		errno = -EINVAL;
		goto out;
	}

	rc = verify_pin_xattr_object(yaml);
	if (rc) {
		llapi_error(LLAPI_MSG_ERROR, -rc, "Invalid pin object.");
		cYAML_free_tree(yaml);
		yaml = NULL;
		errno = -rc;
		goto out;
	}

out:
	return yaml;
}

struct cYAML *read_pin_xattr_object_fd(int fd)
{
	int rc, i;
	struct cYAML *yaml = NULL;
	char buff[XATTR_SIZE_MAX];

	rc = fgetxattr(fd, XATTR_LUSTRE_PIN, buff, sizeof(buff));
	if (rc < 0)
		goto out;

	if (buff[0] != '[' || buff[rc - 1] != ']') {
		llapi_error(LLAPI_MSG_ERROR, EINVAL,
			    "invalid pin string '%s'.", buff);
		rc = -EINVAL;
		goto out;
	}

	for (i = 0; i < rc; i++)
		if (buff[i] == ',')
			buff[i] = '\n';

	yaml = cYAML_build_tree(NULL, buff + 1, rc - 2, NULL, false);
	if (yaml == NULL) {
		llapi_error(LLAPI_MSG_ERROR, EINVAL,
			    "invalid pin string '%s'.", buff);
		errno = -EINVAL;
		goto out;
	}

	rc = verify_pin_xattr_object(yaml);
	if (rc) {
		llapi_error(LLAPI_MSG_ERROR, -rc, "Invalid pin object.");
		cYAML_free_tree(yaml);
		yaml = NULL;
		errno = -rc;
		goto out;
	}

out:
	return yaml;
}

int llapi_pcc_pin_file(const char *path, __u32 id)
{
	int rc = 0;
	struct cYAML *yaml, *node;
	char buff[XATTR_SIZE_MAX];

	yaml = read_pin_xattr_object(path);

	if (yaml == NULL && errno == ENODATA) {
		snprintf(buff, sizeof(buff), "[%s: %d]", PIN_YAML_HSM_STR, id);
		goto set;

	}
	if (yaml == NULL) {
		llapi_error(LLAPI_MSG_ERROR, errno,
			    "cannot read or parse pin xattr of file '%s'.",
			    path);
		rc = -errno;
		goto out;
	}

	/* Now we have an valid pin object, search for existing entry */
	for (node = yaml->cy_child; node != NULL; node = node->cy_next) {
		if (strcmp(node->cy_string, PIN_YAML_HSM_STR) == 0 &&
		    node->cy_valueint == id)
			break;
	}
	if (node != NULL) {
		rc = 0;
		goto out;
	}

	node = cYAML_create_number(yaml, PIN_YAML_HSM_STR, id);
	if (node == NULL) {
		rc = -errno;
		goto out;
	}

	rc = dump_pin_object(yaml, buff, sizeof(buff));
	if (rc)
		goto out;

set:
	rc = setxattr(path, XATTR_LUSTRE_PIN, buff, strlen(buff), 0);
	if (rc < 0)
		rc = -errno;
out:
	return rc;
}

int llapi_pcc_unpin_file(const char *path, __u32 id)
{
	int rc = 0;
	struct cYAML *yaml, *node;
	char buff[XATTR_SIZE_MAX];

	yaml = read_pin_xattr_object(path);

	if (yaml == NULL && errno == ENODATA) {
		rc = 0;
		goto out;
	}
	if (yaml == NULL) {
		llapi_error(LLAPI_MSG_ERROR, errno,
			    "cannot read or parse pin xattr of file '%s'.",
			    path);
		rc = -errno;
		goto out;
	}

	/* We have an valid pin object, search for the entry to be deleted */
	for (node = yaml->cy_child; node != NULL; node = node->cy_next) {
		if (strcmp(node->cy_string, PIN_YAML_HSM_STR) == 0 &&
		    node->cy_valueint == id)
			break;
	}
	if (node == NULL) {
		rc = 0;
		goto out;
	}

	/* Remove the node */
	if (node == yaml->cy_child) {
		/* the first child */
		if (node->cy_next)
			node->cy_next->cy_prev = NULL;
		yaml->cy_child = node->cy_next;
	} else {
		/* not the first child */
		node->cy_prev->cy_next = node->cy_next;
		if (node->cy_next)
			node->cy_next->cy_prev = node->cy_prev;
	}
	node->cy_prev = node->cy_next = NULL;
	cYAML_free_tree(node);

	rc = dump_pin_object(yaml, buff, sizeof(buff));
	if (rc)
		goto out;

	if (strlen(buff) == 0)
		rc = removexattr(path, XATTR_LUSTRE_PIN);
	else
		rc = setxattr(path, XATTR_LUSTRE_PIN, buff, strlen(buff), 0);

	if (rc < 0)
		rc = -errno;

out:
	return rc;
}