/* * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2015 by Delphix. All rights reserved. */ /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; #define S_ISPECIAL(a) (S_ISLNK(a) || S_ISFIFO(a) || S_ISBLK(a) || \ S_ISCHR(a)) /* * 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); } switch (attr->st_mode & S_IFMT) { case S_IFDIR: tar_hdr->th_linkflag = LF_DIR; break; case S_IFIFO: tar_hdr->th_linkflag = LF_FIFO; break; case S_IFBLK: case S_IFCHR: if (S_ISBLK(attr->st_mode)) tar_hdr->th_linkflag = LF_BLK; else tar_hdr->th_linkflag = LF_CHR; (void) snprintf(tar_hdr->th_shared.th_dev.th_devmajor, sizeof (tar_hdr->th_shared.th_dev.th_devmajor), "%06o ", major(attr->st_rdev)); (void) snprintf(tar_hdr->th_shared.th_dev.th_devminor, sizeof (tar_hdr->th_shared.th_dev.th_devminor), "%06o ", minor(attr->st_rdev)); break; default: 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_ISPECIAL(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_ISPECIAL(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_MAX_DATASET_NAME_LEN); (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_MAX_DATASET_NAME_LEN, "%s@%llu", typestr, (longlong_t)rid); } else { (void) snprintf(mpp->mp_name, ZFS_MAX_DATASET_NAME_LEN, "%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_MAX_DATASET_NAME_LEN); } (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); }