text: /*
   * virfirewall.c: integration with firewalls
   *
   * Copyright (C) 2013, 2014 Red Hat, Inc.
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation; either
   * version 2.1 of the License, or (at your option) any later version.
   *
   * This library 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
   * Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library.  If not, see
   * <http://www.gnu.org/licenses/>.
   *
   * Authors:
   *    Daniel P. Berrange <berrange@redhat.com>
   */
  
  #include <config.h>
  
  #define __VIR_FIREWALL_PRIV_H_ALLOW__
  
  #include <stdarg.h>
  
  #include "viralloc.h"
  #include "virfirewallpriv.h"
  #include "virerror.h"
  #include "virutil.h"
  #include "virstring.h"
  #include "vircommand.h"
  #include "virlog.h"
  #include "virdbus.h"
  #include "virfile.h"
  #include "virthread.h"
  
  #define VIR_FROM_THIS VIR_FROM_FIREWALL
  
  VIR_LOG_INIT("util.firewall");
  
  typedef struct _virFirewallGroup virFirewallGroup;
  typedef virFirewallGroup *virFirewallGroupPtr;
  
  VIR_ENUM_DECL(virFirewallLayerCommand)
  VIR_ENUM_IMPL(virFirewallLayerCommand, VIR_FIREWALL_LAYER_LAST,
                EBTABLES_PATH,
                IPTABLES_PATH,
                IP6TABLES_PATH);
  
  VIR_ENUM_DECL(virFirewallLayerFirewallD)
  VIR_ENUM_IMPL(virFirewallLayerFirewallD, VIR_FIREWALL_LAYER_LAST,
                "eb", "ipv4", "ipv6")
  
  
  struct _virFirewallRule {
      virFirewallLayer layer;
  
      virFirewallQueryCallback queryCB;
      void *queryOpaque;
      bool ignoreErrors;
  
      size_t argsAlloc;
      size_t argsLen;
      char **args;
  };
  
  struct _virFirewallGroup {
      unsigned int actionFlags;
      unsigned int rollbackFlags;
  
      size_t naction;
      virFirewallRulePtr *action;
  
      size_t nrollback;
      virFirewallRulePtr *rollback;
  
      bool addingRollback;
  };
  
  
  struct _virFirewall {
      int err;
  
      size_t ngroups;
      virFirewallGroupPtr *groups;
      size_t currentGroup;
  };
  
  static virFirewallBackend currentBackend = VIR_FIREWALL_BACKEND_AUTOMATIC;
  static virMutex ruleLock = VIR_MUTEX_INITIALIZER;
  
  static int
  virFirewallValidateBackend(virFirewallBackend backend);
  
  static int
  virFirewallOnceInit(void)
  {
      return virFirewallValidateBackend(currentBackend);
  }
  
  VIR_ONCE_GLOBAL_INIT(virFirewall)
  
  static bool iptablesUseLock;
  static bool ip6tablesUseLock;
  static bool ebtablesUseLock;
  static bool lockOverride; /* true to avoid lock probes */
  
  void
  virFirewallSetLockOverride(bool avoid)
  {
      lockOverride = avoid;
  }
  
  static void
  virFirewallCheckUpdateLock(bool *lockflag,
                             const char *const*args)
  {
      int status; /* Ignore failed commands without logging them */
      virCommandPtr cmd = virCommandNewArgs(args);
      if (virCommandRun(cmd, &status) < 0 || status) {
          VIR_INFO("locking not supported by %s", args[0]);
      } else {
          VIR_INFO("using locking for %s", args[0]);
          *lockflag = true;
      }
      virCommandFree(cmd);
  }
  
  static void
  virFirewallCheckUpdateLocking(void)
  {
      const char *iptablesArgs[] = {
          IPTABLES_PATH, "-w", "-L", "-n", NULL,
      };
      const char *ip6tablesArgs[] = {
          IP6TABLES_PATH, "-w", "-L", "-n", NULL,
      };
      const char *ebtablesArgs[] = {
          EBTABLES_PATH, "--concurrent", "-L", NULL,
      };
      if (lockOverride)
          return;
      virFirewallCheckUpdateLock(&iptablesUseLock,
                                 iptablesArgs);
      virFirewallCheckUpdateLock(&ip6tablesUseLock,
                                 ip6tablesArgs);
      virFirewallCheckUpdateLock(&ebtablesUseLock,
                                 ebtablesArgs);
  }
  
  static int
  virFirewallValidateBackend(virFirewallBackend backend)
  {
      VIR_DEBUG("Validating backend %d", backend);
      if (backend == VIR_FIREWALL_BACKEND_AUTOMATIC ||
          backend == VIR_FIREWALL_BACKEND_FIREWALLD) {
          int rv = virDBusIsServiceRegistered(VIR_FIREWALL_FIREWALLD_SERVICE);
  
          VIR_DEBUG("Firewalld is registered ? %d", rv);
          if (rv < 0) {
              if (rv == -2) {
                  if (backend == VIR_FIREWALL_BACKEND_FIREWALLD) {
                      virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                                     _("firewalld firewall backend requested, but service is not running"));
                      return -1;
                  } else {
                      VIR_DEBUG("firewalld service not running, trying direct backend");
                      backend = VIR_FIREWALL_BACKEND_DIRECT;
                  }
              } else {
                  return -1;
              }
          } else {
              VIR_DEBUG("firewalld service running, using firewalld backend");
              backend = VIR_FIREWALL_BACKEND_FIREWALLD;
          }
      }
  
      if (backend == VIR_FIREWALL_BACKEND_DIRECT) {
          const char *commands[] = {
              IPTABLES_PATH, IP6TABLES_PATH, EBTABLES_PATH
          };
          size_t i;
  
          for (i = 0; i < ARRAY_CARDINALITY(commands); i++) {
              if (!virFileIsExecutable(commands[i])) {
                  virReportSystemError(errno,
                                       _("direct firewall backend requested, but %s is not available"),
                                       commands[i]);
                  return -1;
              }
          }
          VIR_DEBUG("found iptables/ip6tables/ebtables, using direct backend");
      }
  
      currentBackend = backend;
  
      virFirewallCheckUpdateLocking();
  
      return 0;
  }
  
  int
  virFirewallSetBackend(virFirewallBackend backend)
  {
      currentBackend = backend;
  
      if (virFirewallInitialize() < 0)
          return -1;
  
      return virFirewallValidateBackend(backend);
  }
  
  static virFirewallGroupPtr
  virFirewallGroupNew(void)
  {
      virFirewallGroupPtr group;
  
      if (VIR_ALLOC(group) < 0)
          return NULL;
  
      return group;
  }
  
  
  /**
   * virFirewallNew:
   *
   * Creates a new firewall ruleset for changing rules
   * of @layer. This should be followed by a call to
   * virFirewallStartTransaction before adding
   * any rules
   *
   * Returns the new firewall ruleset
   */
  virFirewallPtr virFirewallNew(void)
  {
      virFirewallPtr firewall;
  
      if (virFirewallInitialize() < 0)
          return NULL;
  
      if (VIR_ALLOC(firewall) < 0)
          return NULL;
  
      return firewall;
  }
  
  
  static void
  virFirewallRuleFree(virFirewallRulePtr rule)
  {
      size_t i;
  
      if (!rule)
          return;
  
      for (i = 0; i < rule->argsLen; i++)
          VIR_FREE(rule->args[i]);
      VIR_FREE(rule->args);
      VIR_FREE(rule);
  }
  
  
  static void
  virFirewallGroupFree(virFirewallGroupPtr group)
  {
      size_t i;
  
      if (!group)
          return;
  
      for (i = 0; i < group->naction; i++)
          virFirewallRuleFree(group->action[i]);
      VIR_FREE(group->action);
  
      for (i = 0; i < group->nrollback; i++)
          virFirewallRuleFree(group->rollback[i]);
      VIR_FREE(group->rollback);
  
      VIR_FREE(group);
  }
  
  
  /**
   * virFirewallFree:
   *
   * Release all memory associated with the firewall
   * ruleset
   */
  void virFirewallFree(virFirewallPtr firewall)
  {
      size_t i;
  
      if (!firewall)
          return;
  
      for (i = 0; i < firewall->ngroups; i++)
          virFirewallGroupFree(firewall->groups[i]);
      VIR_FREE(firewall->groups);
  
      VIR_FREE(firewall);
  }
  
  #define VIR_FIREWALL_RETURN_IF_ERROR(firewall)              \
      do {                                                    \
          if (!firewall || firewall->err)                     \
              return;                                         \
      } while (0)
  
  #define VIR_FIREWALL_RULE_RETURN_IF_ERROR(firewall, rule)\
      do {                                                    \
          if (!firewall || firewall->err || !rule)            \
              return;                                         \
      } while (0)
  
  #define VIR_FIREWALL_RETURN_NULL_IF_ERROR(firewall)         \
      do {                                                    \
          if (!firewall || firewall->err)                     \
              return NULL;                                    \
      } while (0)
  
  #define ADD_ARG(rule, str)                                  \
      do {                                                    \
          if (VIR_RESIZE_N(rule->args,                        \
                           rule->argsAlloc,                   \
                           rule->argsLen, 1) < 0)             \
              goto no_memory;                                 \
                                                              \
          if (VIR_STRDUP(rule->args[rule->argsLen++], str) < 0) \
              goto no_memory;                                 \
      } while (0)
  
  static virFirewallRulePtr
  virFirewallAddRuleFullV(virFirewallPtr firewall,
                          virFirewallLayer layer,
                          bool ignoreErrors,
                          virFirewallQueryCallback cb,
                          void *opaque,
                          va_list args)
  {
      virFirewallGroupPtr group;
      virFirewallRulePtr rule;
      char *str;
  
      VIR_FIREWALL_RETURN_NULL_IF_ERROR(firewall);
  
      if (firewall->ngroups == 0) {
          firewall->err = EINVAL;
          return NULL;
      }
      group = firewall->groups[firewall->currentGroup];
  
  
      if (VIR_ALLOC(rule) < 0)
          goto no_memory;
  
      rule->layer = layer;
      rule->queryCB = cb;
      rule->queryOpaque = opaque;
      rule->ignoreErrors = ignoreErrors;
  
      switch (rule->layer) {
      case VIR_FIREWALL_LAYER_ETHERNET:
          if (ebtablesUseLock)
              ADD_ARG(rule, "--concurrent");
          break;
      case VIR_FIREWALL_LAYER_IPV4:
          if (iptablesUseLock)
              ADD_ARG(rule, "-w");
          break;
      case VIR_FIREWALL_LAYER_IPV6:
          if (ip6tablesUseLock)
              ADD_ARG(rule, "-w");
          break;
      case VIR_FIREWALL_LAYER_LAST:
          break;
      }
  
      while ((str = va_arg(args, char *)) != NULL)
          ADD_ARG(rule, str);
  
      if (group->addingRollback) {
          if (VIR_APPEND_ELEMENT_COPY(group->rollback,
                                      group->nrollback,
                                      rule) < 0)
              goto no_memory;
      } else {
          if (VIR_APPEND_ELEMENT_COPY(group->action,
                                      group->naction,
                                      rule) < 0)
              goto no_memory;
      }
  
  
      return rule;
  
   no_memory:
      firewall->err = ENOMEM;
      virFirewallRuleFree(rule);
      return NULL;
  }
  
  /**
   * virFirewallAddRule:
   * @firewall: firewall ruleset to add to
   * @layer: the firewall layer to change
   * @...: NULL terminated list of strings for the rule
   *
   * Add any type of rule to the firewall ruleset.
   *
   * Returns the new rule
   */
  virFirewallRulePtr
  virFirewallAddRule(virFirewallPtr firewall,
                     virFirewallLayer layer,
                     ...)
  {
      virFirewallRulePtr rule;
      va_list args;
      va_start(args, layer);
      rule = virFirewallAddRuleFullV(firewall, layer, false, NULL, NULL, args);
      va_end(args);
      return rule;
  }
  
  
  /**
   * virFirewallAddRuleFull:
   * @firewall: firewall ruleset to add to
   * @layer: the firewall layer to change
   * @ignoreErrors: true to ignore failure of the command
   * @cb: callback to invoke with result of query
   * @opaque: data passed into @cb
   * @...: NULL terminated list of strings for the rule
   *
   * Add any type of rule to the firewall ruleset. Any output
   * generated by the addition will be fed into the query
   * callback @cb. This callback is permitted to create new
   * rules by invoking the virFirewallAddRule method, but
   * is not permitted to start new transactions.
   *
   * If @ignoreErrors is set to TRUE, then any failure of
   * the command is ignored. If it is set to FALSE, then
   * the behaviour upon failure is determined by the flags
   * set when the transaction was started.
   *
   * Returns the new rule
   */
  virFirewallRulePtr virFirewallAddRuleFull(virFirewallPtr firewall,
                                            virFirewallLayer layer,
                                            bool ignoreErrors,
                                            virFirewallQueryCallback cb,
                                            void *opaque,
                                            ...)
  {
      virFirewallRulePtr rule;
      va_list args;
      va_start(args, opaque);
      rule = virFirewallAddRuleFullV(firewall, layer, ignoreErrors, cb, opaque, args);
      va_end(args);
      return rule;
  }
  
  
  /**
   * virFirewallRemoveRule:
   * @firewall: firewall ruleset to remove from
   * @rule: the rule to remove
   *
   * Remove a rule from the current transaction
   */
  void virFirewallRemoveRule(virFirewallPtr firewall,
                             virFirewallRulePtr rule)
  {
      size_t i;
      virFirewallGroupPtr group;
  
      /* Explicitly not checking firewall->err too,
       * because if rule was partially created
       * before hitting error we must still remove
       * it to avoid leaking 'rule'
       */
      if (!firewall)
          return;
  
      if (firewall->ngroups == 0)
          return;
      group = firewall->groups[firewall->currentGroup];
  
      if (group->addingRollback) {
          for (i = 0; i < group->nrollback; i++) {
              if (group->rollback[i] == rule) {
                  VIR_DELETE_ELEMENT(group->rollback,
                                     i,
                                     group->nrollback);
                  virFirewallRuleFree(rule);
                  break;
              }
          }
      } else {
          for (i = 0; i < group->naction; i++) {
              if (group->action[i] == rule) {
                  VIR_DELETE_ELEMENT(group->action,
                                     i,
                                     group->naction);
                  virFirewallRuleFree(rule);
                  return;
              }
          }
      }
  }
  
  
  void virFirewallRuleAddArg(virFirewallPtr firewall,
                             virFirewallRulePtr rule,
                             const char *arg)
  {
      VIR_FIREWALL_RULE_RETURN_IF_ERROR(firewall, rule);
  
      ADD_ARG(rule, arg);
  
      return;
  
   no_memory:
      firewall->err = ENOMEM;
  }
  
  
  void virFirewallRuleAddArgFormat(virFirewallPtr firewall,
                                   virFirewallRulePtr rule,
                                   const char *fmt, ...)
  {
      char *arg;
      va_list list;
  
      VIR_FIREWALL_RULE_RETURN_IF_ERROR(firewall, rule);
  
      va_start(list, fmt);
  
      if (virVasprintf(&arg, fmt, list) < 0)
          goto no_memory;
  
      ADD_ARG(rule, arg);
  
      va_end(list);
  
      VIR_FREE(arg);
      return;
  
   no_memory:
      firewall->err = ENOMEM;
      va_end(list);
      VIR_FREE(arg);
  }
  
  
  void virFirewallRuleAddArgSet(virFirewallPtr firewall,
                                virFirewallRulePtr rule,
                                const char *const *args)
  {
      VIR_FIREWALL_RULE_RETURN_IF_ERROR(firewall, rule);
  
      while (*args) {
          ADD_ARG(rule, *args);
          args++;
      }
  
      return;
  
   no_memory:
      firewall->err = ENOMEM;
  }
  
  
  void virFirewallRuleAddArgList(virFirewallPtr firewall,
                                 virFirewallRulePtr rule,
                                 ...)
  {
      va_list list;
      const char *str;
  
      VIR_FIREWALL_RULE_RETURN_IF_ERROR(firewall, rule);
  
      va_start(list, rule);
  
      while ((str = va_arg(list, char *)) != NULL)
          ADD_ARG(rule, str);
  
      va_end(list);
  
      return;
  
   no_memory:
      firewall->err = ENOMEM;
      va_end(list);
  }
  
  
  size_t virFirewallRuleGetArgCount(virFirewallRulePtr rule)
  {
      if (!rule)
          return 0;
      return rule->argsLen;
  }
  
  
  /**
   * virFirewallStartTransaction:
   * @firewall: the firewall ruleset
   * @flags: bitset of virFirewallTransactionFlags
   *
   * Start a new transaction with associated rollback
   * block.
   *
   * Should be followed by calls to add various rules to
   * the transaction. Then virFirwallStartRollback should
   * be used to provide rules to rollback upon transaction
   * failure
   */
  void virFirewallStartTransaction(virFirewallPtr firewall,
                                   unsigned int flags)
  {
      virFirewallGroupPtr group;
  
      VIR_FIREWALL_RETURN_IF_ERROR(firewall);
  
      if (!(group = virFirewallGroupNew())) {
          firewall->err = ENOMEM;
          return;
      }
      group->actionFlags = flags;
  
      if (VIR_EXPAND_N(firewall->groups,
                       firewall->ngroups, 1) < 0) {
          firewall->err = ENOMEM;
          virFirewallGroupFree(group);
          return;
      }
      firewall->groups[firewall->ngroups - 1] = group;
      firewall->currentGroup = firewall->ngroups - 1;
  }
  
  /**
   * virFirewallBeginRollback:
   * @firewall: the firewall ruleset
   * @flags: bitset of virFirewallRollbackFlags
   *
   * Mark the beginning of a set of rules able to rollback
   * changes in this and all earlier transactions.
   *
   * Should be followed by calls to add various rules needed
   * to rollback state. Then virFirewallStartTransaction
   * should be used to indicate the beginning of the next
   * transactional ruleset.
   */
  void virFirewallStartRollback(virFirewallPtr firewall,
                                unsigned int flags)
  {
      virFirewallGroupPtr group;
  
      VIR_FIREWALL_RETURN_IF_ERROR(firewall);
  
      if (firewall->ngroups == 0) {
          firewall->err = EINVAL;
          return;
      }
  
      group = firewall->groups[firewall->ngroups-1];
      group->rollbackFlags = flags;
      group->addingRollback = true;
  }
  
  
  static char *
  virFirewallRuleToString(virFirewallRulePtr rule)
  {
      const char *bin = virFirewallLayerCommandTypeToString(rule->layer);
      virBuffer buf = VIR_BUFFER_INITIALIZER;
      size_t i;
  
      virBufferAdd(&buf, bin, -1);
      for (i = 0; i < rule->argsLen; i++) {
          virBufferAddLit(&buf, " ");
          virBufferAdd(&buf, rule->args[i], -1);
      }
  
      return virBufferContentAndReset(&buf);
  }
  
  static int
  virFirewallApplyRuleDirect(virFirewallRulePtr rule,
                             bool ignoreErrors,
                             char **output)
  {
      size_t i;
      const char *bin = virFirewallLayerCommandTypeToString(rule->layer);
      virCommandPtr cmd = NULL;
      int status;
      int ret = -1;
      char *error = NULL;
  
      if (!bin) {
          virReportError(VIR_ERR_INTERNAL_ERROR,
                         _("Unknown firewall layer %d"),
                         rule->layer);
          goto cleanup;
      }
  
      cmd = virCommandNewArgList(bin, NULL);
  
      for (i = 0; i < rule->argsLen; i++)
          virCommandAddArg(cmd, rule->args[i]);
  
      virCommandSetOutputBuffer(cmd, output);
      virCommandSetErrorBuffer(cmd, &error);
  
      if (virCommandRun(cmd, &status) < 0)
          goto cleanup;
  
      if (status != 0) {
          if (ignoreErrors) {
              VIR_DEBUG("Ignoring error running command");
          } else {
              char *args = virCommandToString(cmd);
              virReportError(VIR_ERR_INTERNAL_ERROR,
                             _("Failed to apply firewall rules %s: %s"),
                             NULLSTR(args), NULLSTR(error));
              VIR_FREE(args);
              VIR_FREE(*output);
              goto cleanup;
          }
      }
  
      ret = 0;
   cleanup:
      VIR_FREE(error);
      virCommandFree(cmd);
      return ret;
  }
  
  
  static int
  virFirewallApplyRuleFirewallD(virFirewallRulePtr rule,
                                bool ignoreErrors,
                                char **output)
  {
      const char *ipv = virFirewallLayerFirewallDTypeToString(rule->layer);
      DBusConnection *sysbus = virDBusGetSystemBus();
      DBusMessage *reply = NULL;
      virError error;
      int ret = -1;
  
      if (!sysbus)
          return -1;
  
      memset(&error, 0, sizeof(error));
  
      if (!ipv) {
          virReportError(VIR_ERR_INTERNAL_ERROR,
                         _("Unknown firewall layer %d"),
                         rule->layer);
          goto cleanup;
      }
  
      if (virDBusCallMethod(sysbus,
                            &reply,
                            &error,
                            VIR_FIREWALL_FIREWALLD_SERVICE,
                            "/org/fedoraproject/FirewallD1",
                            "org.fedoraproject.FirewallD1.direct",
                            "passthrough",
                            "sa&s",
                            ipv,
                            (int)rule->argsLen,
                            rule->args) < 0)
          goto cleanup;
  
      if (error.level == VIR_ERR_ERROR) {
          /*
           * As of firewalld-0.3.9.3-1.fc20.noarch the name and
           * message fields in the error look like
           *
           *    name="org.freedesktop.DBus.Python.dbus.exceptions.DBusException"
           * message="COMMAND_FAILED: '/sbin/iptables --table filter --delete
           *          INPUT --in-interface virbr0 --protocol udp --destination-port 53
           *          --jump ACCEPT' failed: iptables: Bad rule (does a matching rule
           *          exist in that chain?)."
           *
           * We'd like to only ignore DBus errors precisely related to the failure
           * of iptables/ebtables commands. A well designed DBus interface would
           * return specific named exceptions not the top level generic python dbus
           * exception name. With this current scheme our only option is todo a
           * sub-string match for 'COMMAND_FAILED' on the message. eg like
           *
           * if (ignoreErrors &&
           *     STREQ(error.name,
           *           "org.freedesktop.DBus.Python.dbus.exceptions.DBusException") &&
           *     STRPREFIX(error.message, "COMMAND_FAILED"))
           *    ...
           *
           * But this risks our error detecting code being broken if firewalld changes
           * ever alter the message string, so we're avoiding doing that.
           */
          if (ignoreErrors) {
              VIR_DEBUG("Ignoring error '%s': '%s'",
                        error.str1, error.message);
          } else {
              virReportErrorObject(&error);
              goto cleanup;
          }
      } else {
          if (virDBusMessageRead(reply, "s", output) < 0)
              goto cleanup;
      }
  
      ret = 0;
  
   cleanup:
      virResetError(&error);
      if (reply)
          dbus_message_unref(reply);
      return ret;
  }
  
  static int
  virFirewallApplyRule(virFirewallPtr firewall,
                       virFirewallRulePtr rule,
                       bool ignoreErrors)
  {
      char *output = NULL;
      char **lines = NULL;
      int ret = -1;
      char *str = virFirewallRuleToString(rule);
      VIR_INFO("Applying rule '%s'", NULLSTR(str));
      VIR_FREE(str);
  
      if (rule->ignoreErrors)
          ignoreErrors = rule->ignoreErrors;
  
      switch (currentBackend) {
      case VIR_FIREWALL_BACKEND_DIRECT:
          if (virFirewallApplyRuleDirect(rule, ignoreErrors, &output) < 0)
              return -1;
          break;
      case VIR_FIREWALL_BACKEND_FIREWALLD:
          if (virFirewallApplyRuleFirewallD(rule, ignoreErrors, &output) < 0)
              return -1;
          break;
      default:
          virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                         _("Unexpected firewall engine backend"));
          return -1;
      }
  
      if (rule->queryCB && output) {
          if (!(lines = virStringSplit(output, "\n", -1)))
              goto cleanup;
  
          VIR_DEBUG("Invoking query %p with '%s'", rule->queryCB, output);
          if (rule->queryCB(firewall, (const char *const *)lines, rule->queryOpaque) < 0)
              goto cleanup;
  
          if (firewall->err == ENOMEM) {
              virReportOOMError();
              goto cleanup;
          }
          if (firewall->err) {
              virReportSystemError(firewall->err, "%s",
                                   _("Unable to create rule"));
              goto cleanup;
          }
  
      }
  
      ret = 0;
   cleanup:
      virStringFreeList(lines);
      VIR_FREE(output);
      return ret;
  }
  
  static int
  virFirewallApplyGroup(virFirewallPtr firewall,
                        size_t idx)
  {
      virFirewallGroupPtr group = firewall->groups[idx];
      bool ignoreErrors = (group->actionFlags & VIR_FIREWALL_TRANSACTION_IGNORE_ERRORS);
      size_t i;
  
      VIR_INFO("Starting transaction for firewall=%p group=%p flags=%x",
               firewall, group, group->actionFlags);
      firewall->currentGroup = idx;
      group->addingRollback = false;
      for (i = 0; i < group->naction; i++) {
          if (virFirewallApplyRule(firewall,
                                   group->action[i],
                                   ignoreErrors) < 0)
              return -1;
      }
      return 0;
  }
  
  
  static void
  virFirewallRollbackGroup(virFirewallPtr firewall,
                           size_t idx)
  {
      virFirewallGroupPtr group = firewall->groups[idx];
      size_t i;
  
      VIR_INFO("Starting rollback for group %p", group);
      firewall->currentGroup = idx;
      group->addingRollback = true;
      for (i = 0; i < group->nrollback; i++) {
          ignore_value(virFirewallApplyRule(firewall,
                                            group->rollback[i],
                                            true));
      }
  }
  
  
  int
  virFirewallApply(virFirewallPtr firewall)
  {
      size_t i, j;
      int ret = -1;
  
      virMutexLock(&ruleLock);
  
      if (!firewall || firewall->err == ENOMEM) {
          virReportOOMError();
          goto cleanup;
      }
      if (firewall->err) {
          virReportSystemError(firewall->err, "%s",
                               _("Unable to create rule"));
          goto cleanup;
      }
  
      VIR_DEBUG("Applying groups for %p", firewall);
      for (i = 0; i < firewall->ngroups; i++) {
          if (virFirewallApplyGroup(firewall, i) < 0) {
              VIR_DEBUG("Rolling back groups upto %zu for %p", i, firewall);
              size_t first = i;
              virErrorPtr saved_error = virSaveLastError();
  
              /*
               * Look at any inheritance markers to figure out
               * what the first rollback group we need to apply is
               */
              for (j = 0; j < i; j++) {
                  VIR_DEBUG("Checking inheritance of group %zu", i - j);
                  if (firewall->groups[i - j]->rollbackFlags &
                      VIR_FIREWALL_ROLLBACK_INHERIT_PREVIOUS)
                      first = (i - j) - 1;
              }
              /*
               * Now apply all rollback groups in order
               */
              for (j = first; j <= i; j++) {
                  VIR_DEBUG("Rolling back group %zu", j);
                  virFirewallRollbackGroup(firewall, j);
              }
  
              virSetError(saved_error);
              virFreeError(saved_error);
              VIR_DEBUG("Done rolling back groups for %p", firewall);
              goto cleanup;
          }
      }
      VIR_DEBUG("Done applying groups for %p", firewall);
  
      ret = 0;
   cleanup:
      virMutexUnlock(&ruleLock);
      return ret;
  }
  