Exemple #1
0
int
main(int argc, char **argv)
{
	struct archive_entry *entry = archive_entry_new();
	unsigned long set, clear;
	const wchar_t *remainder;

	remainder = archive_entry_copy_fflags_text_w(entry, L"nosappnd dump archive,,,,,,,");
	archive_entry_fflags(entry, &set, &clear);

	wprintf(L"set=0x%lX clear=0x%lX remainder='%ls'\n", set, clear, remainder);

	wprintf(L"new flags='%s'\n", archive_entry_fflags_text(entry));
	return (0);
}
static int
get_keys(struct mtree_writer *mtree, struct archive_entry *entry)
{
	int keys;

	keys = mtree->keys;
	if (mtree->set.keys == 0)
		return (keys);
	if ((mtree->set.keys & (F_GNAME | F_GID)) != 0 &&
	     mtree->set.gid == archive_entry_gid(entry))
		keys &= ~(F_GNAME | F_GID);
	if ((mtree->set.keys & (F_UNAME | F_UID)) != 0 &&
	     mtree->set.uid == archive_entry_uid(entry))
		keys &= ~(F_UNAME | F_UID);
	if (mtree->set.keys & F_FLAGS) {
		unsigned long set, clear;

		archive_entry_fflags(entry, &set, &clear);
		if (mtree->set.fflags_set == set &&
		    mtree->set.fflags_clear == clear)
			keys &= ~F_FLAGS;
	}
	if ((mtree->set.keys & F_MODE) != 0 &&
	     mtree->set.mode == (archive_entry_mode(entry) & 07777))
		keys &= ~F_MODE;

	switch (archive_entry_filetype(entry)) {
	case AE_IFLNK: case AE_IFSOCK: case AE_IFCHR:
	case AE_IFBLK: case AE_IFIFO:
		break;
	case AE_IFDIR:
		if ((mtree->set.keys & F_TYPE) != 0 &&
		    mtree->set.type == AE_IFDIR)
			keys &= ~F_TYPE;
		break;
	case AE_IFREG:
	default:	/* Handle unknown file types as regular files. */
		if ((mtree->set.keys & F_TYPE) != 0 &&
		    mtree->set.type == AE_IFREG)
			keys &= ~F_TYPE;
		break;
	}

	return (keys);
}
Exemple #3
0
static int
do_extract(struct archive *a, struct archive_entry *ae, const char *location,
    int nfiles, struct pkg *pkg, struct pkg *local)
{
	int	retcode = EPKG_OK;
	int	ret = 0, cur_file = 0;
	char	path[MAXPATHLEN], pathname[MAXPATHLEN], rpath[MAXPATHLEN];
	struct stat st;
	const struct stat *aest;
	bool renamed = false;
	const struct pkg_file *rf;
	struct pkg_config_file *rcf;
	struct sbuf *newconf;
	bool automerge = pkg_object_bool(pkg_config_get("AUTOMERGE"));
	unsigned long set, clear;

#ifndef HAVE_ARC4RANDOM
	srand(time(NULL));
#endif

	if (nfiles == 0)
		return (EPKG_OK);

	pkg_emit_extract_begin(pkg);
	pkg_emit_progress_start(NULL);

	newconf = sbuf_new_auto();

	do {
		ret = ARCHIVE_OK;
		sbuf_clear(newconf);
		rf = NULL;
		rcf = NULL;
		pkg_absolutepath(archive_entry_pathname(ae), path, sizeof(path));
		snprintf(pathname, sizeof(pathname), "%s%s%s",
		    location ? location : "", *path == '/' ? "" : "/",
		    path
		);
		strlcpy(rpath, pathname, sizeof(rpath));

		aest = archive_entry_stat(ae);
		archive_entry_fflags(ae, &set, &clear);
		if (lstat(rpath, &st) != -1) {
			/*
			 * We have an existing file on the path, so handle it
			 */
			if (!S_ISDIR(aest->st_mode)) {
				pkg_debug(2, "Old version found, renaming");
				pkg_add_file_random_suffix(rpath, sizeof(rpath), 12);
				renamed = true;
			}

			if (!S_ISDIR(st.st_mode) && S_ISDIR(aest->st_mode)) {
				if (S_ISLNK(st.st_mode)) {
					if (stat(rpath, &st) == -1) {
						pkg_emit_error("Dead symlink %s", rpath);
					} else {
						pkg_debug(2, "Directory is a symlink, use it");
						pkg_emit_progress_tick(cur_file++, nfiles);
						continue;
					}
				}
			}
		}

		archive_entry_set_pathname(ae, rpath);

		/* load in memory the content of config files */
		if (pkg_is_config_file(pkg, path, &rf, &rcf)) {
			pkg_debug(1, "Populating config_file %s", pathname);
			size_t len = archive_entry_size(ae);
			rcf->content = malloc(len);
			archive_read_data(a, rcf->content, len);
			if (renamed && (!automerge || local == NULL))
				strlcat(pathname, ".pkgnew", sizeof(pathname));
		}

		/*
		 * check if the file is already provided by previous package
		 */
		if (!automerge)
			attempt_to_merge(renamed, rcf, local, pathname, path, newconf);

		if (sbuf_len(newconf) == 0 && (rcf == NULL || rcf->content == NULL)) {
			pkg_debug(1, "Extracting: %s", archive_entry_pathname(ae));
			int install_as_user = (getenv("INSTALL_AS_USER") != NULL);
			int extract_flags = EXTRACT_ARCHIVE_FLAGS;
			if (install_as_user) {
				/* when installing as user don't try to set file ownership */
				extract_flags &= ~ARCHIVE_EXTRACT_OWNER;
			}
			ret = archive_read_extract(a, ae, extract_flags);
		} else {
			if (sbuf_len(newconf) == 0) {
				sbuf_cat(newconf, rcf->content);
				sbuf_finish(newconf);
			}
			pkg_debug(2, "Writing conf in %s", pathname);
			unlink(rpath);
			FILE *f = fopen(rpath, "w+");
			fprintf(f, "%s", sbuf_data(newconf));
			fclose(f);
		}

		if (ret != ARCHIVE_OK) {
			/*
			 * show error except when the failure is during
			 * extracting a directory and that the directory already
			 * exists.
			 * this allow to install packages linux_base from
			 * package for example
			 */
			if (archive_entry_filetype(ae) != AE_IFDIR ||
			    !is_dir(pathname)) {
				pkg_emit_error("archive_read_extract(): %s",
				    archive_error_string(a));
				retcode = EPKG_FATAL;
				goto cleanup;
			}
		}
		/* Reapply modes to the directories to work around a problem on FreeBSD 9 */
		if (archive_entry_filetype(ae) == AE_IFDIR)
			chmod(pathname, aest->st_mode);

		pkg_emit_progress_tick(cur_file++, nfiles);

		/* Rename old file */
		if (renamed) {

			pkg_debug(1, "Renaming %s -> %s", rpath, pathname);
#ifdef HAVE_CHFLAGS
			bool old = false;
			if (set & NOCHANGESFLAGS)
				chflags(rpath, 0);

			if (lstat(pathname, &st) != -1) {
				old = true;
				if (st.st_flags & NOCHANGESFLAGS)
					chflags(pathname, 0);
			}
#endif

			if (rename(rpath, pathname) == -1) {
#ifdef HAVE_CHFLAGS
				/* restore flags */
				if (old)
					chflags(pathname, st.st_flags);
#endif
				pkg_emit_error("cannot rename %s to %s: %s", rpath, pathname,
					strerror(errno));
				retcode = EPKG_FATAL;
				goto cleanup;
			}
#ifdef HAVE_CHFLAGS
			/* Restore flags */
			chflags(pathname, set);
#endif
		}

		if (string_end_with(pathname, ".pkgnew"))
			pkg_emit_notice("New configuration file: %s", pathname);

		renamed = false;
	} while ((ret = archive_read_next_header(a, &ae)) == ARCHIVE_OK);

	if (ret != ARCHIVE_EOF) {
		pkg_emit_error("archive_read_next_header(): %s",
		    archive_error_string(a));
		retcode = EPKG_FATAL;
	}

cleanup:

	pkg_emit_progress_tick(nfiles, nfiles);
	pkg_emit_extract_finished(pkg);

	if (renamed && retcode == EPKG_FATAL) {
#ifdef HAVE_CHFLAGS
		if (set & NOCHANGESFLAGS)
			chflags(rpath, set & ~NOCHANGESFLAGS);
#endif
		unlink(rpath);
	}

	return (retcode);
}
/*
 * Write /set keyword. It means set global datas.
 * [directory-only mode]
 *   - It is only once to write /set keyword. It is using values of the
 *     first entry.
 * [normal mode]
 *   - Write /set keyword. It is using values of the first entry whose
 *     filetype is a regular file.
 *   - When a parent directory of the entry whose filetype is the regular
 *     file is changed, check the global datas and write it again if its
 *     values are different from the entry's.
 */
static void
set_global(struct mtree_writer *mtree, struct archive_entry *entry)
{
	struct archive_string setstr;
	struct archive_string unsetstr;
	const char *name;
	int keys, oldkeys, effkeys;
	mode_t set_type = 0;

	switch (archive_entry_filetype(entry)) {
	case AE_IFLNK: case AE_IFSOCK: case AE_IFCHR:
	case AE_IFBLK: case AE_IFIFO:
		break;
	case AE_IFDIR:
		if (mtree->dironly)
			set_type = AE_IFDIR;
		break;
	case AE_IFREG:
	default:	/* Handle unknown file types as regular files. */
		if (!mtree->dironly)
			set_type = AE_IFREG;
		break;
	}
	if (set_type == 0)
		return;
	if (mtree->set.processed &&
	    !parent_dir_changed(&mtree->set.parent, entry))
		return;
	/* At first, save a parent directory of the entry for following
	 * entries. */
	if (!mtree->set.processed && set_type == AE_IFREG)
		parent_dir_changed(&mtree->set.parent, entry);

	archive_string_init(&setstr);
	archive_string_init(&unsetstr);
	keys = mtree->keys & (F_FLAGS | F_GID | F_GNAME | F_NLINK | F_MODE
	    | F_TYPE | F_UID | F_UNAME);
	oldkeys = mtree->set.keys;
	effkeys = keys;
	if (mtree->set.processed) {
		/*
		 * Check the global datas for whether it needs updating.
		 */
		effkeys &= ~F_TYPE;
		if ((oldkeys & (F_UNAME | F_UID)) != 0 &&
		    mtree->set.uid == archive_entry_uid(entry))
			effkeys &= ~(F_UNAME | F_UID);
		if ((oldkeys & (F_GNAME | F_GID)) != 0 &&
		    mtree->set.gid == archive_entry_gid(entry))
			effkeys &= ~(F_GNAME | F_GID);
		if ((oldkeys & F_MODE) != 0 &&
		    mtree->set.mode == (archive_entry_mode(entry) & 07777))
			effkeys &= ~F_MODE;
		if ((oldkeys & F_FLAGS) != 0) {
			unsigned long	fflags_set;
			unsigned long	fflags_clear;

			archive_entry_fflags(entry, &fflags_set, &fflags_clear);
			if (fflags_set == mtree->set.fflags_set &&
			    fflags_clear == mtree->set.fflags_clear)
				effkeys &= ~F_FLAGS;
		}
	}
	if ((keys & effkeys & F_TYPE) != 0) {
		mtree->set.type = set_type;
		if (set_type == AE_IFDIR)
			archive_strcat(&setstr, " type=dir");
		else
			archive_strcat(&setstr, " type=file");
	}
	if ((keys & effkeys & F_UNAME) != 0) {
		if ((name = archive_entry_uname(entry)) != NULL) {
			archive_strcat(&setstr, " uname=");
			mtree_quote(&setstr, name);
		} else if ((oldkeys & F_UNAME) != 0)
			archive_strcat(&unsetstr, " uname");
		else
			keys &= ~F_UNAME;
	}
	if ((keys & effkeys & F_UID) != 0) {
		mtree->set.uid = archive_entry_uid(entry);
		archive_string_sprintf(&setstr, " uid=%jd",
		    (intmax_t)mtree->set.uid);
	}
	if ((keys & effkeys & F_GNAME) != 0) {
		if ((name = archive_entry_gname(entry)) != NULL) {
			archive_strcat(&setstr, " gname=");
			mtree_quote(&setstr, name);
		} else if ((oldkeys & F_GNAME) != 0)
			archive_strcat(&unsetstr, " gname");
		else
			keys &= ~F_GNAME;
	}
	if ((keys & effkeys & F_GID) != 0) {
		mtree->set.gid = archive_entry_gid(entry);
		archive_string_sprintf(&setstr, " gid=%jd",
		    (intmax_t)mtree->set.gid);
	}
	if ((keys & effkeys & F_MODE) != 0) {
		mtree->set.mode = archive_entry_mode(entry) & 07777;
		archive_string_sprintf(&setstr, " mode=%o", mtree->set.mode);
	}
	if ((keys & effkeys & F_FLAGS) != 0) {
		if ((name = archive_entry_fflags_text(entry)) != NULL) {
			archive_strcat(&setstr, " flags=");
			mtree_quote(&setstr, name);
			archive_entry_fflags(entry, &mtree->set.fflags_set,
			    &mtree->set.fflags_clear);
		} else if ((oldkeys & F_FLAGS) != 0)
			archive_strcat(&unsetstr, " flags");
		else
			keys &= ~F_FLAGS;
	}
	if (unsetstr.length > 0)
		archive_string_sprintf(&mtree->buf, "/unset%s\n", unsetstr.s);
	archive_string_free(&unsetstr);
	if (setstr.length > 0)
		archive_string_sprintf(&mtree->buf, "/set%s\n", setstr.s);
	archive_string_free(&setstr);
	mtree->set.keys = keys;
	mtree->set.processed = 1;
	/* On directory-only mode, it is only once to write /set keyword. */
	if (mtree->dironly)
		mtree->set.output = 0;
}
Exemple #5
0
/*
 * Add the file or dir hierarchy named by 'path' to the archive
 */
static void
write_hierarchy(struct bsdtar *bsdtar, struct archive *a, const char *path)
{
	struct archive_entry *entry = NULL, *spare_entry = NULL;
	struct tree *tree;
	char symlink_mode = bsdtar->symlink_mode;
	dev_t first_dev = 0;
	int dev_recorded = 0;
	int tree_ret;
	dev_t last_dev = 0;
	char * fstype;

	tree = tree_open(path);

	if (!tree) {
		bsdtar_warnc(bsdtar, errno, "%s: Cannot open", path);
		bsdtar->return_value = 1;
		return;
	}

	while ((tree_ret = tree_next(tree))) {
		int r;
		const char *name = tree_current_path(tree);
		const struct stat *st = NULL; /* info to use for this entry */
		const struct stat *lst = NULL; /* lstat() information */
		int descend;

		if (truncate_archive(bsdtar))
			break;
		if (checkpoint_archive(bsdtar, 0))
			exit(1);
		disk_pause(bsdtar);
		if (network_select(0))
			exit(1);

		if (tree_ret == TREE_ERROR_FATAL)
			bsdtar_errc(bsdtar, 1, tree_errno(tree),
			    "%s: Unable to continue traversing directory tree",
			    name);
		if (tree_ret == TREE_ERROR_DIR) {
			bsdtar_warnc(bsdtar, errno,
			    "%s: Couldn't visit directory", name);
			bsdtar->return_value = 1;
		}
		if (tree_ret != TREE_REGULAR)
			continue;

		/*
		 * If this file/dir is excluded by a filename
		 * pattern, skip it.
		 */
		if (excluded(bsdtar, name))
			continue;

		/*
		 * Get lstat() info from the tree library.
		 */
		lst = tree_current_lstat(tree);
		if (lst == NULL) {
			/* Couldn't lstat(); must not exist. */
			bsdtar_warnc(bsdtar, errno, "%s: Cannot stat", name);
			/* Return error if files disappear during traverse. */
			bsdtar->return_value = 1;
			continue;
		}

		/*
		 * Distinguish 'L'/'P'/'H' symlink following.
		 */
		switch(symlink_mode) {
		case 'H':
			/* 'H': After the first item, rest like 'P'. */
			symlink_mode = 'P';
			/* 'H': First item (from command line) like 'L'. */
			/* FALLTHROUGH */
		case 'L':
			/* 'L': Do descend through a symlink to dir. */
			descend = tree_current_is_dir(tree);
			/* 'L': Follow symlinks to files. */
			archive_read_disk_set_symlink_logical(bsdtar->diskreader);
			/* 'L': Archive symlinks as targets, if we can. */
			st = tree_current_stat(tree);
			if (st != NULL)
				break;
			/* If stat fails, we have a broken symlink;
			 * in that case, don't follow the link. */
			/* FALLTHROUGH */
		default:
			/* 'P': Don't descend through a symlink to dir. */
			descend = tree_current_is_physical_dir(tree);
			/* 'P': Don't follow symlinks to files. */
			archive_read_disk_set_symlink_physical(bsdtar->diskreader);
			/* 'P': Archive symlinks as symlinks. */
			st = lst;
			break;
		}

		if (bsdtar->option_no_subdirs)
			descend = 0;

		/*
		 * If user has asked us not to cross mount points,
		 * then don't descend into a dir on a different
		 * device.
		 */
		if (!dev_recorded) {
			last_dev = first_dev = lst->st_dev;
			dev_recorded = 1;
		}
		if (bsdtar->option_dont_traverse_mounts) {
			if (lst->st_dev != first_dev)
				descend = 0;
		}

		/*
		 * If the user did not specify --insane-filesystems, do not
		 * cross into a new filesystem which is known to be synthetic.
		 * Note that we will archive synthetic filesystems if we are
		 * explicitly told to do so.
		 */
		if ((bsdtar->option_insane_filesystems == 0) &&
		    (descend != 0) &&
		    (lst->st_dev != last_dev)) {
			fstype = getfstype(tree_current_access_path(tree));
			if (fstype == NULL)
				bsdtar_errc(bsdtar, 1, errno,
				    "%s: Error getting filesystem type",
				    name);
			if (getfstype_issynthetic(fstype)) {
				if (!bsdtar->option_quiet)
					bsdtar_warnc(bsdtar, 0,
					    "Not descending into filesystem of type %s: %s",
					    fstype, name);
				descend = 0;
			} else {
				/* This device is ok to archive. */
				last_dev = lst->st_dev;
			}
			free(fstype);
		}

		/*
		 * In -u mode, check that the file is newer than what's
		 * already in the archive; in all modes, obey --newerXXX flags.
		 */
		if (!new_enough(bsdtar, name, st)) {
			if (!descend)
				continue;
			if (bsdtar->option_interactive &&
			    !yes("add '%s'", name))
				continue;
			tree_descend(tree);
			continue;
		}

		archive_entry_free(entry);
		entry = archive_entry_new();

		archive_entry_set_pathname(entry, name);
		archive_entry_copy_sourcepath(entry,
		    tree_current_access_path(tree));

		/* Populate the archive_entry with metadata from the disk. */
		/* XXX TODO: Arrange to open a regular file before
		 * calling this so we can pass in an fd and shorten
		 * the race to query metadata.  The linkify dance
		 * makes this more complex than it might sound. */
		r = archive_read_disk_entry_from_file(bsdtar->diskreader,
		    entry, -1, st);
		if (r != ARCHIVE_OK)
			bsdtar_warnc(bsdtar, archive_errno(bsdtar->diskreader),
			    "%s", archive_error_string(bsdtar->diskreader));
		if (r < ARCHIVE_WARN)
			continue;

		/* XXX TODO: Just use flag data from entry; avoid the
		 * duplicate check here. */

		/*
		 * If this file/dir is flagged "nodump" and we're
		 * honoring such flags, skip this file/dir.
		 */
#if defined(HAVE_STRUCT_STAT_ST_FLAGS) && defined(UF_NODUMP)
		/* BSD systems store flags in struct stat */
		if (bsdtar->option_honor_nodump &&
		    (lst->st_flags & UF_NODUMP))
			continue;
#endif

#if defined(EXT2_NODUMP_FL)
		/* Linux uses ioctl to read flags. */
		if (bsdtar->option_honor_nodump) {
			unsigned long fflags, dummy;
			archive_entry_fflags(entry, &fflags, &dummy);
			if (fflags & EXT2_NODUMP_FL)
				continue;
		}
#endif

		/*
		 * Don't back up the cache directory or any files inside it.
		 */
		if ((lst->st_ino == bsdtar->cachedir_ino) &&
		    (lst->st_dev == bsdtar->cachedir_dev)) {
			if (!bsdtar->option_quiet)
				bsdtar_warnc(bsdtar, 0,
				    "Not adding cache directory to archive: %s",
				name);
			continue;
		}

		/*
		 * If the user vetoes this file/directory, skip it.
		 * We want this to be fairly late; if some other
		 * check would veto this file, we shouldn't bother
		 * the user with it.
		 */
		if (bsdtar->option_interactive &&
		    !yes("add '%s'", name))
			continue;

		/* Note: if user vetoes, we won't descend. */
		if (descend)
			tree_descend(tree);

		/*
		 * Rewrite the pathname to be archived.  If rewrite
		 * fails, skip the entry.
		 */
		if (edit_pathname(bsdtar, entry))
			continue;

		/*
		 * If this is a socket, skip the entry: POSIX requires that
		 * pax(1) emit a "diagnostic message" (i.e., warning) that
		 * sockets cannot be archived, but this can make backups of
		 * running systems very noisy.
		 */
		if (S_ISSOCK(st->st_mode))
			continue;

		/* Display entry as we process it.
		 * This format is required by SUSv2. */
		if (bsdtar->verbose)
			safe_fprintf(stderr, "a %s",
			    archive_entry_pathname(entry));

		/*
		 * If the user hasn't specifically asked to have the access
		 * time stored, zero it.  At the moment this usually only
		 * matters for files which have flags set, since the "posix
		 * restricted" format doesn't store access times for most
		 * other files.
		 */
		if (bsdtar->option_store_atime == 0)
			archive_entry_set_atime(entry, 0, 0);

		/* Non-regular files get archived with zero size. */
		if (!S_ISREG(st->st_mode))
			archive_entry_set_size(entry, 0);

		/* Record what we're doing, for SIGINFO / SIGUSR1. */
		siginfo_setinfo(bsdtar, "adding",
		    archive_entry_pathname(entry), archive_entry_size(entry));
		archive_entry_linkify(bsdtar->resolver, &entry, &spare_entry);

		/* Handle SIGINFO / SIGUSR1 request if one was made. */
		siginfo_printinfo(bsdtar, 0);

		while (entry != NULL) {
			write_entry_backend(bsdtar, a, entry, st,
			    tree_current_realpath(tree));
			archive_entry_free(entry);
			entry = spare_entry;
			spare_entry = NULL;
		}

		if (bsdtar->verbose)
			fprintf(stderr, "\n");
	}
	archive_entry_free(entry);
	if (tree_close(tree))
		bsdtar_errc(bsdtar, 1, 0, "Error traversing directory tree");
}
static void
test_write_format_mtree_sub(int use_set)
{
	struct archive_entry *ae;
	struct archive* a;
	size_t used;
	int i;

	/* Create a mtree format archive. */
	assert((a = archive_write_new()) != NULL);
	assertEqualIntA(a, ARCHIVE_OK, archive_write_set_format_mtree(a));
	if (use_set)
		assertEqualIntA(a, ARCHIVE_OK,
		    archive_write_set_options(a, "use-set,!all,flags,type"));
	else
		assertEqualIntA(a, ARCHIVE_OK,
		    archive_write_set_options(a, "!all,flags,type"));
	assertEqualIntA(a, ARCHIVE_OK,
	    archive_write_open_memory(a, buff, sizeof(buff)-1, &used));

	/* Write entries */
	for (i = 0; entries[i].path != NULL; i++) {
		assert((ae = archive_entry_new()) != NULL);
		archive_entry_set_fflags(ae, entries[i].fflags, 0);
		archive_entry_copy_pathname(ae, entries[i].path);
		archive_entry_set_size(ae, 0);
		assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
		archive_entry_free(ae);
	}
	assertEqualIntA(a, ARCHIVE_OK, archive_write_close(a));
        assertEqualInt(ARCHIVE_OK, archive_write_free(a));

	if (use_set) {
		const char *p;

		buff[used] = '\0';
		assert(NULL != (p = strstr(buff, "\n/set ")));
		if (p != NULL) {
			char *r;
			const char *o;
			p++;
			r = strchr(p, '\n');
			if (r != NULL)
				*r = '\0';
			o = "/set type=file flags=uchg,nodump";
			assertEqualString(o, p);
			if (r != NULL)
				*r = '\n';
		}
	}

	/*
	 * Read the data and check it.
	 */
	assert((a = archive_read_new()) != NULL);
	assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a));
	assertEqualIntA(a, ARCHIVE_OK, archive_read_support_filter_all(a));
	assertEqualIntA(a, ARCHIVE_OK, archive_read_open_memory(a, buff, used));

	/* Read entries */
	for (i = 0; entries[i].path != NULL; i++) {
		unsigned long fset, fclr;

		assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
		archive_entry_fflags(ae, &fset, &fclr);
		assertEqualInt((int)entries[i].fflags, (int)fset);
		assertEqualInt(0, (int)fclr);
		assertEqualString(entries[i].path, archive_entry_pathname(ae));
	}
	assertEqualIntA(a, ARCHIVE_OK, archive_read_close(a));
	assertEqualInt(ARCHIVE_OK, archive_read_free(a));
}