[Reproducible-commits] [dpkg] 42/63: libdpkg: New treewalk module

Jérémy Bobbio lunar at moszumanska.debian.org
Fri Mar 4 17:44:44 UTC 2016


This is an automated email from the git hooks/post-receive script.

lunar pushed a commit to branch pu/buildinfo
in repository dpkg.

commit c2989c39ecfe9b917dbbc0c6087c7b5df9f28d23
Author: Guillem Jover <guillem at debian.org>
Date:   Fri Feb 26 10:49:42 2016 +0100

    libdpkg: New treewalk module
    
    This has the nice properties of avoiding duplicated stat(2) calls,
    not calling find(1), and sorting the output w/o stalling on the
    entire input being slurped and sorted.
---
 debian/changelog        |   3 +
 lib/dpkg/Makefile.am    |   2 +
 lib/dpkg/t/.gitignore   |   1 +
 lib/dpkg/t/Makefile.am  |   2 +
 lib/dpkg/t/t-tree.t     | 160 +++++++++++++++
 lib/dpkg/t/t-treewalk.c | 132 ++++++++++++
 lib/dpkg/treewalk.c     | 534 ++++++++++++++++++++++++++++++++++++++++++++++++
 lib/dpkg/treewalk.h     |  88 ++++++++
 po/POTFILES.in          |   1 +
 9 files changed, 923 insertions(+)

diff --git a/debian/changelog b/debian/changelog
index e7ce043..476eeae 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -35,6 +35,9 @@ dpkg (1.18.5) UNRELEASED; urgency=medium
     Thanks to Carsten Hey <carsten at debian.org>.
   * Allow broken versions starting with a dash in dpkg-maintscript-helper.
     Thanks to Carsten Hey <carsten at debian.org>.
+  * Add a new treewalk module in libdpkg, with the nice properties of avoiding
+    duplicate stat(2) calls, not calling find(1), and sorting the output w/o
+    stalling on the entire input being slurped and sorted.
   * Portability:
     - Move DPKG_ADMINDIR environment variable name out from update-alternatives
       code, to make life easier for non-dpkg-based systems.
diff --git a/lib/dpkg/Makefile.am b/lib/dpkg/Makefile.am
index 3a75b3c..090272e 100644
--- a/lib/dpkg/Makefile.am
+++ b/lib/dpkg/Makefile.am
@@ -93,6 +93,7 @@ libdpkg_la_SOURCES = \
 	subproc.c \
 	tarfn.c \
 	test.h \
+	treewalk.c \
 	trigname.c \
 	trignote.c \
 	triglib.c \
@@ -138,6 +139,7 @@ pkginclude_HEADERS = \
 	string.h \
 	subproc.h \
 	tarfn.h \
+	treewalk.h \
 	trigdeferred.h \
 	triglib.h \
 	varbuf.h \
diff --git a/lib/dpkg/t/.gitignore b/lib/dpkg/t/.gitignore
index f5955b9..fbff216 100644
--- a/lib/dpkg/t/.gitignore
+++ b/lib/dpkg/t/.gitignore
@@ -18,6 +18,7 @@ t-tar/
 t-tarextract
 t-test
 t-test-skip
+t-treewalk
 t-trigger
 t-varbuf
 t-version
diff --git a/lib/dpkg/t/Makefile.am b/lib/dpkg/t/Makefile.am
index 505f77f..419fd4f 100644
--- a/lib/dpkg/t/Makefile.am
+++ b/lib/dpkg/t/Makefile.am
@@ -41,11 +41,13 @@ test_programs = \
 
 test_scripts = \
 	t-tar.t \
+	t-tree.t \
 	$(nil)
 
 check_PROGRAMS = \
 	$(test_programs) \
 	t-tarextract \
+	t-treewalk \
 	$(nil)
 
 test_tmpdir = t.tmp
diff --git a/lib/dpkg/t/t-tree.t b/lib/dpkg/t/t-tree.t
new file mode 100755
index 0000000..1622129
--- /dev/null
+++ b/lib/dpkg/t/t-tree.t
@@ -0,0 +1,160 @@
+#!/usr/bin/perl
+#
+# Copyright © 2016 Guillem Jover <guillem at debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use strict;
+use warnings;
+use version;
+
+use Test::More tests => 6;
+use Cwd;
+use File::Path qw(make_path remove_tree);
+use File::Temp qw(tempdir);
+use File::Basename;
+use File::Find;
+
+use Dpkg::IPC;
+
+my $srcdir = $ENV{srcdir} || '.';
+my $builddir = $ENV{builddir} || '.';
+my $tmpdir = 't.tmp/t-tree';
+
+# Set a known umask.
+umask 0022;
+
+sub make_file {
+    my ($pathname) = @_;
+
+    open my $fh, '>>', $pathname or die "cannot touch $pathname: $!";
+    close $fh;
+}
+
+# Popullate the tree hierarchy.
+sub make_tree {
+    my ($dirtree) = @_;
+    my $cwd = cwd();
+
+    make_path($dirtree);
+    chdir $dirtree;
+
+    # Deep tree.
+    make_path('aaaa/aaaa/aaaa/aaaa/');
+    make_file('aaaa/aaaa/aaaa/aaaa/abcde');
+    make_file('aaaa/aaaa/aaaa/aaaa/ddddd');
+    make_file('aaaa/aaaa/aaaa/aaaa/wwwwa');
+    make_file('aaaa/aaaa/aaaa/aaaa/wwwwz');
+    make_file('aaaa/aaaa/aaaa/aaaa/zzzzz');
+
+    # Shallow tree.
+    make_path('bbbb/');
+    make_file('bbbb/abcde');
+    make_file('bbbb/ddddd');
+    make_file('bbbb/wwwwa');
+    make_file('bbbb/wwwwz');
+    make_file('bbbb/zzzzz');
+
+    # Popullated tree.
+    make_path('cccc/aa/aa/aa/');
+    make_path('cccc/aa/aa/bb/aa/');
+    make_file('cccc/aa/aa/bb/aa/file-a');
+    make_file('cccc/aa/aa/bb/aa/file-z');
+    make_path('cccc/aa/bb/');
+    make_path('cccc/bb/aa/');
+    make_path('cccc/bb/bb/aa/aa/');
+    make_file('cccc/bb/bb/aa/aa/file-a');
+    make_file('cccc/bb/bb/aa/aa/file-z');
+    make_path('cccc/bb/bb/bb/');
+    make_file('cccc/bb/bb/bb/file-w');
+    make_path('cccc/cc/aa/');
+    make_path('cccc/cc/bb/aa/');
+    make_file('cccc/cc/bb/aa/file-t');
+    make_path('cccc/cc/bb/bb/');
+    make_file('cccc/cc/bb/bb/file-x');
+    make_path('cccc/cc/cc/');
+    make_path('cccc/dd/aa/aa/aa/');
+    make_file('cccc/dd/aa/aa/aa/file-y');
+    make_path('cccc/dd/aa/aa/bb/');
+    make_file('cccc/dd/aa/aa/bb/file-o');
+    make_path('cccc/dd/aa/bb/aa/');
+    make_file('cccc/dd/aa/bb/aa/file-k');
+    make_path('cccc/dd/aa/bb/bb/');
+    make_file('cccc/dd/aa/bb/bb/file-l');
+    make_path('cccc/dd/aa/cc/aa/');
+    make_file('cccc/dd/aa/cc/aa/file-s');
+    make_path('cccc/dd/aa/cc/bb/');
+    make_file('cccc/dd/aa/cc/bb/file-u');
+
+    # Tree with symlinks cycles.
+    make_path('llll/self/');
+    make_file('llll/file');
+    symlink '..', 'llll/self/loop';
+    make_path('llll/real/');
+    make_file('llll/real/file-r');
+    symlink '../virt', 'llll/real/loop';
+    make_path('llll/virt/');
+    make_file('llll/virt/file-v');
+    symlink '../real', 'llll/virt/loop';
+
+    chdir $cwd;
+}
+
+sub test_treewalker {
+    my $stdout;
+    my $stderr;
+    my $dirtree = $tmpdir;
+
+    # Check generated tarballs.
+    foreach my $type (qw(full skip)) {
+        my @paths;
+
+        make_tree($dirtree);
+
+        find({ no_chdir => 1, wanted => sub {
+                   return if $type eq 'skip' and m{^\Q$dirtree\E/cccc};
+                   push @paths, $_ =~ s{\./}{}r;
+               },
+             }, $dirtree);
+
+        my $expected;
+
+        foreach my $path (sort @paths) {
+            lstat $path;
+
+            my $ptype;
+            if (-f _) {
+                $ptype = 'f';
+            } elsif (-l _) {
+                $ptype = 'l';
+            } elsif (-d _) {
+                $ptype = 'd';
+            }
+            my $pname = basename($path);
+            my $pvirt = $path =~ s{\Q$dirtree\E/?}{}r;
+
+            $expected .= "T=$ptype N=$pname V=$pvirt R=$path\n";
+        }
+
+        $ENV{TREEWALK_SKIP} = $type eq 'skip' ? "$dirtree/cccc" : undef;
+
+        spawn(exec => [ './t-treewalk', $dirtree ],
+              nocheck => 1, to_string => \$stdout, to_error => \$stderr);
+        ok($? == 0, "tree walker $type should succeed");
+        is($stderr, undef, "tree walker $type stderr is empty");
+        is($stdout, $expected, "tree walker $type is ok");
+    }
+}
+
+test_treewalker();
diff --git a/lib/dpkg/t/t-treewalk.c b/lib/dpkg/t/t-treewalk.c
new file mode 100644
index 0000000..a3eb2f9
--- /dev/null
+++ b/lib/dpkg/t/t-treewalk.c
@@ -0,0 +1,132 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * t-treewalk.c - test tree walk implementation
+ *
+ * Copyright © 2013-2016 Guillem Jover <guillem at debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/ehandle.h>
+#include <dpkg/treewalk.h>
+
+static int
+treenode_type(struct treenode *node)
+{
+	switch (treenode_get_mode(node) & S_IFMT) {
+	case S_IFREG:
+		return 'f';
+	case S_IFDIR:
+		return 'd';
+	case S_IFLNK:
+		return 'l';
+	case S_IFIFO:
+		return 'p';
+	case S_IFBLK:
+		return 'b';
+	case S_IFCHR:
+		return 'c';
+	case S_IFSOCK:
+		return 's';
+	default:
+		return '?';
+	}
+}
+
+static int
+treenode_visit_meta(struct treenode *node)
+{
+	printf("T=%c N=%s V=%s R=%s\n",
+	       treenode_type(node), treenode_get_name(node),
+	       treenode_get_virtname(node), treenode_get_pathname(node));
+
+	return 0;
+}
+
+static int
+treenode_visit_virt(struct treenode *node)
+{
+	printf("%s\n", treenode_get_virtname(node));
+
+	return 0;
+}
+
+static int
+treenode_visit_path(struct treenode *node)
+{
+	printf("%s\n", treenode_get_pathname(node));
+
+	return 0;
+}
+
+static const char *skipname;
+
+static bool
+treenode_skip(struct treenode *node)
+{
+	const char *absname = treenode_get_pathname(node);
+
+	if (strcmp(absname, skipname) == 0)
+		return true;
+
+	return false;
+}
+
+static void
+test_treewalk_list(const char *rootdir)
+{
+	const char *visitname = getenv("TREEWALK_VISIT");
+	struct treewalk_funcs funcs = { NULL };
+
+	if (visitname == NULL || strcmp(visitname, "meta") == 0)
+		funcs.visit = treenode_visit_meta;
+	else if (strcmp(visitname, "virt") == 0)
+		funcs.visit = treenode_visit_virt;
+	else if (strcmp(visitname, "path") == 0)
+		funcs.visit = treenode_visit_path;
+	else
+		ohshit("unknown treewalk visit name");
+
+	skipname = getenv("TREEWALK_SKIP");
+	if (skipname)
+		funcs.skip = treenode_skip;
+
+	treewalk(rootdir, 0, &funcs);
+}
+
+int
+main(int argc, char **argv)
+{
+	const char *rootdir = argv[1];
+
+	setvbuf(stdout, NULL, _IOLBF, 0);
+
+	push_error_context();
+
+	if (rootdir == NULL)
+		ohshit("missing treewalk root dir");
+
+	test_treewalk_list(rootdir);
+
+	pop_error_context(ehflag_normaltidy);
+
+	return 0;
+}
diff --git a/lib/dpkg/treewalk.c b/lib/dpkg/treewalk.c
new file mode 100644
index 0000000..a77ead8
--- /dev/null
+++ b/lib/dpkg/treewalk.c
@@ -0,0 +1,534 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * treewalk.c - directory tree walk support
+ *
+ * Copyright © 2013-2016 Guillem Jover <guillem at debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <dpkg/dpkg.h>
+#include <dpkg/i18n.h>
+#include <dpkg/treewalk.h>
+
+/* treenode functions. */
+
+typedef int treewalk_stat_func(const char *pathname, struct stat *st);
+
+struct treenode {
+	struct treenode *up;	/* Parent dir. */
+	struct treenode *next;	/* Next node in dir. */
+	struct treenode **down;	/* Dir contents. */
+
+	char *pathname;		/* Node pathname. */
+	char *virtname;		/* Node virtname. */
+	char *name;		/* Node name. */
+
+	struct stat *stat;	/* Node metadata. */
+	mode_t mode;
+
+	size_t pathname_len;	/* Node pathname length. */
+
+	size_t down_used;	/* Number of used nodes in dir. */
+	size_t down_size;	/* Number of allocated nodes in dir. */
+};
+
+static inline struct treenode *
+treenode_alloc(void)
+{
+	return m_calloc(1, sizeof(struct treenode));
+}
+
+static struct treenode *
+treenode_root_new(const char *rootdir)
+{
+	struct treenode *node;
+
+	node = treenode_alloc();
+	node->up = NULL;
+	node->pathname = m_strdup(rootdir);
+	node->pathname_len = strlen(node->pathname);
+	node->virtname = node->pathname + node->pathname_len;
+	node->name = strrchr(node->pathname, '/');
+	if (node->name == NULL)
+		node->name = node->pathname;
+	else
+		node->name++;
+
+	return node;
+}
+
+static struct treenode *
+treenode_node_new(struct treenode *root, struct treenode *dir, const char *name)
+{
+	struct treenode *node;
+
+	node = treenode_alloc();
+	node->up = dir;
+
+	node->pathname = str_fmt("%s/%s", node->up->pathname, name);
+	node->pathname_len = strlen(node->pathname);
+	node->virtname = node->pathname + root->pathname_len + 1;
+	node->name = node->pathname + node->up->pathname_len + 1;
+
+	return node;
+}
+
+static void
+treenode_stat(struct treenode *node, treewalk_stat_func *stat_func)
+{
+	if (node->stat)
+		return;
+
+	node->stat = m_malloc(sizeof(*node->stat));
+
+	if (stat_func(node->pathname, node->stat) < 0)
+		ohshite(_("cannot stat pathname '%s'"), node->pathname);
+
+	node->mode = node->stat->st_mode;
+}
+
+static mode_t
+dirent_to_mode_type(struct dirent *e)
+{
+#ifdef _DIRENT_HAVE_D_TYPE
+	switch (e->d_type) {
+	case DT_REG:
+		return S_IFREG;
+	case DT_DIR:
+		return S_IFDIR;
+	case DT_LNK:
+		return S_IFLNK;
+	case DT_CHR:
+		return S_IFCHR;
+	case DT_BLK:
+		return S_IFBLK;
+	case DT_FIFO:
+		return S_IFIFO;
+	case DT_SOCK:
+		return S_IFSOCK;
+	case DT_UNKNOWN:
+	default:
+		return 0;
+	}
+#else
+	return 0;
+#endif
+}
+
+static void
+treenode_stat_shallow(struct treenode *node, struct dirent *e,
+                      treewalk_stat_func *stat_func)
+{
+	mode_t mode;
+
+	mode = dirent_to_mode_type(e);
+	if (mode == 0 || S_ISDIR(mode) || S_ISLNK(mode))
+		treenode_stat(node, stat_func);
+	else
+		node->mode |= mode;
+}
+
+const char *
+treenode_get_name(struct treenode *node)
+{
+	return node->name;
+}
+
+const char *
+treenode_get_pathname(struct treenode *node)
+{
+	return node->pathname;
+}
+
+const char *
+treenode_get_virtname(struct treenode *node)
+{
+	return node->virtname;
+}
+
+mode_t
+treenode_get_mode(struct treenode *node)
+{
+	return node->mode;
+}
+
+struct stat *
+treenode_get_stat(struct treenode *node)
+{
+	treenode_stat(node, lstat);
+	return node->stat;
+}
+
+struct treenode *
+treenode_get_parent(struct treenode *node)
+{
+	return node->up;
+}
+
+static inline bool
+treenode_is_dir(struct treenode *node)
+{
+	return node && S_ISDIR(node->mode);
+}
+
+static void
+treenode_resize_down(struct treenode *node)
+{
+	size_t new_size;
+
+	if (node->down_size)
+		node->down_size *= 2;
+	else if (node->stat->st_nlink > 4)
+		node->down_size = node->stat->st_nlink * 2;
+	else
+		node->down_size = 8;
+
+	new_size = node->down_size * sizeof(struct treenode *);
+	node->down = m_realloc(node->down, new_size);
+}
+
+static int
+treenode_cmp(const void *a, const void *b)
+{
+	return strcmp((*(const struct treenode **)a)->name,
+	              (*(const struct treenode **)b)->name);
+}
+
+static void
+treenode_sort_down(struct treenode *dir)
+{
+	size_t i;
+
+	qsort(dir->down, dir->down_used, sizeof(struct treenode *), treenode_cmp);
+
+	/* Relink the nodes. */
+	for (i = 0; i < dir->down_used - 1; i++)
+		dir->down[i]->next = dir->down[i + 1];
+	dir->down[i]->next = NULL;
+}
+
+static void
+treenode_fill_down(struct treenode *root, struct treenode *dir,
+                   enum treewalk_options options)
+{
+	DIR *d;
+	struct dirent *e;
+	treewalk_stat_func *stat_func;
+
+	if (options & TREEWALK_FOLLOW_LINKS)
+		stat_func = stat;
+	else
+		stat_func = lstat;
+
+	d = opendir(dir->pathname);
+	if (!d)
+		ohshite(_("cannot open directory '%s'"), dir->pathname);
+
+	while ((e = readdir(d)) != NULL) {
+		struct treenode *node;
+
+		if (strcmp(e->d_name, ".") == 0 ||
+		    strcmp(e->d_name, "..") == 0)
+			continue;
+
+		if (dir->down_used >= dir->down_size)
+			treenode_resize_down(dir);
+
+		node = treenode_node_new(root, dir, e->d_name);
+		if (options & TREEWALK_FORCE_STAT)
+			treenode_stat(node, stat_func);
+		else
+			treenode_stat_shallow(node, e, stat_func);
+
+		dir->down[dir->down_used] = node;
+		dir->down_used++;
+	}
+
+	closedir(d);
+}
+
+static void
+treenode_free_node(struct treenode *node)
+{
+	free(node->pathname);
+	free(node);
+}
+
+static void
+treenode_free_down(struct treenode *node)
+{
+	size_t i;
+
+	if (!node->down_size)
+		return;
+
+	for (i = 0; i < node->down_used; i++)
+		treenode_free_node(node->down[i]);
+	free(node->down);
+}
+
+
+/* treeroot functions. */
+
+struct treeroot {
+	struct treenode *rootnode;
+
+	struct treenode *curdir;
+	struct treenode *curnode;
+
+	enum treewalk_options options;
+	struct treewalk_funcs func;
+};
+
+static inline void
+treeroot_set_curdir(struct treeroot *tree, struct treenode *node)
+{
+	tree->curdir = node;
+}
+
+static inline void
+treeroot_set_curnode(struct treeroot *tree, struct treenode *node)
+{
+	tree->curnode = node;
+}
+
+static bool
+treeroot_skip_node(struct treeroot *tree, struct treenode *node)
+{
+	if (tree->func.skip)
+		return tree->func.skip(node);
+
+	return false;
+}
+
+static void
+treeroot_fill_node(struct treeroot *tree, struct treenode *dir)
+{
+	treenode_fill_down(tree->rootnode, dir, tree->options);
+}
+
+static void
+treeroot_sort_node(struct treeroot *tree, struct treenode *dir)
+{
+	static struct treenode *down_empty[] = { NULL, NULL };
+
+	if (dir->down_used == 0)
+		dir->down = down_empty;
+	else if (tree->func.sort)
+		tree->func.sort(dir);
+	else
+		treenode_sort_down(dir);
+}
+
+static void
+treeroot_visit_node(struct treeroot *tree, struct treenode *node)
+{
+	if (tree->func.visit == NULL)
+		return;
+
+	if (!treeroot_skip_node(tree, node))
+		tree->func.visit(node);
+}
+
+/**
+ * Open a new tree to be walked.
+ *
+ * @param rootdir The root directory to start walking the tree.
+ * @param options The options specifying how to walk the tree.
+ * @param funcs   The functions callbacks.
+ */
+struct treeroot *
+treewalk_open(const char *rootdir, enum treewalk_options options,
+              struct treewalk_funcs *func)
+{
+	struct treeroot *tree;
+	struct treenode *root;
+
+	tree = m_malloc(sizeof(struct treeroot));
+
+	tree->options = options;
+	if (func)
+		tree->func = *func;
+	else
+		tree->func = (struct treewalk_funcs){ };
+
+	root = treenode_root_new(rootdir);
+	treenode_stat(root, lstat);
+
+	if (!treenode_is_dir(root))
+		ohshit(_("treewalk root %s is not a directory"), rootdir);
+
+	treeroot_set_curnode(tree, root);
+	tree->rootnode = tree->curdir = root;
+
+	return tree;
+}
+
+/**
+ * Return the current node.
+ *
+ * This function is only needed if you want to start walking the tree from
+ * the root node. With something like:
+ *
+ * @code
+ * struct treeroot *tree;
+ * struct treenode *node;
+ *
+ * tree = treewalk_open(...);
+ * for (node = treewalk_node(tree); node; node = treewalk_next(tree))
+ *         visitor(node);
+ * treewalk_close(tree);
+ * @endcode
+ *
+ * @param tree The tree structure.
+ */
+struct treenode *
+treewalk_node(struct treeroot *tree)
+{
+	return tree->curnode;
+}
+
+/**
+ * Return the next node.
+ *
+ * This function works basically as an iterator. And will return NULL when
+ * the whole tree has been traversed. When starting it will skip the root
+ * node, so you might want to use treewalk_node() to get that, otherwise
+ * you could use it like this:
+ *
+ * @code
+ * struct treeroot *tree;
+ * struct treenode *node;
+ *
+ * tree = treewalk_open(...);
+ * while ((node = treewalk_next(tree))
+ *         visitor(node);
+ * treewalk_close(tree);
+ * @endcode
+ *
+ * @param tree The tree structure.
+ */
+struct treenode *
+treewalk_next(struct treeroot *tree)
+{
+	struct treenode *node;
+
+	node = tree->curnode;
+
+	/* Handle end of tree. */
+	if (node == NULL)
+		return NULL;
+
+	/* Get next node, descending or sidewide. */
+	if (treenode_is_dir(node) && !treeroot_skip_node(tree, node)) {
+		struct treenode *dir;
+
+		treeroot_fill_node(tree, node);
+		treeroot_sort_node(tree, node);
+		treeroot_set_curdir(tree, node);
+
+		/* Check for directory loops. */
+		for (dir = node->up; dir; dir = dir->up) {
+			if (dir->stat->st_dev == node->stat->st_dev &&
+			    dir->stat->st_ino == node->stat->st_ino)
+				break;
+		}
+
+		/* Skip directory loops. */
+		if (dir)
+			node = node->next;
+		else
+			node = node->down[0];
+	} else {
+		node = node->next;
+	}
+
+	/* Back track node, ascending. */
+	while (node == NULL) {
+		struct treenode *olddir = tree->curdir;
+
+		if (tree->curdir->next) {
+			/* Next entry in the parent directory. */
+			node = tree->curdir->next;
+			treeroot_set_curdir(tree, olddir->up);
+			treenode_free_down(olddir);
+		} else if (tree->curdir->up) {
+			/* Next entry in the grand-parent directory. */
+			node = tree->curdir->up->next;
+			treeroot_set_curdir(tree, olddir->up->up);
+			treenode_free_down(olddir);
+			treenode_free_down(olddir->up);
+		} else {
+			/* Otherwise, we're in the rootnode. */
+			treenode_free_down(olddir);
+			treenode_free_node(olddir);
+			break;
+		}
+
+		if (tree->curdir == NULL)
+			break;
+	}
+
+	treeroot_set_curnode(tree, node);
+
+	return node;
+}
+
+/**
+ * Closes the tree being walked.
+ *
+ * It will free any resources previously allocated.
+ */
+void
+treewalk_close(struct treeroot *tree)
+{
+	free(tree);
+}
+
+/**
+ * Tree walker.
+ *
+ * @param rootdir The root directory to start walking the tree.
+ * @param options The options specifying how to walk the tree.
+ * @param funcs   The function callbacks.
+ */
+int
+treewalk(const char *rootdir, enum treewalk_options options,
+         struct treewalk_funcs *func)
+{
+	struct treeroot *tree;
+	struct treenode *node;
+
+	tree = treewalk_open(rootdir, options, func);
+
+	/* Breath first visit. */
+	for (node = treewalk_node(tree); node; node = treewalk_next(tree))
+		treeroot_visit_node(tree, node);
+
+	treewalk_close(tree);
+
+	return 0;
+}
diff --git a/lib/dpkg/treewalk.h b/lib/dpkg/treewalk.h
new file mode 100644
index 0000000..67a6129
--- /dev/null
+++ b/lib/dpkg/treewalk.h
@@ -0,0 +1,88 @@
+/*
+ * libdpkg - Debian packaging suite library routines
+ * treewalk.h - directory tree walk support
+ *
+ * Copyright © 2013-2015 Guillem Jover <guillem at debian.org>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBDPKG_TREEWALK_H
+#define LIBDPKG_TREEWALK_H
+
+#include <dpkg/macros.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <stdbool.h>
+
+DPKG_BEGIN_DECLS
+
+/**
+ * @defgroup treewalk Directory tree walking
+ * @ingroup dpkg-internal
+ * @{
+ */
+
+enum treewalk_options {
+	TREEWALK_NONE = 0,
+	TREEWALK_FORCE_STAT = DPKG_BIT(0),
+	TREEWALK_FOLLOW_LINKS = DPKG_BIT(1),
+};
+
+struct treenode;
+
+typedef int treenode_visit_func(struct treenode *node);
+typedef bool treenode_skip_func(struct treenode *node);
+typedef int treenode_sort_func(struct treenode *node);
+
+struct treewalk_funcs {
+	treenode_visit_func *visit;
+	treenode_sort_func *sort;
+	treenode_skip_func *skip;
+};
+
+struct treeroot *
+treewalk_open(const char *rootdir, enum treewalk_options options,
+              struct treewalk_funcs *funcs);
+struct treenode *
+treewalk_node(struct treeroot *tree);
+struct treenode *
+treewalk_next(struct treeroot *tree);
+void
+treewalk_close(struct treeroot *tree);
+
+int
+treewalk(const char *rootdir, enum treewalk_options options,
+         struct treewalk_funcs *funcs);
+
+struct treenode *
+treenode_get_parent(struct treenode *node);
+const char *
+treenode_get_name(struct treenode *node);
+const char *
+treenode_get_pathname(struct treenode *node);
+const char *
+treenode_get_virtname(struct treenode *node);
+mode_t
+treenode_get_mode(struct treenode *node);
+struct stat *
+treenode_get_stat(struct treenode *node);
+
+/** @} */
+
+DPKG_END_DECLS
+
+#endif /* LIBDPKG_TREEWALK_H */
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 501f9e9..13d941e 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -40,6 +40,7 @@ lib/dpkg/string.c
 lib/dpkg/strwide.c
 lib/dpkg/subproc.c
 lib/dpkg/tarfn.c
+lib/dpkg/treewalk.c
 lib/dpkg/trigdeferred.l
 lib/dpkg/triglib.c
 lib/dpkg/trigname.c

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/reproducible/dpkg.git



More information about the Reproducible-commits mailing list