[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