Browse Source

list.h: opaque list iteration functionality

See https://github.com/rustyrussell/ccan/pull/1#issuecomment-2856561
for details.
(Reworked with minor cleanups -- Rusty)
Andrey Smirnov 14 years ago
parent
commit
e59b7388d8
4 changed files with 208 additions and 8 deletions
  1. 96 7
      ccan/list/list.h
  2. 56 0
      ccan/list/test/helper.c
  3. 9 0
      ccan/list/test/helper.h
  4. 47 1
      ccan/list/test/run.c

+ 96 - 7
ccan/list/list.h

@@ -326,9 +326,7 @@ static inline const void *list_tail_(const struct list_head *h, size_t off)
  *		printf("Name: %s\n", child->name);
  */
 #define list_for_each(h, i, member)					\
-	for (i = container_of_var(list_debug(h)->n.next, i, member);	\
-	     &i->member != &(h)->n;					\
-	     i = container_of_var(i->member.next, i, member))
+	list_for_each_off(h, i, list_off_var_(i, member))
 
 /**
  * list_for_each_rev - iterate through a list backwards.
@@ -367,14 +365,105 @@ static inline const void *list_tail_(const struct list_head *h, size_t off)
  *	}
  */
 #define list_for_each_safe(h, i, nxt, member)				\
-	for (i = container_of_var(list_debug(h)->n.next, i, member),	\
-		nxt = container_of_var(i->member.next, i, member);	\
-	     &i->member != &(h)->n;					\
-	     i = nxt, nxt = container_of_var(i->member.next, i, member))
+	list_for_each_safe_off(h, i, nxt, list_off_var_(i, member))
+
+/**
+ * list_for_each_off - iterate through a list of memory regions.
+ * @h: the list_head
+ * @i: the pointer to a memory region wich contains list node data.
+ * @off: offset(relative to @i) at which list node data resides.
+ *
+ * This is a low-level wrapper to iterate @i over the entire list, used to
+ * implement all oher, more high-level, for-each constructs. It's a for loop,
+ * so you can break and continue as normal.
+ *
+ * WARNING! Being the low-level macro that it is, this wrapper doesn't know
+ * nor care about the type of @i. The only assumtion made is that @i points
+ * to a chunk of memory that at some @offset, relative to @i, contains a
+ * properly filled `struct node_list' which in turn contains pointers to
+ * memory chunks and it's turtles all the way down. Whith all that in mind
+ * remember that given the wrong pointer/offset couple this macro will
+ * happilly churn all you memory untill SEGFAULT stops it, in other words
+ * caveat emptor.
+ *
+ * It is worth mentioning that one of legitimate use-cases for that wrapper
+ * is operation on opaque types with known offset for `struct list_node'
+ * member(preferably 0), because it allows you not to disclose the type of
+ * @i.
+ *
+ * Example:
+ *	list_for_each_off(&parent->children, child,
+ *				offsetof(struct child, list))
+ *		printf("Name: %s\n", child->name);
+ */
+#define list_for_each_off(h, i, off)                                    \
+  for (i = list_node_to_off_(list_debug(h)->n.next, (off));             \
+       list_node_from_off_((void *)i, (off)) != &(h)->n;                \
+       i = list_node_to_off_(list_node_from_off_((void *)i, (off))->next, \
+                             (off)))
+
+/**
+ * list_for_each_safe_off - iterate through a list of memory regions, maybe
+ * during deletion
+ * @h: the list_head
+ * @i: the pointer to a memory region wich contains list node data.
+ * @nxt: the structure containing the list_node
+ * @off: offset(relative to @i) at which list node data resides.
+ *
+ * For details see `list_for_each_off' and `list_for_each_safe'
+ * descriptions.
+ *
+ * Example:
+ *	list_for_each_safe_off(&parent->children, child,
+ *		next, offsetof(struct child, list))
+ *		printf("Name: %s\n", child->name);
+ */
+#define list_for_each_safe_off(h, i, nxt, off)                          \
+  for (i = list_node_to_off_(list_debug(h)->n.next, (off)),             \
+         nxt = list_node_to_off_(list_node_from_off_(i, (off))->next,   \
+                                 (off));                                \
+       list_node_from_off_(i, (off)) != &(h)->n;                        \
+       i = nxt,                                                         \
+         nxt = list_node_to_off_(list_node_from_off_(i, (off))->next,   \
+                                 (off)))
+
+
+/* Other -off variants. */
+#define list_entry_off(n, type, off)		\
+	((type *)list_node_from_off_((n), (off)))
+
+#define list_head_off(h, type, off)		\
+	((type *)list_head_off((h), (off)))
+
+#define list_tail_off(h, type, off)		\
+	((type *)list_tail_((h), (off)))
+
+#define list_add_off(h, n, off)                 \
+	list_add((h), list_node_from_off_((n), (off)))
+
+#define list_del_off(n, off)                    \
+	list_del(list_node_from_off_((n), (off)))
+
+#define list_del_from_off(h, n, off)			\
+	list_del_from(h, list_node_from_off_((n), (off)))
+
+/* Offset helper functions so we only single-evaluate. */
+static inline void *list_node_to_off_(struct list_node *node, size_t off)
+{
+	return (void *)((char *)node - off);
+}
+static inline struct list_node *list_node_from_off_(void *ptr, size_t off)
+{
+	return (struct list_node *)((char *)ptr + off);
+}
 
 /* Get the offset of the member, but make sure it's a list_node. */
 #define list_off_(type, member)					\
 	(container_off(type, member) +				\
 	 check_type(((type *)0)->member, struct list_node))
 
+#define list_off_var_(var, member)			\
+	(container_off_var(var, member) +		\
+	 check_type(var->member, struct list_node))
+
 #endif /* CCAN_LIST_H */

+ 56 - 0
ccan/list/test/helper.c

@@ -0,0 +1,56 @@
+#include <stdlib.h>
+#include <stdbool.h>
+#include <time.h>
+
+#include <ccan/list/list.h>
+#include "helper.h"
+
+#define ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING \
+  (42)
+
+struct opaque {
+  struct list_node list;
+  size_t secret_offset;
+  char   secret_drawer[42];
+};
+
+static bool not_randomized = true;
+
+struct opaque *create_opaque_blob(void)
+{
+  struct opaque *blob = calloc(1, sizeof(struct opaque));
+
+  if (not_randomized) {
+    srandom((int)time(NULL));
+    not_randomized = false;
+  }
+
+  blob->secret_offset = random() % (sizeof(blob->secret_drawer));
+  blob->secret_drawer[blob->secret_offset] =
+    ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING;
+
+  return blob;
+}
+
+bool if_blobs_know_the_secret(struct opaque *blob)
+{
+  bool answer = true;
+  int i;
+  for (i = 0; i < sizeof(blob->secret_drawer) /
+               sizeof(blob->secret_drawer[0]); i++)
+          if (i != blob->secret_offset)
+                  answer = answer && (blob->secret_drawer[i] == 0);
+          else
+                  answer = answer &&
+                          (blob->secret_drawer[blob->secret_offset] ==
+           ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING);
+
+  return answer;
+}
+
+void destroy_opaque_blob(struct opaque *blob)
+{
+  free(blob);
+}
+
+

+ 9 - 0
ccan/list/test/helper.h

@@ -0,0 +1,9 @@
+/* These are in a separate C file so we can test undefined structures. */
+struct opaque;
+typedef struct opaque opaque_t;
+
+opaque_t *create_opaque_blob(void);
+bool if_blobs_know_the_secret(opaque_t *blob);
+void destroy_opaque_blob(opaque_t *blob);
+
+

+ 47 - 1
ccan/list/test/run.c

@@ -1,6 +1,7 @@
 #include <ccan/list/list.h>
 #include <ccan/tap/tap.h>
 #include <ccan/list/list.c>
+#include "helper.h"
 
 struct parent {
 	const char *name;
@@ -21,8 +22,10 @@ int main(int argc, char *argv[])
 	struct child c1, c2, c3, *c, *n;
 	unsigned int i;
 	struct list_head list = LIST_HEAD_INIT(list);
+	opaque_t *q, *nq;
+	struct list_head opaque_list = LIST_HEAD_INIT(opaque_list);
 
-	plan_tests(53);
+	plan_tests(65);
 	/* Test LIST_HEAD, LIST_HEAD_INIT, list_empty and check_list */
 	ok1(list_empty(&static_list));
 	ok1(list_check(&static_list, NULL));
@@ -147,6 +150,49 @@ int main(int argc, char *argv[])
 	ok1(i == 3);
 	ok1(list_empty(&parent.children));
 
+	/* Test list_for_each_off. */
+	list_add_tail(&opaque_list,
+		      (struct list_node *)create_opaque_blob());
+	list_add_tail(&opaque_list,
+		      (struct list_node *)create_opaque_blob());
+	list_add_tail(&opaque_list,
+		      (struct list_node *)create_opaque_blob());
+
+	i = 0;
+
+	list_for_each_off(&opaque_list, q, 0) {
+	  i++;
+	  ok1(if_blobs_know_the_secret(q));
+	}
+	ok1(i == 3);
+
+	/* Test list_for_each_safe_off, list_del_off and list_del_from_off. */
+	i = 0;
+	list_for_each_safe_off(&opaque_list, q, nq, 0) {
+		switch (i++) {
+		case 0:
+			ok1(if_blobs_know_the_secret(q));
+			list_del_off(q, 0);
+			destroy_opaque_blob(q);
+			break;
+		case 1:
+			ok1(if_blobs_know_the_secret(q));
+			list_del_from_off(&opaque_list, q, 0);
+			destroy_opaque_blob(q);
+			break;
+		case 2:
+			ok1(c == &c3);
+			list_del_from_off(&opaque_list, q, 0);
+			destroy_opaque_blob(q);
+			break;
+		}
+		ok1(list_check(&opaque_list, NULL));
+		if (i > 2)
+			break;
+	}
+	ok1(i == 3);
+	ok1(list_empty(&opaque_list));
+
 	/* Test list_top/list_tail on empty list. */
 	ok1(list_top(&parent.children, struct child, list) == NULL);
 	ok1(list_tail(&parent.children, struct child, list) == NULL);