diff --git a/SOURCES/openvswitch-2.13.0.patch b/SOURCES/openvswitch-2.13.0.patch index 5746669..c68b0a9 100644 --- a/SOURCES/openvswitch-2.13.0.patch +++ b/SOURCES/openvswitch-2.13.0.patch @@ -785,6 +785,19 @@ index dbf88ec43f..7acdaac06b 100644 Reporting Bugs -------------- +diff --git a/Documentation/intro/install/general.rst b/Documentation/intro/install/general.rst +index 09f2c13f16..6312f5879c 100644 +--- a/Documentation/intro/install/general.rst ++++ b/Documentation/intro/install/general.rst +@@ -167,7 +167,7 @@ other than plain text, only if you have the following: + If you are going to extensively modify Open vSwitch, consider installing the + following to obtain better warnings: + +-- "sparse" version 0.5.1 or later ++- "sparse" version 0.6.2 or later + (https://git.kernel.org/pub/scm/devel/sparse/sparse.git/). + + - GNU make. diff --git a/Documentation/topics/dpdk/phy.rst b/Documentation/topics/dpdk/phy.rst index 38e52c8deb..55a98e2b0e 100644 --- a/Documentation/topics/dpdk/phy.rst @@ -1535,7 +1548,7 @@ index e06ddf2671..8e64d74aee 100644 :target: https://ci.appveyor.com/project/blp/ovs/history .. image:: https://api.cirrus-ci.com/github/openvswitch/ovs.svg diff --git a/acinclude.m4 b/acinclude.m4 -index c1470ccc6b..6401471494 100644 +index c1470ccc6b..106689493f 100644 --- a/acinclude.m4 +++ b/acinclude.m4 @@ -192,10 +192,10 @@ dnl Configure Linux tc compat. @@ -1645,6 +1658,15 @@ index c1470ccc6b..6401471494 100644 OVS_FIND_PARAM_IFELSE([$KSRC/include/net/protocol.h], [udp_add_offload], [net], [OVS_DEFINE([HAVE_UDP_ADD_OFFLOAD_TAKES_NET])]) +@@ -1284,7 +1319,7 @@ AC_DEFUN([OVS_ENABLE_SPARSE], + : ${SPARSE=sparse} + AC_SUBST([SPARSE]) + AC_CONFIG_COMMANDS_PRE( +- [CC='$(if $(C:0=),env REAL_CC="'"$CC"'" CHECK="$(SPARSE) $(SPARSE_WERROR) -I $(top_srcdir)/include/sparse $(SPARSEFLAGS) $(SPARSE_EXTRA_INCLUDES) " cgcc $(CGCCFLAGS),'"$CC"')']) ++ [CC='$(if $(C:0=),env REAL_CC="'"$CC"'" CHECK="$(SPARSE) $(SPARSE_WERROR) -I $(top_srcdir)/include/sparse -I $(top_srcdir)/include $(SPARSEFLAGS) $(SPARSE_EXTRA_INCLUDES) " cgcc $(CGCCFLAGS),'"$CC"')']) + + AC_ARG_ENABLE( + [sparse], @@ -1294,11 +1329,11 @@ AC_DEFUN([OVS_ENABLE_SPARSE], dnl OVS_CTAGS_IDENTIFIERS @@ -80868,6 +80890,211 @@ index 5289a70f6e..cf009f8264 100644 #define OVS_LOCKABLE __attribute__((lockable)) #define OVS_REQ_RDLOCK(...) __attribute__((shared_locks_required(__VA_ARGS__))) #define OVS_ACQ_RDLOCK(...) __attribute__((shared_lock_function(__VA_ARGS__))) +diff --git a/include/openvswitch/hmap.h b/include/openvswitch/hmap.h +index 8aea9c22db..68c284cf14 100644 +--- a/include/openvswitch/hmap.h ++++ b/include/openvswitch/hmap.h +@@ -134,15 +134,17 @@ struct hmap_node *hmap_random_node(const struct hmap *); + * without using 'break', NODE will be NULL. This is true for all of the + * HMAP_FOR_EACH_*() macros. + */ +-#define HMAP_FOR_EACH_WITH_HASH(NODE, MEMBER, HASH, HMAP) \ +- for (INIT_CONTAINER(NODE, hmap_first_with_hash(HMAP, HASH), MEMBER); \ +- (NODE != OBJECT_CONTAINING(NULL, NODE, MEMBER)) || (NODE = NULL); \ +- ASSIGN_CONTAINER(NODE, hmap_next_with_hash(&(NODE)->MEMBER), \ +- MEMBER)) +-#define HMAP_FOR_EACH_IN_BUCKET(NODE, MEMBER, HASH, HMAP) \ +- for (INIT_CONTAINER(NODE, hmap_first_in_bucket(HMAP, HASH), MEMBER); \ +- (NODE != OBJECT_CONTAINING(NULL, NODE, MEMBER)) || (NODE = NULL); \ +- ASSIGN_CONTAINER(NODE, hmap_next_in_bucket(&(NODE)->MEMBER), MEMBER)) ++#define HMAP_FOR_EACH_WITH_HASH(NODE, MEMBER, HASH, HMAP) \ ++ for (INIT_MULTIVAR(NODE, MEMBER, hmap_first_with_hash(HMAP, HASH), \ ++ struct hmap_node); \ ++ CONDITION_MULTIVAR(NODE, MEMBER, ITER_VAR(NODE) != NULL); \ ++ UPDATE_MULTIVAR(NODE, hmap_next_with_hash(ITER_VAR(NODE)))) ++ ++#define HMAP_FOR_EACH_IN_BUCKET(NODE, MEMBER, HASH, HMAP) \ ++ for (INIT_MULTIVAR(NODE, MEMBER, hmap_first_in_bucket(HMAP, HASH), \ ++ struct hmap_node); \ ++ CONDITION_MULTIVAR(NODE, MEMBER, ITER_VAR(NODE) != NULL); \ ++ UPDATE_MULTIVAR(NODE, hmap_next_in_bucket(ITER_VAR(NODE)))) + + static inline struct hmap_node *hmap_first_with_hash(const struct hmap *, + size_t hash); +@@ -168,50 +170,62 @@ bool hmap_contains(const struct hmap *, const struct hmap_node *); + /* Iterates through every node in HMAP. */ + #define HMAP_FOR_EACH(NODE, MEMBER, HMAP) \ + HMAP_FOR_EACH_INIT(NODE, MEMBER, HMAP, (void) 0) +-#define HMAP_FOR_EACH_INIT(NODE, MEMBER, HMAP, ...) \ +- for (INIT_CONTAINER(NODE, hmap_first(HMAP), MEMBER), __VA_ARGS__; \ +- (NODE != OBJECT_CONTAINING(NULL, NODE, MEMBER)) || (NODE = NULL); \ +- ASSIGN_CONTAINER(NODE, hmap_next(HMAP, &(NODE)->MEMBER), MEMBER)) ++#define HMAP_FOR_EACH_INIT(NODE, MEMBER, HMAP, ...) \ ++ for (INIT_MULTIVAR_EXP(NODE, MEMBER, hmap_first(HMAP), struct hmap_node, \ ++ __VA_ARGS__); \ ++ CONDITION_MULTIVAR(NODE, MEMBER, ITER_VAR(NODE) != NULL); \ ++ UPDATE_MULTIVAR(NODE, hmap_next(HMAP, ITER_VAR(NODE)))) + + /* Safe when NODE may be freed (not needed when NODE may be removed from the + * hash map but its members remain accessible and intact). */ + #define HMAP_FOR_EACH_SAFE(NODE, NEXT, MEMBER, HMAP) \ +- HMAP_FOR_EACH_SAFE_INIT(NODE, NEXT, MEMBER, HMAP, (void) 0) +-#define HMAP_FOR_EACH_SAFE_INIT(NODE, NEXT, MEMBER, HMAP, ...) \ +- for (INIT_CONTAINER(NODE, hmap_first(HMAP), MEMBER), __VA_ARGS__; \ +- ((NODE != OBJECT_CONTAINING(NULL, NODE, MEMBER)) || (NODE = NULL) \ +- ? INIT_CONTAINER(NEXT, hmap_next(HMAP, &(NODE)->MEMBER), MEMBER), 1 \ +- : 0); \ +- (NODE) = (NEXT)) ++ HMAP_FOR_EACH_SAFE_INIT (NODE, NEXT, MEMBER, HMAP, (void) NEXT) ++ ++#define HMAP_FOR_EACH_SAFE_INIT(NODE, NEXT, MEMBER, HMAP, ...) \ ++ for (INIT_MULTIVAR_SAFE_LONG_EXP(NODE, NEXT, MEMBER, hmap_first(HMAP), \ ++ struct hmap_node, __VA_ARGS__); \ ++ CONDITION_MULTIVAR_SAFE_LONG(NODE, NEXT, MEMBER, \ ++ ITER_VAR(NODE) != NULL, \ ++ ITER_VAR(NEXT) = hmap_next(HMAP, ITER_VAR(NODE)), \ ++ ITER_VAR(NEXT) != NULL); \ ++ UPDATE_MULTIVAR_SAFE_LONG(NODE, NEXT)) + + /* Continues an iteration from just after NODE. */ + #define HMAP_FOR_EACH_CONTINUE(NODE, MEMBER, HMAP) \ + HMAP_FOR_EACH_CONTINUE_INIT(NODE, MEMBER, HMAP, (void) 0) +-#define HMAP_FOR_EACH_CONTINUE_INIT(NODE, MEMBER, HMAP, ...) \ +- for (ASSIGN_CONTAINER(NODE, hmap_next(HMAP, &(NODE)->MEMBER), MEMBER), \ +- __VA_ARGS__; \ +- (NODE != OBJECT_CONTAINING(NULL, NODE, MEMBER)) || (NODE = NULL); \ +- ASSIGN_CONTAINER(NODE, hmap_next(HMAP, &(NODE)->MEMBER), MEMBER)) ++#define HMAP_FOR_EACH_CONTINUE_INIT(NODE, MEMBER, HMAP, ...) \ ++ for (INIT_MULTIVAR_EXP(NODE, MEMBER, hmap_next(HMAP, &(NODE)->MEMBER), \ ++ struct hmap_node, __VA_ARGS__); \ ++ CONDITION_MULTIVAR(NODE, MEMBER, ITER_VAR(NODE) != NULL); \ ++ UPDATE_MULTIVAR(NODE, hmap_next(HMAP, ITER_VAR(NODE)))) ++ ++struct hmap_pop_helper_iter__ { ++ size_t bucket; ++ struct hmap_node *node; ++}; + +-static inline struct hmap_node * +-hmap_pop_helper__(struct hmap *hmap, size_t *bucket) { ++static inline void ++hmap_pop_helper__(struct hmap *hmap, struct hmap_pop_helper_iter__ *iter) { + +- for (; *bucket <= hmap->mask; (*bucket)++) { +- struct hmap_node *node = hmap->buckets[*bucket]; ++ for (; iter->bucket <= hmap->mask; (iter->bucket)++) { ++ struct hmap_node *node = hmap->buckets[iter->bucket]; + + if (node) { + hmap_remove(hmap, node); +- return node; ++ iter->node = node; ++ return; + } + } +- +- return NULL; ++ iter->node = NULL; + } + +-#define HMAP_FOR_EACH_POP(NODE, MEMBER, HMAP) \ +- for (size_t bucket__ = 0; \ +- INIT_CONTAINER(NODE, hmap_pop_helper__(HMAP, &bucket__), MEMBER), \ +- (NODE != OBJECT_CONTAINING(NULL, NODE, MEMBER)) || (NODE = NULL);) ++#define HMAP_FOR_EACH_POP(NODE, MEMBER, HMAP) \ ++ for (struct hmap_pop_helper_iter__ ITER_VAR(NODE) = { 0, NULL }; \ ++ hmap_pop_helper__(HMAP, &ITER_VAR(NODE)), \ ++ (ITER_VAR(NODE).node != NULL) ? \ ++ (((NODE) = OBJECT_CONTAINING(ITER_VAR(NODE).node, \ ++ NODE, MEMBER)),1): \ ++ (((NODE) = NULL), 0);) + + static inline struct hmap_node *hmap_first(const struct hmap *); + static inline struct hmap_node *hmap_next(const struct hmap *, +diff --git a/include/openvswitch/list.h b/include/openvswitch/list.h +index 8ad5eeb327..bbd2edbd0c 100644 +--- a/include/openvswitch/list.h ++++ b/include/openvswitch/list.h +@@ -72,37 +72,48 @@ static inline bool ovs_list_is_empty(const struct ovs_list *); + static inline bool ovs_list_is_singleton(const struct ovs_list *); + static inline bool ovs_list_is_short(const struct ovs_list *); + +-#define LIST_FOR_EACH(ITER, MEMBER, LIST) \ +- for (INIT_CONTAINER(ITER, (LIST)->next, MEMBER); \ +- &(ITER)->MEMBER != (LIST); \ +- ASSIGN_CONTAINER(ITER, (ITER)->MEMBER.next, MEMBER)) +-#define LIST_FOR_EACH_CONTINUE(ITER, MEMBER, LIST) \ +- for (ASSIGN_CONTAINER(ITER, (ITER)->MEMBER.next, MEMBER); \ +- &(ITER)->MEMBER != (LIST); \ +- ASSIGN_CONTAINER(ITER, (ITER)->MEMBER.next, MEMBER)) +-#define LIST_FOR_EACH_REVERSE(ITER, MEMBER, LIST) \ +- for (INIT_CONTAINER(ITER, (LIST)->prev, MEMBER); \ +- &(ITER)->MEMBER != (LIST); \ +- ASSIGN_CONTAINER(ITER, (ITER)->MEMBER.prev, MEMBER)) +-#define LIST_FOR_EACH_REVERSE_SAFE(ITER, PREV, MEMBER, LIST) \ +- for (INIT_CONTAINER(ITER, (LIST)->prev, MEMBER); \ +- (&(ITER)->MEMBER != (LIST) \ +- ? INIT_CONTAINER(PREV, (ITER)->MEMBER.prev, MEMBER), 1 \ +- : 0); \ +- (ITER) = (PREV)) +-#define LIST_FOR_EACH_REVERSE_CONTINUE(ITER, MEMBER, LIST) \ +- for (ASSIGN_CONTAINER(ITER, (ITER)->MEMBER.prev, MEMBER); \ +- &(ITER)->MEMBER != (LIST); \ +- ASSIGN_CONTAINER(ITER, (ITER)->MEMBER.prev, MEMBER)) +-#define LIST_FOR_EACH_SAFE(ITER, NEXT, MEMBER, LIST) \ +- for (INIT_CONTAINER(ITER, (LIST)->next, MEMBER); \ +- (&(ITER)->MEMBER != (LIST) \ +- ? INIT_CONTAINER(NEXT, (ITER)->MEMBER.next, MEMBER), 1 \ +- : 0); \ +- (ITER) = (NEXT)) +-#define LIST_FOR_EACH_POP(ITER, MEMBER, LIST) \ +- while (!ovs_list_is_empty(LIST) \ +- && (INIT_CONTAINER(ITER, ovs_list_pop_front(LIST), MEMBER), 1)) ++#define LIST_FOR_EACH(VAR, MEMBER, LIST) \ ++ for (INIT_MULTIVAR(VAR, MEMBER, (LIST)->next, struct ovs_list); \ ++ CONDITION_MULTIVAR(VAR, MEMBER, ITER_VAR(VAR) != (LIST)); \ ++ UPDATE_MULTIVAR(VAR, ITER_VAR(VAR)->next)) ++ ++#define LIST_FOR_EACH_CONTINUE(VAR, MEMBER, LIST) \ ++ for (INIT_MULTIVAR(VAR, MEMBER, VAR->MEMBER.next, struct ovs_list); \ ++ CONDITION_MULTIVAR(VAR, MEMBER, ITER_VAR(VAR) != (LIST)); \ ++ UPDATE_MULTIVAR(VAR, ITER_VAR(VAR)->next)) ++ ++#define LIST_FOR_EACH_REVERSE(VAR, MEMBER, LIST) \ ++ for (INIT_MULTIVAR(VAR, MEMBER, (LIST)->prev, struct ovs_list); \ ++ CONDITION_MULTIVAR(VAR, MEMBER, ITER_VAR(VAR) != (LIST)); \ ++ UPDATE_MULTIVAR(VAR, ITER_VAR(VAR)->prev)) ++ ++#define LIST_FOR_EACH_REVERSE_CONTINUE(VAR, MEMBER, LIST) \ ++ for (INIT_MULTIVAR(VAR, MEMBER, VAR->MEMBER.prev, struct ovs_list); \ ++ CONDITION_MULTIVAR(VAR, MEMBER, ITER_VAR(VAR) != (LIST)); \ ++ UPDATE_MULTIVAR(VAR, ITER_VAR(VAR)->prev)) ++ ++#define LIST_FOR_EACH_REVERSE_SAFE(VAR, PREV, MEMBER, LIST) \ ++ for (INIT_MULTIVAR_SAFE_LONG(VAR, PREV, MEMBER, (LIST)->prev, \ ++ struct ovs_list); \ ++ CONDITION_MULTIVAR_SAFE_LONG(VAR, PREV, MEMBER, \ ++ ITER_VAR(VAR) != (LIST), \ ++ ITER_VAR(PREV) = ITER_VAR(VAR)->prev, \ ++ ITER_VAR(PREV) != (LIST)); \ ++ UPDATE_MULTIVAR_SAFE_LONG(VAR, PREV)) ++ ++#define LIST_FOR_EACH_SAFE(VAR, NEXT, MEMBER, LIST) \ ++ for (INIT_MULTIVAR_SAFE_LONG(VAR, NEXT, MEMBER, (LIST)->next, \ ++ struct ovs_list); \ ++ CONDITION_MULTIVAR_SAFE_LONG(VAR, NEXT, MEMBER, \ ++ ITER_VAR(VAR) != (LIST), \ ++ ITER_VAR(NEXT) = ITER_VAR(VAR)->next, \ ++ ITER_VAR(NEXT) != (LIST)); \ ++ UPDATE_MULTIVAR_SAFE_LONG(VAR, NEXT)) ++ ++#define LIST_FOR_EACH_POP(ITER, MEMBER, LIST) \ ++ while (!ovs_list_is_empty(LIST) ? \ ++ (INIT_CONTAINER(ITER, ovs_list_pop_front(LIST), MEMBER), 1) : \ ++ (ITER = NULL, 0)) + + /* Inline implementations. */ + diff --git a/include/openvswitch/meta-flow.h b/include/openvswitch/meta-flow.h index 1f81d830e7..4b9893388b 100644 --- a/include/openvswitch/meta-flow.h @@ -80936,6 +81163,161 @@ index 0000000000..a49563f071 +#endif + +#endif /* usdt-probes.h */ +diff --git a/include/openvswitch/util.h b/include/openvswitch/util.h +index 9189e6480b..9a924719bf 100644 +--- a/include/openvswitch/util.h ++++ b/include/openvswitch/util.h +@@ -143,6 +143,150 @@ OVS_NO_RETURN void ovs_assert_failure(const char *, const char *, const char *); + #define INIT_CONTAINER(OBJECT, POINTER, MEMBER) \ + ((OBJECT) = NULL, ASSIGN_CONTAINER(OBJECT, POINTER, MEMBER)) + ++/* Multi-variable container iterators. ++ * ++ * The following macros facilitate safe iteration over data structures ++ * contained in objects. It does so by using an internal iterator variable of ++ * the type of the member object pointer (i.e: pointer to the data structure). ++ */ ++ ++/* Multi-variable iterator variable name. ++ * Returns the name of the internal iterator variable. ++ */ ++#define ITER_VAR(NAME) NAME ## __iterator__ ++ ++/* Multi-variable initialization. Creates an internal iterator variable that ++ * points to the provided pointer. The type of the iterator variable is ++ * ITER_TYPE*. It must be the same type as &VAR->MEMBER. ++ * ++ * The _EXP version evaluates the extra expressions once. ++ */ ++#define INIT_MULTIVAR(VAR, MEMBER, POINTER, ITER_TYPE) \ ++ INIT_MULTIVAR_EXP(VAR, MEMBER, POINTER, ITER_TYPE, (void) 0) ++ ++#define INIT_MULTIVAR_EXP(VAR, MEMBER, POINTER, ITER_TYPE, ...) \ ++ ITER_TYPE *ITER_VAR(VAR) = ( __VA_ARGS__ , (ITER_TYPE *) POINTER) ++ ++/* Multi-variable condition. ++ * Evaluates the condition expression (that must be based on the internal ++ * iterator variable). Only if the result of expression is true, the OBJECT is ++ * set to the object containing the current value of the iterator variable. ++ * ++ * It is up to the caller to make sure it is safe to run OBJECT_CONTAINING on ++ * the pointers that verify the condition. ++ */ ++#define CONDITION_MULTIVAR(VAR, MEMBER, EXPR) \ ++ ((EXPR) ? \ ++ (((VAR) = OBJECT_CONTAINING(ITER_VAR(VAR), VAR, MEMBER)), 1) : \ ++ (((VAR) = NULL), 0)) ++ ++/* Multi-variable update. ++ * Sets the iterator value to NEXT_ITER. ++ */ ++#define UPDATE_MULTIVAR(VAR, NEXT_ITER) \ ++ (ITER_VAR(VAR) = NEXT_ITER) ++ ++/* In the safe version of the multi-variable container iteration, the next ++ * value of the iterator is precalculated on the condition expression. ++ * This allows for the iterator to be freed inside the loop. ++ * ++ * Two versions of the macros are provided: ++ * ++ * * In the _SHORT version, the user does not have to provide a variable to ++ * store the next value of the iterator. Instead, a second iterator variable ++ * is declared in the INIT_ macro and its name is determined by ++ * ITER_NEXT_VAR(OBJECT). ++ * ++ * * In the _LONG version, the user provides another variable of the same type ++ * as the iterator object variable to store the next containing object. ++ * We still declare an iterator variable inside the loop but in this case it's ++ * name is derived from the name of the next containing variable. ++ * The value of the next containing object will only be set ++ * (via OBJECT_CONTAINING) if an additional condition is statisfied. This ++ * second condition must ensure it is safe to call OBJECT_CONTAINING on the ++ * next iterator variable. ++ * With respect to the value of the next containing object: ++ * - Inside of the loop: the variable is either NULL or safe to use. ++ * - Outside of the loop: the variable is NULL if the loop ends normally. ++ * If the loop ends with a "break;" statement, rules of Inside the loop ++ * apply. ++ */ ++#define ITER_NEXT_VAR(NAME) NAME ## __iterator__next__ ++ ++/* Safe initialization declares both iterators. */ ++#define INIT_MULTIVAR_SAFE_SHORT(VAR, MEMBER, POINTER, ITER_TYPE) \ ++ INIT_MULTIVAR_SAFE_SHORT_EXP(VAR, MEMBER, POINTER, ITER_TYPE, (void) 0) ++ ++#define INIT_MULTIVAR_SAFE_SHORT_EXP(VAR, MEMBER, POINTER, ITER_TYPE, ...) \ ++ ITER_TYPE *ITER_VAR(VAR) = ( __VA_ARGS__ , (ITER_TYPE *) POINTER), \ ++ *ITER_NEXT_VAR(VAR) = NULL ++ ++/* Evaluate the condition expression and, if satisfied, update the _next_ ++ * iterator with the NEXT_EXPR. ++ * Both EXPR and NEXT_EXPR should only use ITER_VAR(VAR) and ++ * ITER_NEXT_VAR(VAR). ++ */ ++#define CONDITION_MULTIVAR_SAFE_SHORT(VAR, MEMBER, EXPR, NEXT_EXPR) \ ++ ((EXPR) ? \ ++ (((VAR) = OBJECT_CONTAINING(ITER_VAR(VAR), VAR, MEMBER)), \ ++ (NEXT_EXPR), 1) : \ ++ (((VAR) = NULL), 0)) ++ ++#define UPDATE_MULTIVAR_SAFE_SHORT(VAR) \ ++ UPDATE_MULTIVAR(VAR, ITER_NEXT_VAR(VAR)) ++ ++/* _LONG versions of the macros. */ ++ ++#define INIT_MULTIVAR_SAFE_LONG(VAR, NEXT_VAR, MEMBER, POINTER, ITER_TYPE) \ ++ INIT_MULTIVAR_SAFE_LONG_EXP(VAR, NEXT_VAR, MEMBER, POINTER, ITER_TYPE, \ ++ (void) 0) \ ++ ++#define INIT_MULTIVAR_SAFE_LONG_EXP(VAR, NEXT_VAR, MEMBER, POINTER, \ ++ ITER_TYPE, ...) \ ++ ITER_TYPE *ITER_VAR(VAR) = ( __VA_ARGS__ , (ITER_TYPE *) POINTER), \ ++ *ITER_VAR(NEXT_VAR) = NULL ++ ++/* Evaluate the condition expression and, if satisfied, update the _next_ ++ * iterator with the NEXT_EXPR. After, evaluate the NEXT_COND and, if ++ * satisfied, set the value to NEXT_VAR. NEXT_COND must use ITER_VAR(NEXT_VAR). ++ * ++ * Both EXPR and NEXT_EXPR should only use ITER_VAR(VAR) and ++ * ITER_VAR(NEXT_VAR). ++ */ ++#define CONDITION_MULTIVAR_SAFE_LONG(VAR, NEXT_VAR, MEMBER, EXPR, NEXT_EXPR, \ ++ NEXT_COND) \ ++ ((EXPR) ? \ ++ (((VAR) = OBJECT_CONTAINING(ITER_VAR(VAR), VAR, MEMBER)), \ ++ (NEXT_EXPR), ((NEXT_COND) ? \ ++ ((NEXT_VAR) = \ ++ OBJECT_CONTAINING(ITER_VAR(NEXT_VAR), NEXT_VAR, MEMBER)) : \ ++ ((NEXT_VAR) = NULL)), 1) : \ ++ (((VAR) = NULL), ((NEXT_VAR) = NULL), 0)) ++ ++#define UPDATE_MULTIVAR_SAFE_LONG(VAR, NEXT_VAR) \ ++ UPDATE_MULTIVAR(VAR, ITER_VAR(NEXT_VAR)) ++ ++/* Helpers to allow overloading the *_SAFE iterator macros and select either ++ * the LONG or the SHORT version depending on the number of arguments. ++ */ ++#define GET_SAFE_MACRO2(_1, _2, NAME, ...) NAME ++#define GET_SAFE_MACRO3(_1, _2, _3, NAME, ...) NAME ++#define GET_SAFE_MACRO4(_1, _2, _3, _4, NAME, ...) NAME ++#define GET_SAFE_MACRO5(_1, _2, _3, _4, _5, NAME, ...) NAME ++#define GET_SAFE_MACRO6(_1, _2, _3, _4, _5, _6, NAME, ...) NAME ++#define GET_SAFE_MACRO(MAX_ARGS) GET_SAFE_MACRO ## MAX_ARGS ++ ++/* MSVC treats __VA_ARGS__ as a simple token in argument lists. Introduce ++ * a level of indirection to work around that. */ ++#define EXPAND_MACRO(name, args) name args ++ ++/* Overload the LONG and the SHORT version of the macros. MAX_ARGS is the ++ * maximum number of arguments (i.e: the number of arguments of the LONG ++ * version). */ ++#define OVERLOAD_SAFE_MACRO(LONG, SHORT, MAX_ARGS, ...) \ ++ EXPAND_MACRO(GET_SAFE_MACRO(MAX_ARGS), \ ++ (__VA_ARGS__, LONG, SHORT))(__VA_ARGS__) ++ + /* Returns the number of elements in ARRAY. */ + #define ARRAY_SIZE(ARRAY) __ARRAY_SIZE(ARRAY) + diff --git a/ipsec/ovs-monitor-ipsec.in b/ipsec/ovs-monitor-ipsec.in index 37e3703245..41ef886194 100755 --- a/ipsec/ovs-monitor-ipsec.in @@ -81621,6 +82003,53 @@ index d1bd4aa12a..f646a8f742 100644 }; enum { +diff --git a/lib/cmap.h b/lib/cmap.h +index d9db3c915c..8655f76d92 100644 +--- a/lib/cmap.h ++++ b/lib/cmap.h +@@ -108,6 +108,8 @@ size_t cmap_replace(struct cmap *, struct cmap_node *old_node, + * + * CMAP and HASH are evaluated only once. NODE is evaluated many times. + * ++ * After a normal exit of the loop (not through a "break;" statement) NODE is ++ * NULL. + * + * Thread-safety + * ============= +@@ -128,15 +130,15 @@ size_t cmap_replace(struct cmap *, struct cmap_node *old_node, + * CMAP_FOR_EACH_WITH_HASH_PROTECTED may only be used if CMAP is guaranteed not + * to change during iteration. It may be very slightly faster. + */ +-#define CMAP_NODE_FOR_EACH(NODE, MEMBER, CMAP_NODE) \ +- for (INIT_CONTAINER(NODE, CMAP_NODE, MEMBER); \ +- (NODE) != OBJECT_CONTAINING(NULL, NODE, MEMBER); \ +- ASSIGN_CONTAINER(NODE, cmap_node_next(&(NODE)->MEMBER), MEMBER)) +-#define CMAP_NODE_FOR_EACH_PROTECTED(NODE, MEMBER, CMAP_NODE) \ +- for (INIT_CONTAINER(NODE, CMAP_NODE, MEMBER); \ +- (NODE) != OBJECT_CONTAINING(NULL, NODE, MEMBER); \ +- ASSIGN_CONTAINER(NODE, cmap_node_next_protected(&(NODE)->MEMBER), \ +- MEMBER)) ++#define CMAP_NODE_FOR_EACH(NODE, MEMBER, CMAP_NODE) \ ++ for (INIT_MULTIVAR(NODE, MEMBER, CMAP_NODE, struct cmap_node); \ ++ CONDITION_MULTIVAR(NODE, MEMBER, ITER_VAR(NODE) != NULL); \ ++ UPDATE_MULTIVAR(NODE, cmap_node_next(ITER_VAR(NODE)))) ++#define CMAP_NODE_FOR_EACH_PROTECTED(NODE, MEMBER, CMAP_NODE) \ ++ for (INIT_MULTIVAR(NODE, MEMBER, CMAP_NODE, struct cmap_node); \ ++ CONDITION_MULTIVAR(NODE, MEMBER, ITER_VAR(NODE) != NULL); \ ++ UPDATE_MULTIVAR(NODE, cmap_node_next_protected(ITER_VAR(NODE)))) ++ + #define CMAP_FOR_EACH_WITH_HASH(NODE, MEMBER, HASH, CMAP) \ + CMAP_NODE_FOR_EACH(NODE, MEMBER, cmap_find(CMAP, HASH)) + #define CMAP_FOR_EACH_WITH_HASH_PROTECTED(NODE, MEMBER, HASH, CMAP) \ +@@ -223,7 +225,7 @@ unsigned long cmap_find_batch(const struct cmap *cmap, unsigned long map, + ? (INIT_CONTAINER(NODE, (CURSOR)->node, MEMBER), \ + cmap_cursor_advance(CURSOR), \ + true) \ +- : false) ++ : (NODE = NULL, false)) + + #define CMAP_CURSOR_FOR_EACH(NODE, MEMBER, CURSOR, CMAP) \ + for (*(CURSOR) = cmap_cursor_start(CMAP); \ diff --git a/lib/conntrack-icmp.c b/lib/conntrack-icmp.c index 63246f0124..63ddd8038b 100644 --- a/lib/conntrack-icmp.c @@ -82980,6 +83409,75 @@ index 45bb96b543..f3f0358b36 100644 } } else if (OVS_LIKELY(nw_proto == IPPROTO_UDP)) { if (OVS_LIKELY(size >= UDP_HEADER_LEN)) { +diff --git a/lib/hindex.h b/lib/hindex.h +index 876c5a9e39..f7a30d511a 100644 +--- a/lib/hindex.h ++++ b/lib/hindex.h +@@ -128,18 +128,22 @@ void hindex_remove(struct hindex *, struct hindex_node *); + * Evaluates HASH only once. + */ + #define HINDEX_FOR_EACH_WITH_HASH(NODE, MEMBER, HASH, HINDEX) \ +- for (INIT_CONTAINER(NODE, hindex_node_with_hash(HINDEX, HASH), MEMBER); \ +- NODE != OBJECT_CONTAINING(NULL, NODE, MEMBER); \ +- ASSIGN_CONTAINER(NODE, (NODE)->MEMBER.s, MEMBER)) ++ for (INIT_MULTIVAR(NODE, MEMBER, hindex_node_with_hash(HINDEX, HASH), \ ++ struct hindex_node); \ ++ CONDITION_MULTIVAR(NODE, MEMBER, ITER_VAR(NODE) != NULL); \ ++ UPDATE_MULTIVAR(NODE, ITER_VAR(NODE)->s)) + + /* Safe when NODE may be freed (not needed when NODE may be removed from the + * hash map but its members remain accessible and intact). */ +-#define HINDEX_FOR_EACH_WITH_HASH_SAFE(NODE, NEXT, MEMBER, HASH, HINDEX) \ +- for (INIT_CONTAINER(NODE, hindex_node_with_hash(HINDEX, HASH), MEMBER); \ +- (NODE != OBJECT_CONTAINING(NULL, NODE, MEMBER) \ +- ? INIT_CONTAINER(NEXT, (NODE)->MEMBER.s, MEMBER), 1 \ +- : 0); \ +- (NODE) = (NEXT)) ++#define HINDEX_FOR_EACH_WITH_HASH_SAFE(NODE, NEXT, MEMBER, HASH, HINDEX) \ ++ for (INIT_MULTIVAR_SAFE_LONG(NODE, NEXT, MEMBER, \ ++ hindex_node_with_hash(HINDEX, HASH), \ ++ struct hindex_node); \ ++ CONDITION_MULTIVAR_SAFE_LONG(NODE, NEXT, MEMBER, \ ++ ITER_VAR(NODE) != NULL, \ ++ ITER_VAR(NEXT) = ITER_VAR(NODE)->s, \ ++ ITER_VAR(NEXT) != NULL); \ ++ UPDATE_MULTIVAR_SAFE_LONG(NODE, NEXT)) + + /* Returns the head node in 'hindex' with the given 'hash', or a null pointer + * if no nodes have that hash value. */ +@@ -157,19 +161,22 @@ hindex_node_with_hash(const struct hindex *hindex, size_t hash) + /* Iteration. */ + + /* Iterates through every node in HINDEX. */ +-#define HINDEX_FOR_EACH(NODE, MEMBER, HINDEX) \ +- for (INIT_CONTAINER(NODE, hindex_first(HINDEX), MEMBER); \ +- NODE != OBJECT_CONTAINING(NULL, NODE, MEMBER); \ +- ASSIGN_CONTAINER(NODE, hindex_next(HINDEX, &(NODE)->MEMBER), MEMBER)) ++#define HINDEX_FOR_EACH(NODE, MEMBER, HINDEX) \ ++ for (INIT_MULTIVAR(NODE, MEMBER, hindex_first(HINDEX), \ ++ struct hindex_node); \ ++ CONDITION_MULTIVAR(NODE, MEMBER, ITER_VAR(NODE) != NULL); \ ++ UPDATE_MULTIVAR(NODE, hindex_next(HINDEX, ITER_VAR(NODE)))) + + /* Safe when NODE may be freed (not needed when NODE may be removed from the + * hash index but its members remain accessible and intact). */ +-#define HINDEX_FOR_EACH_SAFE(NODE, NEXT, MEMBER, HINDEX) \ +- for (INIT_CONTAINER(NODE, hindex_first(HINDEX), MEMBER); \ +- (NODE != OBJECT_CONTAINING(NULL, NODE, MEMBER) \ +- ? INIT_CONTAINER(NEXT, hindex_next(HINDEX, &(NODE)->MEMBER), MEMBER), 1 \ +- : 0); \ +- (NODE) = (NEXT)) ++#define HINDEX_FOR_EACH_SAFE(NODE, NEXT, MEMBER, HINDEX) \ ++ for (INIT_MULTIVAR_SAFE_LONG(NODE, NEXT, MEMBER, hindex_first(HINDEX), \ ++ struct hindex_node); \ ++ CONDITION_MULTIVAR_SAFE_LONG(NODE, NEXT, MEMBER, \ ++ ITER_VAR(NODE) != NULL, \ ++ ITER_VAR(NEXT) = hindex_next(HINDEX, ITER_VAR(NODE)), \ ++ ITER_VAR(NEXT) != NULL); \ ++ UPDATE_MULTIVAR_SAFE_LONG(NODE, NEXT)) + + struct hindex_node *hindex_first(const struct hindex *); + struct hindex_node *hindex_next(const struct hindex *, diff --git a/lib/ipf.c b/lib/ipf.c index 446e89d13c..009f5d1e9b 100644 --- a/lib/ipf.c @@ -85419,6 +85917,22 @@ index ab8e08b84d..8f8c9041ac 100644
Open vSwitch 2.6 introduced nat
. Linux 4.6 was the
earliest upstream kernel that implemented ct
support for
+diff --git a/lib/ovs-numa.h b/lib/ovs-numa.h
+index 8f2ea34308..8d141b41e3 100644
+--- a/lib/ovs-numa.h
++++ b/lib/ovs-numa.h
+@@ -66,9 +66,9 @@ void ovs_numa_dump_destroy(struct ovs_numa_dump *);
+ int ovs_numa_thread_setaffinity_core(unsigned core_id);
+
+ #define FOR_EACH_CORE_ON_DUMP(ITER, DUMP) \
+- HMAP_FOR_EACH((ITER), hmap_node, &(DUMP)->cores)
++ HMAP_FOR_EACH (ITER, hmap_node, &(DUMP)->cores)
+
+ #define FOR_EACH_NUMA_ON_DUMP(ITER, DUMP) \
+- HMAP_FOR_EACH((ITER), hmap_node, &(DUMP)->numas)
++ HMAP_FOR_EACH (ITER, hmap_node, &(DUMP)->numas)
+
+ #endif /* ovs-numa.h */
diff --git a/lib/ovs-rcu.c b/lib/ovs-rcu.c
index ebc8120f0f..cde1e925ba 100644
--- a/lib/ovs-rcu.c
@@ -86373,6 +86887,98 @@ index b990ed9d59..0d3290dc37 100644
cursor.vector = impl->vector;
cursor.entry_idx = -1;
+diff --git a/lib/rculist.h b/lib/rculist.h
+index 1072b87af2..c0d77acf94 100644
+--- a/lib/rculist.h
++++ b/lib/rculist.h
+@@ -365,35 +365,57 @@ rculist_is_singleton_protected(const struct rculist *list)
+ return list_next == list->prev && list_next != list;
+ }
+
+-#define RCULIST_FOR_EACH(ITER, MEMBER, RCULIST) \
+- for (INIT_CONTAINER(ITER, rculist_next(RCULIST), MEMBER); \
+- &(ITER)->MEMBER != (RCULIST); \
+- ASSIGN_CONTAINER(ITER, rculist_next(&(ITER)->MEMBER), MEMBER))
+-#define RCULIST_FOR_EACH_CONTINUE(ITER, MEMBER, RCULIST) \
+- for (ASSIGN_CONTAINER(ITER, rculist_next(&(ITER)->MEMBER), MEMBER); \
+- &(ITER)->MEMBER != (RCULIST); \
+- ASSIGN_CONTAINER(ITER, rculist_next(&(ITER)->MEMBER), MEMBER))
+-
+-#define RCULIST_FOR_EACH_REVERSE_PROTECTED(ITER, MEMBER, RCULIST) \
+- for (INIT_CONTAINER(ITER, (RCULIST)->prev, MEMBER); \
+- &(ITER)->MEMBER != (RCULIST); \
+- ASSIGN_CONTAINER(ITER, (ITER)->MEMBER.prev, MEMBER))
+-#define RCULIST_FOR_EACH_REVERSE_PROTECTED_CONTINUE(ITER, MEMBER, RCULIST) \
+- for (ASSIGN_CONTAINER(ITER, (ITER)->MEMBER.prev, MEMBER); \
+- &(ITER)->MEMBER != (RCULIST); \
+- ASSIGN_CONTAINER(ITER, (ITER)->MEMBER.prev, MEMBER))
+-
+-#define RCULIST_FOR_EACH_PROTECTED(ITER, MEMBER, RCULIST) \
+- for (INIT_CONTAINER(ITER, rculist_next_protected(RCULIST), MEMBER); \
+- &(ITER)->MEMBER != (RCULIST); \
+- ASSIGN_CONTAINER(ITER, rculist_next_protected(&(ITER)->MEMBER), \
+- MEMBER))
+-
+-#define RCULIST_FOR_EACH_SAFE_PROTECTED(ITER, NEXT, MEMBER, RCULIST) \
+- for (INIT_CONTAINER(ITER, rculist_next_protected(RCULIST), MEMBER); \
+- (&(ITER)->MEMBER != (RCULIST) \
+- ? INIT_CONTAINER(NEXT, rculist_next_protected(&(ITER)->MEMBER), \
+- MEMBER), 1 : 0); \
+- (ITER) = (NEXT))
++#define RCULIST_FOR_EACH(ITER, MEMBER, RCULIST) \
++ for (INIT_MULTIVAR(ITER, MEMBER, rculist_next(RCULIST), \
++ const struct rculist); \
++ CONDITION_MULTIVAR(ITER, MEMBER, ITER_VAR(ITER) != (RCULIST)); \
++ UPDATE_MULTIVAR(ITER, rculist_next(ITER_VAR(ITER))))
++
++#define RCULIST_FOR_EACH_CONTINUE(ITER, MEMBER, RCULIST) \
++ for (INIT_MULTIVAR(ITER, MEMBER, rculist_next(&(ITER)->MEMBER), \
++ const struct rculist); \
++ CONDITION_MULTIVAR(ITER, MEMBER, ITER_VAR(ITER) != (RCULIST)); \
++ UPDATE_MULTIVAR(ITER, rculist_next(ITER_VAR(ITER))))
++
++#define RCULIST_FOR_EACH_REVERSE_PROTECTED(ITER, MEMBER, RCULIST) \
++ for (INIT_MULTIVAR(ITER, MEMBER, (RCULIST)->prev, struct rculist); \
++ CONDITION_MULTIVAR(ITER, MEMBER, ITER_VAR(ITER) != (RCULIST)); \
++ UPDATE_MULTIVAR(ITER, ITER_VAR(VAR).prev))
++
++#define RCULIST_FOR_EACH_REVERSE_PROTECTED_CONTINUE(ITER, MEMBER, RCULIST) \
++ for (INIT_MULTIVAR(ITER, MEMBER, (ITER)->MEMBER.prev, struct rculist); \
++ CONDITION_MULTIVAR(ITER, MEMBER, ITER_VAR(ITER) != (RCULIST)); \
++ UPDATE_MULTIVAR(ITER, ITER_VAR(VAR).prev))
++
++#define RCULIST_FOR_EACH_PROTECTED(ITER, MEMBER, RCULIST) \
++ for (INIT_MULTIVAR(ITER, MEMBER, rculist_next_protected(RCULIST), \
++ struct rculist); \
++ CONDITION_MULTIVAR(ITER, MEMBER, ITER_VAR(ITER) != (RCULIST)); \
++ UPDATE_MULTIVAR(ITER, rculist_next_protected(ITER_VAR(ITER))) \
++
++#define RCULIST_FOR_EACH_SAFE_SHORT_PROTECTED(ITER, MEMBER, RCULIST) \
++ for (INIT_MULTIVAR_SAFE_SHORT(ITER, MEMBER, \
++ rculist_next_protected(RCULIST), \
++ struct rculist); \
++ CONDITION_MULTIVAR_SAFE_SHORT(ITER, MEMBER, \
++ ITER_VAR(ITER) != (RCULIST), \
++ ITER_NEXT_VAR(ITER) = rculist_next_protected(ITER_VAR(VAR))); \
++ UPDATE_MULTIVAR_SHORT(ITER))
++
++#define RCULIST_FOR_EACH_SAFE_LONG_PROTECTED(ITER, NEXT, MEMBER, RCULIST) \
++ for (INIT_MULTIVAR_SAFE_LONG(ITER, NEXT, MEMBER, \
++ rculist_next_protected(RCULIST) \
++ struct rculist); \
++ CONDITION_MULTIVAR_SAFE_LONG(VAR, NEXT, MEMBER \
++ ITER_VAR(ITER) != (RCULIST), \
++ ITER_VAR(NEXT) = rculist_next_protected(ITER_VAR(VAR)), \
++ ITER_VAR(NEXT) != (RCULIST)); \
++ UPDATE_MULTIVAR_LONG(ITER))
++
++#define RCULIST_FOR_EACH_SAFE_PROTECTED(...) \
++ OVERLOAD_SAFE_MACRO(RCULIST_FOR_EACH_SAFE_LONG_PROTECTED, \
++ RCULIST_FOR_EACH_SAFE_SHORT_PROTECTED, \
++ 4, __VA_ARGS__)
++
+
+ #endif /* rculist.h */
diff --git a/lib/reconnect.c b/lib/reconnect.c
index c89abab889..a929ddfd2d 100644
--- a/lib/reconnect.c
@@ -87587,6 +88193,19 @@ index add3aabcc2..391d6d8f7c 100644
dnl Checks for MSVC x64 compiler.
AC_DEFUN([OVS_CHECK_WIN64],
[AC_CACHE_CHECK(
+diff --git a/ofproto/bond.c b/ofproto/bond.c
+index 405202fb64..54e06211a6 100644
+--- a/ofproto/bond.c
++++ b/ofproto/bond.c
+@@ -1165,7 +1165,7 @@ insert_bal(struct ovs_list *bals, struct bond_slave *slave)
+ break;
+ }
+ }
+- ovs_list_insert(&pos->bal_node, &slave->bal_node);
++ ovs_list_insert(pos ? &pos->bal_node : bals, &slave->bal_node);
+ }
+
+ /* Removes 'slave' from its current list and then inserts it into 'bals' so
diff --git a/ofproto/connmgr.c b/ofproto/connmgr.c
index 51d656cba9..fd926cbb82 100644
--- a/ofproto/connmgr.c
@@ -94068,6 +94687,34 @@ index 6d53d016de..2d98fad485 100644
}
assert(n_rules <= cls->n_rules);
}
+diff --git a/tests/test-cmap.c b/tests/test-cmap.c
+index 0705475606..f8cc4dd80a 100644
+--- a/tests/test-cmap.c
++++ b/tests/test-cmap.c
+@@ -74,6 +74,7 @@ check_cmap(struct cmap *cmap, const int values[], size_t n,
+ cmap_values[i++] = e->value;
+ }
+ assert(i == n);
++ assert(e == NULL);
+
+ /* Here we test iteration with cmap_next_position() */
+ i = 0;
+@@ -107,6 +108,7 @@ check_cmap(struct cmap *cmap, const int values[], size_t n,
+ count += e->value == values[i];
+ }
+ assert(count == 1);
++ assert(e == NULL);
+ }
+
+ /* Check that all the values are there in batched lookup. */
+@@ -130,6 +132,7 @@ check_cmap(struct cmap *cmap, const int values[], size_t n,
+ CMAP_NODE_FOR_EACH (e, node, nodes[k]) {
+ count += e->value == values[i + k];
+ }
++ assert(e == NULL);
+ }
+ assert(count == j); /* j elements in a batch. */
+ }
diff --git a/tests/test-conntrack.c b/tests/test-conntrack.c
index f77ee75e38..da1ac63b6c 100644
--- a/tests/test-conntrack.c
@@ -94090,6 +94737,79 @@ index f77ee75e38..da1ac63b6c 100644
}
ovs_barrier_block(&barrier);
destroy_packets(pkt_batch);
+diff --git a/tests/test-hindex.c b/tests/test-hindex.c
+index af06be5fcc..95e49284ee 100644
+--- a/tests/test-hindex.c
++++ b/tests/test-hindex.c
+@@ -265,6 +265,11 @@ test_hindex_for_each_safe(hash_func *hash)
+ i = 0;
+ n_remaining = n;
+ HINDEX_FOR_EACH_SAFE (e, next, node, &hindex) {
++ if (hindex_next(&hindex, &e->node) == NULL) {
++ assert(next == NULL);
++ } else {
++ assert(&next->node == hindex_next(&hindex, &e->node));
++ }
+ assert(i < n);
+ if (pattern & (1ul << e->value)) {
+ size_t j;
+@@ -281,6 +286,7 @@ test_hindex_for_each_safe(hash_func *hash)
+ i++;
+ }
+ assert(i == n);
++ assert(next == NULL);
+
+ for (i = 0; i < n; i++) {
+ if (pattern & (1ul << i)) {
+diff --git a/tests/test-hmap.c b/tests/test-hmap.c
+index 9259b0b3fc..47b4755386 100644
+--- a/tests/test-hmap.c
++++ b/tests/test-hmap.c
+@@ -62,6 +62,7 @@ check_hmap(struct hmap *hmap, const int values[], size_t n,
+ hmap_values[i++] = e->value;
+ }
+ assert(i == n);
++ assert(e == NULL);
+
+ memcpy(sort_values, values, sizeof *sort_values * n);
+ qsort(sort_values, n, sizeof *sort_values, compare_ints);
+@@ -82,6 +83,7 @@ check_hmap(struct hmap *hmap, const int values[], size_t n,
+ count += e->value == values[i];
+ }
+ assert(count == 1);
++ assert(e == NULL);
+ }
+
+ /* Check counters. */
+@@ -243,6 +245,11 @@ test_hmap_for_each_safe(hash_func *hash)
+ i = 0;
+ n_remaining = n;
+ HMAP_FOR_EACH_SAFE (e, next, node, &hmap) {
++ if (hmap_next(&hmap, &e->node) == NULL) {
++ assert(next == NULL);
++ } else {
++ assert(&next->node == hmap_next(&hmap, &e->node));
++ }
+ assert(i < n);
+ if (pattern & (1ul << e->value)) {
+ size_t j;
+@@ -259,6 +266,8 @@ test_hmap_for_each_safe(hash_func *hash)
+ i++;
+ }
+ assert(i == n);
++ assert(next == NULL);
++ assert(e == NULL);
+
+ for (i = 0; i < n; i++) {
+ if (pattern & (1ul << i)) {
+@@ -308,6 +317,7 @@ test_hmap_for_each_pop(hash_func *hash)
+ i++;
+ }
+ assert(i == n);
++ assert(e == NULL);
+
+ hmap_destroy(&hmap);
+ }
diff --git a/tests/test-jsonrpc.py b/tests/test-jsonrpc.py
index 3eabcd78d5..1df5afa221 100644
--- a/tests/test-jsonrpc.py
@@ -94115,6 +94835,52 @@ index d7854a1df3..32a77392c6 100755
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
+diff --git a/tests/test-list.c b/tests/test-list.c
+index 6f1fb059bc..648e02a5e2 100644
+--- a/tests/test-list.c
++++ b/tests/test-list.c
+@@ -61,7 +61,7 @@ check_list(struct ovs_list *list, const int values[], size_t n)
+ assert(e->value == values[i]);
+ i++;
+ }
+- assert(&e->node == list);
++ assert(e == NULL);
+ assert(i == n);
+
+ i = 0;
+@@ -70,7 +70,7 @@ check_list(struct ovs_list *list, const int values[], size_t n)
+ assert(e->value == values[n - i - 1]);
+ i++;
+ }
+- assert(&e->node == list);
++ assert(e == NULL);
+ assert(i == n);
+
+ assert(ovs_list_is_empty(list) == !n);
+@@ -135,6 +135,13 @@ test_list_for_each_safe(void)
+ values_idx = 0;
+ n_remaining = n;
+ LIST_FOR_EACH_SAFE (e, next, node, &list) {
++ /* "next" is valid as long as it's not pointing to &list. */
++ if (&e->node == list.prev) {
++ assert(next == NULL);
++ } else {
++ assert(&next->node == e->node.next);
++ }
++
+ assert(i < n);
+ if (pattern & (1ul << i)) {
+ ovs_list_remove(&e->node);
+@@ -148,7 +155,8 @@ test_list_for_each_safe(void)
+ i++;
+ }
+ assert(i == n);
+- assert(&e->node == &list);
++ assert(e == NULL);
++ assert(next == NULL);
+
+ for (i = 0; i < n; i++) {
+ if (pattern & (1ul << i)) {
diff --git a/tests/test-ovsdb.c b/tests/test-ovsdb.c
index b1a4be36bb..c4845956ca 100644
--- a/tests/test-ovsdb.c
diff --git a/SPECS/openvswitch2.13.spec b/SPECS/openvswitch2.13.spec
index 121e9b8..1e52cae 100644
--- a/SPECS/openvswitch2.13.spec
+++ b/SPECS/openvswitch2.13.spec
@@ -59,7 +59,7 @@ Summary: Open vSwitch
Group: System Environment/Daemons daemon/database/utilities
URL: http://www.openvswitch.org/
Version: 2.13.0
-Release: 170%{?commit0:.%{date}git%{shortcommit0}}%{?commit1:dpdk%{shortcommit1}}%{?dist}
+Release: 171%{?commit0:.%{date}git%{shortcommit0}}%{?commit1:dpdk%{shortcommit1}}%{?dist}
# Nearly all of openvswitch is ASL 2.0. The bugtool is LGPLv2+, and the
# lib/sflow*.[ch] files are SISSL
@@ -715,6 +715,22 @@ exit 0
%endif
%changelog
+* Wed Mar 30 2022 Open vSwitch CI