diff options
author | Jianfeng Tan <henry.tjf@antfin.com> | 2019-11-18 06:59:50 +0000 |
---|---|---|
committer | Jianfeng Tan <henry.tjf@antfin.com> | 2020-03-05 01:31:33 +0800 |
commit | 78c896b3b3127515478090c19447e27dc406427e (patch) | |
tree | d6d67d4683e9ca0409f9984a834547a572fb5310 /test/packetdrill/script.c | |
parent | e4380f4866091fd92a7a57667dd938a99144f9cd (diff) |
TLDKv2dev-next-socket
Signed-off-by: Jianfeng Tan <henry.tjf@antfin.com>
Signed-off-by: Jielong Zhou <jielong.zjl@antfin.com>
Signed-off-by: Jian Zhang <wuzai.zj@antfin.com>
Signed-off-by: Chen Zhao <winters.zc@antfin.com>
Change-Id: I55c39de4c6cd30f991f35631eb507f770230f08e
Diffstat (limited to 'test/packetdrill/script.c')
-rw-r--r-- | test/packetdrill/script.c | 745 |
1 files changed, 745 insertions, 0 deletions
diff --git a/test/packetdrill/script.c b/test/packetdrill/script.c new file mode 100644 index 0000000..253e9f7 --- /dev/null +++ b/test/packetdrill/script.c @@ -0,0 +1,745 @@ +/* + * Copyright 2013 Google Inc. + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ +/* + * Author: ncardwell@google.com (Neal Cardwell) + * + * Implementation of functions to help interpret a test script. + */ + +#include "script.h" + +#include <poll.h> +#include <stdlib.h> + +#include "assert.h" +#include "symbols.h" +#include "gre.h" + +/* Fill in a value representing the given expression in + * fully-evaluated form (e.g. symbols resolved to ints). On success, + * returns STATUS_OK. On error return STATUS_ERR and fill in *error. + */ +static int evaluate(struct expression *in, + struct expression **out_ptr, char **error); + +/* Initialize script object */ +void init_script(struct script *script) +{ + memset(script, 0, sizeof(*script)); + script->option_list = NULL; + script->init_command = NULL; + script->event_list = NULL; +} + +/* This table maps expression types to human-readable strings */ +struct expression_type_entry { + enum expression_t type; + const char *name; +}; +struct expression_type_entry expression_type_table[] = { + { EXPR_ELLIPSIS, "ellipsis" }, + { EXPR_INTEGER, "integer" }, + { EXPR_WORD, "word" }, + { EXPR_STRING, "string" }, + { EXPR_GRE, "gre" }, + { EXPR_IN6_ADDR, "in6_addr" }, + { EXPR_SOCKET_ADDRESS_IPV4, "sockaddr_in" }, + { EXPR_SOCKET_ADDRESS_IPV6, "sockaddr_in6" }, + { EXPR_LINGER, "linger" }, + { EXPR_BINARY, "binary_expression" }, + { EXPR_LIST, "list" }, + { EXPR_IOVEC, "iovec" }, + { EXPR_MSGHDR, "msghdr" }, + { EXPR_CMSG, "cmsg" }, + { EXPR_POLLFD, "pollfd" }, + { EXPR_MPLS_STACK, "mpls_stack" }, + { EXPR_SCM_TIMESTAMPING, "scm_timestamping"}, + { EXPR_SOCK_EXTENDED_ERR, "sock_extended_err"}, + { EXPR_EPOLLEV, "epollev" }, + {-1, NULL} +}; + +const char *expression_type_to_string(enum expression_t type) +{ + int i = 0; + assert(ARRAY_SIZE(expression_type_table) == NUM_EXPR_TYPES + 1); + for (i = 0; expression_type_table[i].name != NULL; ++i) + if (expression_type_table[i].type == type) + return expression_type_table[i].name; + return "UNKNOWN_TYPE"; +} + +/* Cross-platform symbols. */ +struct int_symbol cross_platform_symbols[] = { + { AF_INET, "AF_INET" }, + { AF_INET6, "AF_INET6" }, + + { PF_INET, "PF_INET" }, + { PF_INET6, "PF_INET6" }, + + { SOCK_STREAM, "SOCK_STREAM" }, + { SOCK_DGRAM, "SOCK_DGRAM" }, + { SOCK_NONBLOCK, "SOCK_NONBLOCK" }, + + { IPPROTO_IP, "IPPROTO_IP" }, + { IPPROTO_IPV6, "IPPROTO_IPV6" }, + { IPPROTO_ICMP, "IPPROTO_ICMP" }, + { IPPROTO_ICMPV6, "IPPROTO_ICMPV6" }, + { IPPROTO_TCP, "IPPROTO_TCP" }, + { IPPROTO_UDP, "IPPROTO_UDP" }, + + { SHUT_RD, "SHUT_RD" }, + { SHUT_WR, "SHUT_WR" }, + { SHUT_RDWR, "SHUT_RDWR" }, + + { SOL_SOCKET, "SOL_SOCKET" }, + + { 0, "NULL" }, + + /* Sentinel marking the end of the table. */ + { 0, NULL }, +}; + +/* Do a symbol->int lookup, and return true iff we found the symbol. */ +static bool lookup_int_symbol(const char *input_symbol, s64 *output_integer, + struct int_symbol *symbols) +{ + int i; + for (i = 0; symbols[i].name != NULL ; ++i) { + if (strcmp(input_symbol, symbols[i].name) == 0) { + *output_integer = symbols[i].value; + return true; + } + } + return false; +} + +int symbol_to_int(const char *input_symbol, s64 *output_integer, + char **error) +{ + if (lookup_int_symbol(input_symbol, output_integer, + cross_platform_symbols)) + return STATUS_OK; + + if (lookup_int_symbol(input_symbol, output_integer, + platform_symbols())) + return STATUS_OK; + + asprintf(error, "unknown symbol: '%s'", input_symbol); + return STATUS_ERR; +} + +/* Names for the events and revents bit mask flags for poll() system call */ +struct flag_name poll_flags[] = { + + { POLLIN, "POLLIN" }, + { POLLPRI, "POLLPRI" }, + { POLLOUT, "POLLOUT" }, + +#ifdef POLLRDNORM + { POLLRDNORM, "POLLRDNORM" }, +#endif +#ifdef POLLRDBAND + { POLLRDBAND, "POLLRDBAND" }, +#endif +#ifdef POLLWRNORM + { POLLWRNORM, "POLLWRNORM" }, +#endif +#ifdef POLLWRBAND + { POLLWRBAND, "POLLWRBAND" }, +#endif + +#ifdef POLLMSG + { POLLMSG, "POLLMSG" }, +#endif +#ifdef POLLREMOVE + { POLLREMOVE, "POLLREMOVE" }, +#endif +#ifdef POLLRDHUP + { POLLRDHUP, "POLLRDHUP" }, +#endif + +#ifdef POLLINIGNEOF + { POLLINIGNEOF, "POLLINIGNEOF" }, +#endif + + { POLLERR, "POLLERR" }, + { POLLHUP, "POLLHUP" }, + { POLLNVAL, "POLLNVAL" }, + + { 0, "" }, +}; + +/* Return the human-readable ASCII string corresponding to a given + * flag value, or "???" if none matches. + */ +static const char *flag_name(struct flag_name *flags_array, u64 flag) +{ + while (flags_array->name && flags_array->flag != flag) + flags_array++; + if (flags_array->flag == flag) + return flags_array->name; + else + return "???"; +} + +char *flags_to_string(struct flag_name *flags_array, u64 flags) +{ + u64 bit_mask = 1; + int i = 0; + char *out = strdup(""); + + for (i = 0; i < 64; ++i) { + if (flags & bit_mask) { + char *tmp = NULL; + asprintf(&tmp, "%s%s%s", + out, + out[0] ? "|" : "", + flag_name(flags_array, bit_mask)); + free(out); + out = tmp; + } + bit_mask <<= 1; + } + return out; +} + +/* Fill in 'out' with an unescaped version of the input string. On + * success, return STATUS_OK; on error, return STATUS_ERR and store + * an error message in *error. + */ +static int unescape_cstring_expression(const char *input_string, + struct expression *out, char **error) +{ + int bytes = strlen(input_string); + out->type = EXPR_STRING; + out->value.string = (char *)calloc(1, bytes + 1); + const char *c_in = input_string; + char *c_out = out->value.string; + while (*c_in != '\0') { + if (*c_in == '\\') { + ++c_in; + switch (*c_in) { + case '\\': + *c_out = '\\'; + case '"': + *c_out = '"'; + case 'f': + *c_out = '\f'; + break; + case 'n': + *c_out = '\n'; + break; + case 'r': + *c_out = '\r'; + break; + case 't': + *c_out = '\t'; + break; + case 'v': + *c_out = '\v'; + break; + case 'x': { + ++c_in; + if (strlen(c_in) >= 2) { + char s[] = { c_in[0], c_in[1], 0 }; + char *end = NULL; + + *c_out = strtol(s, &end, 16); + if (s[0] != '\0' && *end == '\0') { + ++c_in; + break; + } + } + asprintf(error, + "invalid hex escape (\\xhh): '\\x%s'", + c_in); + return STATUS_ERR; + } + default: + asprintf(error, "unsupported escape code: '%c'", + *c_in); + return STATUS_ERR; + } + } else { + *c_out = *c_in; + } + ++c_in; + ++c_out; + } + return STATUS_OK; +} + +void free_expression(struct expression *expression) +{ + if (expression == NULL) + return; + if (expression->type >= NUM_EXPR_TYPES) + assert(!"bad expression type"); + switch (expression->type) { + case EXPR_ELLIPSIS: + case EXPR_INTEGER: + case EXPR_GRE: + case EXPR_IN6_ADDR: + case EXPR_LINGER: + break; + case EXPR_WORD: + assert(expression->value.string); + free(expression->value.string); + break; + case EXPR_STRING: + assert(expression->value.string); + free(expression->value.string); + break; + case EXPR_SOCKET_ADDRESS_IPV4: + assert(expression->value.socket_address_ipv4); + free(expression->value.socket_address_ipv4); + break; + case EXPR_SOCKET_ADDRESS_IPV6: + assert(expression->value.socket_address_ipv6); + free(expression->value.socket_address_ipv6); + break; + case EXPR_BINARY: + assert(expression->value.binary); + free(expression->value.binary->op); + free_expression(expression->value.binary->lhs); + free_expression(expression->value.binary->rhs); + free(expression->value.binary); + break; + case EXPR_LIST: + free_expression_list(expression->value.list); + break; + case EXPR_IOVEC: + assert(expression->value.iovec); + free_expression(expression->value.iovec->iov_base); + free_expression(expression->value.iovec->iov_len); + break; + case EXPR_MSGHDR: + assert(expression->value.msghdr); + free_expression(expression->value.msghdr->msg_name); + free_expression(expression->value.msghdr->msg_namelen); + free_expression(expression->value.msghdr->msg_iov); + free_expression(expression->value.msghdr->msg_iovlen); + free_expression(expression->value.msghdr->msg_control); + free_expression(expression->value.msghdr->msg_flags); + break; + case EXPR_CMSG: + assert(expression->value.cmsg); + free_expression(expression->value.cmsg->cmsg_level); + free_expression(expression->value.cmsg->cmsg_type); + free_expression(expression->value.cmsg->cmsg_data); + break; + case EXPR_POLLFD: + assert(expression->value.pollfd); + free_expression(expression->value.pollfd->fd); + free_expression(expression->value.pollfd->events); + free_expression(expression->value.pollfd->revents); + break; + case EXPR_SCM_TIMESTAMPING: + assert(expression->value.scm_timestamping); + free(expression->value.scm_timestamping); + break; + case EXPR_SOCK_EXTENDED_ERR: + assert(expression->value.sock_extended_err); + free_expression(expression->value.sock_extended_err->ee_errno); + free_expression(expression->value.sock_extended_err->ee_origin); + free_expression(expression->value.sock_extended_err->ee_type); + free_expression(expression->value.sock_extended_err->ee_code); + free_expression(expression->value.sock_extended_err->ee_info); + free_expression(expression->value.sock_extended_err->ee_data); + free(expression->value.sock_extended_err); + break; + case EXPR_MPLS_STACK: + assert(expression->value.mpls_stack); + free(expression->value.mpls_stack); + break; + case NUM_EXPR_TYPES: + break; + case EXPR_EPOLLEV: + assert(expression->value.epollev); + free_expression(expression->value.epollev->events); + if (expression->value.epollev->ptr) + free_expression(expression->value.epollev->ptr); + if (expression->value.epollev->fd) + free_expression(expression->value.epollev->fd); + if (expression->value.epollev->u32) + free_expression(expression->value.epollev->u32); + if (expression->value.epollev->u64) + free_expression(expression->value.epollev->u64); + break; + + /* missing default case so compiler catches missing cases */ + } + memset(expression, 0, sizeof(*expression)); /* paranoia */ + free(expression); +} + +void free_expression_list(struct expression_list *list) +{ + while (list != NULL) { + free_expression(list->expression); + struct expression_list *dead = list; + list = list->next; + free(dead); + } +} + +static int evaluate_binary_expression(struct expression *in, + struct expression *out, char **error) +{ + int result = STATUS_ERR; + assert(in->type == EXPR_BINARY); + assert(in->value.binary); + out->type = EXPR_INTEGER; + + struct expression *lhs = NULL; + struct expression *rhs = NULL; + if (evaluate(in->value.binary->lhs, &lhs, error)) + goto error_out; + if (evaluate(in->value.binary->rhs, &rhs, error)) + goto error_out; + if (strcmp("|", in->value.binary->op) == 0) { + if (lhs->type != EXPR_INTEGER) { + asprintf(error, "left hand side of | not an integer"); + } else if (rhs->type != EXPR_INTEGER) { + asprintf(error, "right hand side of | not an integer"); + } else { + out->value.num = lhs->value.num | rhs->value.num; + result = STATUS_OK; + } + } else if (strcmp("=", in->value.binary->op) == 0) { + out->value.binary = calloc(1, sizeof(struct binary_expression)); + out->value.binary->op = strdup(in->value.binary->op); + out->value.binary->lhs = lhs; + out->value.binary->rhs = rhs; + out->type = EXPR_BINARY; + return STATUS_OK; + } else { + asprintf(error, "bad binary operator '%s'", + in->value.binary->op); + } +error_out: + free_expression(rhs); + free_expression(lhs); + return result; +} + +static int evaluate_list_expression(struct expression *in, + struct expression *out, char **error) +{ + assert(in->type == EXPR_LIST); + assert(out->type == EXPR_LIST); + + out->value.list = NULL; + return evaluate_expression_list(in->value.list, + &out->value.list, error); +} + +static int evaluate_iovec_expression(struct expression *in, + struct expression *out, char **error) +{ + struct iovec_expr *in_iov; + struct iovec_expr *out_iov; + + assert(in->type == EXPR_IOVEC); + assert(in->value.iovec); + assert(out->type == EXPR_IOVEC); + + out->value.iovec = calloc(1, sizeof(struct iovec_expr)); + + in_iov = in->value.iovec; + out_iov = out->value.iovec; + + if (evaluate(in_iov->iov_base, &out_iov->iov_base, error)) + return STATUS_ERR; + if (evaluate(in_iov->iov_len, &out_iov->iov_len, error)) + return STATUS_ERR; + + return STATUS_OK; +} + +static int evaluate_msghdr_expression(struct expression *in, + struct expression *out, char **error) +{ + struct msghdr_expr *in_msg; + struct msghdr_expr *out_msg; + + assert(in->type == EXPR_MSGHDR); + assert(in->value.msghdr); + assert(out->type == EXPR_MSGHDR); + + out->value.msghdr = calloc(1, sizeof(struct msghdr_expr)); + + in_msg = in->value.msghdr; + out_msg = out->value.msghdr; + + if (evaluate(in_msg->msg_name, &out_msg->msg_name, error)) + return STATUS_ERR; + if (evaluate(in_msg->msg_namelen, &out_msg->msg_namelen, error)) + return STATUS_ERR; + if (evaluate(in_msg->msg_iov, &out_msg->msg_iov, error)) + return STATUS_ERR; + if (evaluate(in_msg->msg_iovlen, &out_msg->msg_iovlen, error)) + return STATUS_ERR; + if (evaluate(in_msg->msg_control, &out_msg->msg_control, error)) + return STATUS_ERR; + if (evaluate(in_msg->msg_flags, &out_msg->msg_flags, error)) + return STATUS_ERR; + + return STATUS_OK; +} + +static int evaluate_cmsg_expression(struct expression *in, + struct expression *out, char **error) +{ + struct cmsg_expr *in_cmsg; + struct cmsg_expr *out_cmsg; + + assert(in->type == EXPR_CMSG); + assert(in->value.cmsg); + assert(out->type == EXPR_CMSG); + + out->value.cmsg = calloc(1, sizeof(struct cmsg_expr)); + + in_cmsg = in->value.cmsg; + out_cmsg = out->value.cmsg; + + if (evaluate(in_cmsg->cmsg_level, &out_cmsg->cmsg_level, error)) + return STATUS_ERR; + if (evaluate(in_cmsg->cmsg_type, &out_cmsg->cmsg_type, error)) + return STATUS_ERR; + if (evaluate(in_cmsg->cmsg_data, &out_cmsg->cmsg_data, error)) + return STATUS_ERR; + + return STATUS_OK; +} + +static int evaluate_sock_extended_err(struct expression *in, + struct expression *out, char **error) +{ + struct sock_extended_err_expr *in_ee_err; + struct sock_extended_err_expr *out_ee_err; + + assert(in->type == EXPR_SOCK_EXTENDED_ERR); + assert(in->value.sock_extended_err); + assert(out->type == EXPR_SOCK_EXTENDED_ERR); + + out->value.sock_extended_err = + calloc(1, sizeof(struct sock_extended_err_expr)); + + in_ee_err = in->value.sock_extended_err; + out_ee_err = out->value.sock_extended_err; + + if (evaluate(in_ee_err->ee_errno, &out_ee_err->ee_errno, error)) + return STATUS_ERR; + if (evaluate(in_ee_err->ee_origin, &out_ee_err->ee_origin, error)) + return STATUS_ERR; + if (evaluate(in_ee_err->ee_type, &out_ee_err->ee_type, error)) + return STATUS_ERR; + if (evaluate(in_ee_err->ee_code, &out_ee_err->ee_code, error)) + return STATUS_ERR; + if (evaluate(in_ee_err->ee_info, &out_ee_err->ee_info, error)) + return STATUS_ERR; + if (evaluate(in_ee_err->ee_data, &out_ee_err->ee_data, error)) + return STATUS_ERR; + + return STATUS_OK; +} + +static int evaluate_pollfd_expression(struct expression *in, + struct expression *out, char **error) +{ + struct pollfd_expr *in_pollfd; + struct pollfd_expr *out_pollfd; + + assert(in->type == EXPR_POLLFD); + assert(in->value.pollfd); + assert(out->type == EXPR_POLLFD); + + out->value.pollfd = calloc(1, sizeof(struct pollfd_expr)); + + in_pollfd = in->value.pollfd; + out_pollfd = out->value.pollfd; + + if (evaluate(in_pollfd->fd, &out_pollfd->fd, error)) + return STATUS_ERR; + if (evaluate(in_pollfd->events, &out_pollfd->events, error)) + return STATUS_ERR; + if (evaluate(in_pollfd->revents, &out_pollfd->revents, error)) + return STATUS_ERR; + + return STATUS_OK; +} + +static int evaluate_epollev_expression(struct expression *in, + struct expression *out, char **error) +{ + struct epollev_expr *in_epollev; + struct epollev_expr *out_epollev; + + assert(in->type == EXPR_EPOLLEV); + assert(in->value.epollev); + assert(out->type == EXPR_EPOLLEV); + + out->value.epollev = calloc(1, sizeof(struct epollev_expr)); + in_epollev = in->value.epollev; + out_epollev = out->value.epollev; + + if (evaluate(in_epollev->events, &out_epollev->events, error)) + return STATUS_ERR; + + if (in_epollev->ptr) { + if (evaluate(in_epollev->ptr, &out_epollev->ptr, error)) + return STATUS_ERR; + } else if (in_epollev->fd) { + if (evaluate(in_epollev->fd, &out_epollev->fd, error)) + return STATUS_ERR; + } else if (in_epollev->u32) { + if (evaluate(in_epollev->u32, &out_epollev->u32, error)) + return STATUS_ERR; + } else if (in_epollev->u64) { + if (evaluate(in_epollev->u64, &out_epollev->u64, error)) + return STATUS_ERR; + } else { + return STATUS_ERR; + } + return STATUS_OK; +} + +static int evaluate(struct expression *in, + struct expression **out_ptr, char **error) +{ + int result = STATUS_OK; + struct expression *out = calloc(1, sizeof(struct expression)); + *out_ptr = out; + out->type = in->type; /* most types of expression stay the same */ + + if (in->type >= NUM_EXPR_TYPES) { + asprintf(error, "bad expression type: %d", in->type); + return STATUS_ERR; + } + switch (in->type) { + case EXPR_ELLIPSIS: + break; + case EXPR_INTEGER: /* copy as-is */ + out->value.num = in->value.num; + break; + case EXPR_GRE: /* copy as-is */ + memcpy(&out->value.gre, &in->value.gre, + gre_len(&in->value.gre)); + break; + case EXPR_IN6_ADDR: /* copy as-is */ + memcpy(&out->value.address_ipv6, &in->value.address_ipv6, + sizeof(in->value.address_ipv6)); + break; + case EXPR_LINGER: /* copy as-is */ + memcpy(&out->value.linger, &in->value.linger, + sizeof(in->value.linger)); + break; + case EXPR_WORD: + out->type = EXPR_INTEGER; + if (symbol_to_int(in->value.string, + &out->value.num, error)) + return STATUS_ERR; + break; + case EXPR_STRING: + if (unescape_cstring_expression(in->value.string, out, error)) + return STATUS_ERR; + break; + case EXPR_SOCKET_ADDRESS_IPV4: /* copy as-is */ + out->value.socket_address_ipv4 = + malloc(sizeof(struct sockaddr_in)); + memcpy(out->value.socket_address_ipv4, + in->value.socket_address_ipv4, + sizeof(*(out->value.socket_address_ipv4))); + break; + case EXPR_SOCKET_ADDRESS_IPV6: /* copy as-is */ + out->value.socket_address_ipv6 = + malloc(sizeof(struct sockaddr_in6)); + memcpy(out->value.socket_address_ipv6, + in->value.socket_address_ipv6, + sizeof(*(out->value.socket_address_ipv6))); + break; + case EXPR_BINARY: + result = evaluate_binary_expression(in, out, error); + break; + case EXPR_LIST: + result = evaluate_list_expression(in, out, error); + break; + case EXPR_IOVEC: + result = evaluate_iovec_expression(in, out, error); + break; + case EXPR_MSGHDR: + result = evaluate_msghdr_expression(in, out, error); + break; + case EXPR_CMSG: + result = evaluate_cmsg_expression(in, out, error); + break; + case EXPR_SCM_TIMESTAMPING: + memcpy(&out->value.scm_timestamping, + &in->value.scm_timestamping, + sizeof(in->value.scm_timestamping)); + break; + case EXPR_SOCK_EXTENDED_ERR: + result = evaluate_sock_extended_err(in, out, error); + break; + case EXPR_POLLFD: + result = evaluate_pollfd_expression(in, out, error); + break; + case EXPR_MPLS_STACK: /* copy as-is */ + out->value.mpls_stack = malloc(sizeof(struct mpls_stack)); + memcpy(out->value.mpls_stack, + in->value.mpls_stack, + sizeof(*out->value.mpls_stack)); + break; + case EXPR_EPOLLEV: + result = evaluate_epollev_expression(in, out, error); + break; + case NUM_EXPR_TYPES: + break; + /* missing default case so compiler catches missing cases */ + } + + return result; +} + +/* Return a copy of the given expression list with each expression + * evaluated (e.g. symbols resolved to ints). On failure, return NULL + * and fill in *error. + */ +int evaluate_expression_list(struct expression_list *in_list, + struct expression_list **out_list, + char **error) +{ + struct expression_list **node_ptr = out_list; + while (in_list != NULL) { + struct expression_list *node = + calloc(1, sizeof(struct expression_list)); + *node_ptr = node; + if (evaluate(in_list->expression, + &node->expression, error)) { + free_expression_list(*out_list); + *out_list = NULL; + return STATUS_ERR; + } + node_ptr = &(node->next); + in_list = in_list->next; + } + return STATUS_OK; +} |