summaryrefslogtreecommitdiff
path: root/net/unix/garbage.c
diff options
context:
space:
mode:
Diffstat (limited to 'net/unix/garbage.c')
-rw-r--r--net/unix/garbage.c46
1 files changed, 44 insertions, 2 deletions
diff --git a/net/unix/garbage.c b/net/unix/garbage.c
index 8b16ab9e240e..33aadaa35346 100644
--- a/net/unix/garbage.c
+++ b/net/unix/garbage.c
@@ -251,11 +251,19 @@ static LIST_HEAD(unix_visited_vertices);
static void __unix_walk_scc(struct unix_vertex *vertex)
{
unsigned long index = UNIX_VERTEX_INDEX_START;
+ LIST_HEAD(vertex_stack);
struct unix_edge *edge;
LIST_HEAD(edge_stack);
next_vertex:
+ /* Push vertex to vertex_stack.
+ * The vertex will be popped when finalising SCC later.
+ */
+ vertex->on_stack = true;
+ list_add(&vertex->scc_entry, &vertex_stack);
+
vertex->index = index;
+ vertex->lowlink = index;
index++;
/* Explore neighbour vertices (receivers of the current vertex's fd). */
@@ -283,12 +291,46 @@ prev_vertex:
edge = list_first_entry(&edge_stack, typeof(*edge), stack_entry);
list_del_init(&edge->stack_entry);
+ next_vertex = vertex;
vertex = edge->predecessor->vertex;
+
+ /* If the successor has a smaller lowlink, two vertices
+ * are in the same SCC, so propagate the smaller lowlink
+ * to skip SCC finalisation.
+ */
+ vertex->lowlink = min(vertex->lowlink, next_vertex->lowlink);
+ } else if (next_vertex->on_stack) {
+ /* Loop detected by a back/cross edge.
+ *
+ * The successor is on vertex_stack, so two vertices are
+ * in the same SCC. If the successor has a smaller index,
+ * propagate it to skip SCC finalisation.
+ */
+ vertex->lowlink = min(vertex->lowlink, next_vertex->index);
+ } else {
+ /* The successor was already grouped as another SCC */
}
}
- /* Don't restart DFS from this vertex in unix_walk_scc(). */
- list_move_tail(&vertex->entry, &unix_visited_vertices);
+ if (vertex->index == vertex->lowlink) {
+ struct list_head scc;
+
+ /* SCC finalised.
+ *
+ * If the lowlink was not updated, all the vertices above on
+ * vertex_stack are in the same SCC. Group them using scc_entry.
+ */
+ __list_cut_position(&scc, &vertex_stack, &vertex->scc_entry);
+
+ list_for_each_entry_reverse(vertex, &scc, scc_entry) {
+ /* Don't restart DFS from this vertex in unix_walk_scc(). */
+ list_move_tail(&vertex->entry, &unix_visited_vertices);
+
+ vertex->on_stack = false;
+ }
+
+ list_del(&scc);
+ }
/* Need backtracking ? */
if (!list_empty(&edge_stack))