OpenSolaris_b135/cmd/ndmpd/tlm/tlm_backup_reader.c

Compare this file to the similar file:
Show the results in this format:

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * BSD 3 Clause License
 *
 * Copyright (c) 2007, The Storage Networking Industry Association.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 	- Redistributions of source code must retain the above copyright
 *	  notice, this list of conditions and the following disclaimer.
 *
 * 	- Redistributions in binary form must reproduce the above copyright
 *	  notice, this list of conditions and the following disclaimer in
 *	  the documentation and/or other materials provided with the
 *	  distribution.
 *
 *	- Neither the name of The Storage Networking Industry Association (SNIA)
 *	  nor the names of its contributors may be used to endorse or promote
 *	  products derived from this software without specific prior written
 *	  permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
#include <stdio.h>
#include <limits.h>
#include <time.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <pthread.h>
#include <archives.h>
#include <tlm.h>
#include <sys/fs/zfs.h>
#include <libzfs.h>
#include <libcmdutils.h>
#include <pwd.h>
#include <grp.h>
#include "tlm_proto.h"


static char *get_write_buffer(long size,
    long *actual_size,
    boolean_t zero,
    tlm_cmd_t *);
static int output_acl_header(sec_attr_t *,
    tlm_cmd_t *);
static int output_file_header(char *name,
    char *link,
    tlm_acls_t *,
    int section,
    tlm_cmd_t *);
static int output_xattr_header(char *fname,
    char *aname,
    int fd,
    tlm_acls_t *,
    int section,
    tlm_cmd_t *);

extern  libzfs_handle_t *zlibh;
extern	mutex_t zlib_mtx;


/*
 * output_mem
 *
 * Gets a IO write buffer and copies memory to the that.
 */
static void
output_mem(tlm_cmd_t *local_commands, char *mem,
    int len)
{
	long actual_size, rec_size;
	char *rec;

	while (len > 0) {
		rec = get_write_buffer(len, &actual_size,
		    FALSE, local_commands);
		rec_size = min(actual_size, len);
		(void) memcpy(rec, mem, rec_size);
		mem += rec_size;
		len -= rec_size;
	}
}

/*
 * tlm_output_dir
 *
 * Put the directory information into the output buffers.
 */
int
tlm_output_dir(char *name, tlm_acls_t *tlm_acls,
    tlm_cmd_t *local_commands, tlm_job_stats_t *job_stats)
{
	u_longlong_t pos;

	/*
	 * Send the node or path history of the directory itself.
	 */
	pos = tlm_get_data_offset(local_commands);
	NDMP_LOG(LOG_DEBUG, "pos: %10lld  [%s]", pos, name);
	(void) tlm_log_fhnode(job_stats, name, "", &tlm_acls->acl_attr, pos);
	(void) tlm_log_fhpath_name(job_stats, name, &tlm_acls->acl_attr, pos);
	/* fhdir_cb is handled in ndmpd_tar3.c */

	(void) output_acl_header(&tlm_acls->acl_info,
	    local_commands);
	(void) output_file_header(name, "", tlm_acls, 0,
	    local_commands);

	return (0);
}

/*
 * tar_putdir
 *
 * Main dir backup function for tar
 */
int
tar_putdir(char *name, tlm_acls_t *tlm_acls,
    tlm_cmd_t *local_commands, tlm_job_stats_t *job_stats)
{
	int rv;

	rv = tlm_output_dir(name, tlm_acls, local_commands, job_stats);
	return (rv < 0 ? rv : 0);
}

/*
 * output_acl_header
 *
 * output the ACL header record and data
 */
static int
output_acl_header(sec_attr_t *acl_info,
    tlm_cmd_t *local_commands)
{
	long	actual_size;
	tlm_tar_hdr_t *tar_hdr;
	long	acl_size;

	if ((acl_info == NULL) || (*acl_info->attr_info == '\0'))
		return (0);

	tar_hdr = (tlm_tar_hdr_t *)get_write_buffer(RECORDSIZE,
	    &actual_size, TRUE, local_commands);
	if (!tar_hdr)
		return (0);

	tar_hdr->th_linkflag = LF_ACL;
	acl_info->attr_type = UFSD_ACL;
	(void) snprintf(acl_info->attr_len, sizeof (acl_info->attr_len),
	    "%06o", strlen(acl_info->attr_info));

	acl_size = sizeof (*acl_info);
	(void) strlcpy(tar_hdr->th_name, "UFSACL", TLM_NAME_SIZE);
	(void) snprintf(tar_hdr->th_size, sizeof (tar_hdr->th_size), "%011o ",
	    acl_size);
	(void) snprintf(tar_hdr->th_mode, sizeof (tar_hdr->th_mode), "%06o ",
	    0444);
	(void) snprintf(tar_hdr->th_uid, sizeof (tar_hdr->th_uid), "%06o ", 0);
	(void) snprintf(tar_hdr->th_gid, sizeof (tar_hdr->th_gid), "%06o ", 0);
	(void) snprintf(tar_hdr->th_mtime, sizeof (tar_hdr->th_mtime),
	    "%011o ", 0);
	(void) strlcpy(tar_hdr->th_magic, TLM_MAGIC,
	    sizeof (tar_hdr->th_magic));

	tlm_build_header_checksum(tar_hdr);

	(void) output_mem(local_commands, (void *)acl_info, acl_size);
	return (0);
}

/*
 * output_humongus_header
 *
 * output a special header record for HUGE files
 * output is:	1) a TAR "HUGE" header redord
 * 		2) a "file" of size, name
 */
static int
output_humongus_header(char *fullname, longlong_t file_size,
    tlm_cmd_t *local_commands)
{
	char	*buf;
	int	len;
	long	actual_size;
	tlm_tar_hdr_t *tar_hdr;

	/*
	 * buf will contain: "%llu %s":
	 * - 20 is the maximum length of 'ulong_tlong' decimal notation.
	 * - The first '1' is for the ' ' between the "%llu" and the fullname.
	 * - The last '1' is for the null-terminator of fullname.
	 */
	len = 20 + 1 + strlen(fullname) + 1;

	if ((buf = ndmp_malloc(sizeof (char) * len)) == NULL)
		return (-1);

	tar_hdr = (tlm_tar_hdr_t *)get_write_buffer(RECORDSIZE,
	    &actual_size, TRUE, local_commands);
	if (!tar_hdr) {
		free(buf);
		return (0);
	}

	tar_hdr->th_linkflag = LF_HUMONGUS;
	(void) snprintf(tar_hdr->th_size, sizeof (tar_hdr->th_size), "%011o ",
	    len);
	tlm_build_header_checksum(tar_hdr);
	(void) snprintf(buf, len, "%lld %s", file_size, fullname);
	(void) output_mem(local_commands, buf, len);

	free(buf);
	return (0);
}


/*
 * output_xattr_header
 *
 * output the TAR header record for extended attributes
 */
static int
output_xattr_header(char *fname, char *aname, int fd,
    tlm_acls_t *tlm_acls, int section, tlm_cmd_t *local_commands)
{
	struct stat64 *attr = &tlm_acls->acl_attr;
	struct xattr_hdr *xhdr;
	struct xattr_buf *xbuf;
	tlm_tar_hdr_t *tar_hdr;
	long	actual_size;
	char	*section_name = ndmp_malloc(TLM_MAX_PATH_NAME);
	int	hsize;
	int	comlen;
	int	namesz;

	if (section_name == NULL)
		return (-TLM_NO_SCRATCH_SPACE);

	if (fstat64(fd, attr) == -1) {
		NDMP_LOG(LOG_DEBUG, "output_file_header stat failed.");
		free(section_name);
		return (-TLM_OPEN_ERR);
	}

	/*
	 * if the file has to go out in sections,
	 * we must mung the name.
	 */
	if (section == 0) {
		(void) snprintf(section_name, TLM_MAX_PATH_NAME,
		    "/dev/null/%s.hdr", aname);
	} else {
		(void) snprintf(section_name,
		    TLM_MAX_PATH_NAME, "%s.%03d", aname, section);
	}
	namesz = strlen(section_name) + strlen(fname) + 2; /* 2 nulls */
	hsize = namesz + sizeof (struct xattr_hdr) + sizeof (struct xattr_buf);
	comlen = namesz + sizeof (struct xattr_buf);

	tar_hdr = (tlm_tar_hdr_t *)get_write_buffer(RECORDSIZE,
	    &actual_size, TRUE, local_commands);
	if (!tar_hdr) {
		free(section_name);
		return (0);
	}

	(void) strlcpy(tar_hdr->th_name, section_name, TLM_NAME_SIZE);

	tar_hdr->th_linkflag = LF_XATTR;
	(void) snprintf(tar_hdr->th_size, sizeof (tar_hdr->th_size), "%011o ",
	    hsize);
	(void) snprintf(tar_hdr->th_mode, sizeof (tar_hdr->th_mode), "%06o ",
	    attr->st_mode & 07777);
	(void) snprintf(tar_hdr->th_uid, sizeof (tar_hdr->th_uid), "%06o ",
	    attr->st_uid);
	(void) snprintf(tar_hdr->th_gid, sizeof (tar_hdr->th_gid), "%06o ",
	    attr->st_gid);
	(void) snprintf(tar_hdr->th_mtime, sizeof (tar_hdr->th_mtime), "%011o ",
	    attr->st_mtime);
	(void) strlcpy(tar_hdr->th_magic, TLM_MAGIC,
	    sizeof (tar_hdr->th_magic));

	NDMP_LOG(LOG_DEBUG, "xattr_hdr: %s size %d mode %06o uid %d gid %d",
	    aname, hsize, attr->st_mode & 07777, attr->st_uid, attr->st_gid);

	tlm_build_header_checksum(tar_hdr);

	xhdr = (struct xattr_hdr *)get_write_buffer(RECORDSIZE,
	    &actual_size, TRUE, local_commands);
	if (!xhdr) {
		free(section_name);
		return (0);
	}

	(void) snprintf(xhdr->h_version, sizeof (xhdr->h_version), "%s",
	    XATTR_ARCH_VERS);
	(void) snprintf(xhdr->h_size, sizeof (xhdr->h_size), "%0*d",
	    sizeof (xhdr->h_size) - 1, hsize);
	(void) snprintf(xhdr->h_component_len, sizeof (xhdr->h_component_len),
	    "%0*d", sizeof (xhdr->h_component_len) - 1, comlen);
	(void) snprintf(xhdr->h_link_component_len,
	    sizeof (xhdr->h_link_component_len), "%0*d",
	    sizeof (xhdr->h_link_component_len) - 1, 0);

	xbuf = (struct xattr_buf *)(((caddr_t)xhdr) +
	    sizeof (struct xattr_hdr));
	(void) snprintf(xbuf->h_namesz, sizeof (xbuf->h_namesz), "%0*d",
	    sizeof (xbuf->h_namesz) - 1, namesz);

	/* No support for links in extended attributes */
	xbuf->h_typeflag = LF_NORMAL;

	(void) strlcpy(xbuf->h_names, fname, TLM_NAME_SIZE);
	(void) strlcpy(&xbuf->h_names[strlen(fname) + 1], aname,
	    TLM_NAME_SIZE);

	free(section_name);
	return (0);
}


/*
 * output_file_header
 *
 * output the TAR header record
 */
static int
output_file_header(char *name, char *link,
    tlm_acls_t *tlm_acls, int section, tlm_cmd_t *local_commands)
{
	static	longlong_t file_count = 0;
	struct stat64 *attr = &tlm_acls->acl_attr;
	tlm_tar_hdr_t *tar_hdr;
	long	actual_size;
	boolean_t long_name = FALSE;
	boolean_t long_link = FALSE;
	char	*section_name = ndmp_malloc(TLM_MAX_PATH_NAME);
	int	nmlen, lnklen;
	uid_t uid;
	gid_t gid;
	char *uname = "";
	char *gname = "";
	struct passwd *pwd;
	struct group *grp;

	if (section_name == NULL)
		return (-TLM_NO_SCRATCH_SPACE);

	/*
	 * if the file has to go out in sections,
	 * we must mung the name.
	 */
	if (section == 0) {
		(void) strlcpy(section_name, name, TLM_MAX_PATH_NAME);
	} else {
		(void) snprintf(section_name,
		    TLM_MAX_PATH_NAME, "%s.%03d", name, section);
	}

	if ((pwd = getpwuid(attr->st_uid)) != NULL)
		uname = pwd->pw_name;
	if ((grp = getgrgid(attr->st_gid)) != NULL)
		gname = grp->gr_name;

	if ((ulong_t)(uid = attr->st_uid) > (ulong_t)OCTAL7CHAR)
		uid = UID_NOBODY;
	if ((ulong_t)(gid = attr->st_gid) > (ulong_t)OCTAL7CHAR)
		gid = GID_NOBODY;

	nmlen = strlen(section_name);
	if (nmlen >= NAMSIZ) {
		/*
		 * file name is too big, it must go out
		 * in its own data file
		 */
		tar_hdr = (tlm_tar_hdr_t *)get_write_buffer(RECORDSIZE,
		    &actual_size, TRUE, local_commands);
		if (!tar_hdr) {
			free(section_name);
			return (0);
		}
		(void) snprintf(tar_hdr->th_name,
		    sizeof (tar_hdr->th_name),
		    "%s%08qd.fil",
		    LONGNAME_PREFIX,
		    file_count++);

		tar_hdr->th_linkflag = LF_LONGNAME;
		(void) snprintf(tar_hdr->th_size, sizeof (tar_hdr->th_size),
		    "%011o ", nmlen);
		(void) snprintf(tar_hdr->th_mode, sizeof (tar_hdr->th_mode),
		    "%06o ", attr->st_mode & 07777);
		(void) snprintf(tar_hdr->th_uid, sizeof (tar_hdr->th_uid),
		    "%06o ", uid);
		(void) snprintf(tar_hdr->th_gid, sizeof (tar_hdr->th_gid),
		    "%06o ", gid);
		(void) snprintf(tar_hdr->th_uname, sizeof (tar_hdr->th_uname),
		    "%.31s", uname);
		(void) snprintf(tar_hdr->th_gname, sizeof (tar_hdr->th_gname),
		    "%.31s", gname);
		(void) snprintf(tar_hdr->th_mtime, sizeof (tar_hdr->th_mtime),
		    "%011o ", attr->st_mtime);
		(void) strlcpy(tar_hdr->th_magic, TLM_MAGIC,
		    sizeof (tar_hdr->th_magic));

		tlm_build_header_checksum(tar_hdr);

		(void) output_mem(local_commands,
		    (void *)section_name, nmlen);
		long_name = TRUE;
	}

	lnklen = strlen(link);
	if (lnklen >= NAMSIZ) {
		/*
		 * link name is too big, it must go out
		 * in its own data file
		 */
		tar_hdr = (tlm_tar_hdr_t *)get_write_buffer(RECORDSIZE,
		    &actual_size, TRUE, local_commands);
		if (!tar_hdr) {
			free(section_name);
			return (0);
		}
		(void) snprintf(tar_hdr->th_linkname,
		    sizeof (tar_hdr->th_name),
		    "%s%08qd.slk",
		    LONGNAME_PREFIX,
		    file_count++);

		tar_hdr->th_linkflag = LF_LONGLINK;
		(void) snprintf(tar_hdr->th_size, sizeof (tar_hdr->th_size),
		    "%011o ", lnklen);
		(void) snprintf(tar_hdr->th_mode, sizeof (tar_hdr->th_mode),
		    "%06o ", attr->st_mode & 07777);
		(void) snprintf(tar_hdr->th_uid, sizeof (tar_hdr->th_uid),
		    "%06o ", uid);
		(void) snprintf(tar_hdr->th_gid, sizeof (tar_hdr->th_gid),
		    "%06o ", gid);
		(void) snprintf(tar_hdr->th_uname, sizeof (tar_hdr->th_uname),
		    "%.31s", uname);
		(void) snprintf(tar_hdr->th_gname, sizeof (tar_hdr->th_gname),
		    "%.31s", gname);
		(void) snprintf(tar_hdr->th_mtime, sizeof (tar_hdr->th_mtime),
		    "%011o ", attr->st_mtime);
		(void) strlcpy(tar_hdr->th_magic, TLM_MAGIC,
		    sizeof (tar_hdr->th_magic));

		tlm_build_header_checksum(tar_hdr);

		(void) output_mem(local_commands, (void *)link,
		    lnklen);
		long_link = TRUE;
	}
	tar_hdr = (tlm_tar_hdr_t *)get_write_buffer(RECORDSIZE,
	    &actual_size, TRUE, local_commands);
	if (!tar_hdr) {
		free(section_name);
		return (0);
	}
	if (long_name) {
		(void) snprintf(tar_hdr->th_name,
		    sizeof (tar_hdr->th_name),
		    "%s%08qd.fil",
		    LONGNAME_PREFIX,
		    file_count++);
	} else {
		(void) strlcpy(tar_hdr->th_name, section_name, TLM_NAME_SIZE);
	}

	NDMP_LOG(LOG_DEBUG, "long_link: %s [%s]", long_link ? "TRUE" : "FALSE",
	    link);

	if (long_link) {
		(void) snprintf(tar_hdr->th_linkname,
		    sizeof (tar_hdr->th_name),
		    "%s%08qd.slk",
		    LONGNAME_PREFIX,
		    file_count++);
	} else {
		(void) strlcpy(tar_hdr->th_linkname, link, TLM_NAME_SIZE);
	}
	if (S_ISDIR(attr->st_mode)) {
		tar_hdr->th_linkflag = LF_DIR;
	} else if (S_ISFIFO(attr->st_mode)) {
		tar_hdr->th_linkflag = LF_FIFO;
	} else if (attr->st_nlink > 1) {
		/* mark file with hardlink LF_LINK */
		tar_hdr->th_linkflag = LF_LINK;
		(void) snprintf(tar_hdr->th_shared.th_hlink_ino,
		    sizeof (tar_hdr->th_shared.th_hlink_ino),
		    "%011llo ", attr->st_ino);
	} else {
		tar_hdr->th_linkflag = *link == 0 ? LF_NORMAL : LF_SYMLINK;
		NDMP_LOG(LOG_DEBUG, "linkflag: '%c'", tar_hdr->th_linkflag);
	}
	(void) snprintf(tar_hdr->th_size, sizeof (tar_hdr->th_size), "%011o ",
	    (long)attr->st_size);
	(void) snprintf(tar_hdr->th_mode, sizeof (tar_hdr->th_mode), "%06o ",
	    attr->st_mode & 07777);
	(void) snprintf(tar_hdr->th_uid, sizeof (tar_hdr->th_uid), "%06o ",
	    uid);
	(void) snprintf(tar_hdr->th_gid, sizeof (tar_hdr->th_gid), "%06o ",
	    gid);
	(void) snprintf(tar_hdr->th_uname, sizeof (tar_hdr->th_uname), "%.31s",
	    uname);
	(void) snprintf(tar_hdr->th_gname, sizeof (tar_hdr->th_gname), "%.31s",
	    gname);
	(void) snprintf(tar_hdr->th_mtime, sizeof (tar_hdr->th_mtime), "%011o ",
	    attr->st_mtime);
	(void) strlcpy(tar_hdr->th_magic, TLM_MAGIC,
	    sizeof (tar_hdr->th_magic));

	tlm_build_header_checksum(tar_hdr);
	if (long_name || long_link) {
		if (file_count > 99999990) {
			file_count = 0;
		}
	}
	free(section_name);
	return (0);
}


/*
 * tlm_readlink
 *
 * Read where the softlink points to.  Read the link in the checkpointed
 * path if the backup is being done on a checkpointed file system.
 */
static int
tlm_readlink(char *nm, char *snap, char *buf, int bufsize)
{
	int len;

	if ((len = readlink(snap, buf, bufsize)) >= 0) {
		/*
		 * realink(2) doesn't null terminate the link name.  We must
		 * do it here.
		 */
		buf[len] = '\0';
	} else {
		NDMP_LOG(LOG_DEBUG, "Error %d reading softlink of [%s]",
		    errno, nm);
		buf[0] = '\0';

		/* Backup the link if the destination missing */
		if (errno == ENOENT)
			return (0);

	}

	return (len);
}

/*
 * Read the system attribute file in a single buffer to write
 * it as a single write. A partial write to system attribute would
 * cause an EINVAL on write.
 */
static char *
get_write_one_buf(char *buf, char *rec, int buf_size, int rec_size,
    tlm_cmd_t *lc)
{
	int len;
	long write_size;

	if (rec_size > buf_size)
		return (rec);

	len = rec_size;
	(void) memcpy(rec, buf, len);
	buf += len;
	while (rec_size < buf_size) {
		rec = get_write_buffer(buf_size - rec_size,
		    &write_size, FALSE, lc);
		if (!rec)
			return (0);

		len = min(buf_size - rec_size, write_size);
		(void) memcpy(rec, buf, len);
		rec_size += len;
		buf += len;
	}
	return (rec);
}


/*
 * tlm_output_xattr
 *
 * Put this file into the output buffers.
 */
/*ARGSUSED*/
longlong_t
tlm_output_xattr(char  *dir, char *name, char *chkdir,
    tlm_acls_t *tlm_acls, tlm_commands_t *commands,
    tlm_cmd_t *local_commands, tlm_job_stats_t *job_stats)
{
	char	*fullname;		/* directory + name */
	char	*snapname;		/* snapshot name */
	int	section;		/* section of a huge file */
	int	fd;
	int	afd = 0;
	longlong_t seek_spot = 0;	/* location in the file */
					/* for Multi Volume record */
	u_longlong_t pos;
	DIR *dp;
	struct dirent *dtp;
	char *attrname;
	char *fnamep;
	int rv = 0;

	if (S_ISLNK(tlm_acls->acl_attr.st_mode) ||
	    S_ISFIFO(tlm_acls->acl_attr.st_mode)) {
		return (TLM_NO_SOURCE_FILE);
	}

	fullname = ndmp_malloc(TLM_MAX_PATH_NAME);
	if (fullname == NULL) {
		free(fullname);
		return (-TLM_NO_SCRATCH_SPACE);
	}

	if (!tlm_cat_path(fullname, dir, name)) {
		NDMP_LOG(LOG_DEBUG, "Path too long.");
		free(fullname);
		return (-TLM_NO_SCRATCH_SPACE);
	}

	if (pathconf(fullname, _PC_XATTR_EXISTS) != 1 &&
	    sysattr_support(fullname, _PC_SATTR_EXISTS) != 1) {
		free(fullname);
		return (0);
	}

	attrname = ndmp_malloc(TLM_MAX_PATH_NAME);
	snapname = ndmp_malloc(TLM_MAX_PATH_NAME);
	if (attrname == NULL || snapname == NULL) {
		rv = -TLM_NO_SCRATCH_SPACE;
		goto err_out;
	}

	if (!tlm_cat_path(snapname, chkdir, name)) {
		NDMP_LOG(LOG_DEBUG, "Path too long.");
		rv = -TLM_NO_SCRATCH_SPACE;
		goto err_out;
	}

	fnamep = (tlm_acls->acl_checkpointed) ? snapname : fullname;

	/*
	 * Open the file for reading.
	 */
	fd = attropen(fnamep, ".", O_RDONLY);
	if (fd == -1) {
		NDMP_LOG(LOG_DEBUG, "BACKUP> Can't open file [%s][%s]",
		    fullname, fnamep);
		rv = TLM_NO_SOURCE_FILE;
		goto err_out;
	}

	pos = tlm_get_data_offset(local_commands);
	NDMP_LOG(LOG_DEBUG, "pos: %10lld  [%s]", pos, name);

	section = 0;

	dp = (DIR *)fdopendir(fd);
	if (dp == NULL) {
		NDMP_LOG(LOG_DEBUG, "BACKUP> Can't open file [%s]", fullname);
		(void) close(fd);
		rv = TLM_NO_SOURCE_FILE;
		goto err_out;
	}

	while ((dtp = readdir(dp)) != NULL) {
		int section_size;

		if (*dtp->d_name == '.')
			continue;

		if (sysattr_rdonly(dtp->d_name))
			continue;

		afd = attropen(fnamep, dtp->d_name, O_RDONLY);
		if (afd == -1) {
			NDMP_LOG(LOG_DEBUG,
			    "problem(%d) opening xattr file [%s][%s]", errno,
			    fullname, fnamep);
			goto tear_down;
		}

		(void) output_xattr_header(fullname, dtp->d_name, afd,
		    tlm_acls, section, local_commands);
		(void) snprintf(attrname, TLM_MAX_PATH_NAME, "/dev/null/%s",
		    dtp->d_name);
		(void) output_file_header(attrname, "", tlm_acls, 0,
		    local_commands);

		section_size = (long)llmin(tlm_acls->acl_attr.st_size,
		    (longlong_t)TLM_MAX_TAR_IMAGE);

		/* We only can read upto one section extended attribute */
		while (section_size > 0) {
			char	*buf;
			long	actual_size;
			int	read_size;
			int sysattr_read = 0;
			char *rec;
			int size;

			/*
			 * check for Abort commands
			 */
			if (commands->tcs_reader != TLM_BACKUP_RUN) {
				local_commands->tc_writer = TLM_ABORT;
				goto tear_down;
			}

			local_commands->tc_buffers->tbs_buffer[
			    local_commands->tc_buffers->tbs_buffer_in].
			    tb_file_size = section_size;
			local_commands->tc_buffers->tbs_buffer[
			    local_commands->tc_buffers->tbs_buffer_in].
			    tb_seek_spot = seek_spot;

			buf = get_write_buffer(section_size,
			    &actual_size, FALSE, local_commands);
			if (!buf)
				goto tear_down;

			if ((actual_size < section_size) &&
			    sysattr_rw(dtp->d_name)) {
				rec = buf;
				buf = ndmp_malloc(section_size);
				if (!buf)
					goto tear_down;
				size = actual_size;
				actual_size = section_size;
				sysattr_read = 1;
			}

			/*
			 * check for Abort commands
			 */
			if (commands->tcs_reader != TLM_BACKUP_RUN) {
				local_commands->tc_writer = TLM_ABORT;
				goto tear_down;
			}

			read_size = min(section_size, actual_size);
			if ((actual_size = read(afd, buf, read_size)) < 0)
				break;

			if (sysattr_read) {
				if (get_write_one_buf(buf, rec, read_size,
				    size, local_commands) == 0) {
					free(buf);
					goto tear_down;
				}
				free(buf);
			}


			NS_ADD(rdisk, actual_size);
			NS_INC(rfile);

			if (actual_size == -1) {
				NDMP_LOG(LOG_DEBUG,
				    "problem(%d) reading file [%s][%s]",
				    errno, fullname, snapname);
				goto tear_down;
			}
			seek_spot += actual_size;
			section_size -= actual_size;
		}
		(void) close(afd);
		afd = -1;
	}

tear_down:
	local_commands->tc_buffers->tbs_buffer[
	    local_commands->tc_buffers->tbs_buffer_in].tb_seek_spot = 0;

	if (afd > 0)
		(void) close(afd);

	/* closedir closes fd too */
	(void) closedir(dp);

err_out:
	free(fullname);
	free(attrname);
	free(snapname);
	return (rv);
}


/*
 * tlm_output_file
 *
 * Put this file into the output buffers.
 */
longlong_t
tlm_output_file(char *dir, char *name, char *chkdir,
    tlm_acls_t *tlm_acls, tlm_commands_t *commands, tlm_cmd_t *local_commands,
    tlm_job_stats_t *job_stats, struct hardlink_q *hardlink_q)
{
	char	*fullname;		/* directory + name */
	char	*snapname;		/* snapshot name */
	char	*linkname;		/* where this file points */
	int	section = 0;		/* section of a huge file */
	int	fd;
	longlong_t real_size;		/* the origional file size */
	longlong_t file_size;		/* real size of this file */
	longlong_t seek_spot = 0;	/* location in the file */
					/* for Multi Volume record */
	u_longlong_t pos;
	char *fnamep;

	/* Indicate whether a file with the same inode has been backed up. */
	int hardlink_done = 0;

	/*
	 * If a file with the same inode has been backed up, hardlink_pos holds
	 * the tape offset of the data record.
	 */
	u_longlong_t hardlink_pos = 0;

	if (tlm_is_too_long(tlm_acls->acl_checkpointed, dir, name)) {
		NDMP_LOG(LOG_DEBUG, "Path too long [%s][%s]", dir, name);
		return (-TLM_NO_SCRATCH_SPACE);
	}

	fullname = ndmp_malloc(TLM_MAX_PATH_NAME);
	linkname = ndmp_malloc(TLM_MAX_PATH_NAME);
	snapname = ndmp_malloc(TLM_MAX_PATH_NAME);
	if (fullname == NULL || linkname == NULL || snapname == NULL) {
		real_size = -TLM_NO_SCRATCH_SPACE;
		goto err_out;
	}
	if (!tlm_cat_path(fullname, dir, name) ||
	    !tlm_cat_path(snapname, chkdir, name)) {
		NDMP_LOG(LOG_DEBUG, "Path too long.");
		real_size = -TLM_NO_SCRATCH_SPACE;
		goto err_out;
	}

	pos = tlm_get_data_offset(local_commands);
	NDMP_LOG(LOG_DEBUG, "pos: %10lld  [%s]", pos, name);

	if (S_ISLNK(tlm_acls->acl_attr.st_mode) ||
	    S_ISFIFO(tlm_acls->acl_attr.st_mode)) {
		if (S_ISLNK(tlm_acls->acl_attr.st_mode)) {
			file_size = tlm_readlink(fullname, snapname, linkname,
			    TLM_MAX_PATH_NAME-1);
			if (file_size < 0) {
				real_size = -ENOENT;
				goto err_out;
			}
		}

		/*
		 * Since soft links can not be read(2), we should only
		 * backup the file header.
		 */
		(void) output_file_header(fullname,
		    linkname,
		    tlm_acls,
		    section,
		    local_commands);

		(void) tlm_log_fhnode(job_stats, dir, name,
		    &tlm_acls->acl_attr, pos);
		(void) tlm_log_fhpath_name(job_stats, fullname,
		    &tlm_acls->acl_attr, pos);

		free(fullname);
		free(linkname);
		free(snapname);
		return (0);
	}

	fnamep = (tlm_acls->acl_checkpointed) ? snapname : fullname;

	/*
	 * For hardlink, only read the data if no other link
	 * belonging to the same inode has been backed up.
	 */
	if (tlm_acls->acl_attr.st_nlink > 1) {
		hardlink_done = !hardlink_q_get(hardlink_q,
		    tlm_acls->acl_attr.st_ino, &hardlink_pos, NULL);
	}

	if (!hardlink_done) {
		/*
		 * Open the file for reading.
		 */
		fd = open(fnamep, O_RDONLY);
		if (fd == -1) {
			NDMP_LOG(LOG_DEBUG,
			    "BACKUP> Can't open file [%s][%s] err(%d)",
			    fullname, fnamep, errno);
			real_size = -TLM_NO_SOURCE_FILE;
			goto err_out;
		}
	} else {
		NDMP_LOG(LOG_DEBUG, "found hardlink, inode = %llu, pos = %llu ",
		    tlm_acls->acl_attr.st_ino, hardlink_pos);

		fd = -1;
	}

	linkname[0] = 0;

	real_size = tlm_acls->acl_attr.st_size;
	(void) output_acl_header(&tlm_acls->acl_info,
	    local_commands);

	/*
	 * section = 0: file is small enough for TAR
	 * section > 0: file goes out in TLM_MAX_TAR_IMAGE sized chunks
	 * 		and the file name gets munged
	 */
	file_size = real_size;
	if (file_size > TLM_MAX_TAR_IMAGE) {
		if (output_humongus_header(fullname, file_size,
		    local_commands) < 0) {
			(void) close(fd);
			real_size = -TLM_NO_SCRATCH_SPACE;
			goto err_out;
		}
		section = 1;
	} else {
		section = 0;
	}

	/*
	 * For hardlink, if other link belonging to the same inode
	 * has been backed up, only backup an empty record.
	 */
	if (hardlink_done)
		file_size = 0;

	/*
	 * work
	 */
	if (file_size == 0) {
		(void) output_file_header(fullname,
		    linkname,
		    tlm_acls,
		    section,
		    local_commands);
		/*
		 * this can fall right through since zero size files
		 * will be skipped by the WHILE loop anyway
		 */
	}

	while (file_size > 0) {
		int section_size = llmin(file_size,
		    (longlong_t)TLM_MAX_TAR_IMAGE);

		tlm_acls->acl_attr.st_size = (longlong_t)section_size;
		(void) output_file_header(fullname,
		    linkname,
		    tlm_acls,
		    section,
		    local_commands);
		while (section_size > 0) {
			char	*buf;
			long	actual_size;
			int	read_size;

			/*
			 * check for Abort commands
			 */
			if (commands->tcs_reader != TLM_BACKUP_RUN) {
				local_commands->tc_writer = TLM_ABORT;
				goto tear_down;
			}

			local_commands->tc_buffers->tbs_buffer[
			    local_commands->tc_buffers->tbs_buffer_in].
			    tb_file_size = section_size;
			local_commands->tc_buffers->tbs_buffer[
			    local_commands->tc_buffers->tbs_buffer_in].
			    tb_seek_spot = seek_spot;

			buf = get_write_buffer(section_size,
			    &actual_size, FALSE, local_commands);
			if (!buf)
				goto tear_down;

			/*
			 * check for Abort commands
			 */
			if (commands->tcs_reader != TLM_BACKUP_RUN) {
				local_commands->tc_writer = TLM_ABORT;
				goto tear_down;
			}

			read_size = min(section_size, actual_size);
			actual_size = read(fd, buf, read_size);
			NS_ADD(rdisk, actual_size);
			NS_INC(rfile);

			if (actual_size == 0)
				break;

			if (actual_size == -1) {
				NDMP_LOG(LOG_DEBUG,
				    "problem(%d) reading file [%s][%s]",
				    errno, fullname, snapname);
				goto tear_down;
			}
			seek_spot += actual_size;
			file_size -= actual_size;
			section_size -= actual_size;
		}
		section++;
	}

	/*
	 * If data belonging to this hardlink has been backed up, add the link
	 * to hardlink queue.
	 */
	if (tlm_acls->acl_attr.st_nlink > 1 && !hardlink_done) {
		(void) hardlink_q_add(hardlink_q, tlm_acls->acl_attr.st_ino,
		    pos, NULL, 0);
		NDMP_LOG(LOG_DEBUG,
		    "backed up hardlink file %s, inode = %llu, pos = %llu ",
		    fullname, tlm_acls->acl_attr.st_ino, pos);
	}

	/*
	 * For hardlink, if other link belonging to the same inode has been
	 * backed up, no add_node entry should be sent for this link.
	 */
	if (hardlink_done) {
		NDMP_LOG(LOG_DEBUG,
		    "backed up hardlink link %s, inode = %llu, pos = %llu ",
		    fullname, tlm_acls->acl_attr.st_ino, hardlink_pos);
	} else {
		(void) tlm_log_fhnode(job_stats, dir, name,
		    &tlm_acls->acl_attr, pos);
	}

	(void) tlm_log_fhpath_name(job_stats, fullname, &tlm_acls->acl_attr,
	    pos);

tear_down:
	local_commands->tc_buffers->tbs_buffer[
	    local_commands->tc_buffers->tbs_buffer_in].tb_seek_spot = 0;

	(void) close(fd);

err_out:
	free(fullname);
	free(linkname);
	free(snapname);
	return (real_size);
}

/*
 * tar_putfile
 *
 * Main file backup function for tar
 */
int
tar_putfile(char *dir, char *name, char *chkdir,
    tlm_acls_t *tlm_acls, tlm_commands_t *commands,
    tlm_cmd_t *local_commands, tlm_job_stats_t *job_stats,
    struct hardlink_q *hardlink_q)
{
	int rv;

	rv = tlm_output_file(dir, name, chkdir, tlm_acls, commands,
	    local_commands, job_stats, hardlink_q);
	if (rv < 0)
		return (rv);

	rv = tlm_output_xattr(dir, name, chkdir, tlm_acls, commands,
	    local_commands, job_stats);

	return (rv < 0 ? rv : 0);
}

/*
 * get_write_buffer
 *
 * a wrapper to tlm_get_write_buffer so that
 * we can cleanly detect ABORT commands
 * without involving the TLM library with
 * our problems.
 */
static char *
get_write_buffer(long size, long *actual_size,
    boolean_t zero, tlm_cmd_t *local_commands)
{
	while (local_commands->tc_reader == TLM_BACKUP_RUN) {
		char *rec = tlm_get_write_buffer(size, actual_size,
		    local_commands->tc_buffers, zero);
		if (rec != 0) {
			return (rec);
		}
	}
	return (NULL);
}

#define	NDMP_MORE_RECORDS	2

/*
 * write_tar_eof
 *
 * This function is initially written for NDMP support.  It appends
 * two tar headers to the tar file, and also N more empty buffers
 * to make sure that the two tar headers will be read as a part of
 * a mover record and don't get locked because of EOM on the mover
 * side.
 */
void
write_tar_eof(tlm_cmd_t *local_commands)
{
	int i;
	long actual_size;
	tlm_buffers_t *bufs;

	/*
	 * output 2 zero filled records,
	 * TAR wants this.
	 */
	(void) get_write_buffer(sizeof (tlm_tar_hdr_t),
	    &actual_size, TRUE, local_commands);
	(void) get_write_buffer(sizeof (tlm_tar_hdr_t),
	    &actual_size, TRUE, local_commands);

	/*
	 * NDMP: Clear the rest of the buffer and write two more buffers
	 * to the tape.
	 */
	bufs = local_commands->tc_buffers;
	(void) get_write_buffer(bufs->tbs_data_transfer_size,
	    &actual_size, TRUE, local_commands);

	for (i = 0; i < NDMP_MORE_RECORDS &&
	    local_commands->tc_reader == TLM_BACKUP_RUN; i++) {
		/*
		 * We don't need the return value of get_write_buffer(),
		 * since it's already zeroed out if the buffer is returned.
		 */
		(void) get_write_buffer(bufs->tbs_data_transfer_size,
		    &actual_size, TRUE, local_commands);
	}

	bufs->tbs_buffer[bufs->tbs_buffer_in].tb_full = TRUE;
	tlm_buffer_release_in_buf(bufs);
}

/*
 * Callback to backup each ZFS property
 */
static int
zfs_put_prop_cb(int prop, void *pp)
{
	ndmp_metadata_handle_t *mhd;
	ndmp_metadata_header_ext_t *mhp;
	ndmp_metadata_property_ext_t *mpp;
	char vbuf[ZFS_MAXPROPLEN];
	char sbuf[ZFS_MAXPROPLEN];
	zprop_source_t stype;
	char *sourcestr;

	if (pp == NULL)
		return (ZPROP_INVAL);

	mhd = (ndmp_metadata_handle_t *)pp;
	mhp = mhd->ml_xhdr;
	mpp = &mhp->nh_property[mhp->nh_count];

	if (mhp->nh_count * sizeof (ndmp_metadata_property_ext_t) +
	    sizeof (ndmp_metadata_header_ext_t) > mhp->nh_total_bytes)
		return (ZPROP_INVAL);

	if (zfs_prop_get(mhd->ml_handle, prop, vbuf, sizeof (vbuf),
	    &stype, sbuf, sizeof (sbuf), B_TRUE) != 0) {
		mhp->nh_count++;
		return (ZPROP_CONT);
	}

	(void) strlcpy(mpp->mp_name, zfs_prop_to_name(prop), ZFS_MAXNAMELEN);
	(void) strlcpy(mpp->mp_value, vbuf, ZFS_MAXPROPLEN);

	switch (stype) {
	case ZPROP_SRC_NONE:
		sourcestr = "none";
		break;
	case ZPROP_SRC_RECEIVED:
		sourcestr = "received";
		break;
	case ZPROP_SRC_LOCAL:
		sourcestr = mhp->nh_dataset;
		break;
	case ZPROP_SRC_TEMPORARY:
		sourcestr = "temporary";
		break;
	case ZPROP_SRC_DEFAULT:
		sourcestr = "default";
		break;
	default:
		sourcestr = sbuf;
		break;
	}
	(void) strlcpy(mpp->mp_source, sourcestr, ZFS_MAXPROPLEN);

	mhp->nh_count++;
	return (ZPROP_CONT);
}

/*
 * Callback to backup each ZFS user/group quota
 */
static int
zfs_put_quota_cb(void *pp, const char *domain, uid_t rid, uint64_t space)
{
	ndmp_metadata_handle_t *mhd;
	ndmp_metadata_header_ext_t *mhp;
	ndmp_metadata_property_ext_t *mpp;
	char *typestr;

	if (pp == NULL)
		return (ZPROP_INVAL);

	mhd = (ndmp_metadata_handle_t *)pp;
	mhp = mhd->ml_xhdr;
	mpp = &mhp->nh_property[mhp->nh_count];

	if (mhp->nh_count * sizeof (ndmp_metadata_property_ext_t) +
	    sizeof (ndmp_metadata_header_ext_t) > mhp->nh_total_bytes)
		return (ZPROP_INVAL);

	if (mhd->ml_quota_prop == ZFS_PROP_USERQUOTA)
		typestr = "userquota";
	else
		typestr = "groupquota";

	if (domain == NULL || *domain == '\0')
		(void) snprintf(mpp->mp_name, ZFS_MAXNAMELEN, "%s@%llu",
		    typestr, (longlong_t)rid);
	else
		(void) snprintf(mpp->mp_name, ZFS_MAXNAMELEN, "%s@%s-%llu",
		    typestr, domain, (longlong_t)rid);
	(void) snprintf(mpp->mp_value, ZFS_MAXPROPLEN, "%llu", space);
	(void) strlcpy(mpp->mp_source, mhp->nh_dataset, ZFS_MAXPROPLEN);

	mhp->nh_count++;
	return (0);
}

/*
 * Callback to count each ZFS property
 */
/*ARGSUSED*/
static int
zfs_count_prop_cb(int prop, void *pp)
{
	(*(int *)pp)++;
	return (ZPROP_CONT);
}

/*
 * Callback to count each ZFS user/group quota
 */
/*ARGSUSED*/
static int
zfs_count_quota_cb(void *pp, const char *domain, uid_t rid, uint64_t space)
{
	(*(int *)pp)++;
	return (0);
}

/*
 * Count the number of ZFS properties and user/group quotas
 */
int
zfs_get_prop_counts(zfs_handle_t *zhp)
{
	int count = 0;
	nvlist_t *uprops;
	nvpair_t *elp;

	if (zhp == NULL)
		return (0);

	(void) zprop_iter(zfs_count_prop_cb, &count, TRUE, TRUE,
	    ZFS_TYPE_VOLUME | ZFS_TYPE_DATASET);

	(void) zfs_userspace(zhp, ZFS_PROP_USERQUOTA, zfs_count_quota_cb,
	    &count);
	(void) zfs_userspace(zhp, ZFS_PROP_GROUPQUOTA, zfs_count_quota_cb,
	    &count);

	uprops = zfs_get_user_props(zhp);

	elp = nvlist_next_nvpair(uprops, NULL);
	for (; elp != NULL; elp = nvlist_next_nvpair(uprops, elp))
		count++;

	return (count);
}

/*
 * Notifies ndmpd that the metadata associated with the given ZFS dataset
 * should be backed up.
 */
int
ndmp_include_zfs(ndmp_context_t *nctx, const char *dataset)
{
	tlm_commands_t *cmds;
	ndmp_metadata_handle_t mhd;
	ndmp_metadata_header_ext_t *mhp;
	ndmp_metadata_property_ext_t *mpp;
	zfs_handle_t *zhp;
	tlm_cmd_t *lcmd;
	long actual_size;
	nvlist_t *uprops, *ulist;
	const char *pname;
	nvpair_t *elp;
	char *sval, *ssrc;
	char *wbuf, *pp, *tp;
	long size, lsize, sz;
	int align = RECORDSIZE - 1;
	int pcount;

	if (nctx == NULL || (cmds = (tlm_commands_t *)nctx->nc_cmds) == NULL)
		return (-1);

	if ((lcmd = cmds->tcs_command) == NULL ||
	    lcmd->tc_buffers == NULL)
		return (-1);

	(void) mutex_lock(&zlib_mtx);
	if ((zhp = zfs_open(zlibh, dataset, ZFS_TYPE_DATASET)) == NULL) {
		(void) mutex_unlock(&zlib_mtx);
		return (-1);
	}

	pcount = zfs_get_prop_counts(zhp);
	size = sizeof (ndmp_metadata_header_ext_t) +
	    pcount * sizeof (ndmp_metadata_property_ext_t);

	size += align;
	size &= ~align;

	if ((mhp = malloc(size)) == NULL) {
		zfs_close(zhp);
		(void) mutex_unlock(&zlib_mtx);
		return (-1);
	}

	(void) memset(mhp, 0, size);

	mhd.ml_handle = zhp;
	mhd.ml_xhdr = mhp;
	mhp->nh_total_bytes = size;
	mhp->nh_major = META_HDR_MAJOR_VERSION;
	mhp->nh_minor = META_HDR_MINOR_VERSION;
	mhp->nh_plversion = nctx->nc_plversion;

	(void) strlcpy(mhp->nh_plname, nctx->nc_plname,
	    sizeof (mhp->nh_plname));
	(void) strlcpy(mhp->nh_magic, ZFS_META_MAGIC_EXT,
	    sizeof (mhp->nh_magic));
	(void) strlcpy(mhp->nh_dataset, dataset, sizeof (mhp->nh_dataset));

	/* Get all the ZFS properties */
	(void) zprop_iter(zfs_put_prop_cb, &mhd, TRUE, TRUE,
	    ZFS_TYPE_VOLUME | ZFS_TYPE_DATASET);

	/* Get user properties */
	uprops = zfs_get_user_props(mhd.ml_handle);

	elp = nvlist_next_nvpair(uprops, NULL);

	while (elp != NULL) {
		mpp = &mhp->nh_property[mhp->nh_count];
		if (nvpair_value_nvlist(elp, &ulist) != 0 ||
		    nvlist_lookup_string(ulist, ZPROP_VALUE, &sval) != 0 ||
		    nvlist_lookup_string(ulist, ZPROP_SOURCE, &ssrc) != 0) {
			zfs_close(mhd.ml_handle);
			(void) mutex_unlock(&zlib_mtx);
			free(mhp);
			return (-1);
		}
		if ((pname = nvpair_name(elp)) != NULL)
			(void) strlcpy(mpp->mp_name, pname, ZFS_MAXNAMELEN);

		(void) strlcpy(mpp->mp_value, sval, ZFS_MAXPROPLEN);
		(void) strlcpy(mpp->mp_source, ssrc, ZFS_MAXPROPLEN);
		mhp->nh_count++;
		elp = nvlist_next_nvpair(uprops, elp);
	}

	mhd.ml_quota_prop = ZFS_PROP_USERQUOTA;
	(void) zfs_userspace(mhd.ml_handle, ZFS_PROP_USERQUOTA,
	    zfs_put_quota_cb, &mhd);
	mhd.ml_quota_prop = ZFS_PROP_GROUPQUOTA;
	(void) zfs_userspace(mhd.ml_handle, ZFS_PROP_GROUPQUOTA,
	    zfs_put_quota_cb, &mhd);
	mhp->nh_count = pcount;

	zfs_close(mhd.ml_handle);
	(void) mutex_unlock(&zlib_mtx);

	if ((wbuf = get_write_buffer(size, &actual_size, TRUE,
	    lcmd)) != NULL) {
		pp = (char *)mhp;

		(void) memcpy(wbuf, pp, (actual_size < size) ?
		    actual_size : size);
		pp += (actual_size < size) ? actual_size : size;

		sz = actual_size;
		while (sz < size &&
		    ((tp = get_write_buffer(size - sz, &lsize,
		    TRUE, lcmd))) != NULL) {
			(void) memcpy(tp, pp, lsize);
			sz += lsize;
			pp += lsize;
		}
		if (sz > size) {
			tlm_unget_write_buffer(lcmd->tc_buffers, sz - size);
		}
	}

	free(mhp);
	return (0);
}