From 47f7566f6829c2b14e21bbbba699916de4998c72 Mon Sep 17 00:00:00 2001
From: Patrick Ohly <patrick.ohly@intel.com>
Date: Mon, 24 Oct 2016 12:54:48 +0200
Subject: [PATCH 1/1] non-recursive extract and list

Sometimes it makes sense to extract or list a directory contained in
an archive without also doing the same for the content of the
directory, i.e. allowing -n (= --no-recursion) in combination with the
x and t modes.

bsdtar uses the match functionality in libarchive to track include
matches. A new libarchive API call
archive_match_include_directories_recursively() gets introduced to
influence the matching behavior, with the default behavior as before.

Non-recursive matching can be achieved by anchoring the path match at
both start and end. Asking for a directory which itself isn't in the
archive when in non-recursive mode is an error and handled by the
existing mechanism for tracking unused inclusion entries.

Upstream-Status: Submitted [https://github.com/libarchive/libarchive/pull/812]

Signed-off-by: Patrick Ohly <patrick.ohly@intel.com>

---
 libarchive/archive.h       |  2 ++
 libarchive/archive_match.c | 30 +++++++++++++++++++++++++++++-
 tar/bsdtar.1               |  3 +--
 tar/bsdtar.c               | 12 ++++++++++--
 4 files changed, 42 insertions(+), 5 deletions(-)

diff --git a/libarchive/archive.h b/libarchive/archive.h
index 32710201..59fb4aa6 100644
--- a/libarchive/archive.h
+++ b/libarchive/archive.h
@@ -1093,6 +1093,8 @@ __LA_DECL int	archive_match_excluded(struct archive *,
  */
 __LA_DECL int	archive_match_path_excluded(struct archive *,
 		    struct archive_entry *);
+/* Control recursive inclusion of directory content when directory is included. Default on. */
+__LA_DECL int	archive_match_include_directories_recursively(struct archive *, int _enabled);
 /* Add exclusion pathname pattern. */
 __LA_DECL int	archive_match_exclude_pattern(struct archive *, const char *);
 __LA_DECL int	archive_match_exclude_pattern_w(struct archive *,
diff --git a/libarchive/archive_match.c b/libarchive/archive_match.c
index be72066e..bb6a3407 100644
--- a/libarchive/archive_match.c
+++ b/libarchive/archive_match.c
@@ -93,6 +93,9 @@ struct archive_match {
 	/* exclusion/inclusion set flag. */
 	int			 setflag;
 
+	/* Recursively include directory content? */
+	int			 recursive_include;
+
 	/*
 	 * Matching filename patterns.
 	 */
@@ -223,6 +226,7 @@ archive_match_new(void)
 		return (NULL);
 	a->archive.magic = ARCHIVE_MATCH_MAGIC;
 	a->archive.state = ARCHIVE_STATE_NEW;
+	a->recursive_include = 1;
 	match_list_init(&(a->inclusions));
 	match_list_init(&(a->exclusions));
 	__archive_rb_tree_init(&(a->exclusion_tree), &rb_ops_mbs);
@@ -471,6 +475,28 @@ archive_match_path_excluded(struct archive *_a,
 }
 
 /*
+ * When recursive inclusion of directory content is enabled,
+ * an inclusion pattern that matches a directory will also
+ * include everything beneath that directory. Enabled by default.
+ *
+ * For compatibility with GNU tar, exclusion patterns always
+ * match if a subset of the full patch matches (i.e., they are
+ * are not rooted at the beginning of the path) and thus there
+ * is no corresponding non-recursive exclusion mode.
+ */
+int
+archive_match_include_directories_recursively(struct archive *_a, int _enabled)
+{
+	struct archive_match *a;
+
+	archive_check_magic(_a, ARCHIVE_MATCH_MAGIC,
+	    ARCHIVE_STATE_NEW, "archive_match_include_directories_recursively");
+	a = (struct archive_match *)_a;
+	a->recursive_include = _enabled;
+	return (ARCHIVE_OK);
+}
+
+/*
  * Utility functions to get statistic information for inclusion patterns.
  */
 int
@@ -781,7 +807,9 @@ static int
 match_path_inclusion(struct archive_match *a, struct match *m,
     int mbs, const void *pn)
 {
-	int flag = PATHMATCH_NO_ANCHOR_END;
+	int flag = a->recursive_include ?
+		PATHMATCH_NO_ANCHOR_END : /* Prefix match is good enough. */
+		0; /* Full match required. */
 	int r;
 
 	if (mbs) {
diff --git a/tar/bsdtar.1 b/tar/bsdtar.1
index 132e1145..1dd2a847 100644
--- a/tar/bsdtar.1
+++ b/tar/bsdtar.1
@@ -386,8 +386,7 @@ and the default behavior in c, r, and u modes or if
 .Nm
 is run in x mode as root.
 .It Fl n , Fl Fl norecurse , Fl Fl no-recursion
-(c, r, u modes only)
-Do not recursively archive the contents of directories.
+Do not recursively archive (c, r, u), extract (x) or list (t) the contents of directories.
 .It Fl Fl newer Ar date
 (c, r, u modes only)
 Only include files and directories newer than the specified date.
diff --git a/tar/bsdtar.c b/tar/bsdtar.c
index 11dedbf9..d014cc3e 100644
--- a/tar/bsdtar.c
+++ b/tar/bsdtar.c
@@ -794,8 +794,6 @@ main(int argc, char **argv)
 			break;
 		}
 	}
-	if (bsdtar->flags & OPTFLAG_NO_SUBDIRS)
-		only_mode(bsdtar, "-n", "cru");
 	if (bsdtar->flags & OPTFLAG_STDOUT)
 		only_mode(bsdtar, "-O", "xt");
 	if (bsdtar->flags & OPTFLAG_UNLINK_FIRST)
@@ -845,6 +843,16 @@ main(int argc, char **argv)
 		only_mode(bsdtar, buff, "cru");
 	}
 
+	/*
+	 * When creating an archive from a directory tree, the directory
+	 * walking code will already avoid entering directories when
+	 * recursive inclusion of directory content is disabled, therefore
+	 * changing the matching behavior has no effect for creation modes.
+	 * It is relevant for extraction or listing.
+	 */
+	archive_match_include_directories_recursively(bsdtar->matching,
+						      !(bsdtar->flags & OPTFLAG_NO_SUBDIRS));
+
 	/* Filename "-" implies stdio. */
 	if (strcmp(bsdtar->filename, "-") == 0)
 		bsdtar->filename = NULL;
-- 
2.11.0

